Compare commits

..

122 Commits

Author SHA1 Message Date
Harshavardhana 92788e4cf4 fix: re-arrange console-sys to log properly in k8s/docker (#19129)
fixes #19125
2024-02-26 01:33:48 -08:00
Harshavardhana 8a698fef71 fix: crash in ResourceMetrics RPC handling concurrent writers (#19123)
Continuation of #19103 that had fixed the crash in peer metrics for cluster endpoint.
2024-02-25 00:51:38 -08:00
Minio Trusted b49ce1713f Update yaml files to latest version RELEASE.2024-02-24T17-11-14Z 2024-02-25 05:23:21 +00:00
Harshavardhana c2b54d92f6 allow all disk full errors to be handled (#19117) 2024-02-24 09:11:14 -08:00
Harshavardhana f965434022 fix: re-use endpoint strings to avoid allocation during audit (#19116) 2024-02-23 16:19:13 -08:00
Harshavardhana a3ac62596c move timedValue -> cachevalue package (#19114) 2024-02-23 13:28:14 -08:00
Harshavardhana 2faba02d6b fix: allow diskInfo at storageRPC to be cached (#19112)
Bonus: convert timedValue into a typed implementation
2024-02-23 09:21:38 -08:00
Krishnan Parthasarathi ee158e1610 ilm: Update action count only on success (#19093)
It also fixes a long-standing bug in expiring transitioned objects.
The expiration action was deleting the current version in the case'
of tiered objects instead of adding a delete marker.
2024-02-22 15:00:32 -08:00
Anis Eleuch fa68efb1e7 s3: CopyObject to disallow invalid dest object names (#19110)
By not doing so, objects can risk being in a wrong erasure set if the
destination object name contains e.g. '//'
2024-02-22 10:05:17 -08:00
Anis Eleuch 8c53a4405a Add audit for folder excess (#19109)
Also replace ilm:expiry with scanner to avoid user confusion
2024-02-22 08:18:13 -08:00
Harshavardhana c32f699105 turn-off md5sum for SSE-KMS/SSE-C as optimization for multipart (#19106)
only enable md5sum if explicitly asked by the client, otherwise
its not necessary to compute md5sum when SSE-KMS/SSE-C is enabled.

this is continuation of #17958
2024-02-22 04:24:11 -08:00
Harshavardhana 53aa8f5650 use typos instead of codespell (#19088) 2024-02-21 22:26:06 -08:00
Shubhendu 56887f3208 Add DeleteAll with expiry days non zero value only (#19095)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-02-21 12:28:34 -08:00
Klaus Post 92180bc793 Add array recycling safety (#19103)
Nil entries when recycling arrays.
2024-02-21 12:27:35 -08:00
Klaus Post 22aa16ab12 Fix grid reconnection deadlock (#19101)
If network conditions have filled the output queue before a reconnect happens blocked sends could stop reconnects from happening. In short `respMu` would be held for a mux client while sending - if the queue is full this will never get released and closing the mux client will hang.

A) Use the mux client context instead of connection context for sends, so sends are unblocked when the mux client is canceled.

B) Use a `TryLock` on "close" and cancel the request if we cannot get the lock at once. This will unblock any attempts to send.
2024-02-21 07:49:34 -08:00
Poorna 526b829a09 site replication: Disallow removal of site-replicator account (#19092) 2024-02-21 02:09:33 -08:00
schmittey c44f311c4f Add missing yaml syntax highlighting in prometheus README.md (#19087) 2024-02-20 16:22:37 -08:00
Anis Eleuch 9ea5d08ecd site-repl: Fix endpoint in the error with unexpected deployment-id (#19086) 2024-02-20 15:02:35 -08:00
Harshavardhana 35deb1a8e2 do not block on send channels under high load (#19090)
all send channels must compete with `ctx` if not
they will perpetually stay alive.
2024-02-20 15:00:35 -08:00
Harshavardhana c7f7c47388 allow renames() for inlined writes without data-dir (#18801)
data-dir not being present is okay, however we can still
rely on the `rename()` atomic call instead of relying on
write xl.meta write which may truncate the io.EOF.
2024-02-20 07:05:57 -08:00
Shubhendu cb7dab17cb Graph cluster and bucket replication proxied requests (#19078)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-02-20 01:45:00 -08:00
Harshavardhana cd419a35fe simplify broker healthcheck by following kafka guidelines (#19082)
fixes #19081
2024-02-20 00:16:35 -08:00
Klaus Post e06168596f Convert more peer <--> peer REST calls (#19004)
* Convert more peer <--> peer REST calls
* Clean up in general.
* Add JSON wrapper.
* Add slice wrapper.
* Add option to make handler return nil error if no connection is given, `IgnoreNilConn`.

Converts the following:

```
+	HandlerGetMetrics
+	HandlerGetResourceMetrics
+	HandlerGetMemInfo
+	HandlerGetProcInfo
+	HandlerGetOSInfo
+	HandlerGetPartitions
+	HandlerGetNetInfo
+	HandlerGetCPUs
+	HandlerServerInfo
+	HandlerGetSysConfig
+	HandlerGetSysServices
+	HandlerGetSysErrors
+	HandlerGetAllBucketStats
+	HandlerGetBucketStats
+	HandlerGetSRMetrics
+	HandlerGetPeerMetrics
+	HandlerGetMetacacheListing
+	HandlerUpdateMetacacheListing
+	HandlerGetPeerBucketMetrics
+	HandlerStorageInfo
+	HandlerGetLocks
+	HandlerBackgroundHealStatus
+	HandlerGetLastDayTierStats
+	HandlerSignalService
+	HandlerGetBandwidth
```
2024-02-19 14:54:46 -08:00
Harshavardhana 4c8197a119 reject expired STS credentials early without decoding sessionToken (#19072) 2024-02-19 07:34:10 -08:00
Minio Trusted 23c10350f3 Update yaml files to latest version RELEASE.2024-02-17T01-15-57Z 2024-02-17 01:37:10 +00:00
Harshavardhana b6e98aed01 fix: found races in accessing globalLocalDrives (#19069)
make a copy before accessing globalLocalDrives

Bonus: update console v0.46.0

Signed-off-by: Harshavardhana <harsha@minio.io>
2024-02-16 17:15:57 -08:00
Anis Eleuch 00dcba9ddd Fix typo in jwt skewed date/time error (#19066) 2024-02-16 10:48:30 -08:00
Harshavardhana 607cafadbc converge clusterRead health into cluster health (#19063) 2024-02-15 16:48:36 -08:00
Anis Eleuch 68dde2359f log: Add logger.Event to send to console and other logger targets (#19060)
Add a new function logger.Event() to send the log to Console and
http/kafka log webhooks. This will include some internal events such as
disk healing and rebalance/decommissioning
2024-02-15 15:13:30 -08:00
Poorna f9dbf41e27 sr: add validation to disallow updating bandwidth limit on self (#19062) 2024-02-15 13:03:40 -08:00
Krishnan Parthasarathi 7405760f44 Refresh tier config periodically (#19049)
- Increase the parity for tier-config.bin object
- Refresh globalTierConfigMgr cached value once every 15 mins
2024-02-15 11:52:44 -08:00
Harshavardhana 7e4a6b4bcd remove rename2 entirely, avoids the risk of moving data (#19058) 2024-02-14 17:09:38 -08:00
Minio Trusted b5791e6f28 Update yaml files to latest version RELEASE.2024-02-14T21-36-02Z 2024-02-14 21:51:12 +00:00
Harshavardhana 00cb58eaf3 add customer specific hotfixes to 'registry.min.dev' (#19057)
```
REPO="registry.min.dev/<customer>" CRED_DIR=/media/builder/minio make docker-hotfix-push
```
2024-02-14 13:36:02 -08:00
Harshavardhana f961ec4aaf fix: revert allow offline disks on fresh start (#19052)
the PR in #16541 was incorrect and hand wrong assumptions
about the overall setup, revert this since this expectation
to have offline servers is wrong and we can end up with a
bigger chicken and egg problem.

This reverts commit 5996c8c4d5.

Bonus:

- preserve disk in globalLocalDrives properly upon connectDisks()
- do not return 'nil' from newXLStorage(), getting it ready for
  the next set of changes for 'format.json' loading.
2024-02-14 10:37:34 -08:00
Harshavardhana 134db72bb7 fix: reject service account access key same as root credentials (#19055) 2024-02-14 10:37:12 -08:00
Harshavardhana 6fd0b434e2 upgrade all deps (#19041) 2024-02-14 09:51:34 -08:00
Harshavardhana effe21f3eb send correct objectname in audit events for DeleteAll ILM (#19053) 2024-02-14 08:07:58 -08:00
Praveen raj Mani 1118b285d3 fix: race in deleting objects during batch expiry (#19054) 2024-02-14 08:07:44 -08:00
Poorna 912a0031b7 fix sr tests to capture all server logs (#19051) 2024-02-13 20:51:23 -08:00
Aditya Manthramurthy a14e192376 fix: remove unnecessary panic in iam-store (#19050) 2024-02-13 19:29:36 -08:00
Minio Trusted f8e15e7d09 Update yaml files to latest version RELEASE.2024-02-13T15-35-11Z 2024-02-13 16:01:38 +00:00
Shireesh Anjal 7b9f9e0628 fix incorrect disk io stats in k8s environment (#19016)
The previous logic of calculating per second values for disk io stats
divides the stats by the host uptime. This doesn't work in k8s
environment as the uptime is of the pod, but the stats (from
/proc/diskstats) are from the host.

Fix this by storing the initial values of uptime and the stats at the
timme of server startup, and using the difference between current and
initial values when calculating the per second values.
2024-02-13 07:35:11 -08:00
Praveen raj Mani ac8e9ce04f Send a bucket notification event on DeleteObject() for non-existing object (#19037)
Send a bucket notification event on DeleteObject for non-existing objects
2024-02-13 07:34:17 -08:00
Praveen raj Mani cfd8645843 fix: update batch replication stats for snowball uploads (#19045) 2024-02-13 07:33:27 -08:00
Harshavardhana 0c068b15c7 add missing handler for reloading site replication config on peers (#19042) 2024-02-13 06:55:54 -08:00
Anis Eleuch 30a466aa71 sts: Add test for DurationSeconds condition (#19044) 2024-02-13 06:55:37 -08:00
Taran Pelkey 4d94609c44 FIx unexpected behavior when creating service account (#19036) 2024-02-13 02:31:43 -08:00
Minio Trusted 6b63123ca9 Update yaml files to latest version RELEASE.2024-02-12T21-02-27Z 2024-02-12 21:40:49 +00:00
Poorna 0cc9fb73e1 metrics: fix typo in namespace for proxy tagging metric (#19039)
Relevant PR introducing this metric: #18957
2024-02-12 13:02:27 -08:00
Harshavardhana eac4e4b279 honor replaced disk properly by updating globalLocalDrives (#19038)
globalLocalDrives seem to be not updated during the
HealFormat() leads to a requirement where the server
needs to be restarted for the healing to continue.
2024-02-12 13:00:20 -08:00
Harshavardhana 6d381f7c0a relax pre-emptive GetBucketInfo() for multi-object delete (#19035) 2024-02-12 08:46:46 -08:00
Anis Eleuch 4fa06aefc6 Convert service account add/update expiration to cond values (#19024)
In order to force some users allowed to create or update a service
account to provide an expiration satifying the user policy conditions.
2024-02-12 08:36:16 -08:00
Harshavardhana 0e177a44e0 preserve conflicting objects when parent object is being deleted (#19034)
a/prefix
a/prefix/1.txt

where `a/prefix` is an object which does not have `/` at the end,
we do not have to aggressively recursively delete all the sub-folders
as well. Instead convert the call into self contained to deleting
'xl.meta' and then subsequently attempting to Remove the parent.
2024-02-12 08:30:40 -08:00
Harshavardhana afd19de5a9 fix: allow configuring excess versions alerting (#19028)
Bonus: enable audit alerts for object versions
beyond the configured value, default is '100'
versions per object beyond which scanner will
alert for each such objects.
2024-02-11 23:41:53 -08:00
Harshavardhana e3fbac9e24 do not have to use the same distributionAlgo as first pool (#19031)
when we expand via pools, there is no reason to stick
with the same distributionAlgo as the rest. Since the
algo only makes sense with-in a pool not across pools.

This allows for newer pools to use newer codepaths to
avoid legacy file lookups when they have a pre-existing
deployment from 2019, they can expand their new pool
to be of a newer distribution format, allowing the
pool to be more performant.
2024-02-11 23:21:56 -08:00
Poorna a9cf32811c Fix panic in tagging request proxying (#19032) 2024-02-11 18:18:43 -08:00
Harshavardhana 53997ecc79 avoid excessive logging for objects that do not exist (#19030)
in replicated setups, that have proxying enabled for
replicated buckets.
2024-02-11 14:21:08 -08:00
Minio Trusted 8e69f3cb89 Update yaml files to latest version RELEASE.2024-02-09T21-25-16Z 2024-02-09 22:48:07 +00:00
Harshavardhana 997ba3a574 introduce reader deadlines for net.Conn (#19023)
Bonus: set "retry-after" header for AWS SDKs if possible to honor them.
2024-02-09 13:25:16 -08:00
Klaus Post 8e68ff9321 Add extra disconnect safety (#19022)
Fix reported races that are actually synchronized by network calls.

But this should add some extra safety for untimely disconnects.

Race reported:

```
WARNING: DATA RACE
Read at 0x00c00171c9c0 by goroutine 214:
  github.com/minio/minio/internal/grid.(*muxClient).addResponse()
      e:/gopath/src/github.com/minio/minio/internal/grid/muxclient.go:519 +0x111
  github.com/minio/minio/internal/grid.(*muxClient).error()
      e:/gopath/src/github.com/minio/minio/internal/grid/muxclient.go:470 +0x21d
  github.com/minio/minio/internal/grid.(*Connection).handleDisconnectClientMux()
      e:/gopath/src/github.com/minio/minio/internal/grid/connection.go:1391 +0x15b
  github.com/minio/minio/internal/grid.(*Connection).handleMsg()
      e:/gopath/src/github.com/minio/minio/internal/grid/connection.go:1190 +0x1ab
  github.com/minio/minio/internal/grid.(*Connection).handleMessages.func1()
      e:/gopath/src/github.com/minio/minio/internal/grid/connection.go:981 +0x610

Previous write at 0x00c00171c9c0 by goroutine 1081:
  github.com/minio/minio/internal/grid.(*muxClient).roundtrip()
      e:/gopath/src/github.com/minio/minio/internal/grid/muxclient.go:94 +0x324
  github.com/minio/minio/internal/grid.(*muxClient).traceRoundtrip()
      e:/gopath/src/github.com/minio/minio/internal/grid/trace.go:74 +0x10e4
  github.com/minio/minio/internal/grid.(*Subroute).Request()
      e:/gopath/src/github.com/minio/minio/internal/grid/connection.go:366 +0x230
  github.com/minio/minio/internal/grid.(*SingleHandler[go.shape.*github.com/minio/minio/cmd.DiskInfoOptions,go.shape.*github.com/minio/minio/cmd.DiskInfo]).Call()
      e:/gopath/src/github.com/minio/minio/internal/grid/handlers.go:554 +0x3fd
  github.com/minio/minio/cmd.(*storageRESTClient).DiskInfo()
      e:/gopath/src/github.com/minio/minio/cmd/storage-rest-client.go:314 +0x270
  github.com/minio/minio/cmd.erasureObjects.getOnlineDisksWithHealingAndInfo.func1()
      e:/gopath/src/github.com/minio/minio/cmd/erasure.go:293 +0x171
```

This read will always happen after the write, since there is a network call in between.

However a disconnect could come in while we are setting up the call, so we protect against that with extra checks.
2024-02-09 08:43:38 -08:00
Harshavardhana 62761a23e6 remove unnecessary metrics in 'mc admin info' output (#19020)
Reduce the amount of data transfer on large deployments
2024-02-08 19:28:46 -08:00
Harshavardhana 404d8b3084 fix: dangling objects honor parityBlocks instead of dataBlocks (#19019)
Bonus: do not recreate buckets if NoRecreate is asked.
2024-02-08 15:22:16 -08:00
Klaus Post 6005ad3d48 Fix shared top locks client (#19018)
`client` is shared across goroutines.

Seen with `mc support top locks` on minio built with `-race`.
2024-02-08 12:28:05 -08:00
Harshavardhana 035a3ea4ae optimize startup sequence performance (#19009)
- bucket metadata does not need to look for legacy things
  anymore if b.Created is non-zero

- stagger bucket metadata loads across lots of nodes to
  avoid the current thundering herd problem.

- Remove deadlines for RenameData, RenameFile - these
  calls should not ever be timed out and should wait
  until completion or wait for client timeout. Do not
  choose timeouts for applications during the WRITE phase.

- increase R/W buffer size, increase maxMergeMessages to 30
2024-02-08 11:21:21 -08:00
Klaus Post 7ec43bd177 Fix blocked streams blocking reconnects (#19017)
We have observed cases where a blocked stream will block for cancellations.

This happens when response channel is blocked and we want to push an error.
This will have the response mutex locked, which will prevent all other operations until upstream is unblocked.

Make this behavior non-blocking and if blocked spawn a goroutine that will send the response and close the output.

Still a lot of "dancing". Added a test for this and reviewed.
2024-02-08 10:15:27 -08:00
Aditya Manthramurthy a29c66ed74 Update IAM access manager plugin demo (#19007)
Now prints the JSON payload for easier debugging.
2024-02-08 09:15:20 -08:00
Aditya Manthramurthy e104b183d8 fix: skip policy usage validation for cache update (#19008)
When updating the policy cache, we do not need to validate policy usage
as the policy has already been deleted by the node sending the
notification.
2024-02-07 20:39:53 -08:00
Klaus Post 7e082f232e Add GetBucketInfo toStorageErr conversion (#19005)
Convert error to storageError since it is used for quorum calculations here: https://github.com/minio/minio/blob/ff80cfd83dc874f65e4436cf021186f04d44557e/cmd/peer-s3-client.go#L339
2024-02-07 14:24:24 -08:00
Harshavardhana d28bf71f25 listing must return WalkDir() errors first (#19006) 2024-02-07 13:20:07 -08:00
Harshavardhana 5b1a74b6b2 do not block iam.store registration (#18999)
current implementation would quite simply
block the sys.store registration, making
sys.Initialized() call to be blocked.
2024-02-07 12:41:58 -08:00
Minio Trusted eead4db1d2 Update yaml files to latest version RELEASE.2024-02-06T21-36-22Z 2024-02-07 12:11:36 +00:00
Shubhendu 980fb5e2ab Enable expired-object-all-versions (#18954)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-02-06 13:36:22 -08:00
Klaus Post 9bcc46d93d Fix second muxclient context leak (#18987)
Subrouted requests were also leaking contexts in mux clients.

Similar to #18956
2024-02-06 13:35:16 -08:00
Klaus Post 22687c1f50 Add websocket TCP write timeouts (#18988)
Add 3 second write timeout to writes.

This will make dead TCP connections terminate in a reasonable time.

Fixes writes blocking for reconnection.
2024-02-06 13:34:46 -08:00
Klaus Post ebc6c9b498 Fix tracing send on closed channel (#18982)
Depending on when the context cancelation is picked up the handler may return and close the channel before `SubscribeJSON` returns, causing:

```
Feb 05 17:12:00 s3-us-node11 minio[3973657]: panic: send on closed channel
Feb 05 17:12:00 s3-us-node11 minio[3973657]: goroutine 378007076 [running]:
Feb 05 17:12:00 s3-us-node11 minio[3973657]: github.com/minio/minio/internal/pubsub.(*PubSub[...]).SubscribeJSON.func1()
Feb 05 17:12:00 s3-us-node11 minio[3973657]:         github.com/minio/minio/internal/pubsub/pubsub.go:139 +0x12d
Feb 05 17:12:00 s3-us-node11 minio[3973657]: created by github.com/minio/minio/internal/pubsub.(*PubSub[...]).SubscribeJSON in goroutine 378010884
Feb 05 17:12:00 s3-us-node11 minio[3973657]:         github.com/minio/minio/internal/pubsub/pubsub.go:124 +0x352
```

Wait explicitly for the goroutine to exit.

Bonus: Listen for doneCh when sending to not risk getting blocked there is channel isn't being emptied.
2024-02-06 08:57:30 -08:00
Harshavardhana 630963fa6b protect tracker copy properly to avoid race (#18984)
```
WARNING: DATA RACE
Write at 0x00c000aac1e0 by goroutine 1133:
  github.com/minio/minio/cmd.(*healingTracker).updateProgress()
      github.com/minio/minio/cmd/background-newdisks-heal-ops.go:183 +0x117
  github.com/minio/minio/cmd.(*erasureObjects).healErasureSet.func5()
      github.com/minio/minio/cmd/global-heal.go:292 +0x1d3

Previous read at 0x00c000aac1e0 by goroutine 1003:
  github.com/minio/minio/cmd.(*allHealState).updateHealStatus()
      github.com/minio/minio/cmd/admin-heal-ops.go:136 +0xcb
  github.com/minio/minio/cmd.(*healingTracker).save()
      github.com/minio/minio/cmd/background-newdisks-heal-ops.go:223 +0x424
```
2024-02-06 08:56:59 -08:00
Harshavardhana f674168b8b Add missing gob register for map[string]string{} (#18974)
```
minio[1303918]: API: SYSTEM()
minio[1303918]: Time: 02:04:28 UTC 02/05/2024
minio[1303918]: DeploymentID: 0972de33-2d17-4499-8967-aff6437dd9da
minio[1303918]: Error: gob: type not registered for interface: map[string]string (*errors.errorString)
minio[1303918]:        4: internal/logger/logonce.go:118:logger.(*logOnceType).logOnceIf()
minio[1303918]:        3: internal/logger/logonce.go:149:logger.LogOnceIf()
minio[1303918]:        2: cmd/peer-rest-server.go:533:cmd.(*peerRESTServer).GetSysConfigHandler()
minio[1303918]:        1: net/http/server.go:2136:http.HandlerFunc.ServeHTTP()
```
2024-02-06 08:23:23 -08:00
Harshavardhana 7e023f2d50 remove go.mod replace tag 2024-02-06 01:56:57 -08:00
Poorna 27d02ea6f7 metrics: add replication metrics on proxied requests (#18957) 2024-02-05 22:00:45 -08:00
Harshavardhana 794a7993cb calculate correct quorum check for metadata updates on object (#18979)
this fixes rare bugs we have seen but never really found a
reproducer for

- PutObjectRetention() returning 503s
- PutObjectTags() returning 503s
- PutObjectMetadata() updates during replication returning 503s

These calls return errors, and this perpetuates with
no apparent fix.

This PR fixes with correct quorum requirement.
2024-02-05 21:44:40 -08:00
Harshavardhana 6f16d1cb2c do not count context canceled as timeout errors (#18975) 2024-02-05 18:16:13 -08:00
Anis Eleuch 7aa00bff89 sts: Add support of AssumeRoleWithWebIdentity and DurationSeconds (#18835)
To force limit the duration of STS accounts, the user can create a new
policy, like the following:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["sts:AssumeRoleWithWebIdentity"],
    "Condition": {"NumericLessThanEquals": {"sts:DurationSeconds": "300"}}
  }]
}

And force binding the policy to all OpenID users, whether using a claim name or role
ARN.
2024-02-05 11:44:23 -08:00
Klaus Post e046eb1d17 Disable Rename2 metrics on non-linux (#18970)
Logging a call that always fails is pointless.
2024-02-05 10:48:14 -08:00
Anis Eleuch ba975ca320 Add defensive code to ignore checking parts with transitioned objects (#18973)
Though dataErrs are nil with transitioned objects, add a more defensive
code to ignore counting missing parts in that case
2024-02-05 10:48:03 -08:00
Harshavardhana fec13b0ec1 remove unused DiskMTime (#18965) 2024-02-05 01:04:26 -08:00
Harshavardhana 100c35c281 avoid excessive logs when peer is down (#18969) 2024-02-04 23:25:42 -08:00
Minio Trusted 8414aff424 Update yaml files to latest version RELEASE.2024-02-04T22-36-13Z 2024-02-05 00:46:41 +00:00
Harshavardhana f225ca3312 Add more advanced cases for dangling (#18968) 2024-02-04 14:36:13 -08:00
Frank Wessels 8b68e0bfdc Fix typo in api-router.go (#18955) 2024-02-03 14:03:51 -08:00
Anis Eleuch 6ae97aedc9 xl: Disable rename2 in decommissioning/rebalance (#18964)
Always disable rename2 optimization in decom/rebalance
2024-02-03 14:03:30 -08:00
Harshavardhana 960d604013 disconnected returns, an unexpected error to List() returning 500s (#18959)
provide the error string appropriately so that the
matching of error types works.

Also add a string based fallback for the said error.
2024-02-03 01:04:33 -08:00
Klaus Post 63bf5f42a1 Fix mux client memory leak (#18956)
Add missing client cancellation, resulting in memory buildup tracing back to context.WithCancelCause/context.WithCancelDeadlineCause
2024-02-02 15:31:06 -08:00
Harshavardhana ff80cfd83d move Make,Delete,Head,Heal bucket calls to websockets (#18951) 2024-02-02 14:54:54 -08:00
Harshavardhana 99fde2ba85 deprecate disk tokens, instead rely on deadlines and active monitoring (#18947)
disk tokens usage is not necessary anymore with the implementation
of deadlines for storage calls and active monitoring of the drive
for I/O timeouts.

Functionality kicking off a bad drive is still supported, it's just that 
we do not have to serialize I/O in the manner tokens would do.
2024-02-02 10:10:54 -08:00
Klaus Post ce0cb913bc Fix ineffective recycling (#18952)
Recycle would always be called on the dummy value `any(newRT())` instead of the actual value given to the recycle function.

Caught by race tests, but mostly harmless, except for reduced perf.

Other minor cleanups. Introduced in #18940 (unreleased)
2024-02-02 08:48:12 -08:00
Harshavardhana d99d16e8c3 simplify deadlineWriter, re-use WithDeadline (#18948) 2024-02-02 03:02:31 -08:00
Frank Wessels 31743789dc Fix some leftover issues from PR 18936 (#18946) 2024-02-01 19:42:56 -08:00
Anis Eleuch 6fd63e920a log: Use error log type instead of Application/MinIO type (#18930)
* log: Use error log type instead of Application/MinIO type

Also bump github.com/shirou/gopsutil version to address cross
compilation issues.

* Apply suggestions from code review

Co-authored-by: Aditya Manthramurthy <donatello@users.noreply.github.com>

---------

Co-authored-by: Anis Eleuch <anis@min.io>
Co-authored-by: Harshavardhana <harsha@minio.io>
Co-authored-by: Aditya Manthramurthy <donatello@users.noreply.github.com>
2024-02-01 16:13:57 -08:00
Aditya Manthramurthy 59cc3e93d6 fix: null inline policy handling for access keys (#18945)
Interpret `null` inline policy for access keys as inheriting parent
policy. Since MinIO Console currently sends this value, we need to honor it
for now. A larger fix in Console and in the server are required.

Fixes #18939.
2024-02-01 14:45:03 -08:00
Anis Eleuch 61a4bb38cd batch: Fix a typo while validating smallerThan field (#18942) 2024-02-01 13:53:26 -08:00
Klaus Post b192bc348c Improve object reuse for grid messages (#18940)
Allow internal types to support a `Recycler` interface, which will allow for sharing of common types across handlers.

This means that all `grid.MSS` (and similar) objects are shared across in a common pool instead of a per-handler pool.

Add internal request reuse of internal types. Add for safe (pointerless) types explicitly.

Only log params for internal types. Doing Sprint(obj) is just a bit too messy.
2024-02-01 12:41:20 -08:00
Harshavardhana 6440d0fbf3 move a collection of peer APIs to websockets (#18936) 2024-02-01 10:47:20 -08:00
Minio Trusted ee0055b929 Update yaml files to latest version RELEASE.2024-01-31T20-20-33Z 2024-01-31 20:35:33 +00:00
Anis Eleuch 24ecc44bac Keep ServiceV1 admin stop/restart API and mark as deprecated (#18932) 2024-01-31 12:20:33 -08:00
Aditya Manthramurthy 0ae4915a93 fix: permission checks for editing access keys (#18928)
With this change, only a user with `UpdateServiceAccountAdminAction`
permission is able to edit access keys.

We would like to let a user edit their own access keys, however the
feature needs to be re-designed for better security and integration with
external systems like AD/LDAP and OpenID.

This change prevents privilege escalation via service accounts.
2024-01-31 10:56:45 -08:00
Frank Wessels 4cd777a5e0 Correct small typo in pubsub (#18923) 2024-01-31 01:01:53 -08:00
Aditya Manthramurthy 65028d4a35 Update service file version in makefile (#18925) 2024-01-31 01:01:30 -08:00
Harshavardhana caac9d216e remove all the frivolous logs, that may or may not be actionable (#18922)
for actionable, inspections we have `mc support inspect`

we do not need double logging, healing will report relevant
errors if any, in terms of quorum lost etc.
2024-01-30 18:11:45 -08:00
Harshavardhana 057192913c add total usable capacity, free and used to DataUsageInfo() (#18921) 2024-01-30 17:49:37 -08:00
Harshavardhana f25cbdf43c use all the available nr_requests for NVMe (#18920) 2024-01-30 14:10:06 -08:00
Klaus Post 6da4a9c7bb Improve tracing & notification scalability (#18903)
* Perform JSON encoding on remote machines and only forward byte slices.
* Migrate tracing & notification to WebSockets.
2024-01-30 12:49:02 -08:00
Harshavardhana 80ca120088 remove checkBucketExist check entirely to avoid fan-out calls (#18917)
Each Put, List, Multipart operations heavily rely on making
GetBucketInfo() call to verify if bucket exists or not on
a regular basis. This has a large performance cost when there
are tons of servers involved.

We did optimize this part by vectorizing the bucket calls,
however its not enough, beyond 100 nodes and this becomes
fairly visible in terms of performance.
2024-01-30 12:43:25 -08:00
Anis Eleuch a669946357 Add cgroup v2 support for memory limit (#18905) 2024-01-30 11:13:27 -08:00
Poorna 7ffc162ea8 exclude veeam virtual objects from replication (#18918)
Fixes: #18916
2024-01-30 10:43:58 -08:00
Poorna bcfd7fbbcf reuse transports for callhome and remote tgt validation (#18912) 2024-01-29 23:05:39 -08:00
Harshavardhana 486e2e48ea enable xattr capture by default (#18911)
- healing must not set the write xattr
  because that is the job of active healing
  to update. what we need to preserve is
  permanent deletes.

- remove older env for drive monitoring and
  enable it accordingly, as a global value.
2024-01-29 23:03:58 -08:00
Harshavardhana 2ddf2ca934 allow configuring maximum idle connections per host (#18908) 2024-01-29 16:50:37 -08:00
Daniel Valdivia 403ec7cf21 fix: metrics URI path in prometheus docs (#18907) 2024-01-29 14:34:21 -08:00
Poorna 29b1a29044 fix metrics panic in node metrics endpoint (#18894) 2024-01-29 12:32:44 -08:00
jiuker b4ab8e095a fix: preserve bucket metric of data usage for replication info (#18895) 2024-01-29 08:54:20 -08:00
Minio Trusted ff4f4d4649 Update yaml files to latest version RELEASE.2024-01-29T03-56-32Z 2024-01-29 05:33:23 +00:00
229 changed files with 12476 additions and 4530 deletions
-8
View File
@@ -1,8 +0,0 @@
[codespell]
# certs_test.go - has lots of ceritificates.
skip = go.mod,go.sum,*.txt,LICENSE,*.zip,.git,*.pdf,*.svg,.codespellrc,CREDITS,certs_test.go
check-hidden = true
ignore-regex = \b(newfolder/afile|filterIn|HelpES)\b
ignore-words-list = inout,bui,to,bu,te,ot,toi,ist,parms,flate
-20
View File
@@ -1,20 +0,0 @@
---
name: Codespell
on:
pull_request:
branches: [master]
permissions:
contents: read
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Codespell
uses: codespell-project/actions-codespell@v2
+2 -2
View File
@@ -25,8 +25,8 @@ if [ ! -f ./mc ]; then
fi
(
cd /tmp
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
cd ./docs/debugging/s3-check-md5
go install -v
)
export RELEASE=RELEASE.2023-08-29T23-07-35Z
+15
View File
@@ -0,0 +1,15 @@
---
name: Test GitHub Action
on: [pull_request]
jobs:
run:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
- name: Check spelling of repo
uses: crate-ci/typos@master
+31
View File
@@ -0,0 +1,31 @@
[files]
extend-exclude = [
".git/",
"docs/",
]
ignore-hidden = false
[default]
extend-ignore-re = [
"Patrick Collison",
"Copyright 2014 Unknwon",
"[0-9A-Za-z/+=]{64}",
"ZXJuZXQxDjAMBgNVBA-some-junk-Q4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF",
"eyJmb28iOiJiYXIifQ",
'http\.Header\{"X-Amz-Server-Side-Encryptio":',
'sessionToken',
]
[default.extend-words]
"encrypter" = "encrypter"
"requestor" = "requestor"
[default.extend-identifiers]
"bui" = "bui"
"toi" = "toi"
"ot" = "ot"
"dm2nd" = "dm2nd"
"HashiCorp" = "HashiCorp"
"ParseND" = "ParseND"
"ParseNDStream" = "ParseNDStream"
"TestGetPartialObjectMisAligned" = "TestGetPartialObjectMisAligned"
-207
View File
@@ -16432,213 +16432,6 @@ https://github.com/matttproud/golang_protobuf_extensions
================================================================
github.com/matttproud/golang_protobuf_extensions/v2
https://github.com/matttproud/golang_protobuf_extensions/v2
----------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================================
github.com/miekg/dns
https://github.com/miekg/dns
----------------------------------------------------------------
+7 -7
View File
@@ -6,7 +6,8 @@ GOARCH := $(shell go env GOARCH)
GOOS := $(shell go env GOOS)
VERSION ?= $(shell git describe --tags)
TAG ?= "quay.io/minio/minio:$(VERSION)"
REPO ?= quay.io/minio
TAG ?= $(REPO)/minio:$(VERSION)
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
@@ -144,12 +145,11 @@ hotfix-vars:
$(eval LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix.$(shell git rev-parse --short HEAD)" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#')))
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
$(eval TAG := "minio/minio:$(VERSION)")
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.0/pkger_2.2.0_linux_amd64.deb
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.0/linux-systemd/distributed/minio.service
@sudo apt install ./pkger_2.2.0_linux_amd64.deb --yes
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.1/pkger_2.2.1_linux_amd64.deb
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service
@sudo apt install ./pkger_2.2.1_linux_amd64.deb --yes
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
@@ -177,9 +177,9 @@ docker: build ## builds minio docker container
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
install-race: checks ## builds minio to $(PWD)
@echo "Building minio binary to './minio'"
@echo "Building minio binary with -race to './minio'"
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
@echo "Installing minio binary with -race to '$(GOPATH)/bin/minio'"
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
install: build ## builds minio and installs it to $GOPATH/bin.
+1 -1
View File
@@ -123,7 +123,7 @@ You can also connect using any S3-compatible tool, such as the MinIO Client `mc`
## Install from Source
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.19](https://golang.org/dl/#stable)
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.21](https://golang.org/dl/#stable)
```sh
go install github.com/minio/minio@latest
+5 -1
View File
@@ -87,7 +87,11 @@ function verify_rewrite() {
exit 1
fi
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
(
cd ./docs/debugging/s3-check-md5
go install -v
)
if ! s3-check-md5 \
-debug \
-versions \
+3 -3
View File
@@ -58,7 +58,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
password := cred.SecretKey
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err, logger.Application)
logger.LogIf(ctx, err, logger.ErrorKind)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
@@ -162,7 +162,7 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
password := cred.SecretKey
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err, logger.Application)
logger.LogIf(ctx, err, logger.ErrorKind)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
@@ -443,7 +443,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
password := cred.SecretKey
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err, logger.Application)
logger.LogIf(ctx, err, logger.ErrorKind)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
+1 -1
View File
@@ -60,7 +60,7 @@ func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.R
password := cred.SecretKey
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err, logger.Application)
logger.LogIf(ctx, err, logger.ErrorKind)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
+1 -1
View File
@@ -132,7 +132,7 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
password := cred.SecretKey
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err, logger.Application)
logger.LogIf(ctx, err, logger.ErrorKind)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
+48 -26
View File
@@ -27,6 +27,7 @@ import (
"net/http"
"os"
"sort"
"strconv"
"time"
"github.com/klauspost/compress/zip"
@@ -620,6 +621,11 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
return
}
if createReq.AccessKey == globalActiveCred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
return
}
var (
targetGroups []string
err error
@@ -774,25 +780,6 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
return
}
if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.UpdateServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner,
Claims: cred.Claims,
}) {
requestUser := cred.AccessKey
if cred.ParentUser != "" {
requestUser = cred.ParentUser
}
if requestUser != svcAccount.ParentUser {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
return
}
}
password := cred.SecretKey
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
@@ -813,6 +800,31 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
return
}
condValues := getConditionValues(r, "", cred)
addExpirationToCondValues(updateReq.NewExpiration, condValues)
// Permission checks:
//
// 1. Any type of account (i.e. access keys (previously/still called service
// accounts), STS accounts, internal IDP accounts, etc) with the
// policy.UpdateServiceAccountAdminAction permission can update any service
// account.
//
// 2. We would like to let a user update their own access keys, however it
// is currently blocked pending a re-design. Users are still able to delete
// and re-create them.
if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.UpdateServiceAccountAdminAction,
ConditionValues: condValues,
IsOwner: owner,
Claims: cred.Claims,
}) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
return
}
var sp *policy.Policy
if len(updateReq.NewPolicy) > 0 {
sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
@@ -1060,6 +1072,10 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
return
}
if serviceAccount == siteReplicatorSvcAcc && globalSiteReplicationSys.isEnabled() {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidArgument), r.URL)
return
}
// We do not care if service account is readable or not at this point,
// since this is a delete call we shall allow it to be deleted if possible.
svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
@@ -1188,7 +1204,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
// Rely on older value if usage loading fails from disk.
bucketStorageCache.Relax = true
bucketStorageCache.Update = func() (interface{}, error) {
bucketStorageCache.Update = func() (DataUsageInfo, error) {
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
defer done()
@@ -1196,11 +1212,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
}
})
var dataUsageInfo DataUsageInfo
v, _ := bucketStorageCache.Get()
if v != nil {
dataUsageInfo, _ = v.(DataUsageInfo)
}
dataUsageInfo, _ := bucketStorageCache.Get()
// If etcd, dns federation configured list buckets from etcd.
var err error
@@ -2414,6 +2426,13 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
}
}
func addExpirationToCondValues(exp *time.Time, condValues map[string][]string) {
if exp == nil {
return
}
condValues["DurationSeconds"] = []string{strconv.FormatInt(int64(exp.Sub(time.Now()).Seconds()), 10)}
}
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
ctx := r.Context()
@@ -2469,13 +2488,16 @@ func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials
claims: make(map[string]interface{}),
}
condValues := getConditionValues(r, "", cred)
addExpirationToCondValues(createReq.Expiration, condValues)
// Check if action is allowed if creating access key for another user
// Check if action is explicitly denied if for self
if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.CreateServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred),
ConditionValues: condValues,
IsOwner: owner,
Claims: cred.Claims,
DenyOnly: (targetUser == cred.AccessKey || targetUser == cred.ParentUser),
+178 -8
View File
@@ -206,7 +206,9 @@ func TestIAMInternalIDPServerSuite(t *testing.T) {
suite.TestCannedPolicies(c)
suite.TestGroupAddRemove(c)
suite.TestServiceAccountOpsByAdmin(c)
suite.TestServiceAccountPrivilegeEscalationBug(c)
suite.TestServiceAccountOpsByUser(c)
suite.TestServiceAccountDurationSecondsCondition(c)
suite.TestAddServiceAccountPerms(c)
suite.TearDownSuite(c)
},
@@ -896,14 +898,6 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
// 3. Check S3 access
c.assertSvcAccS3Access(ctx, s, cr, bucket)
// 4. Check that svc account can restrict the policy, and that the
// session policy can be updated.
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, accessKey, bucket)
// 4. Check that service account's secret key and account status can be
// updated.
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, accessKey, bucket)
// 5. Check that service account can be deleted.
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
@@ -911,6 +905,93 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
c.mustNotCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
}
func (s *TestSuiteIAM) TestServiceAccountDurationSecondsCondition(c *check) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()
bucket := getRandomBucketName()
err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
if err != nil {
c.Fatalf("bucket creat error: %v", err)
}
// Create policy, user and associate policy
policy := "mypolicy"
policyBytes := []byte(fmt.Sprintf(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"admin:CreateServiceAccount",
"admin:UpdateServiceAccount"
],
"Condition": {"NumericGreaterThan": {"svc:DurationSeconds": "3600"}}
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::%s/*"
]
}
]
}`, bucket))
err = s.adm.AddCannedPolicy(ctx, policy, policyBytes)
if err != nil {
c.Fatalf("policy add error: %v", err)
}
accessKey, secretKey := mustGenerateCredentials(c)
err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled)
if err != nil {
c.Fatalf("Unable to set user: %v", err)
}
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
if err != nil {
c.Fatalf("Unable to set policy: %v", err)
}
// Create an madmin client with user creds
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
Secure: s.secure,
})
if err != nil {
c.Fatalf("Err creating user admin client: %v", err)
}
userAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
distantExpiration := time.Now().Add(30 * time.Minute)
cr, err := userAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
TargetUser: accessKey,
AccessKey: "svc-accesskey",
SecretKey: "svc-secretkey",
Expiration: &distantExpiration,
})
if err != nil {
c.Fatalf("Unable to create svc acc: %v", err)
}
c.assertSvcAccS3Access(ctx, s, cr, bucket)
closeExpiration := time.Now().Add(2 * time.Hour)
_, err = userAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
TargetUser: accessKey,
AccessKey: "svc-accesskey",
SecretKey: "svc-secretkey",
Expiration: &closeExpiration,
})
if err == nil {
c.Fatalf("Creating a svc acc with distant expiration should fail")
}
}
func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()
@@ -979,6 +1060,95 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket)
}
func (s *TestSuiteIAM) TestServiceAccountPrivilegeEscalationBug(c *check) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()
err := s.client.MakeBucket(ctx, "public", minio.MakeBucketOptions{})
if err != nil {
c.Fatalf("bucket creat error: %v", err)
}
err = s.client.MakeBucket(ctx, "private", minio.MakeBucketOptions{})
if err != nil {
c.Fatalf("bucket creat error: %v", err)
}
pubPolicyBytes := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::public",
"arn:aws:s3:::public/*"
]
}
]
}`)
fullS3PolicyBytes := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}
`)
// Create a service account for the root user.
cr, err := s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
TargetUser: globalActiveCred.AccessKey,
Policy: pubPolicyBytes,
})
if err != nil {
c.Fatalf("admin should be able to create service account for themselves %s", err)
}
svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "")
// Check that the service account can access the public bucket.
buckets, err := svcClient.ListBuckets(ctx)
if err != nil {
c.Fatalf("err fetching buckets %s", err)
}
if len(buckets) != 1 || buckets[0].Name != "public" {
c.Fatalf("service account should only have access to public bucket")
}
// Create an madmin client with the service account creds.
svcAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
Creds: credentials.NewStaticV4(cr.AccessKey, cr.SecretKey, ""),
Secure: s.secure,
})
if err != nil {
c.Fatalf("Err creating svcacct admin client: %v", err)
}
svcAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
// Attempt to update the policy on the service account.
err = svcAdmClient.UpdateServiceAccount(ctx, cr.AccessKey,
madmin.UpdateServiceAccountReq{
NewPolicy: fullS3PolicyBytes,
})
if err == nil {
c.Fatalf("service account should not be able to update policy on itself")
} else if !strings.Contains(err.Error(), "Access Denied") {
c.Fatalf("unexpected error: %v", err)
}
}
func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()
+98 -56
View File
@@ -50,6 +50,7 @@ import (
"github.com/minio/madmin-go/v3/estream"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/dsync"
"github.com/minio/minio/internal/grid"
"github.com/minio/minio/internal/handlers"
xhttp "github.com/minio/minio/internal/http"
xioutil "github.com/minio/minio/internal/ioutil"
@@ -429,6 +430,8 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
// ServiceHandler - POST /minio/admin/v3/service?action={action}
// ----------
// Supports following actions:
// - restart (restarts all the MinIO instances in a setup)
// - stop (stops all the MinIO instances in a setup)
// - freeze (freezes all incoming S3 API calls)
// - unfreeze (unfreezes previously frozen S3 API calls)
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
@@ -438,25 +441,30 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
action := vars["action"]
var serviceSig serviceSignal
switch act := madmin.ServiceAction(action); act {
switch madmin.ServiceAction(action) {
case madmin.ServiceActionRestart:
serviceSig = serviceRestart
case madmin.ServiceActionStop:
serviceSig = serviceStop
case madmin.ServiceActionFreeze:
serviceSig = serviceFreeze
case madmin.ServiceActionUnfreeze:
serviceSig = serviceUnFreeze
default:
process := act == madmin.ServiceActionRestart || act == madmin.ServiceActionStop
if process {
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
apiErr.Description = "process actions are not supported via this API anymore, please upgrade 'mc' or madmin-go to use ServiceV2() API"
writeErrorResponseJSON(ctx, w, apiErr, r.URL)
return
}
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.Application)
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.ErrorKind)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
return
}
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
var objectAPI ObjectLayer
switch serviceSig {
case serviceRestart:
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction)
case serviceStop:
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction)
case serviceFreeze, serviceUnFreeze:
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
}
if objectAPI == nil {
return
}
@@ -477,6 +485,8 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
freezeServices()
case serviceUnFreeze:
unfreezeServices()
case serviceRestart, serviceStop:
globalServiceSignalCh <- serviceSig
}
}
@@ -524,7 +534,7 @@ func (a adminAPIHandlers) ServiceV2Handler(w http.ResponseWriter, r *http.Reques
case madmin.ServiceActionUnfreeze:
serviceSig = serviceUnFreeze
default:
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.Application)
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.ErrorKind)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
return
}
@@ -795,7 +805,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
}
}
// DataUsageInfoHandler - GET /minio/admin/v3/datausage
// DataUsageInfoHandler - GET /minio/admin/v3/datausage?capacity={true}
// ----------
// Get server/cluster data usage info
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
@@ -818,6 +828,16 @@ func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Re
return
}
// Get capacity info when asked.
if r.Form.Get("capacity") == "true" {
sinfo := objectAPI.StorageInfo(ctx, false)
dataUsageInfo.TotalCapacity = GetTotalUsableCapacity(sinfo.Disks, sinfo)
dataUsageInfo.TotalFreeCapacity = GetTotalUsableCapacityFree(sinfo.Disks, sinfo)
if dataUsageInfo.TotalCapacity > dataUsageInfo.TotalFreeCapacity {
dataUsageInfo.TotalUsedCapacity = dataUsageInfo.TotalCapacity - dataUsageInfo.TotalFreeCapacity
}
}
writeSuccessResponseJSON(w, dataUsageInfoJSON)
}
@@ -1172,7 +1192,7 @@ type healInitParams struct {
}
// extractHealInitParams - Validates params for heal init API.
func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) {
func extractHealInitParams(vars map[string]string, qParams url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) {
hip.bucket = vars[mgmtBucket]
hip.objPrefix = vars[mgmtPrefix]
@@ -1193,13 +1213,13 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
return
}
if len(qParms[mgmtClientToken]) > 0 {
hip.clientToken = qParms[mgmtClientToken][0]
if len(qParams[mgmtClientToken]) > 0 {
hip.clientToken = qParams[mgmtClientToken][0]
}
if _, ok := qParms[mgmtForceStart]; ok {
if _, ok := qParams[mgmtForceStart]; ok {
hip.forceStart = true
}
if _, ok := qParms[mgmtForceStop]; ok {
if _, ok := qParams[mgmtForceStop]; ok {
hip.forceStop = true
}
@@ -1219,7 +1239,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
if hip.clientToken == "" {
jerr := json.NewDecoder(r).Decode(&hip.hs)
if jerr != nil {
logger.LogIf(GlobalContext, jerr, logger.Application)
logger.LogIf(GlobalContext, jerr, logger.ErrorKind)
err = ErrRequestBodyParse
return
}
@@ -1632,7 +1652,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
duration = time.Second * 10
}
storageInfo := objectAPI.StorageInfo(ctx, true)
storageInfo := objectAPI.StorageInfo(ctx, false)
sufficientCapacity, canAutotune, capacityErrMsg := validateObjPerfOptions(ctx, storageInfo, concurrent, size, autotune)
if !sufficientCapacity {
@@ -1909,16 +1929,13 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
setEventStreamHeaders(w)
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
// Use buffered channel to take care of burst sends or slow w.Write()
// Keep 100k buffered channel, should be sufficient to ensure we do not lose any events.
traceCh := make(chan madmin.TraceInfo, 100000)
// Keep 100k buffered channel.
// If receiver cannot keep up with that we drop events.
traceCh := make(chan []byte, 100000)
peers, _ := newPeerRestClients(globalEndpoints)
err = globalTrace.Subscribe(traceOpts.TraceTypes(), traceCh, ctx.Done(), func(entry madmin.TraceInfo) bool {
err = globalTrace.SubscribeJSON(traceOpts.TraceTypes(), traceCh, ctx.Done(), func(entry madmin.TraceInfo) bool {
return shouldTrace(entry, traceOpts)
})
}, nil)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -1933,19 +1950,19 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
if peer == nil {
continue
}
peer.Trace(traceCh, ctx.Done(), traceOpts)
peer.Trace(ctx, traceCh, traceOpts)
}
keepAliveTicker := time.NewTicker(time.Second)
defer keepAliveTicker.Stop()
enc := json.NewEncoder(w)
for {
select {
case entry := <-traceCh:
if err := enc.Encode(entry); err != nil {
if _, err := w.Write(entry); err != nil {
return
}
grid.PutByteBuffer(entry)
if len(traceCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
@@ -1967,7 +1984,6 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
// The ConsoleLogHandler handler sends console logs to the connected HTTP client.
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConsoleLogAdminAction)
if objectAPI == nil {
return
@@ -1992,44 +2008,65 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
setEventStreamHeaders(w)
logCh := make(chan log.Info, 4000)
logCh := make(chan log.Info, 1000)
peers, _ := newPeerRestClients(globalEndpoints)
encodedCh := make(chan []byte, 1000+len(peers)*1000)
err = globalConsoleSys.Subscribe(logCh, ctx.Done(), node, limitLines, logKind, nil)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Convert local entries to JSON
go func() {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
for {
select {
case <-ctx.Done():
return
case li := <-logCh:
if !li.SendLog(node, logKind) {
continue
}
buf.Reset()
if err := enc.Encode(li); err != nil {
continue
}
select {
case <-ctx.Done():
return
case encodedCh <- append(grid.GetByteBuffer()[:0], buf.Bytes()...):
}
}
}
}()
// Collect from matching peers
for _, peer := range peers {
if peer == nil {
continue
}
if node == "" || strings.EqualFold(peer.host.Name, node) {
peer.ConsoleLog(logCh, ctx.Done())
peer.ConsoleLog(ctx, logKind, encodedCh)
}
}
enc := json.NewEncoder(w)
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
defer keepAliveTicker.Stop()
for {
select {
case log, ok := <-logCh:
case log, ok := <-encodedCh:
if !ok {
return
}
if log.SendLog(node, logKind) {
if err := enc.Encode(log); err != nil {
return
}
if len(logCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
}
_, err = w.Write(log)
if err != nil {
return
}
grid.PutByteBuffer(log)
if len(logCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
}
case <-keepAliveTicker.C:
if len(logCh) > 0 {
@@ -2184,7 +2221,10 @@ func getPoolsInfo(ctx context.Context, allDisks []madmin.Disk) (map[int]map[int]
return nil, errServerNotInitialized
}
z, _ := objectAPI.(*erasureServerPools)
z, ok := objectAPI.(*erasureServerPools)
if !ok {
return nil, errServerNotInitialized
}
poolsInfo := make(map[int]map[int]madmin.ErasureSetInfo)
for _, d := range allDisks {
@@ -2215,7 +2255,7 @@ func getPoolsInfo(ctx context.Context, allDisks []madmin.Disk) (map[int]map[int]
return poolsInfo, nil
}
func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request) madmin.InfoMessage {
func getServerInfo(ctx context.Context, pools, metrics bool, r *http.Request) madmin.InfoMessage {
ldap := madmin.LDAP{}
if globalIAMSys.LDAPConfig.Enabled() {
ldapConn, err := globalIAMSys.LDAPConfig.LDAP.Connect()
@@ -2236,8 +2276,8 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
// Get the notification target info
notifyTarget := fetchLambdaInfo()
local := getLocalServerProperty(globalEndpoints, r)
servers := globalNotificationSys.ServerInfo()
local := getLocalServerProperty(globalEndpoints, r, metrics)
servers := globalNotificationSys.ServerInfo(metrics)
servers = append(servers, local)
assignPoolNumbers(servers)
@@ -2291,7 +2331,7 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
DrivesPerSet: backendInfo.DrivesPerSet,
}
if poolsInfoEnabled {
if pools {
poolsInfo, _ = getPoolsInfo(ctx, allDisks)
}
}
@@ -2337,7 +2377,7 @@ func getKubernetesInfo(dctx context.Context) madmin.KubernetesInfo {
}
client := &http.Client{
Transport: NewHTTPTransport(),
Transport: globalHealthChkTransport,
Timeout: 10 * time.Second,
}
@@ -2673,7 +2713,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
getAndWriteSysConfig()
if query.Get("minioinfo") == "true" {
infoMessage := getServerInfo(healthCtx, false, nil)
infoMessage := getServerInfo(healthCtx, false, true, nil)
servers := make([]madmin.ServerInfo, 0, len(infoMessage.Servers))
for _, server := range infoMessage.Servers {
anonEndpoint := anonAddr(server.Endpoint)
@@ -2861,8 +2901,10 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
return
}
metrics := r.Form.Get(peerRESTMetrics) == "true"
// Marshal API response
jsonBytes, err := json.Marshal(getServerInfo(ctx, true, r))
jsonBytes, err := json.Marshal(getServerInfo(ctx, true, metrics, r))
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -3053,7 +3095,7 @@ func getClusterMetaInfo(ctx context.Context) []byte {
ci.Info.NoOfServers = totalNodeCount()
ci.Info.MinioVersion = Version
si := objectAPI.StorageInfo(ctx, true)
si := objectAPI.StorageInfo(ctx, false)
ci.Info.NoOfDrives = len(si.Disks)
for _, disk := range si.Disks {
+3 -3
View File
@@ -348,7 +348,7 @@ func TestExtractHealInitParams(t *testing.T) {
}
return v
}
qParmsArr := []url.Values{
qParamsArr := []url.Values{
// Invalid cases
mkParams("", true, true),
mkParams("111", true, true),
@@ -373,9 +373,9 @@ func TestExtractHealInitParams(t *testing.T) {
body := `{"recursive": false, "dryRun": true, "remove": false, "scanMode": 0}`
// Test all combinations!
for pIdx, parms := range qParmsArr {
for pIdx, params := range qParamsArr {
for vIdx, vars := range varsArr {
_, err := extractHealInitParams(vars, parms, bytes.NewReader([]byte(body)))
_, err := extractHealInitParams(vars, params, bytes.NewReader([]byte(body)))
isErrCase := false
if pIdx < 4 || vIdx < 1 {
isErrCase = true
+9 -3
View File
@@ -133,7 +133,13 @@ func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
func (ahs *allHealState) updateHealStatus(tracker *healingTracker) {
ahs.Lock()
defer ahs.Unlock()
ahs.healStatus[tracker.ID] = *tracker
tracker.mu.RLock()
t := *tracker
t.QueuedBuckets = append(make([]string, 0, len(tracker.QueuedBuckets)), tracker.QueuedBuckets...)
t.HealedBuckets = append(make([]string, 0, len(tracker.HealedBuckets)), tracker.HealedBuckets...)
ahs.healStatus[tracker.ID] = t
tracker.mu.RUnlock()
}
// Sort by zone, set and disk index
@@ -707,7 +713,7 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
select {
case globalBackgroundHealRoutine.tasks <- task:
if serverDebugLog {
logger.Info("Task in the queue: %#v", task)
fmt.Printf("Task in the queue: %#v\n", task)
}
default:
// task queue is full, no more workers, we shall move on and heal later.
@@ -724,7 +730,7 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
select {
case globalBackgroundHealRoutine.tasks <- task:
if serverDebugLog {
logger.Info("Task in the queue: %#v", task)
fmt.Printf("Task in the queue: %#v\n", task)
}
case <-h.ctx.Done():
return nil
+2 -2
View File
@@ -34,7 +34,7 @@ import (
// getLocalServerProperty - returns madmin.ServerProperties for only the
// local endpoints from given list of endpoints
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request, metrics bool) madmin.ServerProperties {
addr := globalLocalNodeName
if r != nil {
addr = r.Host
@@ -141,7 +141,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
objLayer := newObjectLayerFn()
if objLayer != nil {
storageInfo := objLayer.LocalStorageInfo(GlobalContext, true)
storageInfo := objLayer.LocalStorageInfo(GlobalContext, metrics)
props.State = string(madmin.ItemOnline)
props.Disks = storageInfo.Disks
} else {
+35 -32
View File
@@ -24,6 +24,7 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
@@ -297,7 +298,6 @@ const (
ErrAdminConfigLDAPValidation
ErrAdminConfigIDPCfgNameAlreadyExists
ErrAdminConfigIDPCfgNameDoesNotExist
ErrAdminCredentialsMismatch
ErrInsecureClientRequest
ErrObjectTampered
@@ -390,7 +390,7 @@ const (
ErrParseExpectedIdentForGroupName
ErrParseExpectedIdentForAlias
ErrParseUnsupportedCallWithStar
ErrParseNonUnaryAgregateFunctionCall
ErrParseNonUnaryAggregateFunctionCall
ErrParseMalformedJoin
ErrParseExpectedIdentForAt
ErrParseAsteriskIsNotAloneInSelectList
@@ -433,6 +433,9 @@ const (
// New Codes for GetObjectAttributes and GetObjectVersionAttributes
ErrInvalidAttributeName
ErrAdminNoAccessKey
ErrAdminNoSecretKey
apiErrCodeEnd // This is used only for the testing code
)
@@ -1359,6 +1362,16 @@ var errorCodes = errorCodeMap{
Description: "The secret key is invalid.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminNoAccessKey: {
Code: "XMinioAdminNoAccessKey",
Description: "No access key was provided.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminNoSecretKey: {
Code: "XMinioAdminNoSecretKey",
Description: "No secret key was provided.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminConfigNoQuorum: {
Code: "XMinioAdminConfigNoQuorum",
Description: "Configuration update failed because server quorum was not met",
@@ -1425,11 +1438,6 @@ var errorCodes = errorCodeMap{
Description: "Unable to perform the requested operation because profiling is not enabled",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminCredentialsMismatch: {
Code: "XMinioAdminCredentialsMismatch",
Description: "Credentials in config mismatch with server environment variables",
HTTPStatusCode: http.StatusServiceUnavailable,
},
ErrAdminBucketQuotaExceeded: {
Code: "XMinioAdminBucketQuotaExceeded",
Description: "Bucket quota exceeded",
@@ -1891,8 +1899,8 @@ var errorCodes = errorCodeMap{
Description: "Only COUNT with (*) as a parameter is supported in the SQL expression.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrParseNonUnaryAgregateFunctionCall: {
Code: "ParseNonUnaryAgregateFunctionCall",
ErrParseNonUnaryAggregateFunctionCall: {
Code: "ParseNonUnaryAggregateFunctionCall",
Description: "Only one argument is supported for aggregate functions in the SQL expression.",
HTTPStatusCode: http.StatusBadRequest,
},
@@ -2013,7 +2021,7 @@ var errorCodes = errorCodeMap{
},
ErrAddUserInvalidArgument: {
Code: "XMinioInvalidIAMCredentials",
Description: "User is not allowed to be same as admin access key",
Description: "Credential is not allowed to be same as admin access key",
HTTPStatusCode: http.StatusForbidden,
},
ErrAdminResourceInvalidArgument: {
@@ -2082,6 +2090,11 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
return ErrNone
}
// Errors that are generated by net.Conn and any context errors must be handled here.
if errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, context.DeadlineExceeded) {
return ErrRequestTimedout
}
// Only return ErrClientDisconnected if the provided context is actually canceled.
// This way downstream context.Canceled will still report ErrRequestTimedout
if contextCanceled(ctx) && errors.Is(ctx.Err(), context.Canceled) {
@@ -2124,6 +2137,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrAdminInvalidAccessKey
case auth.ErrInvalidSecretKeyLength:
apiErr = ErrAdminInvalidSecretKey
case auth.ErrNoAccessKeyWithSecretKey:
apiErr = ErrAdminNoAccessKey
case auth.ErrNoSecretKeyWithAccessKey:
apiErr = ErrAdminNoSecretKey
case errInvalidStorageClass:
apiErr = ErrInvalidStorageClass
case errErasureReadQuorum:
@@ -2351,31 +2368,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
case dns.ErrBucketConflict:
apiErr = ErrBucketAlreadyExists
default:
if _, ok := err.(tags.Error); ok {
// tag errors are not exported, so we check their custom interface to avoid logging.
// The correct type is inserted by toAPIError.
apiErr = ErrInternalError
break
}
var ie, iw int
// This work-around is to handle the issue golang/go#30648
//nolint:gocritic
if _, ferr := fmt.Fscanf(strings.NewReader(err.Error()),
"request declared a Content-Length of %d but only wrote %d bytes",
&ie, &iw); ferr != nil {
apiErr = ErrInternalError
// Make sure to log the errors which we cannot translate
// to a meaningful S3 API errors. This is added to aid in
// debugging unexpected/unhandled errors.
logger.LogIf(ctx, err)
} else if ie > iw {
if strings.Contains(err.Error(), "request declared a Content-Length") {
apiErr = ErrIncompleteBody
} else {
apiErr = ErrInternalError
// Make sure to log the errors which we cannot translate
// to a meaningful S3 API errors. This is added to aid in
// debugging unexpected/unhandled errors.
logger.LogIf(ctx, err)
}
}
@@ -2519,6 +2515,13 @@ func toAPIError(ctx context.Context, err error) APIError {
}
}
if apiErr.Code == "InternalError" {
// Make sure to log the errors which we cannot translate
// to a meaningful S3 API errors. This is added to aid in
// debugging unexpected/unhandled errors.
logger.LogIf(ctx, err)
}
return apiErr
}
+7 -5
View File
@@ -891,7 +891,7 @@ func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType
}
// Similar check to http.checkWriteHeaderCode
if statusCode < 100 || statusCode > 999 {
logger.Error(fmt.Sprintf("invalid WriteHeader code %v", statusCode))
logger.LogIf(context.Background(), fmt.Errorf("invalid WriteHeader code %v", statusCode))
statusCode = http.StatusInternalServerError
}
setCommonHeaders(w)
@@ -944,13 +944,15 @@ func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
writeResponse(w, http.StatusOK, nil, mimeNone)
}
// writeErrorRespone writes error headers
// writeErrorResponse writes error headers
func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
switch err.Code {
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
if err.HTTPStatusCode == http.StatusServiceUnavailable {
// Set retry-after header to indicate user-agents to retry request after 120secs.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
w.Header().Set(xhttp.RetryAfter, "120")
}
switch err.Code {
case "InvalidRegion":
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region)
case "AuthorizationHeaderMalformed":
@@ -959,7 +961,7 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
// Similar check to http.checkWriteHeaderCode
if err.HTTPStatusCode < 100 || err.HTTPStatusCode > 999 {
logger.Error(fmt.Sprintf("invalid WriteHeader code %v from %v", err.HTTPStatusCode, err.Code))
logger.LogIf(ctx, fmt.Errorf("invalid WriteHeader code %v from %v", err.HTTPStatusCode, err.Code))
err.HTTPStatusCode = http.StatusInternalServerError
}
+1 -1
View File
@@ -227,7 +227,7 @@ func registerAPIRouter(router *mux.Router) {
// HeadObject
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
// GetObjectAttribytes
// GetObjectAttributes
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
collectAPIStats("getobjectattributes", maxClients(gz(httpTraceHdrs(api.GetObjectAttributesHandler))))).Queries("attributes", "")
// CopyObjectPart
+124 -123
View File
File diff suppressed because one or more lines are too long
+10 -4
View File
@@ -162,7 +162,8 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
s3Err := ErrAccessDenied
if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
getRequestAuthType(r) == authTypeSigned {
// We only support admin credentials to access admin APIs.
// Get credential information from the request.
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
if s3Err != ErrNone {
return cred, owner, s3Err
@@ -256,7 +257,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
if err != nil {
// Base64 decoding fails, we should log to indicate
// something is malforming the request sent by client.
logger.LogIf(GlobalContext, err, logger.Application)
logger.LogIf(GlobalContext, err, logger.ErrorKind)
return nil, errAuthentication
}
claims.MapClaims[sessionPolicyNameExtracted] = string(spBytes)
@@ -293,6 +294,11 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
return nil, ErrInvalidToken
}
// Expired credentials must return error right away.
if cred.IsTemp() && cred.IsExpired() {
return nil, toAPIErrorCode(r.Context(), errInvalidAccessKeyID)
}
secret := globalActiveCred.SecretKey
if cred.IsServiceAccount() {
token = cred.SessionToken
@@ -338,7 +344,7 @@ func checkRequestAuthTypeWithVID(ctx context.Context, r *http.Request, action po
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
if logger.GetReqInfo(ctx) == nil {
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.ErrorKind)
return ErrAccessDenied
}
@@ -377,7 +383,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
// To extract region from XML in request body, get copy of request body.
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
if err != nil {
logger.LogIf(ctx, err, logger.Application)
logger.LogIf(ctx, err, logger.ErrorKind)
return ErrMalformedXML
}
+1 -1
View File
@@ -237,7 +237,7 @@ func TestIsRequestPresignedSignatureV2(t *testing.T) {
}
}
// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection.
// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature version v4 detection.
func TestIsRequestPresignedSignatureV4(t *testing.T) {
testCases := []struct {
inputQueryKey string
+5 -9
View File
@@ -353,7 +353,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
func getLocalDisksToHeal() (disksToHeal Endpoints) {
globalLocalDrivesMu.RLock()
localDrives := globalLocalDrives
localDrives := cloneDrives(globalLocalDrives)
globalLocalDrivesMu.RUnlock()
for _, disk := range localDrives {
_, err := disk.GetDiskID()
@@ -420,7 +420,7 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
tracker = initHealingTracker(disk, mustGetUUID())
}
logger.Info(fmt.Sprintf("Healing drive '%s' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint))
logger.Event(ctx, "Healing drive '%s' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint)
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
// Buckets data are dispersed in multiple pools/sets, make
@@ -440,10 +440,6 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
return buckets[i].Created.After(buckets[j].Created)
})
if serverDebugLog {
logger.Info("Healing drive '%v' on %s pool, belonging to %s erasure set", disk, humanize.Ordinal(poolIdx+1), humanize.Ordinal(setIdx+1))
}
// Load bucket totals
cache := dataUsageCache{}
if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil {
@@ -464,9 +460,9 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
}
if tracker.ItemsFailed > 0 {
logger.Info("Healing of drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
logger.Event(ctx, "Healing of drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
} else {
logger.Info("Healing of drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
logger.Event(ctx, "Healing of drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
}
if len(tracker.QueuedBuckets) > 0 {
@@ -475,7 +471,7 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
if serverDebugLog {
tracker.printTo(os.Stdout)
logger.Info("\n")
fmt.Printf("\n")
}
if tracker.HealID == "" { // HealID was empty only before Feb 2023
+7 -2
View File
@@ -572,7 +572,11 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
}()
expireCh := make(chan []expireObjInfo, workerSize)
go batchObjsForDelete(ctx, r, ri, job, api, wk, expireCh)
expireDoneCh := make(chan struct{})
go func() {
defer close(expireDoneCh)
batchObjsForDelete(ctx, r, ri, job, api, wk, expireCh)
}()
var (
prevObj ObjectInfo
@@ -651,7 +655,8 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
}
xioutil.SafeClose(expireCh)
wk.Wait() // waits for all expire goroutines to complete
<-expireDoneCh // waits for the expire goroutine to complete
wk.Wait() // waits for all expire workers to retire
ri.Complete = ri.ObjectsFailed == 0
ri.Failed = ri.ObjectsFailed > 0
+20
View File
@@ -925,6 +925,21 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
ri.countItem(info.Size, info.DeleteMarker, success)
}
func (ri *batchJobInfo) trackCurrentBucketBatch(bucket string, batch []ObjectInfo) {
if ri == nil {
return
}
ri.mu.Lock()
defer ri.mu.Unlock()
ri.Bucket = bucket
for i := range batch {
ri.Object = batch[i].Name
ri.countItem(batch[i].Size, batch[i].DeleteMarker, true)
}
}
// Start start the batch replication job, resumes if there was a pending job via "job.ID"
func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
ri := &batchJobInfo{
@@ -1087,6 +1102,11 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
for _, b := range batch {
slowCh <- b
}
} else {
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
globalBatchJobsMetrics.save(job.ID, ri)
// persist in-memory state to disk after every 10secs.
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
}
batch = batch[:0]
}
+7 -4
View File
@@ -213,11 +213,14 @@ func (b BatchJobSnowball) Validate() error {
}
}
_, err := humanize.ParseBytes(*b.SmallerThan)
return BatchJobYamlErr{
line: b.line,
col: b.col,
msg: err.Error(),
if err != nil {
return BatchJobYamlErr{
line: b.line,
col: b.col,
msg: err.Error(),
}
}
return nil
}
// BatchJobSizeFilter supports size based filters - LesserThan and GreaterThan
+2 -26
View File
@@ -20,16 +20,12 @@ package cmd
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"hash"
"io"
"strings"
"sync"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger"
)
// Calculates bitrot in chunks and writes the hash into the stream.
@@ -90,7 +86,7 @@ func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize
}
// Returns streaming bitrot writer implementation.
func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
r, w := io.Pipe()
h := algo.New()
@@ -110,7 +106,7 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
totalFileSize = bitrotSumsTotalSize + length
}
r.CloseWithError(disk.CreateFile(context.TODO(), volume, filePath, totalFileSize, r))
r.CloseWithError(disk.CreateFile(context.TODO(), origvolume, volume, filePath, totalFileSize, r))
}()
return bw
}
@@ -154,29 +150,12 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
// Can never happen unless there are programmer bugs
return 0, errUnexpected
}
ignoredErrs := []error{
errDiskNotFound,
}
if strings.HasPrefix(b.volume, minioMetaBucket) {
ignoredErrs = append(ignoredErrs,
errFileNotFound,
errVolumeNotFound,
errFileVersionNotFound,
)
}
if b.rc == nil {
// For the first ReadAt() call we need to open the stream for reading.
b.currOffset = offset
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
if len(b.data) == 0 && b.tillOffset != streamOffset {
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
if err != nil {
if !IsErr(err, ignoredErrs...) {
logger.LogOnceIf(GlobalContext,
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
b.disk, b.volume, b.filePath, err), "bitrot-read-file-stream-"+b.volume+"-"+b.filePath)
}
}
} else {
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
}
@@ -198,10 +177,7 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
return 0, err
}
b.h.Write(buf)
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s - content hash does not match - expected %s, got %s",
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))))
return 0, errFileCorrupt
}
b.currOffset += int64(len(buf))
-7
View File
@@ -19,11 +19,8 @@ package cmd
import (
"context"
"fmt"
"hash"
"io"
"github.com/minio/minio/internal/logger"
)
// Implementation to calculate bitrot for the whole file.
@@ -38,12 +35,10 @@ type wholeBitrotWriter struct {
func (b *wholeBitrotWriter) Write(p []byte) (int, error) {
err := b.disk.AppendFile(context.TODO(), b.volume, b.filePath, p)
if err != nil {
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
return 0, err
}
_, err = b.Hash.Write(p)
if err != nil {
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
return 0, err
}
return len(p), nil
@@ -72,12 +67,10 @@ func (b *wholeBitrotReader) ReadAt(buf []byte, offset int64) (n int, err error)
if b.buf == nil {
b.buf = make([]byte, b.tillOffset-offset)
if _, err := b.disk.ReadFile(context.TODO(), b.volume, b.filePath, offset, b.buf, b.verifier); err != nil {
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, err))
return 0, err
}
}
if len(b.buf) < len(buf) {
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, errLessData))
return 0, errLessData
}
n = copy(buf, b.buf)
+2 -2
View File
@@ -102,9 +102,9 @@ func BitrotAlgorithmFromString(s string) (a BitrotAlgorithm) {
return
}
func newBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
func newBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
if algo == HighwayHash256S {
return newStreamingBitrotWriter(disk, volume, filePath, length, algo, shardSize)
return newStreamingBitrotWriter(disk, origvolume, volume, filePath, length, algo, shardSize)
}
return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize)
}
+1 -1
View File
@@ -36,7 +36,7 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
disk.MakeVol(context.Background(), volume)
writer := newBitrotWriter(disk, volume, filePath, 35, bitrotAlgo, 10)
writer := newBitrotWriter(disk, "", volume, filePath, 35, bitrotAlgo, 10)
_, err = writer.Write([]byte("aaaaaaaaaa"))
if err != nil {
+3 -1
View File
@@ -152,10 +152,12 @@ func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg *ServerSys
return nil
}
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSSWith(map[string]string{}))
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSS())
if err != nil {
return err
}
// We do not need the response after returning.
defer serverVerifyHandler.PutResponse(recvCfg)
return srcCfg.Diff(recvCfg)
}
+27 -10
View File
@@ -467,13 +467,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Ignore errors here to preserve the S3 error behavior of GetBucketInfo()
checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, "")
// Before proceeding validate if bucket exists.
_, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{})
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
deleteObjectsFn := objectAPI.DeleteObjects
// Return Malformed XML as S3 spec if the number of objects is empty
@@ -611,6 +604,12 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
VersionSuspended: vc.Suspended(),
})
// Are all objects saying bucket not found?
if isAllBucketsNotFound(errs) {
writeErrorResponse(ctx, w, toAPIError(ctx, errs[0]), r.URL)
return
}
for i := range errs {
// DeleteMarkerVersionID is not used specifically to avoid
// lookup errors, since DeleteMarkerVersionID is only
@@ -618,7 +617,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// specify a versionID.
objToDel := ObjectToDelete{
ObjectV: ObjectV{
ObjectName: dObjects[i].ObjectName,
ObjectName: decodeDirObject(dObjects[i].ObjectName),
VersionID: dObjects[i].VersionID,
},
VersionPurgeStatus: dObjects[i].VersionPurgeStatus(),
@@ -1395,7 +1394,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// Notify object created events.
sendEvent(eventArgsList[i])
if eventArgsList[i].Object.NumVersions > dataScannerExcessiveVersionsThreshold {
if eventArgsList[i].Object.NumVersions > int(scannerExcessObjectVersions.Load()) {
// Send events for excessive versions.
sendEvent(eventArgs{
EventName: event.ObjectManyVersions,
@@ -1406,6 +1405,15 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
Host: handlers.GetSourceIP(r),
})
auditLogInternal(context.Background(), AuditLogOptions{
Event: "scanner:manyversions",
APIName: "PostPolicyBucket",
Bucket: eventArgsList[i].Object.Bucket,
Object: eventArgsList[i].Object.Name,
VersionID: eventArgsList[i].Object.VersionID,
Status: http.StatusText(http.StatusOK),
})
}
}
@@ -1461,7 +1469,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Host: handlers.GetSourceIP(r),
})
if objInfo.NumVersions > dataScannerExcessiveVersionsThreshold {
if objInfo.NumVersions > int(scannerExcessObjectVersions.Load()) {
defer sendEvent(eventArgs{
EventName: event.ObjectManyVersions,
BucketName: objInfo.Bucket,
@@ -1471,6 +1479,15 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
UserAgent: r.UserAgent(),
Host: handlers.GetSourceIP(r),
})
auditLogInternal(context.Background(), AuditLogOptions{
Event: "scanner:manyversions",
APIName: "PostPolicyBucket",
Bucket: objInfo.Bucket,
Object: objInfo.Name,
VersionID: objInfo.VersionID,
Status: http.StatusText(http.StatusOK),
})
}
if redirectURL != nil { // success_action_redirect is valid and set.
+12 -4
View File
@@ -580,7 +580,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
expectedRespStatus: http.StatusOK,
},
// Test case - 2.
// Test case with invalid accessKey to produce and validate Signature MisMatch error.
// Test case with invalid accessKey to produce and validate Signature Mismatch error.
{
bucketName: bucketName,
accessKey: "abcd",
@@ -880,6 +880,15 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
expectedContent: encodedAnonResponseWithPartialPublicAccess,
expectedRespStatus: http.StatusOK,
},
// Test case - 7.
// Bucket does not exist.
7: {
bucket: "unknown-bucket-name",
objects: successRequest0,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusNotFound,
},
}
for i, testCase := range testCases {
@@ -888,13 +897,12 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
// Generate a signed or anonymous request based on the testCase
if testCase.accessKey != "" {
req, err = newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
req, err = newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", testCase.bucket),
int64(len(testCase.objects)), bytes.NewReader(testCase.objects), testCase.accessKey, testCase.secretKey, nil)
} else {
req, err = newTestRequest(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
req, err = newTestRequest(http.MethodPost, getDeleteMultipleObjectsURL("", testCase.bucket),
int64(len(testCase.objects)), bytes.NewReader(testCase.objects))
}
if err != nil {
t.Fatalf("Failed to create HTTP request for DeleteMultipleObjects: <ERROR> %v", err)
}
+19 -9
View File
@@ -395,12 +395,15 @@ func enqueueTransitionImmediate(obj ObjectInfo, src lcEventSrc) {
//
// 1. when a restored (via PostRestoreObject API) object expires.
// 2. when a transitioned object expires (based on an ILM rule).
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, lcEvent lifecycle.Event, src lcEventSrc) error {
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) error {
traceFn := globalLifecycleSys.trace(*oi)
var opts ObjectOptions
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name)
opts.VersionID = lcOpts.VersionID
opts.Expiration = ExpirationOptions{Expire: true}
opts := ObjectOptions{
Versioned: globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name),
Expiration: ExpirationOptions{Expire: true},
}
if lcEvent.Action.DeleteVersioned() {
opts.VersionID = oi.VersionID
}
tags := newLifecycleAuditEvent(src, lcEvent).Tags()
if lcEvent.Action.DeleteRestored() {
// delete locally restored copy of object or object version
@@ -435,13 +438,13 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
defer auditLogLifecycle(ctx, *oi, ILMExpiry, tags, traceFn)
eventName := event.ObjectRemovedDelete
if lcOpts.DeleteMarker {
if oi.DeleteMarker {
eventName = event.ObjectRemovedDeleteMarkerCreated
}
objInfo := ObjectInfo{
Name: oi.Name,
VersionID: lcOpts.VersionID,
DeleteMarker: lcOpts.DeleteMarker,
VersionID: oi.VersionID,
DeleteMarker: oi.DeleteMarker,
}
// Notify object deleted event.
sendEvent(eventArgs{
@@ -471,7 +474,14 @@ func genTransitionObjName(bucket string) (string, error) {
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
// to the transition tier without decrypting or re-encrypting.
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) error {
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) (err error) {
defer func() {
if err != nil {
return
}
globalScannerMetrics.timeILM(lae.Action)(1)
}()
opts := ObjectOptions{
Transition: TransitionOptions{
Status: lifecycle.TransitionPending,
+1 -1
View File
@@ -289,7 +289,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
return
}
// Extract all the litsObjectsV1 query params to their native values.
// Extract all the listObjectsV1 query params to their native values.
prefix, marker, delimiter, maxKeys, encodingType, s3Error := getListObjectsV1Args(r.Form)
if s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
+6 -5
View File
@@ -22,6 +22,7 @@ import (
"encoding/xml"
"errors"
"fmt"
"math/rand"
"sync"
"time"
@@ -482,11 +483,11 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
for index := range buckets {
index := index
g.Go(func() error {
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
// Ensure heal opts for bucket metadata be deep healed all the time.
ScanMode: madmin.HealDeepScan,
Recreate: true,
})
// Sleep and stagger to avoid blocked CPU and thundering
// herd upon start up sequence.
time.Sleep(25*time.Millisecond + time.Duration(rand.Int63n(int64(100*time.Millisecond))))
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{Recreate: true})
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name)
if err != nil {
return err
+24 -15
View File
@@ -182,26 +182,34 @@ func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket
b.defaultTimestamps()
}
configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
if err != nil {
return b, err
// If bucket metadata is missing look for legacy files,
// since we only ever had b.Created as non-zero when
// migration was complete in 2020-May release. So this
// a check to avoid migrating for buckets that already
// have this field set.
if b.Created.IsZero() {
configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
if err != nil {
return b, err
}
if len(configs) > 0 {
// Old bucket without bucket metadata. Hence we migrate existing settings.
if err = b.convertLegacyConfigs(ctx, objectAPI, configs); err != nil {
return b, err
}
}
}
if len(configs) == 0 {
if parse {
// nothing to update, parse and proceed.
err = b.parseAllConfigs(ctx, objectAPI)
if parse {
// nothing to update, parse and proceed.
if err = b.parseAllConfigs(ctx, objectAPI); err != nil {
return b, err
}
} else {
// Old bucket without bucket metadata. Hence we migrate existing settings.
err = b.convertLegacyConfigs(ctx, objectAPI, configs)
}
if err != nil {
return b, err
}
// migrate unencrypted remote targets
if err := b.migrateTargetConfig(ctx, objectAPI); err != nil {
if err = b.migrateTargetConfig(ctx, objectAPI); err != nil {
return b, err
}
@@ -331,7 +339,7 @@ func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI Obje
for _, legacyFile := range legacyConfigs {
configFile := path.Join(bucketMetaPrefix, b.Name, legacyFile)
configData, err := readConfig(ctx, objectAPI, configFile)
configData, info, err := readConfigWithMetadata(ctx, objectAPI, configFile, ObjectOptions{})
if err != nil {
if _, ok := err.(ObjectExistsAsDirectory); ok {
// in FS mode it possible that we have actual
@@ -346,6 +354,7 @@ func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI Obje
return nil, err
}
configs[legacyFile] = configData
b.Created = info.ModTime
}
return configs, nil
+8
View File
@@ -66,6 +66,14 @@ func NewPolicySys() *PolicySys {
return &PolicySys{}
}
func getSTSConditionValues(r *http.Request, lc string, cred auth.Credentials) map[string][]string {
m := make(map[string][]string)
if d := r.Form.Get("DurationSeconds"); d != "" {
m["DurationSeconds"] = []string{d}
}
return m
}
func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[string][]string {
currTime := UTCNow()
+11 -10
View File
@@ -25,6 +25,7 @@ import (
"time"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/cachevalue"
"github.com/minio/minio/internal/logger"
)
@@ -42,7 +43,7 @@ func NewBucketQuotaSys() *BucketQuotaSys {
return &BucketQuotaSys{}
}
var bucketStorageCache timedValue
var bucketStorageCache = cachevalue.New[DataUsageInfo]()
// Init initialize bucket quota.
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
@@ -52,7 +53,7 @@ func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
bucketStorageCache.TTL = 10 * time.Second
// Rely on older value if usage loading fails from disk.
bucketStorageCache.Relax = true
bucketStorageCache.Update = func() (interface{}, error) {
bucketStorageCache.Update = func() (DataUsageInfo, error) {
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
defer done()
@@ -63,23 +64,23 @@ func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
// GetBucketUsageInfo return bucket usage info for a given bucket
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
v, err := bucketStorageCache.Get()
dui, err := bucketStorageCache.Get()
timedout := OperationTimedOut{}
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
if v != nil {
if len(dui.BucketsUsage) > 0 {
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
} else {
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
}
}
var bui BucketUsageInfo
dui, ok := v.(DataUsageInfo)
if ok {
bui = dui.BucketsUsage[bucket]
if len(dui.BucketsUsage) > 0 {
bui, ok := dui.BucketsUsage[bucket]
if ok {
return bui, nil
}
}
return bui, nil
return BucketUsageInfo{}, nil
}
// parseBucketQuota parses BucketQuota from json
+142
View File
@@ -379,3 +379,145 @@ func (s *SMA) simpleMovingAvg() float64 {
const (
defaultWindowSize = 10
)
type proxyStatsCache struct {
srProxyStats ProxyMetric
bucketStats map[string]ProxyMetric
sync.RWMutex // mutex for proxy stats
}
func newProxyStatsCache() proxyStatsCache {
return proxyStatsCache{
bucketStats: make(map[string]ProxyMetric),
}
}
func (p *proxyStatsCache) inc(bucket string, api replProxyAPI, isErr bool) {
p.Lock()
defer p.Unlock()
v, ok := p.bucketStats[bucket]
if !ok {
v = ProxyMetric{}
}
switch api {
case putObjectTaggingAPI:
if !isErr {
atomic.AddUint64(&v.PutTagTotal, 1)
atomic.AddUint64(&p.srProxyStats.PutTagTotal, 1)
} else {
atomic.AddUint64(&v.PutTagFailedTotal, 1)
atomic.AddUint64(&p.srProxyStats.PutTagFailedTotal, 1)
}
case getObjectTaggingAPI:
if !isErr {
atomic.AddUint64(&v.GetTagTotal, 1)
atomic.AddUint64(&p.srProxyStats.GetTagTotal, 1)
} else {
atomic.AddUint64(&v.GetTagFailedTotal, 1)
atomic.AddUint64(&p.srProxyStats.GetTagFailedTotal, 1)
}
case removeObjectTaggingAPI:
if !isErr {
atomic.AddUint64(&v.RmvTagTotal, 1)
atomic.AddUint64(&p.srProxyStats.RmvTagTotal, 1)
} else {
atomic.AddUint64(&v.RmvTagFailedTotal, 1)
atomic.AddUint64(&p.srProxyStats.RmvTagFailedTotal, 1)
}
case headObjectAPI:
if !isErr {
atomic.AddUint64(&v.HeadTotal, 1)
atomic.AddUint64(&p.srProxyStats.HeadTotal, 1)
} else {
atomic.AddUint64(&v.HeadFailedTotal, 1)
atomic.AddUint64(&p.srProxyStats.HeadFailedTotal, 1)
}
case getObjectAPI:
if !isErr {
atomic.AddUint64(&v.GetTotal, 1)
atomic.AddUint64(&p.srProxyStats.GetTotal, 1)
} else {
atomic.AddUint64(&v.GetFailedTotal, 1)
atomic.AddUint64(&p.srProxyStats.GetFailedTotal, 1)
}
default:
return
}
p.bucketStats[bucket] = v
}
func (p *proxyStatsCache) getBucketStats(bucket string) ProxyMetric {
p.RLock()
defer p.RUnlock()
v, ok := p.bucketStats[bucket]
if !ok {
return ProxyMetric{}
}
return ProxyMetric{
PutTagTotal: atomic.LoadUint64(&v.PutTagTotal),
GetTagTotal: atomic.LoadUint64(&v.GetTagTotal),
RmvTagTotal: atomic.LoadUint64(&v.RmvTagTotal),
HeadTotal: atomic.LoadUint64(&v.HeadTotal),
GetTotal: atomic.LoadUint64(&v.GetTotal),
PutTagFailedTotal: atomic.LoadUint64(&v.PutTagFailedTotal),
GetTagFailedTotal: atomic.LoadUint64(&v.GetTagFailedTotal),
RmvTagFailedTotal: atomic.LoadUint64(&v.RmvTagFailedTotal),
HeadFailedTotal: atomic.LoadUint64(&v.HeadFailedTotal),
GetFailedTotal: atomic.LoadUint64(&v.GetFailedTotal),
}
}
func (p *proxyStatsCache) getSiteStats() ProxyMetric {
v := p.srProxyStats
return ProxyMetric{
PutTagTotal: atomic.LoadUint64(&v.PutTagTotal),
GetTagTotal: atomic.LoadUint64(&v.GetTagTotal),
RmvTagTotal: atomic.LoadUint64(&v.RmvTagTotal),
HeadTotal: atomic.LoadUint64(&v.HeadTotal),
GetTotal: atomic.LoadUint64(&v.GetTotal),
PutTagFailedTotal: atomic.LoadUint64(&v.PutTagFailedTotal),
GetTagFailedTotal: atomic.LoadUint64(&v.GetTagFailedTotal),
RmvTagFailedTotal: atomic.LoadUint64(&v.RmvTagFailedTotal),
HeadFailedTotal: atomic.LoadUint64(&v.HeadFailedTotal),
GetFailedTotal: atomic.LoadUint64(&v.GetFailedTotal),
}
}
type replProxyAPI string
const (
putObjectTaggingAPI replProxyAPI = "PutObjectTagging"
getObjectTaggingAPI replProxyAPI = "GetObjectTagging"
removeObjectTaggingAPI replProxyAPI = "RemoveObjectTagging"
headObjectAPI replProxyAPI = "HeadObject"
getObjectAPI replProxyAPI = "GetObject"
)
// ProxyMetric holds stats for replication proxying
type ProxyMetric struct {
PutTagTotal uint64 `json:"putTaggingProxyTotal" msg:"ptc"`
GetTagTotal uint64 `json:"getTaggingProxyTotal" msg:"gtc"`
RmvTagTotal uint64 `json:"removeTaggingProxyTotal" msg:"rtc"`
GetTotal uint64 `json:"getProxyTotal" msg:"gc"`
HeadTotal uint64 `json:"headProxyTotal" msg:"hc"`
PutTagFailedTotal uint64 `json:"putTaggingProxyFailed" msg:"ptf"`
GetTagFailedTotal uint64 `json:"getTaggingProxyFailed" msg:"gtf"`
RmvTagFailedTotal uint64 `json:"removeTaggingProxyFailed" msg:"rtf"`
GetFailedTotal uint64 `json:"getProxyFailed" msg:"gf"`
HeadFailedTotal uint64 `json:"headProxyFailed" msg:"hf"`
}
func (p *ProxyMetric) add(p2 ProxyMetric) {
atomic.AddUint64(&p.GetTotal, p2.GetTotal)
atomic.AddUint64(&p.HeadTotal, p2.HeadTotal)
atomic.AddUint64(&p.GetTagTotal, p2.GetTagTotal)
atomic.AddUint64(&p.PutTagTotal, p2.PutTagTotal)
atomic.AddUint64(&p.RmvTagTotal, p2.RmvTagTotal)
atomic.AddUint64(&p.GetFailedTotal, p2.GetFailedTotal)
atomic.AddUint64(&p.HeadFailedTotal, p2.HeadFailedTotal)
atomic.AddUint64(&p.GetTagFailedTotal, p2.GetTagFailedTotal)
atomic.AddUint64(&p.PutTagFailedTotal, p2.PutTagFailedTotal)
atomic.AddUint64(&p.RmvTagFailedTotal, p2.RmvTagFailedTotal)
}
+328
View File
@@ -635,6 +635,334 @@ func (z InQueueStats) Msgsize() (s int) {
return
}
// DecodeMsg implements msgp.Decodable
func (z *ProxyMetric) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "ptc":
z.PutTagTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "PutTagTotal")
return
}
case "gtc":
z.GetTagTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "GetTagTotal")
return
}
case "rtc":
z.RmvTagTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "RmvTagTotal")
return
}
case "gc":
z.GetTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "GetTotal")
return
}
case "hc":
z.HeadTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "HeadTotal")
return
}
case "ptf":
z.PutTagFailedTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "PutTagFailedTotal")
return
}
case "gtf":
z.GetTagFailedTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "GetTagFailedTotal")
return
}
case "rtf":
z.RmvTagFailedTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "RmvTagFailedTotal")
return
}
case "gf":
z.GetFailedTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "GetFailedTotal")
return
}
case "hf":
z.HeadFailedTotal, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "HeadFailedTotal")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *ProxyMetric) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 10
// write "ptc"
err = en.Append(0x8a, 0xa3, 0x70, 0x74, 0x63)
if err != nil {
return
}
err = en.WriteUint64(z.PutTagTotal)
if err != nil {
err = msgp.WrapError(err, "PutTagTotal")
return
}
// write "gtc"
err = en.Append(0xa3, 0x67, 0x74, 0x63)
if err != nil {
return
}
err = en.WriteUint64(z.GetTagTotal)
if err != nil {
err = msgp.WrapError(err, "GetTagTotal")
return
}
// write "rtc"
err = en.Append(0xa3, 0x72, 0x74, 0x63)
if err != nil {
return
}
err = en.WriteUint64(z.RmvTagTotal)
if err != nil {
err = msgp.WrapError(err, "RmvTagTotal")
return
}
// write "gc"
err = en.Append(0xa2, 0x67, 0x63)
if err != nil {
return
}
err = en.WriteUint64(z.GetTotal)
if err != nil {
err = msgp.WrapError(err, "GetTotal")
return
}
// write "hc"
err = en.Append(0xa2, 0x68, 0x63)
if err != nil {
return
}
err = en.WriteUint64(z.HeadTotal)
if err != nil {
err = msgp.WrapError(err, "HeadTotal")
return
}
// write "ptf"
err = en.Append(0xa3, 0x70, 0x74, 0x66)
if err != nil {
return
}
err = en.WriteUint64(z.PutTagFailedTotal)
if err != nil {
err = msgp.WrapError(err, "PutTagFailedTotal")
return
}
// write "gtf"
err = en.Append(0xa3, 0x67, 0x74, 0x66)
if err != nil {
return
}
err = en.WriteUint64(z.GetTagFailedTotal)
if err != nil {
err = msgp.WrapError(err, "GetTagFailedTotal")
return
}
// write "rtf"
err = en.Append(0xa3, 0x72, 0x74, 0x66)
if err != nil {
return
}
err = en.WriteUint64(z.RmvTagFailedTotal)
if err != nil {
err = msgp.WrapError(err, "RmvTagFailedTotal")
return
}
// write "gf"
err = en.Append(0xa2, 0x67, 0x66)
if err != nil {
return
}
err = en.WriteUint64(z.GetFailedTotal)
if err != nil {
err = msgp.WrapError(err, "GetFailedTotal")
return
}
// write "hf"
err = en.Append(0xa2, 0x68, 0x66)
if err != nil {
return
}
err = en.WriteUint64(z.HeadFailedTotal)
if err != nil {
err = msgp.WrapError(err, "HeadFailedTotal")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *ProxyMetric) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 10
// string "ptc"
o = append(o, 0x8a, 0xa3, 0x70, 0x74, 0x63)
o = msgp.AppendUint64(o, z.PutTagTotal)
// string "gtc"
o = append(o, 0xa3, 0x67, 0x74, 0x63)
o = msgp.AppendUint64(o, z.GetTagTotal)
// string "rtc"
o = append(o, 0xa3, 0x72, 0x74, 0x63)
o = msgp.AppendUint64(o, z.RmvTagTotal)
// string "gc"
o = append(o, 0xa2, 0x67, 0x63)
o = msgp.AppendUint64(o, z.GetTotal)
// string "hc"
o = append(o, 0xa2, 0x68, 0x63)
o = msgp.AppendUint64(o, z.HeadTotal)
// string "ptf"
o = append(o, 0xa3, 0x70, 0x74, 0x66)
o = msgp.AppendUint64(o, z.PutTagFailedTotal)
// string "gtf"
o = append(o, 0xa3, 0x67, 0x74, 0x66)
o = msgp.AppendUint64(o, z.GetTagFailedTotal)
// string "rtf"
o = append(o, 0xa3, 0x72, 0x74, 0x66)
o = msgp.AppendUint64(o, z.RmvTagFailedTotal)
// string "gf"
o = append(o, 0xa2, 0x67, 0x66)
o = msgp.AppendUint64(o, z.GetFailedTotal)
// string "hf"
o = append(o, 0xa2, 0x68, 0x66)
o = msgp.AppendUint64(o, z.HeadFailedTotal)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *ProxyMetric) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "ptc":
z.PutTagTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "PutTagTotal")
return
}
case "gtc":
z.GetTagTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "GetTagTotal")
return
}
case "rtc":
z.RmvTagTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "RmvTagTotal")
return
}
case "gc":
z.GetTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "GetTotal")
return
}
case "hc":
z.HeadTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "HeadTotal")
return
}
case "ptf":
z.PutTagFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "PutTagFailedTotal")
return
}
case "gtf":
z.GetTagFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "GetTagFailedTotal")
return
}
case "rtf":
z.RmvTagFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "RmvTagFailedTotal")
return
}
case "gf":
z.GetFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "GetFailedTotal")
return
}
case "hf":
z.HeadFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "HeadFailedTotal")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *ProxyMetric) Msgsize() (s int) {
s = 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 3 + msgp.Uint64Size + 3 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 3 + msgp.Uint64Size + 3 + msgp.Uint64Size
return
}
// DecodeMsg implements msgp.Decodable
func (z *QStat) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
+113
View File
@@ -348,6 +348,119 @@ func BenchmarkDecodeInQueueStats(b *testing.B) {
}
}
func TestMarshalUnmarshalProxyMetric(t *testing.T) {
v := ProxyMetric{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgProxyMetric(b *testing.B) {
v := ProxyMetric{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgProxyMetric(b *testing.B) {
v := ProxyMetric{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalProxyMetric(b *testing.B) {
v := ProxyMetric{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodeProxyMetric(t *testing.T) {
v := ProxyMetric{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodeProxyMetric Msgsize() is inaccurate")
}
vn := ProxyMetric{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodeProxyMetric(b *testing.B) {
v := ProxyMetric{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodeProxyMetric(b *testing.B) {
v := ProxyMetric{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshalQStat(t *testing.T) {
v := QStat{}
bts, err := v.MarshalMsg(nil)
+21 -2
View File
@@ -45,6 +45,8 @@ type ReplicationStats struct {
workers *ActiveWorkerStat
// queue stats cache
qCache queueCache
pCache proxyStatsCache
// mrf backlog stats
mrfStats ReplicationMRFStats
// for bucket replication, continue to use existing cache
@@ -305,6 +307,7 @@ func (r *ReplicationStats) getSRMetricsForNode() SRMetricsSummary {
Queued: r.qCache.getSiteStats(),
ActiveWorkers: r.ActiveWorkers(),
Metrics: r.srStats.get(),
Proxied: r.pCache.getSiteStats(),
ReplicaSize: atomic.LoadInt64(&r.srStats.ReplicaSize),
ReplicaCount: atomic.LoadInt64(&r.srStats.ReplicaCount),
}
@@ -333,6 +336,7 @@ func NewReplicationStats(ctx context.Context, objectAPI ObjectLayer) *Replicatio
rs := ReplicationStats{
Cache: make(map[string]*BucketReplicationStats),
qCache: newQueueCache(r),
pCache: newProxyStatsCache(),
srStats: newSRStats(),
movingAvgTicker: time.NewTicker(2 * time.Second),
wTimer: time.NewTicker(2 * time.Second),
@@ -371,6 +375,7 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, bucket
Stats: make(map[string]*BucketReplicationStat),
},
QueueStats: ReplicationQueueStats{},
ProxyStats: ProxyMetric{},
}
return bs
}
@@ -430,11 +435,16 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, bucket
for _, bs := range bucketStats {
qs.Nodes = append(qs.Nodes, bs.QueueStats.Nodes...)
}
qs.Uptime = UTCNow().Unix() - globalBootTime.Unix()
var ps ProxyMetric
for _, bs := range bucketStats {
ps.add(bs.ProxyStats)
}
bs = BucketStats{
ReplicationStats: s,
QueueStats: qs,
ProxyStats: ps,
}
r.mostRecentStatsMu.Lock()
if len(r.mostRecentStats.Stats) == 0 {
@@ -454,7 +464,7 @@ func (r *ReplicationStats) getLatestReplicationStats(bucket string) (s BucketSta
return r.calculateBucketReplicationStats(bucket, bucketStats)
}
func (r *ReplicationStats) incQ(bucket string, sz int64, isDeleleRepl bool, opType replication.Type) {
func (r *ReplicationStats) incQ(bucket string, sz int64, isDeleteRepl bool, opType replication.Type) {
r.qCache.Lock()
defer r.qCache.Unlock()
v, ok := r.qCache.bucketStats[bucket]
@@ -482,3 +492,12 @@ func (r *ReplicationStats) decQ(bucket string, sz int64, isDelMarker bool, opTyp
atomic.AddInt64(&r.qCache.srQueueStats.nowBytes, -1*sz)
atomic.AddInt64(&r.qCache.srQueueStats.nowCount, -1)
}
// incProxy increments proxy metrics for proxied calls
func (r *ReplicationStats) incProxy(bucket string, api replProxyAPI, isErr bool) {
r.pCache.inc(bucket, api, isErr)
}
func (r *ReplicationStats) getProxyStats(bucket string) ProxyMetric {
return r.pCache.getBucketStats(bucket)
}
+14 -6
View File
@@ -179,7 +179,7 @@ func checkRemoteEndpoint(ctx context.Context, epURL *url.URL) error {
}
client := &http.Client{
Transport: NewHTTPTransport(),
Transport: globalRemoteTargetTransport,
Timeout: 10 * time.Second,
}
@@ -2235,6 +2235,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
if opts.ProxyRequest || (opts.ProxyHeaderSet && !opts.ProxyRequest) { // true only when site B sets MinIOSourceProxyRequest header
return nil, oi, proxy
}
var perr error
for _, t := range proxyTargets.Targets {
tgt = globalBucketTargetSys.GetRemoteTargetClient(bucket, t.Arn)
if tgt == nil || globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
@@ -2264,6 +2265,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
objInfo, err := tgt.StatObject(ctx, t.TargetBucket, object, gopts)
if err != nil {
perr = err
if isErrInvalidRange(ErrorRespToObjectError(err, bucket, object)) {
return nil, oi, proxyResult{Err: err}
}
@@ -2300,6 +2302,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
}
return tgt, oi, proxyResult{Proxy: true}
}
proxy.Err = perr
return nil, oi, proxy
}
@@ -2406,8 +2409,7 @@ func proxyTaggingToRepTarget(ctx context.Context, bucket, object string, tags *t
taggedCount++
continue
}
errCode := minio.ToErrorResponse(err).Code
if errCode != "NoSuchKey" && errCode != "NoSuchVersion" {
if err != nil {
terr = err
}
}
@@ -2467,6 +2469,9 @@ func proxyGetTaggingToRepTarget(ctx context.Context, bucket, object string, opts
tgs, _ = tags.MapToObjectTags(tagSlc[idx])
}
}
if len(errs) == 1 {
proxy.Err = errs[0]
}
return tgs, proxy
}
@@ -3213,6 +3218,9 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
return roi
}
if isVeeamSOSAPIObject(oi.Name) {
return roi
}
if rcfg.Config == nil || rcfg.remotes == nil {
return roi
}
@@ -3385,12 +3393,12 @@ func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntr
}
globalLocalDrivesMu.RLock()
localDrives := globalLocalDrives
localDrives := cloneDrives(globalLocalDrives)
globalLocalDrivesMu.RUnlock()
for _, localDrive := range localDrives {
r := newReader()
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
err := localDrive.CreateFile(ctx, "", minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
r.Close()
if err == nil {
break
@@ -3452,7 +3460,7 @@ func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
}
globalLocalDrivesMu.RLock()
localDrives := globalLocalDrives
localDrives := cloneDrives(globalLocalDrives)
globalLocalDrivesMu.RUnlock()
for _, localDrive := range localDrives {
+1
View File
@@ -154,6 +154,7 @@ type BucketStats struct {
Uptime int64 `json:"uptime"`
ReplicationStats BucketReplicationStats `json:"currStats"` // current replication stats since cluster startup
QueueStats ReplicationQueueStats `json:"queueStats"` // replication queue stats
ProxyStats ProxyMetric `json:"proxyStats"`
}
// BucketReplicationStats represents inline replication statistics
+34 -5
View File
@@ -1142,6 +1142,12 @@ func (z *BucketStats) DecodeMsg(dc *msgp.Reader) (err error) {
}
}
}
case "ProxyStats":
err = z.ProxyStats.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "ProxyStats")
return
}
default:
err = dc.Skip()
if err != nil {
@@ -1155,9 +1161,9 @@ func (z *BucketStats) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable
func (z *BucketStats) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 3
// map header, size 4
// write "Uptime"
err = en.Append(0x83, 0xa6, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65)
err = en.Append(0x84, 0xa6, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65)
if err != nil {
return
}
@@ -1209,15 +1215,25 @@ func (z *BucketStats) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "QueueStats", "Uptime")
return
}
// write "ProxyStats"
err = en.Append(0xaa, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73)
if err != nil {
return
}
err = z.ProxyStats.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "ProxyStats")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *BucketStats) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 3
// map header, size 4
// string "Uptime"
o = append(o, 0x83, 0xa6, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65)
o = append(o, 0x84, 0xa6, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65)
o = msgp.AppendInt64(o, z.Uptime)
// string "ReplicationStats"
o = append(o, 0xb0, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73)
@@ -1242,6 +1258,13 @@ func (z *BucketStats) MarshalMsg(b []byte) (o []byte, err error) {
// string "Uptime"
o = append(o, 0xa6, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65)
o = msgp.AppendInt64(o, z.QueueStats.Uptime)
// string "ProxyStats"
o = append(o, 0xaa, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73)
o, err = z.ProxyStats.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "ProxyStats")
return
}
return
}
@@ -1323,6 +1346,12 @@ func (z *BucketStats) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
}
}
case "ProxyStats":
bts, err = z.ProxyStats.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "ProxyStats")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
@@ -1341,7 +1370,7 @@ func (z *BucketStats) Msgsize() (s int) {
for za0001 := range z.QueueStats.Nodes {
s += z.QueueStats.Nodes[za0001].Msgsize()
}
s += 7 + msgp.Int64Size
s += 7 + msgp.Int64Size + 11 + z.ProxyStats.Msgsize()
return
}
+4 -1
View File
@@ -105,6 +105,7 @@ func init() {
gob.Register(StorageErr(""))
gob.Register(madmin.TimeInfo{})
gob.Register(madmin.XFSErrorConfigs{})
gob.Register(map[string]string{})
gob.Register(map[string]interface{}{})
// All minio-go and madmin-go API operations shall be performed only once,
@@ -393,10 +394,12 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
ctxt.ConnReadDeadline = ctx.Duration("conn-read-deadline")
ctxt.ConnWriteDeadline = ctx.Duration("conn-write-deadline")
ctxt.ConnClientReadDeadline = ctx.Duration("conn-client-read-deadline")
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
ctxt.IdleTimeout = ctx.Duration("idle-timeout")
ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")
ctxt.MaxIdleConnsPerHost = ctx.Int("max-idle-conns-per-host")
if conf := ctx.String("config"); len(conf) > 0 {
err = mergeServerCtxtFromConfigFile(conf, ctxt)
@@ -1033,7 +1036,7 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
}
if err = manager.AddCertificate(certFile, keyFile); err != nil {
err = fmt.Errorf("Unable to load TLS certificate '%s,%s': %w", certFile, keyFile, err)
logger.LogIf(GlobalContext, err, logger.Minio)
logger.LogIf(GlobalContext, err, logger.ErrorKind)
}
}
secureConn = true
+2
View File
@@ -602,6 +602,8 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
// update dynamic scanner values.
scannerIdleMode.Store(scannerCfg.IdleMode)
scannerCycle.Store(scannerCfg.Cycle)
scannerExcessObjectVersions.Store(scannerCfg.ExcessVersions)
scannerExcessFolders.Store(scannerCfg.ExcessFolders)
logger.LogIf(ctx, scannerSleeper.Update(scannerCfg.Delay, scannerCfg.MaxWait))
case config.LoggerWebhookSubSys:
loggerCfg, err := logger.LookupConfigForSubSys(ctx, s, config.LoggerWebhookSubSys)
+1 -1
View File
@@ -196,7 +196,7 @@ func (sys *HTTPConsoleLoggerSys) Send(ctx context.Context, entry interface{}) er
sys.logBuf.Value = lg
sys.logBuf = sys.logBuf.Next()
sys.Unlock()
err := sys.console.Send(entry, string(logger.All))
err := sys.console.Send(entry)
if err != nil {
atomic.AddInt64(&sys.failedMessages, 1)
}
+67 -28
View File
@@ -57,18 +57,17 @@ const (
healDeleteDangling = true
healObjectSelectProb = 1024 // Overall probability of a file being scanned; one in n.
dataScannerExcessiveVersionsThreshold = 100 // Issue a warning when a single object has more versions than this
dataScannerExcessiveFoldersThreshold = 50000 // Issue a warning when a folder has more subfolders than this in a *set*
)
var (
globalHealConfig heal.Config
// Sleeper values are updated when config is loaded.
scannerSleeper = newDynamicSleeper(2, time.Second, true) // Keep defaults same as config defaults
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
scannerIdleMode = uatomic.NewInt32(0) // default is throttled when idle
scannerSleeper = newDynamicSleeper(2, time.Second, true) // Keep defaults same as config defaults
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
scannerIdleMode = uatomic.NewInt32(0) // default is throttled when idle
scannerExcessObjectVersions = uatomic.NewInt64(100)
scannerExcessFolders = uatomic.NewInt64(50000)
)
// initDataScanner will start the scanner in the background.
@@ -530,18 +529,27 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
len(existingFolders)+len(newFolders) >= dataScannerCompactAtFolders ||
len(existingFolders)+len(newFolders) >= dataScannerForceCompactAtFolders
if len(existingFolders)+len(newFolders) > dataScannerExcessiveFoldersThreshold {
// Notify object accessed via a GET request.
if totalFolders := len(existingFolders) + len(newFolders); totalFolders > int(scannerExcessFolders.Load()) {
prefixName := strings.TrimSuffix(folder.name, "/") + "/"
sendEvent(eventArgs{
EventName: event.PrefixManyFolders,
BucketName: f.root,
Object: ObjectInfo{
Name: strings.TrimSuffix(folder.name, "/") + "/",
Size: int64(len(existingFolders) + len(newFolders)),
Name: prefixName,
Size: int64(totalFolders),
},
UserAgent: "scanner",
UserAgent: "Scanner",
Host: globalMinioHost,
})
auditLogInternal(context.Background(), AuditLogOptions{
Event: "scanner:manyprefixes",
APIName: "Scanner",
Bucket: f.root,
Object: prefixName,
Tags: map[string]interface{}{
"x-minio-prefixes-total": strconv.Itoa(totalFolders),
},
})
}
if !into.Compacted && shouldCompact {
into.Compacted = true
@@ -955,15 +963,6 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), lcEvt.Action)
}
}
defer func() {
if lcEvt.Action != lifecycle.NoneAction {
numVersions := uint64(1)
if lcEvt.Action == lifecycle.DeleteAllVersionsAction {
numVersions = uint64(oi.NumVersions)
}
globalScannerMetrics.timeILM(lcEvt.Action)(numVersions)
}
}()
switch lcEvt.Action {
// This version doesn't contribute towards sizeS only when it is permanently deleted.
@@ -1101,7 +1100,7 @@ func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fi
}
// Check if we have many versions after applyNewerNoncurrentVersionLimit.
if len(objInfos) > dataScannerExcessiveVersionsThreshold {
if len(objInfos) > int(scannerExcessObjectVersions.Load()) {
// Notify object accessed via a GET request.
sendEvent(eventArgs{
EventName: event.ObjectManyVersions,
@@ -1109,9 +1108,19 @@ func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fi
Object: ObjectInfo{
Name: i.objectPath(),
},
UserAgent: "Internal: [Scanner]",
UserAgent: "Scanner",
Host: globalLocalNodeName,
RespElements: map[string]string{"x-minio-versions": strconv.Itoa(len(fivs))},
RespElements: map[string]string{"x-minio-versions": strconv.Itoa(len(objInfos))},
})
auditLogInternal(context.Background(), AuditLogOptions{
Event: "scanner:manyversions",
APIName: "Scanner",
Bucket: i.bucket,
Object: i.objectPath(),
Tags: map[string]interface{}{
"x-minio-versions": strconv.Itoa(len(objInfos)),
},
})
}
@@ -1210,7 +1219,17 @@ func applyTransitionRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo)
}
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) bool {
if err := expireTransitionedObject(ctx, objLayer, &obj, obj.ToLifecycleOpts(), lcEvent, src); err != nil {
var err error
defer func() {
if err != nil {
return
}
// Note: DeleteAllVersions action is not supported for
// transitioned objects
globalScannerMetrics.timeILM(lcEvent.Action)(1)
}()
if err = expireTransitionedObject(ctx, objLayer, &obj, lcEvent, src); err != nil {
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
return false
}
@@ -1237,8 +1256,25 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
if lcEvent.Action.DeleteAll() {
opts.DeletePrefix = true
}
var (
dobj ObjectInfo
err error
)
defer func() {
if err != nil {
return
}
obj, err := objLayer.DeleteObject(ctx, obj.Bucket, obj.Name, opts)
if lcEvent.Action != lifecycle.NoneAction {
numVersions := uint64(1)
if lcEvent.Action == lifecycle.DeleteAllVersionsAction {
numVersions = uint64(obj.NumVersions)
}
globalScannerMetrics.timeILM(lcEvent.Action)(numVersions)
}
}()
dobj, err = objLayer.DeleteObject(ctx, obj.Bucket, obj.Name, opts)
if err != nil {
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
return false
@@ -1247,10 +1283,13 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
logger.LogOnceIf(ctx, err, "non-transition-expiry")
return false
}
if dobj.Name == "" {
dobj = obj
}
tags := newLifecycleAuditEvent(src, lcEvent).Tags()
// Send audit for the lifecycle delete operation
auditLogLifecycle(ctx, obj, ILMExpiry, tags, traceFn)
auditLogLifecycle(ctx, dobj, ILMExpiry, tags, traceFn)
eventName := event.ObjectRemovedDelete
if obj.DeleteMarker {
@@ -1260,8 +1299,8 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
// Notify object deleted event.
sendEvent(eventArgs{
EventName: eventName,
BucketName: obj.Bucket,
Object: obj,
BucketName: dobj.Bucket,
Object: dobj,
UserAgent: "Internal: [ILM-Expiry]",
Host: globalLocalNodeName,
})
+5
View File
@@ -71,6 +71,10 @@ type BucketUsageInfo struct {
// DataUsageInfo represents data usage stats of the underlying Object API
type DataUsageInfo struct {
TotalCapacity uint64 `json:"capacity,omitempty"`
TotalUsedCapacity uint64 `json:"usedCapacity,omitempty"`
TotalFreeCapacity uint64 `json:"freeCapacity,omitempty"`
// LastUpdate is the timestamp of when the data usage info was last updated.
// This does not indicate a full scan.
LastUpdate time.Time `json:"lastUpdate"`
@@ -87,6 +91,7 @@ type DataUsageInfo struct {
// Objects total size across all buckets
ObjectsTotalSize uint64 `json:"objectsTotalSize"`
ReplicationInfo map[string]BucketTargetUsageInfo `json:"objectsReplicationInfo"`
// Total number of buckets in this cluster
BucketsCount uint64 `json:"bucketsCount"`
+7 -9
View File
@@ -24,6 +24,7 @@ import (
"time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/internal/cachevalue"
"github.com/minio/minio/internal/logger"
)
@@ -62,7 +63,7 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
}
}
var prefixUsageCache timedValue
var prefixUsageCache = cachevalue.New[map[string]uint64]()
// loadPrefixUsageFromBackend returns prefix usages found in passed buckets
//
@@ -81,7 +82,7 @@ func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket
// No need to fail upon Update() error, fallback to old value.
prefixUsageCache.Relax = true
prefixUsageCache.Update = func() (interface{}, error) {
prefixUsageCache.Update = func() (map[string]uint64, error) {
m := make(map[string]uint64)
for _, pool := range z.serverPools {
for _, er := range pool.sets {
@@ -109,12 +110,7 @@ func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket
}
})
v, _ := prefixUsageCache.Get()
if v != nil {
return v.(map[string]uint64), nil
}
return map[string]uint64{}, nil
return prefixUsageCache.Get()
}
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
@@ -155,7 +151,9 @@ func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsag
bui.ReplicationFailedSizeV1 > 0 || bui.ReplicationPendingCountV1 > 0 {
cfg, _ := getReplicationConfig(GlobalContext, bucket)
if cfg != nil && cfg.RoleArn != "" {
dataUsageInfo.ReplicationInfo = make(map[string]BucketTargetUsageInfo)
if dataUsageInfo.ReplicationInfo == nil {
dataUsageInfo.ReplicationInfo = make(map[string]BucketTargetUsageInfo)
}
dataUsageInfo.ReplicationInfo[cfg.RoleArn] = BucketTargetUsageInfo{
ReplicationFailedSize: bui.ReplicationFailedSizeV1,
ReplicationFailedCount: bui.ReplicationFailedCountV1,
+72 -13
View File
@@ -62,8 +62,9 @@ type ProxyEndpoint struct {
// Node holds information about a node in this cluster
type Node struct {
*url.URL
Pools []int
IsLocal bool
Pools []int
IsLocal bool
GridHost string
}
// Endpoint - any type of endpoint.
@@ -254,6 +255,7 @@ func (l EndpointServerPools) GetNodes() (nodes []Node) {
Scheme: ep.Scheme,
Host: ep.Host,
}
node.GridHost = ep.GridHost()
}
if !slices.Contains(node.Pools, ep.PoolIdx) {
node.Pools = append(node.Pools, ep.PoolIdx)
@@ -411,6 +413,70 @@ func (l EndpointServerPools) GridHosts() (gridHosts []string, gridLocal string)
return gridHosts, gridLocal
}
// FindGridHostsFromPeerPool will return a matching peerPool from provided peer (as string)
func (l EndpointServerPools) FindGridHostsFromPeerPool(peer string) []int {
if peer == "" {
return nil
}
var pools []int
for _, ep := range l {
for _, endpoint := range ep.Endpoints {
if endpoint.IsLocal {
continue
}
if !slices.Contains(pools, endpoint.PoolIdx) {
pools = append(pools, endpoint.PoolIdx)
}
}
}
return pools
}
// FindGridHostsFromPeerStr will return a matching peer from provided peer (as string)
func (l EndpointServerPools) FindGridHostsFromPeerStr(peer string) (peerGrid string) {
if peer == "" {
return ""
}
for _, ep := range l {
for _, endpoint := range ep.Endpoints {
if endpoint.IsLocal {
continue
}
if endpoint.Host == peer {
return endpoint.GridHost()
}
}
}
return ""
}
// FindGridHostsFromPeer will return a matching peer from provided peer.
func (l EndpointServerPools) FindGridHostsFromPeer(peer *xnet.Host) (peerGrid string) {
if peer == nil {
return ""
}
for _, ep := range l {
for _, endpoint := range ep.Endpoints {
if endpoint.IsLocal {
continue
}
host, err := xnet.ParseHost(endpoint.Host)
if err != nil {
continue
}
if host.String() == peer.String() {
return endpoint.GridHost()
}
}
}
return ""
}
// Hostnames - returns list of unique hostnames
func (l EndpointServerPools) Hostnames() []string {
foundSet := set.NewStringSet()
@@ -501,13 +567,6 @@ func (endpoints Endpoints) GetAllStrings() (all []string) {
func hostResolveToLocalhost(endpoint Endpoint) bool {
hostIPs, err := getHostIP(endpoint.Hostname())
if err != nil {
// Log the message to console about the host resolving
reqInfo := (&logger.ReqInfo{}).AppendTags(
"host",
endpoint.Hostname(),
)
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
return false
}
var loopback int
@@ -579,7 +638,7 @@ func (endpoints Endpoints) UpdateIsLocal() error {
ctx := logger.SetReqInfo(GlobalContext,
reqInfo)
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
endpoints[i].Hostname()), endpoints[i].Hostname(), logger.Application)
endpoints[i].Hostname()), endpoints[i].Hostname(), logger.ErrorKind)
}
continue
@@ -608,7 +667,7 @@ func (endpoints Endpoints) UpdateIsLocal() error {
))
ctx := logger.SetReqInfo(GlobalContext,
reqInfo)
logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.Application)
logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.ErrorKind)
}
} else {
resolvedList[i] = true
@@ -773,7 +832,7 @@ func (p PoolEndpointList) UpdateIsLocal() error {
ctx := logger.SetReqInfo(GlobalContext,
reqInfo)
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
endpoint.Hostname()), endpoint.Hostname(), logger.Application)
endpoint.Hostname()), endpoint.Hostname(), logger.ErrorKind)
}
continue
}
@@ -801,7 +860,7 @@ func (p PoolEndpointList) UpdateIsLocal() error {
))
ctx := logger.SetReqInfo(GlobalContext,
reqInfo)
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
logger.LogOnceIf(ctx, fmt.Errorf("Unable to resolve DNS for %s: %w", endpoint, err), endpoint.Hostname(), logger.ErrorKind)
}
} else {
resolvedList[endpoint] = true
+1 -7
View File
@@ -80,11 +80,9 @@ func (e *Erasure) EncodeData(ctx context.Context, data []byte) ([][]byte, error)
}
encoded, err := e.encoder().Split(data)
if err != nil {
logger.LogIf(ctx, err)
return nil, err
}
if err = e.encoder().Encode(encoded); err != nil {
logger.LogIf(ctx, err)
return nil, err
}
return encoded, nil
@@ -111,11 +109,7 @@ func (e *Erasure) DecodeDataBlocks(data [][]byte) error {
// DecodeDataAndParityBlocks decodes the given erasure-coded data and verifies it.
// It returns an error if the decoding failed.
func (e *Erasure) DecodeDataAndParityBlocks(ctx context.Context, data [][]byte) error {
if err := e.encoder().Reconstruct(data); err != nil {
logger.LogIf(ctx, err)
return err
}
return nil
return e.encoder().Reconstruct(data)
}
// ShardSize - returns actual shared size from erasure blockSize.
-7
View File
@@ -26,7 +26,6 @@ import (
"sync/atomic"
xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger"
)
// Reads in parallel from readers.
@@ -211,11 +210,9 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
// A set of preferred drives can be supplied. In that case they will be used and the data reconstructed.
func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.ReaderAt, offset, length, totalLength int64, prefer []bool) (written int64, derr error) {
if offset < 0 || length < 0 {
logger.LogIf(ctx, errInvalidArgument)
return -1, errInvalidArgument
}
if offset+length > totalLength {
logger.LogIf(ctx, errInvalidArgument)
return -1, errInvalidArgument
}
@@ -269,7 +266,6 @@ func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.Read
}
if err = e.DecodeDataBlocks(bufs); err != nil {
logger.LogIf(ctx, err)
return -1, err
}
@@ -282,7 +278,6 @@ func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.Read
}
if bytesWritten != length {
logger.LogIf(ctx, errLessData)
return bytesWritten, errLessData
}
@@ -321,7 +316,6 @@ func (e Erasure) Heal(ctx context.Context, writers []io.Writer, readers []io.Rea
}
if err = e.DecodeDataAndParityBlocks(ctx, bufs); err != nil {
logger.LogOnceIf(ctx, err, "erasure-heal-decode")
return err
}
@@ -332,7 +326,6 @@ func (e Erasure) Heal(ctx context.Context, writers []io.Writer, readers []io.Rea
}
if err = w.Write(ctx, bufs); err != nil {
logger.LogOnceIf(ctx, err, "erasure-heal-write")
return err
}
}
+3 -3
View File
@@ -106,7 +106,7 @@ func TestErasureDecode(t *testing.T) {
buffer := make([]byte, test.blocksize, 2*test.blocksize)
writers := make([]io.Writer, len(disks))
for i, disk := range disks {
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(test.data), writeAlgorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(test.data), writeAlgorithm, erasure.ShardSize())
}
n, err := erasure.Encode(context.Background(), bytes.NewReader(data), writers, buffer, erasure.dataBlocks+1)
closeBitrotWriters(writers)
@@ -228,7 +228,7 @@ func TestErasureDecodeRandomOffsetLength(t *testing.T) {
if disk == nil {
continue
}
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(length), DefaultBitrotAlgorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(length), DefaultBitrotAlgorithm, erasure.ShardSize())
}
// 10000 iterations with random offsets and lengths.
@@ -297,7 +297,7 @@ func benchmarkErasureDecode(data, parity, dataDown, parityDown int, size int64,
if disk == nil {
continue
}
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
}
content := make([]byte, size)
+3 -9
View File
@@ -22,9 +22,6 @@ import (
"fmt"
"io"
"sync"
"github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger"
)
// Writes in parallel to writers
@@ -92,29 +89,26 @@ func (e *Erasure) Encode(ctx context.Context, src io.Reader, writers []io.Writer
io.EOF,
io.ErrUnexpectedEOF,
}...) {
if !hash.IsChecksumMismatch(err) {
logger.LogIf(ctx, err)
}
return 0, err
}
}
eof := err == io.EOF || err == io.ErrUnexpectedEOF
if n == 0 && total != 0 {
// Reached EOF, nothing more to be done.
break
}
// We take care of the situation where if n == 0 and total == 0 by creating empty data and parity files.
blocks, err = e.EncodeData(ctx, buf[:n])
if err != nil {
logger.LogIf(ctx, err)
return 0, err
}
if err = writer.Write(ctx, blocks); err != nil {
logger.LogIf(ctx, err)
return 0, err
}
total += int64(n)
if eof {
break
+4 -4
View File
@@ -41,7 +41,7 @@ func (a badDisk) ReadFileStream(ctx context.Context, volume, path string, offset
return nil, errFaultyDisk
}
func (a badDisk) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error {
func (a badDisk) CreateFile(ctx context.Context, origvolume, volume, path string, size int64, reader io.Reader) error {
return errFaultyDisk
}
@@ -103,7 +103,7 @@ func TestErasureEncode(t *testing.T) {
if disk == OfflineDisk {
continue
}
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
}
n, err := erasure.Encode(context.Background(), bytes.NewReader(data[test.offset:]), writers, buffer, erasure.dataBlocks+1)
closeBitrotWriters(writers)
@@ -127,7 +127,7 @@ func TestErasureEncode(t *testing.T) {
if disk == nil {
continue
}
writers[i] = newBitrotWriter(disk, "testbucket", "object2", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, "", "testbucket", "object2", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
}
for j := range disks[:test.offDisks] {
switch w := writers[j].(type) {
@@ -192,7 +192,7 @@ func benchmarkErasureEncode(data, parity, dataDown, parityDown int, size int64,
Recursive: false,
Immediate: false,
})
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
}
_, err := erasure.Encode(context.Background(), bytes.NewReader(content), writers, buffer, erasure.dataBlocks+1)
closeBitrotWriters(writers)
+2 -2
View File
@@ -86,7 +86,7 @@ func TestErasureHeal(t *testing.T) {
buffer := make([]byte, test.blocksize, 2*test.blocksize)
writers := make([]io.Writer, len(disks))
for i, disk := range disks {
writers[i] = newBitrotWriter(disk, "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, "", "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
}
_, err = erasure.Encode(context.Background(), bytes.NewReader(data), writers, buffer, erasure.dataBlocks+1)
closeBitrotWriters(writers)
@@ -128,7 +128,7 @@ func TestErasureHeal(t *testing.T) {
continue
}
os.Remove(pathJoin(disk.String(), "testbucket", "testobject"))
staleWriters[i] = newBitrotWriter(disk, "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
staleWriters[i] = newBitrotWriter(disk, "", "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
}
// test case setup is complete - now call Heal()
+7 -7
View File
@@ -58,8 +58,8 @@ func commonETags(etags []string) (etag string, maxima int) {
}
// commonTime returns a maximally occurring time from a list of time.
func commonTimeAndOccurence(times []time.Time, group time.Duration) (maxTime time.Time, maxima int) {
timeOccurenceMap := make(map[int64]int, len(times))
func commonTimeAndOccurrence(times []time.Time, group time.Duration) (maxTime time.Time, maxima int) {
timeOccurrenceMap := make(map[int64]int, len(times))
groupNano := group.Nanoseconds()
// Ignore the uuid sentinel and count the rest.
for _, t := range times {
@@ -68,7 +68,7 @@ func commonTimeAndOccurence(times []time.Time, group time.Duration) (maxTime tim
}
nano := t.UnixNano()
if group > 0 {
for k := range timeOccurenceMap {
for k := range timeOccurrenceMap {
if k == nano {
// We add to ourself later
continue
@@ -79,12 +79,12 @@ func commonTimeAndOccurence(times []time.Time, group time.Duration) (maxTime tim
}
// We are within the limit
if diff < groupNano {
timeOccurenceMap[k]++
timeOccurrenceMap[k]++
}
}
}
// Add ourself...
timeOccurenceMap[nano]++
timeOccurrenceMap[nano]++
}
maxima = 0 // Counter for remembering max occurrence of elements.
@@ -92,7 +92,7 @@ func commonTimeAndOccurence(times []time.Time, group time.Duration) (maxTime tim
// Find the common cardinality from previously collected
// occurrences of elements.
for nano, count := range timeOccurenceMap {
for nano, count := range timeOccurrenceMap {
if count < maxima {
continue
}
@@ -111,7 +111,7 @@ func commonTimeAndOccurence(times []time.Time, group time.Duration) (maxTime tim
// commonTime returns a maximally occurring time from a list of time if it
// occurs >= quorum, else return timeSentinel
func commonTime(modTimes []time.Time, quorum int) time.Time {
if modTime, count := commonTimeAndOccurence(modTimes, 0); count >= quorum {
if modTime, count := commonTimeAndOccurrence(modTimes, 0); count >= quorum {
return modTime
}
+5 -5
View File
@@ -246,7 +246,7 @@ func TestListOnlineDisks(t *testing.T) {
t.Fatalf("Failed to putObject %v", err)
}
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
fi, err := getLatestFileInfo(ctx, partsMetadata, z.serverPools[0].sets[0].defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo %v", err)
@@ -424,7 +424,7 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
t.Fatalf("Failed to putObject %v", err)
}
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", true, true)
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", true, true)
fi, err := getLatestFileInfo(ctx, partsMetadata, z.serverPools[0].sets[0].defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo %v", err)
@@ -437,7 +437,7 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
partsMetadata[j].ModTime = test.modTimes[j]
}
if erasureDisks, err = writeUniqueFileInfo(ctx, erasureDisks, bucket, object, partsMetadata, diskCount(erasureDisks)); err != nil {
if erasureDisks, err = writeUniqueFileInfo(ctx, erasureDisks, "", bucket, object, partsMetadata, diskCount(erasureDisks)); err != nil {
t.Fatal(ctx, err)
}
@@ -534,7 +534,7 @@ func TestDisksWithAllParts(t *testing.T) {
t.Fatalf("Failed to putObject %v", err)
}
_, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
_, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
readQuorum := len(erasureDisks) / 2
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
t.Fatalf("Failed to read xl meta data %v", reducedErr)
@@ -542,7 +542,7 @@ func TestDisksWithAllParts(t *testing.T) {
// Test 1: Test that all disks are returned without any failures with
// unmodified meta data
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
if err != nil {
t.Fatalf("Failed to read xl meta data %v", err)
}
+60 -40
View File
@@ -178,7 +178,10 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta File
return false
}
const xMinIOHealing = ReservedMetadataPrefix + "healing"
const (
xMinIOHealing = ReservedMetadataPrefix + "healing"
xMinIODataMov = ReservedMetadataPrefix + "data-mov"
)
// SetHealing marks object (version) as being healed.
// Note: this is to be used only from healObject
@@ -196,6 +199,21 @@ func (fi FileInfo) Healing() bool {
return ok
}
// SetDataMov marks object (version) as being currently
// in movement, such as decommissioning or rebalance.
func (fi *FileInfo) SetDataMov() {
if fi.Metadata == nil {
fi.Metadata = make(map[string]string)
}
fi.Metadata[xMinIODataMov] = "true"
}
// DataMov returns true if object is being in movement
func (fi FileInfo) DataMov() bool {
_, ok := fi.Metadata[xMinIODataMov]
return ok
}
// Heals an object by re-writing corrupt/missing erasure blocks.
func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
dryRun := opts.DryRun
@@ -230,7 +248,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
}
// Re-read when we have lock...
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, bucket, object, versionID, true, true)
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, "", bucket, object, versionID, true, true)
if isAllNotFound(errs) {
err := errFileNotFound
if versionID != "" {
@@ -518,7 +536,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
inlineBuffers[i] = bytes.NewBuffer(make([]byte, 0, erasure.ShardFileSize(latestMeta.Size)+32))
writers[i] = newStreamingBitrotWriterBuffer(inlineBuffers[i], DefaultBitrotAlgorithm, erasure.ShardSize())
} else {
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, partPath,
writers[i] = newBitrotWriter(disk, bucket, minioMetaTmpBucket, partPath,
tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
}
}
@@ -796,7 +814,7 @@ func statAllDirs(ctx context.Context, storageDisks []StorageAPI, bucket, prefix
}
index := index
g.Go(func() error {
entries, err := storageDisks[index].ListDir(ctx, bucket, prefix, 1)
entries, err := storageDisks[index].ListDir(ctx, "", bucket, prefix, 1)
if err != nil {
return err
}
@@ -810,6 +828,10 @@ func statAllDirs(ctx context.Context, storageDisks []StorageAPI, bucket, prefix
return g.Wait()
}
func isAllVolumeNotFound(errs []error) bool {
return countErrs(errs, errVolumeNotFound) == len(errs)
}
// isAllNotFound will return if any element of the error slice is not
// errFileNotFound, errFileVersionNotFound or errVolumeNotFound.
// A 0 length slice will always return false.
@@ -839,8 +861,12 @@ func isAllBucketsNotFound(errs []error) bool {
}
notFoundCount := 0
for _, err := range errs {
if err != nil && errors.Is(err, errVolumeNotFound) {
notFoundCount++
if err != nil {
if errors.Is(err, errVolumeNotFound) {
notFoundCount++
} else if isErrBucketNotFound(err) {
notFoundCount++
}
}
}
return len(errs) == notFoundCount
@@ -878,11 +904,10 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
// We can consider an object data not reliable
// when xl.meta is not found in read quorum disks.
// or when xl.meta is not readable in read quorum disks.
danglingErrsCount := func(cerrs []error) (int, int, int) {
danglingErrsCount := func(cerrs []error) (int, int) {
var (
notFoundCount int
corruptedCount int
driveNotFoundCount int
nonActionableCount int
)
for _, readErr := range cerrs {
if readErr == nil {
@@ -891,29 +916,16 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
switch {
case errors.Is(readErr, errFileNotFound) || errors.Is(readErr, errFileVersionNotFound):
notFoundCount++
case errors.Is(readErr, errFileCorrupt):
corruptedCount++
default:
// All other errors are non-actionable
driveNotFoundCount++
nonActionableCount++
}
}
return notFoundCount, corruptedCount, driveNotFoundCount
return notFoundCount, nonActionableCount
}
ndataErrs := make([]error, len(dataErrs))
for i := range dataErrs {
if errs[i] != dataErrs[i] {
// Only count part errors, if the error is not
// same as xl.meta error. This is to avoid
// double counting when both parts and xl.meta
// are not available.
ndataErrs[i] = dataErrs[i]
}
}
notFoundMetaErrs, corruptedMetaErrs, driveNotFoundMetaErrs := danglingErrsCount(errs)
notFoundPartsErrs, corruptedPartsErrs, driveNotFoundPartsErrs := danglingErrsCount(ndataErrs)
notFoundMetaErrs, nonActionableMetaErrs := danglingErrsCount(errs)
notFoundPartsErrs, nonActionablePartsErrs := danglingErrsCount(dataErrs)
for _, m := range metaArr {
if m.IsValid() {
@@ -923,10 +935,9 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
}
if !validMeta.IsValid() {
// validMeta is invalid because notFoundPartsErrs is
// greater than parity blocks, thus invalidating the FileInfo{}
// every dataErrs[i], metaArr[i] is an empty FileInfo{}
dataBlocks := (len(ndataErrs) + 1) / 2
// validMeta is invalid because all xl.meta is missing apparently
// we should figure out if dataDirs are also missing > dataBlocks.
dataBlocks := (len(dataErrs) + 1) / 2
if notFoundPartsErrs > dataBlocks {
// Not using parity to ensure that we do not delete
// any valid content, if any is recoverable. But if
@@ -943,25 +954,34 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
return validMeta, false
}
if driveNotFoundMetaErrs > 0 || driveNotFoundPartsErrs > 0 {
if nonActionableMetaErrs > 0 || nonActionablePartsErrs > 0 {
return validMeta, false
}
if validMeta.Deleted {
// notFoundPartsErrs is ignored since
// - delete marker does not have any parts
return validMeta, corruptedMetaErrs+notFoundMetaErrs > len(errs)/2
dataBlocks := (len(errs) + 1) / 2
return validMeta, notFoundMetaErrs > dataBlocks
}
totalErrs := notFoundMetaErrs + corruptedMetaErrs + notFoundPartsErrs + corruptedPartsErrs
if validMeta.IsRemote() {
// notFoundPartsErrs is ignored since
// - transition status of complete has no parts
totalErrs = notFoundMetaErrs + corruptedMetaErrs
// TODO: It is possible to replay the object via just single
// xl.meta file, considering quorum number of data-dirs are still
// present on other drives.
//
// However this requires a bit of a rewrite, leave this up for
// future work.
if notFoundMetaErrs > 0 && notFoundMetaErrs > validMeta.Erasure.ParityBlocks {
// All xl.meta is beyond data blocks missing, this is dangling
return validMeta, true
}
// We have valid meta, now verify if we have enough files with parity blocks.
return validMeta, totalErrs > validMeta.Erasure.ParityBlocks
if !validMeta.IsRemote() && notFoundPartsErrs > 0 && notFoundPartsErrs > validMeta.Erasure.ParityBlocks {
// All data-dir is beyond data blocks missing, this is dangling
return validMeta, true
}
return validMeta, false
}
// HealObject - heal the given object, automatically deletes the object if stale/corrupted if `remove` is true.
@@ -993,7 +1013,7 @@ func (er erasureObjects) HealObject(ctx context.Context, bucket, object, version
// Perform quick read without lock.
// This allows to quickly check if all is ok or all are missing.
_, errs := readAllFileInfo(healCtx, storageDisks, bucket, object, versionID, false, false)
_, errs := readAllFileInfo(healCtx, storageDisks, "", bucket, object, versionID, false, false)
if isAllNotFound(errs) {
err := errFileNotFound
if versionID != "" {
+108 -67
View File
@@ -41,6 +41,10 @@ func TestIsObjectDangling(t *testing.T) {
fi := newFileInfo("test-object", 2, 2)
fi.Erasure.Index = 1
ifi := newFileInfo("test-object", 2, 2)
ifi.SetInlineData()
ifi.Erasure.Index = 1
testCases := []struct {
name string
metaArr []FileInfo
@@ -129,13 +133,77 @@ func TestIsObjectDangling(t *testing.T) {
expectedMeta: FileInfo{},
expectedDangling: false,
},
{
name: "FileInfoUnDecided-case4",
metaArr: []FileInfo{
{},
{},
{},
ifi,
},
errs: []error{
errFileNotFound,
errFileCorrupt,
errFileCorrupt,
nil,
},
dataErrs: nil,
expectedMeta: ifi,
expectedDangling: false,
},
{
name: "FileInfoUnDecided-case5-(ignore errFileCorrupt error)",
metaArr: []FileInfo{
{},
{},
{},
fi,
},
errs: []error{
errFileNotFound,
errFileCorrupt,
nil,
nil,
},
dataErrs: []error{
errFileCorrupt,
errFileNotFound,
nil,
errFileCorrupt,
},
expectedMeta: fi,
expectedDangling: false,
},
{
name: "FileInfoUnDecided-case6-(data-dir intact)",
metaArr: []FileInfo{
{},
{},
{},
fi,
},
errs: []error{
errFileNotFound,
errFileNotFound,
errFileNotFound,
nil,
},
dataErrs: []error{
errFileNotFound,
errFileCorrupt,
nil,
nil,
},
expectedMeta: fi,
expectedDangling: false,
},
{
name: "FileInfoDecided-case1",
metaArr: []FileInfo{
{},
{},
{},
fi,
ifi,
},
errs: []error{
errFileNotFound,
@@ -144,25 +212,7 @@ func TestIsObjectDangling(t *testing.T) {
nil,
},
dataErrs: nil,
expectedMeta: fi,
expectedDangling: true,
},
{
name: "FileInfoDecided-case2",
metaArr: []FileInfo{
{},
{},
{},
fi,
},
errs: []error{
errFileNotFound,
errFileCorrupt,
errFileCorrupt,
nil,
},
dataErrs: nil,
expectedMeta: fi,
expectedMeta: ifi,
expectedDangling: true,
},
{
@@ -175,8 +225,8 @@ func TestIsObjectDangling(t *testing.T) {
},
errs: []error{
errFileNotFound,
errFileCorrupt,
errFileCorrupt,
errFileNotFound,
errFileNotFound,
nil,
},
dataErrs: nil,
@@ -184,26 +234,26 @@ func TestIsObjectDangling(t *testing.T) {
expectedDangling: true,
},
{
name: "FileInfoDecided-case2-(duplicate data errors)",
name: "FileInfoDecided-case3-(enough data-dir missing)",
metaArr: []FileInfo{
{},
{},
{},
{Deleted: true},
fi,
},
errs: []error{
errFileNotFound,
errFileCorrupt,
errFileCorrupt,
errFileNotFound,
nil,
nil,
},
dataErrs: []error{
errFileNotFound,
errFileCorrupt,
nil,
errFileNotFound,
nil,
errFileNotFound,
},
expectedMeta: FileInfo{Deleted: true},
expectedMeta: fi,
expectedDangling: true,
},
// Add new cases as seen
@@ -266,7 +316,7 @@ func TestHealing(t *testing.T) {
}
disk := er.getDisks()[0]
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -289,7 +339,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err)
}
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -308,7 +358,7 @@ func TestHealing(t *testing.T) {
// gone down when an object was replaced by a new object.
fileInfoOutDated := fileInfoPreHeal
fileInfoOutDated.ModTime = time.Now()
err = disk.WriteMetadata(context.Background(), bucket, object, fileInfoOutDated)
err = disk.WriteMetadata(context.Background(), "", bucket, object, fileInfoOutDated)
if err != nil {
t.Fatal(err)
}
@@ -318,7 +368,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err)
}
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -426,11 +476,11 @@ func TestHealingVersioned(t *testing.T) {
}
disk := er.getDisks()[0]
fileInfoPreHeal1, err := disk.ReadVersion(context.Background(), bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
fileInfoPreHeal1, err := disk.ReadVersion(context.Background(), "", bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
fileInfoPreHeal2, err := disk.ReadVersion(context.Background(), bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
fileInfoPreHeal2, err := disk.ReadVersion(context.Background(), "", bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -453,11 +503,11 @@ func TestHealingVersioned(t *testing.T) {
t.Fatal(err)
}
fileInfoPostHeal1, err := disk.ReadVersion(context.Background(), bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal1, err := disk.ReadVersion(context.Background(), "", bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
fileInfoPostHeal2, err := disk.ReadVersion(context.Background(), bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal2, err := disk.ReadVersion(context.Background(), "", bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -479,7 +529,7 @@ func TestHealingVersioned(t *testing.T) {
// gone down when an object was replaced by a new object.
fileInfoOutDated := fileInfoPreHeal1
fileInfoOutDated.ModTime = time.Now()
err = disk.WriteMetadata(context.Background(), bucket, object, fileInfoOutDated)
err = disk.WriteMetadata(context.Background(), "", bucket, object, fileInfoOutDated)
if err != nil {
t.Fatal(err)
}
@@ -489,7 +539,7 @@ func TestHealingVersioned(t *testing.T) {
t.Fatal(err)
}
fileInfoPostHeal1, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal1, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -499,7 +549,7 @@ func TestHealingVersioned(t *testing.T) {
t.Fatal("HealObject failed")
}
fileInfoPostHeal2, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal2, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -564,7 +614,7 @@ func TestHealingDanglingObject(t *testing.T) {
resetGlobalHealState()
defer resetGlobalHealState()
// Set globalStoragClass.STANDARD to EC:4 for this test
// Set globalStorageClass.STANDARD to EC:4 for this test
saveSC := globalStorageClass
defer func() {
globalStorageClass.Update(saveSC)
@@ -637,7 +687,7 @@ func TestHealingDanglingObject(t *testing.T) {
// Restore...
setDisks(orgDisks[:4]...)
fileInfoPreHeal, err := disks[0].ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPreHeal, err := disks[0].ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -654,7 +704,7 @@ func TestHealingDanglingObject(t *testing.T) {
t.Fatal(err)
}
fileInfoPostHeal, err := disks[0].ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal, err := disks[0].ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -684,7 +734,7 @@ func TestHealingDanglingObject(t *testing.T) {
setDisks(orgDisks[:4]...)
disk := getDisk(0)
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -702,7 +752,7 @@ func TestHealingDanglingObject(t *testing.T) {
}
disk = getDisk(0)
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -733,7 +783,7 @@ func TestHealingDanglingObject(t *testing.T) {
setDisks(orgDisks[:4]...)
disk = getDisk(0)
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -751,7 +801,7 @@ func TestHealingDanglingObject(t *testing.T) {
}
disk = getDisk(0)
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
if err != nil {
t.Fatal(err)
}
@@ -836,7 +886,7 @@ func TestHealCorrectQuorum(t *testing.T) {
er := set.sets[0]
erasureDisks := er.getDisks()
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
nfi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if errors.Is(err, errFileNotFound) {
continue
@@ -858,12 +908,12 @@ func TestHealCorrectQuorum(t *testing.T) {
t.Fatal(err)
}
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
if countErrs(errs, nil) != len(fileInfos) {
t.Fatal("Expected all xl.meta healed, but partial heal detected")
}
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, minioMetaBucket, cfgFile, "", false, true)
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", minioMetaBucket, cfgFile, "", false, true)
nfi, err = getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if errors.Is(err, errFileNotFound) {
continue
@@ -885,7 +935,7 @@ func TestHealCorrectQuorum(t *testing.T) {
t.Fatal(err)
}
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, minioMetaBucket, cfgFile, "", false, true)
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", minioMetaBucket, cfgFile, "", false, true)
if countErrs(errs, nil) != len(fileInfos) {
t.Fatal("Expected all xl.meta healed, but partial heal detected")
}
@@ -970,7 +1020,7 @@ func TestHealObjectCorruptedPools(t *testing.T) {
t.Fatalf("Failed to heal object - %v", err)
}
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo - %v", err)
@@ -998,14 +1048,12 @@ func TestHealObjectCorruptedPools(t *testing.T) {
t.Errorf("Expected nil but received %v", err)
}
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
nfi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo - %v", err)
}
fi.DiskMTime = time.Time{}
nfi.DiskMTime = time.Time{}
if !reflect.DeepEqual(fi, nfi) {
t.Fatalf("FileInfo not equal after healing: %v != %v", fi, nfi)
}
@@ -1029,14 +1077,12 @@ func TestHealObjectCorruptedPools(t *testing.T) {
t.Errorf("Expected nil but received %v", err)
}
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
nfi, err = getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo - %v", err)
}
fi.DiskMTime = time.Time{}
nfi.DiskMTime = time.Time{}
if !reflect.DeepEqual(fi, nfi) {
t.Fatalf("FileInfo not equal after healing: %v != %v", fi, nfi)
}
@@ -1133,7 +1179,7 @@ func TestHealObjectCorruptedXLMeta(t *testing.T) {
firstDisk := erasureDisks[0]
// Test 1: Remove the object backend files from the first disk.
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo - %v", err)
@@ -1156,14 +1202,12 @@ func TestHealObjectCorruptedXLMeta(t *testing.T) {
t.Errorf("Expected xl.meta file to be present but stat failed - %v", err)
}
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
nfi1, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo - %v", err)
}
fi.DiskMTime = time.Time{}
nfi1.DiskMTime = time.Time{}
if !reflect.DeepEqual(fi, nfi1) {
t.Fatalf("FileInfo not equal after healing")
}
@@ -1179,14 +1223,12 @@ func TestHealObjectCorruptedXLMeta(t *testing.T) {
t.Errorf("Expected nil but received %v", err)
}
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
nfi2, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo - %v", err)
}
fi.DiskMTime = time.Time{}
nfi2.DiskMTime = time.Time{}
if !reflect.DeepEqual(fi, nfi2) {
t.Fatalf("FileInfo not equal after healing")
}
@@ -1277,7 +1319,7 @@ func TestHealObjectCorruptedParts(t *testing.T) {
firstDisk := erasureDisks[0]
secondDisk := erasureDisks[1]
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo - %v", err)
@@ -1603,7 +1645,6 @@ func TestHealLastDataShard(t *testing.T) {
_, err = obj.PutObject(ctx, bucket, object,
mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts)
if err != nil {
t.Fatal(err)
}
+2 -27
View File
@@ -20,9 +20,7 @@ package cmd
import (
"context"
"errors"
"fmt"
"hash/crc32"
"io"
"github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/sync/errgroup"
@@ -148,29 +146,9 @@ func hashOrder(key string, cardinality int) []int {
return nums
}
var readFileInfoIgnoredErrs = append(objectOpIgnoredErrs,
errFileNotFound,
errVolumeNotFound,
errFileVersionNotFound,
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
io.EOF, // some times we would read without locks, ignore these errors
)
func readFileInfo(ctx context.Context, disk StorageAPI, bucket, object, versionID string, opts ReadOptions) (FileInfo, error) {
fi, err := disk.ReadVersion(ctx, bucket, object, versionID, opts)
if err != nil && !IsErr(err, readFileInfoIgnoredErrs...) {
logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)",
disk.String(), bucket, object, err),
disk.String())
}
return fi, err
}
// Reads all `xl.meta` metadata as a FileInfo slice.
// Returns error slice indicating the failed metadata reads.
func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, versionID string, readData, healing bool) ([]FileInfo, []error) {
func readAllFileInfo(ctx context.Context, disks []StorageAPI, origbucket string, bucket, object, versionID string, readData, healing bool) ([]FileInfo, []error) {
metadataArray := make([]FileInfo, len(disks))
opts := ReadOptions{
@@ -186,7 +164,7 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
if disks[index] == nil {
return errDiskNotFound
}
metadataArray[index], err = readFileInfo(ctx, disks[index], bucket, object, versionID, opts)
metadataArray[index], err = disks[index].ReadVersion(ctx, origbucket, bucket, object, versionID, opts)
return err
}, index)
}
@@ -330,15 +308,12 @@ var (
// returns error if totalSize is -1, partSize is 0, partIndex is 0.
func calculatePartSizeFromIdx(ctx context.Context, totalSize int64, partSize int64, partIndex int) (currPartSize int64, err error) {
if totalSize < -1 {
logger.LogIf(ctx, errInvalidArgument)
return 0, errInvalidArgument
}
if partSize == 0 {
logger.LogIf(ctx, errPartSizeZero)
return 0, errPartSizeZero
}
if partIndex < 1 {
logger.LogIf(ctx, errPartSizeIndex)
return 0, errPartSizeIndex
}
if totalSize == -1 {
+2 -2
View File
@@ -381,7 +381,7 @@ func pickValidFileInfo(ctx context.Context, metaArr []FileInfo, modTime time.Tim
}
// writeUniqueFileInfo - writes unique `xl.meta` content for each disk concurrently.
func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, bucket, prefix string, files []FileInfo, quorum int) ([]StorageAPI, error) {
func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, origbucket, bucket, prefix string, files []FileInfo, quorum int) ([]StorageAPI, error) {
g := errgroup.WithNErrs(len(disks))
// Start writing `xl.meta` to all disks in parallel.
@@ -395,7 +395,7 @@ func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, bucket, prefix
fi := files[index]
fi.Erasure.Index = index + 1
if fi.IsValid() {
return disks[index].WriteMetadata(ctx, bucket, prefix, fi)
return disks[index].WriteMetadata(ctx, origbucket, bucket, prefix, fi)
}
return errCorruptedFormat
}, index)
+40 -26
View File
@@ -61,7 +61,7 @@ func (er erasureObjects) getMultipartSHADir(bucket, object string) string {
// checkUploadIDExists - verify if a given uploadID exists and is valid.
func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object, uploadID string, write bool) (fi FileInfo, metArr []FileInfo, err error) {
defer func() {
if errors.Is(err, errFileNotFound) || errors.Is(err, errVolumeNotFound) {
if errors.Is(err, errFileNotFound) {
err = errUploadIDNotFound
}
}()
@@ -71,7 +71,7 @@ func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object
storageDisks := er.getDisks()
// Read metadata associated with the object from all disks.
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, minioMetaMultipartBucket,
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, bucket, minioMetaMultipartBucket,
uploadIDPath, "", false, false)
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, partsMetadata, errs, er.defaultParityCount)
@@ -87,15 +87,14 @@ func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object
// List all online disks.
_, modTime, etag := listOnlineDisks(storageDisks, partsMetadata, errs, quorum)
var reducedErr error
if write {
reducedErr := reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
if errors.Is(reducedErr, errErasureWriteQuorum) {
return fi, nil, reducedErr
}
reducedErr = reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
} else {
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
return fi, nil, reducedErr
}
reducedErr = reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum)
}
if reducedErr != nil {
return fi, nil, reducedErr
}
// Pick one from the first valid metadata.
@@ -200,7 +199,7 @@ func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk Sto
readDirFn(pathJoin(diskPath, minioMetaMultipartBucket), func(shaDir string, typ os.FileMode) error {
readDirFn(pathJoin(diskPath, minioMetaMultipartBucket, shaDir), func(uploadIDDir string, typ os.FileMode) error {
uploadIDPath := pathJoin(shaDir, uploadIDDir)
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, uploadIDPath, "", ReadOptions{})
fi, err := disk.ReadVersion(ctx, "", minioMetaMultipartBucket, uploadIDPath, "", ReadOptions{})
if err != nil {
return nil
}
@@ -281,15 +280,14 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
if !disk.IsOnline() {
continue
}
uploadIDs, err = disk.ListDir(ctx, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
uploadIDs, err = disk.ListDir(ctx, bucket, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
if err != nil {
if errors.Is(err, errDiskNotFound) {
continue
}
if errors.Is(err, errFileNotFound) || errors.Is(err, errVolumeNotFound) {
if errors.Is(err, errFileNotFound) {
return result, nil
}
logger.LogIf(ctx, err)
return result, toObjectErr(err, bucket, object)
}
break
@@ -303,10 +301,10 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
// to read the metadata entry.
var uploads []MultipartInfo
populatedUploadIds := set.NewStringSet()
populatedUploadIDs := set.NewStringSet()
for _, uploadID := range uploadIDs {
if populatedUploadIds.Contains(uploadID) {
if populatedUploadIDs.Contains(uploadID) {
continue
}
// If present, use time stored in ID.
@@ -323,7 +321,7 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
UploadID: base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf("%s.%s", globalDeploymentID(), uploadID))),
Initiated: startTime,
})
populatedUploadIds.Add(uploadID)
populatedUploadIDs.Add(uploadID)
}
sort.Slice(uploads, func(i int, j int) bool {
@@ -486,8 +484,8 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string,
uploadIDPath := er.getUploadIDDir(bucket, object, uploadUUID)
// Write updated `xl.meta` to all disks.
if _, err := writeUniqueFileInfo(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath, partsMetadata, writeQuorum); err != nil {
return nil, toObjectErr(err, minioMetaMultipartBucket, uploadIDPath)
if _, err := writeUniqueFileInfo(ctx, onlineDisks, bucket, minioMetaMultipartBucket, uploadIDPath, partsMetadata, writeQuorum); err != nil {
return nil, toObjectErr(err, bucket, object)
}
return &NewMultipartUploadResult{
UploadID: uploadID,
@@ -582,6 +580,13 @@ func writeAllDisks(ctx context.Context, disks []StorageAPI, dstBucket, dstEntry
func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, r *PutObjReader, opts ObjectOptions) (pi PartInfo, err error) {
auditObjectErasureSet(ctx, object, &er)
data := r.Reader
// Validate input data size and it can never be less than zero.
if data.Size() < -1 {
logger.LogIf(ctx, errInvalidArgument, logger.ErrorKind)
return pi, toObjectErr(errInvalidArgument)
}
// Read lock for upload id.
// Only held while reading the upload metadata.
uploadIDRLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
@@ -596,16 +601,12 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
// Validates if upload ID exists.
fi, _, err := er.checkUploadIDExists(rctx, bucket, object, uploadID, true)
if err != nil {
if errors.Is(err, errVolumeNotFound) {
return pi, toObjectErr(err, bucket)
}
return pi, toObjectErr(err, bucket, object, uploadID)
}
data := r.Reader
// Validate input data size and it can never be less than zero.
if data.Size() < -1 {
logger.LogIf(rctx, errInvalidArgument, logger.Application)
return pi, toObjectErr(errInvalidArgument)
}
// Write lock for this part ID, only hold it if we are planning to read from the
// streamto avoid any concurrent updates.
//
@@ -681,7 +682,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
if disk == nil {
continue
}
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, tmpPartPath, erasure.ShardFileSize(data.Size()), DefaultBitrotAlgorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, bucket, minioMetaTmpBucket, tmpPartPath, erasure.ShardFileSize(data.Size()), DefaultBitrotAlgorithm, erasure.ShardSize())
}
toEncode := io.Reader(data)
@@ -794,6 +795,9 @@ func (er erasureObjects) GetMultipartInfo(ctx context.Context, bucket, object, u
fi, _, err := er.checkUploadIDExists(ctx, bucket, object, uploadID, false)
if err != nil {
if errors.Is(err, errVolumeNotFound) {
return result, toObjectErr(err, bucket)
}
return result, toObjectErr(err, bucket, object, uploadID)
}
@@ -979,6 +983,9 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
fi, partsMetadata, err := er.checkUploadIDExists(ctx, bucket, object, uploadID, true)
if err != nil {
if errors.Is(err, errVolumeNotFound) {
return oi, toObjectErr(err, bucket)
}
return oi, toObjectErr(err, bucket, object, uploadID)
}
@@ -1237,6 +1244,10 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
// Save the consolidated actual size.
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(objectActualSize, 10)
if opts.DataMovement {
fi.SetDataMov()
}
// Update all erasure metadata, make sure to not modify fields like
// checksum which are different on each disks.
for index := range partsMetadata {
@@ -1337,6 +1348,9 @@ func (er erasureObjects) AbortMultipartUpload(ctx context.Context, bucket, objec
// Validates if upload ID exists.
if _, _, err = er.checkUploadIDExists(ctx, bucket, object, uploadID, false); err != nil {
if errors.Is(err, errVolumeNotFound) {
return toObjectErr(err, bucket)
}
return toObjectErr(err, bucket, object, uploadID)
}
+30 -52
View File
@@ -46,7 +46,6 @@ import (
"github.com/minio/pkg/v2/mimedb"
"github.com/minio/pkg/v2/sync/errgroup"
"github.com/minio/pkg/v2/wildcard"
"github.com/tinylib/msgp/msgp"
)
// list all errors which can be ignored in object operations.
@@ -91,7 +90,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
// Read metadata associated with the object from all disks.
if srcOpts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true, false)
metaArr, errs = readAllFileInfo(ctx, storageDisks, "", srcBucket, srcObject, srcOpts.VersionID, true, false)
} else {
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false, true)
}
@@ -179,7 +178,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
}
// Write unique `xl.meta` for each disk.
if _, err = writeUniqueFileInfo(ctx, onlineDisks, srcBucket, srcObject, metaArr, writeQuorum); err != nil {
if _, err = writeUniqueFileInfo(ctx, onlineDisks, "", srcBucket, srcObject, metaArr, writeQuorum); err != nil {
return oi, toObjectErr(err, srcBucket, srcObject)
}
@@ -546,29 +545,6 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
return m, err
}
var readRawFileInfoErrs = append(objectOpIgnoredErrs,
errFileNotFound,
errFileNameTooLong,
errVolumeNotFound,
errFileVersionNotFound,
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
io.EOF, // some times we would read without locks, ignore these errors
msgp.ErrShortBytes,
context.DeadlineExceeded,
context.Canceled,
)
func readRawFileInfo(ctx context.Context, disk StorageAPI, bucket, object string, readData bool) (RawFileInfo, error) {
rf, err := disk.ReadXL(ctx, bucket, object, readData)
if err != nil && !IsErr(err, readRawFileInfoErrs...) {
logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)",
disk.String(), bucket, object, err),
disk.String())
}
return rf, err
}
func fileInfoFromRaw(ri RawFileInfo, bucket, object string, readData, inclFreeVers, allParts bool) (FileInfo, error) {
var xl xlMetaV2
if err := xl.LoadOrConvert(ri.Buf); err != nil {
@@ -598,7 +574,6 @@ func fileInfoFromRaw(ri RawFileInfo, bucket, object string, readData, inclFreeVe
fileInfo.Data = xl.data.find(versionID)
}
fileInfo.DiskMTime = ri.DiskMTime
return fileInfo, nil
}
@@ -611,7 +586,7 @@ func readAllRawFileInfo(ctx context.Context, disks []StorageAPI, bucket, object
if disks[index] == nil {
return errDiskNotFound
}
rf, err := readRawFileInfo(ctx, disks[index], bucket, object, readData)
rf, err := disks[index].ReadXL(ctx, bucket, object, readData)
if err != nil {
return err
}
@@ -649,9 +624,7 @@ func pickLatestQuorumFilesInfo(ctx context.Context, rawFileInfos []RawFileInfo,
continue
}
metadataArray[index] = &xl
metaFileInfos[index] = FileInfo{
DiskMTime: rf.DiskMTime,
}
metaFileInfos[index] = FileInfo{}
}
for index := range metadataArray {
@@ -691,7 +664,6 @@ func pickLatestQuorumFilesInfo(ctx context.Context, rawFileInfos []RawFileInfo,
}
// make sure to preserve this for diskmtime based healing bugfix.
diskMTime := metaFileInfos[index].DiskMTime
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID, inclFreeVers, allParts)
if errs[index] != nil {
continue
@@ -700,7 +672,6 @@ func pickLatestQuorumFilesInfo(ctx context.Context, rawFileInfos []RawFileInfo,
if readData {
metaFileInfos[index].Data = metadataArray[index].data.find(versionID)
}
metaFileInfos[index].DiskMTime = diskMTime
}
if !readData {
for i := range v2bufs {
@@ -791,10 +762,10 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
if opts.VersionID != "" {
// Read a specific version ID
fi, err = readFileInfo(ctx, disk, bucket, object, opts.VersionID, ropts)
fi, err = disk.ReadVersion(ctx, "", bucket, object, opts.VersionID, ropts)
} else {
// Read the latest version
rfi, err = readRawFileInfo(ctx, disk, bucket, object, readData)
rfi, err = disk.ReadXL(ctx, bucket, object, readData)
if err == nil {
fi, err = fileInfoFromRaw(rfi, bucket, object, readData, opts.InclFreeVersions, true)
}
@@ -829,7 +800,11 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
var missingBlocks int
for i := range errs {
if errors.Is(errs[i], errFileNotFound) {
if IsErr(errs[i],
errFileNotFound,
errFileVersionNotFound,
errFileCorrupt,
) {
missingBlocks++
}
}
@@ -962,7 +937,11 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
onlineDisks[i] = nil
}
mrfCheck <- fi.ShallowCopy()
select {
case mrfCheck <- fi.ShallowCopy():
case <-ctx.Done():
return fi, onlineMeta, onlineDisks, toObjectErr(ctx.Err(), bucket, object)
}
return fi, onlineMeta, onlineDisks, nil
}
@@ -985,7 +964,7 @@ func (er erasureObjects) getObjectInfo(ctx context.Context, bucket, object strin
return objInfo, nil
}
// getObjectInfoAndQuroum - wrapper for reading object metadata and constructs ObjectInfo, additionally returns write quorum for the object.
// getObjectInfoAndQuorum - wrapper for reading object metadata and constructs ObjectInfo, additionally returns write quorum for the object.
func (er erasureObjects) getObjectInfoAndQuorum(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, wquorum int, err error) {
fi, _, _, err := er.getObjectFileInfo(ctx, bucket, object, opts, false)
if err != nil {
@@ -1114,7 +1093,7 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
// Validate input data size and it can never be less than zero.
if data.Size() < -1 {
logger.LogIf(ctx, errInvalidArgument, logger.Application)
logger.LogIf(ctx, errInvalidArgument, logger.ErrorKind)
return ObjectInfo{}, toObjectErr(errInvalidArgument)
}
@@ -1219,7 +1198,7 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
}
}
if _, err = writeUniqueFileInfo(ctx, onlineDisks, minioMetaBucket, key, partsMetadata, writeQuorum); err != nil {
if _, err = writeUniqueFileInfo(ctx, onlineDisks, "", minioMetaBucket, key, partsMetadata, writeQuorum); err != nil {
return ObjectInfo{}, toObjectErr(err, minioMetaBucket, key)
}
@@ -1301,7 +1280,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
// Validate input data size and it can never be less than -1.
if data.Size() < -1 {
logger.LogIf(ctx, errInvalidArgument, logger.Application)
logger.LogIf(ctx, errInvalidArgument, logger.ErrorKind)
return ObjectInfo{}, toObjectErr(errInvalidArgument)
}
@@ -1449,7 +1428,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
continue
}
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, tempErasureObj, shardFileSize, DefaultBitrotAlgorithm, erasure.ShardSize())
writers[i] = newBitrotWriter(disk, bucket, minioMetaTmpBucket, tempErasureObj, shardFileSize, DefaultBitrotAlgorithm, erasure.ShardSize())
}
toEncode := io.Reader(data)
@@ -1469,7 +1448,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
n, erasureErr := erasure.Encode(ctx, toEncode, writers, buffer, writeQuorum)
closeBitrotWriters(writers)
if erasureErr != nil {
return ObjectInfo{}, toObjectErr(erasureErr, minioMetaTmpBucket, tempErasureObj)
return ObjectInfo{}, toObjectErr(erasureErr, bucket, object)
}
// Should return IncompleteBody{} error when reader has fewer bytes
@@ -1536,13 +1515,12 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
partsMetadata[index].Metadata = userDefined
partsMetadata[index].Size = n
partsMetadata[index].ModTime = modTime
}
if len(inlineBuffers) > 0 {
// Set an additional header when data is inlined.
for index := range partsMetadata {
if len(inlineBuffers) > 0 {
partsMetadata[index].SetInlineData()
}
if opts.DataMovement {
partsMetadata[index].SetDataMov()
}
}
// Rename the successfully written temporary object to final location.
@@ -2057,7 +2035,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
// Read metadata associated with the object from all disks.
if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false, false)
metaArr, errs = readAllFileInfo(ctx, disks, "", bucket, object, opts.VersionID, false, false)
} else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
}
@@ -2130,7 +2108,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
// Read metadata associated with the object from all disks.
if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false, false)
metaArr, errs = readAllFileInfo(ctx, disks, "", bucket, object, opts.VersionID, false, false)
} else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
}
@@ -2197,7 +2175,7 @@ func (er erasureObjects) updateObjectMetaWithOpts(ctx context.Context, bucket, o
// Wait for all the routines.
mErrs := g.Wait()
return reduceWriteQuorumErrs(ctx, mErrs, objectOpIgnoredErrs, er.defaultWQuorum())
return reduceWriteQuorumErrs(ctx, mErrs, objectOpIgnoredErrs, fi.WriteQuorum(er.defaultWQuorum()))
}
// updateObjectMeta will update the metadata of a file.
@@ -2458,7 +2436,7 @@ func (er erasureObjects) DecomTieredObject(ctx context.Context, bucket, object s
var onlineDisks []StorageAPI
onlineDisks, partsMetadata = shuffleDisksAndPartsMetadata(storageDisks, partsMetadata, fi)
if _, err := writeUniqueFileInfo(ctx, onlineDisks, bucket, object, partsMetadata, writeQuorum); err != nil {
if _, err := writeUniqueFileInfo(ctx, onlineDisks, "", bucket, object, partsMetadata, writeQuorum); err != nil {
return toObjectErr(err, bucket, object)
}
+9 -9
View File
@@ -521,7 +521,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
}
for _, disk := range xl.getDisks() {
files, _ := disk.ListDir(ctx, bucket, object, -1)
files, _ := disk.ListDir(ctx, "", bucket, object, -1)
for _, file := range files {
if file != "xl.meta" {
disk.Delete(ctx, bucket, pathJoin(object, file), DeleteOptions{
@@ -625,7 +625,7 @@ func TestHeadObjectNoQuorum(t *testing.T) {
t.Fatal(err)
}
for _, disk := range xl.getDisks() {
files, _ := disk.ListDir(ctx, bucket, object, -1)
files, _ := disk.ListDir(ctx, "", bucket, object, -1)
for _, file := range files {
if file != "xl.meta" {
disk.Delete(ctx, bucket, pathJoin(object, file), DeleteOptions{
@@ -908,7 +908,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
t.Fatalf("Failed to putObject %v", err)
}
parts1, errs1 := readAllFileInfo(ctx, erasureDisks, bucket, object1, "", false, false)
parts1, errs1 := readAllFileInfo(ctx, erasureDisks, "", bucket, object1, "", false, false)
parts1SC := globalStorageClass
// Object for test case 2 - No StorageClass defined, MetaData in PutObject requesting RRS Class
@@ -920,7 +920,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
t.Fatalf("Failed to putObject %v", err)
}
parts2, errs2 := readAllFileInfo(ctx, erasureDisks, bucket, object2, "", false, false)
parts2, errs2 := readAllFileInfo(ctx, erasureDisks, "", bucket, object2, "", false, false)
parts2SC := globalStorageClass
// Object for test case 3 - No StorageClass defined, MetaData in PutObject requesting Standard Storage Class
@@ -932,7 +932,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
t.Fatalf("Failed to putObject %v", err)
}
parts3, errs3 := readAllFileInfo(ctx, erasureDisks, bucket, object3, "", false, false)
parts3, errs3 := readAllFileInfo(ctx, erasureDisks, "", bucket, object3, "", false, false)
parts3SC := globalStorageClass
// Object for test case 4 - Standard StorageClass defined as Parity 6, MetaData in PutObject requesting Standard Storage Class
@@ -950,7 +950,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
t.Fatalf("Failed to putObject %v", err)
}
parts4, errs4 := readAllFileInfo(ctx, erasureDisks, bucket, object4, "", false, false)
parts4, errs4 := readAllFileInfo(ctx, erasureDisks, "", bucket, object4, "", false, false)
parts4SC := storageclass.Config{
Standard: storageclass.StorageClass{
Parity: 6,
@@ -973,7 +973,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
t.Fatalf("Failed to putObject %v", err)
}
parts5, errs5 := readAllFileInfo(ctx, erasureDisks, bucket, object5, "", false, false)
parts5, errs5 := readAllFileInfo(ctx, erasureDisks, "", bucket, object5, "", false, false)
parts5SC := globalStorageClass
// Object for test case 6 - RRS StorageClass defined as Parity 2, MetaData in PutObject requesting Standard Storage Class
@@ -994,7 +994,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
t.Fatalf("Failed to putObject %v", err)
}
parts6, errs6 := readAllFileInfo(ctx, erasureDisks, bucket, object6, "", false, false)
parts6, errs6 := readAllFileInfo(ctx, erasureDisks, "", bucket, object6, "", false, false)
parts6SC := storageclass.Config{
RRS: storageclass.StorageClass{
Parity: 2,
@@ -1017,7 +1017,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
t.Fatalf("Failed to putObject %v", err)
}
parts7, errs7 := readAllFileInfo(ctx, erasureDisks, bucket, object7, "", false, false)
parts7, errs7 := readAllFileInfo(ctx, erasureDisks, "", bucket, object7, "", false, false)
parts7SC := storageclass.Config{
Standard: storageclass.StorageClass{
Parity: 5,
+5 -2
View File
@@ -653,7 +653,8 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
}
}
_, err = z.CompleteMultipartUpload(ctx, bucket, objInfo.Name, res.UploadID, parts, ObjectOptions{
MTime: objInfo.ModTime,
DataMovement: true,
MTime: objInfo.ModTime,
})
if err != nil {
err = fmt.Errorf("decommissionObject: CompleteMultipartUpload() %w", err)
@@ -665,11 +666,13 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
if err != nil {
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
}
_, err = z.PutObject(ctx,
bucket,
objInfo.Name,
NewPutObjReader(hr),
ObjectOptions{
DataMovement: true,
VersionID: objInfo.VersionID,
MTime: objInfo.ModTime,
UserDefined: objInfo.UserDefined,
@@ -1164,7 +1167,7 @@ func (z *erasureServerPools) doDecommissionInRoutine(ctx context.Context, idx in
z.poolMetaMutex.Unlock()
if !failed {
logger.Info("Decommissioning complete for pool '%s', verifying for any pending objects", poolCmdLine)
logger.Event(dctx, "Decommissioning complete for pool '%s', verifying for any pending objects", poolCmdLine)
err := z.checkAfterDecom(dctx, idx)
if err != nil {
logger.LogIf(ctx, err)
+8 -1
View File
@@ -432,6 +432,8 @@ func (z *erasureServerPools) rebalanceBuckets(ctx context.Context, poolIdx int)
}
}()
logger.Event(ctx, "Pool %d rebalancing is started", poolIdx+1)
for {
select {
case <-ctx.Done():
@@ -456,6 +458,8 @@ func (z *erasureServerPools) rebalanceBuckets(ctx context.Context, poolIdx int)
z.bucketRebalanceDone(bucket, poolIdx)
}
logger.Event(ctx, "Pool %d rebalancing is done", poolIdx+1)
return err
}
@@ -809,7 +813,8 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
}
}
_, err = z.CompleteMultipartUpload(ctx, bucket, oi.Name, res.UploadID, parts, ObjectOptions{
MTime: oi.ModTime,
DataMovement: true,
MTime: oi.ModTime,
})
if err != nil {
err = fmt.Errorf("rebalanceObject: CompleteMultipartUpload() %w", err)
@@ -821,11 +826,13 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
if err != nil {
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
}
_, err = z.PutObject(ctx,
bucket,
oi.Name,
NewPutObjReader(hr),
ObjectOptions{
DataMovement: true,
VersionID: oi.VersionID,
MTime: oi.ModTime,
UserDefined: oi.UserDefined,
+68 -100
View File
@@ -39,6 +39,7 @@ import (
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/bpool"
"github.com/minio/minio/internal/cachevalue"
"github.com/minio/minio/internal/config/storageclass"
xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger"
@@ -72,20 +73,20 @@ func (z *erasureServerPools) SinglePool() bool {
func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServerPools) (ObjectLayer, error) {
var (
deploymentID string
distributionAlgo string
commonParityDrives int
err error
formats = make([]*formatErasureV3, len(endpointServerPools))
storageDisks = make([][]StorageAPI, len(endpointServerPools))
z = &erasureServerPools{
serverPools: make([]*erasureSets, len(endpointServerPools)),
s3Peer: NewS3PeerSys(endpointServerPools),
serverPools: make([]*erasureSets, len(endpointServerPools)),
s3Peer: NewS3PeerSys(endpointServerPools),
distributionAlgo: formatErasureVersionV3DistributionAlgoV3,
}
)
// Maximum number of reusable buffers per node at any given point in time.
n := 1024 // single node single/multiple drives set this to 1024 entries
n := uint64(1024) // single node single/multiple drives set this to 1024 entries
if globalIsDistErasure {
n = 2048
@@ -95,6 +96,11 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
n = 256 // 256MiB for CI/CD environments is sufficient
}
// Avoid allocating more than half of the available memory
if maxN := availableMemory() / (blockSizeV2 * 2); n > maxN {
n = maxN
}
// Initialize byte pool once for all sets, bpool size is set to
// setCount * setDriveCount with each memory upto blockSizeV2.
globalBytePoolCap = bpool.NewBytePoolCap(n, blockSizeV2, blockSizeV2*2)
@@ -121,7 +127,7 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
bootstrapTrace("waitForFormatErasure: loading disks", func() {
storageDisks[i], formats[i], err = waitForFormatErasure(local, ep.Endpoints, i+1,
ep.SetCount, ep.DrivesPerSet, deploymentID, distributionAlgo)
ep.SetCount, ep.DrivesPerSet, deploymentID)
})
if err != nil {
return nil, err
@@ -132,10 +138,6 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
deploymentID = formats[i].ID
}
if distributionAlgo == "" {
distributionAlgo = formats[i].Erasure.DistributionAlgo
}
// Validate if users brought different DeploymentID pools.
if deploymentID != formats[i].ID {
return nil, fmt.Errorf("all pools must have same deployment ID - expected %s, got %s for pool(%s)", deploymentID, formats[i].ID, humanize.Ordinal(i+1))
@@ -152,10 +154,6 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
z.deploymentID = uuid.MustParse(deploymentID)
}
if distributionAlgo != "" && z.distributionAlgo == "" {
z.distributionAlgo = distributionAlgo
}
for _, storageDisk := range storageDisks[i] {
if storageDisk != nil && storageDisk.IsLocal() {
localDrives = append(localDrives, storageDisk)
@@ -1004,20 +1002,10 @@ func (z *erasureServerPools) PutObject(ctx context.Context, bucket string, objec
}
object = encodeDirObject(object)
if z.SinglePool() {
if !isMinioMetaBucketName(bucket) {
avail, err := hasSpaceFor(getDiskInfos(ctx, z.serverPools[0].getHashedSet(object).getDisks()...), data.Size())
if err != nil {
logger.LogOnceIf(ctx, err, "erasure-write-quorum")
return ObjectInfo{}, toObjectErr(errErasureWriteQuorum)
}
if !avail {
return ObjectInfo{}, toObjectErr(errDiskFull)
}
}
return z.serverPools[0].PutObject(ctx, bucket, object, data, opts)
}
if !opts.NoLock {
ns := z.NewNSLock(bucket, object)
lkctx, err := ns.GetLock(ctx, globalOperationTimeout)
@@ -1199,6 +1187,13 @@ func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, o
}
func (z *erasureServerPools) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error) {
if err := checkCopyObjArgs(ctx, srcBucket, srcObject); err != nil {
return ObjectInfo{}, err
}
if err := checkCopyObjArgs(ctx, dstBucket, dstObject); err != nil {
return ObjectInfo{}, err
}
srcObject = encodeDirObject(srcObject)
dstObject = encodeDirObject(dstObject)
@@ -1307,7 +1302,10 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
merged, err := z.listPath(ctx, &opts)
if err != nil && err != io.EOF {
return loi, err
if !isErrBucketNotFound(err) {
logger.LogOnceIf(ctx, err, "erasure-list-objects-path-"+bucket)
}
return loi, toObjectErr(err, bucket)
}
defer merged.truncate(0) // Release when returning
@@ -1456,6 +1454,12 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
}
return loi, nil
}
if isErrBucketNotFound(err) {
return loi, err
}
if contextCanceled(ctx) {
return ListObjectsInfo{}, ctx.Err()
}
}
if len(prefix) > 0 && maxKeys == 1 && marker == "" {
@@ -1481,7 +1485,9 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
loi.Objects = append(loi.Objects, objInfo)
return loi, nil
}
if isErrBucketNotFound(err) {
return ListObjectsInfo{}, err
}
if contextCanceled(ctx) {
return ListObjectsInfo{}, ctx.Err()
}
@@ -1492,7 +1498,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
if !isErrBucketNotFound(err) {
logger.LogOnceIf(ctx, err, "erasure-list-objects-path-"+bucket)
}
return loi, err
return loi, toObjectErr(err, bucket)
}
merged.forwardPast(opts.Marker)
@@ -1537,7 +1543,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
}
func (z *erasureServerPools) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, error) {
if err := checkListMultipartArgs(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, z); err != nil {
if err := checkListMultipartArgs(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter); err != nil {
return ListMultipartsInfo{}, err
}
@@ -1566,21 +1572,11 @@ func (z *erasureServerPools) ListMultipartUploads(ctx context.Context, bucket, p
// Initiate a new multipart upload on a hashedSet based on object name.
func (z *erasureServerPools) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (*NewMultipartUploadResult, error) {
if err := checkNewMultipartArgs(ctx, bucket, object, z); err != nil {
if err := checkNewMultipartArgs(ctx, bucket, object); err != nil {
return nil, err
}
if z.SinglePool() {
if !isMinioMetaBucketName(bucket) {
avail, err := hasSpaceFor(getDiskInfos(ctx, z.serverPools[0].getHashedSet(object).getDisks()...), -1)
if err != nil {
logger.LogIf(ctx, err)
return nil, toObjectErr(errErasureWriteQuorum)
}
if !avail {
return nil, toObjectErr(errDiskFull)
}
}
return z.serverPools[0].NewMultipartUpload(ctx, bucket, object, opts)
}
@@ -1613,7 +1609,7 @@ func (z *erasureServerPools) NewMultipartUpload(ctx context.Context, bucket, obj
// Copies a part of an object from source hashedSet to destination hashedSet.
func (z *erasureServerPools) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (PartInfo, error) {
if err := checkNewMultipartArgs(ctx, srcBucket, srcObject, z); err != nil {
if err := checkNewMultipartArgs(ctx, srcBucket, srcObject); err != nil {
return PartInfo{}, err
}
@@ -1623,7 +1619,7 @@ func (z *erasureServerPools) CopyObjectPart(ctx context.Context, srcBucket, srcO
// PutObjectPart - writes part of an object to hashedSet based on the object name.
func (z *erasureServerPools) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (PartInfo, error) {
if err := checkPutObjectPartArgs(ctx, bucket, object, uploadID, z); err != nil {
if err := checkPutObjectPartArgs(ctx, bucket, object, uploadID); err != nil {
return PartInfo{}, err
}
@@ -1655,7 +1651,7 @@ func (z *erasureServerPools) PutObjectPart(ctx context.Context, bucket, object,
}
func (z *erasureServerPools) GetMultipartInfo(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) (MultipartInfo, error) {
if err := checkListPartsArgs(ctx, bucket, object, uploadID, z); err != nil {
if err := checkListPartsArgs(ctx, bucket, object, uploadID); err != nil {
return MultipartInfo{}, err
}
@@ -1686,7 +1682,7 @@ func (z *erasureServerPools) GetMultipartInfo(ctx context.Context, bucket, objec
// ListObjectParts - lists all uploaded parts to an object in hashedSet.
func (z *erasureServerPools) ListObjectParts(ctx context.Context, bucket, object, uploadID string, partNumberMarker int, maxParts int, opts ObjectOptions) (ListPartsInfo, error) {
if err := checkListPartsArgs(ctx, bucket, object, uploadID, z); err != nil {
if err := checkListPartsArgs(ctx, bucket, object, uploadID); err != nil {
return ListPartsInfo{}, err
}
@@ -1715,7 +1711,7 @@ func (z *erasureServerPools) ListObjectParts(ctx context.Context, bucket, object
// Aborts an in-progress multipart operation on hashedSet based on the object name.
func (z *erasureServerPools) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) error {
if err := checkAbortMultipartArgs(ctx, bucket, object, uploadID, z); err != nil {
if err := checkAbortMultipartArgs(ctx, bucket, object, uploadID); err != nil {
return err
}
@@ -1746,7 +1742,7 @@ func (z *erasureServerPools) AbortMultipartUpload(ctx context.Context, bucket, o
// CompleteMultipartUpload - completes a pending multipart transaction, on hashedSet based on object name.
func (z *erasureServerPools) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error) {
if err = checkCompleteMultipartArgs(ctx, bucket, object, uploadID, z); err != nil {
if err = checkCompleteMultipartArgs(ctx, bucket, object, uploadID); err != nil {
return objInfo, err
}
@@ -1824,12 +1820,6 @@ func (z *erasureServerPools) DeleteBucket(ctx context.Context, bucket string, op
}
}
if err != nil && !isErrBucketNotFound(err) {
if !opts.NoRecreate {
z.s3Peer.MakeBucket(ctx, bucket, MakeBucketOptions{})
}
}
if err == nil {
// Purge the entire bucket metadata entirely.
z.deleteAll(context.Background(), minioMetaBucket, pathJoin(bucketMetaPrefix, bucket))
@@ -1851,7 +1841,7 @@ func (z *erasureServerPools) deleteAll(ctx context.Context, bucket, prefix strin
}
}
var listBucketsCache timedValue
var listBucketsCache = cachevalue.New[[]BucketInfo]()
// List all buckets from one of the serverPools, we are not doing merge
// sort here just for simplification. As per design it is assumed
@@ -1862,7 +1852,7 @@ func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions
listBucketsCache.TTL = time.Second
listBucketsCache.Relax = true
listBucketsCache.Update = func() (interface{}, error) {
listBucketsCache.Update = func() ([]BucketInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
buckets, err = z.s3Peer.ListBuckets(ctx, opts)
cancel()
@@ -1879,12 +1869,7 @@ func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions
}
})
v, _ := listBucketsCache.Get()
if v != nil {
return v.([]BucketInfo), nil
}
return buckets, nil
return listBucketsCache.Get()
}
buckets, err = z.s3Peer.ListBuckets(ctx, opts)
@@ -1942,11 +1927,7 @@ func (z *erasureServerPools) HealFormat(ctx context.Context, dryRun bool) (madmi
}
func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
// Attempt heal on the bucket metadata, ignore any failures
hopts := opts
hopts.Recreate = false
defer z.HealObject(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, bucketMetadataFile), "", hopts)
// .metadata.bin healing is not needed here, it is automatically healed via read() call.
return z.s3Peer.HealBucket(ctx, bucket, opts)
}
@@ -1956,7 +1937,7 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts
// error walker returns error. Optionally if context.Done() is received
// then Walk() stops the walker.
func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, results chan<- ObjectInfo, opts WalkOptions) error {
if err := checkListObjsArgs(ctx, bucket, prefix, "", z); err != nil {
if err := checkListObjsArgs(ctx, bucket, prefix, ""); err != nil {
// Upon error close the channel.
xioutil.SafeClose(results)
return err
@@ -2282,55 +2263,23 @@ type HealthOptions struct {
// was queried
type HealthResult struct {
Healthy bool
HealthyRead bool
HealingDrives int
ESHealth []struct {
Maintenance bool
PoolID, SetID int
Healthy bool
HealthyRead bool
HealthyDrives int
HealingDrives int
ReadQuorum int
WriteQuorum int
}
WriteQuorum int
ReadQuorum int
UsingDefaults bool
}
// ReadHealth returns if the cluster can serve read requests
func (z *erasureServerPools) ReadHealth(ctx context.Context) bool {
erasureSetUpCount := make([][]int, len(z.serverPools))
for i := range z.serverPools {
erasureSetUpCount[i] = make([]int, len(z.serverPools[i].sets))
}
diskIDs := globalNotificationSys.GetLocalDiskIDs(ctx)
diskIDs = append(diskIDs, getLocalDiskIDs(z))
for _, localDiskIDs := range diskIDs {
for _, id := range localDiskIDs {
poolIdx, setIdx, _, err := z.getPoolAndSet(id)
if err != nil {
logger.LogIf(ctx, err)
continue
}
erasureSetUpCount[poolIdx][setIdx]++
}
}
b := z.BackendInfo()
poolReadQuorums := make([]int, len(b.StandardSCData))
copy(poolReadQuorums, b.StandardSCData)
for poolIdx := range erasureSetUpCount {
for setIdx := range erasureSetUpCount[poolIdx] {
if erasureSetUpCount[poolIdx][setIdx] < poolReadQuorums[poolIdx] {
return false
}
}
}
return true
}
// Health - returns current status of the object layer health,
// provides if write access exists across sets, additionally
// can be used to query scenarios if health may be lost
@@ -2399,9 +2348,20 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
}
}
var maximumReadQuorum int
for _, readQuorum := range poolReadQuorums {
if maximumReadQuorum == 0 {
maximumReadQuorum = readQuorum
}
if readQuorum > maximumReadQuorum {
maximumReadQuorum = readQuorum
}
}
result := HealthResult{
Healthy: true,
WriteQuorum: maximumWriteQuorum,
ReadQuorum: maximumReadQuorum,
UsingDefaults: usingDefaults, // indicates if config was not initialized and we are using defaults on this node.
}
@@ -2411,6 +2371,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
Maintenance bool
PoolID, SetID int
Healthy bool
HealthyRead bool
HealthyDrives, HealingDrives int
ReadQuorum, WriteQuorum int
}{
@@ -2418,6 +2379,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
SetID: setIdx,
PoolID: poolIdx,
Healthy: erasureSetUpCount[poolIdx][setIdx].online >= poolWriteQuorums[poolIdx],
HealthyRead: erasureSetUpCount[poolIdx][setIdx].online >= poolReadQuorums[poolIdx],
HealthyDrives: erasureSetUpCount[poolIdx][setIdx].online,
HealingDrives: erasureSetUpCount[poolIdx][setIdx].healing,
ReadQuorum: poolReadQuorums[poolIdx],
@@ -2430,6 +2392,12 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
fmt.Errorf("Write quorum may be lost on pool: %d, set: %d, expected write quorum: %d",
poolIdx, setIdx, poolWriteQuorums[poolIdx]))
}
result.HealthyRead = erasureSetUpCount[poolIdx][setIdx].online >= poolReadQuorums[poolIdx]
if !result.HealthyRead {
logger.LogIf(logger.SetReqInfo(ctx, reqInfo),
fmt.Errorf("Read quorum may be lost on pool: %d, set: %d, expected read quorum: %d",
poolIdx, setIdx, poolReadQuorums[poolIdx]))
}
}
}
+42 -29
View File
@@ -264,13 +264,22 @@ func (s *erasureSets) connectDisks() {
disk.SetDiskLoc(s.poolIndex, setIndex, diskIndex)
disk.SetFormatData(formatData)
s.erasureDisks[setIndex][diskIndex] = disk
s.erasureDisksMu.Unlock()
if disk.IsLocal() && globalIsDistErasure {
if disk.IsLocal() {
globalLocalDrivesMu.Lock()
globalLocalSetDrives[s.poolIndex][setIndex][diskIndex] = disk
if globalIsDistErasure {
globalLocalSetDrives[s.poolIndex][setIndex][diskIndex] = disk
}
for i, ldisk := range globalLocalDrives {
_, k, l := ldisk.GetDiskLoc()
if k == setIndex && l == diskIndex {
globalLocalDrives[i] = disk
break
}
}
globalLocalDrivesMu.Unlock()
}
s.erasureDisksMu.Unlock()
}(endpoint)
}
@@ -316,15 +325,18 @@ func (s *erasureSets) GetLockers(setIndex int) func() ([]dsync.NetLocker, string
}
}
func (s *erasureSets) GetEndpointStrings(setIndex int) func() []string {
return func() []string {
eps := make([]string, s.setDriveCount)
copy(eps, s.endpointStrings[setIndex*s.setDriveCount:setIndex*s.setDriveCount+s.setDriveCount])
return eps
}
}
func (s *erasureSets) GetEndpoints(setIndex int) func() []Endpoint {
return func() []Endpoint {
s.erasureDisksMu.RLock()
defer s.erasureDisksMu.RUnlock()
eps := make([]Endpoint, s.setDriveCount)
for i := 0; i < s.setDriveCount; i++ {
eps[i] = s.endpoints.Endpoints[setIndex*s.setDriveCount+i]
}
copy(eps, s.endpoints.Endpoints[setIndex*s.setDriveCount:setIndex*s.setDriveCount+s.setDriveCount])
return eps
}
}
@@ -454,7 +466,6 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
return
}
disk.SetDiskLoc(s.poolIndex, m, n)
s.endpointStrings[m*setDriveCount+n] = disk.String()
s.erasureDisks[m][n] = disk
}(disk, i, j)
}
@@ -469,6 +480,7 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
getDisks: s.GetDisks(i),
getLockers: s.GetLockers(i),
getEndpoints: s.GetEndpoints(i),
getEndpointStrings: s.GetEndpointStrings(i),
nsMutex: mutex,
}
}(i)
@@ -562,18 +574,11 @@ func auditObjectErasureSet(ctx context.Context, object string, set *erasureObjec
return
}
object = decodeDirObject(object)
endpoints := set.getEndpoints()
disksEndpoints := make([]string, 0, len(endpoints))
for _, endpoint := range endpoints {
disksEndpoints = append(disksEndpoints, endpoint.String())
}
op := auditObjectOp{
Name: object,
Name: decodeDirObject(object),
Pool: set.poolIndex + 1,
Set: set.setIndex + 1,
Disks: disksEndpoints,
Disks: set.getEndpointStrings(),
}
logger.GetReqInfo(ctx).AppendTags("objectLocation", op)
@@ -733,7 +738,7 @@ func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBucke
// we ignore disk not found errors
return nil
}
volsInfo, err := storageDisks[index].ListDir(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix), -1)
volsInfo, err := storageDisks[index].ListDir(ctx, "", minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix), -1)
if err != nil {
if errors.Is(err, errFileNotFound) {
return nil
@@ -1136,14 +1141,13 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
xldisk, ok := disk.(*xlStorageDiskIDCheck)
if ok {
if driveQuorum {
commonWrites, commonDeletes := calcCommonWritesDeletes(currentDisksInfo[m], (s.setDriveCount+1)/2)
xldisk.totalWrites.Store(commonWrites)
xldisk.totalDeletes.Store(commonDeletes)
xldisk.storage.setWriteAttribute(commonWrites)
xldisk.storage.setDeleteAttribute(commonDeletes)
_, commonDeletes := calcCommonWritesDeletes(currentDisksInfo[m], (s.setDriveCount+1)/2)
xldisk.totalDeletes.Store(commonDeletes)
xldisk.storage.setDeleteAttribute(commonDeletes)
if globalDriveMonitoring {
go xldisk.monitorDiskWritable(xldisk.diskCtx)
}
go xldisk.monitorDiskWritable(xldisk.diskCtx)
}
} else {
disk.Close() // Close the remote storage client, re-initialize with healthchecks.
@@ -1156,9 +1160,18 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
s.erasureDisks[m][n] = disk
if disk.IsLocal() && globalIsDistErasure {
if disk.IsLocal() {
globalLocalDrivesMu.Lock()
globalLocalSetDrives[s.poolIndex][m][n] = disk
if globalIsDistErasure {
globalLocalSetDrives[s.poolIndex][m][n] = disk
}
for i, ldisk := range globalLocalDrives {
_, k, l := ldisk.GetDiskLoc()
if k == m && l == n {
globalLocalDrives[i] = disk
break
}
}
globalLocalDrivesMu.Unlock()
}
}
+3 -3
View File
@@ -174,18 +174,18 @@ func TestNewErasureSets(t *testing.T) {
}
endpoints := mustGetNewEndpoints(0, 16, erasureDisks...)
_, _, err := waitForFormatErasure(true, endpoints, 1, 0, 16, "", "")
_, _, err := waitForFormatErasure(true, endpoints, 1, 0, 16, "")
if err != errInvalidArgument {
t.Fatalf("Expecting error, got %s", err)
}
_, _, err = waitForFormatErasure(true, nil, 1, 1, 16, "", "")
_, _, err = waitForFormatErasure(true, nil, 1, 1, 16, "")
if err != errInvalidArgument {
t.Fatalf("Expecting error, got %s", err)
}
// Initializes all erasure disks
storageDisks, format, err := waitForFormatErasure(true, endpoints, 1, 1, 16, "", "")
storageDisks, format, err := waitForFormatErasure(true, endpoints, 1, 1, 16, "")
if err != nil {
t.Fatalf("Unable to format drives for erasure, %s", err)
}
-14
View File
@@ -25,7 +25,6 @@ import (
"strings"
"github.com/klauspost/reedsolomon"
"github.com/minio/minio/internal/logger"
)
// getDataBlockLen - get length of data blocks from encoded blocks.
@@ -43,19 +42,16 @@ func getDataBlockLen(enBlocks [][]byte, dataBlocks int) int {
func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, dataBlocks int, offset int64, length int64) (int64, error) {
// Offset and out size cannot be negative.
if offset < 0 || length < 0 {
logger.LogIf(ctx, errUnexpected)
return 0, errUnexpected
}
// Do we have enough blocks?
if len(enBlocks) < dataBlocks {
logger.LogIf(ctx, fmt.Errorf("diskBlocks(%d)/dataBlocks(%d) - %w", len(enBlocks), dataBlocks, reedsolomon.ErrTooFewShards))
return 0, reedsolomon.ErrTooFewShards
}
// Do we have enough data?
if int64(getDataBlockLen(enBlocks, dataBlocks)) < length {
logger.LogIf(ctx, fmt.Errorf("getDataBlockLen(enBlocks, dataBlocks)(%d)/length(%d) - %w", getDataBlockLen(enBlocks, dataBlocks), length, reedsolomon.ErrShortData))
return 0, reedsolomon.ErrShortData
}
@@ -85,11 +81,6 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data
if write < int64(len(block)) {
n, err := dst.Write(block[:write])
if err != nil {
// The writer will be closed in case of range queries, which will emit ErrClosedPipe.
// The reader pipe might be closed at ListObjects io.EOF ignore it.
if err != io.ErrClosedPipe && err != io.EOF {
logger.LogIf(ctx, err)
}
return 0, err
}
totalWritten += int64(n)
@@ -99,11 +90,6 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data
// Copy the block.
n, err := dst.Write(block)
if err != nil {
// The writer will be closed in case of range queries, which will emit ErrClosedPipe.
// The reader pipe might be closed at ListObjects io.EOF ignore it.
if err != io.ErrClosedPipe && err != io.EOF {
logger.LogIf(ctx, err)
}
return 0, err
}
+5 -3
View File
@@ -58,10 +58,14 @@ type erasureObjects struct {
// getLockers returns list of remote and local lockers.
getLockers func() ([]dsync.NetLocker, string)
// getEndpoints returns list of endpoint strings belonging this set.
// getEndpoints returns list of endpoint belonging this set.
// some may be local and some remote.
getEndpoints func() []Endpoint
// getEndpoints returns list of endpoint strings belonging this set.
// some may be local and some remote.
getEndpointStrings func() []string
// Locker mutex map.
nsMutex *nsLockMap
}
@@ -203,7 +207,6 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint, metrics bool) (disks
APICalls: make(map[string]uint64, len(info.Metrics.APICalls)),
TotalErrorsAvailability: info.Metrics.TotalErrorsAvailability,
TotalErrorsTimeout: info.Metrics.TotalErrorsTimeout,
TotalTokens: info.Metrics.TotalTokens,
TotalWaiting: info.Metrics.TotalWaiting,
}
for k, v := range info.Metrics.LastMinute {
@@ -497,7 +500,6 @@ func (er erasureObjects) nsScanner(ctx context.Context, buckets []BucketInfo, wa
cache.Info.SkipHealing = healing
cache.Info.NextCycle = wantCycle
if cache.Info.Name != bucket.Name {
logger.LogIf(ctx, fmt.Errorf("cache name mismatch: %s != %s", cache.Info.Name, bucket.Name))
cache.Info = dataUsageCacheInfo{
Name: bucket.Name,
LastUpdate: time.Time{},
+5 -1
View File
@@ -225,7 +225,11 @@ func (args eventArgs) ToEvent(escape bool) event.Event {
},
}
if args.EventName != event.ObjectRemovedDelete && args.EventName != event.ObjectRemovedDeleteMarkerCreated {
isRemovedEvent := args.EventName == event.ObjectRemovedDelete ||
args.EventName == event.ObjectRemovedDeleteMarkerCreated ||
args.EventName == event.ObjectRemovedNoOP
if !isRemovedEvent {
newEvent.S3.Object.ETag = args.Object.ETag
newEvent.S3.Object.Size = args.Object.Size
newEvent.S3.Object.ContentType = args.Object.ContentType
+9 -16
View File
@@ -306,7 +306,12 @@ func countErrs(errs []error, err error) int {
return i
}
// Check if unformatted disks are equal to write quorum.
// Does all errors indicate we need to initialize all disks?.
func shouldInitErasureDisks(errs []error) bool {
return countErrs(errs, errUnformattedDisk) == len(errs)
}
// Check if unformatted disks are equal to 50%+1 of all the drives.
func quorumUnformattedDisks(errs []error) bool {
return countErrs(errs, errUnformattedDisk) >= (len(errs)/2)+1
}
@@ -761,7 +766,7 @@ func fixFormatErasureV3(storageDisks []StorageAPI, endpoints Endpoints, formats
}
// initFormatErasure - save Erasure format configuration on all disks.
func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount, setDriveCount int, deploymentID, distributionAlgo string, sErrs []error) (*formatErasureV3, error) {
func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount, setDriveCount int, deploymentID string, sErrs []error) (*formatErasureV3, error) {
format := newFormatErasureV3(setCount, setDriveCount)
formats := make([]*formatErasureV3, len(storageDisks))
wantAtMost, err := ecDrivesNoConfig(setDriveCount)
@@ -773,14 +778,8 @@ func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount,
hostCount := make(map[string]int, setDriveCount)
for j := 0; j < setDriveCount; j++ {
disk := storageDisks[i*setDriveCount+j]
if disk == nil {
continue
}
newFormat := format.Clone()
newFormat.Erasure.This = format.Erasure.Sets[i][j]
if distributionAlgo != "" {
newFormat.Erasure.DistributionAlgo = distributionAlgo
}
if deploymentID != "" {
newFormat.ID = deploymentID
}
@@ -803,14 +802,8 @@ func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount,
logger.Info(" - Drive: %s", disk.String())
}
})
var warning string
if wantAtMost == 0 {
warning = fmt.Sprintf("Host %v has all drives of set. ", host)
} else {
warning = fmt.Sprintf("Host %v has more than %v drives of set. ", host, wantAtMost)
}
logger.Info(color.Yellow("WARNING: ") + warning +
"A host failure will result in data becoming unavailable.")
logger.Info(color.Yellow("WARNING:")+" Host %v has more than %v drives of set. "+
"A host failure will result in data becoming unavailable.", host, wantAtMost)
}
}
}
+6 -6
View File
@@ -35,7 +35,7 @@ type minioLogger struct{}
// Print implement Logger
func (log *minioLogger) Print(sessionID string, message interface{}) {
if serverDebugLog {
logger.Info("%s %s", sessionID, message)
fmt.Printf("%s %s\n", sessionID, message)
}
}
@@ -43,9 +43,9 @@ func (log *minioLogger) Print(sessionID string, message interface{}) {
func (log *minioLogger) Printf(sessionID string, format string, v ...interface{}) {
if serverDebugLog {
if sessionID != "" {
logger.Info("%s %s", sessionID, fmt.Sprintf(format, v...))
fmt.Printf("%s %s\n", sessionID, fmt.Sprintf(format, v...))
} else {
logger.Info(format, v...)
fmt.Printf(format+"\n", v...)
}
}
}
@@ -54,9 +54,9 @@ func (log *minioLogger) Printf(sessionID string, format string, v ...interface{}
func (log *minioLogger) PrintCommand(sessionID string, command string, params string) {
if serverDebugLog {
if command == "PASS" {
logger.Info("%s > PASS ****", sessionID)
fmt.Printf("%s > PASS ****\n", sessionID)
} else {
logger.Info("%s > %s %s", sessionID, command, params)
fmt.Printf("%s > %s %s\n", sessionID, command, params)
}
}
}
@@ -64,7 +64,7 @@ func (log *minioLogger) PrintCommand(sessionID string, command string, params st
// PrintResponse implement Logger
func (log *minioLogger) PrintResponse(sessionID string, code int, message string) {
if serverDebugLog {
logger.Info("%s < %d %s", sessionID, code, message)
fmt.Printf("%s < %d %s\n", sessionID, code, message)
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
numHealers = uint64(v)
}
logger.Info(fmt.Sprintf("Healing drive '%s' - use %d parallel workers.", tracker.disk.String(), numHealers))
logger.Event(ctx, fmt.Sprintf("Healing drive '%s' - use %d parallel workers.", tracker.disk.String(), numHealers))
jt, _ := workers.New(int(numHealers))
+20 -7
View File
@@ -34,6 +34,7 @@ import (
"github.com/minio/minio/internal/bucket/bandwidth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/config/browser"
"github.com/minio/minio/internal/grid"
"github.com/minio/minio/internal/handlers"
"github.com/minio/minio/internal/kms"
"go.uber.org/atomic"
@@ -56,6 +57,7 @@ import (
"github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/pubsub"
"github.com/minio/pkg/v2/certs"
"github.com/minio/pkg/v2/env"
xnet "github.com/minio/pkg/v2/net"
)
@@ -129,6 +131,11 @@ const (
tlsClientSessionCacheSize = 100
)
func init() {
// Injected to prevent circular dependency.
pubsub.GetByteBuffer = grid.GetByteBuffer
}
type poolDisksLayout struct {
cmdline string
layout [][]string
@@ -153,13 +160,15 @@ type serverCtxt struct {
FTP []string
SFTP []string
UserTimeout time.Duration
ConnReadDeadline time.Duration
ConnWriteDeadline time.Duration
UserTimeout time.Duration
ConnReadDeadline time.Duration
ConnWriteDeadline time.Duration
ConnClientReadDeadline time.Duration
ShutdownTimeout time.Duration
IdleTimeout time.Duration
ReadHeaderTimeout time.Duration
ShutdownTimeout time.Duration
IdleTimeout time.Duration
ReadHeaderTimeout time.Duration
MaxIdleConnsPerHost int
// The layout of disks as interpreted
Layout disksLayout
@@ -388,6 +397,8 @@ var (
globalRemoteTargetTransport http.RoundTripper
globalHealthChkTransport http.RoundTripper
globalDNSCache = &dnscache.Resolver{
Timeout: 5 * time.Second,
}
@@ -408,10 +419,12 @@ var (
globalServiceFreezeMu sync.Mutex // Updates.
// List of local drives to this node, this is only set during server startup,
// and should never be mutated. Hold globalLocalDrivesMu to access.
// and is only mutated by HealFormat. Hold globalLocalDrivesMu to access.
globalLocalDrives []StorageAPI
globalLocalDrivesMu sync.RWMutex
globalDriveMonitoring = env.Get("_MINIO_DRIVE_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn
// Is MINIO_CI_CD set?
globalIsCICD bool
+26 -15
View File
@@ -56,16 +56,31 @@ type apiConfig struct {
syncEvents bool
}
const cgroupLimitFile = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
const (
cgroupV1MemLimitFile = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
cgroupV2MemLimitFile = "/sys/fs/cgroup/memory.max"
cgroupMemNoLimit = 9223372036854771712
)
func cgroupLimit(limitFile string) (limit uint64) {
buf, err := os.ReadFile(limitFile)
func cgroupMemLimit() (limit uint64) {
buf, err := os.ReadFile(cgroupV2MemLimitFile)
if err != nil {
return 9223372036854771712
buf, err = os.ReadFile(cgroupV1MemLimitFile)
}
if err != nil {
return 0
}
limit, err = strconv.ParseUint(string(buf), 10, 64)
if err != nil {
return 9223372036854771712
// The kernel can return valid but non integer values
// but still, no need to interpret more
return 0
}
if limit == cgroupMemNoLimit {
// No limit set, It's the highest positive signed 64-bit
// integer (2^63-1), rounded down to multiples of 4096 (2^12),
// the most common page size on x86 systems - for cgroup_limits.
return 0
}
return limit
}
@@ -74,23 +89,19 @@ func availableMemory() (available uint64) {
available = 8 << 30 // Default to 8 GiB when we can't find the limits.
if runtime.GOOS == "linux" {
available = cgroupLimit(cgroupLimitFile)
// No limit set, It's the highest positive signed 64-bit
// integer (2^63-1), rounded down to multiples of 4096 (2^12),
// the most common page size on x86 systems - for cgroup_limits.
if available != 9223372036854771712 {
// This means cgroup memory limit is configured.
// Useful in container mode
limit := cgroupMemLimit()
if limit > 0 {
// A valid value is found
available = limit
return
} // no-limit set proceed to set the limits based on virtual memory.
}
} // for all other platforms limits are based on virtual memory.
memStats, err := mem.VirtualMemory()
if err != nil {
return
}
available = memStats.Available / 2
return
}
+1 -1
View File
@@ -32,7 +32,7 @@ import (
)
// Tests validate bucket LocationConstraint.
func TestIsValidLocationContraint(t *testing.T) {
func TestIsValidLocationConstraint(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
+20 -4
View File
@@ -81,12 +81,28 @@ func ClusterReadCheckHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(ctx, globalAPIConfig.getClusterDeadline())
defer cancel()
result := objLayer.ReadHealth(ctx)
if !result {
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
opts := HealthOptions{
Maintenance: r.Form.Get("maintenance") == "true",
DeploymentType: r.Form.Get("deployment-type"),
}
result := objLayer.Health(ctx, opts)
w.Header().Set(xhttp.MinIOReadQuorum, strconv.Itoa(result.ReadQuorum))
w.Header().Set(xhttp.MinIOStorageClassDefaults, strconv.FormatBool(result.UsingDefaults))
// return how many drives are being healed if any
if result.HealingDrives > 0 {
w.Header().Set(xhttp.MinIOHealingDrives, strconv.Itoa(result.HealingDrives))
}
if !result.HealthyRead {
// As a maintenance call we are purposefully asked to be taken
// down, this is for orchestrators to know if we can safely
// take this server down, return appropriate error.
if opts.Maintenance {
writeResponse(w, http.StatusPreconditionFailed, nil, mimeNone)
} else {
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
}
return
}
writeResponse(w, http.StatusOK, nil, mimeNone)
}
+38 -39
View File
@@ -1166,8 +1166,11 @@ func (store *IAMStoreSys) PolicyNotificationHandler(ctx context.Context, policy
return err
}
// DeletePolicy - deletes policy from storage and cache.
func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string) error {
// DeletePolicy - deletes policy from storage and cache. When this called in
// response to a notification (i.e. isFromNotification = true), it skips the
// validation of policy usage and the attempt to delete in the backend as well
// (as this is already done by the notifying node).
func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string, isFromNotification bool) error {
if policy == "" {
return errInvalidArgument
}
@@ -1175,42 +1178,44 @@ func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string) error
cache := store.lock()
defer store.unlock()
// Check if policy is mapped to any existing user or group. If so, we do not
// allow deletion of the policy. If the policy is mapped to an STS account,
// we do allow deletion.
users := []string{}
groups := []string{}
for u, mp := range cache.iamUserPolicyMap {
pset := mp.policySet()
if store.getUsersSysType() == MinIOUsersSysType {
if _, ok := cache.iamUsersMap[u]; !ok {
// This case can happen when a temporary account is
// deleted or expired - remove it from userPolicyMap.
delete(cache.iamUserPolicyMap, u)
continue
if !isFromNotification {
// Check if policy is mapped to any existing user or group. If so, we do not
// allow deletion of the policy. If the policy is mapped to an STS account,
// we do allow deletion.
users := []string{}
groups := []string{}
for u, mp := range cache.iamUserPolicyMap {
pset := mp.policySet()
if store.getUsersSysType() == MinIOUsersSysType {
if _, ok := cache.iamUsersMap[u]; !ok {
// This case can happen when a temporary account is
// deleted or expired - remove it from userPolicyMap.
delete(cache.iamUserPolicyMap, u)
continue
}
}
if pset.Contains(policy) {
users = append(users, u)
}
}
if pset.Contains(policy) {
users = append(users, u)
for g, mp := range cache.iamGroupPolicyMap {
pset := mp.policySet()
if pset.Contains(policy) {
groups = append(groups, g)
}
}
}
for g, mp := range cache.iamGroupPolicyMap {
pset := mp.policySet()
if pset.Contains(policy) {
groups = append(groups, g)
if len(users) != 0 || len(groups) != 0 {
return errPolicyInUse
}
}
if len(users) != 0 || len(groups) != 0 {
return errPolicyInUse
}
err := store.deletePolicyDoc(ctx, policy)
if errors.Is(err, errNoSuchPolicy) {
// Ignore error if policy is already deleted.
err = nil
}
if err != nil {
return err
err := store.deletePolicyDoc(ctx, policy)
if errors.Is(err, errNoSuchPolicy) {
// Ignore error if policy is already deleted.
err = nil
}
if err != nil {
return err
}
}
delete(cache.iamPolicyDocsMap, policy)
@@ -2424,17 +2429,11 @@ func (store *IAMStoreSys) GetSTSAndServiceAccounts() []auth.Credentials {
var res []auth.Credentials
for _, u := range cache.iamUsersMap {
cred := u.Credentials
if cred.IsTemp() {
panic("unexpected STS credential found in iamUsersMap")
}
if cred.IsServiceAccount() {
res = append(res, cred)
}
}
for _, u := range cache.iamSTSAccountsMap {
if !u.Credentials.IsTemp() {
panic("unexpected non STS credential found in iamSTSAccountsMap")
}
res = append(res, u.Credentials)
}
+95 -38
View File
@@ -270,16 +270,13 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
setGlobalAuthZPlugin(polplugin.New(authZPluginCfg))
sys.Lock()
defer sys.Unlock()
sys.LDAPConfig = ldapConfig
sys.OpenIDConfig = openidConfig
sys.STSTLSConfig = stsTLSConfig
sys.iamRefreshInterval = iamRefreshInterval
// Initialize IAM store
sys.initStore(objAPI, etcdClient)
sys.Unlock()
retryCtx, cancel := context.WithCancel(ctx)
@@ -529,7 +526,9 @@ func (sys *IAMSys) GetRolePolicy(arnStr string) (arn.ARN, string, error) {
return roleArn, rolePolicy, nil
}
// DeletePolicy - deletes a canned policy from backend or etcd.
// DeletePolicy - deletes a canned policy from backend. `notifyPeers` is true
// whenever this is called via the API. It is false when called via a
// notification from another peer. This is to avoid infinite loops.
func (sys *IAMSys) DeletePolicy(ctx context.Context, policyName string, notifyPeers bool) error {
if !sys.Initialized() {
return errServerNotInitialized
@@ -543,7 +542,7 @@ func (sys *IAMSys) DeletePolicy(ctx context.Context, policyName string, notifyPe
}
}
err := sys.store.DeletePolicy(ctx, policyName)
err := sys.store.DeletePolicy(ctx, policyName, !notifyPeers)
if err != nil {
return err
}
@@ -941,6 +940,13 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
return auth.Credentials{}, time.Time{}, errInvalidArgument
}
if len(opts.accessKey) > 0 && len(opts.secretKey) == 0 {
return auth.Credentials{}, time.Time{}, auth.ErrNoSecretKeyWithAccessKey
}
if len(opts.secretKey) > 0 && len(opts.accessKey) == 0 {
return auth.Credentials{}, time.Time{}, auth.ErrNoAccessKeyWithSecretKey
}
var policyBuf []byte
if opts.sessionPolicy != nil {
err := opts.sessionPolicy.Validate()
@@ -974,7 +980,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
m[iamPolicyClaimNameSA()] = inheritedPolicyType
}
// Add all the necessary claims for the service accounts.
// Add all the necessary claims for the service account.
for k, v := range opts.claims {
_, ok := m[k]
if !ok {
@@ -984,7 +990,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
var accessKey, secretKey string
var err error
if len(opts.accessKey) > 0 {
if len(opts.accessKey) > 0 || len(opts.secretKey) > 0 {
accessKey, secretKey = opts.accessKey, opts.secretKey
} else {
accessKey, secretKey, err = auth.GenerateCredentials()
@@ -1848,37 +1854,14 @@ func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string)
return isOwnerDerived || combinedPolicy.IsAllowed(parentArgs)
}
// Now check if we have a sessionPolicy.
spolicy, ok := args.Claims[sessionPolicyNameExtracted]
if !ok {
return false
// 3. If an inline session-policy is present, evaluate it.
hasSessionPolicy, isAllowedSP := isAllowedBySessionPolicyForServiceAccount(args)
if hasSessionPolicy {
return isAllowedSP && (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
}
spolicyStr, ok := spolicy.(string)
if !ok {
// Sub policy if set, should be a string reject
// malformed/malicious requests.
return false
}
// Check if policy is parseable.
subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
if err != nil {
// Log any error in input session policy config.
logger.LogIf(GlobalContext, err)
return false
}
// This can only happen if policy was set but with an empty JSON.
if subPolicy.Version == "" && len(subPolicy.Statements) == 0 {
return isOwnerDerived || combinedPolicy.IsAllowed(parentArgs)
}
if subPolicy.Version == "" {
return false
}
return subPolicy.IsAllowed(parentArgs) && (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
// Sub policy not set. Evaluate only the parent policies.
return (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
}
// IsAllowedSTS is meant for STS based temporary credentials,
@@ -1968,6 +1951,67 @@ func (sys *IAMSys) IsAllowedSTS(args policy.Args, parentUser string) bool {
return isOwnerDerived || combinedPolicy.IsAllowed(args)
}
func isAllowedBySessionPolicyForServiceAccount(args policy.Args) (hasSessionPolicy bool, isAllowed bool) {
hasSessionPolicy = false
isAllowed = false
// Now check if we have a sessionPolicy.
spolicy, ok := args.Claims[sessionPolicyNameExtracted]
if !ok {
return
}
hasSessionPolicy = true
spolicyStr, ok := spolicy.(string)
if !ok {
// Sub policy if set, should be a string reject
// malformed/malicious requests.
return
}
// Check if policy is parseable.
subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
if err != nil {
// Log any error in input session policy config.
logger.LogIf(GlobalContext, err)
return
}
// SPECIAL CASE: For service accounts, any valid JSON is allowed as a
// policy, regardless of whether the number of statements is 0, this
// includes `null`, `{}` and `{"Statement": null}`. In fact, MinIO Console
// sends `null` when no policy is set and the intended behavior is that the
// service account should inherit parent policy.
//
// However, for a policy like `{"Statement":[]}`, the intention is to not
// provide any permissions via the session policy - i.e. the service account
// can do nothing (such a JSON could be generated by an external application
// as the policy for the service account). Inheriting the parent policy in
// such a case, is a security issue. Ideally, we should not allow such
// behavior, but for compatibility with the Console, we currently allow it.
//
// TODO:
//
// 1. fix console behavior and allow this inheritance for service accounts
// created before a certain (TBD) future date.
//
// 2. do not allow empty statement policies for service accounts.
if subPolicy.Version == "" && subPolicy.Statements == nil && subPolicy.ID == "" {
hasSessionPolicy = false
return
}
// As the session policy exists, even if the parent is the root account, it
// must be restricted by it. So, we set `.IsOwner` to false here
// unconditionally.
sessionPolicyArgs := args
sessionPolicyArgs.IsOwner = false
// Sub policy is set and valid.
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
}
func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowed bool) {
hasSessionPolicy = false
isAllowed = false
@@ -2000,8 +2044,14 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe
return
}
// As the session policy exists, even if the parent is the root account, it
// must be restricted by it. So, we set `.IsOwner` to false here
// unconditionally.
sessionPolicyArgs := args
sessionPolicyArgs.IsOwner = false
// Sub policy is set and valid.
return hasSessionPolicy, subPolicy.IsAllowed(args)
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
}
// GetCombinedPolicy returns a combined policy combining all policies
@@ -2010,6 +2060,13 @@ func (sys *IAMSys) GetCombinedPolicy(policies ...string) policy.Policy {
return policy
}
// doesPolicyAllow - checks if the given policy allows the passed action with given args. This is rarely needed.
// Notice there is no account name involved, so this is a dangerous function.
func (sys *IAMSys) doesPolicyAllow(policy string, args policy.Args) bool {
// Policies were found, evaluate all of them.
return sys.GetCombinedPolicy(policy).IsAllowed(args)
}
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (sys *IAMSys) IsAllowed(args policy.Args) bool {
// If opa is configured, use OPA always.
+5 -1
View File
@@ -46,7 +46,7 @@ var (
errAccessKeyDisabled = errors.New("The access key you provided is disabled")
errAuthentication = errors.New("Authentication failed, check your access credentials")
errNoAuthToken = errors.New("JWT token missing")
errSkewedAuthTime = errors.New("Skewed authenticationdate/time")
errSkewedAuthTime = errors.New("Skewed authentication date/time")
errMalformedAuth = errors.New("Malformed authentication input")
)
@@ -113,6 +113,10 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b
return nil, errInvalidAccessKeyID
}
cred := u.Credentials
// Expired credentials return error.
if cred.IsTemp() && cred.IsExpired() {
return nil, errInvalidAccessKeyID
}
return []byte(cred.SecretKey), nil
} // this means claims.AccessKey == rootAccessKey
if !globalAPIConfig.permitRootAccess() {
+32 -7
View File
@@ -18,12 +18,14 @@
package cmd
import (
"bytes"
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/grid"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/pubsub"
"github.com/minio/mux"
@@ -116,11 +118,32 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
// Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers.
// Use buffered channel to take care of burst sends or slow w.Write()
listenCh := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity()*len(globalEndpoints.Hostnames()))
mergeCh := make(chan []byte, globalAPIConfig.getRequestsPoolCapacity()*len(globalEndpoints.Hostnames()))
localCh := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity())
// Convert local messages to JSON and send to mergeCh
go func() {
buf := bytes.NewBuffer(grid.GetByteBuffer()[:0])
enc := json.NewEncoder(buf)
tmpEvt := struct{ Records []event.Event }{[]event.Event{{}}}
for {
select {
case ev := <-localCh:
buf.Reset()
tmpEvt.Records[0] = ev
if err := enc.Encode(tmpEvt); err != nil {
logger.LogOnceIf(ctx, err, "event: Encode failed")
continue
}
mergeCh <- append(grid.GetByteBuffer()[:0], buf.Bytes()...)
case <-ctx.Done():
grid.PutByteBuffer(buf.Bytes())
return
}
}
}()
peers, _ := newPeerRestClients(globalEndpoints)
err := globalHTTPListen.Subscribe(mask, listenCh, ctx.Done(), func(ev event.Event) bool {
err := globalHTTPListen.Subscribe(mask, localCh, ctx.Done(), func(ev event.Event) bool {
if ev.S3.Bucket.Name != "" && bucketName != "" {
if ev.S3.Bucket.Name != bucketName {
return false
@@ -139,7 +162,7 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
if peer == nil {
continue
}
peer.Listen(listenCh, ctx.Done(), values)
peer.Listen(ctx, mergeCh, values)
}
var (
@@ -170,14 +193,16 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
enc := json.NewEncoder(w)
for {
select {
case ev := <-listenCh:
if err := enc.Encode(struct{ Records []event.Event }{[]event.Event{ev}}); err != nil {
case ev := <-mergeCh:
_, err := w.Write(ev)
if err != nil {
return
}
if len(listenCh) == 0 {
if len(mergeCh) == 0 {
// Flush if nothing is queued
w.(http.Flusher).Flush()
}
grid.PutByteBuffer(ev)
case <-emptyEventTicker:
if err := enc.Encode(struct{ Records []event.Event }{}); err != nil {
return
+10 -7
View File
@@ -17,6 +17,8 @@
package cmd
//go:generate msgp -file=$GOFILE -unexported
import (
"context"
"fmt"
@@ -36,12 +38,9 @@ type lockRequesterInfo struct {
TimeLastRefresh time.Time // Timestamp for last lock refresh.
Source string // Contains line, function and filename requesting the lock.
Group bool // indicates if it was a group lock.
// Owner represents the UUID of the owner who originally requested the lock
// useful in expiry.
Owner string
// Quorum represents the quorum required for this lock to be active.
Quorum int
idx int
Owner string // Owner represents the UUID of the owner who originally requested the lock.
Quorum int // Quorum represents the quorum required for this lock to be active.
idx int `msg:"-"` // index of the lock in the lockMap.
}
// isWriteLock returns whether the lock is a write or read lock.
@@ -50,6 +49,8 @@ func isWriteLock(lri []lockRequesterInfo) bool {
}
// localLocker implements Dsync.NetLocker
//
//msgp:ignore localLocker
type localLocker struct {
mutex sync.Mutex
lockMap map[string][]lockRequesterInfo
@@ -238,7 +239,9 @@ func (l *localLocker) stats() lockStats {
return st
}
func (l *localLocker) DupLockMap() map[string][]lockRequesterInfo {
type localLockMap map[string][]lockRequesterInfo
func (l *localLocker) DupLockMap() localLockMap {
l.mutex.Lock()
defer l.mutex.Unlock()
+620
View File
@@ -0,0 +1,620 @@
package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *localLockMap) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0004 uint32
zb0004, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if (*z) == nil {
(*z) = make(localLockMap, zb0004)
} else if len((*z)) > 0 {
for key := range *z {
delete((*z), key)
}
}
for zb0004 > 0 {
zb0004--
var zb0001 string
var zb0002 []lockRequesterInfo
zb0001, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err)
return
}
var zb0005 uint32
zb0005, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err, zb0001)
return
}
if cap(zb0002) >= int(zb0005) {
zb0002 = (zb0002)[:zb0005]
} else {
zb0002 = make([]lockRequesterInfo, zb0005)
}
for zb0003 := range zb0002 {
err = zb0002[zb0003].DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, zb0001, zb0003)
return
}
}
(*z)[zb0001] = zb0002
}
return
}
// EncodeMsg implements msgp.Encodable
func (z localLockMap) EncodeMsg(en *msgp.Writer) (err error) {
err = en.WriteMapHeader(uint32(len(z)))
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0006, zb0007 := range z {
err = en.WriteString(zb0006)
if err != nil {
err = msgp.WrapError(err)
return
}
err = en.WriteArrayHeader(uint32(len(zb0007)))
if err != nil {
err = msgp.WrapError(err, zb0006)
return
}
for zb0008 := range zb0007 {
err = zb0007[zb0008].EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, zb0006, zb0008)
return
}
}
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z localLockMap) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendMapHeader(o, uint32(len(z)))
for zb0006, zb0007 := range z {
o = msgp.AppendString(o, zb0006)
o = msgp.AppendArrayHeader(o, uint32(len(zb0007)))
for zb0008 := range zb0007 {
o, err = zb0007[zb0008].MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, zb0006, zb0008)
return
}
}
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *localLockMap) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0004 uint32
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if (*z) == nil {
(*z) = make(localLockMap, zb0004)
} else if len((*z)) > 0 {
for key := range *z {
delete((*z), key)
}
}
for zb0004 > 0 {
var zb0001 string
var zb0002 []lockRequesterInfo
zb0004--
zb0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
var zb0005 uint32
zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, zb0001)
return
}
if cap(zb0002) >= int(zb0005) {
zb0002 = (zb0002)[:zb0005]
} else {
zb0002 = make([]lockRequesterInfo, zb0005)
}
for zb0003 := range zb0002 {
bts, err = zb0002[zb0003].UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, zb0001, zb0003)
return
}
}
(*z)[zb0001] = zb0002
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z localLockMap) Msgsize() (s int) {
s = msgp.MapHeaderSize
if z != nil {
for zb0006, zb0007 := range z {
_ = zb0007
s += msgp.StringPrefixSize + len(zb0006) + msgp.ArrayHeaderSize
for zb0008 := range zb0007 {
s += zb0007[zb0008].Msgsize()
}
}
}
return
}
// DecodeMsg implements msgp.Decodable
func (z *lockRequesterInfo) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "Name":
z.Name, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
case "Writer":
z.Writer, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "Writer")
return
}
case "UID":
z.UID, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "UID")
return
}
case "Timestamp":
z.Timestamp, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, "Timestamp")
return
}
case "TimeLastRefresh":
z.TimeLastRefresh, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, "TimeLastRefresh")
return
}
case "Source":
z.Source, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Source")
return
}
case "Group":
z.Group, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "Group")
return
}
case "Owner":
z.Owner, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Owner")
return
}
case "Quorum":
z.Quorum, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Quorum")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *lockRequesterInfo) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 9
// write "Name"
err = en.Append(0x89, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
if err != nil {
return
}
err = en.WriteString(z.Name)
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
// write "Writer"
err = en.Append(0xa6, 0x57, 0x72, 0x69, 0x74, 0x65, 0x72)
if err != nil {
return
}
err = en.WriteBool(z.Writer)
if err != nil {
err = msgp.WrapError(err, "Writer")
return
}
// write "UID"
err = en.Append(0xa3, 0x55, 0x49, 0x44)
if err != nil {
return
}
err = en.WriteString(z.UID)
if err != nil {
err = msgp.WrapError(err, "UID")
return
}
// write "Timestamp"
err = en.Append(0xa9, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70)
if err != nil {
return
}
err = en.WriteTime(z.Timestamp)
if err != nil {
err = msgp.WrapError(err, "Timestamp")
return
}
// write "TimeLastRefresh"
err = en.Append(0xaf, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68)
if err != nil {
return
}
err = en.WriteTime(z.TimeLastRefresh)
if err != nil {
err = msgp.WrapError(err, "TimeLastRefresh")
return
}
// write "Source"
err = en.Append(0xa6, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65)
if err != nil {
return
}
err = en.WriteString(z.Source)
if err != nil {
err = msgp.WrapError(err, "Source")
return
}
// write "Group"
err = en.Append(0xa5, 0x47, 0x72, 0x6f, 0x75, 0x70)
if err != nil {
return
}
err = en.WriteBool(z.Group)
if err != nil {
err = msgp.WrapError(err, "Group")
return
}
// write "Owner"
err = en.Append(0xa5, 0x4f, 0x77, 0x6e, 0x65, 0x72)
if err != nil {
return
}
err = en.WriteString(z.Owner)
if err != nil {
err = msgp.WrapError(err, "Owner")
return
}
// write "Quorum"
err = en.Append(0xa6, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d)
if err != nil {
return
}
err = en.WriteInt(z.Quorum)
if err != nil {
err = msgp.WrapError(err, "Quorum")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *lockRequesterInfo) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 9
// string "Name"
o = append(o, 0x89, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
o = msgp.AppendString(o, z.Name)
// string "Writer"
o = append(o, 0xa6, 0x57, 0x72, 0x69, 0x74, 0x65, 0x72)
o = msgp.AppendBool(o, z.Writer)
// string "UID"
o = append(o, 0xa3, 0x55, 0x49, 0x44)
o = msgp.AppendString(o, z.UID)
// string "Timestamp"
o = append(o, 0xa9, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70)
o = msgp.AppendTime(o, z.Timestamp)
// string "TimeLastRefresh"
o = append(o, 0xaf, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68)
o = msgp.AppendTime(o, z.TimeLastRefresh)
// string "Source"
o = append(o, 0xa6, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65)
o = msgp.AppendString(o, z.Source)
// string "Group"
o = append(o, 0xa5, 0x47, 0x72, 0x6f, 0x75, 0x70)
o = msgp.AppendBool(o, z.Group)
// string "Owner"
o = append(o, 0xa5, 0x4f, 0x77, 0x6e, 0x65, 0x72)
o = msgp.AppendString(o, z.Owner)
// string "Quorum"
o = append(o, 0xa6, 0x51, 0x75, 0x6f, 0x72, 0x75, 0x6d)
o = msgp.AppendInt(o, z.Quorum)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *lockRequesterInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "Name":
z.Name, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
case "Writer":
z.Writer, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Writer")
return
}
case "UID":
z.UID, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "UID")
return
}
case "Timestamp":
z.Timestamp, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Timestamp")
return
}
case "TimeLastRefresh":
z.TimeLastRefresh, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, "TimeLastRefresh")
return
}
case "Source":
z.Source, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Source")
return
}
case "Group":
z.Group, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Group")
return
}
case "Owner":
z.Owner, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Owner")
return
}
case "Quorum":
z.Quorum, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Quorum")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *lockRequesterInfo) Msgsize() (s int) {
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 7 + msgp.BoolSize + 4 + msgp.StringPrefixSize + len(z.UID) + 10 + msgp.TimeSize + 16 + msgp.TimeSize + 7 + msgp.StringPrefixSize + len(z.Source) + 6 + msgp.BoolSize + 6 + msgp.StringPrefixSize + len(z.Owner) + 7 + msgp.IntSize
return
}
// DecodeMsg implements msgp.Decodable
func (z *lockStats) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "Total":
z.Total, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Total")
return
}
case "Writes":
z.Writes, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Writes")
return
}
case "Reads":
z.Reads, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Reads")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z lockStats) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 3
// write "Total"
err = en.Append(0x83, 0xa5, 0x54, 0x6f, 0x74, 0x61, 0x6c)
if err != nil {
return
}
err = en.WriteInt(z.Total)
if err != nil {
err = msgp.WrapError(err, "Total")
return
}
// write "Writes"
err = en.Append(0xa6, 0x57, 0x72, 0x69, 0x74, 0x65, 0x73)
if err != nil {
return
}
err = en.WriteInt(z.Writes)
if err != nil {
err = msgp.WrapError(err, "Writes")
return
}
// write "Reads"
err = en.Append(0xa5, 0x52, 0x65, 0x61, 0x64, 0x73)
if err != nil {
return
}
err = en.WriteInt(z.Reads)
if err != nil {
err = msgp.WrapError(err, "Reads")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z lockStats) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 3
// string "Total"
o = append(o, 0x83, 0xa5, 0x54, 0x6f, 0x74, 0x61, 0x6c)
o = msgp.AppendInt(o, z.Total)
// string "Writes"
o = append(o, 0xa6, 0x57, 0x72, 0x69, 0x74, 0x65, 0x73)
o = msgp.AppendInt(o, z.Writes)
// string "Reads"
o = append(o, 0xa5, 0x52, 0x65, 0x61, 0x64, 0x73)
o = msgp.AppendInt(o, z.Reads)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *lockStats) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "Total":
z.Total, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Total")
return
}
case "Writes":
z.Writes, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Writes")
return
}
case "Reads":
z.Reads, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Reads")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z lockStats) Msgsize() (s int) {
s = 1 + 6 + msgp.IntSize + 7 + msgp.IntSize + 6 + msgp.IntSize
return
}
+349
View File
@@ -0,0 +1,349 @@
package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"bytes"
"testing"
"github.com/tinylib/msgp/msgp"
)
func TestMarshalUnmarshallocalLockMap(t *testing.T) {
v := localLockMap{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsglocalLockMap(b *testing.B) {
v := localLockMap{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsglocalLockMap(b *testing.B) {
v := localLockMap{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshallocalLockMap(b *testing.B) {
v := localLockMap{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodelocalLockMap(t *testing.T) {
v := localLockMap{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodelocalLockMap Msgsize() is inaccurate")
}
vn := localLockMap{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodelocalLockMap(b *testing.B) {
v := localLockMap{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodelocalLockMap(b *testing.B) {
v := localLockMap{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshallockRequesterInfo(t *testing.T) {
v := lockRequesterInfo{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsglockRequesterInfo(b *testing.B) {
v := lockRequesterInfo{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsglockRequesterInfo(b *testing.B) {
v := lockRequesterInfo{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshallockRequesterInfo(b *testing.B) {
v := lockRequesterInfo{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodelockRequesterInfo(t *testing.T) {
v := lockRequesterInfo{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodelockRequesterInfo Msgsize() is inaccurate")
}
vn := lockRequesterInfo{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodelockRequesterInfo(b *testing.B) {
v := lockRequesterInfo{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodelockRequesterInfo(b *testing.B) {
v := lockRequesterInfo{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshallockStats(t *testing.T) {
v := lockStats{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsglockStats(b *testing.B) {
v := lockStats{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsglockStats(b *testing.B) {
v := lockStats{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshallockStats(b *testing.B) {
v := lockStats{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodelockStats(t *testing.T) {
v := lockStats{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodelockStats Msgsize() is inaccurate")
}
vn := lockStats{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodelockStats(b *testing.B) {
v := lockStats{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodelockStats(b *testing.B) {
v := lockStats{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
+11 -5
View File
@@ -58,9 +58,10 @@ func renameAllBucketMetacache(epPath string) error {
// Other important fields are Limit, Marker.
// List ID always derived from the Marker.
func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (entries metaCacheEntriesSorted, err error) {
if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker, z); err != nil {
if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker); err != nil {
return entries, err
}
// Marker points to before the prefix, just ignore it.
if o.Marker < o.Prefix {
o.Marker = ""
@@ -304,6 +305,15 @@ func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions,
cancelList()
wg.Wait()
// we should return 'errs' from per disk
if isAllNotFound(errs) {
if isAllVolumeNotFound(errs) {
return errVolumeNotFound
}
return nil
}
if err != nil {
return err
}
@@ -312,10 +322,6 @@ func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions,
return ctx.Err()
}
if isAllNotFound(errs) {
return nil
}
for _, err := range errs {
if errors.Is(err, io.EOF) {
continue
+26 -8
View File
@@ -42,6 +42,8 @@ import (
"github.com/minio/pkg/v2/console"
)
//go:generate msgp -file $GOFILE -unexported
type listPathOptions struct {
// ID of the listing.
// This will be used to persist the list.
@@ -99,18 +101,18 @@ type listPathOptions struct {
// Versioning config is used for if the path
// has versioning enabled.
Versioning *versioning.Versioning
Versioning *versioning.Versioning `msg:"-"`
// Lifecycle performs filtering based on lifecycle.
// This will filter out objects if the most recent version should be deleted by lifecycle.
// Is not transferred across request calls.
Lifecycle *lifecycle.Lifecycle
Lifecycle *lifecycle.Lifecycle `msg:"-"`
// Retention configuration, needed to be passed along with lifecycle if set.
Retention lock.Retention
Retention lock.Retention `msg:"-"`
// Replication configuration
Replication replicationConfig
Replication replicationConfig `msg:"-"`
// StopDiskAtLimit will stop listing on each disk when limit number off objects has been returned.
StopDiskAtLimit bool
@@ -217,7 +219,10 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
// Do not return io.EOF
if resCh != nil {
resErr = nil
resCh <- results
select {
case resCh <- results:
case <-ctx.Done():
}
resCh = nil
returned = true
}
@@ -427,7 +432,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
if !disk.IsOnline() {
continue
}
_, err := disk.ReadVersion(ctx, minioMetaBucket,
_, err := disk.ReadVersion(ctx, "", minioMetaBucket,
o.objectPath(0), "", ReadOptions{})
if err != nil {
time.Sleep(retryDelay250)
@@ -504,7 +509,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
if !disk.IsOnline() {
continue
}
_, err := disk.ReadVersion(ctx, minioMetaBucket,
_, err := disk.ReadVersion(ctx, "", minioMetaBucket,
o.objectPath(partN), "", ReadOptions{})
if err != nil {
time.Sleep(retryDelay250)
@@ -767,6 +772,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions, resul
})
}
//msgp:ignore metaCacheRPC
type metaCacheRPC struct {
o listPathOptions
mu sync.Mutex
@@ -917,6 +923,7 @@ func (er *erasureObjects) saveMetaCacheStream(ctx context.Context, mc *metaCache
return nil
}
//msgp:ignore listPathRawOptions
type listPathRawOptions struct {
disks []StorageAPI
fallbackDisks []StorageAPI
@@ -1057,7 +1064,7 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
for {
// Get the top entry from each
var current metaCacheEntry
var atEOF, fnf, hasErr, agree int
var atEOF, fnf, vnf, hasErr, agree int
for i := range topEntries {
topEntries[i] = metaCacheEntry{}
}
@@ -1083,6 +1090,11 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
errDiskNotFound.Error():
atEOF++
fnf++
// This is a special case, to handle bucket does
// not exist situations.
if errors.Is(err, errVolumeNotFound) {
vnf++
}
continue
}
hasErr++
@@ -1140,6 +1152,10 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
return errors.New(strings.Join(combinedErr, ", "))
}
if vnf == len(readers) {
return errVolumeNotFound
}
// Break if all at EOF or error.
if atEOF+hasErr == len(readers) {
if hasErr > 0 && opts.finished != nil {
@@ -1147,9 +1163,11 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
}
break
}
if fnf == len(readers) {
return errFileNotFound
}
if agree == len(readers) {
// Everybody agreed
for _, r := range readers {
+535
View File
@@ -0,0 +1,535 @@
package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *listPathOptions) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "ID":
z.ID, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "ID")
return
}
case "Bucket":
z.Bucket, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Bucket")
return
}
case "BaseDir":
z.BaseDir, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "BaseDir")
return
}
case "Prefix":
z.Prefix, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Prefix")
return
}
case "FilterPrefix":
z.FilterPrefix, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "FilterPrefix")
return
}
case "Marker":
z.Marker, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Marker")
return
}
case "Limit":
z.Limit, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Limit")
return
}
case "AskDisks":
z.AskDisks, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "AskDisks")
return
}
case "InclDeleted":
z.InclDeleted, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "InclDeleted")
return
}
case "Recursive":
z.Recursive, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "Recursive")
return
}
case "Separator":
z.Separator, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Separator")
return
}
case "Create":
z.Create, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "Create")
return
}
case "IncludeDirectories":
z.IncludeDirectories, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "IncludeDirectories")
return
}
case "Transient":
z.Transient, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "Transient")
return
}
case "Versioned":
z.Versioned, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "Versioned")
return
}
case "StopDiskAtLimit":
z.StopDiskAtLimit, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "StopDiskAtLimit")
return
}
case "pool":
z.pool, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "pool")
return
}
case "set":
z.set, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "set")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *listPathOptions) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 18
// write "ID"
err = en.Append(0xde, 0x0, 0x12, 0xa2, 0x49, 0x44)
if err != nil {
return
}
err = en.WriteString(z.ID)
if err != nil {
err = msgp.WrapError(err, "ID")
return
}
// write "Bucket"
err = en.Append(0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
if err != nil {
return
}
err = en.WriteString(z.Bucket)
if err != nil {
err = msgp.WrapError(err, "Bucket")
return
}
// write "BaseDir"
err = en.Append(0xa7, 0x42, 0x61, 0x73, 0x65, 0x44, 0x69, 0x72)
if err != nil {
return
}
err = en.WriteString(z.BaseDir)
if err != nil {
err = msgp.WrapError(err, "BaseDir")
return
}
// write "Prefix"
err = en.Append(0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
if err != nil {
return
}
err = en.WriteString(z.Prefix)
if err != nil {
err = msgp.WrapError(err, "Prefix")
return
}
// write "FilterPrefix"
err = en.Append(0xac, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
if err != nil {
return
}
err = en.WriteString(z.FilterPrefix)
if err != nil {
err = msgp.WrapError(err, "FilterPrefix")
return
}
// write "Marker"
err = en.Append(0xa6, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x72)
if err != nil {
return
}
err = en.WriteString(z.Marker)
if err != nil {
err = msgp.WrapError(err, "Marker")
return
}
// write "Limit"
err = en.Append(0xa5, 0x4c, 0x69, 0x6d, 0x69, 0x74)
if err != nil {
return
}
err = en.WriteInt(z.Limit)
if err != nil {
err = msgp.WrapError(err, "Limit")
return
}
// write "AskDisks"
err = en.Append(0xa8, 0x41, 0x73, 0x6b, 0x44, 0x69, 0x73, 0x6b, 0x73)
if err != nil {
return
}
err = en.WriteString(z.AskDisks)
if err != nil {
err = msgp.WrapError(err, "AskDisks")
return
}
// write "InclDeleted"
err = en.Append(0xab, 0x49, 0x6e, 0x63, 0x6c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64)
if err != nil {
return
}
err = en.WriteBool(z.InclDeleted)
if err != nil {
err = msgp.WrapError(err, "InclDeleted")
return
}
// write "Recursive"
err = en.Append(0xa9, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65)
if err != nil {
return
}
err = en.WriteBool(z.Recursive)
if err != nil {
err = msgp.WrapError(err, "Recursive")
return
}
// write "Separator"
err = en.Append(0xa9, 0x53, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72)
if err != nil {
return
}
err = en.WriteString(z.Separator)
if err != nil {
err = msgp.WrapError(err, "Separator")
return
}
// write "Create"
err = en.Append(0xa6, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65)
if err != nil {
return
}
err = en.WriteBool(z.Create)
if err != nil {
err = msgp.WrapError(err, "Create")
return
}
// write "IncludeDirectories"
err = en.Append(0xb2, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73)
if err != nil {
return
}
err = en.WriteBool(z.IncludeDirectories)
if err != nil {
err = msgp.WrapError(err, "IncludeDirectories")
return
}
// write "Transient"
err = en.Append(0xa9, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x65, 0x6e, 0x74)
if err != nil {
return
}
err = en.WriteBool(z.Transient)
if err != nil {
err = msgp.WrapError(err, "Transient")
return
}
// write "Versioned"
err = en.Append(0xa9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x64)
if err != nil {
return
}
err = en.WriteBool(z.Versioned)
if err != nil {
err = msgp.WrapError(err, "Versioned")
return
}
// write "StopDiskAtLimit"
err = en.Append(0xaf, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x69, 0x73, 0x6b, 0x41, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74)
if err != nil {
return
}
err = en.WriteBool(z.StopDiskAtLimit)
if err != nil {
err = msgp.WrapError(err, "StopDiskAtLimit")
return
}
// write "pool"
err = en.Append(0xa4, 0x70, 0x6f, 0x6f, 0x6c)
if err != nil {
return
}
err = en.WriteInt(z.pool)
if err != nil {
err = msgp.WrapError(err, "pool")
return
}
// write "set"
err = en.Append(0xa3, 0x73, 0x65, 0x74)
if err != nil {
return
}
err = en.WriteInt(z.set)
if err != nil {
err = msgp.WrapError(err, "set")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *listPathOptions) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 18
// string "ID"
o = append(o, 0xde, 0x0, 0x12, 0xa2, 0x49, 0x44)
o = msgp.AppendString(o, z.ID)
// string "Bucket"
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
o = msgp.AppendString(o, z.Bucket)
// string "BaseDir"
o = append(o, 0xa7, 0x42, 0x61, 0x73, 0x65, 0x44, 0x69, 0x72)
o = msgp.AppendString(o, z.BaseDir)
// string "Prefix"
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
o = msgp.AppendString(o, z.Prefix)
// string "FilterPrefix"
o = append(o, 0xac, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
o = msgp.AppendString(o, z.FilterPrefix)
// string "Marker"
o = append(o, 0xa6, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x72)
o = msgp.AppendString(o, z.Marker)
// string "Limit"
o = append(o, 0xa5, 0x4c, 0x69, 0x6d, 0x69, 0x74)
o = msgp.AppendInt(o, z.Limit)
// string "AskDisks"
o = append(o, 0xa8, 0x41, 0x73, 0x6b, 0x44, 0x69, 0x73, 0x6b, 0x73)
o = msgp.AppendString(o, z.AskDisks)
// string "InclDeleted"
o = append(o, 0xab, 0x49, 0x6e, 0x63, 0x6c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64)
o = msgp.AppendBool(o, z.InclDeleted)
// string "Recursive"
o = append(o, 0xa9, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65)
o = msgp.AppendBool(o, z.Recursive)
// string "Separator"
o = append(o, 0xa9, 0x53, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72)
o = msgp.AppendString(o, z.Separator)
// string "Create"
o = append(o, 0xa6, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65)
o = msgp.AppendBool(o, z.Create)
// string "IncludeDirectories"
o = append(o, 0xb2, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73)
o = msgp.AppendBool(o, z.IncludeDirectories)
// string "Transient"
o = append(o, 0xa9, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x65, 0x6e, 0x74)
o = msgp.AppendBool(o, z.Transient)
// string "Versioned"
o = append(o, 0xa9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x64)
o = msgp.AppendBool(o, z.Versioned)
// string "StopDiskAtLimit"
o = append(o, 0xaf, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x69, 0x73, 0x6b, 0x41, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74)
o = msgp.AppendBool(o, z.StopDiskAtLimit)
// string "pool"
o = append(o, 0xa4, 0x70, 0x6f, 0x6f, 0x6c)
o = msgp.AppendInt(o, z.pool)
// string "set"
o = append(o, 0xa3, 0x73, 0x65, 0x74)
o = msgp.AppendInt(o, z.set)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *listPathOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "ID":
z.ID, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "ID")
return
}
case "Bucket":
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Bucket")
return
}
case "BaseDir":
z.BaseDir, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "BaseDir")
return
}
case "Prefix":
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Prefix")
return
}
case "FilterPrefix":
z.FilterPrefix, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "FilterPrefix")
return
}
case "Marker":
z.Marker, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Marker")
return
}
case "Limit":
z.Limit, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Limit")
return
}
case "AskDisks":
z.AskDisks, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AskDisks")
return
}
case "InclDeleted":
z.InclDeleted, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "InclDeleted")
return
}
case "Recursive":
z.Recursive, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Recursive")
return
}
case "Separator":
z.Separator, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Separator")
return
}
case "Create":
z.Create, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Create")
return
}
case "IncludeDirectories":
z.IncludeDirectories, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "IncludeDirectories")
return
}
case "Transient":
z.Transient, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Transient")
return
}
case "Versioned":
z.Versioned, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Versioned")
return
}
case "StopDiskAtLimit":
z.StopDiskAtLimit, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "StopDiskAtLimit")
return
}
case "pool":
z.pool, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "pool")
return
}
case "set":
z.set, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "set")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *listPathOptions) Msgsize() (s int) {
s = 3 + 3 + msgp.StringPrefixSize + len(z.ID) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 8 + msgp.StringPrefixSize + len(z.BaseDir) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 13 + msgp.StringPrefixSize + len(z.FilterPrefix) + 7 + msgp.StringPrefixSize + len(z.Marker) + 6 + msgp.IntSize + 9 + msgp.StringPrefixSize + len(z.AskDisks) + 12 + msgp.BoolSize + 10 + msgp.BoolSize + 10 + msgp.StringPrefixSize + len(z.Separator) + 7 + msgp.BoolSize + 19 + msgp.BoolSize + 10 + msgp.BoolSize + 10 + msgp.BoolSize + 16 + msgp.BoolSize + 5 + msgp.IntSize + 4 + msgp.IntSize
return
}
+123
View File
@@ -0,0 +1,123 @@
package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"bytes"
"testing"
"github.com/tinylib/msgp/msgp"
)
func TestMarshalUnmarshallistPathOptions(t *testing.T) {
v := listPathOptions{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsglistPathOptions(b *testing.B) {
v := listPathOptions{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsglistPathOptions(b *testing.B) {
v := listPathOptions{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshallistPathOptions(b *testing.B) {
v := listPathOptions{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodelistPathOptions(t *testing.T) {
v := listPathOptions{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodelistPathOptions Msgsize() is inaccurate")
}
vn := listPathOptions{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodelistPathOptions(b *testing.B) {
v := listPathOptions{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodelistPathOptions(b *testing.B) {
v := listPathOptions{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
+12 -9
View File
@@ -73,12 +73,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if !skipAccessChecks(opts.Bucket) {
// Stat a volume entry.
if err = Access(volumeDir); err != nil {
if osIsNotExist(err) {
return errVolumeNotFound
} else if isSysErrIO(err) {
return errFaultyDisk
}
return err
return convertAccessError(err, errVolumeAccessDenied)
}
}
@@ -169,7 +164,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if s.walkMu != nil {
s.walkMu.Lock()
}
entries, err := s.ListDir(ctx, opts.Bucket, current, -1)
entries, err := s.ListDir(ctx, "", opts.Bucket, current, -1)
if s.walkMu != nil {
s.walkMu.Unlock()
}
@@ -304,7 +299,11 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
// If directory entry on stack before this, pop it now.
for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name {
pop := dirStack[len(dirStack)-1]
out <- metaCacheEntry{name: pop}
select {
case <-ctx.Done():
return ctx.Err()
case out <- metaCacheEntry{name: pop}:
}
if opts.Recursive {
// Scan folder we found. Should be in correct sort order where we are.
err := scanDir(pop)
@@ -373,7 +372,11 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
return ctx.Err()
}
pop := dirStack[len(dirStack)-1]
out <- metaCacheEntry{name: pop}
select {
case <-ctx.Done():
return ctx.Err()
case out <- metaCacheEntry{name: pop}:
}
if opts.Recursive {
// Scan folder we found. Should be in correct sort order where we are.
logger.LogIf(ctx, scanDir(pop))
+76 -32
View File
@@ -83,6 +83,11 @@ var (
// resourceMetricsHelpMap maps metric name to its help string
resourceMetricsHelpMap map[MetricName]string
resourceMetricsGroups []*MetricsGroup
// initial values for drives (at the time of server startup)
// used for calculating avg values for drive metrics
initialDriveStats map[string]madmin.DiskIOStats
initialDriveStatsMu sync.RWMutex
initialUptime uint64
)
// PeerResourceMetrics represents the resource metrics
@@ -214,50 +219,67 @@ func updateResourceMetrics(subSys MetricSubsystem, name MetricName, val float64,
resourceMetricsMap[subSys] = subsysMetrics
}
func collectDriveMetrics(m madmin.RealtimeMetrics) {
upt, _ := host.Uptime()
kib := 1 << 10
// updateDriveIOStats - Updates the drive IO stats by calculating the difference between the current
// and initial values. We cannot rely on host.Uptime here as it will not work in k8s environments, where
// it will return the pod's uptime but the disk metrics are always from the host (/proc/diskstats)
func updateDriveIOStats(currentStats madmin.DiskIOStats, initialStats madmin.DiskIOStats, labels map[string]string) {
sectorSize := uint64(512)
kib := float64(1 << 10)
uptime, _ := host.Uptime()
uptimeDiff := float64(uptime - initialUptime)
if uptimeDiff == 0 {
// too soon to update the stats
return
}
diffStats := madmin.DiskIOStats{
ReadIOs: currentStats.ReadIOs - initialStats.ReadIOs,
WriteIOs: currentStats.WriteIOs - initialStats.WriteIOs,
ReadTicks: currentStats.ReadTicks - initialStats.ReadTicks,
WriteTicks: currentStats.WriteTicks - initialStats.WriteTicks,
TotalTicks: currentStats.TotalTicks - initialStats.TotalTicks,
ReadSectors: currentStats.ReadSectors - initialStats.ReadSectors,
WriteSectors: currentStats.WriteSectors - initialStats.WriteSectors,
}
updateResourceMetrics(driveSubsystem, readsPerSec, float64(diffStats.ReadIOs)/uptimeDiff, labels, false)
readKib := float64(diffStats.ReadSectors*sectorSize) / kib
updateResourceMetrics(driveSubsystem, readsKBPerSec, readKib/uptimeDiff, labels, false)
updateResourceMetrics(driveSubsystem, writesPerSec, float64(diffStats.WriteIOs)/uptimeDiff, labels, false)
writeKib := float64(diffStats.WriteSectors*sectorSize) / kib
updateResourceMetrics(driveSubsystem, writesKBPerSec, writeKib/uptimeDiff, labels, false)
rdAwait := 0.0
if diffStats.ReadIOs > 0 {
rdAwait = float64(diffStats.ReadTicks) / float64(diffStats.ReadIOs)
}
updateResourceMetrics(driveSubsystem, readsAwait, rdAwait, labels, false)
wrAwait := 0.0
if diffStats.WriteIOs > 0 {
wrAwait = float64(diffStats.WriteTicks) / float64(diffStats.WriteIOs)
}
updateResourceMetrics(driveSubsystem, writesAwait, wrAwait, labels, false)
updateResourceMetrics(driveSubsystem, percUtil, float64(diffStats.TotalTicks)/(uptimeDiff*10), labels, false)
}
func collectDriveMetrics(m madmin.RealtimeMetrics) {
for d, dm := range m.ByDisk {
stats := dm.IOStats
labels := map[string]string{"drive": d}
updateResourceMetrics(driveSubsystem, readsPerSec, float64(stats.ReadIOs)/float64(upt), labels, false)
readBytes := stats.ReadSectors * sectorSize
readKib := float64(readBytes) / float64(kib)
readKibPerSec := readKib / float64(upt)
updateResourceMetrics(driveSubsystem, readsKBPerSec, readKibPerSec, labels, false)
updateResourceMetrics(driveSubsystem, writesPerSec, float64(stats.WriteIOs)/float64(upt), labels, false)
writeBytes := stats.WriteSectors * sectorSize
writeKib := float64(writeBytes) / float64(kib)
writeKibPerSec := writeKib / float64(upt)
updateResourceMetrics(driveSubsystem, writesKBPerSec, writeKibPerSec, labels, false)
rdAwait := 0.0
if stats.ReadIOs > 0 {
rdAwait = float64(stats.ReadTicks) / float64(stats.ReadIOs)
initialStats, ok := initialDriveStats[d]
if !ok {
continue
}
updateResourceMetrics(driveSubsystem, readsAwait, rdAwait, labels, false)
wrAwait := 0.0
if stats.WriteIOs > 0 {
wrAwait = float64(stats.WriteTicks) / float64(stats.WriteIOs)
}
updateResourceMetrics(driveSubsystem, writesAwait, wrAwait, labels, false)
updateResourceMetrics(driveSubsystem, percUtil, float64(stats.TotalTicks)/float64(upt*10), labels, false)
updateDriveIOStats(dm.IOStats, initialStats, labels)
}
globalLocalDrivesMu.RLock()
localDrives := globalLocalDrives
localDrives := cloneDrives(globalLocalDrives)
globalLocalDrivesMu.RUnlock()
for _, d := range localDrives {
labels := map[string]string{"drive": d.Endpoint().RawPath}
di, err := d.DiskInfo(GlobalContext, DiskInfoOptions{})
labels := map[string]string{"drive": di.Endpoint}
if err == nil {
updateResourceMetrics(driveSubsystem, usedBytes, float64(di.Used), labels, false)
updateResourceMetrics(driveSubsystem, totalBytes, float64(di.Total), labels, false)
@@ -339,8 +361,30 @@ func collectLocalResourceMetrics() {
collectDriveMetrics(m)
}
// populateInitialValues - populates the initial values
// for drive stats and host uptime
func populateInitialValues() {
initialDriveStatsMu.Lock()
m := collectLocalMetrics(madmin.MetricsDisk, collectMetricsOpts{
hosts: map[string]struct{}{
globalLocalNodeName: {},
},
})
initialDriveStats = map[string]madmin.DiskIOStats{}
for d, dm := range m.ByDisk {
initialDriveStats[d] = dm.IOStats
}
initialUptime, _ = host.Uptime()
initialDriveStatsMu.Unlock()
}
// startResourceMetricsCollection - starts the job for collecting resource metrics
func startResourceMetricsCollection() {
populateInitialValues()
resourceMetricsMapMu.Lock()
resourceMetricsMap = map[MetricSubsystem]ResourceMetrics{}
resourceMetricsMapMu.Unlock()
+232 -65
View File
@@ -32,6 +32,7 @@ import (
"github.com/minio/kes-go"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/cachevalue"
xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext"
@@ -42,6 +43,8 @@ import (
"github.com/prometheus/procfs"
)
//go:generate msgp -file=$GOFILE -unexported -io=false
var (
nodeCollector *minioNodeCollector
clusterCollector *minioClusterCollector
@@ -225,10 +228,20 @@ const (
linkOfflineDuration MetricName = "link_offline_duration_seconds"
linkDowntimeTotalDuration MetricName = "link_downtime_duration_seconds"
avgInQueueCount MetricName = "average_queued_count"
avgInQueueBytes MetricName = "average_queued_bytes"
maxInQueueCount MetricName = "max_queued_count"
maxInQueueBytes MetricName = "max_queued_bytes"
avgInQueueCount MetricName = "average_queued_count"
avgInQueueBytes MetricName = "average_queued_bytes"
maxInQueueCount MetricName = "max_queued_count"
maxInQueueBytes MetricName = "max_queued_bytes"
proxiedGetRequestsTotal MetricName = "proxied_get_requests_total"
proxiedHeadRequestsTotal MetricName = "proxied_head_requests_total"
proxiedPutTaggingRequestsTotal MetricName = "proxied_put_tagging_requests_total"
proxiedGetTaggingRequestsTotal MetricName = "proxied_get_tagging_requests_total"
proxiedDeleteTaggingRequestsTotal MetricName = "proxied_delete_tagging_requests_total"
proxiedGetRequestsFailures MetricName = "proxied_get_requests_failures"
proxiedHeadRequestsFailures MetricName = "proxied_head_requests_failures"
proxiedPutTaggingRequestFailures MetricName = "proxied_put_tagging_requests_failures"
proxiedGetTaggingRequestFailures MetricName = "proxied_get_tagging_requests_failures"
proxiedDeleteTaggingRequestFailures MetricName = "proxied_delete_tagging_requests_failures"
freeBytes MetricName = "free_bytes"
readBytes MetricName = "read_bytes"
@@ -318,7 +331,7 @@ type Metric struct {
// MetricsGroup are a group of metrics that are initialized together.
type MetricsGroup struct {
metricsCache timedValue
metricsCache *cachevalue.Cache[[]Metric] `msg:"-"`
cacheInterval time.Duration
metricsGroupOpts MetricsGroupOpts
}
@@ -342,10 +355,11 @@ type MetricsGroupOpts struct {
// RegisterRead register the metrics populator function to be used
// to populate new values upon cache invalidation.
func (g *MetricsGroup) RegisterRead(read func(ctx context.Context) []Metric) {
g.metricsCache = cachevalue.New[[]Metric]()
g.metricsCache.Once.Do(func() {
g.metricsCache.Relax = true
g.metricsCache.TTL = g.cacheInterval
g.metricsCache.Update = func() (interface{}, error) {
g.metricsCache.Update = func() ([]Metric, error) {
if g.metricsGroupOpts.dependGlobalObjectAPI {
objLayer := newObjectLayerFn()
// Service not initialized yet
@@ -408,14 +422,14 @@ func (g *MetricsGroup) RegisterRead(read func(ctx context.Context) []Metric) {
})
}
func (m *Metric) copyMetric() Metric {
func (m *Metric) clone() Metric {
metric := Metric{
Description: m.Description,
Value: m.Value,
HistogramBucketLabel: m.HistogramBucketLabel,
StaticLabels: make(map[string]string),
VariableLabels: make(map[string]string),
Histogram: make(map[string]uint64),
StaticLabels: make(map[string]string, len(m.StaticLabels)),
VariableLabels: make(map[string]string, len(m.VariableLabels)),
Histogram: make(map[string]uint64, len(m.Histogram)),
}
for k, v := range m.StaticLabels {
metric.StaticLabels[k] = v
@@ -433,15 +447,14 @@ func (m *Metric) copyMetric() Metric {
// once the TTL expires "read()" registered function is called
// to return the new values and updated.
func (g *MetricsGroup) Get() (metrics []Metric) {
c, _ := g.metricsCache.Get()
m, ok := c.([]Metric)
if !ok {
m, _ := g.metricsCache.Get()
if len(m) == 0 {
return []Metric{}
}
metrics = make([]Metric, 0, len(m))
for i := range m {
metrics = append(metrics, m[i].copyMetric())
metrics = append(metrics, m[i].clone())
}
return metrics
}
@@ -526,7 +539,7 @@ func getNodeDriveTimeoutErrorsMD() MetricDescription {
}
}
func getNodeDriveAvailablityErrorsMD() MetricDescription {
func getNodeDriveAvailabilityErrorsMD() MetricDescription {
return MetricDescription{
Namespace: nodeMetricNamespace,
Subsystem: driveSubsystem,
@@ -546,16 +559,6 @@ func getNodeDriveWaitingIOMD() MetricDescription {
}
}
func getNodeDriveTokensIOMD() MetricDescription {
return MetricDescription{
Namespace: nodeMetricNamespace,
Subsystem: driveSubsystem,
Name: "io_tokens",
Help: "Total number concurrent I/O operations configured on drive",
Type: counterMetric,
}
}
func getNodeDriveFreeBytesMD() MetricDescription {
return MetricDescription{
Namespace: nodeMetricNamespace,
@@ -1124,6 +1127,106 @@ func getClusterReplMaxTransferRateMD() MetricDescription {
}
}
func getClusterReplProxiedGetOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedGetRequestsTotal,
Help: "Number of GET requests proxied to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedHeadOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedHeadRequestsTotal,
Help: "Number of HEAD requests proxied to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedPutTaggingOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedPutTaggingRequestsTotal,
Help: "Number of PUT tagging requests proxied to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedGetTaggingOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedGetTaggingRequestsTotal,
Help: "Number of GET tagging requests proxied to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedRmvTaggingOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedDeleteTaggingRequestsTotal,
Help: "Number of DELETE tagging requests proxied to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedGetFailedOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedGetRequestsFailures,
Help: "Number of failures in GET requests proxied to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedHeadFailedOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedHeadRequestsFailures,
Help: "Number of failures in HEAD requests proxied to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedPutTaggingFailedOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedPutTaggingRequestFailures,
Help: "Number of failures in PUT tagging proxy requests to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedGetTaggingFailedOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedGetTaggingRequestFailures,
Help: "Number of failures in GET tagging proxy requests to replication target",
Type: counterMetric,
}
}
func getClusterReplProxiedRmvTaggingFailedOperationsMD(ns MetricNamespace) MetricDescription {
return MetricDescription{
Namespace: ns,
Subsystem: replicationSubsystem,
Name: proxiedDeleteTaggingRequestFailures,
Help: "Number of failures in DELETE tagging proxy requests to replication target",
Type: counterMetric,
}
}
func getBucketObjectDistributionMD() MetricDescription {
return MetricDescription{
Namespace: bucketMetricNamespace,
@@ -2154,58 +2257,45 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
if globalReplicationStats != nil {
qs := globalReplicationStats.getNodeQueueStatsSummary()
activeWorkersCount := Metric{
Description: getClusterReplActiveWorkersCountMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplActiveWorkersCountMD(),
}
avgActiveWorkersCount := Metric{
Description: getClusterReplAvgActiveWorkersCountMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplAvgActiveWorkersCountMD(),
}
maxActiveWorkersCount := Metric{
Description: getClusterReplMaxActiveWorkersCountMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplMaxActiveWorkersCountMD(),
}
currInQueueCount := Metric{
Description: getClusterReplCurrQueuedOperationsMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplCurrQueuedOperationsMD(),
}
currInQueueBytes := Metric{
Description: getClusterReplCurrQueuedBytesMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplCurrQueuedBytesMD(),
}
currTransferRate := Metric{
Description: getClusterReplCurrentTransferRateMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplCurrentTransferRateMD(),
}
avgQueueCount := Metric{
Description: getClusterReplAvgQueuedOperationsMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplAvgQueuedOperationsMD(),
}
avgQueueBytes := Metric{
Description: getClusterReplAvgQueuedBytesMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplAvgQueuedBytesMD(),
}
maxQueueCount := Metric{
Description: getClusterReplMaxQueuedOperationsMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplMaxQueuedOperationsMD(),
}
maxQueueBytes := Metric{
Description: getClusterReplMaxQueuedBytesMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplMaxQueuedBytesMD(),
}
avgTransferRate := Metric{
Description: getClusterReplAvgTransferRateMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplAvgTransferRateMD(),
}
maxTransferRate := Metric{
Description: getClusterReplMaxTransferRateMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Description: getClusterReplMaxTransferRateMD(),
}
mrfCount := Metric{
Description: getClusterReplMRFFailedOperationsMD(),
VariableLabels: map[string]string{serverName: qs.NodeName},
Value: float64(qs.MRFStats.LastFailedCount),
Description: getClusterReplMRFFailedOperationsMD(),
Value: float64(qs.MRFStats.LastFailedCount),
}
if qs.QStats.Avg.Count > 0 || qs.QStats.Curr.Count > 0 {
@@ -2249,7 +2339,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
Description: getClusterRepLinkLatencyCurrMD(),
VariableLabels: map[string]string{
"endpoint": ep,
serverName: globalLocalNodeName,
},
}
m.Value = float64(health.latency.curr / time.Millisecond)
@@ -2260,7 +2349,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
Description: getClusterRepLinkLatencyAvgMD(),
VariableLabels: map[string]string{
"endpoint": ep,
serverName: globalLocalNodeName,
},
}
m.Value = float64(health.latency.avg / time.Millisecond)
@@ -2271,7 +2359,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
Description: getClusterRepLinkLatencyMaxMD(),
VariableLabels: map[string]string{
"endpoint": ep,
serverName: globalLocalNodeName,
},
}
m.Value = float64(health.latency.peak / time.Millisecond)
@@ -2281,7 +2368,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
Description: getClusterRepLinkOnlineMD(),
VariableLabels: map[string]string{
"endpoint": ep,
serverName: globalLocalNodeName,
},
}
online := Offline
@@ -2294,7 +2380,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
Description: getClusterRepLinkCurrOfflineDurationMD(),
VariableLabels: map[string]string{
"endpoint": ep,
serverName: globalLocalNodeName,
},
}
currDowntime := time.Duration(0)
@@ -2308,7 +2393,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
Description: getClusterRepLinkTotalOfflineDurationMD(),
VariableLabels: map[string]string{
"endpoint": ep,
serverName: globalLocalNodeName,
},
}
dwntime := currDowntime
@@ -2400,6 +2484,46 @@ func getReplicationSiteMetrics(opts MetricsGroupOpts) *MetricsGroup {
})
}
}
ml = append(ml, Metric{
Description: getClusterReplProxiedGetOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.GetTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedHeadOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.HeadTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedPutTaggingOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.PutTagTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedGetTaggingOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.GetTagTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedRmvTaggingOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.RmvTagTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedGetFailedOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.GetFailedTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedHeadFailedOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.HeadFailedTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedPutTaggingFailedOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.PutTagFailedTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedGetTaggingFailedOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.GetTagFailedTotal),
})
ml = append(ml, Metric{
Description: getClusterReplProxiedRmvTaggingFailedOperationsMD(clusterMetricNamespace),
Value: float64(m.Proxied.RmvTagFailedTotal),
})
}
return ml
@@ -3117,6 +3241,51 @@ func getBucketUsageMetrics(opts MetricsGroupOpts) *MetricsGroup {
Value: float64(stats.ReplicaCount),
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedGetOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.GetTotal),
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedHeadOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.HeadTotal),
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedPutTaggingOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.PutTagTotal),
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedGetTaggingOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.GetTagTotal),
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedRmvTaggingOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.RmvTagTotal),
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedGetFailedOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.GetFailedTotal),
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedHeadFailedOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.HeadFailedTotal),
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedPutTaggingFailedOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.PutTagFailedTotal),
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedGetTaggingFailedOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.GetTagFailedTotal),
})
metrics = append(metrics, Metric{
Description: getClusterReplProxiedRmvTaggingFailedOperationsMD(bucketMetricNamespace),
Value: float64(s.ProxyStats.RmvTagFailedTotal),
})
}
if stats.hasReplicationUsage() {
for arn, stat := range stats.Stats {
@@ -3298,7 +3467,7 @@ func getLocalStorageMetrics(opts MetricsGroupOpts) *MetricsGroup {
})
metrics = append(metrics, Metric{
Description: getNodeDriveAvailablityErrorsMD(),
Description: getNodeDriveAvailabilityErrorsMD(),
Value: float64(disk.Metrics.TotalErrorsAvailability),
VariableLabels: map[string]string{"drive": disk.DrivePath},
})
@@ -3309,12 +3478,6 @@ func getLocalStorageMetrics(opts MetricsGroupOpts) *MetricsGroup {
VariableLabels: map[string]string{"drive": disk.DrivePath},
})
metrics = append(metrics, Metric{
Description: getNodeDriveTokensIOMD(),
Value: float64(disk.Metrics.TotalTokens),
VariableLabels: map[string]string{"drive": disk.DrivePath},
})
for apiName, latency := range disk.Metrics.LastMinute {
metrics = append(metrics, Metric{
Description: getNodeDriveAPILatencyMD(),
@@ -3829,6 +3992,7 @@ func collectMetric(metric Metric, labels []string, values []string, metricName s
}
}
//msgp:ignore minioBucketCollector
type minioBucketCollector struct {
metricsGroups []*MetricsGroup
desc *prometheus.Desc
@@ -3864,6 +4028,7 @@ func (c *minioBucketCollector) Collect(out chan<- prometheus.Metric) {
wg.Wait()
}
//msgp:ignore minioClusterCollector
type minioClusterCollector struct {
metricsGroups []*MetricsGroup
desc *prometheus.Desc
@@ -3923,6 +4088,8 @@ func ReportMetrics(ctx context.Context, metricsGroups []*MetricsGroup) <-chan Me
}
// minioNodeCollector is the Custom Collector
//
//msgp:ignore minioNodeCollector
type minioNodeCollector struct {
metricsGroups []*MetricsGroup
desc *prometheus.Desc
+644
View File
@@ -0,0 +1,644 @@
package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// MarshalMsg implements msgp.Marshaler
func (z *Metric) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 6
// string "Description"
o = append(o, 0x86, 0xab, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e)
o, err = z.Description.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "Description")
return
}
// string "StaticLabels"
o = append(o, 0xac, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73)
o = msgp.AppendMapHeader(o, uint32(len(z.StaticLabels)))
for za0001, za0002 := range z.StaticLabels {
o = msgp.AppendString(o, za0001)
o = msgp.AppendString(o, za0002)
}
// string "Value"
o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
o = msgp.AppendFloat64(o, z.Value)
// string "VariableLabels"
o = append(o, 0xae, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73)
o = msgp.AppendMapHeader(o, uint32(len(z.VariableLabels)))
for za0003, za0004 := range z.VariableLabels {
o = msgp.AppendString(o, za0003)
o = msgp.AppendString(o, za0004)
}
// string "HistogramBucketLabel"
o = append(o, 0xb4, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x61, 0x62, 0x65, 0x6c)
o = msgp.AppendString(o, z.HistogramBucketLabel)
// string "Histogram"
o = append(o, 0xa9, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d)
o = msgp.AppendMapHeader(o, uint32(len(z.Histogram)))
for za0005, za0006 := range z.Histogram {
o = msgp.AppendString(o, za0005)
o = msgp.AppendUint64(o, za0006)
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *Metric) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "Description":
bts, err = z.Description.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "Description")
return
}
case "StaticLabels":
var zb0002 uint32
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "StaticLabels")
return
}
if z.StaticLabels == nil {
z.StaticLabels = make(map[string]string, zb0002)
} else if len(z.StaticLabels) > 0 {
for key := range z.StaticLabels {
delete(z.StaticLabels, key)
}
}
for zb0002 > 0 {
var za0001 string
var za0002 string
zb0002--
za0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "StaticLabels")
return
}
za0002, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "StaticLabels", za0001)
return
}
z.StaticLabels[za0001] = za0002
}
case "Value":
z.Value, bts, err = msgp.ReadFloat64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "Value")
return
}
case "VariableLabels":
var zb0003 uint32
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "VariableLabels")
return
}
if z.VariableLabels == nil {
z.VariableLabels = make(map[string]string, zb0003)
} else if len(z.VariableLabels) > 0 {
for key := range z.VariableLabels {
delete(z.VariableLabels, key)
}
}
for zb0003 > 0 {
var za0003 string
var za0004 string
zb0003--
za0003, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "VariableLabels")
return
}
za0004, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "VariableLabels", za0003)
return
}
z.VariableLabels[za0003] = za0004
}
case "HistogramBucketLabel":
z.HistogramBucketLabel, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "HistogramBucketLabel")
return
}
case "Histogram":
var zb0004 uint32
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Histogram")
return
}
if z.Histogram == nil {
z.Histogram = make(map[string]uint64, zb0004)
} else if len(z.Histogram) > 0 {
for key := range z.Histogram {
delete(z.Histogram, key)
}
}
for zb0004 > 0 {
var za0005 string
var za0006 uint64
zb0004--
za0005, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Histogram")
return
}
za0006, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "Histogram", za0005)
return
}
z.Histogram[za0005] = za0006
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *Metric) Msgsize() (s int) {
s = 1 + 12 + z.Description.Msgsize() + 13 + msgp.MapHeaderSize
if z.StaticLabels != nil {
for za0001, za0002 := range z.StaticLabels {
_ = za0002
s += msgp.StringPrefixSize + len(za0001) + msgp.StringPrefixSize + len(za0002)
}
}
s += 6 + msgp.Float64Size + 15 + msgp.MapHeaderSize
if z.VariableLabels != nil {
for za0003, za0004 := range z.VariableLabels {
_ = za0004
s += msgp.StringPrefixSize + len(za0003) + msgp.StringPrefixSize + len(za0004)
}
}
s += 21 + msgp.StringPrefixSize + len(z.HistogramBucketLabel) + 10 + msgp.MapHeaderSize
if z.Histogram != nil {
for za0005, za0006 := range z.Histogram {
_ = za0006
s += msgp.StringPrefixSize + len(za0005) + msgp.Uint64Size
}
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *MetricDescription) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 5
// string "Namespace"
o = append(o, 0x85, 0xa9, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65)
o = msgp.AppendString(o, string(z.Namespace))
// string "Subsystem"
o = append(o, 0xa9, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d)
o = msgp.AppendString(o, string(z.Subsystem))
// string "Name"
o = append(o, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
o = msgp.AppendString(o, string(z.Name))
// string "Help"
o = append(o, 0xa4, 0x48, 0x65, 0x6c, 0x70)
o = msgp.AppendString(o, z.Help)
// string "Type"
o = append(o, 0xa4, 0x54, 0x79, 0x70, 0x65)
o = msgp.AppendString(o, string(z.Type))
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *MetricDescription) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "Namespace":
{
var zb0002 string
zb0002, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Namespace")
return
}
z.Namespace = MetricNamespace(zb0002)
}
case "Subsystem":
{
var zb0003 string
zb0003, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Subsystem")
return
}
z.Subsystem = MetricSubsystem(zb0003)
}
case "Name":
{
var zb0004 string
zb0004, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Name")
return
}
z.Name = MetricName(zb0004)
}
case "Help":
z.Help, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Help")
return
}
case "Type":
{
var zb0005 string
zb0005, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Type")
return
}
z.Type = MetricType(zb0005)
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *MetricDescription) Msgsize() (s int) {
s = 1 + 10 + msgp.StringPrefixSize + len(string(z.Namespace)) + 10 + msgp.StringPrefixSize + len(string(z.Subsystem)) + 5 + msgp.StringPrefixSize + len(string(z.Name)) + 5 + msgp.StringPrefixSize + len(z.Help) + 5 + msgp.StringPrefixSize + len(string(z.Type))
return
}
// MarshalMsg implements msgp.Marshaler
func (z MetricName) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendString(o, string(z))
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *MetricName) UnmarshalMsg(bts []byte) (o []byte, err error) {
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
(*z) = MetricName(zb0001)
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z MetricName) Msgsize() (s int) {
s = msgp.StringPrefixSize + len(string(z))
return
}
// MarshalMsg implements msgp.Marshaler
func (z MetricNamespace) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendString(o, string(z))
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *MetricNamespace) UnmarshalMsg(bts []byte) (o []byte, err error) {
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
(*z) = MetricNamespace(zb0001)
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z MetricNamespace) Msgsize() (s int) {
s = msgp.StringPrefixSize + len(string(z))
return
}
// MarshalMsg implements msgp.Marshaler
func (z MetricSubsystem) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendString(o, string(z))
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *MetricSubsystem) UnmarshalMsg(bts []byte) (o []byte, err error) {
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
(*z) = MetricSubsystem(zb0001)
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z MetricSubsystem) Msgsize() (s int) {
s = msgp.StringPrefixSize + len(string(z))
return
}
// MarshalMsg implements msgp.Marshaler
func (z MetricType) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendString(o, string(z))
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *MetricType) UnmarshalMsg(bts []byte) (o []byte, err error) {
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
(*z) = MetricType(zb0001)
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z MetricType) Msgsize() (s int) {
s = msgp.StringPrefixSize + len(string(z))
return
}
// MarshalMsg implements msgp.Marshaler
func (z *MetricsGroup) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 2
// string "cacheInterval"
o = append(o, 0x82, 0xad, 0x63, 0x61, 0x63, 0x68, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c)
o = msgp.AppendDuration(o, z.cacheInterval)
// string "metricsGroupOpts"
o = append(o, 0xb0, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4f, 0x70, 0x74, 0x73)
o, err = z.metricsGroupOpts.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "metricsGroupOpts")
return
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *MetricsGroup) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "cacheInterval":
z.cacheInterval, bts, err = msgp.ReadDurationBytes(bts)
if err != nil {
err = msgp.WrapError(err, "cacheInterval")
return
}
case "metricsGroupOpts":
bts, err = z.metricsGroupOpts.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "metricsGroupOpts")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *MetricsGroup) Msgsize() (s int) {
s = 1 + 14 + msgp.DurationSize + 17 + z.metricsGroupOpts.Msgsize()
return
}
// MarshalMsg implements msgp.Marshaler
func (z *MetricsGroupOpts) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 12
// string "dependGlobalObjectAPI"
o = append(o, 0x8c, 0xb5, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x41, 0x50, 0x49)
o = msgp.AppendBool(o, z.dependGlobalObjectAPI)
// string "dependGlobalAuthNPlugin"
o = append(o, 0xb7, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x4e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e)
o = msgp.AppendBool(o, z.dependGlobalAuthNPlugin)
// string "dependGlobalSiteReplicationSys"
o = append(o, 0xbe, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x73)
o = msgp.AppendBool(o, z.dependGlobalSiteReplicationSys)
// string "dependGlobalNotificationSys"
o = append(o, 0xbb, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x79, 0x73)
o = msgp.AppendBool(o, z.dependGlobalNotificationSys)
// string "dependGlobalKMS"
o = append(o, 0xaf, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4b, 0x4d, 0x53)
o = msgp.AppendBool(o, z.dependGlobalKMS)
// string "bucketOnly"
o = append(o, 0xaa, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x79)
o = msgp.AppendBool(o, z.bucketOnly)
// string "dependGlobalLambdaTargetList"
o = append(o, 0xbc, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4c, 0x61, 0x6d, 0x62, 0x64, 0x61, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x69, 0x73, 0x74)
o = msgp.AppendBool(o, z.dependGlobalLambdaTargetList)
// string "dependGlobalIAMSys"
o = append(o, 0xb2, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x41, 0x4d, 0x53, 0x79, 0x73)
o = msgp.AppendBool(o, z.dependGlobalIAMSys)
// string "dependGlobalLockServer"
o = append(o, 0xb6, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4c, 0x6f, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72)
o = msgp.AppendBool(o, z.dependGlobalLockServer)
// string "dependGlobalIsDistErasure"
o = append(o, 0xb9, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x73, 0x44, 0x69, 0x73, 0x74, 0x45, 0x72, 0x61, 0x73, 0x75, 0x72, 0x65)
o = msgp.AppendBool(o, z.dependGlobalIsDistErasure)
// string "dependGlobalBackgroundHealState"
o = append(o, 0xbf, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65)
o = msgp.AppendBool(o, z.dependGlobalBackgroundHealState)
// string "dependBucketTargetSys"
o = append(o, 0xb5, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x79, 0x73)
o = msgp.AppendBool(o, z.dependBucketTargetSys)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *MetricsGroupOpts) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "dependGlobalObjectAPI":
z.dependGlobalObjectAPI, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalObjectAPI")
return
}
case "dependGlobalAuthNPlugin":
z.dependGlobalAuthNPlugin, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalAuthNPlugin")
return
}
case "dependGlobalSiteReplicationSys":
z.dependGlobalSiteReplicationSys, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalSiteReplicationSys")
return
}
case "dependGlobalNotificationSys":
z.dependGlobalNotificationSys, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalNotificationSys")
return
}
case "dependGlobalKMS":
z.dependGlobalKMS, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalKMS")
return
}
case "bucketOnly":
z.bucketOnly, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "bucketOnly")
return
}
case "dependGlobalLambdaTargetList":
z.dependGlobalLambdaTargetList, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalLambdaTargetList")
return
}
case "dependGlobalIAMSys":
z.dependGlobalIAMSys, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalIAMSys")
return
}
case "dependGlobalLockServer":
z.dependGlobalLockServer, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalLockServer")
return
}
case "dependGlobalIsDistErasure":
z.dependGlobalIsDistErasure, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalIsDistErasure")
return
}
case "dependGlobalBackgroundHealState":
z.dependGlobalBackgroundHealState, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependGlobalBackgroundHealState")
return
}
case "dependBucketTargetSys":
z.dependBucketTargetSys, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "dependBucketTargetSys")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *MetricsGroupOpts) Msgsize() (s int) {
s = 1 + 22 + msgp.BoolSize + 24 + msgp.BoolSize + 31 + msgp.BoolSize + 28 + msgp.BoolSize + 16 + msgp.BoolSize + 11 + msgp.BoolSize + 29 + msgp.BoolSize + 19 + msgp.BoolSize + 23 + msgp.BoolSize + 26 + msgp.BoolSize + 32 + msgp.BoolSize + 22 + msgp.BoolSize
return
}

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