Compare commits

..

379 Commits

Author SHA1 Message Date
Harshavardhana f678bcf7ba update dependencies to the latest releases (#16694) 2023-02-22 10:23:45 -08:00
Harshavardhana a0f06eac2a add Veeam SOS API first implementation (#16688) 2023-02-22 19:54:57 +05:30
jiuker 83fe1a2732 log: add more info about BROWSER_URL (#16678) 2023-02-22 19:54:05 +05:30
Harshavardhana 5c98223c89 add correct HostId instead of deploymentId for error responses (#16686) 2023-02-22 15:41:09 +05:30
jiuker 663a0b7783 save correct bucketInfo on it's indexes (#16685) 2023-02-22 14:08:34 +05:30
Anis Elleuch 6efe4d1df6 perf: Only remove generated data when no bucket name specified (#16610) 2023-02-21 21:21:40 -08:00
Daniel Valdivia fb17f97cf3 move audit and logger message structure to minio/pkg (#16655)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-21 21:21:17 -08:00
Shubhendu 6b65ba1551 Added attribute proxy for mc admin config set ALIAS logger_webhook (#16657)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-02-21 21:19:46 -08:00
Poorna 9202c6e26a Reload bucket targets when replication config is refreshed (#16684) 2023-02-21 21:18:49 -08:00
Poorna 59a5456091 fix: STS error translation to API error (#16683) 2023-02-22 09:57:48 +05:30
Allan Roger Reid 8bfe972bab Set meaningful message from minio with env variable KMS_SECRET_KEY (#16584) 2023-02-22 07:13:01 +05:30
Klaus Post fd6622458b Add detailed scanner trace output and notifications (#16668) 2023-02-21 09:33:33 -08:00
Poorna 8a08861dd9 fix: healing of replication config for endpoint changes (#16648) 2023-02-20 16:06:13 +05:30
Harshavardhana 82dcfd4e10 update dependencies to latest releases (#16651) 2023-02-20 16:05:20 +05:30
Harshavardhana b66d7dc708 add missing x-amz-id-2 to event notification date (#16646) 2023-02-20 15:41:47 +05:30
Harshavardhana eebdd2b31d update NOTICE to 2015-2023 copyright 2023-02-19 00:03:50 +05:30
Harshavardhana b94733ab31 avoid locks when unnecessary in SiteReplicationMetaInfo() (#16650) 2023-02-18 05:35:22 -08:00
Minio Trusted 7f2c90a0ed Update yaml files to latest version RELEASE.2023-02-17T17-52-43Z 2023-02-17 19:07:39 +00:00
Klaus Post 84bb7d05a9 fix: healing deadlocks and ordering (#16643) 2023-02-17 23:22:43 +05:30
Harshavardhana 98a84d88e2 fix: trim and ignore './' directories (#16642) 2023-02-17 07:15:03 -08:00
jiuker e470268c7c fix: a possible closer leak in SelectObjectHandler (#16598) 2023-02-17 01:44:40 -08:00
jiuker 3a6cd4f73d fix: sync.pool won't Put reader back (#16638) 2023-02-17 01:42:43 -08:00
Harshavardhana 6ea150fd68 fix: avoid printing certain errors under few locations (#16631) 2023-02-17 01:40:31 -08:00
Anis Elleuch a7188bc9d0 fix: evaluate BypassGov policy action in deletion correctly (#16635) 2023-02-17 07:53:34 +05:30
Harshavardhana e1e9ddd4a4 use kes.Status() for Status() call (#16629) 2023-02-16 22:12:24 +05:30
Krishnan Parthasarathi a1dd08f2e6 Include tier name in MinIO/S3 target user-agent (#16630) 2023-02-15 22:09:46 -08:00
Krishnan Parthasarathi d136ac0596 Don't close transition task channel on server exit (#16627) 2023-02-15 22:09:25 -08:00
Poorna c33a237067 fix: under site replication disallow remote target modification (#16628) 2023-02-15 20:22:13 -08:00
Poorna eb7d3da994 Fix site replication status reporting of quota (#16626) 2023-02-16 08:08:35 +05:30
Harshavardhana 37134e42d4 ignore io.EOF, io.ErrUnexpectedEOF on xl.meta reads in WalkDir() (#16625) 2023-02-15 07:12:48 -08:00
Klaus Post 626a4efaad Update reedsolomon to v1.11.7 (#16624) 2023-02-15 05:07:35 -08:00
Harshavardhana 0c1f8b4e0f add user-agent for all minio.Client usage (#16619) 2023-02-14 13:19:30 -08:00
Anis Elleuch 857674c3a0 heal: Do not mark buckets as done when there is no online disks (#16621) 2023-02-14 12:50:13 -08:00
Harshavardhana 15a75bd79b ignore preconditionFailed error in batch replication (#16615) 2023-02-14 07:22:08 -08:00
Andreas Auernhammer 74887c7372 kms: add support for KES API keys and switch to KES Go SDK (#16617)
Signed-off-by: Andreas Auernhammer <hi@aead.dev>
2023-02-14 07:19:20 -08:00
Harshavardhana 31188e9327 add parallel workers in batch replication (#16609) 2023-02-13 12:07:58 -08:00
Harshavardhana ee6d96eb46 Periodically remove stale buckets from in-memory (#16597) 2023-02-13 08:09:52 -08:00
Minio Trusted 11fe2fd79a update helm v5.0.7
Signed-off-by: Harshavardhana <harsha@minio.io>
2023-02-13 16:07:23 +05:30
atalakey4work c2863cc6ef helm: remove parentheses from podDisruptionBudget (#16605) 2023-02-13 02:36:06 -08:00
Anis Elleuch d6d01067a0 fix: allow global leader lock context merge to be canceled (#16603)
Global leader lock was first designated to only acquired once
until the node is killed. However, currently, the code acquires
it repeatedly during the lifetime of the server, now there is a
goroutine leak.
2023-02-13 01:26:38 -08:00
Minio Trusted 1d3b18c3f4 update helm v5.0.6
Signed-off-by: Harshavardhana <harsha@minio.io>
2023-02-13 12:23:06 +05:30
jiuker a15b6f21b8 remove incorrect use of WaitGroup (#16596) 2023-02-12 20:59:45 -08:00
Anis Elleuch 689179bf18 ServerInfo: return per erasure set information (#16583) 2023-02-11 18:31:56 +05:30
Minio Trusted bf749eec61 Update yaml files to latest version RELEASE.2023-02-10T18-48-39Z 2023-02-10 20:55:45 +00:00
Klaus Post d0f4cc89a5 Re-add Veeam Listing workaround (#16593) 2023-02-10 10:48:39 -08:00
Harshavardhana d65debb6bc fix: comply with RFC6750 UserInfo endpoint requirements (#16592) 2023-02-10 22:20:25 +05:30
Harshavardhana 72daccd468 fix: scanner in healing cycle must use actual size (#16589) 2023-02-10 06:53:03 -08:00
Harshavardhana b363400587 fix: username replacements for aws:username must use parentUser (#16591) 2023-02-10 06:52:31 -08:00
k0i 6b41f941b6 fix: README.md in docs/config (#16564) 2023-02-10 03:02:11 -08:00
jiuker b9f5f9ba3f fix: aclHandlers convert XML parse error to relevant client error (#16587) 2023-02-10 02:59:10 -08:00
Anis Elleuch c8ffa59d28 Periodically refresh buckets metadata from the backend disks (#16561)
fixes #16553
2023-02-09 10:29:20 -08:00
Minio Trusted 1141187bf2 Update yaml files to latest version RELEASE.2023-02-09T05-16-53Z 2023-02-09 06:21:34 +00:00
Poorna 52aeebebea Fix site replication meta info call to be non-blocking (#16526)
Co-authored-by: Harshavardhana <harsha@minio.io>
2023-02-08 21:16:53 -08:00
Aditya Manthramurthy e101384aa4 Update console to v0.23.1 (#16574) 2023-02-08 20:01:53 -08:00
Harshavardhana 71f02adfca Revert "Print golang http errors in MinIO log format (#16465)"
This reverts commit 1fd7946dce.
2023-02-09 09:27:27 +05:30
Krishnan Parthasarathi 9de26531e4 tiering: UpdateWorkers may be called before Init (#16573) 2023-02-08 19:13:34 -08:00
Anis Elleuch fadc46b906 Add the access key and parent user in the audit log (#16572) 2023-02-08 11:05:26 -08:00
Anis Elleuch b1d98febfd New disk healing goes through the healing workers (#16568) 2023-02-08 09:25:29 -08:00
jiuker 1828fb212a fix: avoid goroutine leak after timeouts in PeerMetrics (#16569) 2023-02-08 09:11:16 -08:00
Harshavardhana c97f50e274 replication syncs happen in 60sec intervals (#16571) 2023-02-08 08:23:26 -08:00
jiuker be92046dfd Anonymous pools in health info output are double counted (#16566) 2023-02-08 06:58:50 -08:00
Krishnan Parthasarathi 990fc415f7 Ensure safety of transitionState at startup (#16563) 2023-02-07 23:11:42 -08:00
Harshavardhana d8daabae9b make site replication healing safer (#16560) 2023-02-07 21:44:42 -08:00
Harshavardhana 84fe4fd156 fix: multiObjectDelete by passing versionId for authorization (#16562) 2023-02-08 08:01:00 +05:30
Harshavardhana 747d475e76 initialize subsystems that are not dependent on buckets first (#16559) 2023-02-07 12:46:47 -08:00
Anis Elleuch 095b518802 Show a better error msg when internal data encryption key is incorrect (#16549) 2023-02-07 05:22:54 -08:00
Harshavardhana 0319ae756a fix: pass proper username (simple) string as expected (#16555) 2023-02-07 03:43:08 -08:00
Harshavardhana 11c7ecb5cf support if-match/if-none-match with s3 uploads (#16551) 2023-02-06 18:58:29 -08:00
Cesar Celis Hernandez 422c396d73 Removing old action that is no longer needed (#16550) 2023-02-07 07:06:29 +05:30
Anis Elleuch ffd57fde90 Convert 'server closed idle connection' to errDiskNotFound (#16548) 2023-02-06 10:42:08 -08:00
jiuker a451d1cb8d fix: close resp.body checking for kubernetes version (#16547) 2023-02-06 10:41:41 -08:00
Harshavardhana 14cf8f1b22 upgrade deps for minio/pkg v1.6.1 to include groups conditions (#16538) 2023-02-06 09:27:29 -08:00
Harshavardhana 5996c8c4d5 feat: allow offline disks on a fresh start (#16541) 2023-02-06 09:26:09 -08:00
Harshavardhana 21885f9457 fix: liveness/readiness must return errors if KMS is unreachable (#16540) 2023-02-06 08:55:56 -08:00
Christian Niessner 6ac48aff46 helm: shared secrets handling for user and svcacct's (#16379) 2023-02-05 21:51:10 -08:00
Mathieu Parent 85ff76e7b0 fix(helm): use PodDisruptionBudget v1 for newer k8s versions (#16466) 2023-02-05 21:50:45 -08:00
Harshavardhana e47a31f9fc fix: object size distribution in metrics for all objects (#16539) 2023-02-04 21:10:10 -08:00
Harshavardhana 517fcd423d add necessary tools to our docker release (#16536) 2023-02-03 19:40:25 -08:00
Harshavardhana b780359598 helm version v5.0.5 2023-02-04 02:24:02 +05:30
Harshavardhana aa8b9572b9 remove double ENABLED help output (#16528) 2023-02-03 05:52:52 -08:00
Cesar Celis Hernandez 8ca14e6267 Updating enterprise action (#16518) 2023-02-02 19:23:31 +05:30
Poorna 876e1a91b2 replication: Fix typo checking PreconditionFailed status code (#16517) 2023-02-02 19:22:02 +05:30
Klaus Post 0b7989aa4b Fix Kafka initialization crash (#16523) 2023-02-02 19:21:19 +05:30
Anis Elleuch 2278fc8f47 Print original error when IAM load is failed in some places (#16511) 2023-02-01 17:32:22 +05:30
Harshavardhana a91f353621 support 'mc admin service restart' for windows (#16512) 2023-02-01 17:31:46 +05:30
Klaus Post cdb1b48ad9 Make localLocker lock attempts cancellable (#16510) 2023-01-31 09:41:17 -08:00
Minio Trusted a24037bfec Update yaml files to latest version RELEASE.2023-01-31T02-24-19Z 2023-01-31 07:49:15 +00:00
Kaan Kabalak 2d0f30f062 Fix typo in code comment (#16509) 2023-01-31 07:54:19 +05:30
Krishnan Parthasarathi cea2ca8c8e Add restore-status header for multipart objects (#16508) 2023-01-31 07:53:45 +05:30
Klaus Post f713436dd0 Fix truncated list response on deleted replicated objects (#16504) 2023-01-30 09:13:53 -08:00
Klaus Post b923a62425 Check pool-index for invalid setups (#16501) 2023-01-30 18:33:07 +05:30
Harshavardhana 67fce4a5b3 fix: dangling delete() upon success should return 404 (#16494) 2023-01-27 12:43:45 -08:00
Poorna eaa65b7ade fix replication healing on list to consider all versions (#16496) 2023-01-27 12:43:28 -08:00
Poorna 820d94447c replication: fix target bucket passed on GET proxy (#16495) 2023-01-27 10:24:51 -08:00
Poorna ed20134a7b replication: detect proxy header presence correctly (#16489) 2023-01-27 01:29:32 -08:00
Harshavardhana d19cbc81b5 fix: do not return IAM/Bucket metadata replication errors to client (#16486) 2023-01-26 11:11:54 -08:00
Anis Elleuch 1fd7946dce Print golang http errors in MinIO log format (#16465) 2023-01-26 22:46:16 +05:30
Klaus Post 027ff0f3a8 fix: set modTime to current in snowball if archive shows empty (#16482) 2023-01-26 22:20:35 +05:30
Jan Zhanal 8fa80874a6 doc: LDAP/AD - nested groups (#16483) 2023-01-26 22:17:59 +05:30
Harshavardhana 430669cfad do not fail groupadd if group already exists
fixes #16488
2023-01-26 21:32:02 +05:30
Harshavardhana 54b561898f fix: anonymize the x-amz-id-2 value from hostname (#16478) 2023-01-25 10:25:36 -08:00
Harshavardhana 65c104a589 add x-amz-id-2 to indicate the node that received the request (#16474) 2023-01-25 09:14:10 -08:00
Anis Elleuch 0a0416b6ea Better error when setting up replication with a service account alias (#16472) 2023-01-25 21:50:12 +05:30
Anis Elleuch 441babdc41 Rename peer S3 prefix to avoid collision in the future (#16473) 2023-01-25 06:46:30 -08:00
Minio Trusted 1bf1fafc86 Update yaml files to latest version RELEASE.2023-01-25T00-19-54Z 2023-01-25 02:09:27 +00:00
Aditya Manthramurthy 50d58e9b2d Bump up console to v0.23.0 (#16470) 2023-01-24 16:19:54 -08:00
Harshavardhana e64b9f6751 fix: disallow SSE-C encrypted objects on replicated buckets (#16467) 2023-01-24 15:46:33 -08:00
Florian Schwab d67a846ec4 allow restarting of decommissioning if completed, failed or canceld (#16464) 2023-01-24 07:07:59 -08:00
Poorna ca2a1c3f60 replication: clone metrics while loading metrics cache (#16462) 2023-01-24 02:10:32 -08:00
Poorna 93fbb228bf Validate if parent user exists for service acct (#16443) 2023-01-24 08:17:18 +05:30
Harshavardhana 3683673fb0 add missing gorilla/mux migration, update credits (#16461) 2023-01-23 08:46:37 -08:00
Anis Elleuch f37a5b6dae Add CPU info in the check update user-agent (#16447) 2023-01-23 08:07:55 -08:00
Harshavardhana 31b0decd46 migrate to minio/mux from gorilla/mux (#16456) 2023-01-23 16:42:47 +05:30
Harshavardhana eb561e1c05 allow bootstrap platform checks to be pool specific (#16455) 2023-01-23 16:24:50 +05:30
Mathieu Parent 54c9ecff5b fix(helm): fsGroup is only on podSecurityContext (#16402) 2023-01-21 10:03:03 -08:00
Wessel Valkenburg (prevue.ch) edcd72585d Update post-job.yaml in Helm chart (#16387) 2023-01-21 10:02:08 -08:00
Alex 1a17fc17bb GitHub Workflows security hardening (#15708) 2023-01-21 09:55:17 -08:00
Poorna ddad231921 replication: Avoid logging PreConditionFailed error (#16450) 2023-01-21 07:33:04 +05:30
Anis Elleuch e73894fa50 grafana: Show one metric for the total data growth (#16449) 2023-01-20 09:39:28 -08:00
Klaus Post 03b94f907f fix: deleted object names for directory objects (#16448) 2023-01-20 21:16:06 +05:30
Minio Trusted 3fa7218c44 Update yaml files to latest version RELEASE.2023-01-20T02-05-44Z 2023-01-20 08:11:58 +00:00
Shireesh Anjal 0f591d245d fix: incorrect anonymization of drive endpoint (#16442) 2023-01-20 07:35:44 +05:30
Poorna 1b02e046c2 Fix bandwidth monitoring to be per remote target (#16360) 2023-01-19 18:52:16 +05:30
Harshavardhana d08e3cc895 add a way to avoid blocking queueHealTask() depending on caller (#16433) 2023-01-19 18:50:54 +05:30
Anis Elleuch d98116559b Use async healing in PutObject call (#16431) 2023-01-19 00:54:22 -08:00
Krishnan Parthasarathi 71c95ad0d0 Signal stop-rebalance to all rebalancing pools (#16438) 2023-01-19 06:54:23 +05:30
Minio Trusted 5c1a4ba5f9 Update yaml files to latest version RELEASE.2023-01-18T04-36-38Z 2023-01-18 07:46:44 +00:00
Aditya Manthramurthy 698862ec5d Fix transports/timeouts related regressions (#16427) 2023-01-18 10:06:38 +05:30
Harshavardhana b4ef5ff294 remove unnecessary code checking for supported features (#16423) 2023-01-17 19:37:47 +05:30
Harshavardhana 3db658e51e use correct xml package for custom MarshalXML() (#16421) 2023-01-17 05:08:33 +05:30
Shireesh Anjal 5a9f7516d6 Add monthly license update job (#16391) 2023-01-17 05:08:15 +05:30
Anis Elleuch 3039fd4519 Optimize background heal status to use LocalStorageInfo (#16414) 2023-01-17 05:02:00 +05:30
Harshavardhana 095fc0561d feat: allow decom of multiple pools (#16416) 2023-01-16 21:36:34 +05:30
Anis Elleuch beb1924437 Properly restart fresh disk healing when failed in some places (#16413) 2023-01-14 05:06:46 +05:30
jiuker c8e1154f1e fix: reading from erasureDisks must be protected via read lock() (#16407) 2023-01-13 04:16:23 -08:00
Poorna b204c2dbec fix: enforce deny on DeleteVersionAction (#16409) 2023-01-13 04:16:00 -08:00
Poorna b22b39de96 Avoid dangling deletes if disk not found (#16401) 2023-01-12 22:20:19 -08:00
Harshavardhana c242e6c391 fix: calculate common parity properly (#16406) 2023-01-13 03:28:16 +05:30
Anis Elleuch e05205756f metrics: Add more logs when unable to read bucket usage (#16405) 2023-01-13 02:32:00 +05:30
Daniel Valdivia d03b244fcd Remove checks target from docker target (#16399)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-12 01:39:12 -08:00
Aditya Manthramurthy 5ef679d8f1 Simplify/speedup container image publishing script (#16403) 2023-01-12 01:35:47 -08:00
Minio Trusted d33c527e39 Update yaml files to latest version RELEASE.2023-01-12T02-06-16Z 2023-01-12 07:27:07 +00:00
Aditya Manthramurthy 7bc95c47a3 Update console to 0.22.5 (#16400) 2023-01-11 18:06:16 -08:00
Anis Elleuch 475a88b555 fix: error out if an object is found after a full decom (#16277) 2023-01-12 05:52:51 +05:30
Allan Roger Reid 9815dac48f fix: allow bind on ipv6 loopback failures (#16388) 2023-01-11 08:47:39 +05:30
Anis Elleuch 1ece3d1dfe Add comment field to service accounts (#16380) 2023-01-10 21:57:52 +04:00
Anis Elleuch 2146ed4033 xl: Quit early when EC config is incorrect (#16390)
Co-authored-by: Anis Elleuch <anis@min.io>
2023-01-09 23:07:45 -08:00
Minio Trusted 52b88b52f0 Update yaml files to latest version RELEASE.2023-01-06T18-11-18Z 2023-01-08 07:51:31 +00:00
Anis Elleuch ebd4388cca s3: Return XMinioInvalidObjectName if the object contains null char (#16372) 2023-01-06 10:11:18 -08:00
Harshavardhana 57fd02ee57 update console v0.22.4 (#16374)
Signed-off-by: Harshavardhana <harsha@minio.io>
2023-01-05 22:15:51 -08:00
Anis Elleuch 0333412148 fix: heal only once per disk per set among multiple disks (#16358) 2023-01-05 20:41:19 -08:00
Anis Elleuch 1c85652cff lint: Fix in darwin environment (#16368) 2023-01-05 10:12:01 -08:00
Harshavardhana e0086c1be7 reduce startup delays on kubernetes (#16356) 2023-01-05 02:32:43 -08:00
Poorna b29e159604 docs: Update replication setup commands (#16361) 2023-01-04 13:39:37 -08:00
Anis Elleuch 7883e55da2 Merge buckets list from different nodes in ListBuckets() call (#16357) 2023-01-04 08:53:58 -08:00
Harshavardhana b197623ed2 remove unnecessary kernel-tuning docs (#16354) 2023-01-04 01:33:40 -08:00
Harshavardhana a15a2556c3 converge listBuckets() as a peer call (#16346) 2023-01-03 23:39:40 -08:00
Harshavardhana 14d29b77ae update replication tests with latest 'mc' (#16348) 2023-01-03 22:54:39 -08:00
Harshavardhana a2514ffeed update klauspost/compress dependency (#16343) 2023-01-03 10:41:14 -08:00
Harshavardhana f1bbb7fef5 vectorize cluster-wide calls such as bucket operations (#16313) 2023-01-03 08:16:39 -08:00
Minio Trusted 72394a8319 Update yaml files to latest version RELEASE.2023-01-02T09-40-09Z 2023-01-03 10:16:34 +00:00
Harshavardhana 1cd8e1d8b6 remove the startup jitter before locks() (#16340) 2023-01-02 01:40:09 -08:00
jiuker 62cd918061 fix: close helmInfo file descriptor (#16319) 2023-01-01 23:26:59 -08:00
Klaus Post 6a04067514 fix: tweak read buffer size to reduce over-reading (#16338) 2023-01-01 08:14:20 -08:00
Taran Pelkey 49b3908635 fix: misplaced write response command in DetachPolicy() (#16333) 2022-12-30 20:04:03 -08:00
Harshavardhana 75faef888e disable builds for go1.18 (#16332) 2022-12-30 11:37:07 -08:00
Harshavardhana b67d97b1ba add missing fields in audit logs for non-compressed handlers (#16328) 2022-12-30 10:20:19 -08:00
Anis Elleuch b8943fdf19 doc: Update prometheus metrics list (#16329) 2022-12-29 15:08:22 -08:00
Harshavardhana f93183f66e fix: a deadlock by refactoring listBuckets() under site replication (#16323) 2022-12-29 00:08:31 -08:00
Harshavardhana 2937711390 fix: DeleteObject() API with versionId under replication (#16325) 2022-12-28 22:48:33 -08:00
Wojtek Czekalski aa56c6d51d helm: Make bucket existence check faster (#16321) 2022-12-27 10:32:39 -08:00
Anis Elleuch 27417459fb metrics: Show healing info for all nodes (#16315) 2022-12-26 08:35:32 -08:00
Harshavardhana 5b8fe2e89a allow locks with object affinity to spread across pools (#16312) 2022-12-23 20:55:45 -08:00
Anis Elleuch acc9c033ed debug: Add X-Amz-Request-ID to lock/unlock calls (#16309) 2022-12-23 19:49:07 -08:00
Poorna 8528b265a9 Validate replication target update to avoid duplicate endpoints (#16311) 2022-12-23 15:44:48 -08:00
Han Cen 44250f1a52 helm: disallow empty containers in post job template (#16281) 2022-12-23 12:32:18 -08:00
Minio Trusted f7560670d9 update helm v5.0.4
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-12-23 12:29:40 -08:00
Russell Sim 3891885800 helm: fix creating users, via proper secretKey (#16310) 2022-12-23 12:28:43 -08:00
Harshavardhana b882310e2b avoid locks for internal and invalid buckets in MakeBucket() (#16302) 2022-12-23 07:46:00 -08:00
Poorna de0b43de32 persist replication stats with leader lock (#16282) 2022-12-22 14:25:13 -08:00
Harshavardhana 48152a56ac upgrade UBI image to 8.7 (#16301) 2022-12-22 10:56:05 -08:00
jiuker 29dd7f1d68 tier verification leaks fd, that must be closed (#16296)
Co-authored-by: Harshavardhana <harsha@minio.io>
2022-12-22 10:35:54 -08:00
Poorna 6423e4c767 Remove site replication config if it succeeded locally (#16279) 2022-12-22 01:31:20 -08:00
Harshavardhana 1dd8f0e8f3 update console v0.22.3 (#16292)
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-12-21 23:47:51 -08:00
Krishnan Parthasarathi 2fa35def2c Fix DeleteObject when only free versions remain (#16289) 2022-12-21 16:24:07 -08:00
Anis Elleuch 34167c51d5 trace: Add bootstrap tracing events (#16286) 2022-12-21 15:52:29 -08:00
Harshavardhana a5f8af4efb serialize replication stats() only when needed (#16280) 2022-12-20 00:07:53 -08:00
Harshavardhana 5a218f38a1 allow retries for transaction lock on startup (#16273) 2022-12-19 22:00:00 -08:00
Anis Elleuch e57e946206 Do not save credentials in config.json (#16275) 2022-12-19 12:27:06 -08:00
Klaus Post b4f71362e9 Avoid config migration on every startup (#16278) 2022-12-19 11:10:14 -08:00
Taran Pelkey ed37b7a9d5 Add API to fetch policy user/group associations (#16239) 2022-12-19 10:37:03 -08:00
Minio Trusted 6511021fbe update helm v5.0.3 2022-12-19 00:53:02 -08:00
mruzicka 6197ba851b helm: Fix post job template (#16236) 2022-12-18 08:01:22 -08:00
Minio Trusted 3ae1f9d852 update helm v5.0.2 2022-12-17 23:57:10 -08:00
orblazer 0db1930f48 helm: add policy to svcacct (#16272) 2022-12-17 22:50:37 -08:00
Anis Elleuch 89db3fdb5d Do not return an error when version disparity is detected (#16269) 2022-12-16 08:52:12 -08:00
Harshavardhana 80fc3a8a52 use newDynamicTimeoutWithOpts() when appropriate (#16266) 2022-12-15 13:11:37 -08:00
Klaus Post 988a2e8fed Faster startup of large distributed systems with latency (#16259) 2022-12-15 08:31:21 -08:00
Harshavardhana 2433698372 fix: remove unnecessary logs for client conn errors (#16261) 2022-12-15 08:25:05 -08:00
Harshavardhana 5d7e8f79ed fix: remove scanner healing with unnecessary logs (#16260) 2022-12-14 16:39:18 -08:00
Harshavardhana bad229e16e fix: support event name s3:Restore:* (#16257) 2022-12-14 05:12:07 -08:00
Poorna d37e514733 Cleanup remote targets automatically on replication config removal. (#16221) 2022-12-14 03:24:06 -08:00
Harshavardhana c73ea27ed7 do not log checksum mismatch error, client received the error (#16246) 2022-12-14 01:57:40 -08:00
Krishnan Parthasarathi 0159b56717 fix: rebalance to account for object's on-disk size (#16240) 2022-12-14 00:15:14 -08:00
Aditya Manthramurthy 9e6cc847f8 Add HTTP2 config option for policy plugin (#16225) 2022-12-13 14:28:48 -08:00
Taran Pelkey 709eb283d9 Add endpoints for managing IAM policies (#15897)
Co-authored-by: Taran <taran@minio.io>
Co-authored-by: ¨taran-p¨ <¨taran@minio.io¨>
Co-authored-by: Aditya Manthramurthy <donatello@users.noreply.github.com>
2022-12-13 12:13:23 -08:00
Anis Elleuch 76dde82b41 Implement STS account info API (#16115) 2022-12-13 08:38:50 -08:00
Anis Elleuch 939c0100a6 log: Do not interpret verbs in object names in console output (#16233) 2022-12-13 08:27:40 -08:00
Aditya Manthramurthy 2d60bf8c50 Refactor HTTP transports (#16222) 2022-12-12 20:31:21 -08:00
Harshavardhana 37e20f6ef2 feat: allow listening specific addrs for API port (#16223) 2022-12-12 18:48:46 -08:00
Minio Trusted 76905b7a67 Update yaml files to latest version RELEASE.2022-12-12T19-27-27Z 2022-12-13 01:10:31 +00:00
Aditya Manthramurthy a469e6768d Add LDAP DNS SRV record lookup support (#16201) 2022-12-12 11:27:27 -08:00
Harshavardhana 2fc182d8e6 fix: iso8601TimeFormat padding issue for certain nanoseconds (#16207) 2022-12-12 10:28:30 -08:00
Shireesh Anjal a2cbeaa9e6 Use different subnet public key during dev/test (#16216) 2022-12-12 10:28:15 -08:00
Harshavardhana 444ff20bc5 do not rename multipart failed transactions back to tmp (#16204) 2022-12-12 01:40:29 -08:00
Harshavardhana 20ef5e7a6a avoid double deletes() when no more versions (#16206) 2022-12-12 01:40:04 -08:00
Minio Trusted c233c8e329 update console to v0.22.2 2022-12-09 21:10:13 -08:00
Aditya Manthramurthy e06127566d Add IAM API to attach/detach policies for LDAP (#16182) 2022-12-09 13:08:33 -08:00
Harshavardhana dfe73629a3 fix: delete marker discrepancies via DeleteObject() API (#16195) 2022-12-08 18:15:16 -08:00
Harshavardhana b03dd1af17 remove hard limit for number of buckets (#16194) 2022-12-08 12:24:03 -08:00
Harshavardhana 4bc367c490 fix: translate tier add errors properly (#16191) 2022-12-08 11:18:07 -08:00
Klaus Post 3eb2d086b2 Replace filepathx with fork (#16192) 2022-12-08 10:42:44 -08:00
Klaus Post 70986b6e6e Add version id to healresult (#16193) 2022-12-08 07:49:10 -08:00
jiuker 8edc2faaa9 reuse sha256 in config GetSettings (#16188) 2022-12-08 03:03:24 -08:00
Klaus Post ebe395788b feat: Encrypt s3zip file index (#16179) 2022-12-07 14:56:07 -08:00
Klaus Post 12fd6678ee Encrypt checksums with KMS on CompleteMultipartUpload (#16177) 2022-12-07 10:18:18 -08:00
Harshavardhana 90d35b70b4 remove unnecessary logs for truncated XML inputs (#16184) 2022-12-07 08:30:52 -08:00
Minio Trusted 9f71369b67 Update yaml files to latest version RELEASE.2022-12-07T00-56-37Z 2022-12-07 01:30:51 +00:00
Javier Adriel 04ae9058ed Populate end_session_endpoint (#16183) 2022-12-06 16:56:37 -08:00
Aditya Manthramurthy a30cfdd88f Bump up madmin-go to v2 (#16162) 2022-12-06 13:46:50 -08:00
Anis Elleuch 1bae32dc96 xl: Delete older data-dir when replacing an existing version-id (#16176) 2022-12-06 13:43:18 -08:00
Anis Elleuch 932d2c3c62 Add X-Amz-Request-Id to internode calls (#16146) 2022-12-06 09:27:26 -08:00
Anis Elleuch 52f4124678 Remove go1.18 from Github workflow tests (#16180) 2022-12-06 09:11:20 -08:00
jiuker 8d8d07ac5c use readlock instead of writelock to get heal information (#16175) 2022-12-06 08:08:22 -08:00
Anis Elleuch 44735be38e s3: Return correct error when Version is invalid in policy document (#16178) 2022-12-06 08:07:24 -08:00
dorman 1ef1b2ba50 helm: modify the job create order (#15696) 2022-12-05 19:22:31 -08:00
Timofei Bredov 6fdbd778d5 Add minimal setup command to helm chart's readme (#16165) 2022-12-05 13:22:02 -08:00
Harshavardhana 419f351df3 avoid logging gzipped body in trace output (#16172) 2022-12-05 13:21:27 -08:00
Klaus Post 180d6b30ca Avoid hot loop when lock is cancelled (#16169) 2022-12-05 13:21:14 -08:00
Klaus Post 3fd9059b4e opt: Only stream big data usage caches (#16168) 2022-12-05 13:01:11 -08:00
Klaus Post a713aee3d5 Run staticcheck on CI (#16170) 2022-12-05 11:18:50 -08:00
Harshavardhana a9f5b58a01 fix: update the JSON keys for latest 'mc' release (#16171) 2022-12-05 10:28:22 -08:00
Andreas Auernhammer d882ba2cb4 kms: add support for KES enclaves (#16139)
Signed-off-by: Andreas Auernhammer <hi@aead.dev>
2022-12-04 02:34:24 -08:00
Cesar Celis Hernandez 90e37a8745 Start PR on Enterprise when there is new MinIO Version (#16121) 2022-12-04 02:29:25 -08:00
jiuker 6086f45d25 fix: in disk cache readCacheFileStream should closed upon return (#16138) 2022-12-04 02:28:10 -08:00
Minio Trusted d6351879f3 Update yaml files to latest version RELEASE.2022-12-02T19-19-22Z 2022-12-02 20:15:36 +00:00
Harshavardhana 5655272f5a ship mc along with MinIO container image (#16156) 2022-12-02 11:19:22 -08:00
Harshavardhana 9b35c72349 fix: a crash in KMS cert reload function (#16158) 2022-12-02 11:19:05 -08:00
Klaus Post 98cffbce03 s3zip: Limit over-read for single file (#16161) 2022-12-02 08:53:24 -08:00
Klaus Post 1cd875de1e Persist updated metadata (#16160) 2022-12-02 08:35:04 -08:00
Harshavardhana 5a8df7efb3 re-implement StorageInfo to be a peer call (#16155) 2022-12-01 14:31:35 -08:00
Anis Elleuch c84e2939e4 trace: Publish storage layer errors (#16153) 2022-12-01 12:10:54 -08:00
Anis Elleuch 641ab24aec repl: resync orchestrator to use global shared lock (#16154) 2022-12-01 12:10:09 -08:00
Harshavardhana 71133105d7 re-order the top-level config keys for priority (#16150) 2022-12-01 07:50:08 -08:00
Harshavardhana 625677b189 update reedsolomon v1.11.3 (#16149) 2022-11-30 13:39:03 -08:00
Minio Trusted 76943ac05e Update yaml files to latest version RELEASE.2022-11-29T23-40-49Z 2022-11-30 21:13:07 +00:00
Aditya Manthramurthy 87cbd41265 feat: Allow at most one claim based OpenID IDP (#16145) 2022-11-29 15:40:49 -08:00
Harshavardhana be92cf5959 change dependency from amqp -> amqp091 (RabbitMQ) official (#16142) 2022-11-28 16:05:06 -08:00
Klaus Post cc1d8f0057 Check for abandoned data when healing (#16122) 2022-11-28 10:20:55 -08:00
Anis Elleuch 1f1dcdce65 move HTTP recorder to an internal library (#16128) 2022-11-28 10:20:27 -08:00
Shireesh Anjal 98a67a3776 Improvements in logger and audit webhooks (#16102) 2022-11-28 08:03:26 -08:00
Andreas Auernhammer 9b1e70e4f9 kms: fix possible deadlock due to nested RLock calls. (#16136)
Signed-off-by: Andreas Auernhammer <hi@aead.dev>
2022-11-28 07:31:07 -08:00
Harshavardhana 09d4f8cd0f avoid serializing decryptKey() every 15mins (#16135)
if the certs are the same in an environment where the 
cert files are symlinks (e.g Kubernetes), then we resort
to reloading certs every 15mins - we can avoid reload
of the kes client instance. Ensure that the price to pay 
for contending with the lock must happen when necessary.
2022-11-28 01:14:33 -08:00
Minio Trusted 53cbc020b9 Update yaml files to latest version RELEASE.2022-11-26T22-43-32Z 2022-11-27 04:08:35 +00:00
Poorna 63fc6ba2cd preserve replicated ETag properly on target (#16129) 2022-11-26 14:43:32 -08:00
jiuker ce53d7f6c2 add disk.Close() in healFreshDisk to indicate idiomatic flow of code (#16124) 2022-11-26 00:26:15 -08:00
jiuker fe8eed963e fix: wrapped error will not equal in decommissioning (#16113) 2022-11-24 08:00:42 -08:00
Anis Elleuch 97eb7dbf5f notify: Return detailed err msg when connecting to target fails (#16118) 2022-11-24 07:59:19 -08:00
Shireesh Anjal 59f877fc64 fix: Timestamp not added in diagnostics report (#16114) 2022-11-23 07:11:22 -08:00
Klaus Post f96fe9773c fix: duplicated shared prefix with custom delimiter when listing (#16111) 2022-11-22 08:51:04 -08:00
Anis Elleuch 04948b4d55 fix: checking for stale STS account under site replication (#16109) 2022-11-22 07:26:33 -08:00
Klaus Post 98ba622679 Reduce temporary file clean-up waits (#16110) 2022-11-22 07:23:36 -08:00
Harshavardhana 08103870a5 update single drive setup error message (#16098) 2022-11-18 14:47:38 -08:00
Anis Elleuch 993e586855 config: return XMinioConfigNotFound code for non existing config (#16065) 2022-11-18 10:28:14 -08:00
Harshavardhana 58ec835af0 fix: skip free version ID and marker in metadata equality (#16093) 2022-11-18 05:48:22 -08:00
Harshavardhana 6aea950d74 avoid partID lock validating uploadID exists prematurely (#16086) 2022-11-18 03:09:35 -08:00
Poorna 7198be5be9 bucket resync: persist reset id to bucket metadata (#16088) 2022-11-18 01:39:05 -08:00
Minio Trusted 3661aaf8a1 Update yaml files to latest version RELEASE.2022-11-17T23-20-09Z 2022-11-18 08:51:38 +00:00
Klaus Post a22b4adf4c distribute replication ops based on names (#16083) 2022-11-17 15:20:09 -08:00
Klaus Post b7bb122be8 fix: replication auto-scaling deadlock (#16084) 2022-11-17 07:35:02 -08:00
Krishnan Parthasarathi 8441a3bf5f fix: update metacache entry only once (#16072) 2022-11-16 11:25:00 -08:00
Harshavardhana 853c4de75a allow changing endpoints in distributed setups (#16071) 2022-11-16 07:59:10 -08:00
jiuker 3597af789e allow resultCh to be closed() after clusterMetaHealthInfo() (#16073) 2022-11-16 03:04:36 -08:00
Andreas Auernhammer 4c9cac0b47 update KES dependency to v0.22.0 (#16077)
Signed-off-by: Andreas Auernhammer <hi@aead.dev>
2022-11-16 03:03:04 -08:00
Minio Trusted 1a0b68498b update console release v0.21.3
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-11-15 16:47:25 -08:00
Shireesh Anjal 5246e3be84 Send health diagnostics data as part of callhome (#16006) 2022-11-15 13:53:05 -08:00
Klaus Post 8a07000e58 fix: refactor getReplicationDiff for safe use (#16051) 2022-11-15 07:59:21 -08:00
Krishnan Parthasarathi 3bb82ef60d top-locks: Include lock-held duration (#16061) 2022-11-15 07:57:52 -08:00
Alexander Overvoorde c8a221a9a7 Add missing argument for tpl in Helm chart (fix for bug in #16064) (#16068) 2022-11-15 07:56:58 -08:00
Harshavardhana 91f45c4aa6 avoid inconsistent versions healing when versions are large (#16066) 2022-11-14 18:35:26 -08:00
Alexander Overvoorde 7c5e4da90c helm: Allow tls.certSecret in chart to be template'd (#16064) 2022-11-14 09:47:59 -08:00
Poorna d6bc141bd1 feat: Add support for site level resync (#15753) 2022-11-14 07:16:40 -08:00
jiuker 7ac64ad24a fix: use errors.Is for wrapped returns (#16062) 2022-11-14 07:15:46 -08:00
asoria-lf 14e52f29b0 helm: Add new job to create service accounts (#15939) 2022-11-13 09:28:07 -08:00
Philipp B 344ae9f84e helm: add extraContainer (#15660)
Signed-off-by: Philipp Born <git@pborn.eu>
2022-11-13 09:22:27 -08:00
Minio Trusted f7db12c7ef helm release v5.0.1 2022-11-13 02:04:51 -08:00
Harshavardhana 962d1f1a71 choose default values upon incorrect storage_class value (#16058) 2022-11-12 10:18:21 -08:00
Harshavardhana 6d76db9d6c improve server startup error when pools are incorrect (#16056) 2022-11-11 19:40:45 -08:00
yanggang 00857f8f59 helm: fix positional parameter in template (#15983)
fixes #15901
2022-11-11 12:44:37 -08:00
Ray 66239f30ce configuring the nats target to reconnect forever (#16050) 2022-11-11 12:42:41 -08:00
jiuker bf89f79694 save deploymentID to avoid mutating request entry in Audit (#16053) 2022-11-11 12:42:15 -08:00
elg0ch0 ce299b47ea helm: update bucket policy setting via 'mc anonymous' (#16055) 2022-11-11 11:34:01 -08:00
Minio Trusted 6dc7109a9f Update yaml files to latest version RELEASE.2022-11-11T03-44-20Z 2022-11-11 04:57:16 +00:00
jiuker bdcb485740 netPerfRX Reset() should use write Lock() (#16043) 2022-11-10 19:44:20 -08:00
Poorna e32b948a49 fix: parsing multipart uploadID under site replicated setup (#16048)
continue the fix from #16034
2022-11-10 16:17:45 -08:00
Minio Trusted 4fe9cbb973 Update yaml files to latest version RELEASE.2022-11-10T18-20-21Z 2022-11-10 19:36:54 +00:00
Klaus Post 5b242f1d11 Add Audit target metrics (#16044) 2022-11-10 10:20:21 -08:00
Poorna 34d28dd79f replication: Avoid blocking on mrf save (#16045) 2022-11-10 10:20:02 -08:00
Krishnan Parthasarathi 6eef9b4a23 lifecycle: simplify Eval and HasActiveRules (#16036) 2022-11-10 07:17:45 -08:00
Aditya Manthramurthy 5f1999cc71 fix: avoid URL unsafe chars in multipart upload ID (#16034) 2022-11-09 16:41:16 -08:00
Krishnan Parthasarathi 40a2c6b882 Return remote tier as StorageClass for transitioned objects (#16035) 2022-11-09 15:57:34 -08:00
Krishnan Parthasarathi 7ba281728f ilm: fix x-amz-expiration header evaluation (#16029) 2022-11-09 04:20:34 -08:00
jiuker 7b7356f04c close the reader under disk cache bitrot verification (#16024) 2022-11-09 04:20:11 -08:00
Klaus Post bbc312fce6 Add notification queue metrics (#16026) 2022-11-08 16:36:47 -08:00
Harshavardhana 1b0dfb0f58 remove printing map() checksums (#16028) 2022-11-08 13:29:24 -08:00
Anis Elleuch 7260241511 Remove some logs caused by external apps (#16027) 2022-11-08 13:29:05 -08:00
Anis Elleuch 3b1a9b9fdf Use the same lock for the scanner and site replication healing (#15985) 2022-11-08 08:55:55 -08:00
yanggang 52769e1e71 remove io/util for advanced golang (#16011) 2022-11-08 07:58:02 -08:00
Harshavardhana 72afc2727a rebalance status must return appropriate error initially (#16022) 2022-11-08 07:56:45 -08:00
Minio Trusted 808739867c Update yaml files to latest version RELEASE.2022-11-08T05-27-07Z 2022-11-08 05:59:18 +00:00
Harshavardhana 752e18e795 upgrade console to v0.21.2 2022-11-07 21:27:07 -08:00
Aditya Manthramurthy 76d822bf1e Add LDAP policy entities API (#15908) 2022-11-07 14:35:09 -08:00
Klaus Post ddeca9f12a fix: filter rest errors and logs returned (#16019) 2022-11-07 10:38:08 -08:00
Shireesh Anjal 19d0340ddf Update version of madmin-go to v1.7.3 (#15994) 2022-11-07 09:32:18 -08:00
Harshavardhana 21251d8c22 initialize streaming events without lazy initialization (#16016) 2022-11-07 08:01:24 -08:00
Harshavardhana 1f3db03bf0 allow changing argument for path for SNSD setup (#16013) 2022-11-07 00:11:58 -08:00
Harshavardhana 944c62daf4 skip flaky tests on windows OS (#16015) 2022-11-07 00:11:21 -08:00
Harshavardhana 9547b7d0e9 add deadlineConnections on remoteTransport (#16010) 2022-11-05 11:09:21 -07:00
Harshavardhana 76c4ea7682 force all internal MinIO operations to be under UTC (#16009) 2022-11-04 16:44:38 -07:00
Klaus Post 808ecfe0f2 merge versions across sets when listing (#16003) 2022-11-04 11:33:22 -07:00
Klaus Post 2894dd4d1a fix: hold lock while serializing replication stats (#16007) 2022-11-04 09:59:14 -07:00
yanggang 797fa7f97b update Elasticsearch dependency to 7.17.7 (#15992) 2022-11-04 08:23:33 -07:00
jiuker fd8750e959 fix: http body must be drained in downloadBinary() (#16001) 2022-11-04 08:22:38 -07:00
Harshavardhana 7be65f66b8 support HS256 series of JWT signature for OpenID connect (#15993) 2022-11-03 16:41:53 -07:00
Poorna 4f5d38a4b1 site replication edit: validate endpoint belongs to deployment (#16000) 2022-11-03 16:23:45 -07:00
Anis Elleuch 7e73fc2870 Implement inspect data API v2 (#15474)
Co-authored-by: Klaus Post <klauspost@gmail.com>
2022-11-02 13:36:38 -07:00
yanggang d2c9a9e395 add windows port allot by "netsh dynamicport" (#15986) 2022-11-02 09:10:26 -07:00
Harshavardhana 0d49b365ff converge SNSD deployments into single code (#15988) 2022-11-01 16:41:01 -07:00
Anis Elleuch 7721595aa9 config: Deprecated delay/max_wait/scanner and introduce speed (#15941) 2022-11-01 08:04:07 -07:00
Harshavardhana fd6f6fc8df cleanup stale parent multipart directories (#15980) 2022-11-01 08:00:02 -07:00
Aditya Manthramurthy 4fb47cd568 fix: update admin IDP APIs to be more RESTful (#15896) 2022-10-31 14:52:26 -07:00
Klaus Post ecc932d5dd Clean entire tmp-old on restart (#15979) 2022-10-31 07:27:50 -07:00
Harshavardhana b57fbff7c1 ignore background healInfo in single drive setup (#15968) 2022-10-31 07:26:10 -07:00
Harshavardhana 4892a766a8 do not panic if webhook returns an error (#15970) 2022-10-30 16:45:53 -07:00
Minio Trusted 0303cd8625 Update yaml files to latest version RELEASE.2022-10-29T06-21-33Z 2022-10-29 09:29:37 +00:00
Poorna d765b89a63 improve validation for replication resync API (#15964) 2022-10-28 23:21:33 -07:00
Harshavardhana 6e4acf0504 add a message of removal for gateway and hide the command (#15965) 2022-10-28 14:11:20 -07:00
Klaus Post 71954faa3a mark pubsub type safe via generics (#15961) 2022-10-28 10:55:42 -07:00
Shireesh Anjal 6d22e74d11 mark SUBNET config keys as sensitive info (#15962) 2022-10-28 10:54:44 -07:00
Poorna dc92bb4646 xl-meta: update metadata version (#15958) 2022-10-28 02:48:43 -07:00
Klaus Post 0f0e154315 fix: inconsistent replication delete marker timestamps (#15956) 2022-10-27 09:46:52 -07:00
Harshavardhana 136d41775f remove numAvailableDisks check as it doesn't serve any purpose (#15954) 2022-10-27 09:05:24 -07:00
Harshavardhana ec77d28e62 make subnet subsys dynamic and simplify callhome (#15927) 2022-10-27 00:20:01 -07:00
Klaus Post 86420a1f46 Store multipart checksums (#15953) 2022-10-26 18:14:58 -07:00
Poorna 7dd8b6c8ed ensure ILM expiry creates non null deleteMarker for versioned bucket (#15947) 2022-10-26 16:09:27 -07:00
杨刚 8afa6fefd8 add cross-build linux/amd64 (#15949) 2022-10-26 16:05:26 -07:00
Anis Elleuch 533c9d4fe3 fix: lockName to disallow parallel same erasure set healing (#15951) 2022-10-26 12:43:54 -07:00
Anis Elleuch a35ef155fc return appropriate error status code in the lock handler (#15950) 2022-10-26 09:51:26 -07:00
Poorna 8dd3c41b2a allow MakeBucket errors to be handled lazily (#15945)
remote error is not required to be passed back to the 
client - this is mostly because we have healing that should 
eventually, catch up on this and heal the bucket.
2022-10-25 23:32:37 -07:00
Krishnan Parthasarathi 4523da6543 feat: introduce pool-level rebalance (#15483) 2022-10-25 12:36:57 -07:00
Poorna ce8456a1a9 proxy multipart to peers via multipart uploadID (#15926) 2022-10-25 10:52:29 -07:00
Minio Trusted 1673778633 add missing helm 5.0.0
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-10-25 03:20:22 -07:00
Poorna 9ce1884732 reject editing bucket replication config when site replication is enabled (#15937) 2022-10-24 20:24:32 -07:00
Harshavardhana 23b329b9df remove gateway completely (#15929) 2022-10-24 17:44:15 -07:00
Krishnan Parthasarathi 0c34e51a75 Filter out tiering metadata during CopyObject (#15936) 2022-10-24 16:32:31 -07:00
Minio Trusted 1633b30979 update helm v4.1.0
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-10-24 13:37:05 -07:00
Minio Trusted 630dabf4b9 Update yaml files to latest version RELEASE.2022-10-24T18-35-07Z 2022-10-24 19:29:30 +00:00
Anis Elleuch fc6c794972 Audit dangling object removal (#15933) 2022-10-24 11:35:07 -07:00
Brien Dieterle 2e33b99c6b helm: move Prometheus TLSConfig to job spec (#15739) 2022-10-24 08:46:41 -07:00
Harshavardhana 3b7292b637 update console v0.21.1 2022-10-24 02:20:47 -07:00
Harshavardhana e4f469ae7a update console dependency to master 2022-10-23 22:36:24 -07:00
Minio Trusted c921dc75c7 Update yaml files to latest version RELEASE.2022-10-21T22-37-48Z 2022-10-22 00:04:37 +00:00
Klaus Post 86d543d0f6 Check for s3zip content offset (#15924) 2022-10-21 15:37:48 -07:00
Poorna e4e90b53c1 fix: delete-marker replication check properly (#15923) 2022-10-21 14:45:06 -07:00
Anis Elleuch 58d776daa0 Set CONSOLE_MINIO_SERVER to 127.0.0.1 by default (#15887) 2022-10-21 14:42:28 -07:00
Krishnan Parthasarathi f6b2e89109 Pass encrypted etag as is for immediate tiering (#15925) 2022-10-21 14:40:50 -07:00
Anis Elleuch ac85c2af76 lifecycle: refactor rules filtering and tagging support (#15914) 2022-10-21 10:46:53 -07:00
Shireesh Anjal 5aba2aedb3 Do not freeze s3 traffic in healthinfo api (#15912) 2022-10-21 00:34:32 -07:00
Minio Trusted bd77f1df4c Update yaml files to latest version RELEASE.2022-10-20T00-55-09Z 2022-10-20 06:38:33 +00:00
465 changed files with 22554 additions and 25679 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.18.x, 1.19.x] go-version: [1.19.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
+3 -2
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.18.5b7] go-version: [1.19.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -30,9 +30,10 @@ jobs:
- name: Setup dockerfile for build test - name: Setup dockerfile for build test
run: | run: |
echo "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:${{ matrix.go-version }}" > Dockerfile.fips.test echo "FROM golang:1.19.4" >> Dockerfile.fips.test
echo "COPY . /minio" >> Dockerfile.fips.test echo "COPY . /minio" >> Dockerfile.fips.test
echo "WORKDIR /minio" >> Dockerfile.fips.test echo "WORKDIR /minio" >> Dockerfile.fips.test
echo "ENV GOEXPERIMENT=boringcrypto" >> Dockerfile.fips.test
echo "RUN make" >> Dockerfile.fips.test echo "RUN make" >> Dockerfile.fips.test
- name: Build - name: Build
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.18.x, 1.19.x] go-version: [1.19.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
+2 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.18.x, 1.19.x] go-version: [1.19.x]
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -34,6 +34,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
GO111MODULE: on GO111MODULE: on
run: | run: |
netsh int ipv4 set dynamicport tcp start=60000 num=61000
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
go test -v --timeout 50m ./... go test -v --timeout 50m ./...
- name: Build on ${{ matrix.os }} - name: Build on ${{ matrix.os }}
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.18.x, 1.19.x] go-version: [1.19.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
# are turned off - i.e. if ldap="", then ldap server is not enabled for # are turned off - i.e. if ldap="", then ldap server is not enabled for
# the tests. # the tests.
matrix: matrix:
go-version: [1.18.x] go-version: [1.19.x]
ldap: ["", "localhost:389"] ldap: ["", "localhost:389"]
etcd: ["", "http://localhost:2379"] etcd: ["", "http://localhost:2379"]
openid: ["", "http://127.0.0.1:5556/dex"] openid: ["", "http://127.0.0.1:5556/dex"]
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
strategy: strategy:
matrix: matrix:
go-version: [1.18.x, 1.19.x] go-version: [1.19.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.18.x, 1.19.x] go-version: [1.19.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
+5 -1
View File
@@ -6,6 +6,10 @@ on:
push: push:
branches: branches:
- master - master
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
vulncheck: vulncheck:
name: Analysis name: Analysis
@@ -16,7 +20,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: 1.19.x go-version: 1.19
check-latest: true check-latest: true
- name: Get official govulncheck - name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest run: go install golang.org/x/vuln/cmd/govulncheck@latest
+1 -1
View File
@@ -25,7 +25,7 @@ mc.*
s3-check-md5* s3-check-md5*
xl-meta* xl-meta*
healing-* healing-*
inspect* inspect*.zip
200M* 200M*
hash-set hash-set
minio.RELEASE* minio.RELEASE*
+1459 -249
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
ARG RELEASE ARG RELEASE
+4 -2
View File
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
ARG TARGETARCH ARG TARGETARCH
@@ -29,13 +29,15 @@ COPY LICENSE /licenses/LICENSE
RUN \ RUN \
microdnf clean all && \ microdnf clean all && \
microdnf update --nodocs && \ microdnf update --nodocs && \
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \ microdnf install curl ca-certificates shadow-utils util-linux gzip lsof tar net-tools --nodocs && \
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \ rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
microdnf install minisign --nodocs && \ microdnf install minisign --nodocs && \
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \ mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /opt/bin/minio && \ curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /opt/bin/minio && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \ curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \ curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /opt/bin/mc && \
gzip /opt/bin/mc && \
microdnf clean all && \ microdnf clean all && \
chmod +x /opt/bin/minio && \ chmod +x /opt/bin/minio && \
chmod +x /usr/bin/docker-entrypoint.sh && \ chmod +x /usr/bin/docker-entrypoint.sh && \
+1 -1
View File
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
ARG TARGETARCH ARG TARGETARCH
+14 -5
View File
@@ -20,8 +20,9 @@ help: ## print this help
getdeps: ## fetch necessary dependencies getdeps: ## fetch necessary dependencies
@mkdir -p ${GOPATH}/bin @mkdir -p ${GOPATH}/bin
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin @echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@f3635b96e4838a6c773babb65ef35297fe5fe2f9 @echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.7
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest @echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
@echo "Installing staticcheck" && go install honnef.co/go/tools/cmd/staticcheck@latest
crosscompile: ## cross compile minio crosscompile: ## cross compile minio
@(env bash $(PWD)/buildscripts/cross-compile.sh) @(env bash $(PWD)/buildscripts/cross-compile.sh)
@@ -35,6 +36,7 @@ check-gen: ## check for updated autogenerated files
lint: ## runs golangci-lint suite of linters lint: ## runs golangci-lint suite of linters
@echo "Running $@ check" @echo "Running $@ check"
@${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml @${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml
@${GOPATH}/bin/staticcheck --tests=false ./...
check: test check: test
test: verifiers build ## builds minio, runs linters, tests test: verifiers build ## builds minio, runs linters, tests
@@ -62,11 +64,18 @@ test-iam: build ## verify IAM (external IDP, etcd backends)
@echo "Running tests for IAM (external IDP, etcd backends) with -race" @echo "Running tests for IAM (external IDP, etcd backends) with -race"
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd @MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
test-replication: install ## verify multi site replication test-replication-2site:
@echo "Running tests for replicating three sites"
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh) @(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
test-replication-3site:
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
test-delete-replication:
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
test-replication: install test-replication-2site test-replication-3site test-delete-replication ## verify multi site replication
@echo "Running tests for replicating three sites"
test-site-replication-ldap: install ## verify automatic site replication test-site-replication-ldap: install ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with LDAP)" @echo "Running tests for automatic site replication of IAM (with LDAP)"
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh) @(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
@@ -133,7 +142,7 @@ docker-hotfix: hotfix-push checks ## builds minio docker container with hotfix t
@echo "Building minio docker image '$(TAG)'" @echo "Building minio docker image '$(TAG)'"
@docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix @docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix
docker: build checks ## builds minio docker container docker: build ## builds minio docker container
@echo "Building minio docker image '$(TAG)'" @echo "Building minio docker image '$(TAG)'"
@docker build -q --no-cache -t $(TAG) . -f Dockerfile @docker build -q --no-cache -t $(TAG) . -f Dockerfile
+1 -1
View File
@@ -1,4 +1,4 @@
MinIO Project, (C) 2015-2021 MinIO, Inc. MinIO Project, (C) 2015-2023 MinIO, Inc.
This product includes software developed at MinIO, Inc. This product includes software developed at MinIO, Inc.
(https://min.io/). (https://min.io/).
+1 -1
View File
@@ -125,7 +125,7 @@ You can also connect using any S3-compatible tool, such as the MinIO Client `mc`
## Install from Source ## 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.18](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.19](https://golang.org/dl/#stable)
```sh ```sh
go install github.com/minio/minio@latest go install github.com/minio/minio@latest
+1 -1
View File
@@ -9,7 +9,7 @@ function _init() {
export CGO_ENABLED=0 export CGO_ENABLED=0
## List of architectures and OS to test coss compilation. ## List of architectures and OS to test coss compilation.
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64" SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
} }
function _build() { function _build() {
+1 -1
View File
@@ -27,7 +27,7 @@ import (
"os" "os"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
) )
func main() { func main() {
+2 -2
View File
@@ -22,9 +22,9 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/gorilla/mux"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
@@ -90,7 +90,7 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
if aclHeader == "" { if aclHeader == "" {
acl := &accessControlPolicy{} acl := &accessControlPolicy{}
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil { if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
if err == io.EOF { if terr, ok := err.(*xml.SyntaxError); ok && terr.Msg == io.EOF.Error() {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader), writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader),
r.URL) r.URL)
return return
+25 -39
View File
@@ -29,11 +29,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/gorilla/mux"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/klauspost/compress/zip" "github.com/klauspost/compress/zip"
"github.com/minio/kes" "github.com/minio/kes-go"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
objectlock "github.com/minio/minio/internal/bucket/object/lock" objectlock "github.com/minio/minio/internal/bucket/object/lock"
@@ -41,6 +40,7 @@ import (
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
iampolicy "github.com/minio/pkg/iam/policy" iampolicy "github.com/minio/pkg/iam/policy"
) )
@@ -107,10 +107,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
} }
// Call site replication hook. // Call site replication hook.
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil { logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Write success response. // Write success response.
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
@@ -160,11 +157,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
bucket := pathClean(vars["bucket"]) bucket := pathClean(vars["bucket"])
update := r.Form.Get("update") == "true" update := r.Form.Get("update") == "true"
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction) objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
if objectAPI == nil { if objectAPI == nil {
@@ -177,7 +169,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
return return
} }
cred, _, _, s3Err := validateAdminSignature(ctx, r, "") cred, _, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone { if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return return
@@ -206,7 +198,18 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
if update { if update {
ops = madmin.GetTargetUpdateOps(r.Form) ops = madmin.GetTargetUpdateOps(r.Form)
} else { } else {
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target) var exists bool // true if arn exists
target.Arn, exists = globalBucketTargetSys.getRemoteARN(bucket, &target, "")
if exists && target.Arn != "" { // return pre-existing ARN
data, err := json.Marshal(target.Arn)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Write success response.
writeSuccessResponseJSON(w, data)
return
}
} }
if target.Arn == "" { if target.Arn == "" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
@@ -222,6 +225,10 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
for _, op := range ops { for _, op := range ops {
switch op { switch op {
case madmin.CredentialsUpdateType: case madmin.CredentialsUpdateType:
if globalSiteReplicationSys.isEnabled() {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetDenyEditError, err), r.URL)
return
}
tgt.Credentials = target.Credentials tgt.Credentials = target.Credentials
tgt.TargetBucket = target.TargetBucket tgt.TargetBucket = target.TargetBucket
tgt.Secure = target.Secure tgt.Secure = target.Secure
@@ -289,10 +296,6 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
bucket := pathClean(vars["bucket"]) bucket := pathClean(vars["bucket"])
arnType := vars["type"] arnType := vars["type"]
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction) objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
if objectAPI == nil { if objectAPI == nil {
@@ -328,10 +331,6 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
bucket := pathClean(vars["bucket"]) bucket := pathClean(vars["bucket"])
arn := vars["arn"] arn := vars["arn"]
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction) objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
if objectAPI == nil { if objectAPI == nil {
@@ -373,10 +372,6 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
bucket := pathClean(r.Form.Get("bucket")) bucket := pathClean(r.Form.Get("bucket"))
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction) objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction)
if objectAPI == nil { if objectAPI == nil {
@@ -661,10 +656,6 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction) objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
if objectAPI == nil { if objectAPI == nil {
@@ -717,7 +708,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
opts := MakeBucketOptions{ opts := MakeBucketOptions{
LockEnabled: config.ObjectLockEnabled == "Enabled", LockEnabled: config.ObjectLockEnabled == "Enabled",
} }
err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts) err = objectAPI.MakeBucket(ctx, bucket, opts)
if err != nil { if err != nil {
if _, ok := err.(BucketExists); !ok { if _, ok := err.(BucketExists); !ok {
rpt.SetStatus(bucket, fileName, err) rpt.SetStatus(bucket, fileName, err)
@@ -778,7 +769,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
continue continue
} }
if _, ok := bucketMap[bucket]; !ok { if _, ok := bucketMap[bucket]; !ok {
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}); err != nil { if err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil {
if _, ok := err.(BucketExists); !ok { if _, ok := err.(BucketExists); !ok {
rpt.SetStatus(bucket, fileName, err) rpt.SetStatus(bucket, fileName, err)
continue continue
@@ -830,7 +821,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
bucket, fileName := slc[0], slc[1] bucket, fileName := slc[0], slc[1]
// create bucket if it does not exist yet. // create bucket if it does not exist yet.
if _, ok := bucketMap[bucket]; !ok { if _, ok := bucketMap[bucket]; !ok {
err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil { if err != nil {
if _, ok := err.(BucketExists); !ok { if _, ok := err.(BucketExists); !ok {
rpt.SetStatus(bucket, "", err) rpt.SetStatus(bucket, "", err)
@@ -884,7 +875,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
// Version in policy must not be empty // Version in policy must not be empty
if bucketPolicy.Version == "" { if bucketPolicy.Version == "" {
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrMalformedPolicy.String())) rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrPolicyInvalidVersion.String()))
continue continue
} }
@@ -1091,11 +1082,6 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff) objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
if objectAPI == nil { if objectAPI == nil {
return return
+23 -29
View File
@@ -23,8 +23,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/minio/kes" "github.com/minio/kes-go"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
iampolicy "github.com/minio/pkg/iam/policy" iampolicy "github.com/minio/pkg/iam/policy"
@@ -84,7 +84,13 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
Description: e.Error(), Description: e.Error(),
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
} }
case config.Error: case config.ErrConfigNotFound:
apiErr = APIError{
Code: "XMinioConfigNotFoundError",
Description: e.Error(),
HTTPStatusCode: http.StatusNotFound,
}
case config.ErrConfigGeneric:
apiErr = APIError{ apiErr = APIError{
Code: "XMinioConfigError", Code: "XMinioConfigError",
Description: e.Error(), Description: e.Error(),
@@ -124,6 +130,18 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
Description: err.Error(), Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
} }
case errors.Is(err, errDecommissionRebalanceAlreadyRunning):
apiErr = APIError{
Code: "XMinioDecommissionNotAllowed",
Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case errors.Is(err, errRebalanceDecommissionAlreadyRunning):
apiErr = APIError{
Code: "XMinioRebalanceNotAllowed",
Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case errors.Is(err, errConfigNotFound): case errors.Is(err, errConfigNotFound):
apiErr = APIError{ apiErr = APIError{
Code: "XMinioConfigError", Code: "XMinioConfigError",
@@ -136,15 +154,9 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
Description: err.Error(), Description: err.Error(),
HTTPStatusCode: http.StatusForbidden, HTTPStatusCode: http.StatusForbidden,
} }
case errors.Is(err, errIAMServiceAccount): case errors.Is(err, errIAMServiceAccountNotAllowed):
apiErr = APIError{ apiErr = APIError{
Code: "XMinioIAMServiceAccount", Code: "XMinioIAMServiceAccountNotAllowed",
Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case errors.Is(err, errIAMServiceAccountUsed):
apiErr = APIError{
Code: "XMinioIAMServiceAccountUsed",
Description: err.Error(), Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
} }
@@ -192,24 +204,6 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
Description: err.Error(), Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
} }
case errors.Is(err, errTierBackendInUse):
apiErr = APIError{
Code: "XMinioAdminTierBackendInUse",
Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case errors.Is(err, errTierBackendNotEmpty):
apiErr = APIError{
Code: "XMinioAdminTierBackendNotEmpty",
Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case errors.Is(err, errTierInsufficientCreds):
apiErr = APIError{
Code: "XMinioAdminTierInsufficientCreds",
Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case errIsTierPermError(err): case errIsTierPermError(err):
apiErr = APIError{ apiErr = APIError{
Code: "XMinioAdminTierInsufficientPermissions", Code: "XMinioAdminTierInsufficientPermissions",
+55 -32
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc. // Copyright (c) 2015-2023 MinIO, Inc.
// //
// This file is part of MinIO Object Storage stack // This file is part of MinIO Object Storage stack
// //
@@ -26,8 +26,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/gorilla/mux" "github.com/minio/madmin-go/v2"
"github.com/minio/madmin-go"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/config/cache" "github.com/minio/minio/internal/config/cache"
"github.com/minio/minio/internal/config/etcd" "github.com/minio/minio/internal/config/etcd"
@@ -37,6 +36,7 @@ import (
polplugin "github.com/minio/minio/internal/config/policy/plugin" polplugin "github.com/minio/minio/internal/config/policy/plugin"
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" iampolicy "github.com/minio/pkg/iam/policy"
) )
@@ -71,7 +71,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
return return
} }
cfg, err := readServerConfig(ctx, objectAPI) cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@@ -117,6 +117,26 @@ func applyDynamic(ctx context.Context, objectAPI ObjectLayer, cfg config.Config,
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue) w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
} }
type badConfigErr struct {
Err error
}
// Error - return the error message
func (bce badConfigErr) Error() string {
return bce.Err.Error()
}
// Unwrap the error to its underlying error.
func (bce badConfigErr) Unwrap() error {
return bce.Err
}
type setConfigResult struct {
Cfg config.Config
SubSys string
Dynamic bool
}
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv // SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetConfigKV") ctx := newContext(r, w, "SetConfigKV")
@@ -142,46 +162,53 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
return return
} }
cfg, err := readServerConfig(ctx, objectAPI) result, err := setConfigKV(ctx, objectAPI, kvBytes)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) switch err.(type) {
case badConfigErr:
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
default:
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
}
return return
} }
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes)) if result.Dynamic {
applyDynamic(ctx, objectAPI, result.Cfg, result.SubSys, r, w)
}
writeSuccessResponseHeadersOnly(w)
}
func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (result setConfigResult, err error) {
result.Cfg, err = readServerConfig(ctx, objectAPI, nil)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
subSys, _, _, err := config.GetSubSys(string(kvBytes)) result.Dynamic, err = result.Cfg.ReadConfig(bytes.NewReader(kvBytes))
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
if err = validateConfig(cfg, subSys); err != nil { result.SubSys, _, _, err = config.GetSubSys(string(kvBytes))
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL) if err != nil {
return
}
if verr := validateConfig(result.Cfg, result.SubSys); verr != nil {
err = badConfigErr{Err: verr}
return return
} }
// Update the actual server config on disk. // Update the actual server config on disk.
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil { if err = saveServerConfig(ctx, objectAPI, result.Cfg); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
// Write to the config input KV to history. // Write the config input KV to history.
if err = saveServerConfigHistory(ctx, objectAPI, kvBytes); err != nil { err = saveServerConfigHistory(ctx, objectAPI, kvBytes)
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return
return
}
if dynamic {
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
}
writeSuccessResponseHeadersOnly(w)
} }
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key} // GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
@@ -225,7 +252,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
var s strings.Builder var s strings.Builder
for _, subSysConfig := range subSysConfigs { for _, subSysConfig := range subSysConfigs {
subSysConfig.AddString(&s, false) subSysConfig.WriteTo(&s, false)
} }
password := cred.SecretKey password := cred.SecretKey
@@ -296,7 +323,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
return return
} }
cfg, err := readServerConfig(ctx, objectAPI) cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@@ -454,10 +481,6 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
var s strings.Builder var s strings.Builder
hkvs := config.HelpSubSysMap[""] hkvs := config.HelpSubSysMap[""]
var count int
for _, hkv := range hkvs {
count += len(cfg[hkv.Key])
}
for _, hkv := range hkvs { for _, hkv := range hkvs {
// We ignore the error below, as we cannot get one. // We ignore the error below, as we cannot get one.
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "") cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "")
@@ -482,7 +505,7 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
case config.IdentityPluginSubSys: case config.IdentityPluginSubSys:
off = !idplugin.Enabled(item.Config) off = !idplugin.Enabled(item.Config)
} }
item.AddString(&s, off) item.WriteTo(&s, off)
} }
} }
+163 -94
View File
@@ -26,23 +26,18 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/gorilla/mux" "github.com/minio/madmin-go/v2"
"github.com/minio/madmin-go" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
"github.com/minio/minio/internal/config/identity/openid" "github.com/minio/minio/internal/config/identity/openid"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" iampolicy "github.com/minio/pkg/iam/policy"
"github.com/minio/pkg/ldap" "github.com/minio/pkg/ldap"
) )
// SetIdentityProviderCfg: func (a adminAPIHandlers) addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
//
// PUT <admin-prefix>/id-cfg?type=openid&name=dex1
func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetIdentityCfg")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
@@ -54,6 +49,14 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
return return
} }
// Ensure body content type is opaque to ensure that request body has not
// been interpreted as form data.
contentType := r.Header.Get("Content-Type")
if contentType != "application/octet-stream" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
return
}
password := cred.SecretKey password := cred.SecretKey
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength)) reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil { if err != nil {
@@ -68,44 +71,43 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
return return
} }
var cfgDataBuilder strings.Builder var subSys string
switch idpCfgType { switch idpCfgType {
case madmin.OpenidIDPCfg: case madmin.OpenidIDPCfg:
fmt.Fprintf(&cfgDataBuilder, "identity_openid") subSys = madmin.IdentityOpenIDSubSys
case madmin.LDAPIDPCfg: case madmin.LDAPIDPCfg:
fmt.Fprintf(&cfgDataBuilder, "identity_ldap") subSys = madmin.IdentityLDAPSubSys
} }
// Ensure body content type is opaque.
contentType := r.Header.Get("Content-Type")
if contentType != "application/octet-stream" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
return
}
// Subsystem configuration name could be empty.
cfgName := mux.Vars(r)["name"] cfgName := mux.Vars(r)["name"]
cfgTarget := madmin.Default
if cfgName != "" { if cfgName != "" {
if idpCfgType == madmin.LDAPIDPCfg { cfgTarget = cfgName
// LDAP does not support multiple configurations. So this must be if idpCfgType == madmin.LDAPIDPCfg && cfgName != madmin.Default {
// empty. // LDAP does not support multiple configurations. So cfgName must be
// empty or `madmin.Default`.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
return return
} }
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.SubSystemSeparator, cfgName)
} }
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.KvSpaceSeparator, string(reqBytes)) // Check that this is a valid Create vs Update API call.
s := globalServerConfig.Clone()
cfgData := cfgDataBuilder.String() if apiErrCode := handleCreateUpdateValidation(s, subSys, cfgTarget, isUpdate); apiErrCode != ErrNone {
subSys, _, _, err := config.GetSubSys(cfgData) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
cfg, err := readServerConfig(ctx, objectAPI) cfgData := ""
{
tgtSuffix := ""
if cfgTarget != madmin.Default {
tgtSuffix = config.SubSystemSeparator + cfgTarget
}
cfgData = subSys + tgtSuffix + config.KvSpaceSeparator + string(reqBytes)
}
cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@@ -153,79 +155,86 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }
// GetIdentityProviderCfg: func handleCreateUpdateValidation(s config.Config, subSys, cfgTarget string, isUpdate bool) APIErrorCode {
// if cfgTarget != madmin.Default {
// GET <admin-prefix>/id-cfg?type=openid&name=dex_test // This cannot give an error at this point.
// subSysTargets, _ := s.GetAvailableTargets(subSys)
// GetIdentityProviderCfg returns a list of configured IDPs on the server if subSysTargetsSet := set.CreateStringSet(subSysTargets...)
// name is empty. If name is non-empty, returns the configuration details for if isUpdate && !subSysTargetsSet.Contains(cfgTarget) {
// the IDP of the given type and configuration name. The configuration name for return ErrAdminConfigIDPCfgNameDoesNotExist
// the default ("un-named") configuration target is `_`. }
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) { if !isUpdate && subSysTargetsSet.Contains(cfgTarget) {
ctx := newContext(r, w, "GetIdentityProviderCfg") return ErrAdminConfigIDPCfgNameAlreadyExists
}
return ErrNone
}
// For the default configuration name, since it will always be an available
// target, we need to check if a configuration value has been set previously
// to figure out if this is a valid create or update API call.
// This cannot really error (FIXME: improve the type for GetConfigInfo)
var cfgInfos []madmin.IDPCfgInfo
switch subSys {
case madmin.IdentityOpenIDSubSys:
cfgInfos, _ = globalOpenIDConfig.GetConfigInfo(s, cfgTarget)
case madmin.IdentityLDAPSubSys:
cfgInfos, _ = globalLDAPConfig.GetConfigInfo(s, cfgTarget)
}
if len(cfgInfos) > 0 && !isUpdate {
return ErrAdminConfigIDPCfgNameAlreadyExists
}
if len(cfgInfos) == 0 && isUpdate {
return ErrAdminConfigIDPCfgNameDoesNotExist
}
return ErrNone
}
// AddIdentityProviderCfg: adds a new IDP config for openid/ldap.
//
// PUT <admin-prefix>/idp-cfg/openid/dex1 -> create named config `dex1`
//
// PUT <admin-prefix>/idp-cfg/openid/_ -> create (default) named config `_`
func (a adminAPIHandlers) AddIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddIdentityProviderCfg")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
a.addOrUpdateIDPHandler(ctx, w, r, false)
}
// UpdateIdentityProviderCfg: updates an existing IDP config for openid/ldap.
//
// PATCH <admin-prefix>/idp-cfg/openid/dex1 -> update named config `dex1`
//
// PATCH <admin-prefix>/idp-cfg/openid/_ -> update (default) named config `_`
func (a adminAPIHandlers) UpdateIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "UpdateIdentityProviderCfg")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
a.addOrUpdateIDPHandler(ctx, w, r, true)
}
// ListIdentityProviderCfg:
//
// GET <admin-prefix>/idp-cfg/openid -> lists openid provider configs.
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListIdentityProviderCfg")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction) objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
if objectAPI == nil { if objectAPI == nil {
return return
} }
idpCfgType := mux.Vars(r)["type"]
cfgName := r.Form.Get("name")
password := cred.SecretKey password := cred.SecretKey
idpCfgType := mux.Vars(r)["type"]
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) { if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
return return
} }
// If no cfgName is provided, we list.
if cfgName == "" {
a.listIdentityProviders(ctx, w, r, idpCfgType, password)
return
}
cfg := globalServerConfig.Clone()
var cfgInfos []madmin.IDPCfgInfo
var err error
switch idpCfgType {
case madmin.OpenidIDPCfg:
cfgInfos, err = globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
case madmin.LDAPIDPCfg:
cfgInfos, err = globalLDAPConfig.GetConfigInfo(cfg, cfgName)
}
if err != nil {
if errors.Is(err, openid.ErrProviderConfigNotFound) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
return
}
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
res := madmin.IDPConfig{
Type: idpCfgType,
Name: cfgName,
Info: cfgInfos,
}
data, err := json.Marshal(res)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, idpCfgType, password string) {
var cfgList []madmin.IDPListItem var cfgList []madmin.IDPListItem
var err error var err error
switch idpCfgType { switch idpCfgType {
@@ -261,9 +270,69 @@ func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.Resp
writeSuccessResponseJSON(w, econfigData) writeSuccessResponseJSON(w, econfigData)
} }
// GetIdentityProviderCfg:
//
// GET <admin-prefix>/idp-cfg/openid/dex_test
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetIdentityProviderCfg")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
if objectAPI == nil {
return
}
idpCfgType := mux.Vars(r)["type"]
cfgName := mux.Vars(r)["name"]
password := cred.SecretKey
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
return
}
cfg := globalServerConfig.Clone()
var cfgInfos []madmin.IDPCfgInfo
var err error
switch idpCfgType {
case madmin.OpenidIDPCfg:
cfgInfos, err = globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
case madmin.LDAPIDPCfg:
cfgInfos, err = globalLDAPConfig.GetConfigInfo(cfg, cfgName)
}
if err != nil {
if errors.Is(err, openid.ErrProviderConfigNotFound) || errors.Is(err, cfgldap.ErrProviderConfigNotFound) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
return
}
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
res := madmin.IDPConfig{
Type: idpCfgType,
Name: cfgName,
Info: cfgInfos,
}
data, err := json.Marshal(res)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
// DeleteIdentityProviderCfg: // DeleteIdentityProviderCfg:
// //
// DELETE <admin-prefix>/id-cfg?type=openid&name=dex_test // DELETE <admin-prefix>/idp-cfg/openid/dex_test
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DeleteIdentityProviderCfg") ctx := newContext(r, w, "DeleteIdentityProviderCfg")
@@ -339,7 +408,7 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
return return
} }
cfg, err := readServerConfig(ctx, objectAPI) cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
+181
View File
@@ -0,0 +1,181 @@
// Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"encoding/json"
"io"
"net/http"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/logger"
"github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy"
)
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
//
// GET <admin-prefix>/idp/ldap/policy-entities?[query-params]
//
// Query params:
//
// user=... -> repeatable query parameter, specifying users to query for
// policy mapping
//
// group=... -> repeatable query parameter, specifying groups to query for
// policy mapping
//
// policy=... -> repeatable query parameter, specifying policy to query for
// user/group mapping
//
// When all query parameters are omitted, returns mappings for all policies.
func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListLDAPPolicyMappingEntities")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
// Check authorization.
objectAPI, cred := validateAdminReq(ctx, w, r,
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction)
if objectAPI == nil {
return
}
// Validate API arguments.
q := madmin.PolicyEntitiesQuery{
Users: r.Form["user"],
Groups: r.Form["group"],
Policy: r.Form["policy"],
}
// Query IAM
res, err := globalIAMSys.QueryLDAPPolicyEntities(r.Context(), q)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Encode result and send response.
data, err := json.Marshal(res)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
password := cred.SecretKey
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
// AttachDetachPolicyLDAP attaches or detaches policies from an LDAP entity
// (user or group).
//
// POST <admin-prefix>/idp/ldap/policy/{operation}
func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AttachDetachPolicyLDAP")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
// Check authorization.
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction)
if objectAPI == nil {
return
}
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
// More than maxConfigSize bytes were available
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
return
}
// Ensure body content type is opaque to ensure that request body has not
// been interpreted as form data.
contentType := r.Header.Get("Content-Type")
if contentType != "application/octet-stream" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
return
}
// Validate operation
operation := mux.Vars(r)["operation"]
if operation != "attach" && operation != "detach" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
return
}
isAttach := operation == "attach"
// Validate API arguments in body.
password := cred.SecretKey
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err, logger.Application)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
var par madmin.PolicyAssociationReq
err = json.Unmarshal(reqBytes, &par)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
if err := par.IsValid(); err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
// Call IAM subsystem
updatedAt, addedOrRemoved, err := globalIAMSys.PolicyDBUpdateLDAP(ctx, isAttach, par)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
respBody := madmin.PolicyAssociationResp{
UpdatedAt: updatedAt,
}
if isAttach {
respBody.PoliciesAttached = addedOrRemoved
} else {
respBody.PoliciesDetached = addedOrRemoved
}
data, err := json.Marshal(respBody)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
encryptedData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, encryptedData)
}
+181 -10
View File
@@ -19,14 +19,21 @@ package cmd
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"github.com/gorilla/mux"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
iampolicy "github.com/minio/pkg/iam/policy" iampolicy "github.com/minio/pkg/iam/policy"
) )
var (
errRebalanceDecommissionAlreadyRunning = errors.New("Rebalance cannot be started, decommission is aleady in progress")
errDecommissionRebalanceAlreadyRunning = errors.New("Decommission cannot be started, rebalance is already in progress")
)
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "StartDecommission") ctx := newContext(r, w, "StartDecommission")
@@ -43,23 +50,53 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
return return
} }
pools, ok := objectAPI.(*erasureServerPools) z, ok := objectAPI.(*erasureServerPools)
if !ok { if !ok || len(z.serverPools) == 1 {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return return
} }
if z.IsDecommissionRunning() {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errDecommissionAlreadyRunning), r.URL)
return
}
if z.IsRebalanceStarted() {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL)
return
}
vars := mux.Vars(r) vars := mux.Vars(r)
v := vars["pool"] v := vars["pool"]
idx := globalEndpoints.GetPoolIdx(v) pools := strings.Split(v, ",")
if idx == -1 { poolIndices := make([]int, 0, len(pools))
// We didn't find any matching pools, invalid input
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL) for _, pool := range pools {
return idx := globalEndpoints.GetPoolIdx(pool)
if idx == -1 {
// We didn't find any matching pools, invalid input
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
return
}
var pool *erasureSets
for pidx := range z.serverPools {
if pidx == idx {
pool = z.serverPools[idx]
break
}
}
if pool == nil {
// We didn't find any matching pools, invalid input
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
return
}
poolIndices = append(poolIndices, idx)
} }
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal { if len(poolIndices) > 0 && globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
ep := globalEndpoints[poolIndices[0]].Endpoints[0]
for nodeIdx, proxyEp := range globalProxyEndpoints { for nodeIdx, proxyEp := range globalProxyEndpoints {
if proxyEp.Endpoint.Host == ep.Host { if proxyEp.Endpoint.Host == ep.Host {
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) { if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
@@ -69,7 +106,7 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
} }
} }
if err := pools.Decommission(r.Context(), idx); err != nil { if err := z.Decommission(r.Context(), poolIndices...); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@@ -200,3 +237,137 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus)) logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus))
} }
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RebalanceStart")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
if objectAPI == nil {
return
}
// NB rebalance-start admin API is always coordinated from first pool's
// first node. The following is required to serialize (the effects of)
// concurrent rebalance-start commands.
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
for nodeIdx, proxyEp := range globalProxyEndpoints {
if proxyEp.Endpoint.Host == ep.Host {
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
return
}
}
}
}
pools, ok := objectAPI.(*erasureServerPools)
if !ok || len(pools.serverPools) == 1 {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
if pools.IsDecommissionRunning() {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errRebalanceDecommissionAlreadyRunning), r.URL)
return
}
if pools.IsRebalanceStarted() {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL)
return
}
bucketInfos, err := objectAPI.ListBuckets(ctx, BucketOptions{})
if err != nil {
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
return
}
buckets := make([]string, 0, len(bucketInfos))
for _, bInfo := range bucketInfos {
buckets = append(buckets, bInfo.Name)
}
var id string
if id, err = pools.initRebalanceMeta(ctx, buckets); err != nil {
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Rebalance routine is run on the first node of any pool participating in rebalance.
pools.StartRebalance()
b, err := json.Marshal(struct {
ID string `json:"id"`
}{ID: id})
if err != nil {
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, b)
// Notify peers to load rebalance.bin and start rebalance routine if they happen to be
// participating pool's leader node
globalNotificationSys.LoadRebalanceMeta(ctx, true)
}
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RebalanceStatus")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
if objectAPI == nil {
return
}
// Proxy rebalance-status to first pool first node, so that users see a
// consistent view of rebalance progress even though different rebalancing
// pools may temporarily have out of date info on the others.
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
for nodeIdx, proxyEp := range globalProxyEndpoints {
if proxyEp.Endpoint.Host == ep.Host {
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
return
}
}
}
}
pools, ok := objectAPI.(*erasureServerPools)
if !ok {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
rs, err := rebalanceStatus(ctx, pools)
if err != nil {
if errors.Is(err, errRebalanceNotStarted) || errors.Is(err, errConfigNotFound) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceNotStarted), r.URL)
return
}
logger.LogIf(ctx, fmt.Errorf("failed to fetch rebalance status: %w", err))
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(rs))
}
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RebalanceStop")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
if objectAPI == nil {
return
}
pools, ok := objectAPI.(*erasureServerPools)
if !ok {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
// Cancel any ongoing rebalance operation
globalNotificationSys.StopRebalance(r.Context())
writeSuccessResponseHeadersOnly(w)
logger.LogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
}
+50 -22
View File
@@ -26,8 +26,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/gorilla/mux" "github.com/minio/madmin-go/v2"
"github.com/minio/madmin-go" "github.com/minio/mux"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
@@ -114,37 +114,23 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
default: default:
err = errSRInvalidRequest(errInvalidArgument) err = errSRInvalidRequest(errInvalidArgument)
case madmin.MakeWithVersioningBktOp: case madmin.MakeWithVersioningBktOp:
_, isLockEnabled := r.Form["lockEnabled"] createdAt, cerr := time.Parse(time.RFC3339Nano, strings.TrimSpace(r.Form.Get("createdAt")))
_, isVersioningEnabled := r.Form["versioningEnabled"]
_, isForceCreate := r.Form["forceCreate"]
createdAtStr := strings.TrimSpace(r.Form.Get("createdAt"))
createdAt, cerr := time.Parse(time.RFC3339Nano, createdAtStr)
if cerr != nil { if cerr != nil {
createdAt = timeSentinel createdAt = timeSentinel
} }
opts := MakeBucketOptions{ opts := MakeBucketOptions{
Location: r.Form.Get("location"), LockEnabled: r.Form.Get("lockEnabled") == "true",
LockEnabled: isLockEnabled, VersioningEnabled: r.Form.Get("versioningEnabled") == "true",
VersioningEnabled: isVersioningEnabled, ForceCreate: r.Form.Get("forceCreate") == "true",
ForceCreate: isForceCreate,
CreatedAt: createdAt, CreatedAt: createdAt,
} }
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts) err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
case madmin.ConfigureReplBktOp: case madmin.ConfigureReplBktOp:
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket) err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
case madmin.DeleteBucketBktOp: case madmin.DeleteBucketBktOp, madmin.ForceDeleteBucketBktOp:
_, noRecreate := r.Form["noRecreate"]
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{ err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
Force: false, Force: operation == madmin.ForceDeleteBucketBktOp,
NoRecreate: noRecreate,
SRDeleteOp: getSRBucketDeleteOp(true),
})
case madmin.ForceDeleteBucketBktOp:
_, noRecreate := r.Form["noRecreate"]
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
Force: true,
NoRecreate: noRecreate,
SRDeleteOp: getSRBucketDeleteOp(true), SRDeleteOp: getSRBucketDeleteOp(true),
}) })
case madmin.PurgeDeletedBucketOp: case madmin.PurgeDeletedBucketOp:
@@ -515,3 +501,45 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
return return
} }
} }
// SiteReplicationResyncOp - PUT /minio/admin/v3/site-replication/resync/op
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SiteReplicationResyncOp")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationResyncAction)
if objectAPI == nil {
return
}
var peerSite madmin.PeerInfo
if err := parseJSONBody(ctx, r.Body, &peerSite, ""); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
vars := mux.Vars(r)
op := madmin.SiteResyncOp(vars["operation"])
var (
status madmin.SRResyncOpStatus
err error
)
switch op {
case madmin.SiteResyncStart:
status, err = globalSiteReplicationSys.startResync(ctx, objectAPI, peerSite)
case madmin.SiteResyncCancel:
status, err = globalSiteReplicationSys.cancelResync(ctx, objectAPI, peerSite)
default:
err = errSRInvalidRequest(errInvalidArgument)
}
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
body, err := json.Marshal(status)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, body)
}
+1 -1
View File
@@ -31,7 +31,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
minio "github.com/minio/minio-go/v7" minio "github.com/minio/minio-go/v7"
) )
File diff suppressed because it is too large Load Diff
+48 -2
View File
@@ -33,7 +33,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
cr "github.com/minio/minio-go/v7/pkg/credentials" cr "github.com/minio/minio-go/v7/pkg/credentials"
@@ -1136,7 +1136,7 @@ func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket) c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
// 6. Check that service account **can** be created for some other user. // 6. Check that service account **can** be created for some other user.
// This is possible because of the policy enforced in the plugin. // This is possible because the policy enforced in the plugin.
c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient) c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
} }
@@ -1261,6 +1261,52 @@ func (c *check) mustListBuckets(ctx context.Context, client *minio.Client) {
} }
} }
func (c *check) mustNotDelete(ctx context.Context, client *minio.Client, bucket string, vid string) {
c.Helper()
err := client.RemoveObject(ctx, bucket, "some-object", minio.RemoveObjectOptions{VersionID: vid})
if err == nil {
c.Fatalf("user must not be allowed to delete")
}
err = client.RemoveObject(ctx, bucket, "some-object", minio.RemoveObjectOptions{})
if err != nil {
c.Fatal("user must be able to create delete marker")
}
}
func (c *check) mustDownload(ctx context.Context, client *minio.Client, bucket string) {
c.Helper()
rd, err := client.GetObject(ctx, bucket, "some-object", minio.GetObjectOptions{})
if err != nil {
c.Fatalf("download did not succeed got %#v", err)
}
if _, err = io.Copy(io.Discard, rd); err != nil {
c.Fatalf("download did not succeed got %#v", err)
}
}
func (c *check) mustUploadReturnVersions(ctx context.Context, client *minio.Client, bucket string) []string {
c.Helper()
versions := []string{}
for i := 0; i < 5; i++ {
ui, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
if err != nil {
c.Fatalf("upload did not succeed got %#v", err)
}
versions = append(versions, ui.VersionID)
}
return versions
}
func (c *check) mustUpload(ctx context.Context, client *minio.Client, bucket string) {
c.Helper()
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
if err != nil {
c.Fatalf("upload did not succeed got %#v", err)
}
}
func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) { func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) {
c.Helper() c.Helper()
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{}) _, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
+329 -318
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -29,9 +29,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/gorilla/mux" "github.com/minio/madmin-go/v2"
"github.com/minio/madmin-go"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/mux"
) )
// adminErasureTestBed - encapsulates subsystems that need to be setup for // adminErasureTestBed - encapsulates subsystems that need to be setup for
+29 -13
View File
@@ -27,7 +27,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
) )
@@ -176,11 +176,12 @@ func (ahs *allHealState) getHealLocalDiskEndpoints() Endpoints {
return endpoints return endpoints
} }
func (ahs *allHealState) markDiskForHealing(ep Endpoint) { // Set, in the memory, the state of the disk as currently healing or not
func (ahs *allHealState) setDiskHealingStatus(ep Endpoint, healing bool) {
ahs.Lock() ahs.Lock()
defer ahs.Unlock() defer ahs.Unlock()
ahs.healLocalDisks[ep] = true ahs.healLocalDisks[ep] = healing
} }
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) { func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
@@ -222,8 +223,8 @@ func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
// getHealSequenceByToken - Retrieve a heal sequence by token. The second // getHealSequenceByToken - Retrieve a heal sequence by token. The second
// argument returns if a heal sequence actually exists. // argument returns if a heal sequence actually exists.
func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence, exists bool) { func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence, exists bool) {
ahs.Lock() ahs.RLock()
defer ahs.Unlock() defer ahs.RUnlock()
for _, healSeq := range ahs.healSeqMap { for _, healSeq := range ahs.healSeqMap {
if healSeq.clientToken == token { if healSeq.clientToken == token {
return healSeq, true return healSeq, true
@@ -235,8 +236,8 @@ func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence,
// getHealSequence - Retrieve a heal sequence by path. The second // getHealSequence - Retrieve a heal sequence by path. The second
// argument returns if a heal sequence actually exists. // argument returns if a heal sequence actually exists.
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) { func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
ahs.Lock() ahs.RLock()
defer ahs.Unlock() defer ahs.RUnlock()
h, exists = ahs.healSeqMap[path] h, exists = ahs.healSeqMap[path]
return h, exists return h, exists
} }
@@ -396,6 +397,7 @@ type healSource struct {
bucket string bucket string
object string object string
versionID string versionID string
noWait bool // a non blocking call, if task queue is full return right away.
opts *madmin.HealOpts // optional heal option overrides default setting opts *madmin.HealOpts // optional heal option overrides default setting
} }
@@ -405,9 +407,6 @@ type healSequence struct {
// bucket, and object on which heal seq. was initiated // bucket, and object on which heal seq. was initiated
bucket, object string bucket, object string
// A channel of entities with heal result
respCh chan healResult
// Report healing progress // Report healing progress
reportProgress bool reportProgress bool
@@ -470,7 +469,6 @@ func newHealSequence(ctx context.Context, bucket, objPrefix, clientAddr string,
clientToken := mustGetUUID() clientToken := mustGetUUID()
return &healSequence{ return &healSequence{
respCh: make(chan healResult),
bucket: bucket, bucket: bucket,
object: objPrefix, object: objPrefix,
reportProgress: true, reportProgress: true,
@@ -699,7 +697,6 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
object: source.object, object: source.object,
versionID: source.versionID, versionID: source.versionID,
opts: h.settings, opts: h.settings,
respCh: h.respCh,
} }
if source.opts != nil { if source.opts != nil {
task.opts = *source.opts task.opts = *source.opts
@@ -712,6 +709,24 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
h.lastHealActivity = UTCNow() h.lastHealActivity = UTCNow()
h.mutex.Unlock() h.mutex.Unlock()
if source.noWait {
select {
case globalBackgroundHealRoutine.tasks <- task:
if serverDebugLog {
logger.Info("Task in the queue: %#v", task)
}
default:
// task queue is full, no more workers, we shall move on and heal later.
return nil
}
// Don't wait for result
return nil
}
// respCh must be set to wait for result.
// We make it size 1, so a result can always be written
// even if we aren't listening.
task.respCh = make(chan healResult, 1)
select { select {
case globalBackgroundHealRoutine.tasks <- task: case globalBackgroundHealRoutine.tasks <- task:
if serverDebugLog { if serverDebugLog {
@@ -721,8 +736,9 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
return nil return nil
} }
// task queued, now wait for the response.
select { select {
case res := <-h.respCh: case res := <-task.respCh:
if !h.reportProgress { if !h.reportProgress {
if errors.Is(res.err, errSkipFile) { // this is only sent usually by nopHeal if errors.Is(res.err, errSkipFile) { // this is only sent usually by nopHeal
return nil return nil
+35 -16
View File
@@ -20,11 +20,11 @@ package cmd
import ( import (
"net/http" "net/http"
"github.com/gorilla/mux"
"github.com/klauspost/compress/gzhttp" "github.com/klauspost/compress/gzhttp"
"github.com/klauspost/compress/gzip" "github.com/klauspost/compress/gzip"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
) )
const ( const (
@@ -60,7 +60,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
// Info operations // Info operations
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler))) adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler)))
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/inspect-data").HandlerFunc(httpTraceHdrs(adminAPI.InspectDataHandler)).Queries("volume", "{volume:.*}", "file", "{file:.*}") adminRouter.Methods(http.MethodGet, http.MethodPost).Path(adminVersion + "/inspect-data").HandlerFunc(httpTraceAll(adminAPI.InspectDataHandler))
// StorageInfo operations // StorageInfo operations
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler))) adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
@@ -84,6 +84,11 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(gz(httpTraceAll(adminAPI.StartDecommission))).Queries("pool", "{pool:.*}") adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(gz(httpTraceAll(adminAPI.StartDecommission))).Queries("pool", "{pool:.*}")
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}") adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}")
// Rebalance operations
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/start").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStart)))
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/rebalance/status").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStatus)))
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/stop").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStop)))
} }
// Profiling operations - deprecated API // Profiling operations - deprecated API
@@ -137,12 +142,18 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts))) adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}") adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
// STS accounts ops
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.TemporaryAccountInfo))).Queries("accessKey", "{accessKey:.*}")
// Info policy IAM latest // Info policy IAM latest
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}") adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}")
// List policies latest // List policies latest
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}") adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies))) adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
// Builtin IAM policy associations
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/builtin/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListPolicyMappingEntities)))
// Remove policy IAM // Remove policy IAM
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}") adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}")
@@ -151,6 +162,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))). HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}") Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
// Attach policies to user or group
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/attach").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachPolicyBuiltin)))
// Detach policies from user or group
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/detach").HandlerFunc(gz(httpTraceHdrs(adminAPI.DetachPolicyBuiltin)))
// Remove user IAM // Remove user IAM
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}") adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
@@ -179,10 +196,15 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM)) adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM))
// IDentity Provider configuration APIs // IDentity Provider configuration APIs
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}") adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddIdentityProviderCfg)))
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg))).Queries("type", "{type:.*}") adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateIdentityProviderCfg)))
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}") adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListIdentityProviderCfg)))
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg)))
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg)))
// LDAP IAM operations
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListLDAPPolicyMappingEntities)))
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachDetachPolicyLDAP)))
// -- END IAM APIs -- // -- END IAM APIs --
// GetBucketQuotaConfig // GetBucketQuotaConfig
@@ -248,6 +270,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit))) adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit))) adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove))) adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove)))
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationResyncOp))).Queries("operation", "{operation:.*}")
if globalIsDistErasure { if globalIsDistErasure {
// Top locks // Top locks
@@ -274,16 +297,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}") adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler))) adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler)))
if !globalIsGateway { // Keep obdinfo for backward compatibility with mc
// Keep obdinfo for backward compatibility with mc adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo"). HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler))) // -- Health API --
// -- Health API -- adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo"). HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
HandlerFunc(gz(httpTraceHdrs(adminAPI.BandwidthMonitorHandler)))
}
} }
// If none of the routes match add default error handler routes // If none of the routes match add default error handler routes
+7 -7
View File
@@ -26,15 +26,15 @@ import (
"strings" "strings"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
) )
// getLocalServerProperty - returns madmin.ServerProperties for only the // getLocalServerProperty - returns madmin.ServerProperties for only the
// local endpoints from given list of endpoints // local endpoints from given list of endpoints
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties { func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
var localEndpoints Endpoints
addr := globalLocalNodeName addr := globalLocalNodeName
if r != nil { if r != nil {
addr = r.Host addr = r.Host
@@ -52,7 +52,6 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
if endpoint.IsLocal { if endpoint.IsLocal {
// Only proceed for local endpoints // Only proceed for local endpoints
network[nodeName] = string(madmin.ItemOnline) network[nodeName] = string(madmin.ItemOnline)
localEndpoints = append(localEndpoints, endpoint)
continue continue
} }
_, present := network[nodeName] _, present := network[nodeName]
@@ -120,7 +119,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
config.EnvRootUser: {}, config.EnvRootUser: {},
config.EnvRootPassword: {}, config.EnvRootPassword: {},
config.EnvMinIOSubnetAPIKey: {}, config.EnvMinIOSubnetAPIKey: {},
config.EnvKMSSecretKey: {}, kms.EnvKMSSecretKey: {},
} }
for _, v := range os.Environ() { for _, v := range os.Environ() {
if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") { if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") {
@@ -142,11 +141,12 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
} }
objLayer := newObjectLayerFn() objLayer := newObjectLayerFn()
if objLayer != nil && !globalIsGateway { if objLayer != nil {
// only need Disks information in server mode. storageInfo := objLayer.LocalStorageInfo(GlobalContext)
storageInfo, _ := objLayer.LocalStorageInfo(GlobalContext)
props.State = string(madmin.ItemOnline) props.State = string(madmin.ItemOnline)
props.Disks = storageInfo.Disks props.Disks = storageInfo.Disks
} else {
props.State = string(madmin.ItemOffline)
} }
return props return props
+82 -22
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc. // Copyright (c) 2015-2023 MinIO, Inc.
// //
// This file is part of MinIO Object Storage stack // This file is part of MinIO Object Storage stack
// //
@@ -30,7 +30,7 @@ import (
"github.com/Azure/azure-storage-blob-go/azblob" "github.com/Azure/azure-storage-blob-go/azblob"
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
@@ -132,6 +132,7 @@ const (
ErrReplicationNeedsVersioningError ErrReplicationNeedsVersioningError
ErrReplicationBucketNeedsVersioningError ErrReplicationBucketNeedsVersioningError
ErrReplicationDenyEditError ErrReplicationDenyEditError
ErrRemoteTargetDenyEditError
ErrReplicationNoExistingObjects ErrReplicationNoExistingObjects
ErrObjectRestoreAlreadyInProgress ErrObjectRestoreAlreadyInProgress
ErrNoSuchKey ErrNoSuchKey
@@ -151,7 +152,7 @@ const (
ErrSignatureVersionNotSupported ErrSignatureVersionNotSupported
ErrBucketNotEmpty ErrBucketNotEmpty
ErrAllAccessDisabled ErrAllAccessDisabled
ErrMalformedPolicy ErrPolicyInvalidVersion
ErrMissingFields ErrMissingFields
ErrMissingCredTag ErrMissingCredTag
ErrCredMalformed ErrCredMalformed
@@ -177,7 +178,6 @@ const (
ErrBucketAlreadyOwnedByYou ErrBucketAlreadyOwnedByYou
ErrInvalidDuration ErrInvalidDuration
ErrBucketAlreadyExists ErrBucketAlreadyExists
ErrTooManyBuckets
ErrMetadataTooLarge ErrMetadataTooLarge
ErrUnsupportedMetadata ErrUnsupportedMetadata
ErrMaximumExpires ErrMaximumExpires
@@ -196,6 +196,8 @@ const (
ErrBucketTaggingNotFound ErrBucketTaggingNotFound
ErrObjectLockInvalidHeaders ErrObjectLockInvalidHeaders
ErrInvalidTagDirective ErrInvalidTagDirective
ErrPolicyAlreadyAttached
ErrPolicyNotAttached
// Add new error codes here. // Add new error codes here.
// SSE-S3/SSE-KMS related API errors // SSE-S3/SSE-KMS related API errors
@@ -207,6 +209,8 @@ const (
ErrSSEMultipartEncrypted ErrSSEMultipartEncrypted
ErrSSEEncryptedObject ErrSSEEncryptedObject
ErrInvalidEncryptionParameters ErrInvalidEncryptionParameters
ErrInvalidEncryptionParametersSSEC
ErrInvalidSSECustomerAlgorithm ErrInvalidSSECustomerAlgorithm
ErrInvalidSSECustomerKey ErrInvalidSSECustomerKey
ErrMissingSSECustomerKey ErrMissingSSECustomerKey
@@ -216,6 +220,7 @@ const (
ErrIncompatibleEncryptionMethod ErrIncompatibleEncryptionMethod
ErrKMSNotConfigured ErrKMSNotConfigured
ErrKMSKeyNotFoundException ErrKMSKeyNotFoundException
ErrKMSDefaultKeyAlreadyConfigured
ErrNoAccessKey ErrNoAccessKey
ErrInvalidToken ErrInvalidToken
@@ -266,6 +271,7 @@ const (
ErrAdminGroupNotEmpty ErrAdminGroupNotEmpty
ErrAdminNoSuchJob ErrAdminNoSuchJob
ErrAdminNoSuchPolicy ErrAdminNoSuchPolicy
ErrAdminPolicyChangeAlreadyApplied
ErrAdminInvalidArgument ErrAdminInvalidArgument
ErrAdminInvalidAccessKey ErrAdminInvalidAccessKey
ErrAdminInvalidSecretKey ErrAdminInvalidSecretKey
@@ -277,6 +283,8 @@ const (
ErrAdminConfigDuplicateKeys ErrAdminConfigDuplicateKeys
ErrAdminConfigInvalidIDPType ErrAdminConfigInvalidIDPType
ErrAdminConfigLDAPValidation ErrAdminConfigLDAPValidation
ErrAdminConfigIDPCfgNameAlreadyExists
ErrAdminConfigIDPCfgNameDoesNotExist
ErrAdminCredentialsMismatch ErrAdminCredentialsMismatch
ErrInsecureClientRequest ErrInsecureClientRequest
ErrObjectTampered ErrObjectTampered
@@ -291,6 +299,10 @@ const (
ErrSiteReplicationIAMError ErrSiteReplicationIAMError
ErrSiteReplicationConfigMissing ErrSiteReplicationConfigMissing
// Pool rebalance errors
ErrAdminRebalanceAlreadyStarted
ErrAdminRebalanceNotStarted
// Bucket Quota error codes // Bucket Quota error codes
ErrAdminBucketQuotaExceeded ErrAdminBucketQuotaExceeded
ErrAdminNoSuchQuotaConfiguration ErrAdminNoSuchQuotaConfiguration
@@ -689,11 +701,6 @@ var errorCodes = errorCodeMap{
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.", Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrTooManyBuckets: {
Code: "TooManyBuckets",
Description: "You have attempted to create more buckets than allowed",
HTTPStatusCode: http.StatusBadRequest,
},
ErrBucketNotEmpty: { ErrBucketNotEmpty: {
Code: "BucketNotEmpty", Code: "BucketNotEmpty",
Description: "The bucket you tried to delete is not empty", Description: "The bucket you tried to delete is not empty",
@@ -709,9 +716,9 @@ var errorCodes = errorCodeMap{
Description: "All access to this resource has been disabled.", Description: "All access to this resource has been disabled.",
HTTPStatusCode: http.StatusForbidden, HTTPStatusCode: http.StatusForbidden,
}, },
ErrMalformedPolicy: { ErrPolicyInvalidVersion: {
Code: "MalformedPolicy", Code: "MalformedPolicy",
Description: "Policy has invalid resource.", Description: "The policy must contain a valid version string",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrMissingFields: { ErrMissingFields: {
@@ -914,6 +921,11 @@ var errorCodes = errorCodeMap{
Description: "No matching ExistingsObjects rule enabled", Description: "No matching ExistingsObjects rule enabled",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrRemoteTargetDenyEditError: {
Code: "XMinioAdminRemoteTargetDenyEdit",
Description: "Cannot alter remote target endpoint since this server is in a cluster replication setup. use `mc admin replicate update`",
HTTPStatusCode: http.StatusBadRequest,
},
ErrReplicationDenyEditError: { ErrReplicationDenyEditError: {
Code: "XMinioReplicationDenyEdit", Code: "XMinioReplicationDenyEdit",
Description: "Cannot alter local replication config since this server is in a cluster replication setup", Description: "Cannot alter local replication config since this server is in a cluster replication setup",
@@ -1111,6 +1123,11 @@ var errorCodes = errorCodeMap{
Description: "The encryption parameters are not applicable to this object.", Description: "The encryption parameters are not applicable to this object.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrInvalidEncryptionParametersSSEC: {
Code: "InvalidRequest",
Description: "SSE-C encryption parameters are not supported on replicated bucket.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidSSECustomerAlgorithm: { ErrInvalidSSECustomerAlgorithm: {
Code: "InvalidArgument", Code: "InvalidArgument",
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.", Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
@@ -1156,6 +1173,11 @@ var errorCodes = errorCodeMap{
Description: "Invalid keyId", Description: "Invalid keyId",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrKMSDefaultKeyAlreadyConfigured: {
Code: "KMS.DefaultKeyAlreadyConfiguredException",
Description: "A default encryption already exists and cannot be changed on KMS",
HTTPStatusCode: http.StatusConflict,
},
ErrNoAccessKey: { ErrNoAccessKey: {
Code: "AccessDenied", Code: "AccessDenied",
Description: "No AWSAccessKey was presented", Description: "No AWSAccessKey was presented",
@@ -1245,6 +1267,12 @@ var errorCodes = errorCodeMap{
Description: "The canned policy does not exist.", Description: "The canned policy does not exist.",
HTTPStatusCode: http.StatusNotFound, HTTPStatusCode: http.StatusNotFound,
}, },
ErrAdminPolicyChangeAlreadyApplied: {
Code: "XMinioAdminPolicyChangeAlreadyApplied",
Description: "The specified policy change is already in effect.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminInvalidArgument: { ErrAdminInvalidArgument: {
Code: "XMinioAdminInvalidArgument", Code: "XMinioAdminInvalidArgument",
Description: "Invalid arguments specified.", Description: "Invalid arguments specified.",
@@ -1301,6 +1329,16 @@ var errorCodes = errorCodeMap{
Description: "LDAP Configuration validation failed", Description: "LDAP Configuration validation failed",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrAdminConfigIDPCfgNameAlreadyExists: {
Code: "XMinioAdminConfigIDPCfgNameAlreadyExists",
Description: "An IDP configuration with the given name aleady exists",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminConfigIDPCfgNameDoesNotExist: {
Code: "XMinioAdminConfigIDPCfgNameDoesNotExist",
Description: "No such IDP configuration exists",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminConfigNotificationTargetsFailed: { ErrAdminConfigNotificationTargetsFailed: {
Code: "XMinioAdminNotificationTargetsTestFailed", Code: "XMinioAdminNotificationTargetsTestFailed",
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers", Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
@@ -1365,7 +1403,7 @@ var errorCodes = errorCodeMap{
ErrSiteReplicationPeerResp: { ErrSiteReplicationPeerResp: {
Code: "XMinioSiteReplicationPeerResp", Code: "XMinioSiteReplicationPeerResp",
Description: "Error received when contacting a peer site", Description: "Error received when contacting a peer site",
HTTPStatusCode: http.StatusServiceUnavailable, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrSiteReplicationBackendIssue: { ErrSiteReplicationBackendIssue: {
Code: "XMinioSiteReplicationBackendIssue", Code: "XMinioSiteReplicationBackendIssue",
@@ -1397,6 +1435,16 @@ var errorCodes = errorCodeMap{
Description: "Site not found in site replication configuration", Description: "Site not found in site replication configuration",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrAdminRebalanceAlreadyStarted: {
Code: "XMinioAdminRebalanceAlreadyStarted",
Description: "Pool rebalance is already started",
HTTPStatusCode: http.StatusConflict,
},
ErrAdminRebalanceNotStarted: {
Code: "XMinioAdminRebalanceNotStarted",
Description: "Pool rebalance is not started",
HTTPStatusCode: http.StatusNotFound,
},
ErrMaximumExpires: { ErrMaximumExpires: {
Code: "AuthorizationQueryParametersError", Code: "AuthorizationQueryParametersError",
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds", Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
@@ -1912,6 +1960,16 @@ var errorCodes = errorCodeMap{
Description: "Invalid checksum provided.", Description: "Invalid checksum provided.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrPolicyAlreadyAttached: {
Code: "XMinioPolicyAlreadyAttached",
Description: "The specified policy is already attached.",
HTTPStatusCode: http.StatusConflict,
},
ErrPolicyNotAttached: {
Code: "XMinioPolicyNotAttached",
Description: "The specified policy is not found.",
HTTPStatusCode: http.StatusNotFound,
},
// Add your error structure here. // Add your error structure here.
} }
@@ -1946,6 +2004,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrAdminNoSuchJob apiErr = ErrAdminNoSuchJob
case errNoSuchPolicy: case errNoSuchPolicy:
apiErr = ErrAdminNoSuchPolicy apiErr = ErrAdminNoSuchPolicy
case errNoPolicyToAttachOrDetach:
apiErr = ErrAdminPolicyChangeAlreadyApplied
case errSignatureMismatch: case errSignatureMismatch:
apiErr = ErrSignatureDoesNotMatch apiErr = ErrSignatureDoesNotMatch
case errInvalidRange: case errInvalidRange:
@@ -1965,6 +2025,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
// SSE errors // SSE errors
case errInvalidEncryptionParameters: case errInvalidEncryptionParameters:
apiErr = ErrInvalidEncryptionParameters apiErr = ErrInvalidEncryptionParameters
case errInvalidEncryptionParametersSSEC:
apiErr = ErrInvalidEncryptionParametersSSEC
case crypto.ErrInvalidEncryptionMethod: case crypto.ErrInvalidEncryptionMethod:
apiErr = ErrInvalidEncryptionMethod apiErr = ErrInvalidEncryptionMethod
case crypto.ErrInvalidEncryptionKeyID: case crypto.ErrInvalidEncryptionKeyID:
@@ -1991,6 +2053,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrKMSNotConfigured apiErr = ErrKMSNotConfigured
case errKMSKeyNotFound: case errKMSKeyNotFound:
apiErr = ErrKMSKeyNotFoundException apiErr = ErrKMSKeyNotFoundException
case errKMSDefaultKeyAlreadyConfigured:
apiErr = ErrKMSDefaultKeyAlreadyConfigured
case context.Canceled, context.DeadlineExceeded: case context.Canceled, context.DeadlineExceeded:
apiErr = ErrOperationTimedOut apiErr = ErrOperationTimedOut
@@ -2136,7 +2200,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrTransitionStorageClassNotFoundError apiErr = ErrTransitionStorageClassNotFoundError
case InvalidObjectState: case InvalidObjectState:
apiErr = ErrInvalidObjectState apiErr = ErrInvalidObjectState
case PreConditionFailed:
apiErr = ErrPreconditionFailed
case BucketQuotaExceeded: case BucketQuotaExceeded:
apiErr = ErrAdminBucketQuotaExceeded apiErr = ErrAdminBucketQuotaExceeded
case *event.ErrInvalidEventName: case *event.ErrInvalidEventName:
@@ -2238,15 +2303,10 @@ func toAPIError(ctx context.Context, err error) APIError {
if apiErr.Code == "InternalError" { if apiErr.Code == "InternalError" {
// If we see an internal error try to interpret // If we see an internal error try to interpret
// any underlying errors if possible depending on // any underlying errors if possible depending on
// their internal error types. This code is only // their internal error types.
// useful with gateway implementations.
switch e := err.(type) { switch e := err.(type) {
case batchReplicationJobError: case batchReplicationJobError:
apiErr = APIError{ apiErr = APIError(e)
Code: e.Code,
Description: e.Description,
HTTPStatusCode: e.HTTPStatusCode,
}
case InvalidArgument: case InvalidArgument:
apiErr = APIError{ apiErr = APIError{
Code: "InvalidArgument", Code: "InvalidArgument",
@@ -2309,7 +2369,7 @@ func toAPIError(ctx context.Context, err error) APIError {
Description: e.Message, Description: e.Message,
HTTPStatusCode: e.StatusCode, HTTPStatusCode: e.StatusCode,
} }
if globalIsGateway && strings.Contains(e.Message, "KMS is not configured") { if strings.Contains(e.Message, "KMS is not configured") {
apiErr = APIError{ apiErr = APIError{
Code: "NotImplemented", Code: "NotImplemented",
Description: e.Message, Description: e.Message,
@@ -2333,7 +2393,7 @@ func toAPIError(ctx context.Context, err error) APIError {
Description: e.Error(), Description: e.Error(),
HTTPStatusCode: e.Response().StatusCode, HTTPStatusCode: e.Response().StatusCode,
} }
// Add more Gateway SDKs here if any in future. // Add more other SDK related errors here if any in future.
default: default:
//nolint:gocritic //nolint:gocritic
if errors.Is(err, errMalformedEncoding) { if errors.Is(err, errMalformedEncoding) {
+23 -6
View File
@@ -20,6 +20,7 @@ package cmd
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"encoding/xml"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@@ -64,15 +65,31 @@ func setCommonHeaders(w http.ResponseWriter) {
// Encodes the response headers into XML format. // Encodes the response headers into XML format.
func encodeResponse(response interface{}) []byte { func encodeResponse(response interface{}) []byte {
var bytesBuffer bytes.Buffer var buf bytes.Buffer
bytesBuffer.WriteString(xxml.Header) buf.WriteString(xml.Header)
buf, err := xxml.Marshal(response) if err := xml.NewEncoder(&buf).Encode(response); err != nil {
if err != nil {
logger.LogIf(GlobalContext, err) logger.LogIf(GlobalContext, err)
return nil return nil
} }
bytesBuffer.Write(buf) return buf.Bytes()
return bytesBuffer.Bytes() }
// Use this encodeResponseList() to support control characters
// this function must be used by only ListObjects() for objects
// with control characters, this is a specialized extension
// to support AWS S3 compatible behavior.
//
// Do not use this function for anything other than ListObjects()
// variants, please open a github discussion if you wish to use
// this in other places.
func encodeResponseList(response interface{}) []byte {
var buf bytes.Buffer
buf.WriteString(xxml.Header)
if err := xxml.NewEncoder(&buf).Encode(response); err != nil {
logger.LogIf(GlobalContext, err)
return nil
}
return buf.Bytes()
} }
// Encodes the response headers into JSON format. // Encodes the response headers into JSON format.
+19 -20
View File
@@ -29,6 +29,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/minio/minio/internal/amztime"
"github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/crypto"
"github.com/minio/minio/internal/handlers" "github.com/minio/minio/internal/handlers"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
@@ -38,12 +39,10 @@ import (
) )
const ( const (
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision. maxDeleteList = 1000 // Limit number of objects deleted in a delete call.
maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse. maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
maxDeleteList = 1000 // Limit number of objects deleted in a delete call. maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
) )
// LocationResponse - format for location response. // LocationResponse - format for location response.
@@ -315,12 +314,12 @@ func (s *Metadata) Set(k, v string) {
} }
type xmlKeyEntry struct { type xmlKeyEntry struct {
XMLName xml.Name XMLName xxml.Name
Value string `xml:",chardata"` Value string `xml:",chardata"`
} }
// MarshalXML - StringMap marshals into XML. // MarshalXML - StringMap marshals into XML.
func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error { func (s *Metadata) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
if s == nil { if s == nil {
return nil return nil
} }
@@ -335,7 +334,7 @@ func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
for _, item := range s.Items { for _, item := range s.Items {
if err := e.Encode(xmlKeyEntry{ if err := e.Encode(xmlKeyEntry{
XMLName: xml.Name{Local: item.Key}, XMLName: xxml.Name{Local: item.Key},
Value: item.Value, Value: item.Value,
}); err != nil { }); err != nil {
return err return err
@@ -482,7 +481,7 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
for _, bucket := range buckets { for _, bucket := range buckets {
listbuckets = append(listbuckets, Bucket{ listbuckets = append(listbuckets, Bucket{
Name: bucket.Name, Name: bucket.Name,
CreationDate: bucket.Created.UTC().Format(iso8601TimeFormat), CreationDate: amztime.ISO8601Format(bucket.Created.UTC()),
}) })
} }
@@ -508,7 +507,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
} }
content := ObjectVersion{} content := ObjectVersion{}
content.Key = s3EncodeName(object.Name, encodingType) content.Key = s3EncodeName(object.Name, encodingType)
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat) content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
if object.ETag != "" { if object.ETag != "" {
content.ETag = "\"" + object.ETag + "\"" content.ETag = "\"" + object.ETag + "\""
} }
@@ -566,7 +565,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
continue continue
} }
content.Key = s3EncodeName(object.Name, encodingType) content.Key = s3EncodeName(object.Name, encodingType)
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat) content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
if object.ETag != "" { if object.ETag != "" {
content.ETag = "\"" + object.ETag + "\"" content.ETag = "\"" + object.ETag + "\""
} }
@@ -615,7 +614,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
continue continue
} }
content.Key = s3EncodeName(object.Name, encodingType) content.Key = s3EncodeName(object.Name, encodingType)
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat) content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
if object.ETag != "" { if object.ETag != "" {
content.ETag = "\"" + object.ETag + "\"" content.ETag = "\"" + object.ETag + "\""
} }
@@ -636,7 +635,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
case crypto.SSEC: case crypto.SSEC:
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES) content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
} }
for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) { for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) { if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
// Do not need to send any internal metadata // Do not need to send any internal metadata
// values to client. // values to client.
@@ -678,7 +677,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse { func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
return CopyObjectResponse{ return CopyObjectResponse{
ETag: "\"" + etag + "\"", ETag: "\"" + etag + "\"",
LastModified: lastModified.UTC().Format(iso8601TimeFormat), LastModified: amztime.ISO8601Format(lastModified.UTC()),
} }
} }
@@ -686,7 +685,7 @@ func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectR
func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse { func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
return CopyObjectPartResponse{ return CopyObjectPartResponse{
ETag: "\"" + etag + "\"", ETag: "\"" + etag + "\"",
LastModified: lastModified.UTC().Format(iso8601TimeFormat), LastModified: amztime.ISO8601Format(lastModified.UTC()),
} }
} }
@@ -746,7 +745,7 @@ func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) Lis
newPart.PartNumber = part.PartNumber newPart.PartNumber = part.PartNumber
newPart.ETag = "\"" + part.ETag + "\"" newPart.ETag = "\"" + part.ETag + "\""
newPart.Size = part.Size newPart.Size = part.Size
newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat) newPart.LastModified = amztime.ISO8601Format(part.LastModified.UTC())
newPart.ChecksumCRC32 = part.ChecksumCRC32 newPart.ChecksumCRC32 = part.ChecksumCRC32
newPart.ChecksumCRC32C = part.ChecksumCRC32C newPart.ChecksumCRC32C = part.ChecksumCRC32C
newPart.ChecksumSHA1 = part.ChecksumSHA1 newPart.ChecksumSHA1 = part.ChecksumSHA1
@@ -780,7 +779,7 @@ func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMult
newUpload := Upload{} newUpload := Upload{}
newUpload.UploadID = upload.UploadID newUpload.UploadID = upload.UploadID
newUpload.Key = s3EncodeName(upload.Object, encodingType) newUpload.Key = s3EncodeName(upload.Object, encodingType)
newUpload.Initiated = upload.Initiated.UTC().Format(iso8601TimeFormat) newUpload.Initiated = amztime.ISO8601Format(upload.Initiated.UTC())
listMultipartUploadsResponse.Uploads[index] = newUpload listMultipartUploadsResponse.Uploads[index] = newUpload
} }
return listMultipartUploadsResponse return listMultipartUploadsResponse
@@ -876,7 +875,7 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
// Generate error response. // Generate error response.
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID) w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
encodedErrorResponse := encodeResponse(errorResponse) encodedErrorResponse := encodeResponse(errorResponse)
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML) writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
} }
@@ -894,7 +893,7 @@ func writeErrorResponseString(ctx context.Context, w http.ResponseWriter, err AP
// useful for admin APIs. // useful for admin APIs.
func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) { func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
// Generate error response. // Generate error response.
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), globalDeploymentID) errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
encodedErrorResponse := encodeResponseJSON(errorResponse) encodedErrorResponse := encodeResponseJSON(errorResponse)
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON) writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
} }
+1 -1
View File
@@ -22,11 +22,11 @@ import (
"net" "net"
"net/http" "net/http"
"github.com/gorilla/mux"
"github.com/klauspost/compress/gzhttp" "github.com/klauspost/compress/gzhttp"
"github.com/minio/console/restapi" "github.com/minio/console/restapi"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/wildcard"
"github.com/rs/cors" "github.com/rs/cors"
) )
+233 -224
View File
File diff suppressed because one or more lines are too long
+52 -26
View File
@@ -39,6 +39,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
xjwt "github.com/minio/minio/internal/jwt" xjwt "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
iampolicy "github.com/minio/pkg/iam/policy" iampolicy "github.com/minio/pkg/iam/policy"
) )
@@ -134,7 +135,7 @@ func getRequestAuthType(r *http.Request) authType {
return authTypeUnknown return authTypeUnknown
} }
func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, map[string]interface{}, bool, APIErrorCode) { func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, bool, APIErrorCode) {
var cred auth.Credentials var cred auth.Credentials
var owner bool var owner bool
s3Err := ErrAccessDenied s3Err := ErrAccessDenied
@@ -143,27 +144,24 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
// We only support admin credentials to access admin APIs. // We only support admin credentials to access admin APIs.
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3) cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
if s3Err != ErrNone { if s3Err != ErrNone {
return cred, nil, owner, s3Err return cred, owner, s3Err
} }
// we only support V4 (no presign) with auth body // we only support V4 (no presign) with auth body
s3Err = isReqAuthenticated(ctx, r, region, serviceS3) s3Err = isReqAuthenticated(ctx, r, region, serviceS3)
} }
if s3Err != ErrNone { if s3Err != ErrNone {
reqInfo := (&logger.ReqInfo{}).AppendTags("requestHeaders", dumpRequest(r)) return cred, owner, s3Err
ctx := logger.SetReqInfo(ctx, reqInfo)
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
return cred, nil, owner, s3Err
} }
return cred, cred.Claims, owner, ErrNone return cred, owner, ErrNone
} }
// checkAdminRequestAuth checks for authentication and authorization for the incoming // checkAdminRequestAuth checks for authentication and authorization for the incoming
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests // request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
// are automatically rejected. // are automatically rejected.
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) { func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region) cred, owner, s3Err := validateAdminSignature(ctx, r, region)
if s3Err != ErrNone { if s3Err != ErrNone {
return cred, s3Err return cred, s3Err
} }
@@ -171,9 +169,9 @@ func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolic
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.Action(action), Action: iampolicy.Action(action),
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
Claims: claims, Claims: cred.Claims,
}) { }) {
// Request is allowed return the appropriate access key. // Request is allowed return the appropriate access key.
return cred, ErrNone return cred, ErrNone
@@ -302,6 +300,17 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac
return s3Err return s3Err
} }
// checkRequestAuthTypeWithVID is similar to checkRequestAuthType
// passes versionID additionally.
func checkRequestAuthTypeWithVID(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName, versionID string) (s3Err APIErrorCode) {
logger.GetReqInfo(ctx).BucketName = bucketName
logger.GetReqInfo(ctx).ObjectName = objectName
logger.GetReqInfo(ctx).VersionID = versionID
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action)
return s3Err
}
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) { func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
if logger.GetReqInfo(ctx) == nil { 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.Minio)
@@ -373,14 +382,16 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
region := reqInfo.Region region := reqInfo.Region
bucket := reqInfo.BucketName bucket := reqInfo.BucketName
object := reqInfo.ObjectName object := reqInfo.ObjectName
versionID := reqInfo.VersionID
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" { if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
// Anonymous checks are not meant for ListAllBuckets action // Anonymous checks are not meant for ListAllBuckets action
if globalPolicySys.IsAllowed(policy.Args{ if globalPolicySys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: action, Action: action,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, region, "", nil), ConditionValues: getConditionValues(r, region, auth.AnonymousCredentials),
IsOwner: false, IsOwner: false,
ObjectName: object, ObjectName: object,
}) { }) {
@@ -393,9 +404,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
// verify as a fallback. // verify as a fallback.
if globalPolicySys.IsAllowed(policy.Args{ if globalPolicySys.IsAllowed(policy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, region, "", nil), ConditionValues: getConditionValues(r, region, auth.AnonymousCredentials),
IsOwner: false, IsOwner: false,
ObjectName: object, ObjectName: object,
}) { }) {
@@ -406,13 +418,27 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
return ErrAccessDenied return ErrAccessDenied
} }
if action == policy.DeleteObjectAction && versionID != "" {
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: iampolicy.Action(policy.DeleteObjectVersionAction),
BucketName: bucket,
ConditionValues: getConditionValues(r, "", cred),
ObjectName: object,
IsOwner: owner,
Claims: cred.Claims,
DenyOnly: true,
}) { // Request is not allowed if Deny action on DeleteObjectVersionAction
return ErrAccessDenied
}
}
if globalIAMSys.IsAllowed(iampolicy.Args{ if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.Action(action), Action: iampolicy.Action(action),
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ConditionValues: getConditionValues(r, "", cred),
ObjectName: object, ObjectName: object,
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -429,7 +455,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListBucketAction, Action: iampolicy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ConditionValues: getConditionValues(r, "", cred),
ObjectName: object, ObjectName: object,
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -544,7 +570,7 @@ func isSupportedS3AuthType(aType authType) bool {
func setAuthHandler(h http.Handler) http.Handler { func setAuthHandler(h http.Handler) http.Handler {
// handler for validating incoming authorization headers. // handler for validating incoming authorization headers.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tc, ok := r.Context().Value(contextTraceReqKey).(*traceCtxt) tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
aType := getRequestAuthType(r) aType := getRequestAuthType(r)
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned { if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
@@ -552,8 +578,8 @@ func setAuthHandler(h http.Handler) http.Handler {
amzDate, errCode := parseAmzDateHeader(r) amzDate, errCode := parseAmzDateHeader(r)
if errCode != ErrNone { if errCode != ErrNone {
if ok { if ok {
tc.funcName = "handler.Auth" tc.FuncName = "handler.Auth"
tc.responseRecorder.LogErrBody = true tc.ResponseRecorder.LogErrBody = true
} }
// All our internal APIs are sensitive towards Date // All our internal APIs are sensitive towards Date
@@ -568,8 +594,8 @@ func setAuthHandler(h http.Handler) http.Handler {
curTime := UTCNow() curTime := UTCNow()
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime { if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
if ok { if ok {
tc.funcName = "handler.Auth" tc.FuncName = "handler.Auth"
tc.responseRecorder.LogErrBody = true tc.ResponseRecorder.LogErrBody = true
} }
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL) writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL)
@@ -583,8 +609,8 @@ func setAuthHandler(h http.Handler) http.Handler {
} }
if ok { if ok {
tc.funcName = "handler.Auth" tc.FuncName = "handler.Auth"
tc.responseRecorder.LogErrBody = true tc.ResponseRecorder.LogErrBody = true
} }
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL) writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL)
@@ -624,9 +650,9 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
return ErrAccessDenied return ErrAccessDenied
} }
conditions := getConditionValues(r, "", cred.AccessKey, cred.Claims) conditions := getConditionValues(r, "", cred)
conditions["object-lock-mode"] = []string{string(retMode)} conditions["object-lock-mode"] = []string{string(retMode)}
conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)} conditions["object-lock-retain-until-date"] = []string{retDate.UTC().Format(time.RFC3339)}
if retDays > 0 { if retDays > 0 {
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)} conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
} }
@@ -698,7 +724,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
Groups: cred.Groups, Groups: cred.Groups,
Action: policy.Action(action), Action: policy.Action(action),
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", "", nil), ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
IsOwner: false, IsOwner: false,
ObjectName: objectName, ObjectName: objectName,
}) { }) {
@@ -712,7 +738,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
Groups: cred.Groups, Groups: cred.Groups,
Action: action, Action: action,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ConditionValues: getConditionValues(r, "", cred),
ObjectName: objectName, ObjectName: objectName,
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
+1 -1
View File
@@ -492,7 +492,7 @@ func TestValidateAdminSignature(t *testing.T) {
if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil { if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
t.Fatalf("Unable to inititalized new signed http request %s", err) t.Fatalf("Unable to inititalized new signed http request %s", err)
} }
_, _, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion) _, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)
if s3Error != testCase.ErrCode { if s3Error != testCase.ErrCode {
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error) t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error)
} }
+58 -12
View File
@@ -19,10 +19,14 @@ package cmd
import ( import (
"context" "context"
"fmt"
"runtime" "runtime"
"strconv"
"time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env"
) )
// healTask represents what to heal along with options // healTask represents what to heal along with options
@@ -54,16 +58,46 @@ type healRoutine struct {
func activeListeners() int { func activeListeners() int {
// Bucket notification and http trace are not costly, it is okay to ignore them // Bucket notification and http trace are not costly, it is okay to ignore them
// while counting the number of concurrent connections // while counting the number of concurrent connections
return int(globalHTTPListen.NumSubscribers(pubsub.MaskAll)) + int(globalTrace.NumSubscribers(pubsub.MaskAll)) return int(globalHTTPListen.Subscribers()) + int(globalTrace.Subscribers())
}
func waitForLowIO(maxIO int, maxWait time.Duration, currentIO func() int) {
// No need to wait run at full speed.
if maxIO <= 0 {
return
}
const waitTick = 100 * time.Millisecond
tmpMaxWait := maxWait
for currentIO() >= maxIO {
if tmpMaxWait > 0 {
if tmpMaxWait < waitTick {
time.Sleep(tmpMaxWait)
} else {
time.Sleep(waitTick)
}
tmpMaxWait -= waitTick
}
if tmpMaxWait <= 0 {
return
}
}
}
func currentHTTPIO() int {
httpServer := newHTTPServerFn()
if httpServer == nil {
return 0
}
return httpServer.GetRequestCount() - activeListeners()
} }
func waitForLowHTTPReq() { func waitForLowHTTPReq() {
var currentIO func() int maxIO, maxWait, _ := globalHealConfig.Clone()
if httpServer := newHTTPServerFn(); httpServer != nil { waitForLowIO(maxIO, maxWait, currentHTTPIO)
currentIO = httpServer.GetRequestCount
}
globalHealConfig.Wait(currentIO, activeListeners)
} }
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) { func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
@@ -89,8 +123,7 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
var err error var err error
switch task.bucket { switch task.bucket {
case nopHeal: case nopHeal:
task.respCh <- healResult{err: errSkipFile} err = errSkipFile
continue
case SlashSeparator: case SlashSeparator:
res, err = healDiskFormat(ctx, objAPI, task.opts) res, err = healDiskFormat(ctx, objAPI, task.opts)
default: default:
@@ -101,7 +134,10 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
} }
} }
task.respCh <- healResult{result: res, err: err} if task.respCh != nil {
task.respCh <- healResult{result: res, err: err}
}
case <-ctx.Done(): case <-ctx.Done():
return return
} }
@@ -110,9 +146,19 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
func newHealRoutine() *healRoutine { func newHealRoutine() *healRoutine {
workers := runtime.GOMAXPROCS(0) / 2 workers := runtime.GOMAXPROCS(0) / 2
if envHealWorkers := env.Get("_MINIO_HEAL_WORKERS", ""); envHealWorkers != "" {
if numHealers, err := strconv.Atoi(envHealWorkers); err != nil {
logger.LogIf(context.Background(), fmt.Errorf("invalid _MINIO_HEAL_WORKERS value: %w", err))
} else {
workers = numHealers
}
}
if workers == 0 { if workers == 0 {
workers = 4 workers = 4
} }
return &healRoutine{ return &healRoutine{
tasks: make(chan healTask), tasks: make(chan healTask),
workers: workers, workers: workers,
+49 -18
View File
@@ -29,7 +29,7 @@ import (
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
) )
@@ -79,6 +79,10 @@ type healingTracker struct {
// Filled during heal. // Filled during heal.
HealedBuckets []string HealedBuckets []string
// ID of the current healing operation
HealID string
// Add future tracking capabilities // Add future tracking capabilities
// Be sure that they are included in toHealingDisk // Be sure that they are included in toHealingDisk
} }
@@ -112,11 +116,12 @@ func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker,
} }
// newHealingTracker will create a new healing tracker for the disk. // newHealingTracker will create a new healing tracker for the disk.
func newHealingTracker(disk StorageAPI) *healingTracker { func newHealingTracker(disk StorageAPI, healID string) *healingTracker {
diskID, _ := disk.GetDiskID() diskID, _ := disk.GetDiskID()
h := healingTracker{ h := healingTracker{
disk: disk, disk: disk,
ID: diskID, ID: diskID,
HealID: healID,
Path: disk.String(), Path: disk.String(),
Endpoint: disk.Endpoint().String(), Endpoint: disk.Endpoint().String(),
Started: time.Now().UTC(), Started: time.Now().UTC(),
@@ -227,6 +232,7 @@ func (h *healingTracker) printTo(writer io.Writer) {
func (h *healingTracker) toHealingDisk() madmin.HealingDisk { func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
return madmin.HealingDisk{ return madmin.HealingDisk{
ID: h.ID, ID: h.ID,
HealID: h.HealID,
Endpoint: h.Endpoint, Endpoint: h.Endpoint,
PoolIndex: h.PoolIndex, PoolIndex: h.PoolIndex,
SetIndex: h.SetIndex, SetIndex: h.SetIndex,
@@ -286,13 +292,11 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second) var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error { func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
disk, format, err := connectEndpoint(endpoint) disk, format, err := connectEndpoint(endpoint)
if err != nil { if err != nil {
return fmt.Errorf("Error: %w, %s", err, endpoint) return fmt.Errorf("Error: %w, %s", err, endpoint)
} }
defer disk.Close()
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint()) poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
if poolIdx < 0 { if poolIdx < 0 {
return fmt.Errorf("unexpected pool index (%d) found in %s", poolIdx, disk.Endpoint()) return fmt.Errorf("unexpected pool index (%d) found in %s", poolIdx, disk.Endpoint())
@@ -310,13 +314,27 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
} }
// Prevent parallel erasure set healing // Prevent parallel erasure set healing
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%s/%d/%d", endpoint, poolIdx, setIdx)) locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%d/%d", poolIdx, setIdx))
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout) lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
if err != nil { if err != nil {
return err return err
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer locker.Unlock(lkctx.Cancel) defer locker.Unlock(lkctx)
// Load healing tracker in this disk
tracker, err := loadHealingTracker(ctx, disk)
if err != nil {
// A healing track can be not found when another disk in the same
// erasure set and same healing-id successfully finished healing.
if err == errFileNotFound {
return nil
}
logger.LogIf(ctx, fmt.Errorf("Unable to load a healing tracker on '%s': %w", disk, err))
tracker = newHealingTracker(disk, mustGetUUID())
}
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
buckets, _ := z.ListBuckets(ctx, BucketOptions{}) buckets, _ := z.ListBuckets(ctx, BucketOptions{})
// Buckets data are dispersed in multiple zones/sets, make // Buckets data are dispersed in multiple zones/sets, make
@@ -340,15 +358,6 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1)) logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
} }
// Load healing tracker in this disk
tracker, err := loadHealingTracker(ctx, disk)
if err != nil {
// So someone changed the drives underneath, healing tracker missing.
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', drive was swapped again on %s pool: %w",
disk, humanize.Ordinal(poolIdx+1), err))
tracker = newHealingTracker(disk)
}
// Load bucket totals // Load bucket totals
cache := dataUsageCache{} cache := dataUsageCache{}
if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil { if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil {
@@ -374,12 +383,33 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
logger.Info("Healing drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed) logger.Info("Healing drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
} }
if len(tracker.QueuedBuckets) > 0 {
return fmt.Errorf("not all buckets were healed: %v", tracker.QueuedBuckets)
}
if serverDebugLog { if serverDebugLog {
tracker.printTo(os.Stdout) tracker.printTo(os.Stdout)
logger.Info("\n") logger.Info("\n")
} }
logger.LogIf(ctx, tracker.delete(ctx)) if tracker.HealID == "" { // HealID is empty only before Feb 2023
logger.LogIf(ctx, tracker.delete(ctx))
return nil
}
// Remove .healing.bin from all disks with similar heal-id
for _, disk := range z.serverPools[poolIdx].sets[setIdx].getDisks() {
t, err := loadHealingTracker(ctx, disk)
if err != nil {
if err != errFileNotFound {
logger.LogIf(ctx, err)
}
continue
}
if t.HealID == tracker.HealID {
t.delete(ctx)
}
}
return nil return nil
} }
@@ -415,9 +445,10 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
for _, disk := range healDisks { for _, disk := range healDisks {
go func(disk Endpoint) { go func(disk Endpoint) {
globalBackgroundHealState.markDiskForHealing(disk) globalBackgroundHealState.setDiskHealingStatus(disk, true)
err := healFreshDisk(ctx, z, disk) err := healFreshDisk(ctx, z, disk)
if err != nil { if err != nil {
globalBackgroundHealState.setDiskHealingStatus(disk, false)
printEndpointError(disk, err, false) printEndpointError(disk, err, false)
return return
} }
+30 -4
View File
@@ -182,6 +182,12 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
return return
} }
} }
case "HealID":
z.HealID, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "HealID")
return
}
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@@ -195,9 +201,9 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) { func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 22 // map header, size 23
// write "ID" // write "ID"
err = en.Append(0xde, 0x0, 0x16, 0xa2, 0x49, 0x44) err = en.Append(0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
if err != nil { if err != nil {
return return
} }
@@ -430,15 +436,25 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
return return
} }
} }
// write "HealID"
err = en.Append(0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
if err != nil {
return
}
err = en.WriteString(z.HealID)
if err != nil {
err = msgp.WrapError(err, "HealID")
return
}
return return
} }
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) { func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 22 // map header, size 23
// string "ID" // string "ID"
o = append(o, 0xde, 0x0, 0x16, 0xa2, 0x49, 0x44) o = append(o, 0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
o = msgp.AppendString(o, z.ID) o = msgp.AppendString(o, z.ID)
// string "PoolIndex" // string "PoolIndex"
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78) o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
@@ -509,6 +525,9 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
for za0002 := range z.HealedBuckets { for za0002 := range z.HealedBuckets {
o = msgp.AppendString(o, z.HealedBuckets[za0002]) o = msgp.AppendString(o, z.HealedBuckets[za0002])
} }
// string "HealID"
o = append(o, 0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
o = msgp.AppendString(o, z.HealID)
return return
} }
@@ -688,6 +707,12 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
return return
} }
} }
case "HealID":
z.HealID, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "HealID")
return
}
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@@ -710,5 +735,6 @@ func (z *healingTracker) Msgsize() (s int) {
for za0002 := range z.HealedBuckets { for za0002 := range z.HealedBuckets {
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002]) s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
} }
s += 7 + msgp.StringPrefixSize + len(z.HealID)
return return
} }
+86 -47
View File
@@ -28,20 +28,24 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/lithammer/shortuuid/v4" "github.com/lithammer/shortuuid/v4"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
miniogo "github.com/minio/minio-go/v7" miniogo "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/workers"
"github.com/minio/pkg/console" "github.com/minio/pkg/console"
"github.com/minio/pkg/env"
iampolicy "github.com/minio/pkg/iam/policy" iampolicy "github.com/minio/pkg/iam/policy"
"github.com/minio/pkg/wildcard" "github.com/minio/pkg/wildcard"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@@ -356,6 +360,8 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
// batchJobInfo current batch replication information // batchJobInfo current batch replication information
type batchJobInfo struct { type batchJobInfo struct {
mu sync.RWMutex `json:"-" msg:"-"`
Version int `json:"-" msg:"v"` Version int `json:"-" msg:"v"`
JobID string `json:"jobID" msg:"jid"` JobID string `json:"jobID" msg:"jid"`
JobType string `json:"jobType" msg:"jt"` JobType string `json:"jobType" msg:"jt"`
@@ -425,6 +431,9 @@ func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobR
return fmt.Errorf("batchRepl: unknown version: %d", binary.LittleEndian.Uint16(data[2:4])) return fmt.Errorf("batchRepl: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
} }
ri.mu.Lock()
defer ri.mu.Unlock()
// OK, parse data. // OK, parse data.
if _, err = ri.UnmarshalMsg(data[4:]); err != nil { if _, err = ri.UnmarshalMsg(data[4:]); err != nil {
return err return err
@@ -439,8 +448,11 @@ func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobR
return nil return nil
} }
func (ri batchJobInfo) clone() batchJobInfo { func (ri *batchJobInfo) clone() *batchJobInfo {
return batchJobInfo{ ri.mu.RLock()
defer ri.mu.RUnlock()
return &batchJobInfo{
Version: ri.Version, Version: ri.Version,
JobID: ri.JobID, JobID: ri.JobID,
JobType: ri.JobType, JobType: ri.JobType,
@@ -458,21 +470,6 @@ func (ri batchJobInfo) clone() batchJobInfo {
} }
} }
func (ri batchJobInfo) save(ctx context.Context, api ObjectLayer, jobLocation string) error {
data := make([]byte, 4, ri.Msgsize()+4)
// Initialize the header.
binary.LittleEndian.PutUint16(data[0:2], batchReplFormat)
binary.LittleEndian.PutUint16(data[2:4], batchReplVersion)
buf, err := ri.MarshalMsg(data)
if err != nil {
return err
}
return saveConfig(ctx, api, pathJoin(jobLocation, batchReplName), buf)
}
func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) { func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) {
if ri == nil { if ri == nil {
return return
@@ -499,14 +496,28 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
return errInvalidArgument return errInvalidArgument
} }
now := UTCNow() now := UTCNow()
ri.mu.Lock()
if now.Sub(ri.LastUpdate) >= duration { if now.Sub(ri.LastUpdate) >= duration {
if serverDebugLog { if serverDebugLog {
console.Debugf("batchReplicate: persisting batchReplication info on drive: threshold:%s, batchRepl:%#v\n", now.Sub(ri.LastUpdate), ri) console.Debugf("batchReplicate: persisting batchReplication info on drive: threshold:%s, batchRepl:%#v\n", now.Sub(ri.LastUpdate), ri)
} }
ri.LastUpdate = now ri.LastUpdate = now
ri.Version = batchReplVersionV1 ri.Version = batchReplVersionV1
return ri.save(ctx, api, jobLocation)
data := make([]byte, 4, ri.Msgsize()+4)
// Initialize the header.
binary.LittleEndian.PutUint16(data[0:2], batchReplFormat)
binary.LittleEndian.PutUint16(data[2:4], batchReplVersion)
buf, err := ri.MarshalMsg(data)
ri.mu.Unlock()
if err != nil {
return err
}
return saveConfig(ctx, api, pathJoin(jobLocation, batchReplName), buf)
} }
ri.mu.Unlock()
return nil return nil
} }
@@ -514,6 +525,10 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
if ri == nil { if ri == nil {
return return
} }
ri.mu.Lock()
defer ri.mu.Unlock()
ri.Bucket = bucket ri.Bucket = bucket
ri.Object = info.Name ri.Object = info.Name
ri.countItem(info.Size, info.DeleteMarker, failed) ri.countItem(info.Size, info.DeleteMarker, failed)
@@ -529,7 +544,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
if err := ri.load(ctx, api, job); err != nil { if err := ri.load(ctx, api, job); err != nil {
return err return err
} }
globalBatchJobsMetrics.save(job.ID, ri.clone()) globalBatchJobsMetrics.save(job.ID, ri)
lastObject := ri.Object lastObject := ri.Object
delay := job.Replicate.Flags.Retry.Delay delay := job.Replicate.Flags.Retry.Delay
@@ -618,10 +633,24 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
if err != nil { if err != nil {
return err return err
} }
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
if err != nil {
return err
}
wk, err := workers.New(workerSize)
if err != nil {
// invalid worker size.
return err
}
retryAttempts := ri.RetryAttempts retryAttempts := ri.RetryAttempts
retry := false retry := false
for attempts := 1; attempts <= retryAttempts; attempts++ { for attempts := 1; attempts <= retryAttempts; attempts++ {
attempts := attempts
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
results := make(chan ObjectInfo, 100) results := make(chan ObjectInfo, 100)
@@ -635,31 +664,43 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
} }
for result := range results { for result := range results {
stopFn := globalBatchJobsMetrics.trace(batchReplicationMetricObject, job.ID, attempts, result) result := result
success := true wk.Take()
if err := r.ReplicateToTarget(ctx, api, c, result, retry); err != nil { go func() {
if isErrVersionNotFound(err) || isErrObjectNotFound(err) { defer wk.Give()
// object must be deleted concurrently, allow
// these failures but do not count them stopFn := globalBatchJobsMetrics.trace(batchReplicationMetricObject, job.ID, attempts, result)
continue success := true
if err := r.ReplicateToTarget(ctx, api, c, result, retry); err != nil {
if miniogo.ToErrorResponse(err).Code == "PreconditionFailed" {
// pre-condition failed means we already have the object copied over.
return
}
// object must be deleted concurrently, allow these failures but do not count them
if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
return
}
stopFn(err)
logger.LogIf(ctx, err)
success = false
} else {
stopFn(nil)
} }
stopFn(err) ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
logger.LogIf(ctx, err) globalBatchJobsMetrics.save(job.ID, ri)
success = false // persist in-memory state to disk after every 10secs.
} else { logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job.Location))
stopFn(nil) }()
}
ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
globalBatchJobsMetrics.save(job.ID, ri.clone())
// persist in-memory state to disk after every 10secs.
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job.Location))
} }
wk.Wait()
ri.RetryAttempts = attempts ri.RetryAttempts = attempts
ri.Complete = ri.ObjectsFailed == 0 ri.Complete = ri.ObjectsFailed == 0
ri.Failed = ri.ObjectsFailed > 0 ri.Failed = ri.ObjectsFailed > 0
globalBatchJobsMetrics.save(job.ID, ri.clone()) globalBatchJobsMetrics.save(job.ID, ri)
// persist in-memory state to disk.
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job.Location))
buf, _ := json.Marshal(ri) buf, _ := json.Marshal(ri)
if err := r.Notify(ctx, bytes.NewReader(buf)); err != nil { if err := r.Notify(ctx, bytes.NewReader(buf)); err != nil {
@@ -697,7 +738,7 @@ func (e batchReplicationJobError) Error() string {
} }
// Validate validates the job definition input // Validate validates the job definition input
func (r *BatchJobReplicateV1) Validate(ctx context.Context, o ObjectLayer) error { func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest, o ObjectLayer) error {
if r == nil { if r == nil {
return nil return nil
} }
@@ -773,6 +814,7 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, o ObjectLayer) error
if err != nil { if err != nil {
return err return err
} }
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
vcfg, err := c.GetBucketVersioning(ctx, r.Target.Bucket) vcfg, err := c.GetBucketVersioning(ctx, r.Target.Bucket)
if err != nil { if err != nil {
@@ -811,7 +853,7 @@ func (j BatchJobRequest) Type() madmin.BatchJobType {
// persisting the job request // persisting the job request
func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error { func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error {
if j.Replicate != nil { if j.Replicate != nil {
return j.Replicate.Validate(ctx, o) return j.Replicate.Validate(ctx, j, o)
} }
return errInvalidArgument return errInvalidArgument
} }
@@ -1029,7 +1071,6 @@ type BatchJobPool struct {
mu sync.Mutex mu sync.Mutex
jobCh chan *BatchJobRequest jobCh chan *BatchJobRequest
workerKillCh chan struct{} workerKillCh chan struct{}
workerWg sync.WaitGroup
workerSize int workerSize int
} }
@@ -1074,7 +1115,6 @@ func (j *BatchJobPool) AddWorker() {
if j == nil { if j == nil {
return return
} }
defer j.workerWg.Done()
for { for {
select { select {
case <-j.ctx.Done(): case <-j.ctx.Done():
@@ -1110,7 +1150,6 @@ func (j *BatchJobPool) ResizeWorkers(n int) {
for j.workerSize < n { for j.workerSize < n {
j.workerSize++ j.workerSize++
j.workerWg.Add(1)
go j.AddWorker() go j.AddWorker()
} }
for j.workerSize > n { for j.workerSize > n {
@@ -1138,11 +1177,11 @@ func (j *BatchJobPool) queueJob(req *BatchJobRequest) error {
//msgp:ignore batchJobMetrics //msgp:ignore batchJobMetrics
type batchJobMetrics struct { type batchJobMetrics struct {
sync.RWMutex sync.RWMutex
metrics map[string]batchJobInfo metrics map[string]*batchJobInfo
} }
var globalBatchJobsMetrics = batchJobMetrics{ var globalBatchJobsMetrics = batchJobMetrics{
metrics: make(map[string]batchJobInfo), metrics: make(map[string]*batchJobInfo),
} }
//msgp:ignore batchReplicationMetric //msgp:ignore batchReplicationMetric
@@ -1210,11 +1249,11 @@ func (m *batchJobMetrics) delete(jobID string) {
delete(m.metrics, jobID) delete(m.metrics, jobID)
} }
func (m *batchJobMetrics) save(jobID string, ri batchJobInfo) { func (m *batchJobMetrics) save(jobID string, ri *batchJobInfo) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
m.metrics[jobID] = ri m.metrics[jobID] = ri.clone()
} }
func (m *batchJobMetrics) trace(d batchReplicationMetric, job string, attempts int, info ObjectInfo) func(err error) { func (m *batchJobMetrics) trace(d batchReplicationMetric, job string, attempts int, info ObjectInfo) func(err error) {
+3 -3
View File
@@ -35,7 +35,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
// obtains random bucket name. // obtains random bucket name.
bucket := getRandomBucketName() bucket := getRandomBucketName()
// create bucket. // create bucket.
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{}) err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -76,7 +76,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
object := getRandomObjectName() object := getRandomObjectName()
// create bucket. // create bucket.
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{}) err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -196,7 +196,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
// obtains random bucket name. // obtains random bucket name.
bucket := getRandomBucketName() bucket := getRandomBucketName()
// create bucket. // create bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{}) err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
+22 -20
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc. // Copyright (c) 2015-2022 MinIO, Inc.
// //
// This file is part of MinIO Object Storage stack // This file is part of MinIO Object Storage stack
// //
@@ -25,14 +25,13 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"reflect" "reflect"
"runtime"
"time" "time"
"github.com/gorilla/mux"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/rest" "github.com/minio/minio/internal/rest"
"github.com/minio/mux"
"github.com/minio/pkg/env" "github.com/minio/pkg/env"
) )
@@ -53,23 +52,22 @@ type bootstrapRESTServer struct{}
// ServerSystemConfig - captures information about server configuration. // ServerSystemConfig - captures information about server configuration.
type ServerSystemConfig struct { type ServerSystemConfig struct {
MinioPlatform string
MinioEndpoints EndpointServerPools MinioEndpoints EndpointServerPools
MinioEnv map[string]string MinioEnv map[string]string
} }
// Diff - returns error on first difference found in two configs. // Diff - returns error on first difference found in two configs.
func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error { func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
if s1.MinioPlatform != s2.MinioPlatform {
return fmt.Errorf("Expected platform '%s', found to be running '%s'",
s1.MinioPlatform, s2.MinioPlatform)
}
if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() { if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(), return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
s2.MinioEndpoints.NEndpoints()) s2.MinioEndpoints.NEndpoints())
} }
for i, ep := range s1.MinioEndpoints { for i, ep := range s1.MinioEndpoints {
if ep.CmdLine != s2.MinioEndpoints[i].CmdLine {
return fmt.Errorf("Expected command line argument %s, seen %s", ep.CmdLine,
s2.MinioEndpoints[i].CmdLine)
}
if ep.SetCount != s2.MinioEndpoints[i].SetCount { if ep.SetCount != s2.MinioEndpoints[i].SetCount {
return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount, return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
s2.MinioEndpoints[i].SetCount) s2.MinioEndpoints[i].SetCount)
@@ -78,11 +76,9 @@ func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet, return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
s2.MinioEndpoints[i].DrivesPerSet) s2.MinioEndpoints[i].DrivesPerSet)
} }
for j, endpoint := range ep.Endpoints { if ep.Platform != s2.MinioEndpoints[i].Platform {
if endpoint.String() != s2.MinioEndpoints[i].Endpoints[j].String() { return fmt.Errorf("Expected platform '%s', found to be on '%s'",
return fmt.Errorf("Expected endpoint %s, seen %s", endpoint, ep.Platform, s2.MinioEndpoints[i].Platform)
s2.MinioEndpoints[i].Endpoints[j])
}
} }
} }
if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) { if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
@@ -125,7 +121,6 @@ func getServerSystemCfg() ServerSystemConfig {
envValues[envK] = env.Get(envK, "") envValues[envK] = env.Get(envK, "")
} }
return ServerSystemConfig{ return ServerSystemConfig{
MinioPlatform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
MinioEndpoints: globalEndpoints, MinioEndpoints: globalEndpoints,
MinioEnv: envValues, MinioEnv: envValues,
} }
@@ -200,15 +195,18 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
srcCfg := getServerSystemCfg() srcCfg := getServerSystemCfg()
clnts := newBootstrapRESTClients(endpointServerPools) clnts := newBootstrapRESTClients(endpointServerPools)
var onlineServers int var onlineServers int
var offlineEndpoints []string var offlineEndpoints []error
var incorrectConfigs []error
var retries int var retries int
for onlineServers < len(clnts)/2 { for onlineServers < len(clnts)/2 {
for _, clnt := range clnts { for _, clnt := range clnts {
if err := clnt.Verify(ctx, srcCfg); err != nil { if err := clnt.Verify(ctx, srcCfg); err != nil {
if !isNetworkError(err) { if !isNetworkError(err) {
logger.LogIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err)) logger.LogOnceIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err), clnt.String())
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err))
} else {
offlineEndpoints = append(offlineEndpoints, fmt.Errorf("%s is unreachable: %w", clnt.String(), err))
} }
offlineEndpoints = append(offlineEndpoints, clnt.String())
continue continue
} }
onlineServers++ onlineServers++
@@ -221,15 +219,19 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
// 100% CPU when half the endpoints are offline. // 100% CPU when half the endpoints are offline.
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
retries++ retries++
// after 5 retries start logging that servers are not reachable yet // after 20 retries start logging that servers are not reachable yet
if retries >= 5 { if retries >= 20 {
logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers to be online for bootstrap check", len(clnts)/2)) logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers with valid configuration to be online", len(clnts)/2))
if len(offlineEndpoints) > 0 { if len(offlineEndpoints) > 0 {
logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints)) logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
} }
if len(incorrectConfigs) > 0 {
logger.Info(fmt.Sprintf("Following servers have mismatching configuration %s", incorrectConfigs))
}
retries = 0 // reset to log again after 5 retries. retries = 0 // reset to log again after 5 retries.
} }
offlineEndpoints = nil offlineEndpoints = nil
incorrectConfigs = nil
} }
} }
return nil return nil
+9 -19
View File
@@ -25,11 +25,11 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/minio/kes-go"
"github.com/minio/kes" "github.com/minio/madmin-go/v2"
"github.com/minio/madmin-go"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
@@ -51,11 +51,6 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
return return
} }
if !objAPI.IsEncryptionSupported() {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@@ -119,15 +114,12 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
// We encode the xml bytes as base64 to ensure there are no encoding // We encode the xml bytes as base64 to ensure there are no encoding
// errors. // errors.
cfgStr := base64.StdEncoding.EncodeToString(configData) cfgStr := base64.StdEncoding.EncodeToString(configData)
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{ logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypeSSEConfig, Type: madmin.SRBucketMetaTypeSSEConfig,
Bucket: bucket, Bucket: bucket,
SSEConfig: &cfgStr, SSEConfig: &cfgStr,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }
@@ -209,16 +201,14 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
// Call site replication hook. // Call site replication hook.
// logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypeSSEConfig, Type: madmin.SRBucketMetaTypeSSEConfig,
Bucket: bucket, Bucket: bucket,
SSEConfig: nil, SSEConfig: nil,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
writeSuccessNoContent(w) writeSuccessNoContent(w)
} }
-9
View File
@@ -34,15 +34,6 @@ func NewBucketSSEConfigSys() *BucketSSEConfigSys {
// Get - gets bucket encryption config for the given bucket. // Get - gets bucket encryption config for the given bucket.
func (sys *BucketSSEConfigSys) Get(bucket string) (*sse.BucketSSEConfig, error) { func (sys *BucketSSEConfigSys) Get(bucket string) (*sse.BucketSSEConfig, error) {
if globalIsGateway {
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
return nil, BucketSSEConfigNotFound{Bucket: bucket}
}
sseCfg, _, err := globalBucketMetadataSys.GetSSEConfig(bucket) sseCfg, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
return sseCfg, err return sseCfg, err
} }
+87 -99
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc. // Copyright (c) 2015-2022 MinIO, Inc.
// //
// This file is part of MinIO Object Storage stack // This file is part of MinIO Object Storage stack
// //
@@ -34,11 +34,12 @@ import (
"sync" "sync"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/mux" "github.com/minio/mux"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/auth"
sse "github.com/minio/minio/internal/bucket/encryption" sse "github.com/minio/minio/internal/bucket/encryption"
objectlock "github.com/minio/minio/internal/bucket/object/lock" objectlock "github.com/minio/minio/internal/bucket/object/lock"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
@@ -359,7 +360,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.ListBucketAction, Action: iampolicy.ListBucketAction,
BucketName: bucketInfo.Name, BucketName: bucketInfo.Name,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
ObjectName: "", ObjectName: "",
Claims: cred.Claims, Claims: cred.Claims,
@@ -371,7 +372,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.GetBucketLocationAction, Action: iampolicy.GetBucketLocationAction,
BucketName: bucketInfo.Name, BucketName: bucketInfo.Name,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner, IsOwner: owner,
ObjectName: "", ObjectName: "",
Claims: cred.Claims, Claims: cred.Claims,
@@ -431,7 +432,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Unmarshal list of keys to be deleted. // Unmarshal list of keys to be deleted.
deleteObjectsReq := &DeleteObjectsRequest{} deleteObjectsReq := &DeleteObjectsRequest{}
if err := xmlDecoder(r.Body, deleteObjectsReq, maxBodySize); err != nil { if err := xmlDecoder(r.Body, deleteObjectsReq, maxBodySize); err != nil {
logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
@@ -495,7 +495,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
vc, _ := globalBucketVersioningSys.Get(bucket) vc, _ := globalBucketVersioningSys.Get(bucket)
oss := make([]*objSweeper, len(deleteObjectsReq.Objects)) oss := make([]*objSweeper, len(deleteObjectsReq.Objects))
for index, object := range deleteObjectsReq.Objects { for index, object := range deleteObjectsReq.Objects {
if apiErrCode := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName); apiErrCode != ErrNone { if apiErrCode := checkRequestAuthTypeWithVID(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName, object.VersionID); apiErrCode != ErrNone {
if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID { if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL)
return return
@@ -511,11 +511,10 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
} }
if object.VersionID != "" && object.VersionID != nullVersionID { if object.VersionID != "" && object.VersionID != nullVersionID {
if _, err := uuid.Parse(object.VersionID); err != nil { if _, err := uuid.Parse(object.VersionID); err != nil {
logger.LogIf(ctx, fmt.Errorf("invalid version-id specified %w", err))
apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion) apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion)
deleteResults[index].errInfo = DeleteError{ deleteResults[index].errInfo = DeleteError{
Code: apiErr.Code, Code: apiErr.Code,
Message: apiErr.Description, Message: fmt.Sprintf("%s (%s)", apiErr.Description, err),
Key: object.ObjectName, Key: object.ObjectName,
VersionID: object.VersionID, VersionID: object.VersionID,
} }
@@ -744,7 +743,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: action, Action: action,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ConditionValues: getConditionValues(r, "", cred),
BucketName: bucket, BucketName: bucket,
IsOwner: owner, IsOwner: owner,
Claims: cred.Claims, Claims: cred.Claims,
@@ -769,16 +768,12 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
return return
} }
// check if client is attempting to create more buckets than allowed maximum. // check if client is attempting to create more buckets, complain about it.
if currBuckets := globalBucketMetadataSys.Count(); currBuckets+1 > maxBuckets { if currBuckets := globalBucketMetadataSys.Count(); currBuckets+1 > maxBuckets {
apiErr := errorCodes.ToAPIErr(ErrTooManyBuckets) logger.LogIf(ctx, fmt.Errorf("An attempt to create %d buckets beyond recommended %d", currBuckets+1, maxBuckets))
apiErr.Description = fmt.Sprintf("You have attempted to create %d buckets than allowed %d", currBuckets+1, maxBuckets)
writeErrorResponse(ctx, w, apiErr, r.URL)
return
} }
opts := MakeBucketOptions{ opts := MakeBucketOptions{
Location: location,
LockEnabled: objectLockEnabled, LockEnabled: objectLockEnabled,
ForceCreate: forceCreate, ForceCreate: forceCreate,
} }
@@ -790,15 +785,14 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
// exists elsewhere // exists elsewhere
if err == dns.ErrNoEntriesFound || err == dns.ErrNotImplemented { if err == dns.ErrNoEntriesFound || err == dns.ErrNotImplemented {
// Proceed to creating a bucket. // Proceed to creating a bucket.
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts); err != nil { if err = objectAPI.MakeBucket(ctx, bucket, opts); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
if err = globalDNSConfig.Put(bucket); err != nil { if err = globalDNSConfig.Put(bucket); err != nil {
objectAPI.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{ objectAPI.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{
Force: false, Force: true,
NoRecreate: true,
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()), SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
}) })
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
@@ -842,7 +836,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
} }
// Proceed to creating a bucket. // Proceed to creating a bucket.
if err := objectAPI.MakeBucketWithLocation(ctx, bucket, opts); err != nil { if err := objectAPI.MakeBucket(ctx, bucket, opts); err != nil {
if _, ok := err.(BucketExists); ok { if _, ok := err.(BucketExists); ok {
// Though bucket exists locally, we send the site-replication // Though bucket exists locally, we send the site-replication
// hook to ensure all sites have this bucket. If the hook // hook to ensure all sites have this bucket. If the hook
@@ -858,10 +852,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
globalNotificationSys.LoadBucketMetadata(GlobalContext, bucket) globalNotificationSys.LoadBucketMetadata(GlobalContext, bucket)
// Call site replication hook // Call site replication hook
if err := globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts); err != nil { logger.LogIf(ctx, globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Make sure to add Location information here only for bucket // Make sure to add Location information here only for bucket
if cp := pathClean(r.URL.Path); cp != "" { if cp := pathClean(r.URL.Path); cp != "" {
@@ -900,11 +891,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
bucket := mux.Vars(r)["bucket"] bucket := mux.Vars(r)["bucket"]
// Require Content-Length to be set in the request // Require Content-Length to be set in the request
@@ -930,16 +916,18 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// be loaded in memory, the remaining being put in temporary files. // be loaded in memory, the remaining being put in temporary files.
reader, err := r.MultipartReader() reader, err := r.MultipartReader()
if err != nil { if err != nil {
logger.LogIf(ctx, err) apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL) apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
writeErrorResponse(ctx, w, apiErr, r.URL)
return return
} }
// Read multipart data and save in memory and in the disk if needed // Read multipart data and save in memory and in the disk if needed
form, err := reader.ReadForm(maxFormMemory) form, err := reader.ReadForm(maxFormMemory)
if err != nil { if err != nil {
logger.LogIf(ctx, err, logger.Application) apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL) apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
writeErrorResponse(ctx, w, apiErr, r.URL)
return return
} }
@@ -949,8 +937,9 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// Extract all form fields // Extract all form fields
fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form) fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form)
if err != nil { if err != nil {
logger.LogIf(ctx, err, logger.Application) apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL) apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
writeErrorResponse(ctx, w, apiErr, r.URL)
return return
} }
@@ -995,7 +984,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Groups: cred.Groups, Groups: cred.Groups,
Action: iampolicy.PutObjectAction, Action: iampolicy.PutObjectAction,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims), ConditionValues: getConditionValues(r, "", cred),
BucketName: bucket, BucketName: bucket,
ObjectName: object, ObjectName: object,
IsOwner: globalActiveCred.AccessKey == cred.AccessKey, IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
@@ -1065,61 +1054,64 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
sseConfig, _ := globalBucketSSEConfigSys.Get(bucket) sseConfig, _ := globalBucketSSEConfigSys.Get(bucket)
sseConfig.Apply(r.Header, sse.ApplyOptions{ sseConfig.Apply(r.Header, sse.ApplyOptions{
AutoEncrypt: globalAutoEncryption, AutoEncrypt: globalAutoEncryption,
Passthrough: globalIsGateway && globalGatewayName == S3BackendGateway,
}) })
// get gateway encryption options
var opts ObjectOptions var opts ObjectOptions
opts, err = putOpts(ctx, r, bucket, object, metadata) opts, err = putOpts(ctx, r, bucket, object, metadata)
if err != nil { if err != nil {
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
return return
} }
if objectAPI.IsEncryptionSupported() {
if crypto.Requested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests if crypto.Requested(formValues) {
if crypto.SSECopy.IsRequested(r.Header) { if crypto.SSECopy.IsRequested(r.Header) {
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
return return
} }
var (
reader io.Reader if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
keyID string writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
key []byte return
kmsCtx kms.Context }
)
kind, _ := crypto.IsRequested(formValues) var (
switch kind { reader io.Reader
case crypto.SSEC: keyID string
key, err = ParseSSECustomerHeader(formValues) key []byte
if err != nil { kmsCtx kms.Context
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) )
return kind, _ := crypto.IsRequested(formValues)
} switch kind {
case crypto.S3KMS: case crypto.SSEC:
keyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(formValues) key, err = ParseSSECustomerHeader(formValues)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
}
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
info := ObjectInfo{Size: fileSize} case crypto.S3KMS:
// do not try to verify encrypted content keyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(formValues)
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
} }
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
info := ObjectInfo{Size: fileSize}
// do not try to verify encrypted content
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
} }
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts) objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
@@ -1150,6 +1142,17 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
Host: handlers.GetSourceIP(r), Host: handlers.GetSourceIP(r),
}) })
if objInfo.NumVersions > dataScannerExcessiveVersionsThreshold {
defer sendEvent(eventArgs{
EventName: event.ObjectManyVersions,
BucketName: objInfo.Bucket,
Object: objInfo,
ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent(),
Host: handlers.GetSourceIP(r),
})
}
if redirectURL != nil { // success_action_redirect is valid and set. if redirectURL != nil { // success_action_redirect is valid and set.
v := redirectURL.Query() v := redirectURL.Query()
@@ -1209,7 +1212,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
readable := globalPolicySys.IsAllowed(policy.Args{ readable := globalPolicySys.IsAllowed(policy.Args{
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", "", nil), ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
IsOwner: false, IsOwner: false,
}) })
@@ -1217,7 +1220,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
writable := globalPolicySys.IsAllowed(policy.Args{ writable := globalPolicySys.IsAllowed(policy.Args{
Action: policy.PutObjectAction, Action: policy.PutObjectAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", "", nil), ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
IsOwner: false, IsOwner: false,
}) })
@@ -1361,11 +1364,9 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
globalNotificationSys.DeleteBucketMetadata(ctx, bucket) globalNotificationSys.DeleteBucketMetadata(ctx, bucket)
globalReplicationPool.deleteResyncMetadata(ctx, bucket) globalReplicationPool.deleteResyncMetadata(ctx, bucket)
// Call site replication hook. // Call site replication hook.
if err := globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete); err != nil { logger.LogIf(ctx, globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Write success response. // Write success response.
writeSuccessNoContent(w) writeSuccessNoContent(w)
@@ -1398,10 +1399,6 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return return
} }
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketObjectLockConfigurationAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketObjectLockConfigurationAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
@@ -1438,15 +1435,12 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
// We encode the xml bytes as base64 to ensure there are no encoding // We encode the xml bytes as base64 to ensure there are no encoding
// errors. // errors.
cfgStr := base64.StdEncoding.EncodeToString(configData) cfgStr := base64.StdEncoding.EncodeToString(configData)
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{ logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypeObjectLockConfig, Type: madmin.SRBucketMetaTypeObjectLockConfig,
Bucket: bucket, Bucket: bucket,
ObjectLockConfig: &cfgStr, ObjectLockConfig: &cfgStr,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Write success response. // Write success response.
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
@@ -1545,15 +1539,12 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h
// We encode the xml bytes as base64 to ensure there are no encoding // We encode the xml bytes as base64 to ensure there are no encoding
// errors. // errors.
cfgStr := base64.StdEncoding.EncodeToString(configData) cfgStr := base64.StdEncoding.EncodeToString(configData)
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{ logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypeTags, Type: madmin.SRBucketMetaTypeTags,
Bucket: bucket, Bucket: bucket,
Tags: &cfgStr, Tags: &cfgStr,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Write success response. // Write success response.
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
@@ -1624,14 +1615,11 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
return return
} }
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{ logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypeTags, Type: madmin.SRBucketMetaTypeTags,
Bucket: bucket, Bucket: bucket,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Write success response. // Write success response.
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
+24 -11
View File
@@ -662,6 +662,9 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
var objectNames []string var objectNames []string
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
objectName := "test-object-" + strconv.Itoa(i) objectName := "test-object-" + strconv.Itoa(i)
if i == 0 {
objectName += "/"
}
// uploading the object. // uploading the object.
_, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{}) _, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
// if object upload fails stop the test. // if object upload fails stop the test.
@@ -793,9 +796,9 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
expectedContent []byte expectedContent []byte
expectedRespStatus int expectedRespStatus int
}{ }{
// Test case - 1. // Test case - 0.
// Delete objects with invalid access key. // Delete objects with invalid access key.
{ 0: {
bucket: bucketName, bucket: bucketName,
objects: successRequest0, objects: successRequest0,
accessKey: "Invalid-AccessID", accessKey: "Invalid-AccessID",
@@ -803,9 +806,19 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
expectedContent: nil, expectedContent: nil,
expectedRespStatus: http.StatusForbidden, expectedRespStatus: http.StatusForbidden,
}, },
// Test case - 2. // Test case - 1.
// Delete valid objects with quiet flag off. // Delete valid objects with quiet flag off.
{ 1: {
bucket: bucketName,
objects: successRequest0,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedContent: encodedSuccessResponse0,
expectedRespStatus: http.StatusOK,
},
// Test case - 2.
// Delete deleted objects with quiet flag off.
2: {
bucket: bucketName, bucket: bucketName,
objects: successRequest0, objects: successRequest0,
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
@@ -815,7 +828,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
}, },
// Test case - 3. // Test case - 3.
// Delete valid objects with quiet flag on. // Delete valid objects with quiet flag on.
{ 3: {
bucket: bucketName, bucket: bucketName,
objects: successRequest1, objects: successRequest1,
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
@@ -825,7 +838,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
}, },
// Test case - 4. // Test case - 4.
// Delete previously deleted objects. // Delete previously deleted objects.
{ 4: {
bucket: bucketName, bucket: bucketName,
objects: successRequest1, objects: successRequest1,
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
@@ -836,7 +849,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
// Test case - 5. // Test case - 5.
// Anonymous user access denied response // Anonymous user access denied response
// Currently anonymous users cannot delete multiple objects in MinIO server // Currently anonymous users cannot delete multiple objects in MinIO server
{ 5: {
bucket: bucketName, bucket: bucketName,
objects: anonRequest, objects: anonRequest,
accessKey: "", accessKey: "",
@@ -847,7 +860,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
// Test case - 6. // Test case - 6.
// Anonymous user has access to some public folder, issue removing with // Anonymous user has access to some public folder, issue removing with
// another private object as well // another private object as well
{ 6: {
bucket: bucketName, bucket: bucketName,
objects: anonRequestWithPartialPublicAccess, objects: anonRequestWithPartialPublicAccess,
accessKey: "", accessKey: "",
@@ -881,19 +894,19 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
apiRouter.ServeHTTP(rec, req) apiRouter.ServeHTTP(rec, req)
// Assert the response code with the expected status. // Assert the response code with the expected status.
if rec.Code != testCase.expectedRespStatus { if rec.Code != testCase.expectedRespStatus {
t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i, instanceType, testCase.expectedRespStatus, rec.Code)
} }
// read the response body. // read the response body.
actualContent, err = io.ReadAll(rec.Body) actualContent, err = io.ReadAll(rec.Body)
if err != nil { if err != nil {
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err) t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i, instanceType, err)
} }
// Verify whether the bucket obtained object is same as the one created. // Verify whether the bucket obtained object is same as the one created.
if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) { if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) {
t.Log(string(testCase.expectedContent), string(actualContent)) t.Log(string(testCase.expectedContent), string(actualContent))
t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i+1, instanceType) t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i, instanceType)
} }
} }
+1 -1
View File
@@ -22,10 +22,10 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/gorilla/mux"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
+51 -51
View File
@@ -58,15 +58,6 @@ type LifecycleSys struct{}
// Get - gets lifecycle config associated to a given bucket name. // Get - gets lifecycle config associated to a given bucket name.
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) { func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
if globalIsGateway {
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
return nil, BucketLifecycleNotFound{Bucket: bucketName}
}
return globalBucketMetadataSys.GetLifecycleConfig(bucketName) return globalBucketMetadataSys.GetLifecycleConfig(bucketName)
} }
@@ -155,9 +146,13 @@ type newerNoncurrentTask struct {
versions []ObjectToDelete versions []ObjectToDelete
} }
type transitionTask struct {
tier string
objInfo ObjectInfo
}
type transitionState struct { type transitionState struct {
once sync.Once transitionCh chan transitionTask
transitionCh chan ObjectInfo
ctx context.Context ctx context.Context
objAPI ObjectLayer objAPI ObjectLayer
@@ -171,33 +166,42 @@ type transitionState struct {
lastDayStats map[string]*lastDayTierStats lastDayStats map[string]*lastDayTierStats
} }
func (t *transitionState) queueTransitionTask(oi ObjectInfo) { func (t *transitionState) queueTransitionTask(oi ObjectInfo, sc string) {
select { select {
case <-GlobalContext.Done(): case <-t.ctx.Done():
t.once.Do(func() { case t.transitionCh <- transitionTask{objInfo: oi, tier: sc}:
close(t.transitionCh)
})
case t.transitionCh <- oi:
default: default:
} }
} }
var globalTransitionState *transitionState var globalTransitionState *transitionState
func newTransitionState(ctx context.Context, objAPI ObjectLayer) *transitionState { // newTransitionState returns a transitionState object ready to be initialized
// via its Init method.
func newTransitionState(ctx context.Context) *transitionState {
return &transitionState{ return &transitionState{
transitionCh: make(chan ObjectInfo, 10000), transitionCh: make(chan transitionTask, 10000),
ctx: ctx, ctx: ctx,
objAPI: objAPI,
killCh: make(chan struct{}), killCh: make(chan struct{}),
lastDayStats: make(map[string]*lastDayTierStats), lastDayStats: make(map[string]*lastDayTierStats),
} }
} }
// Init initializes t with given objAPI and instantiates the configured number
// of transition workers.
func (t *transitionState) Init(objAPI ObjectLayer) {
n := globalAPIConfig.getTransitionWorkers()
t.mu.Lock()
defer t.mu.Unlock()
t.objAPI = objAPI
t.updateWorkers(n)
}
// PendingTasks returns the number of ILM transition tasks waiting for a worker // PendingTasks returns the number of ILM transition tasks waiting for a worker
// goroutine. // goroutine.
func (t *transitionState) PendingTasks() int { func (t *transitionState) PendingTasks() int {
return len(globalTransitionState.transitionCh) return len(t.transitionCh)
} }
// ActiveTasks returns the number of active (ongoing) ILM transition tasks. // ActiveTasks returns the number of active (ongoing) ILM transition tasks.
@@ -206,34 +210,32 @@ func (t *transitionState) ActiveTasks() int {
} }
// worker waits for transition tasks // worker waits for transition tasks
func (t *transitionState) worker(ctx context.Context, objectAPI ObjectLayer) { func (t *transitionState) worker(objectAPI ObjectLayer) {
for { for {
select { select {
case <-t.killCh: case <-t.killCh:
return return
case <-ctx.Done(): case <-t.ctx.Done():
return return
case oi, ok := <-t.transitionCh: case task, ok := <-t.transitionCh:
if !ok { if !ok {
return return
} }
atomic.AddInt32(&t.activeTasks, 1) atomic.AddInt32(&t.activeTasks, 1)
var tier string if err := transitionObject(t.ctx, objectAPI, task.objInfo, task.tier); err != nil {
var err error logger.LogIf(t.ctx, fmt.Errorf("Transition failed for %s/%s version:%s with %w",
if tier, err = transitionObject(ctx, objectAPI, oi); err != nil { task.objInfo.Bucket, task.objInfo.Name, task.objInfo.VersionID, err))
logger.LogIf(ctx, fmt.Errorf("Transition failed for %s/%s version:%s with %w", oi.Bucket, oi.Name, oi.VersionID, err))
} else { } else {
ts := tierStats{ ts := tierStats{
TotalSize: uint64(oi.Size), TotalSize: uint64(task.objInfo.Size),
NumVersions: 1, NumVersions: 1,
} }
if oi.IsLatest { if task.objInfo.IsLatest {
ts.NumObjects = 1 ts.NumObjects = 1
} }
t.addLastDayStats(tier, ts) t.addLastDayStats(task.tier, ts)
} }
atomic.AddInt32(&t.activeTasks, -1) atomic.AddInt32(&t.activeTasks, -1)
} }
} }
} }
@@ -264,9 +266,15 @@ func (t *transitionState) getDailyAllTierStats() DailyAllTierStats {
func (t *transitionState) UpdateWorkers(n int) { func (t *transitionState) UpdateWorkers(n int) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
if t.objAPI == nil { // Init hasn't been called yet.
return
}
t.updateWorkers(n)
}
func (t *transitionState) updateWorkers(n int) {
for t.numWorkers < n { for t.numWorkers < n {
go t.worker(t.ctx, t.objAPI) go t.worker(t.objAPI)
t.numWorkers++ t.numWorkers++
} }
@@ -276,12 +284,6 @@ func (t *transitionState) UpdateWorkers(n int) {
} }
} }
func initBackgroundTransition(ctx context.Context, objectAPI ObjectLayer) {
globalTransitionState = newTransitionState(ctx, objectAPI)
n := globalAPIConfig.getTransitionWorkers()
globalTransitionState.UpdateWorkers(n)
}
var errInvalidStorageClass = errors.New("invalid storage class") var errInvalidStorageClass = errors.New("invalid storage class")
func validateTransitionTier(lc *lifecycle.Lifecycle) error { func validateTransitionTier(lc *lifecycle.Lifecycle) error {
@@ -304,9 +306,10 @@ func validateTransitionTier(lc *lifecycle.Lifecycle) error {
// This is to be called after a successful upload of an object (version). // This is to be called after a successful upload of an object (version).
func enqueueTransitionImmediate(obj ObjectInfo) { func enqueueTransitionImmediate(obj ObjectInfo) {
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil { if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
switch lc.ComputeAction(obj.ToLifecycleOpts()) { event := lc.Eval(obj.ToLifecycleOpts())
switch event.Action {
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction: case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
globalTransitionState.queueTransitionTask(obj) globalTransitionState.queueTransitionTask(obj, event.StorageClass)
} }
} }
} }
@@ -371,7 +374,7 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
EventName: eventName, EventName: eventName,
BucketName: oi.Bucket, BucketName: oi.Bucket,
Object: objInfo, Object: objInfo,
Host: "Internal: [ILM-EXPIRY]", Host: "Internal: [ILM-Expiry]",
}) })
case expireRestoredObj: case expireRestoredObj:
@@ -403,12 +406,7 @@ func genTransitionObjName(bucket string) (string, error) {
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content // 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 // is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
// to the transition tier without decrypting or re-encrypting. // to the transition tier without decrypting or re-encrypting.
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo) (string, error) { func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, tier string) error {
lc, err := globalLifecycleSys.Get(oi.Bucket)
if err != nil {
return "", err
}
tier := lc.TransitionTier(oi.ToLifecycleOpts())
opts := ObjectOptions{ opts := ObjectOptions{
Transition: TransitionOptions{ Transition: TransitionOptions{
Status: lifecycle.TransitionPending, Status: lifecycle.TransitionPending,
@@ -420,7 +418,7 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo)
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name), VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
MTime: oi.ModTime, MTime: oi.ModTime,
} }
return tier, objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts) return objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
} }
type auditTierOp struct { type auditTierOp struct {
@@ -550,7 +548,7 @@ type RestoreObjectRequest struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RestoreRequest" json:"-"` XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RestoreRequest" json:"-"`
Days int `xml:"Days,omitempty"` Days int `xml:"Days,omitempty"`
Type RestoreRequestType `xml:"Type,omitempty"` Type RestoreRequestType `xml:"Type,omitempty"`
Tier string `xml:"Tier,-"` Tier string `xml:"Tier"`
Description string `xml:"Description,omitempty"` Description string `xml:"Description,omitempty"`
SelectParameters *SelectParameters `xml:"SelectParameters,omitempty"` SelectParameters *SelectParameters `xml:"SelectParameters,omitempty"`
OutputLocation OutputLocation `xml:"OutputLocation,omitempty"` OutputLocation OutputLocation `xml:"OutputLocation,omitempty"`
@@ -670,7 +668,9 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
if len(objInfo.UserTags) != 0 { if len(objInfo.UserTags) != 0 {
meta[xhttp.AmzObjectTagging] = objInfo.UserTags meta[xhttp.AmzObjectTagging] = objInfo.UserTags
} }
// Set restore object status
restoreExpiry := lifecycle.ExpectedExpiryTime(time.Now().UTC(), rreq.Days)
meta[xhttp.AmzRestore] = completedRestoreObj(restoreExpiry).String()
return ObjectOptions{ return ObjectOptions{
Versioned: globalBucketVersioningSys.PrefixEnabled(bucket, object), Versioned: globalBucketVersioningSys.PrefixEnabled(bucket, object),
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object), VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object),
+18 -10
View File
@@ -23,8 +23,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/gorilla/mux"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
@@ -35,7 +35,7 @@ import (
// - delimiter if set should be equal to '/', otherwise the request is rejected. // - delimiter if set should be equal to '/', otherwise the request is rejected.
// - marker if set should have a common prefix with 'prefix' param, otherwise // - marker if set should have a common prefix with 'prefix' param, otherwise
// the request is rejected. // the request is rejected.
func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int) APIErrorCode { func validateListObjectsArgs(prefix, marker, delimiter, encodingType string, maxKeys int) APIErrorCode {
// Max keys cannot be negative. // Max keys cannot be negative.
if maxKeys < 0 { if maxKeys < 0 {
return ErrInvalidMaxKeys return ErrInvalidMaxKeys
@@ -48,6 +48,14 @@ func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int
} }
} }
if !IsValidObjectPrefix(prefix) {
return ErrInvalidObjectName
}
if marker != "" && !HasPrefix(marker, prefix) {
return ErrNotImplemented
}
return ErrNone return ErrNone
} }
@@ -83,7 +91,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
} }
// Validate the query params before beginning to serve the request. // Validate the query params before beginning to serve the request.
if s3Error := validateListObjectsArgs(marker, delimiter, encodingType, maxkeys); s3Error != ErrNone { if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxkeys); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
} }
@@ -107,7 +115,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo) response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo)
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodeResponse(response)) writeSuccessResponseXML(w, encodeResponseList(response))
} }
// ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata. // ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata.
@@ -148,7 +156,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
// Validate the query params before beginning to serve the request. // Validate the query params before beginning to serve the request.
// fetch-owner is not validated since it is a boolean // fetch-owner is not validated since it is a boolean
if s3Error := validateListObjectsArgs(token, delimiter, encodingType, maxKeys); s3Error != ErrNone { if s3Error := validateListObjectsArgs(prefix, token, delimiter, encodingType, maxKeys); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
} }
@@ -177,7 +185,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, true) maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, true)
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodeResponse(response)) writeSuccessResponseXML(w, encodeResponseList(response))
} }
// ListObjectsV2Handler - GET Bucket (List Objects) Version 2. // ListObjectsV2Handler - GET Bucket (List Objects) Version 2.
@@ -218,7 +226,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
// Validate the query params before beginning to serve the request. // Validate the query params before beginning to serve the request.
// fetch-owner is not validated since it is a boolean // fetch-owner is not validated since it is a boolean
if s3Error := validateListObjectsArgs(token, delimiter, encodingType, maxKeys); s3Error != ErrNone { if s3Error := validateListObjectsArgs(prefix, token, delimiter, encodingType, maxKeys); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
} }
@@ -252,7 +260,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false) maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false)
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodeResponse(response)) writeSuccessResponseXML(w, encodeResponseList(response))
} }
func parseRequestToken(token string) (subToken string, nodeIndex int) { func parseRequestToken(token string) (subToken string, nodeIndex int) {
@@ -325,7 +333,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
} }
// Validate all the query params before beginning to serve the request. // Validate all the query params before beginning to serve the request.
if s3Error := validateListObjectsArgs(marker, delimiter, encodingType, maxKeys); s3Error != ErrNone { if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxKeys); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
} }
@@ -349,5 +357,5 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo) response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodeResponse(response)) writeSuccessResponseXML(w, encodeResponseList(response))
} }
+161 -116
View File
@@ -18,14 +18,15 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"runtime"
"sync" "sync"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
bucketsse "github.com/minio/minio/internal/bucket/encryption" bucketsse "github.com/minio/minio/internal/bucket/encryption"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
@@ -41,6 +42,8 @@ import (
// BucketMetadataSys captures all bucket metadata for a given cluster. // BucketMetadataSys captures all bucket metadata for a given cluster.
type BucketMetadataSys struct { type BucketMetadataSys struct {
objAPI ObjectLayer
sync.RWMutex sync.RWMutex
metadataMap map[string]BucketMetadata metadataMap map[string]BucketMetadata
} }
@@ -54,27 +57,36 @@ func (sys *BucketMetadataSys) Count() int {
} }
// Remove bucket metadata from memory. // Remove bucket metadata from memory.
func (sys *BucketMetadataSys) Remove(bucket string) { func (sys *BucketMetadataSys) Remove(buckets ...string) {
if globalIsGateway {
return
}
sys.Lock() sys.Lock()
delete(sys.metadataMap, bucket) for _, bucket := range buckets {
globalBucketMonitor.DeleteBucket(bucket) delete(sys.metadataMap, bucket)
globalBucketMonitor.DeleteBucket(bucket)
}
sys.Unlock() sys.Unlock()
} }
// RemoveStaleBuckets removes all stale buckets in memory that are not on disk.
func (sys *BucketMetadataSys) RemoveStaleBuckets(diskBuckets set.StringSet) {
sys.Lock()
defer sys.Unlock()
for bucket := range sys.metadataMap {
if diskBuckets.Contains(bucket) {
continue
} // doesn't exist on disk remove from memory.
delete(sys.metadataMap, bucket)
globalBucketMonitor.DeleteBucket(bucket)
}
}
// Set - sets a new metadata in-memory. // Set - sets a new metadata in-memory.
// Only a shallow copy is saved and fields with references // Only a shallow copy is saved and fields with references
// cannot be modified without causing a race condition, // cannot be modified without causing a race condition,
// so they should be replaced atomically and not appended to, etc. // so they should be replaced atomically and not appended to, etc.
// Data is not persisted to disk. // Data is not persisted to disk.
func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) { func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
if globalIsGateway { if !isMinioMetaBucketName(bucket) {
return
}
if bucket != minioMetaBucket {
sys.Lock() sys.Lock()
sys.metadataMap[bucket] = meta sys.metadataMap[bucket] = meta
sys.Unlock() sys.Unlock()
@@ -87,21 +99,7 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
return updatedAt, errServerNotInitialized return updatedAt, errServerNotInitialized
} }
if globalIsGateway && globalGatewayName != NASBackendGateway { if isMinioMetaBucketName(bucket) {
if configFile == bucketPolicyConfig {
if configData == nil {
return updatedAt, objAPI.DeleteBucketPolicy(ctx, bucket)
}
config, err := policy.ParseConfig(bytes.NewReader(configData), bucket)
if err != nil {
return updatedAt, err
}
return updatedAt, objAPI.SetBucketPolicy(ctx, bucket, config)
}
return updatedAt, NotImplemented{}
}
if bucket == minioMetaBucket {
return updatedAt, errInvalidArgument return updatedAt, errInvalidArgument
} }
@@ -186,7 +184,7 @@ func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configF
// For all other bucket specific metadata, use the relevant // For all other bucket specific metadata, use the relevant
// calls implemented specifically for each of those features. // calls implemented specifically for each of those features.
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) { func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
if globalIsGateway || bucket == minioMetaBucket { if isMinioMetaBucketName(bucket) {
return newBucketMetadata(bucket), errConfigNotFound return newBucketMetadata(bucket), errConfigNotFound
} }
@@ -204,7 +202,7 @@ func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
// GetVersioningConfig returns configured versioning config // GetVersioningConfig returns configured versioning config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Versioning, time.Time, error) { func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Versioning, time.Time, error) {
meta, err := sys.GetConfig(GlobalContext, bucket) meta, _, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, meta.Created, nil return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, meta.Created, nil
@@ -217,7 +215,7 @@ func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Ve
// GetTaggingConfig returns configured tagging config // GetTaggingConfig returns configured tagging config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.Time, error) { func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.Time, error) {
meta, err := sys.GetConfig(GlobalContext, bucket) meta, _, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket} return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket}
@@ -233,7 +231,7 @@ func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.
// GetObjectLockConfig returns configured object lock config // GetObjectLockConfig returns configured object lock config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Config, time.Time, error) { func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Config, time.Time, error) {
meta, err := sys.GetConfig(GlobalContext, bucket) meta, _, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket} return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket}
@@ -249,23 +247,7 @@ func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Co
// GetLifecycleConfig returns configured lifecycle config // GetLifecycleConfig returns configured lifecycle config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, error) { func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, error) {
if globalIsGateway && globalGatewayName == NASBackendGateway { meta, _, err := sys.GetConfig(GlobalContext, bucket)
// Only needed in case of NAS gateway.
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
meta, err := loadBucketMetadata(GlobalContext, objAPI, bucket)
if err != nil {
return nil, err
}
if meta.lifecycleConfig == nil {
return nil, BucketLifecycleNotFound{Bucket: bucket}
}
return meta.lifecycleConfig, nil
}
meta, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, BucketLifecycleNotFound{Bucket: bucket} return nil, BucketLifecycleNotFound{Bucket: bucket}
@@ -281,20 +263,7 @@ func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Life
// GetNotificationConfig returns configured notification config // GetNotificationConfig returns configured notification config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) { func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) {
if globalIsGateway && globalGatewayName == NASBackendGateway { meta, _, err := sys.GetConfig(GlobalContext, bucket)
// Only needed in case of NAS gateway.
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
meta, err := loadBucketMetadata(GlobalContext, objAPI, bucket)
if err != nil {
return nil, err
}
return meta.notificationConfig, nil
}
meta, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -304,7 +273,7 @@ func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Confi
// GetSSEConfig returns configured SSE config // GetSSEConfig returns configured SSE config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEConfig, time.Time, error) { func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEConfig, time.Time, error) {
meta, err := sys.GetConfig(GlobalContext, bucket) meta, _, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket} return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket}
@@ -319,7 +288,7 @@ func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEC
// CreatedAt returns the time of creation of bucket // CreatedAt returns the time of creation of bucket
func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) { func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
meta, err := sys.GetConfig(GlobalContext, bucket) meta, _, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
return time.Time{}, err return time.Time{}, err
} }
@@ -329,16 +298,7 @@ func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
// GetPolicyConfig returns configured bucket policy // GetPolicyConfig returns configured bucket policy
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) { func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) {
if globalIsGateway { meta, _, err := sys.GetConfig(GlobalContext, bucket)
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, time.Time{}, errServerNotInitialized
}
p, err := objAPI.GetBucketPolicy(GlobalContext, bucket)
return p, UTCNow(), err
}
meta, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket} return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket}
@@ -354,7 +314,7 @@ func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, ti
// GetQuotaConfig returns configured bucket quota // GetQuotaConfig returns configured bucket quota
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string) (*madmin.BucketQuota, time.Time, error) { func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string) (*madmin.BucketQuota, time.Time, error) {
meta, err := sys.GetConfig(ctx, bucket) meta, _, err := sys.GetConfig(ctx, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, time.Time{}, BucketQuotaConfigNotFound{Bucket: bucket} return nil, time.Time{}, BucketQuotaConfigNotFound{Bucket: bucket}
@@ -367,7 +327,7 @@ func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string)
// GetReplicationConfig returns configured bucket replication config // GetReplicationConfig returns configured bucket replication config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket string) (*replication.Config, time.Time, error) { func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket string) (*replication.Config, time.Time, error) {
meta, err := sys.GetConfig(ctx, bucket) meta, reloaded, err := sys.GetConfig(ctx, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket} return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
@@ -378,13 +338,18 @@ func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket s
if meta.replicationConfig == nil { if meta.replicationConfig == nil {
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket} return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
} }
if reloaded {
globalBucketTargetSys.set(BucketInfo{
Name: bucket,
}, meta)
}
return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil
} }
// GetBucketTargetsConfig returns configured bucket targets for this bucket // GetBucketTargetsConfig returns configured bucket targets for this bucket
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) { func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) {
meta, err := sys.GetConfig(GlobalContext, bucket) meta, reloaded, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, BucketRemoteTargetNotFound{Bucket: bucket} return nil, BucketRemoteTargetNotFound{Bucket: bucket}
@@ -394,40 +359,56 @@ func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.Buc
if meta.bucketTargetConfig == nil { if meta.bucketTargetConfig == nil {
return nil, BucketRemoteTargetNotFound{Bucket: bucket} return nil, BucketRemoteTargetNotFound{Bucket: bucket}
} }
if reloaded {
globalBucketTargetSys.set(BucketInfo{
Name: bucket,
}, meta)
}
return meta.bucketTargetConfig, nil return meta.bucketTargetConfig, nil
} }
// GetConfig returns a specific configuration from the bucket metadata. // GetConfigFromDisk read bucket metadata config from disk.
// The returned object may not be modified. func (sys *BucketMetadataSys) GetConfigFromDisk(ctx context.Context, bucket string) (BucketMetadata, error) {
func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (BucketMetadata, error) {
objAPI := newObjectLayerFn() objAPI := newObjectLayerFn()
if objAPI == nil { if objAPI == nil {
return newBucketMetadata(bucket), errServerNotInitialized return newBucketMetadata(bucket), errServerNotInitialized
} }
if globalIsGateway { if isMinioMetaBucketName(bucket) {
return newBucketMetadata(bucket), NotImplemented{} return newBucketMetadata(bucket), errInvalidArgument
} }
if bucket == minioMetaBucket { return loadBucketMetadata(ctx, objAPI, bucket)
return newBucketMetadata(bucket), errInvalidArgument }
// GetConfig returns a specific configuration from the bucket metadata.
// The returned object may not be modified.
// reloaded will be true if metadata refreshed from disk
func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (meta BucketMetadata, reloaded bool, err error) {
objAPI := newObjectLayerFn()
if objAPI == nil {
return newBucketMetadata(bucket), reloaded, errServerNotInitialized
}
if isMinioMetaBucketName(bucket) {
return newBucketMetadata(bucket), reloaded, errInvalidArgument
} }
sys.RLock() sys.RLock()
meta, ok := sys.metadataMap[bucket] meta, ok := sys.metadataMap[bucket]
sys.RUnlock() sys.RUnlock()
if ok { if ok {
return meta, nil return meta, reloaded, nil
} }
meta, err := loadBucketMetadata(ctx, objAPI, bucket) meta, err = loadBucketMetadata(ctx, objAPI, bucket)
if err != nil { if err != nil {
return meta, err return meta, reloaded, err
} }
sys.Lock() sys.Lock()
sys.metadataMap[bucket] = meta sys.metadataMap[bucket] = meta
sys.Unlock() sys.Unlock()
return meta, nil return meta, true, nil
} }
// Init - initializes bucket metadata system for all buckets. // Init - initializes bucket metadata system for all buckets.
@@ -436,65 +417,129 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
return errServerNotInitialized return errServerNotInitialized
} }
// In gateway mode, we don't need to load bucket metadata except sys.objAPI = objAPI
// NAS gateway backend.
if globalIsGateway && !objAPI.IsNotificationSupported() {
return nil
}
// Load bucket metadata sys in background // Load bucket metadata sys in background
go sys.load(ctx, buckets, objAPI) go sys.init(ctx, buckets)
return nil
}
func (sys *BucketMetadataSys) loadBucketMetadata(ctx context.Context, bucket BucketInfo) error {
meta, err := loadBucketMetadata(ctx, sys.objAPI, bucket.Name)
if err != nil {
return err
}
sys.Lock()
sys.metadataMap[bucket.Name] = meta
sys.Unlock()
return nil return nil
} }
// concurrently load bucket metadata to speed up loading bucket metadata. // concurrently load bucket metadata to speed up loading bucket metadata.
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) { func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo) {
g := errgroup.WithNErrs(len(buckets)) g := errgroup.WithNErrs(len(buckets))
bucketMetas := make([]BucketMetadata, len(buckets))
for index := range buckets { for index := range buckets {
index := index index := index
g.Go(func() error { g.Go(func() error {
_, _ = objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{ _, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
// Ensure heal opts for bucket metadata be deep healed all the time. // Ensure heal opts for bucket metadata be deep healed all the time.
ScanMode: madmin.HealDeepScan, ScanMode: madmin.HealDeepScan,
Recreate: true, Recreate: true,
}) })
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name) meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name)
if err != nil { if err != nil {
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) { return err
meta = newBucketMetadata(buckets[index].Name)
} else {
return err
}
} }
sys.Lock() bucketMetas[index] = meta
sys.metadataMap[buckets[index].Name] = meta
sys.Unlock()
globalEventNotifier.set(buckets[index], meta) // set notification targets
globalBucketTargetSys.set(buckets[index], meta) // set remote replication targets
return nil return nil
}, index) }, index)
} }
for _, err := range g.Wait() {
errs := g.Wait()
for _, err := range errs {
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
} }
// Hold lock here to update in-memory map at once,
// instead of serializing the Go routines.
sys.Lock()
for i, meta := range bucketMetas {
if errs[i] != nil {
continue
}
sys.metadataMap[buckets[i].Name] = meta
}
sys.Unlock()
for i, meta := range bucketMetas {
if errs[i] != nil {
continue
}
globalEventNotifier.set(buckets[i], meta) // set notification targets
globalBucketTargetSys.set(buckets[i], meta) // set remote replication targets
}
}
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
const bucketMetadataRefresh = 15 * time.Minute
t := time.NewTimer(bucketMetadataRefresh)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
if err != nil {
logger.LogIf(ctx, err)
continue
}
// Handle if we have some buckets in-memory those are stale.
// first delete them and then replace the newer state()
// from disk.
diskBuckets := set.CreateStringSet()
for _, bucket := range buckets {
diskBuckets.Add(bucket.Name)
}
sys.RemoveStaleBuckets(diskBuckets)
for _, bucket := range buckets {
err := sys.loadBucketMetadata(ctx, bucket)
if err != nil {
logger.LogIf(ctx, err)
continue
}
// Check if there is a spare procs, wait 100ms instead
waitForLowIO(runtime.GOMAXPROCS(0), 100*time.Millisecond, currentHTTPIO)
}
t.Reset(bucketMetadataRefresh)
}
}
} }
// Loads bucket metadata for all buckets into BucketMetadataSys. // Loads bucket metadata for all buckets into BucketMetadataSys.
func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) { func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
count := 100 // load 100 bucket metadata at a time. count := 100 // load 100 bucket metadata at a time.
for { for {
if len(buckets) < count { if len(buckets) < count {
sys.concurrentLoad(ctx, buckets, objAPI) sys.concurrentLoad(ctx, buckets)
return break
} }
sys.concurrentLoad(ctx, buckets[:count], objAPI) sys.concurrentLoad(ctx, buckets[:count])
buckets = buckets[count:] buckets = buckets[count:]
} }
if globalIsDistErasure {
go sys.refreshBucketsMetadataLoop(ctx)
}
} }
// Reset the state of the BucketMetadataSys. // Reset the state of the BucketMetadataSys.
+2 -19
View File
@@ -29,7 +29,7 @@ import (
"path" "path"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
bucketsse "github.com/minio/minio/internal/bucket/encryption" bucketsse "github.com/minio/minio/internal/bucket/encryption"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
@@ -135,7 +135,7 @@ func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error { func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error {
if name == "" { if name == "" {
logger.LogIf(ctx, errors.New("bucket name cannot be empty")) logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
return errors.New("bucket name cannot be empty") return errInvalidArgument
} }
configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile) configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile)
data, err := readConfig(ctx, api, configFile) data, err := readConfig(ctx, api, configFile)
@@ -442,23 +442,6 @@ func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
return saveConfig(ctx, api, configFile, data) return saveConfig(ctx, api, configFile, data)
} }
// deleteBucketMetadata deletes bucket metadata
// If config does not exist no error is returned.
func deleteBucketMetadata(ctx context.Context, obj objectDeleter, bucket string) error {
metadataFiles := []string{
dataUsageCacheName,
bucketMetadataFile,
path.Join(replicationDir, resyncFileName),
}
for _, metaFile := range metadataFiles {
configFile := path.Join(bucketMetaPrefix, bucket, metaFile)
if err := deleteConfig(ctx, obj, configFile); err != nil && err != errConfigNotFound {
return err
}
}
return nil
}
// migrate config for remote targets by encrypting data if currently unencrypted and kms is configured. // migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error { func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error {
var err error var err error
+1 -11
View File
@@ -23,9 +23,9 @@ import (
"net/http" "net/http"
"reflect" "reflect"
"github.com/gorilla/mux"
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
@@ -50,11 +50,6 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
return return
} }
if !objAPI.IsNotificationSupported() {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
@@ -119,11 +114,6 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
return return
} }
if !objectAPI.IsNotificationSupported() {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
vars := mux.Vars(r) vars := mux.Vars(r)
bucketName := vars["bucket"] bucketName := vars["bucket"]
+2 -14
View File
@@ -36,15 +36,6 @@ type BucketObjectLockSys struct{}
// Get - Get retention configuration. // Get - Get retention configuration.
func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) { func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) {
if globalIsGateway {
objAPI := newObjectLayerFn()
if objAPI == nil {
return r, errServerNotInitialized
}
return r, nil
}
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucketName) config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucketName)
if err != nil { if err != nil {
if errors.Is(err, BucketObjectLockConfigNotFound{Bucket: bucketName}) { if errors.Is(err, BucketObjectLockConfigNotFound{Bucket: bucketName}) {
@@ -165,11 +156,8 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
return ErrNone return ErrNone
} }
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes // https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
// If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention // If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention, the operation will succeed.
// or s3:GetBucketObjectLockConfiguration permissions, the operation will succeed. if checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName) != ErrNone {
govBypassPerms1 := checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName)
govBypassPerms2 := checkRequestAuthType(ctx, r, policy.GetBucketObjectLockConfigurationAction, bucket, object.ObjectName)
if govBypassPerms1 != ErrNone && govBypassPerms2 != ErrNone {
return ErrAccessDenied return ErrAccessDenied
} }
} }
+12 -14
View File
@@ -24,9 +24,9 @@ import (
"net/http" "net/http"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/gorilla/mux" "github.com/minio/madmin-go/v2"
"github.com/minio/madmin-go"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
@@ -86,13 +86,17 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket) bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, APIError{
Code: "ErrMalformedXML",
HTTPStatusCode: http.StatusBadRequest,
Description: err.Error(),
}, r.URL)
return return
} }
// Version in policy must not be empty // Version in policy must not be empty
if bucketPolicy.Version == "" { if bucketPolicy.Version == "" {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPolicyInvalidVersion), r.URL)
return return
} }
@@ -109,15 +113,12 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
} }
// Call site replication hook. // Call site replication hook.
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{ logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypePolicy, Type: madmin.SRBucketMetaTypePolicy,
Bucket: bucket, Bucket: bucket,
Policy: bucketPolicyBytes, Policy: bucketPolicyBytes,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Success. // Success.
writeSuccessNoContent(w) writeSuccessNoContent(w)
@@ -156,14 +157,11 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
} }
// Call site replication hook. // Call site replication hook.
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{ logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypePolicy, Type: madmin.SRBucketMetaTypePolicy,
Bucket: bucket, Bucket: bucket,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Success. // Success.
writeSuccessNoContent(w) writeSuccessNoContent(w)
+3 -3
View File
@@ -107,7 +107,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
// Wrapper for calling Create Bucket and ensure we get one and only one success. // Wrapper for calling Create Bucket and ensure we get one and only one success.
func TestCreateBucket(t *testing.T) { func TestCreateBucket(t *testing.T) {
ExecObjectLayerAPITest(t, testCreateBucket, []string{"MakeBucketWithLocation"}) ExecObjectLayerAPITest(t, testCreateBucket, []string{"MakeBucket"})
} }
// testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success. // testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success.
@@ -127,7 +127,7 @@ func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRoute
defer wg.Done() defer wg.Done()
// Sync start. // Sync start.
<-start <-start
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil { if err := obj.MakeBucket(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
if _, ok := err.(BucketExists); !ok { if _, ok := err.(BucketExists); !ok {
t.Logf("unexpected error: %T: %v", err, err) t.Logf("unexpected error: %T: %v", err, err)
return return
@@ -162,7 +162,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
credentials auth.Credentials, t *testing.T, credentials auth.Credentials, t *testing.T,
) { ) {
bucketName1 := fmt.Sprintf("%s-1", bucketName) bucketName1 := fmt.Sprintf("%s-1", bucketName)
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil { if err := obj.MakeBucket(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
+23 -10
View File
@@ -28,6 +28,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy" miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/handlers" "github.com/minio/minio/internal/handlers"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
@@ -65,9 +66,20 @@ func NewPolicySys() *PolicySys {
return &PolicySys{} return &PolicySys{}
} }
func getConditionValues(r *http.Request, lc string, username string, claims map[string]interface{}) map[string][]string { func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[string][]string {
currTime := UTCNow() currTime := UTCNow()
var (
username = cred.AccessKey
claims = cred.Claims
groups = cred.Groups
)
if cred.IsTemp() || cred.IsServiceAccount() {
// For derived credentials, check the parent user's permissions.
username = cred.ParentUser
}
principalType := "Anonymous" principalType := "Anonymous"
if username != "" { if username != "" {
principalType = "User" principalType = "User"
@@ -192,17 +204,11 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
for k, v := range claims { for k, v := range claims {
vStr, ok := v.(string) vStr, ok := v.(string)
if ok { if ok {
// Special case for AD/LDAP STS users // Trim any LDAP specific prefix
switch k { args[strings.ToLower(strings.TrimPrefix(k, "ldap"))] = []string{vStr}
case ldapUser:
args["user"] = []string{vStr}
case ldapUserN:
args["username"] = []string{vStr}
default:
args[k] = []string{vStr}
}
} }
} }
// Add groups claim which could be a list. This will ensure that the claim // Add groups claim which could be a list. This will ensure that the claim
// `jwt:groups` works. // `jwt:groups` works.
if grpsVal, ok := claims["groups"]; ok { if grpsVal, ok := claims["groups"]; ok {
@@ -219,6 +225,13 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
} }
} }
// if not claim groups are available use the one with auth.Credentials
if _, ok := args["groups"]; !ok {
if len(groups) > 0 {
args["groups"] = groups
}
}
return args return args
} }
+1 -8
View File
@@ -24,7 +24,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
) )
@@ -35,13 +35,6 @@ type BucketQuotaSys struct {
// Get - Get quota configuration. // Get - Get quota configuration.
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) { func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
if globalIsGateway {
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
return &madmin.BucketQuota{}, nil
}
qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName) qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
return qCfg, err return qCfg, err
} }
+71 -15
View File
@@ -25,10 +25,10 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/gorilla/mux"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
@@ -46,10 +46,6 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return return
} }
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
@@ -59,7 +55,10 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
if globalSiteReplicationSys.isEnabled() && logger.GetReqInfo(ctx).Cred.AccessKey != globalActiveCred.AccessKey {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
return
}
if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned { if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL)
return return
@@ -170,6 +169,21 @@ func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.Respons
return return
} }
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
for _, tgt := range targets.Targets {
if err := globalBucketTargetSys.RemoveTarget(ctx, bucket, tgt.Arn); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
}
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketTargetsFile); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Write success response. // Write success response.
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }
@@ -217,7 +231,19 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseW
w.Header().Set(xhttp.ContentType, string(mimeJSON)) w.Header().Set(xhttp.ContentType, string(mimeJSON))
enc := json.NewEncoder(w) enc := json.NewEncoder(w)
if err = enc.Encode(globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)); err != nil { stats := globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
bwMap := bwRpt.BucketStats[bucket]
for arn, st := range stats.Stats {
if bwMap != nil {
if bw, ok := bwMap[arn]; ok {
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
stats.Stats[arn] = st
}
}
}
if err = enc.Encode(stats); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@@ -277,7 +303,13 @@ func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseW
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
if !config.HasExistingObjectReplication(arn) { hasARN, hasExistingObjEnabled := config.HasExistingObjectReplication(arn)
if !hasARN {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrRemoteTargetNotFoundError), r.URL)
return
}
if !hasExistingObjEnabled {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoExistingObjects), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoExistingObjects), r.URL)
return return
} }
@@ -316,7 +348,27 @@ func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseW
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
} }
} }
if err := startReplicationResync(ctx, bucket, arn, resetID, resetBeforeDate, objectAPI); err != nil { targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
if err != nil {
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
return
}
tgtBytes, err := json.Marshal(&targets)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
return
}
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
return
}
if err := globalReplicationPool.resyncer.start(ctx, objectAPI, resyncOpts{
bucket: bucket,
arn: arn,
resyncID: resetID,
resyncBefore: resetBeforeDate,
}); err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
Bucket: bucket, Bucket: bucket,
Err: err, Err: err,
@@ -365,10 +417,13 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
var tgtStats map[string]TargetReplicationResyncStatus
globalReplicationPool.resyncState.RLock() globalReplicationPool.resyncer.RLock()
brs, ok := globalReplicationPool.resyncState.statusMap[bucket] brs, ok := globalReplicationPool.resyncer.statusMap[bucket]
globalReplicationPool.resyncState.RUnlock() if ok {
tgtStats = brs.cloneTgtStats()
}
globalReplicationPool.resyncer.RUnlock()
if !ok { if !ok {
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI) brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
if err != nil { if err != nil {
@@ -378,10 +433,11 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
}), r.URL) }), r.URL)
return return
} }
tgtStats = brs.cloneTgtStats()
} }
var rinfo ResyncTargetsInfo var rinfo ResyncTargetsInfo
for tarn, st := range brs.TargetsMap { for tarn, st := range tgtStats {
if arn != "" && tarn != arn { if arn != "" && tarn != arn {
continue continue
} }
@@ -389,7 +445,7 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
Arn: tarn, Arn: tarn,
ResetID: st.ResyncID, ResetID: st.ResyncID,
StartTime: st.StartTime, StartTime: st.StartTime,
EndTime: st.EndTime, EndTime: st.LastUpdate,
ResyncStatus: st.ResyncStatus.String(), ResyncStatus: st.ResyncStatus.String(),
ReplicatedSize: st.ReplicatedSize, ReplicatedSize: st.ReplicatedSize,
ReplicatedCount: st.ReplicatedCount, ReplicatedCount: st.ReplicatedCount,
+30 -15
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"context" "context"
"encoding/binary"
"math" "math"
"sync" "sync"
"time" "time"
@@ -37,12 +38,12 @@ func (b *BucketReplicationStats) hasReplicationUsage() bool {
// ReplicationStats holds the global in-memory replication stats // ReplicationStats holds the global in-memory replication stats
type ReplicationStats struct { type ReplicationStats struct {
Cache map[string]*BucketReplicationStats Cache map[string]*BucketReplicationStats
UsageCache map[string]*BucketReplicationStats UsageCache map[string]*BucketReplicationStats
mostRecentStats BucketStatsMap mostRecentStats BucketStatsMap
sync.RWMutex // mutex for Cache sync.RWMutex // mutex for Cache
ulock sync.RWMutex // mutex for UsageCache ulock sync.RWMutex // mutex for UsageCache
dlock sync.RWMutex // mutex for mostRecentStats mostRecentStatsMu sync.Mutex // mutex for mostRecentStats
} }
// Delete deletes in-memory replication statistics for a bucket. // Delete deletes in-memory replication statistics for a bucket.
@@ -196,7 +197,8 @@ func (r *ReplicationStats) loadInitialReplicationMetrics(ctx context.Context) {
m := make(map[string]*BucketReplicationStats) m := make(map[string]*BucketReplicationStats)
if stats, err := globalReplicationPool.loadStatsFromDisk(); err == nil { if stats, err := globalReplicationPool.loadStatsFromDisk(); err == nil {
for b, st := range stats { for b, st := range stats {
m[b] = &st c := st.Clone()
m[b] = &c
} }
r.ulock.Lock() r.ulock.Lock()
r.UsageCache = m r.UsageCache = m
@@ -245,13 +247,23 @@ outer:
r.ulock.Unlock() r.ulock.Unlock()
} }
func (r *ReplicationStats) getAllCachedLatest() BucketStatsMap { // serializeStats will serialize the current stats.
// Will return (nil, nil) if no data.
func (r *ReplicationStats) serializeStats() ([]byte, error) {
if r == nil { if r == nil {
return BucketStatsMap{} return nil, nil
} }
r.dlock.RLock() r.mostRecentStatsMu.Lock()
defer r.dlock.RUnlock() defer r.mostRecentStatsMu.Unlock()
return r.mostRecentStats if len(r.mostRecentStats.Stats) == 0 {
return nil, nil
}
data := make([]byte, 4, 4+r.mostRecentStats.Msgsize())
// Add the replication stats meta header.
binary.LittleEndian.PutUint16(data[0:2], replStatsMetaFormat)
binary.LittleEndian.PutUint16(data[2:4], replStatsVersion)
// Add data
return r.mostRecentStats.MarshalMsg(data)
} }
func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo) (bucketsReplicationStats map[string]BucketReplicationStats) { func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo) (bucketsReplicationStats map[string]BucketReplicationStats) {
@@ -321,6 +333,7 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u Buck
s = BucketReplicationStats{ s = BucketReplicationStats{
Stats: make(map[string]*BucketReplicationStat, len(stats)), Stats: make(map[string]*BucketReplicationStat, len(stats)),
} }
var latestTotReplicatedSize int64 var latestTotReplicatedSize int64
for _, st := range u.ReplicationInfo { for _, st := range u.ReplicationInfo {
latestTotReplicatedSize += int64(st.ReplicatedSize) latestTotReplicatedSize += int64(st.ReplicatedSize)
@@ -356,13 +369,15 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u Buck
// normalize overall stats // normalize overall stats
s.ReplicaSize = int64(math.Max(float64(totReplicaSize), float64(u.ReplicaSize))) s.ReplicaSize = int64(math.Max(float64(totReplicaSize), float64(u.ReplicaSize)))
s.ReplicatedSize = int64(math.Max(float64(s.ReplicatedSize), float64(latestTotReplicatedSize))) s.ReplicatedSize = int64(math.Max(float64(s.ReplicatedSize), float64(latestTotReplicatedSize)))
r.dlock.Lock() r.mostRecentStatsMu.Lock()
if len(r.mostRecentStats.Stats) == 0 { if len(r.mostRecentStats.Stats) == 0 {
r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()} r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()}
} }
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s} if len(s.Stats) > 0 {
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s}
}
r.mostRecentStats.Timestamp = UTCNow() r.mostRecentStats.Timestamp = UTCNow()
r.dlock.Unlock() r.mostRecentStatsMu.Unlock()
return s return s
} }
+38 -9
View File
@@ -28,7 +28,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
) )
@@ -628,26 +628,40 @@ func (v VersionPurgeStatusType) Pending() bool {
return v == Pending || v == Failed return v == Pending || v == Failed
} }
type replicationResyncState struct { type replicationResyncer struct {
// map of bucket to their resync status // map of bucket to their resync status
statusMap map[string]BucketReplicationResyncStatus statusMap map[string]BucketReplicationResyncStatus
workerSize int
resyncCancelCh chan struct{}
workerCh chan struct{}
sync.RWMutex sync.RWMutex
} }
const ( const (
replicationDir = "replication" replicationDir = ".replication"
resyncFileName = "resync.bin" resyncFileName = "resync.bin"
resyncMetaFormat = 1 resyncMetaFormat = 1
resyncMetaVersionV1 = 1 resyncMetaVersionV1 = 1
resyncMetaVersion = resyncMetaVersionV1 resyncMetaVersion = resyncMetaVersionV1
) )
type resyncOpts struct {
bucket string
arn string
resyncID string
resyncBefore time.Time
}
// ResyncStatusType status of resync operation // ResyncStatusType status of resync operation
type ResyncStatusType int type ResyncStatusType int
const ( const (
// NoResync - no resync in progress // NoResync - no resync in progress
NoResync ResyncStatusType = iota NoResync ResyncStatusType = iota
// ResyncPending - resync pending
ResyncPending
// ResyncCanceled - resync canceled
ResyncCanceled
// ResyncStarted - resync in progress // ResyncStarted - resync in progress
ResyncStarted ResyncStarted
// ResyncCompleted - resync finished // ResyncCompleted - resync finished
@@ -656,6 +670,10 @@ const (
ResyncFailed ResyncFailed
) )
func (rt ResyncStatusType) isValid() bool {
return rt != NoResync
}
func (rt ResyncStatusType) String() string { func (rt ResyncStatusType) String() string {
switch rt { switch rt {
case ResyncStarted: case ResyncStarted:
@@ -664,6 +682,10 @@ func (rt ResyncStatusType) String() string {
return "Completed" return "Completed"
case ResyncFailed: case ResyncFailed:
return "Failed" return "Failed"
case ResyncPending:
return "Pending"
case ResyncCanceled:
return "Canceled"
default: default:
return "" return ""
} }
@@ -671,8 +693,8 @@ func (rt ResyncStatusType) String() string {
// TargetReplicationResyncStatus status of resync of bucket for a specific target // TargetReplicationResyncStatus status of resync of bucket for a specific target
type TargetReplicationResyncStatus struct { type TargetReplicationResyncStatus struct {
StartTime time.Time `json:"startTime" msg:"st"` StartTime time.Time `json:"startTime" msg:"st"`
EndTime time.Time `json:"endTime" msg:"et"` LastUpdate time.Time `json:"lastUpdated" msg:"lst"`
// Resync ID assigned to this reset // Resync ID assigned to this reset
ResyncID string `json:"resyncID" msg:"id"` ResyncID string `json:"resyncID" msg:"id"`
// ResyncBeforeDate - resync all objects created prior to this date // ResyncBeforeDate - resync all objects created prior to this date
@@ -701,6 +723,14 @@ type BucketReplicationResyncStatus struct {
LastUpdate time.Time `json:"lastUpdate" msg:"lu"` LastUpdate time.Time `json:"lastUpdate" msg:"lu"`
} }
func (rs *BucketReplicationResyncStatus) cloneTgtStats() (m map[string]TargetReplicationResyncStatus) {
m = make(map[string]TargetReplicationResyncStatus)
for arn, st := range rs.TargetsMap {
m[arn] = st
}
return
}
func newBucketResyncStatus(bucket string) BucketReplicationResyncStatus { func newBucketResyncStatus(bucket string) BucketReplicationResyncStatus {
return BucketReplicationResyncStatus{ return BucketReplicationResyncStatus{
TargetsMap: make(map[string]TargetReplicationResyncStatus), TargetsMap: make(map[string]TargetReplicationResyncStatus),
@@ -767,9 +797,8 @@ func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry {
} }
} }
func getReplicationStatsPath(nodeName string) string { func getReplicationStatsPath() string {
nodeStr := strings.ReplaceAll(nodeName, ":", "_") return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + "replication.stats"
return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + nodeStr + ".stats"
} }
const ( const (
+14 -14
View File
@@ -2117,10 +2117,10 @@ func (z *TargetReplicationResyncStatus) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "StartTime") err = msgp.WrapError(err, "StartTime")
return return
} }
case "et": case "lst":
z.EndTime, err = dc.ReadTime() z.LastUpdate, err = dc.ReadTime()
if err != nil { if err != nil {
err = msgp.WrapError(err, "EndTime") err = msgp.WrapError(err, "LastUpdate")
return return
} }
case "id": case "id":
@@ -2205,14 +2205,14 @@ func (z *TargetReplicationResyncStatus) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "StartTime") err = msgp.WrapError(err, "StartTime")
return return
} }
// write "et" // write "lst"
err = en.Append(0xa2, 0x65, 0x74) err = en.Append(0xa3, 0x6c, 0x73, 0x74)
if err != nil { if err != nil {
return return
} }
err = en.WriteTime(z.EndTime) err = en.WriteTime(z.LastUpdate)
if err != nil { if err != nil {
err = msgp.WrapError(err, "EndTime") err = msgp.WrapError(err, "LastUpdate")
return return
} }
// write "id" // write "id"
@@ -2315,9 +2315,9 @@ func (z *TargetReplicationResyncStatus) MarshalMsg(b []byte) (o []byte, err erro
// string "st" // string "st"
o = append(o, 0x8b, 0xa2, 0x73, 0x74) o = append(o, 0x8b, 0xa2, 0x73, 0x74)
o = msgp.AppendTime(o, z.StartTime) o = msgp.AppendTime(o, z.StartTime)
// string "et" // string "lst"
o = append(o, 0xa2, 0x65, 0x74) o = append(o, 0xa3, 0x6c, 0x73, 0x74)
o = msgp.AppendTime(o, z.EndTime) o = msgp.AppendTime(o, z.LastUpdate)
// string "id" // string "id"
o = append(o, 0xa2, 0x69, 0x64) o = append(o, 0xa2, 0x69, 0x64)
o = msgp.AppendString(o, z.ResyncID) o = msgp.AppendString(o, z.ResyncID)
@@ -2372,10 +2372,10 @@ func (z *TargetReplicationResyncStatus) UnmarshalMsg(bts []byte) (o []byte, err
err = msgp.WrapError(err, "StartTime") err = msgp.WrapError(err, "StartTime")
return return
} }
case "et": case "lst":
z.EndTime, bts, err = msgp.ReadTimeBytes(bts) z.LastUpdate, bts, err = msgp.ReadTimeBytes(bts)
if err != nil { if err != nil {
err = msgp.WrapError(err, "EndTime") err = msgp.WrapError(err, "LastUpdate")
return return
} }
case "id": case "id":
@@ -2450,7 +2450,7 @@ func (z *TargetReplicationResyncStatus) UnmarshalMsg(bts []byte) (o []byte, err
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *TargetReplicationResyncStatus) Msgsize() (s int) { func (z *TargetReplicationResyncStatus) Msgsize() (s int) {
s = 1 + 3 + msgp.TimeSize + 3 + msgp.TimeSize + 3 + msgp.StringPrefixSize + len(z.ResyncID) + 4 + msgp.TimeSize + 4 + msgp.IntSize + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len(z.Bucket) + 4 + msgp.StringPrefixSize + len(z.Object) s = 1 + 3 + msgp.TimeSize + 4 + msgp.TimeSize + 3 + msgp.StringPrefixSize + len(z.ResyncID) + 4 + msgp.TimeSize + 4 + msgp.IntSize + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len(z.Bucket) + 4 + msgp.StringPrefixSize + len(z.Object)
return return
} }
+431 -309
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -24,7 +24,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
) )
+4
View File
@@ -120,6 +120,10 @@ type BucketReplicationStat struct {
FailedCount int64 `json:"failedReplicationCount"` FailedCount int64 `json:"failedReplicationCount"`
// Replication latency information // Replication latency information
Latency ReplicationLatency `json:"replicationLatency"` Latency ReplicationLatency `json:"replicationLatency"`
// bandwidth limit for target
BandWidthLimitInBytesPerSecond int64 `json:"limitInBits"`
// current bandwidth reported
CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"`
} }
func (bs *BucketReplicationStat) hasReplicationUsage() bool { func (bs *BucketReplicationStat) hasReplicationUsage() bool {
+55 -5
View File
@@ -89,6 +89,18 @@ func (z *BucketReplicationStat) DecodeMsg(dc *msgp.Reader) (err error) {
} }
} }
} }
case "BandWidthLimitInBytesPerSecond":
z.BandWidthLimitInBytesPerSecond, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
return
}
case "CurrentBandwidthInBytesPerSecond":
z.CurrentBandwidthInBytesPerSecond, err = dc.ReadFloat64()
if err != nil {
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
return
}
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@@ -102,9 +114,9 @@ func (z *BucketReplicationStat) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) { func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 7 // map header, size 9
// write "PendingSize" // write "PendingSize"
err = en.Append(0x87, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65) err = en.Append(0x89, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
if err != nil { if err != nil {
return return
} }
@@ -179,15 +191,35 @@ func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Latency", "UploadHistogram") err = msgp.WrapError(err, "Latency", "UploadHistogram")
return return
} }
// write "BandWidthLimitInBytesPerSecond"
err = en.Append(0xbe, 0x42, 0x61, 0x6e, 0x64, 0x57, 0x69, 0x64, 0x74, 0x68, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
if err != nil {
return
}
err = en.WriteInt64(z.BandWidthLimitInBytesPerSecond)
if err != nil {
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
return
}
// write "CurrentBandwidthInBytesPerSecond"
err = en.Append(0xd9, 0x20, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
if err != nil {
return
}
err = en.WriteFloat64(z.CurrentBandwidthInBytesPerSecond)
if err != nil {
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
return
}
return return
} }
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) { func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 7 // map header, size 9
// string "PendingSize" // string "PendingSize"
o = append(o, 0x87, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65) o = append(o, 0x89, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
o = msgp.AppendInt64(o, z.PendingSize) o = msgp.AppendInt64(o, z.PendingSize)
// string "ReplicatedSize" // string "ReplicatedSize"
o = append(o, 0xae, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65) o = append(o, 0xae, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65)
@@ -214,6 +246,12 @@ func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Latency", "UploadHistogram") err = msgp.WrapError(err, "Latency", "UploadHistogram")
return return
} }
// string "BandWidthLimitInBytesPerSecond"
o = append(o, 0xbe, 0x42, 0x61, 0x6e, 0x64, 0x57, 0x69, 0x64, 0x74, 0x68, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
o = msgp.AppendInt64(o, z.BandWidthLimitInBytesPerSecond)
// string "CurrentBandwidthInBytesPerSecond"
o = append(o, 0xd9, 0x20, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
o = msgp.AppendFloat64(o, z.CurrentBandwidthInBytesPerSecond)
return return
} }
@@ -300,6 +338,18 @@ func (z *BucketReplicationStat) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
} }
} }
case "BandWidthLimitInBytesPerSecond":
z.BandWidthLimitInBytesPerSecond, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
return
}
case "CurrentBandwidthInBytesPerSecond":
z.CurrentBandwidthInBytesPerSecond, bts, err = msgp.ReadFloat64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
return
}
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@@ -314,7 +364,7 @@ func (z *BucketReplicationStat) UnmarshalMsg(bts []byte) (o []byte, err error) {
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *BucketReplicationStat) Msgsize() (s int) { func (z *BucketReplicationStat) Msgsize() (s int) {
s = 1 + 12 + msgp.Int64Size + 15 + msgp.Int64Size + 12 + msgp.Int64Size + 11 + msgp.Int64Size + 13 + msgp.Int64Size + 12 + msgp.Int64Size + 8 + 1 + 16 + z.Latency.UploadHistogram.Msgsize() s = 1 + 12 + msgp.Int64Size + 15 + msgp.Int64Size + 12 + msgp.Int64Size + 11 + msgp.Int64Size + 13 + msgp.Int64Size + 12 + msgp.Int64Size + 8 + 1 + 16 + z.Latency.UploadHistogram.Msgsize() + 31 + msgp.Int64Size + 34 + msgp.Float64Size
return return
} }
+57 -40
View File
@@ -25,9 +25,8 @@ import (
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
miniogo "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
"github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/crypto"
@@ -99,7 +98,7 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
select { select {
case <-hcTimer.C: case <-hcTimer.C:
sys.hMutex.RLock() sys.hMutex.RLock()
var eps []madmin.ServerProperties eps := make([]madmin.ServerProperties, 0, len(sys.hc))
for _, ep := range sys.hc { for _, ep := range sys.hc {
eps = append(eps, madmin.ServerProperties{Endpoint: ep.Endpoint, Scheme: ep.Scheme}) eps = append(eps, madmin.ServerProperties{Endpoint: ep.Endpoint, Scheme: ep.Scheme})
} }
@@ -107,16 +106,12 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
if len(eps) > 0 { if len(eps) > 0 {
cctx, cancel := context.WithTimeout(ctx, 30*time.Second) cctx, cancel := context.WithTimeout(ctx, 30*time.Second)
m := map[string]epHealth{} m := make(map[string]epHealth, len(eps))
for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) { for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) {
var online bool
if result.Error == nil {
online = result.Online
}
m[result.Endpoint.Host] = epHealth{ m[result.Endpoint.Host] = epHealth{
Endpoint: result.Endpoint.Host, Endpoint: result.Endpoint.Host,
Scheme: result.Endpoint.Scheme, Scheme: result.Endpoint.Scheme,
Online: online, Online: result.Online,
} }
} }
cancel() cancel()
@@ -208,9 +203,6 @@ func (sys *BucketTargetSys) Delete(bucket string) {
// SetTarget - sets a new minio-go client target for this bucket. // SetTarget - sets a new minio-go client target for this bucket.
func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget, update bool) error { func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget, update bool) error {
if globalIsGateway {
return nil
}
if !tgt.Type.IsValid() && !update { if !tgt.Type.IsValid() && !update {
return BucketRemoteArnTypeInvalid{Bucket: bucket} return BucketRemoteArnTypeInvalid{Bucket: bucket}
} }
@@ -221,10 +213,13 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
// validate if target credentials are ok // validate if target credentials are ok
exists, err := clnt.BucketExists(ctx, tgt.TargetBucket) exists, err := clnt.BucketExists(ctx, tgt.TargetBucket)
if err != nil { if err != nil {
if minio.ToErrorResponse(err).Code == "NoSuchBucket" { switch minio.ToErrorResponse(err).Code {
case "NoSuchBucket":
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket} return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
case "AccessDenied":
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err}
} }
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err} return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err}
} }
if !exists { if !exists {
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket} return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
@@ -235,7 +230,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
} }
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket) vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
if err != nil { if err != nil {
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err} return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err, AccessKey: tgt.Credentials.AccessKey}
} }
if !vcfg.Enabled() { if !vcfg.Enabled() {
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket} return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
@@ -257,6 +252,10 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
found = true found = true
continue continue
} }
// fail if endpoint is already present in list of targets and not a matching ARN
if t.Endpoint == tgt.Endpoint {
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
}
} }
newtgts[idx] = t newtgts[idx] = t
} }
@@ -266,29 +265,22 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
sys.targetsMap[bucket] = newtgts sys.targetsMap[bucket] = newtgts
sys.arnRemotesMap[tgt.Arn] = clnt sys.arnRemotesMap[tgt.Arn] = clnt
sys.updateBandwidthLimit(bucket, tgt.BandwidthLimit) sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
return nil return nil
} }
func (sys *BucketTargetSys) updateBandwidthLimit(bucket string, limit int64) { func (sys *BucketTargetSys) updateBandwidthLimit(bucket, arn string, limit int64) {
if globalIsGateway {
return
}
if limit == 0 { if limit == 0 {
globalBucketMonitor.DeleteBucket(bucket) globalBucketMonitor.DeleteBucketThrottle(bucket, arn)
return return
} }
// Setup bandwidth throttling // Setup bandwidth throttling
globalBucketMonitor.SetBandwidthLimit(bucket, limit) globalBucketMonitor.SetBandwidthLimit(bucket, arn, limit)
} }
// RemoveTarget - removes a remote bucket target for this source bucket. // RemoveTarget - removes a remote bucket target for this source bucket.
func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr string) error { func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr string) error {
if globalIsGateway {
return nil
}
if arnStr == "" { if arnStr == "" {
return BucketRemoteArnInvalid{Bucket: bucket} return BucketRemoteArnInvalid{Bucket: bucket}
} }
@@ -336,7 +328,7 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
} }
sys.targetsMap[bucket] = targets sys.targetsMap[bucket] = targets
delete(sys.arnRemotesMap, arnStr) delete(sys.arnRemotesMap, arnStr)
sys.updateBandwidthLimit(bucket, 0) sys.updateBandwidthLimit(bucket, arnStr, 0)
return nil return nil
} }
@@ -406,7 +398,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
// No need for more if not adding anything // No need for more if not adding anything
if tgts == nil || tgts.Empty() { if tgts == nil || tgts.Empty() {
sys.updateBandwidthLimit(bucket, 0) globalBucketMonitor.DeleteBucket(bucket)
return return
} }
@@ -419,7 +411,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
continue continue
} }
sys.arnRemotesMap[tgt.Arn] = tgtClient sys.arnRemotesMap[tgt.Arn] = tgtClient
sys.updateBandwidthLimit(bucket, tgt.BandwidthLimit) sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
} }
sys.targetsMap[bucket] = tgts.Targets sys.targetsMap[bucket] = tgts.Targets
} }
@@ -442,7 +434,7 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
continue continue
} }
sys.arnRemotesMap[tgt.Arn] = tgtClient sys.arnRemotesMap[tgt.Arn] = tgtClient
sys.updateBandwidthLimit(bucket.Name, tgt.BandwidthLimit) sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit)
} }
sys.targetsMap[bucket.Name] = cfg.Targets sys.targetsMap[bucket.Name] = cfg.Targets
} }
@@ -452,7 +444,7 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
config := tcfg.Credentials config := tcfg.Credentials
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "") creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
api, err := minio.New(tcfg.Endpoint, &miniogo.Options{ api, err := minio.New(tcfg.Endpoint, &minio.Options{
Creds: creds, Creds: creds,
Secure: tcfg.Secure, Secure: tcfg.Secure,
Region: tcfg.Region, Region: tcfg.Region,
@@ -461,6 +453,8 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
if err != nil { if err != nil {
return nil, err return nil, err
} }
api.SetAppInfo("minio-replication-target", ReleaseTag+" "+tcfg.Arn)
hcDuration := defaultHealthCheckDuration hcDuration := defaultHealthCheckDuration
if tcfg.HealthCheckDuration >= 1 { // require minimum health check duration of 1 sec. if tcfg.HealthCheckDuration >= 1 { // require minimum health check duration of 1 sec.
hcDuration = tcfg.HealthCheckDuration hcDuration = tcfg.HealthCheckDuration
@@ -481,27 +475,50 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
} }
// getRemoteARN gets existing ARN for an endpoint or generates a new one. // getRemoteARN gets existing ARN for an endpoint or generates a new one.
func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget) string { func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget, deplID string) (arn string, exists bool) {
if target == nil { if target == nil {
return "" return
} }
tgts := sys.targetsMap[bucket] tgts := sys.targetsMap[bucket]
for _, tgt := range tgts { for _, tgt := range tgts {
if tgt.Type == target.Type && tgt.TargetBucket == target.TargetBucket && target.URL().String() == tgt.URL().String() { if tgt.Type == target.Type &&
return tgt.Arn tgt.TargetBucket == target.TargetBucket &&
target.URL().String() == tgt.URL().String() &&
tgt.Credentials.AccessKey == target.Credentials.AccessKey {
return tgt.Arn, true
} }
} }
if !target.Type.IsValid() { if !target.Type.IsValid() {
return "" return
} }
return generateARN(target) return generateARN(target, deplID), false
}
// getRemoteARNForPeer returns the remote target for a peer site in site replication
func (sys *BucketTargetSys) getRemoteARNForPeer(bucket string, peer madmin.PeerInfo) string {
tgts := sys.targetsMap[bucket]
for _, target := range tgts {
ep, _ := url.Parse(peer.Endpoint)
if target.SourceBucket == bucket &&
target.TargetBucket == bucket &&
target.Endpoint == ep.Host &&
target.Secure == (ep.Scheme == "https") &&
target.Type == madmin.ReplicationService {
return target.Arn
}
}
return ""
} }
// generate ARN that is unique to this target type // generate ARN that is unique to this target type
func generateARN(t *madmin.BucketTarget) string { func generateARN(t *madmin.BucketTarget, deplID string) string {
uuid := deplID
if uuid == "" {
uuid = mustGetUUID()
}
arn := madmin.ARN{ arn := madmin.ARN{
Type: t.Type, Type: t.Type,
ID: mustGetUUID(), ID: uuid,
Region: t.Region, Region: t.Region,
Bucket: t.TargetBucket, Bucket: t.TargetBucket,
} }
@@ -543,7 +560,7 @@ func parseBucketTargetConfig(bucket string, cdata, cmetadata []byte) (*madmin.Bu
// TargetClient is the struct for remote target client. // TargetClient is the struct for remote target client.
type TargetClient struct { type TargetClient struct {
*miniogo.Client *minio.Client
healthCheckDuration time.Duration healthCheckDuration time.Duration
Bucket string // remote bucket target Bucket string // remote bucket target
replicateSync bool replicateSync bool
+4 -7
View File
@@ -24,10 +24,10 @@ import (
"net/http" "net/http"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/gorilla/mux" "github.com/minio/madmin-go/v2"
"github.com/minio/madmin-go"
"github.com/minio/minio/internal/bucket/versioning" "github.com/minio/minio/internal/bucket/versioning"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
@@ -108,15 +108,12 @@ func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r
// We encode the xml bytes as base64 to ensure there are no encoding // We encode the xml bytes as base64 to ensure there are no encoding
// errors. // errors.
cfgStr := base64.StdEncoding.EncodeToString(configData) cfgStr := base64.StdEncoding.EncodeToString(configData)
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{ logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypeVersionConfig, Type: madmin.SRBucketMetaTypeVersionConfig,
Bucket: bucket, Bucket: bucket,
Versioning: &cfgStr, Versioning: &cfgStr,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
}); err != nil { }))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }
-5
View File
@@ -69,11 +69,6 @@ func (sys *BucketVersioningSys) PrefixSuspended(bucket, prefix string) bool {
// Get returns stored bucket policy // Get returns stored bucket policy
func (sys *BucketVersioningSys) Get(bucket string) (*versioning.Versioning, error) { func (sys *BucketVersioningSys) Get(bucket string) (*versioning.Versioning, error) {
if globalIsGateway {
// Gateway does not implement versioning.
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, nil
}
if bucket == minioMetaBucket || strings.HasPrefix(bucket, minioMetaBucket) { if bucket == minioMetaBucket || strings.HasPrefix(bucket, minioMetaBucket) {
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, nil return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, nil
} }
+114 -64
View File
@@ -18,53 +18,28 @@
package cmd package cmd
import ( import (
"bytes"
"compress/gzip"
"context" "context"
"encoding/json"
"errors"
"fmt" "fmt"
"math/rand" "math/rand"
"net/url"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
uatomic "go.uber.org/atomic"
) )
const ( var callhomeLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
// callhomeSchemaVersion1 is callhome schema version 1
callhomeSchemaVersion1 = "1"
// callhomeSchemaVersion is current callhome schema version.
callhomeSchemaVersion = callhomeSchemaVersion1
// callhomeCycleDefault is the default interval between two callhome cycles (24hrs)
callhomeCycleDefault = 24 * time.Hour
)
// CallhomeInfo - Contains callhome information
type CallhomeInfo struct {
SchemaVersion string `json:"schema_version"`
AdminInfo madmin.InfoMessage `json:"admin_info"`
}
var (
enableCallhome = uatomic.NewBool(false)
callhomeLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
callhomeFreq = uatomic.NewDuration(callhomeCycleDefault)
)
func updateCallhomeParams(ctx context.Context, objAPI ObjectLayer) {
alreadyEnabled := enableCallhome.Load()
enableCallhome.Store(globalCallhomeConfig.Enable)
callhomeFreq.Store(globalCallhomeConfig.Frequency)
// If callhome was disabled earlier and has now been enabled,
// initialize the callhome process again.
if !alreadyEnabled && enableCallhome.Load() {
initCallhome(ctx, objAPI)
}
}
// initCallhome will start the callhome task in the background. // initCallhome will start the callhome task in the background.
func initCallhome(ctx context.Context, objAPI ObjectLayer) { func initCallhome(ctx context.Context, objAPI ObjectLayer) {
if !globalCallhomeConfig.Enabled() {
return
}
go func() { go func() {
r := rand.New(rand.NewSource(time.Now().UnixNano())) r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Leader node (that successfully acquires the lock inside runCallhome) // Leader node (that successfully acquires the lock inside runCallhome)
@@ -72,79 +47,154 @@ func initCallhome(ctx context.Context, objAPI ObjectLayer) {
// the lock will be released and another node will acquire it and take over // the lock will be released and another node will acquire it and take over
// because of this loop. // because of this loop.
for { for {
runCallhome(ctx, objAPI) if !globalCallhomeConfig.Enabled() {
if !enableCallhome.Load() { return
}
if !runCallhome(ctx, objAPI) {
// callhome was disabled or context was canceled
return return
} }
// callhome running on a different node. // callhome running on a different node.
// sleep for some time and try again. // sleep for some time and try again.
duration := time.Duration(r.Float64() * float64(callhomeFreq.Load())) duration := time.Duration(r.Float64() * float64(globalCallhomeConfig.FrequencyDur()))
if duration < time.Second { if duration < time.Second {
// Make sure to sleep atleast a second to avoid high CPU ticks. // Make sure to sleep atleast a second to avoid high CPU ticks.
duration = time.Second duration = time.Second
} }
time.Sleep(duration) time.Sleep(duration)
if !enableCallhome.Load() {
return
}
} }
}() }()
} }
func runCallhome(ctx context.Context, objAPI ObjectLayer) { func runCallhome(ctx context.Context, objAPI ObjectLayer) bool {
// Make sure only 1 callhome is running on the cluster. // Make sure only 1 callhome is running on the cluster.
locker := objAPI.NewNSLock(minioMetaBucket, "callhome/runCallhome.lock") locker := objAPI.NewNSLock(minioMetaBucket, "callhome/runCallhome.lock")
lkctx, err := locker.GetLock(ctx, callhomeLeaderLockTimeout) lkctx, err := locker.GetLock(ctx, callhomeLeaderLockTimeout)
if err != nil { if err != nil {
return // lock timedout means some other node is the leader,
// cycle back return 'true'
return true
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer locker.Unlock(lkctx.Cancel) defer locker.Unlock(lkctx)
callhomeTimer := time.NewTimer(callhomeFreq.Load()) callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur())
defer callhomeTimer.Stop() defer callhomeTimer.Stop()
for { for {
if !globalCallhomeConfig.Enabled() {
// Stop the processing as callhome got disabled
return false
}
select { select {
case <-ctx.Done(): case <-ctx.Done():
return // indicates that we do not need to run callhome anymore
return false
case <-callhomeTimer.C: case <-callhomeTimer.C:
if !enableCallhome.Load() { if !globalCallhomeConfig.Enabled() {
// Stop the processing as callhome got disabled // Stop the processing as callhome got disabled
return return false
} }
performCallhome(ctx) performCallhome(ctx)
// Reset the timer for next cycle. // Reset the timer for next cycle.
callhomeTimer.Reset(callhomeFreq.Load()) callhomeTimer.Reset(globalCallhomeConfig.FrequencyDur())
} }
} }
} }
func performCallhome(ctx context.Context) { func performCallhome(ctx context.Context) {
err := sendCallhomeInfo( deadline := 10 * time.Second // Default deadline is 10secs for callhome
CallhomeInfo{ objectAPI := newObjectLayerFn()
SchemaVersion: callhomeSchemaVersion, if objectAPI == nil {
AdminInfo: getServerInfo(ctx, nil), logger.LogIf(ctx, errors.New("Callhome: object layer not ready"))
}) return
if err != nil { }
logger.LogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
healthCtx, healthCancel := context.WithTimeout(ctx, deadline)
defer healthCancel()
healthInfoCh := make(chan madmin.HealthInfo)
query := url.Values{}
for _, k := range madmin.HealthDataTypesList {
query.Set(string(k), "true")
}
healthInfo := madmin.HealthInfo{
TimeStamp: time.Now().UTC(),
Version: madmin.HealthInfoVersion,
Minio: madmin.MinioHealthInfo{
Info: madmin.MinioInfo{
DeploymentID: globalDeploymentID,
},
},
}
go fetchHealthInfo(healthCtx, objectAPI, &query, healthInfoCh, healthInfo)
for {
select {
case hi, hasMore := <-healthInfoCh:
if !hasMore {
// Received all data. Send to SUBNET and return
err := sendHealthInfo(ctx, healthInfo)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
}
return
}
healthInfo = hi
case <-healthCtx.Done():
return
}
} }
} }
const ( const (
callhomeURL = "https://subnet.min.io/api/callhome" healthURL = "https://subnet.min.io/api/health/upload"
callhomeURLDev = "http://localhost:9000/api/callhome" healthURLDev = "http://localhost:9000/api/health/upload"
) )
func sendCallhomeInfo(ch CallhomeInfo) error { func sendHealthInfo(ctx context.Context, healthInfo madmin.HealthInfo) error {
url := callhomeURL url := healthURL
if globalIsCICD { if globalIsCICD {
url = callhomeURLDev url = healthURLDev
} }
_, err := globalSubnetConfig.Post(url, ch)
filename := fmt.Sprintf("health_%s.json.gz", UTCNow().Format("20060102150405"))
url += "?filename=" + filename
_, err := globalSubnetConfig.Upload(url, filename, createHealthJSONGzip(ctx, healthInfo))
return err return err
} }
func createHealthJSONGzip(ctx context.Context, healthInfo madmin.HealthInfo) []byte {
var b bytes.Buffer
gzWriter := gzip.NewWriter(&b)
header := struct {
Version string `json:"version"`
}{Version: healthInfo.Version}
enc := json.NewEncoder(gzWriter)
if e := enc.Encode(header); e != nil {
logger.LogIf(ctx, fmt.Errorf("Could not encode health info header: %w", e))
return nil
}
if e := enc.Encode(healthInfo); e != nil {
logger.LogIf(ctx, fmt.Errorf("Could not encode health info: %w", e))
return nil
}
gzWriter.Flush()
gzWriter.Close()
return b.Bytes()
}
+89 -81
View File
@@ -51,8 +51,8 @@ import (
consoleCerts "github.com/minio/console/pkg/certs" consoleCerts "github.com/minio/console/pkg/certs"
"github.com/minio/console/restapi" "github.com/minio/console/restapi"
"github.com/minio/console/restapi/operations" "github.com/minio/console/restapi/operations"
"github.com/minio/kes" "github.com/minio/kes-go"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
@@ -117,7 +117,8 @@ func init() {
go func() { go func() {
var t *time.Ticker var t *time.Ticker
if containers { if containers {
t = time.NewTicker(1 * time.Minute) // k8s DNS TTL is 30s (Attempt a refresh only after)
t = time.NewTicker(30 * time.Second)
} else { } else {
t = time.NewTicker(10 * time.Minute) t = time.NewTicker(10 * time.Minute)
} }
@@ -134,7 +135,7 @@ func init() {
globalForwarder = handlers.NewForwarder(&handlers.Forwarder{ globalForwarder = handlers.NewForwarder(&handlers.Forwarder{
PassHost: true, PassHost: true,
RoundTripper: newGatewayHTTPTransport(1 * time.Hour), RoundTripper: NewHTTPTransportWithTimeout(1 * time.Hour),
Logger: func(err error) { Logger: func(err error) {
if err != nil && !errors.Is(err, context.Canceled) { if err != nil && !errors.Is(err, context.Canceled) {
logger.LogIf(GlobalContext, err) logger.LogIf(GlobalContext, err)
@@ -151,7 +152,7 @@ func init() {
defaultAWSCredProvider = []credentials.Provider{ defaultAWSCredProvider = []credentials.Provider{
&credentials.IAM{ &credentials.IAM{
Client: &http.Client{ Client: &http.Client{
Transport: NewGatewayHTTPTransport(), Transport: NewHTTPTransport(),
}, },
}, },
} }
@@ -175,7 +176,9 @@ func minioConfigToConsoleFeatures() {
if globalMinioEndpoint != "" { if globalMinioEndpoint != "" {
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint) os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
} else { } else {
os.Setenv("CONSOLE_MINIO_SERVER", getAPIEndpoints()[0]) // Explicitly set 127.0.0.1 so Console will automatically bypass TLS verification to the local S3 API.
// This will save users from providing a certificate with IP or FQDN SAN that points to the local host.
os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort))
} }
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" { if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
os.Setenv("CONSOLE_LOG_QUERY_URL", value) os.Setenv("CONSOLE_LOG_QUERY_URL", value)
@@ -207,15 +210,8 @@ func minioConfigToConsoleFeatures() {
} }
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region) os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", "")) os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
if globalSubnetConfig.License != "" {
os.Setenv("CONSOLE_SUBNET_LICENSE", globalSubnetConfig.License) globalSubnetConfig.ApplyEnv()
}
if globalSubnetConfig.APIKey != "" {
os.Setenv("CONSOLE_SUBNET_API_KEY", globalSubnetConfig.APIKey)
}
if globalSubnetConfig.ProxyURL != nil {
os.Setenv("CONSOLE_SUBNET_PROXY", globalSubnetConfig.ProxyURL.String())
}
} }
func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg { func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
@@ -236,6 +232,7 @@ func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
Userinfo: cfg.ClaimUserinfo, Userinfo: cfg.ClaimUserinfo,
RedirectCallbackDynamic: cfg.RedirectURIDynamic, RedirectCallbackDynamic: cfg.RedirectURIDynamic,
RedirectCallback: callback, RedirectCallback: callback,
EndSessionEndpoint: cfg.DiscoveryDoc.EndSessionEndpoint,
RoleArn: cfg.GetRoleArn(), RoleArn: cfg.GetRoleArn(),
} }
} }
@@ -306,22 +303,6 @@ func initConsoleServer() (*restapi.Server, error) {
return server, nil return server, nil
} }
func verifyObjectLayerFeatures(name string, objAPI ObjectLayer) {
if strings.HasPrefix(name, "gateway") {
if GlobalGatewaySSE.IsSet() && GlobalKMS == nil {
uiErr := config.ErrInvalidGWSSEEnvValue(nil).Msg("MINIO_GATEWAY_SSE set but KMS is not configured")
logger.Fatal(uiErr, "Unable to start gateway with SSE")
}
}
globalCompressConfigMu.Lock()
if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() {
logger.Fatal(errInvalidArgument,
"Compression support is requested but '%s' does not support compression", name)
}
globalCompressConfigMu.Unlock()
}
// Check for updates and print a notification message // Check for updates and print a notification message
func checkUpdate(mode string) { func checkUpdate(mode string) {
updateURL := minioReleaseInfoURL updateURL := minioReleaseInfoURL
@@ -619,13 +600,13 @@ func loadEnvVarsFromFiles() {
} }
} }
if env.IsSet(config.EnvKMSSecretKeyFile) { if env.IsSet(kms.EnvKMSSecretKeyFile) {
kmsSecret, err := readFromSecret(env.Get(config.EnvKMSSecretKeyFile, "")) kmsSecret, err := readFromSecret(env.Get(kms.EnvKMSSecretKeyFile, ""))
if err != nil { if err != nil {
logger.Fatal(err, "Unable to read the KMS secret key inherited from secret file") logger.Fatal(err, "Unable to read the KMS secret key inherited from secret file")
} }
if kmsSecret != "" { if kmsSecret != "" {
os.Setenv(config.EnvKMSSecretKey, kmsSecret) os.Setenv(kms.EnvKMSSecretKey, kmsSecret)
} }
} }
@@ -658,7 +639,7 @@ func handleCommonEnvVars() {
if !((u.Scheme == "http" || u.Scheme == "https") && if !((u.Scheme == "http" || u.Scheme == "https") &&
u.Opaque == "" && u.Opaque == "" &&
!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") { !u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u) err := fmt.Errorf("URL contains unexpected resources, expected URL to be one of http(s)://console.example.com or as a subpath via API endpoint http(s)://minio.example.com/minio format: %v", u)
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable") logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable")
} }
globalBrowserRedirectURL = u globalBrowserRedirectURL = u
@@ -793,6 +774,8 @@ func handleCommonEnvVars() {
logger.Info(color.RedBold(msg)) logger.Info(color.RedBold(msg))
} }
globalActiveCred = cred globalActiveCred = cred
} else {
globalActiveCred = auth.DefaultCredentials
} }
} }
@@ -800,20 +783,29 @@ func handleCommonEnvVars() {
// It depends on KMS env variables and global cli flags. // It depends on KMS env variables and global cli flags.
func handleKMSConfig() { func handleKMSConfig() {
switch { switch {
case env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint): case env.IsSet(kms.EnvKMSSecretKey) && env.IsSet(kms.EnvKESEndpoint):
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint)) logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKMSSecretKey, kms.EnvKESEndpoint))
} }
if env.IsSet(config.EnvKMSSecretKey) { if env.IsSet(kms.EnvKMSSecretKey) {
KMS, err := kms.Parse(env.Get(config.EnvKMSSecretKey, "")) KMS, err := kms.Parse(env.Get(kms.EnvKMSSecretKey, ""))
if err != nil { if err != nil {
logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment")
} }
GlobalKMS = KMS GlobalKMS = KMS
} }
if env.IsSet(config.EnvKESEndpoint) { if env.IsSet(kms.EnvKESEndpoint) {
if env.IsSet(kms.EnvKESAPIKey) {
if env.IsSet(kms.EnvKESClientKey) {
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientKey))
}
if env.IsSet(kms.EnvKESClientCert) {
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
}
}
var endpoints []string var endpoints []string
for _, endpoint := range strings.Split(env.Get(config.EnvKESEndpoint, ""), ",") { for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") {
if strings.TrimSpace(endpoint) == "" { if strings.TrimSpace(endpoint) == "" {
continue continue
} }
@@ -829,66 +821,82 @@ func handleKMSConfig() {
endpoints = append(endpoints, strings.Join(lbls, "")) endpoints = append(endpoints, strings.Join(lbls, ""))
} }
} }
rootCAs, err := certs.GetRootCAs(env.Get(config.EnvKESServerCA, globalCertsCADir.Get())) rootCAs, err := certs.GetRootCAs(env.Get(kms.EnvKESServerCA, globalCertsCADir.Get()))
if err != nil { if err != nil {
logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(config.EnvKESServerCA, globalCertsCADir.Get()))) logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(kms.EnvKESServerCA, globalCertsCADir.Get())))
} }
loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) { var kmsConf kms.Config
// Manually load the certificate and private key into memory. if env.IsSet(kms.EnvKESAPIKey) {
// We need to check whether the private key is encrypted, and key, err := kes.ParseAPIKey(env.Get(kms.EnvKESAPIKey, ""))
// if so, decrypt it using the user-provided password.
certBytes, err := os.ReadFile(certFile)
if err != nil { if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err) logger.Fatal(err, fmt.Sprintf("Failed to parse KES API key from %q", env.Get(kms.EnvKESAPIKey, "")))
} }
keyBytes, err := os.ReadFile(keyFile) kmsConf = kms.Config{
if err != nil { Endpoints: endpoints,
return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err) Enclave: env.Get(kms.EnvKESEnclave, ""),
DefaultKeyID: env.Get(kms.EnvKESKeyName, ""),
APIKey: key,
RootCAs: rootCAs,
} }
privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes)) } else {
if len(rest) != 0 { loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) {
return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data") // Manually load the certificate and private key into memory.
} // We need to check whether the private key is encrypted, and
if x509.IsEncryptedPEMBlock(privateKeyPEM) { // if so, decrypt it using the user-provided password.
keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(config.EnvKESClientPassword, ""))) certBytes, err := os.ReadFile(certFile)
if err != nil { if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err) return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
} }
keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes}) keyBytes, err := os.ReadFile(keyFile)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err)
}
privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes))
if len(rest) != 0 {
return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data")
}
if x509.IsEncryptedPEMBlock(privateKeyPEM) {
keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(kms.EnvKESClientPassword, "")))
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err)
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes})
}
certificate, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
}
return certificate, nil
} }
certificate, err := tls.X509KeyPair(certBytes, keyBytes)
reloadCertEvents := make(chan tls.Certificate, 1)
certificate, err := certs.NewCertificate(env.Get(kms.EnvKESClientCert, ""), env.Get(kms.EnvKESClientKey, ""), loadX509KeyPair)
if err != nil { if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err) logger.Fatal(err, "Failed to load KES client certificate")
}
certificate.Watch(context.Background(), 15*time.Minute, syscall.SIGHUP)
certificate.Notify(reloadCertEvents)
kmsConf = kms.Config{
Endpoints: endpoints,
Enclave: env.Get(kms.EnvKESEnclave, ""),
DefaultKeyID: env.Get(kms.EnvKESKeyName, ""),
Certificate: certificate,
ReloadCertEvents: reloadCertEvents,
RootCAs: rootCAs,
} }
return certificate, nil
} }
reloadCertEvents := make(chan tls.Certificate, 1) KMS, err := kms.NewWithConfig(kmsConf)
certificate, err := certs.NewCertificate(env.Get(config.EnvKESClientCert, ""), env.Get(config.EnvKESClientKey, ""), loadX509KeyPair)
if err != nil {
logger.Fatal(err, "Failed to load KES client certificate")
}
certificate.Watch(context.Background(), 15*time.Minute, syscall.SIGHUP)
certificate.Notify(reloadCertEvents)
defaultKeyID := env.Get(config.EnvKESKeyName, "")
KMS, err := kms.NewWithConfig(kms.Config{
Endpoints: endpoints,
DefaultKeyID: defaultKeyID,
Certificate: certificate,
ReloadCertEvents: reloadCertEvents,
RootCAs: rootCAs,
})
if err != nil { if err != nil {
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment") logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
} }
// We check that the default key ID exists or try to create it otherwise. // We check that the default key ID exists or try to create it otherwise.
// This implicitly checks that we can communicate to KES. We don't treat // This implicitly checks that we can communicate to KES. We don't treat
// a policy error as failure condition since MinIO may not have the permission // a policy error as failure condition since MinIO may not have the permission
// to create keys - just to generate/decrypt data encryption keys. // to create keys - just to generate/decrypt data encryption keys.
if err = KMS.CreateKey(context.Background(), defaultKeyID); err != nil && !errors.Is(err, kes.ErrKeyExists) && !errors.Is(err, kes.ErrNotAllowed) { if err = KMS.CreateKey(context.Background(), env.Get(kms.EnvKESKeyName, "")); err != nil && !errors.Is(err, kes.ErrKeyExists) && !errors.Is(err, kes.ErrNotAllowed) {
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment") logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
} }
GlobalKMS = KMS GlobalKMS = KMS
+14 -6
View File
@@ -29,10 +29,14 @@ import (
var errConfigNotFound = errors.New("config file not found") var errConfigNotFound = errors.New("config file not found")
func readConfigWithMetadata(ctx context.Context, store objectIO, configFile string) ([]byte, ObjectInfo, error) { func readConfigWithMetadata(ctx context.Context, store objectIO, configFile string, opts ObjectOptions) ([]byte, ObjectInfo, error) {
r, err := store.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, readLock, ObjectOptions{}) lockType := readLock
if opts.NoLock {
lockType = noLock // erasureObjects.GetObjectNInfo honors lockType argument but not opts.NoLock.
}
r, err := store.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, lockType, opts)
if err != nil { if err != nil {
// Treat object not found as config not found.
if isErrObjectNotFound(err) { if isErrObjectNotFound(err) {
return nil, ObjectInfo{}, errConfigNotFound return nil, ObjectInfo{}, errConfigNotFound
} }
@@ -52,7 +56,7 @@ func readConfigWithMetadata(ctx context.Context, store objectIO, configFile stri
} }
func readConfig(ctx context.Context, store objectIO, configFile string) ([]byte, error) { func readConfig(ctx context.Context, store objectIO, configFile string) ([]byte, error) {
buf, _, err := readConfigWithMetadata(ctx, store, configFile) buf, _, err := readConfigWithMetadata(ctx, store, configFile, ObjectOptions{})
return buf, err return buf, err
} }
@@ -70,16 +74,20 @@ func deleteConfig(ctx context.Context, objAPI objectDeleter, configFile string)
return err return err
} }
func saveConfig(ctx context.Context, store objectIO, configFile string, data []byte) error { func saveConfigWithOpts(ctx context.Context, store objectIO, configFile string, data []byte, opts ObjectOptions) error {
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data))) hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
if err != nil { if err != nil {
return err return err
} }
_, err = store.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader), ObjectOptions{MaxParity: true}) _, err = store.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader), opts)
return err return err
} }
func saveConfig(ctx context.Context, store objectIO, configFile string, data []byte) error {
return saveConfigWithOpts(ctx, store, configFile, data, ObjectOptions{MaxParity: true})
}
func checkConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error { func checkConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error {
if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile, ObjectOptions{}); err != nil { if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile, ObjectOptions{}); err != nil {
// Treat object not found as config not found. // Treat object not found as config not found.
+104 -141
View File
@@ -24,8 +24,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/config/api" "github.com/minio/minio/internal/config/api"
"github.com/minio/minio/internal/config/cache" "github.com/minio/minio/internal/config/cache"
@@ -65,7 +64,6 @@ func initHelp() {
config.SiteSubSys: config.DefaultSiteKVS, config.SiteSubSys: config.DefaultSiteKVS,
config.RegionSubSys: config.DefaultRegionKVS, config.RegionSubSys: config.DefaultRegionKVS,
config.APISubSys: api.DefaultKVS, config.APISubSys: api.DefaultKVS,
config.CredentialsSubSys: config.DefaultCredentialKVS,
config.LoggerWebhookSubSys: logger.DefaultLoggerWebhookKVS, config.LoggerWebhookSubSys: logger.DefaultLoggerWebhookKVS,
config.AuditWebhookSubSys: logger.DefaultAuditWebhookKVS, config.AuditWebhookSubSys: logger.DefaultAuditWebhookKVS,
config.AuditKafkaSubSys: logger.DefaultAuditKafkaKVS, config.AuditKafkaSubSys: logger.DefaultAuditKafkaKVS,
@@ -84,22 +82,34 @@ func initHelp() {
// Captures help for each sub-system // Captures help for each sub-system
helpSubSys := config.HelpKVS{ helpSubSys := config.HelpKVS{
config.HelpKV{
Key: config.SubnetSubSys,
Type: "string",
Description: "register the cluster to MinIO SUBNET",
Optional: true,
},
config.HelpKV{
Key: config.CallhomeSubSys,
Type: "string",
Description: "enable callhome to MinIO SUBNET",
Optional: true,
},
config.HelpKV{ config.HelpKV{
Key: config.SiteSubSys, Key: config.SiteSubSys,
Description: "label the server and its location", Description: "label the server and its location",
}, },
config.HelpKV{ config.HelpKV{
Key: config.CacheSubSys, Key: config.APISubSys,
Description: "add caching storage tier", Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
},
config.HelpKV{
Key: config.ScannerSubSys,
Description: "manage namespace scanning for usage calculation, lifecycle, healing and more",
}, },
config.HelpKV{ config.HelpKV{
Key: config.CompressionSubSys, Key: config.CompressionSubSys,
Description: "enable server side compression of objects", Description: "enable server side compression of objects",
}, },
config.HelpKV{
Key: config.EtcdSubSys,
Description: "federate multiple clusters for IAM and Bucket DNS",
},
config.HelpKV{ config.HelpKV{
Key: config.IdentityOpenIDSubSys, Key: config.IdentityOpenIDSubSys,
Description: "enable OpenID SSO support", Description: "enable OpenID SSO support",
@@ -121,14 +131,6 @@ func initHelp() {
Key: config.PolicyPluginSubSys, Key: config.PolicyPluginSubSys,
Description: "enable Access Management Plugin for policy enforcement", Description: "enable Access Management Plugin for policy enforcement",
}, },
config.HelpKV{
Key: config.APISubSys,
Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
},
config.HelpKV{
Key: config.ScannerSubSys,
Description: "manage namespace scanning for usage calculation, lifecycle, healing and more",
},
config.HelpKV{ config.HelpKV{
Key: config.LoggerWebhookSubSys, Key: config.LoggerWebhookSubSys,
Description: "send server logs to webhook endpoints", Description: "send server logs to webhook endpoints",
@@ -195,16 +197,12 @@ func initHelp() {
MultipleTargets: true, MultipleTargets: true,
}, },
config.HelpKV{ config.HelpKV{
Key: config.SubnetSubSys, Key: config.EtcdSubSys,
Type: "string", Description: "persist IAM assets externally to etcd",
Description: "set subnet config for the cluster e.g. api key",
Optional: true,
}, },
config.HelpKV{ config.HelpKV{
Key: config.CallhomeSubSys, Key: config.CacheSubSys,
Type: "string", Description: "[DEPRECATED] add caching storage tier",
Description: "enable callhome for the cluster",
Optional: true,
}, },
} }
@@ -299,16 +297,9 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
return err return err
} }
case config.CompressionSubSys: case config.CompressionSubSys:
compCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]) if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
if err != nil {
return err return err
} }
if objAPI != nil {
if compCfg.Enabled && !objAPI.IsCompressionSupported() {
return fmt.Errorf("Backend does not support compression")
}
}
case config.HealSubSys: case config.HealSubSys:
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil { if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
return err return err
@@ -331,7 +322,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
} }
case config.IdentityOpenIDSubSys: case config.IdentityOpenIDSubSys:
if _, err := openid.LookupConfig(s, if _, err := openid.LookupConfig(s,
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil { NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
return err return err
} }
case config.IdentityLDAPSubSys: case config.IdentityLDAPSubSys:
@@ -352,7 +343,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
} }
case config.IdentityPluginSubSys: case config.IdentityPluginSubSys:
if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default], if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default],
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil { NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
return err return err
} }
case config.SubnetSubSys: case config.SubnetSubSys:
@@ -360,22 +351,26 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
return err return err
} }
case config.CallhomeSubSys: case config.CallhomeSubSys:
if _, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default]); err != nil { cfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default])
if err != nil {
return err return err
} }
// callhome cannot be enabled if license is not registered yet, throw an error.
if cfg.Enabled() && !globalSubnetConfig.Registered() {
return errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
}
case config.PolicyOPASubSys: case config.PolicyOPASubSys:
// In case legacy OPA config is being set, we treat it as if the // In case legacy OPA config is being set, we treat it as if the
// AuthZPlugin is being set. // AuthZPlugin is being set.
subSys = config.PolicyPluginSubSys subSys = config.PolicyPluginSubSys
fallthrough fallthrough
case config.PolicyPluginSubSys: case config.PolicyPluginSubSys:
if ppargs, err := polplugin.LookupConfig(s[config.PolicyPluginSubSys][config.Default], if ppargs, err := polplugin.LookupConfig(s, GetDefaultConnSettings(), xhttp.DrainBody); err != nil {
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {
return err return err
} else if ppargs.URL == nil { } else if ppargs.URL == nil {
// Check if legacy opa is configured. // Check if legacy opa is configured.
if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default], if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default],
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil { NewHTTPTransport(), xhttp.DrainBody); err != nil {
return err return err
} }
} }
@@ -388,7 +383,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
} }
if config.NotifySubSystems.Contains(subSys) { if config.NotifySubSystems.Contains(subSys) {
if err := notify.TestSubSysNotificationTargets(GlobalContext, s, subSys, NewGatewayHTTPTransport()); err != nil { if err := notify.TestSubSysNotificationTargets(GlobalContext, s, subSys, NewHTTPTransport()); err != nil {
return err return err
} }
} }
@@ -423,53 +418,28 @@ func validateConfig(s config.Config, subSys string) error {
func lookupConfigs(s config.Config, objAPI ObjectLayer) { func lookupConfigs(s config.Config, objAPI ObjectLayer) {
ctx := GlobalContext ctx := GlobalContext
var err error
if !globalActiveCred.IsValid() {
// Env doesn't seem to be set, we fallback to lookup creds from the config.
globalActiveCred, err = config.LookupCreds(s[config.CredentialsSubSys][config.Default])
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Invalid credentials configuration: %w", err))
}
}
dnsURL, dnsUser, dnsPass, err := env.LookupEnv(config.EnvDNSWebhook) dnsURL, dnsUser, dnsPass, err := env.LookupEnv(config.EnvDNSWebhook)
if err != nil { if err != nil {
if globalIsGateway { logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
logger.FatalIf(err, "Unable to initialize remote webhook DNS config")
} else {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
}
} }
if err == nil && dnsURL != "" { if err == nil && dnsURL != "" {
globalDNSConfig, err = dns.NewOperatorDNS(dnsURL, globalDNSConfig, err = dns.NewOperatorDNS(dnsURL,
dns.Authentication(dnsUser, dnsPass), dns.Authentication(dnsUser, dnsPass),
dns.RootCAs(globalRootCAs)) dns.RootCAs(globalRootCAs))
if err != nil { if err != nil {
if globalIsGateway { logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
logger.FatalIf(err, "Unable to initialize remote webhook DNS config")
} else {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
}
} }
} }
etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs) etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs)
if err != nil { if err != nil {
if globalIsGateway { logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
logger.FatalIf(err, "Unable to initialize etcd config")
} else {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
}
} }
if etcdCfg.Enabled { if etcdCfg.Enabled {
globalEtcdClient, err = etcd.New(etcdCfg) globalEtcdClient, err = etcd.New(etcdCfg)
if err != nil { if err != nil {
if globalIsGateway { logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
logger.FatalIf(err, "Unable to initialize etcd config")
} else {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
}
} }
if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil { if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
@@ -485,12 +455,8 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
dns.CoreDNSPath(etcdCfg.CoreDNSPath), dns.CoreDNSPath(etcdCfg.CoreDNSPath),
) )
if err != nil { if err != nil {
if globalIsGateway { logger.LogIf(ctx, fmt.Errorf("Unable to initialize DNS config for %s: %w",
logger.FatalIf(err, "Unable to initialize DNS config") globalDomainNames, err))
} else {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize DNS config for %s: %w",
globalDomainNames, err))
}
} }
} }
} }
@@ -510,11 +476,7 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
globalCacheConfig, err = cache.LookupConfig(s[config.CacheSubSys][config.Default]) globalCacheConfig, err = cache.LookupConfig(s[config.CacheSubSys][config.Default])
if err != nil { if err != nil {
if globalIsGateway { logger.LogIf(ctx, fmt.Errorf("Unable to setup cache: %w", err))
logger.FatalIf(err, "Unable to setup cache")
} else {
logger.LogIf(ctx, fmt.Errorf("Unable to setup cache: %w", err))
}
} }
if globalCacheConfig.Enabled { if globalCacheConfig.Enabled {
@@ -540,25 +502,18 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
logger.LogIf(ctx, fmt.Errorf("CRITICAL: enabling %s is not recommended in a production environment", xtls.EnvIdentityTLSSkipVerify)) logger.LogIf(ctx, fmt.Errorf("CRITICAL: enabling %s is not recommended in a production environment", xtls.EnvIdentityTLSSkipVerify))
} }
globalSubnetConfig, err = subnet.LookupConfig(s[config.SubnetSubSys][config.Default], globalProxyTransport) transport := NewHTTPTransport()
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to parse subnet configuration: %w", err))
}
transport := NewGatewayHTTPTransport()
bootstrapTrace("lookup the event notification targets")
globalConfigTargetList, err = notify.FetchEnabledTargets(GlobalContext, s, transport) globalConfigTargetList, err = notify.FetchEnabledTargets(GlobalContext, s, transport)
if err != nil { if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err)) logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
} }
bootstrapTrace("applying the dynamic configuration")
// Apply dynamic config values // Apply dynamic config values
if err := applyDynamicConfig(ctx, objAPI, s); err != nil { if err := applyDynamicConfig(ctx, objAPI, s); err != nil {
if globalIsGateway { logger.LogIf(ctx, err)
logger.FatalIf(err, "Unable to initialize dynamic configuration")
} else {
logger.LogIf(ctx, err)
}
} }
} }
@@ -579,17 +534,13 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
// Initialize remote instance transport once. // Initialize remote instance transport once.
getRemoteInstanceTransportOnce.Do(func() { getRemoteInstanceTransportOnce.Do(func() {
getRemoteInstanceTransport = newGatewayHTTPTransport(apiConfig.RemoteTransportDeadline) getRemoteInstanceTransport = NewHTTPTransportWithTimeout(apiConfig.RemoteTransportDeadline)
}) })
case config.CompressionSubSys: case config.CompressionSubSys:
cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]) cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
if err != nil { if err != nil {
return fmt.Errorf("Unable to setup Compression: %w", err) return fmt.Errorf("Unable to setup Compression: %w", err)
} }
// Validate if the object layer supports compression.
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
return fmt.Errorf("Backend does not support compression")
}
globalCompressConfigMu.Lock() globalCompressConfigMu.Lock()
globalCompressConfig = cmpCfg globalCompressConfig = cmpCfg
globalCompressConfigMu.Unlock() globalCompressConfigMu.Unlock()
@@ -617,12 +568,12 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
if l.Enabled { if l.Enabled {
l.LogOnce = logger.LogOnceConsoleIf l.LogOnce = logger.LogOnceConsoleIf
l.UserAgent = userAgent l.UserAgent = userAgent
l.Transport = NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey) l.Transport = NewHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
loggerCfg.HTTP[n] = l
} }
loggerCfg.HTTP[n] = l
} }
if err = logger.UpdateSystemTargets(loggerCfg); err != nil { if errs := logger.UpdateSystemTargets(loggerCfg); len(errs) > 0 {
logger.LogIf(ctx, fmt.Errorf("Unable to update logger webhook config: %w", err)) logger.LogIf(ctx, fmt.Errorf("Unable to update logger webhook config: %v", errs))
} }
case config.AuditWebhookSubSys: case config.AuditWebhookSubSys:
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditWebhookSubSys) loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditWebhookSubSys)
@@ -634,13 +585,13 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
if l.Enabled { if l.Enabled {
l.LogOnce = logger.LogOnceConsoleIf l.LogOnce = logger.LogOnceConsoleIf
l.UserAgent = userAgent l.UserAgent = userAgent
l.Transport = NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey) l.Transport = NewHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
loggerCfg.AuditWebhook[n] = l
} }
loggerCfg.AuditWebhook[n] = l
} }
if err = logger.UpdateAuditWebhookTargets(loggerCfg); err != nil { if errs := logger.UpdateAuditWebhookTargets(loggerCfg); len(errs) > 0 {
logger.LogIf(ctx, fmt.Errorf("Unable to update audit webhook targets: %w", err)) logger.LogIf(ctx, fmt.Errorf("Unable to update audit webhook targets: %v", errs))
} }
case config.AuditKafkaSubSys: case config.AuditKafkaSubSys:
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditKafkaSubSys) loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditKafkaSubSys)
@@ -653,8 +604,8 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
loggerCfg.AuditKafka[n] = l loggerCfg.AuditKafka[n] = l
} }
} }
if err = logger.UpdateAuditKafkaTargets(loggerCfg); err != nil { if errs := logger.UpdateAuditKafkaTargets(loggerCfg); len(errs) > 0 {
logger.LogIf(ctx, fmt.Errorf("Unable to update audit kafka targets: %w", err)) logger.LogIf(ctx, fmt.Errorf("Unable to update audit kafka targets: %v", errs))
} }
case config.StorageClassSubSys: case config.StorageClassSubSys:
for i, setDriveCount := range setDriveCounts { for i, setDriveCount := range setDriveCounts {
@@ -669,13 +620,24 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
globalStorageClass.Update(sc) globalStorageClass.Update(sc)
} }
} }
case config.SubnetSubSys:
subnetConfig, err := subnet.LookupConfig(s[config.SubnetSubSys][config.Default], globalProxyTransport)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to parse subnet configuration: %w", err))
} else {
globalSubnetConfig.Update(subnetConfig)
globalSubnetConfig.ApplyEnv() // update environment settings for Console UI
}
case config.CallhomeSubSys: case config.CallhomeSubSys:
callhomeCfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default]) callhomeCfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default])
if err != nil { if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err)) logger.LogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err))
} else { } else {
globalCallhomeConfig = callhomeCfg enable := callhomeCfg.Enable && !globalCallhomeConfig.Enabled()
updateCallhomeParams(ctx, objAPI) globalCallhomeConfig.Update(callhomeCfg)
if enable {
initCallhome(ctx, objAPI)
}
} }
} }
globalServerConfigMu.Lock() globalServerConfigMu.Lock()
@@ -739,37 +701,42 @@ func GetHelp(subSys, key string, envOnly bool) (Help, error) {
h = config.HelpKVS{value} h = config.HelpKVS{value}
} }
envHelp := config.HelpKVS{} help := config.HelpKVS{}
if envOnly {
// Only for multiple targets, make sure // Only for multiple targets, make sure
// to list the ENV, for regular k/v EnableKey is // to list the ENV, for regular k/v EnableKey is
// implicit, for ENVs we cannot make it implicit. // implicit, for ENVs we cannot make it implicit.
if subSysHelp.MultipleTargets { if subSysHelp.MultipleTargets {
envK := config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(madmin.EnableKey) key := madmin.EnableKey
envHelp = append(envHelp, config.HelpKV{ if envOnly {
Key: envK, key = config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(madmin.EnableKey)
Description: fmt.Sprintf("enable %s target, default is 'off'", subSys),
Optional: false,
Type: "on|off",
})
} }
for _, hkv := range h { help = append(help, config.HelpKV{
envK := config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(hkv.Key) Key: key,
envHelp = append(envHelp, config.HelpKV{ Description: fmt.Sprintf("enable %s target, default is 'off'", subSys),
Key: envK, Optional: false,
Description: hkv.Description, Type: "on|off",
Optional: hkv.Optional, })
Type: hkv.Type, }
})
for _, hkv := range h {
key := hkv.Key
if envOnly {
key = config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(hkv.Key)
} }
h = envHelp help = append(help, config.HelpKV{
Key: key,
Description: hkv.Description,
Optional: hkv.Optional,
Type: hkv.Type,
})
} }
return Help{ return Help{
SubSys: subSys, SubSys: subSys,
Description: subSysHelp.Description, Description: subSysHelp.Description,
MultipleTargets: subSysHelp.MultipleTargets, MultipleTargets: subSysHelp.MultipleTargets,
KeysHelp: h, KeysHelp: help,
}, nil }, nil
} }
@@ -783,13 +750,6 @@ func newSrvConfig(objAPI ObjectLayer) error {
// Initialize server config. // Initialize server config.
srvCfg := newServerConfig() srvCfg := newServerConfig()
if globalActiveCred.IsValid() && !globalActiveCred.Equal(auth.DefaultCredentials) {
kvs := srvCfg[config.CredentialsSubSys][config.Default]
kvs.Set(config.AccessKey, globalActiveCred.AccessKey)
kvs.Set(config.SecretKey, globalActiveCred.SecretKey)
srvCfg[config.CredentialsSubSys][config.Default] = kvs
}
// hold the mutex lock before a new config is assigned. // hold the mutex lock before a new config is assigned.
globalServerConfigMu.Lock() globalServerConfigMu.Lock()
globalServerConfig = srvCfg globalServerConfig = srvCfg
@@ -800,17 +760,20 @@ func newSrvConfig(objAPI ObjectLayer) error {
} }
func getValidConfig(objAPI ObjectLayer) (config.Config, error) { func getValidConfig(objAPI ObjectLayer) (config.Config, error) {
return readServerConfig(GlobalContext, objAPI) return readServerConfig(GlobalContext, objAPI, nil)
} }
// loadConfig - loads a new config from disk, overrides params // loadConfig - loads a new config from disk, overrides params
// from env if found and valid // from env if found and valid
func loadConfig(objAPI ObjectLayer) error { // data is optional. If nil it will be loaded from backend.
srvCfg, err := getValidConfig(objAPI) func loadConfig(objAPI ObjectLayer, data []byte) error {
bootstrapTrace("load the configuration")
srvCfg, err := readServerConfig(GlobalContext, objAPI, data)
if err != nil { if err != nil {
return err return err
} }
bootstrapTrace("lookup the configuration")
// Override any values from ENVs. // Override any values from ENVs.
lookupConfigs(srvCfg, objAPI) lookupConfigs(srvCfg, objAPI)
+1 -1
View File
@@ -61,7 +61,7 @@ func TestServerConfig(t *testing.T) {
} }
// Initialize server config. // Initialize server config.
if err := loadConfig(objLayer); err != nil { if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err) t.Fatalf("Unable to initialize from updated config file %s", err)
} }
} }
+15 -10
View File
@@ -25,7 +25,7 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
@@ -37,7 +37,10 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
if err != nil { if err != nil {
return fmt.Errorf("Unable to encrypt config %w", err) return fmt.Errorf("Unable to encrypt config %w", err)
} }
if err = migrateConfigPrefixToEncrypted(objAPI, encrypted); err != nil { if !encrypted {
return nil
}
if err = migrateConfigPrefixToEncrypted(objAPI); err != nil {
return fmt.Errorf("Unable to migrate all config at .minio.sys/config/: %w", err) return fmt.Errorf("Unable to migrate all config at .minio.sys/config/: %w", err)
} }
return nil return nil
@@ -48,6 +51,7 @@ const backendEncryptedFile = "backend-encrypted"
var backendEncryptedMigrationComplete = []byte("encrypted") var backendEncryptedMigrationComplete = []byte("encrypted")
func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) { func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) {
bootstrapTrace("check if etcd backend is encrypted")
data, err := readKeyEtcd(ctx, client, backendEncryptedFile) data, err := readKeyEtcd(ctx, client, backendEncryptedFile)
if err != nil && err != errConfigNotFound { if err != nil && err != errConfigNotFound {
return false, err return false, err
@@ -56,6 +60,7 @@ func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool,
} }
func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) { func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) {
bootstrapTrace("check if the config backend is encrypted")
data, err := readConfig(GlobalContext, objAPI, backendEncryptedFile) data, err := readConfig(GlobalContext, objAPI, backendEncryptedFile)
if err != nil && err != errConfigNotFound { if err != nil && err != errConfigNotFound {
return false, err return false, err
@@ -75,7 +80,9 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
return nil return nil
} }
if encrypted && GlobalKMS != nil { bootstrapTrace("encrypt etcd config")
if GlobalKMS != nil {
stat, err := GlobalKMS.Stat(ctx) stat, err := GlobalKMS.Stat(ctx)
if err != nil { if err != nil {
return err return err
@@ -136,18 +143,16 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
} }
} }
if encrypted && GlobalKMS != nil { if GlobalKMS != nil {
logger.Info("Migration of encrypted IAM config data completed. All data is now encrypted on etcd.") logger.Info("Migration of encrypted IAM config data completed. All data is now encrypted on etcd.")
} }
return deleteKeyEtcd(ctx, client, backendEncryptedFile) return deleteKeyEtcd(ctx, client, backendEncryptedFile)
} }
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error { func migrateConfigPrefixToEncrypted(objAPI ObjectLayer) error {
if !encrypted { bootstrapTrace("migrating config prefix to encrypted")
return nil if GlobalKMS != nil {
}
if encrypted && GlobalKMS != nil {
stat, err := GlobalKMS.Stat(context.Background()) stat, err := GlobalKMS.Stat(context.Background())
if err != nil { if err != nil {
return err return err
@@ -202,7 +207,7 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error {
} }
} }
if encrypted && GlobalKMS != nil { if GlobalKMS != nil {
logger.Info("Migration of encrypted config data completed. All config data is now encrypted.") logger.Info("Migration of encrypted config data completed. All config data is now encrypted.")
} }
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"bytes" "bytes"
"testing" "testing"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
) )
+25 -16
View File
@@ -27,7 +27,7 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/config/cache" "github.com/minio/minio/internal/config/cache"
@@ -46,7 +46,7 @@ import (
"github.com/minio/pkg/quick" "github.com/minio/pkg/quick"
) )
// DO NOT EDIT following message template, please open a GitHub issue to discuss instead. // Do not edit following message template, please open a GitHub issue to discuss instead.
var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully." var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully."
// Save config file to corresponding backend // Save config file to corresponding backend
@@ -2425,6 +2425,7 @@ func migrateV27ToV28() error {
// Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/config.json' // Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/config.json'
// if etcd is configured then migrates /config/config.json to '<export_path>/.minio.sys/config/config.json' // if etcd is configured then migrates /config/config.json to '<export_path>/.minio.sys/config/config.json'
func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) { func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
bootstrapTrace("migrate config to .minio.sys/config/config.json")
// Construct path to config.json for the given bucket. // Construct path to config.json for the given bucket.
configFile := path.Join(minioConfigPrefix, minioConfigFile) configFile := path.Join(minioConfigPrefix, minioConfigFile)
@@ -2472,6 +2473,7 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
// Migrates '.minio.sys/config.json' to v33. // Migrates '.minio.sys/config.json' to v33.
func migrateMinioSysConfig(objAPI ObjectLayer) error { func migrateMinioSysConfig(objAPI ObjectLayer) error {
bootstrapTrace("migrate .minio.sys/config/config.json to latest version")
// Construct path to config.json for the given bucket. // Construct path to config.json for the given bucket.
configFile := path.Join(minioConfigPrefix, minioConfigFile) configFile := path.Join(minioConfigPrefix, minioConfigFile)
@@ -2513,26 +2515,34 @@ func checkConfigVersion(objAPI ObjectLayer, configFile string, version string) (
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{ data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
minioMetaBucket: path.Join(minioMetaBucket, configFile), minioMetaBucket: path.Join(minioMetaBucket, configFile),
}) })
if err != nil { }
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
if err != nil { if GlobalKMS == nil && err != nil {
if err == madmin.ErrMaliciousData {
return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
}
return false, nil, err
}
}
} else {
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data)) data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
if err != nil { if err != nil {
if err == madmin.ErrMaliciousData { if err == madmin.ErrMaliciousData || err == madmin.ErrUnexpectedHeader {
return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil) return false, nil, config.ErrInvalidConfigDecryptionKey(nil)
} }
return false, nil, err return false, nil, err
} }
} }
} }
if version == "kvs" {
// Check api config values are present.
var vcfg struct {
API struct {
E []struct {
Key string `json:"key"`
Value string `json:"value"`
} `json:"_"`
} `json:"api"`
}
if err = json.Unmarshal(data, &vcfg); err != nil {
return false, nil, nil
}
return len(vcfg.API.E) > 0, data, nil
}
var versionConfig struct { var versionConfig struct {
Version string `json:"version"` Version string `json:"version"`
} }
@@ -2722,6 +2732,7 @@ func migrateV32ToV33MinioSys(objAPI ObjectLayer) error {
} }
func migrateMinioSysConfigToKV(objAPI ObjectLayer) error { func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
bootstrapTrace("migrate config to KV style")
configFile := path.Join(minioConfigPrefix, minioConfigFile) configFile := path.Join(minioConfigPrefix, minioConfigFile)
// Check if the config version is latest, if not migrate. // Check if the config version is latest, if not migrate.
@@ -2740,9 +2751,7 @@ func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
newCfg := newServerConfig() newCfg := newServerConfig()
config.SetCredentials(newCfg, cfg.Credential)
config.SetRegion(newCfg, cfg.Region) config.SetRegion(newCfg, cfg.Region)
storageclass.SetStorageClass(newCfg, cfg.StorageClass) storageclass.SetStorageClass(newCfg, cfg.StorageClass)
for k, loggerArgs := range cfg.Logger.HTTP { for k, loggerArgs := range cfg.Logger.HTTP {
+2 -15
View File
@@ -22,8 +22,6 @@ import (
"fmt" "fmt"
"os" "os"
"testing" "testing"
"github.com/minio/minio/internal/config"
) )
// Test if config v1 is purged // Test if config v1 is purged
@@ -65,7 +63,7 @@ func TestServerConfigMigrateV1(t *testing.T) {
} }
// Initialize server config and check again if everything is fine // Initialize server config and check again if everything is fine
if err := loadConfig(objLayer); err != nil { if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err) t.Fatalf("Unable to initialize from updated config file %s", err)
} }
} }
@@ -207,20 +205,9 @@ func TestServerConfigMigrateV2toV33(t *testing.T) {
} }
// Initialize server config and check again if everything is fine // Initialize server config and check again if everything is fine
if err := loadConfig(objLayer); err != nil { if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err) t.Fatalf("Unable to initialize from updated config file %s", err)
} }
// Check if accessKey and secretKey are not altered during migration
caccessKey := globalServerConfig[config.CredentialsSubSys][config.Default].Get(config.AccessKey)
if caccessKey != accessKey {
t.Fatalf("Access key lost during migration, expected: %v, found:%v", accessKey, caccessKey)
}
csecretKey := globalServerConfig[config.CredentialsSubSys][config.Default].Get(config.SecretKey)
if csecretKey != secretKey {
t.Fatalf("Secret key lost during migration, expected: %v, found: %v", secretKey, csecretKey)
}
} }
// Test if all migrate code returns error with corrupted config files // Test if all migrate code returns error with corrupted config files
+34 -19
View File
@@ -28,7 +28,7 @@ import (
"unicode/utf8" "unicode/utf8"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
) )
@@ -152,30 +152,34 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, cfg interface{})
return saveConfig(ctx, objAPI, configFile, data) return saveConfig(ctx, objAPI, configFile, data)
} }
func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, error) { // data is optional. If nil it will be loaded from backend.
func readServerConfig(ctx context.Context, objAPI ObjectLayer, data []byte) (config.Config, error) {
srvCfg := config.New() srvCfg := config.New()
configFile := path.Join(minioConfigPrefix, minioConfigFile) var err error
data, err := readConfig(ctx, objAPI, configFile) if len(data) == 0 {
if err != nil { configFile := path.Join(minioConfigPrefix, minioConfigFile)
if errors.Is(err, errConfigNotFound) { data, err = readConfig(ctx, objAPI, configFile)
lookupConfigs(srvCfg, objAPI)
return srvCfg, nil
}
return nil, err
}
if GlobalKMS != nil && !utf8.Valid(data) {
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
minioMetaBucket: path.Join(minioMetaBucket, configFile),
})
if err != nil { if err != nil {
lookupConfigs(srvCfg, objAPI) if errors.Is(err, errConfigNotFound) {
lookupConfigs(srvCfg, objAPI)
return srvCfg, nil
}
return nil, err return nil, err
} }
if GlobalKMS != nil && !utf8.Valid(data) {
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
minioMetaBucket: path.Join(minioMetaBucket, configFile),
})
if err != nil {
lookupConfigs(srvCfg, objAPI)
return nil, err
}
}
} }
json := jsoniter.ConfigCompatibleWithStandardLibrary json := jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(data, &srvCfg); err != nil { if err := json.Unmarshal(data, &srvCfg); err != nil {
return nil, err return nil, err
} }
@@ -202,6 +206,8 @@ func NewConfigSys() *ConfigSys {
// Initialize and load config from remote etcd or local config directory // Initialize and load config from remote etcd or local config directory
func initConfig(objAPI ObjectLayer) error { func initConfig(objAPI ObjectLayer) error {
bootstrapTrace("load the configuration")
if objAPI == nil { if objAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@@ -212,6 +218,15 @@ func initConfig(objAPI ObjectLayer) error {
} }
} }
// Check if the config version is latest (kvs), if not migrate.
ok, data, err := checkConfigVersion(objAPI, path.Join(minioConfigPrefix, minioConfigFile), "kvs")
if err != nil && !errors.Is(err, errConfigNotFound) {
return err
}
if ok {
return loadConfig(objAPI, data)
}
// Migrates ${HOME}/.minio/config.json or config.json.deprecated // Migrates ${HOME}/.minio/config.json or config.json.deprecated
// to '<export_path>/.minio.sys/config/config.json' // to '<export_path>/.minio.sys/config/config.json'
// ignore if the file doesn't exist. // ignore if the file doesn't exist.
@@ -232,5 +247,5 @@ func initConfig(objAPI ObjectLayer) error {
return fmt.Errorf("migrateMinioSysConfigToKV: %w", err) return fmt.Errorf("migrateMinioSysConfigToKV: %w", err)
} }
return loadConfig(objAPI) return loadConfig(objAPI, nil)
} }
+31 -10
View File
@@ -21,13 +21,14 @@ import (
"container/ring" "container/ring"
"context" "context"
"sync" "sync"
"sync/atomic"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/logger/message/log"
"github.com/minio/minio/internal/logger/target/console" "github.com/minio/minio/internal/logger/target/console"
"github.com/minio/minio/internal/logger/target/types" "github.com/minio/minio/internal/logger/target/types"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/pubsub"
"github.com/minio/pkg/logger/message/log"
xnet "github.com/minio/pkg/net" xnet "github.com/minio/pkg/net"
) )
@@ -36,8 +37,11 @@ const defaultLogBufferCount = 10000
// HTTPConsoleLoggerSys holds global console logger state // HTTPConsoleLoggerSys holds global console logger state
type HTTPConsoleLoggerSys struct { type HTTPConsoleLoggerSys struct {
totalMessages int64
failedMessages int64
sync.RWMutex sync.RWMutex
pubsub *pubsub.PubSub pubsub *pubsub.PubSub[log.Info, madmin.LogMask]
console *console.Target console *console.Target
nodeName string nodeName string
logBuf *ring.Ring logBuf *ring.Ring
@@ -46,14 +50,18 @@ type HTTPConsoleLoggerSys struct {
// NewConsoleLogger - creates new HTTPConsoleLoggerSys with all nodes subscribed to // NewConsoleLogger - creates new HTTPConsoleLoggerSys with all nodes subscribed to
// the console logging pub sub system // the console logging pub sub system
func NewConsoleLogger(ctx context.Context) *HTTPConsoleLoggerSys { func NewConsoleLogger(ctx context.Context) *HTTPConsoleLoggerSys {
ps := pubsub.New(8)
return &HTTPConsoleLoggerSys{ return &HTTPConsoleLoggerSys{
pubsub: ps, pubsub: pubsub.New[log.Info, madmin.LogMask](8),
console: console.New(), console: console.New(),
logBuf: ring.New(defaultLogBufferCount), logBuf: ring.New(defaultLogBufferCount),
} }
} }
// IsOnline always true in case of console logger
func (sys *HTTPConsoleLoggerSys) IsOnline() bool {
return true
}
// SetNodeName - sets the node name if any after distributed setup has initialized // SetNodeName - sets the node name if any after distributed setup has initialized
func (sys *HTTPConsoleLoggerSys) SetNodeName(nodeName string) { func (sys *HTTPConsoleLoggerSys) SetNodeName(nodeName string) {
if !globalIsDistErasure { if !globalIsDistErasure {
@@ -72,11 +80,11 @@ func (sys *HTTPConsoleLoggerSys) SetNodeName(nodeName string) {
// HasLogListeners returns true if console log listeners are registered // HasLogListeners returns true if console log listeners are registered
// for this node or peers // for this node or peers
func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool { func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool {
return sys != nil && sys.pubsub.NumSubscribers(madmin.LogMaskAll) > 0 return sys != nil && sys.pubsub.Subscribers() > 0
} }
// Subscribe starts console logging for this node. // Subscribe starts console logging for this node.
func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan pubsub.Maskable, doneCh <-chan struct{}, node string, last int, logKind madmin.LogMask, filter func(entry pubsub.Maskable) bool) error { func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan log.Info, doneCh <-chan struct{}, node string, last int, logKind madmin.LogMask, filter func(entry log.Info) bool) error {
// Enable console logging for remote client. // Enable console logging for remote client.
if !sys.HasLogListeners() { if !sys.HasLogListeners() {
logger.AddSystemTarget(sys) logger.AddSystemTarget(sys)
@@ -116,7 +124,7 @@ func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan pubsub.Maskable, doneCh <-
} }
} }
} }
return sys.pubsub.Subscribe(pubsub.MaskFromMaskable(madmin.LogMaskAll), subCh, doneCh, filter) return sys.pubsub.Subscribe(madmin.LogMaskAll, subCh, doneCh, filter)
} }
// Init if HTTPConsoleLoggerSys is valid, always returns nil right now // Init if HTTPConsoleLoggerSys is valid, always returns nil right now
@@ -134,6 +142,15 @@ func (sys *HTTPConsoleLoggerSys) String() string {
return logger.ConsoleLoggerTgt return logger.ConsoleLoggerTgt
} }
// Stats returns the target statistics.
func (sys *HTTPConsoleLoggerSys) Stats() types.TargetStats {
return types.TargetStats{
TotalMessages: atomic.LoadInt64(&sys.totalMessages),
FailedMessages: atomic.LoadInt64(&sys.failedMessages),
QueueLength: 0,
}
}
// Content returns the console stdout log // Content returns the console stdout log
func (sys *HTTPConsoleLoggerSys) Content() (logs []log.Entry) { func (sys *HTTPConsoleLoggerSys) Content() (logs []log.Entry) {
sys.RLock() sys.RLock()
@@ -171,6 +188,7 @@ func (sys *HTTPConsoleLoggerSys) Send(entry interface{}) error {
case string: case string:
lg = log.Info{ConsoleMsg: e, NodeName: sys.nodeName} lg = log.Info{ConsoleMsg: e, NodeName: sys.nodeName}
} }
atomic.AddInt64(&sys.totalMessages, 1)
sys.pubsub.Publish(lg) sys.pubsub.Publish(lg)
sys.Lock() sys.Lock()
@@ -178,6 +196,9 @@ func (sys *HTTPConsoleLoggerSys) Send(entry interface{}) error {
sys.logBuf.Value = lg sys.logBuf.Value = lg
sys.logBuf = sys.logBuf.Next() sys.logBuf = sys.logBuf.Next()
sys.Unlock() sys.Unlock()
err := sys.console.Send(entry, string(logger.All))
return sys.console.Send(entry, string(logger.All)) if err != nil {
atomic.AddInt64(&sys.failedMessages, 1)
}
return err
} }
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/gorilla/mux" "github.com/minio/mux"
) )
// Test cross domain xml handler. // Test cross domain xml handler.
+14 -18
View File
@@ -7,7 +7,7 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
) )
@@ -47,6 +47,8 @@ const (
scannerMetricILM scannerMetricILM
scannerMetricCheckReplication scannerMetricCheckReplication
scannerMetricYield scannerMetricYield
scannerMetricCleanAbandoned
scannerMetricApplyNonCurrent
// START Trace metrics: // START Trace metrics:
scannerMetricStartTrace scannerMetricStartTrace
@@ -56,9 +58,10 @@ const (
scannerMetricLastRealtime scannerMetricLastRealtime
// Trace only metrics: // Trace only metrics:
scannerMetricScanFolder // Scan a folder on disk, recursively. scannerMetricScanFolder // Scan a folder on disk, recursively.
scannerMetricScanCycle // Full cycle, cluster global scannerMetricScanCycle // Full cycle, cluster global
scannerMetricScanBucketDisk // Single bucket on one disk scannerMetricScanBucketDrive // Single bucket on one drive
scannerMetricCompactFolder // Folder compacted.
// Must be last: // Must be last:
scannerMetricLast scannerMetricLast
@@ -66,9 +69,9 @@ const (
// log scanner action. // log scanner action.
// Use for s > scannerMetricStartTrace // Use for s > scannerMetricStartTrace
func (p *scannerMetrics) log(s scannerMetric, paths ...string) func() { func (p *scannerMetrics) log(s scannerMetric, paths ...string) func(custom map[string]string) {
startTime := time.Now() startTime := time.Now()
return func() { return func(custom map[string]string) {
duration := time.Since(startTime) duration := time.Since(startTime)
atomic.AddUint64(&p.operations[s], 1) atomic.AddUint64(&p.operations[s], 1)
@@ -77,7 +80,7 @@ func (p *scannerMetrics) log(s scannerMetric, paths ...string) func() {
} }
if s > scannerMetricStartTrace && globalTrace.NumSubscribers(madmin.TraceScanner) > 0 { if s > scannerMetricStartTrace && globalTrace.NumSubscribers(madmin.TraceScanner) > 0 {
globalTrace.Publish(scannerTrace(s, startTime, duration, strings.Join(paths, " "))) globalTrace.Publish(scannerTrace(s, startTime, duration, strings.Join(paths, " "), custom))
} }
} }
} }
@@ -119,13 +122,6 @@ func (p *scannerMetrics) incTime(s scannerMetric, d time.Duration) {
} }
} }
func (p *scannerMetrics) incNoTime(s scannerMetric) {
atomic.AddUint64(&p.operations[s], 1)
if s < scannerMetricLastRealtime {
p.latency[s].add(0)
}
}
// timeILM times an ILM action. // timeILM times an ILM action.
// lifecycle.NoneAction is ignored. // lifecycle.NoneAction is ignored.
// Use for s < scannerMetricLastRealtime // Use for s < scannerMetricLastRealtime
@@ -186,9 +182,9 @@ func (p *scannerMetrics) getCurrentPaths() []string {
return res return res
} }
// activeDisks returns the number of currently active disks. // activeDrives returns the number of currently active disks.
// (since this is concurrent it may not be 100% reliable) // (since this is concurrent it may not be 100% reliable)
func (p *scannerMetrics) activeDisks() int { func (p *scannerMetrics) activeDrives() int {
var i int var i int
p.currentPaths.Range(func(k, v interface{}) bool { p.currentPaths.Range(func(k, v interface{}) bool {
i++ i++
@@ -199,7 +195,7 @@ func (p *scannerMetrics) activeDisks() int {
// lifetime returns the lifetime count of the specified metric. // lifetime returns the lifetime count of the specified metric.
func (p *scannerMetrics) lifetime(m scannerMetric) uint64 { func (p *scannerMetrics) lifetime(m scannerMetric) uint64 {
if m < 0 || m >= scannerMetricLast { if m >= scannerMetricLast {
return 0 return 0
} }
val := atomic.LoadUint64(&p.operations[m]) val := atomic.LoadUint64(&p.operations[m])
@@ -209,7 +205,7 @@ func (p *scannerMetrics) lifetime(m scannerMetric) uint64 {
// lastMinute returns the last minute statistics of a metric. // lastMinute returns the last minute statistics of a metric.
// m should be < scannerMetricLastRealtime // m should be < scannerMetricLastRealtime
func (p *scannerMetrics) lastMinute(m scannerMetric) AccElem { func (p *scannerMetrics) lastMinute(m scannerMetric) AccElem {
if m < 0 || m >= scannerMetricLastRealtime { if m >= scannerMetricLastRealtime {
return AccElem{} return AccElem{}
} }
val := p.latency[m].total() val := p.latency[m].total()
+117 -67
View File
@@ -29,13 +29,14 @@ import (
"math/rand" "math/rand"
"os" "os"
"path" "path"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/bits-and-blooms/bloom/v3" "github.com/bits-and-blooms/bloom/v3"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/bucket/object/lock" "github.com/minio/minio/internal/bucket/object/lock"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
@@ -59,16 +60,14 @@ const (
healDeleteDangling = true healDeleteDangling = true
healFolderIncludeProb = 32 // Include a clean folder one in n cycles. healFolderIncludeProb = 32 // Include a clean folder one in n cycles.
healObjectSelectProb = 512 // Overall probability of a file being scanned; one in n. healObjectSelectProb = 512 // Overall probability of a file being scanned; one in n.
dataScannerExcessiveVersionsThreshold = 1000 // 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 ( var (
globalHealConfig heal.Config globalHealConfig heal.Config
dataScannerLeaderLockTimeout = newDynamicTimeoutWithOpts(dynamicTimeoutOpts{
timeout: 30 * time.Second,
minimum: 10 * time.Second,
retryInterval: time.Second,
})
// Sleeper values are updated when config is loaded. // Sleeper values are updated when config is loaded.
scannerSleeper = newDynamicSleeper(10, 10*time.Second, true) scannerSleeper = newDynamicSleeper(10, 10*time.Second, true)
scannerCycle = uatomic.NewDuration(dataScannerStartDelay) scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
@@ -118,6 +117,10 @@ type backgroundHealInfo struct {
} }
func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundHealInfo { func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundHealInfo {
if globalIsErasureSD {
return backgroundHealInfo{}
}
// Get last healing information // Get last healing information
buf, err := readConfig(ctx, objAPI, backgroundHealInfoPath) buf, err := readConfig(ctx, objAPI, backgroundHealInfoPath)
if err != nil { if err != nil {
@@ -127,15 +130,17 @@ func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundH
return backgroundHealInfo{} return backgroundHealInfo{}
} }
var info backgroundHealInfo var info backgroundHealInfo
err = json.Unmarshal(buf, &info) if err = json.Unmarshal(buf, &info); err != nil {
if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return backgroundHealInfo{}
} }
return info return info
} }
func saveBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer, info backgroundHealInfo) { func saveBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer, info backgroundHealInfo) {
if globalIsErasureSD {
return
}
b, err := json.Marshal(info) b, err := json.Marshal(info)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
@@ -151,19 +156,9 @@ func saveBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer, info backgr
// runDataScanner will start a data scanner. // runDataScanner will start a data scanner.
// The function will block until the context is canceled. // The function will block until the context is canceled.
// There should only ever be one scanner running per cluster. // There should only ever be one scanner running per cluster.
func runDataScanner(pctx context.Context, objAPI ObjectLayer) { func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
// Make sure only 1 scanner is running on the cluster. ctx, cancel := globalLeaderLock.GetLock(ctx)
locker := objAPI.NewNSLock(minioMetaBucket, "scanner/runDataScanner.lock") defer cancel()
lkctx, err := locker.GetLock(pctx, dataScannerLeaderLockTimeout)
if err != nil {
if intDataUpdateTracker.debug {
logger.LogIf(pctx, err)
}
return
}
ctx := lkctx.Context()
defer lkctx.Cancel()
// No unlock for "leader" lock.
// Load current bloom cycle // Load current bloom cycle
var cycleInfo currentScannerCycle var cycleInfo currentScannerCycle
@@ -175,7 +170,7 @@ func runDataScanner(pctx context.Context, objAPI ObjectLayer) {
} else if len(buf) > 8 { } else if len(buf) > 8 {
cycleInfo.next = binary.LittleEndian.Uint64(buf[:8]) cycleInfo.next = binary.LittleEndian.Uint64(buf[:8])
buf = buf[8:] buf = buf[8:]
_, err = cycleInfo.UnmarshalMsg(buf) _, err := cycleInfo.UnmarshalMsg(buf)
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
@@ -216,7 +211,11 @@ func runDataScanner(pctx context.Context, objAPI ObjectLayer) {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
err = objAPI.NSScanner(ctx, bf, results, uint32(cycleInfo.current), scanMode) err = objAPI.NSScanner(ctx, bf, results, uint32(cycleInfo.current), scanMode)
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
stopFn() res := map[string]string{"cycle": fmt.Sprint(cycleInfo.current)}
if err != nil {
res["error"] = err.Error()
}
stopFn(res)
if err == nil { if err == nil {
// Store new cycle... // Store new cycle...
cycleInfo.next++ cycleInfo.next++
@@ -429,7 +428,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
filter := f.withFilter filter := f.withFilter
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name) _, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
var activeLifeCycle *lifecycle.Lifecycle var activeLifeCycle *lifecycle.Lifecycle
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) { if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix) {
if f.dataUsageScannerDebug { if f.dataUsageScannerDebug {
console.Debugf(scannerLogPrefix+" Prefix %q has active rules\n", prefix) console.Debugf(scannerLogPrefix+" Prefix %q has active rules\n", prefix)
} }
@@ -575,6 +574,19 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
len(existingFolders)+len(newFolders) >= dataScannerCompactAtFolders || len(existingFolders)+len(newFolders) >= dataScannerCompactAtFolders ||
len(existingFolders)+len(newFolders) >= dataScannerForceCompactAtFolders len(existingFolders)+len(newFolders) >= dataScannerForceCompactAtFolders
if len(existingFolders)+len(newFolders) > dataScannerExcessiveFoldersThreshold {
// Notify object accessed via a GET request.
sendEvent(eventArgs{
EventName: event.PrefixManyFolders,
BucketName: f.root,
Object: ObjectInfo{
Name: strings.TrimSuffix(folder.name, "/") + "/",
Size: int64(len(existingFolders) + len(newFolders)),
},
UserAgent: "scanner",
Host: globalMinioHost,
})
}
if !into.Compacted && shouldCompact { if !into.Compacted && shouldCompact {
into.Compacted = true into.Compacted = true
newFolders = append(newFolders, existingFolders...) newFolders = append(newFolders, existingFolders...)
@@ -641,7 +653,8 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
f.updateCurrentPath(folder.name) f.updateCurrentPath(folder.name)
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name) stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name)
scanFolder(folder) scanFolder(folder)
stopFn() stopFn(map[string]string{"type": "new"})
// Add new folders if this is new and we don't have existing. // Add new folders if this is new and we don't have existing.
if !into.Compacted { if !into.Compacted {
parent := f.updateCache.find(thisHash.Key()) parent := f.updateCache.find(thisHash.Key())
@@ -670,9 +683,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
} }
} }
f.updateCurrentPath(folder.name) f.updateCurrentPath(folder.name)
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name, "EXISTING") stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name)
scanFolder(folder) scanFolder(folder)
stopFn() stopFn(map[string]string{"type": "existing"})
} }
// Scan for healing // Scan for healing
@@ -770,7 +783,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
object: entry.name, object: entry.name,
versionID: "", versionID: "",
}, madmin.HealItemObject) }, madmin.HealItemObject)
logger.LogIf(ctx, err) if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
foundObjs = foundObjs || err == nil foundObjs = foundObjs || err == nil
return return
} }
@@ -785,7 +800,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
object: fiv.Name, object: fiv.Name,
versionID: ver.VersionID, versionID: ver.VersionID,
}, madmin.HealItemObject) }, madmin.HealItemObject)
logger.LogIf(ctx, err) if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
foundObjs = foundObjs || err == nil foundObjs = foundObjs || err == nil
} }
}, },
@@ -808,7 +825,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
this := cachedFolder{name: k, parent: &thisHash, objectHealProbDiv: 1} this := cachedFolder{name: k, parent: &thisHash, objectHealProbDiv: 1}
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, this.name, "HEALED") stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, this.name, "HEALED")
scanFolder(this) scanFolder(this)
stopFn() stopFn(map[string]string{"type": "healed"})
} }
} }
break break
@@ -822,10 +839,6 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
flat.Compacted = true flat.Compacted = true
var compact bool var compact bool
if flat.Objects < dataScannerCompactLeastObject { if flat.Objects < dataScannerCompactLeastObject {
if f.dataUsageScannerDebug && flat.Objects > 1 {
// Disabled, rather chatty:
// console.Debugf(scannerLogPrefix+" Only %d objects, compacting %s -> %+v\n", flat.Objects, folder.name, flat)
}
compact = true compact = true
} else { } else {
// Compact if we only have objects as children... // Compact if we only have objects as children...
@@ -838,14 +851,20 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
} }
} }
} }
if f.dataUsageScannerDebug && compact {
// Disabled, rather chatty:
// console.Debugf(scannerLogPrefix+" Only objects (%d), compacting %s -> %+v\n", flat.Objects, folder.name, flat)
}
} }
if compact { if compact {
stop := globalScannerMetrics.log(scannerMetricCompactFolder, folder.name)
f.newCache.deleteRecursive(thisHash) f.newCache.deleteRecursive(thisHash)
f.newCache.replaceHashed(thisHash, folder.parent, *flat) f.newCache.replaceHashed(thisHash, folder.parent, *flat)
total := map[string]string{
"objects": fmt.Sprint(flat.Objects),
"size": fmt.Sprint(flat.Size),
}
if flat.Versions > 0 {
total["versions"] = fmt.Sprint(flat.Versions)
}
stop(total)
} }
} }
@@ -954,30 +973,26 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
if i.lifeCycle == nil { if i.lifeCycle == nil {
if i.debug {
// disabled, very chatty:
// console.Debugf(applyActionsLogPrefix+" no lifecycle rules to apply: %q\n", i.objectPath())
}
return false, size return false, size
} }
versionID := oi.VersionID versionID := oi.VersionID
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket) rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
action := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi) lcEvt := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi)
if i.debug { if i.debug {
if versionID != "" { if versionID != "" {
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, action) console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, lcEvt.Action)
} else { } else {
console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), action) console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), lcEvt.Action)
} }
} }
defer globalScannerMetrics.timeILM(action) defer globalScannerMetrics.timeILM(lcEvt.Action)
switch action { switch lcEvt.Action {
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction: case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
return applyLifecycleAction(action, oi), 0 return applyLifecycleAction(lcEvt.Action, oi, ""), 0
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction: case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
return applyLifecycleAction(action, oi), size return applyLifecycleAction(lcEvt.Action, oi, lcEvt.StorageClass), size
default: default:
// No action. // No action.
return false, size return false, size
@@ -1008,7 +1023,8 @@ func (i *scannerItem) applyTierObjSweep(ctx context.Context, o ObjectLayer, oi O
// Remove this free version // Remove this free version
_, err = o.DeleteObject(ctx, oi.Bucket, oi.Name, ObjectOptions{ _, err = o.DeleteObject(ctx, oi.Bucket, oi.Name, ObjectOptions{
VersionID: oi.VersionID, VersionID: oi.VersionID,
InclFreeVersions: true,
}) })
if err == nil { if err == nil {
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete) auditLogLifecycle(ctx, oi, ILMFreeVersionDelete)
@@ -1024,6 +1040,8 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
if i.lifeCycle == nil { if i.lifeCycle == nil {
return fivs, nil return fivs, nil
} }
done := globalScannerMetrics.time(scannerMetricApplyNonCurrent)
defer done()
_, days, lim := i.lifeCycle.NoncurrentVersionsExpirationLimit(lifecycle.ObjectOpts{Name: i.objectPath()}) _, days, lim := i.lifeCycle.NoncurrentVersionsExpirationLimit(lifecycle.ObjectOpts{Name: i.objectPath()})
if lim == 0 || len(fivs) <= lim+1 { // fewer than lim _noncurrent_ versions if lim == 0 || len(fivs) <= lim+1 { // fewer than lim _noncurrent_ versions
@@ -1080,7 +1098,37 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
// applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain // applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain
// after applying lifecycle checks configured. // after applying lifecycle checks configured.
func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]FileInfo, error) { func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]FileInfo, error) {
return i.applyNewerNoncurrentVersionLimit(ctx, o, fivs) if i.heal.enabled {
if healDeleteDangling {
done := globalScannerMetrics.time(scannerMetricCleanAbandoned)
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
done()
if err != nil {
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
}
}
}
fivs, err := i.applyNewerNoncurrentVersionLimit(ctx, o, fivs)
if err != nil {
return fivs, err
}
// Check if we have many versions after applyNewerNoncurrentVersionLimit.
if len(fivs) > dataScannerExcessiveVersionsThreshold {
// Notify object accessed via a GET request.
sendEvent(eventArgs{
EventName: event.ObjectManyVersions,
BucketName: i.bucket,
Object: ObjectInfo{
Name: i.objectPath(),
},
UserAgent: "scanner",
Host: globalMinioHost,
RespElements: map[string]string{"x-minio-versions": strconv.Itoa(len(fivs))},
})
}
return fivs, nil
} }
// applyActions will apply lifecycle checks on to a scanned item. // applyActions will apply lifecycle checks on to a scanned item.
@@ -1109,21 +1157,21 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
return size return size
} }
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) (action lifecycle.Action) { func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) lifecycle.Event {
action = lc.ComputeAction(obj.ToLifecycleOpts()) event := lc.Eval(obj.ToLifecycleOpts())
if serverDebugLog { if serverDebugLog {
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action) console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
} }
if action == lifecycle.NoneAction { if event.Action == lifecycle.NoneAction {
return action return event
} }
switch action { switch event.Action {
case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction: case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction:
// Defensive code, should never happen // Defensive code, should never happen
if obj.VersionID == "" { if obj.VersionID == "" {
return lifecycle.NoneAction return lifecycle.Event{Action: lifecycle.NoneAction}
} }
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) { if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
if serverDebugLog { if serverDebugLog {
@@ -1133,18 +1181,18 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", obj.Name) console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", obj.Name)
} }
} }
return lifecycle.NoneAction return lifecycle.Event{Action: lifecycle.NoneAction}
} }
} }
return action return event
} }
func applyTransitionRule(obj ObjectInfo) bool { func applyTransitionRule(obj ObjectInfo, storageClass string) bool {
if obj.DeleteMarker { if obj.DeleteMarker {
return false return false
} }
globalTransitionState.queueTransitionTask(obj) globalTransitionState.queueTransitionTask(obj, storageClass)
return true return true
} }
@@ -1200,7 +1248,7 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
EventName: eventName, EventName: eventName,
BucketName: obj.Bucket, BucketName: obj.Bucket,
Object: obj, Object: obj,
Host: "Internal: [ILM-EXPIRY]", Host: "Internal: [ILM-Expiry]",
}) })
return true return true
@@ -1213,14 +1261,14 @@ func applyExpiryRule(obj ObjectInfo, restoredObject, applyOnVersion bool) bool {
} }
// Perform actions (removal or transitioning of objects), return true the action is successfully performed // Perform actions (removal or transitioning of objects), return true the action is successfully performed
func applyLifecycleAction(action lifecycle.Action, obj ObjectInfo) (success bool) { func applyLifecycleAction(action lifecycle.Action, obj ObjectInfo, storageClass string) (success bool) {
switch action { switch action {
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction: case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
success = applyExpiryRule(obj, false, action == lifecycle.DeleteVersionAction) success = applyExpiryRule(obj, false, action == lifecycle.DeleteVersionAction)
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction: case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
success = applyExpiryRule(obj, true, action == lifecycle.DeleteRestoredVersionAction) success = applyExpiryRule(obj, true, action == lifecycle.DeleteRestoredVersionAction)
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction: case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
success = applyTransitionRule(obj) success = applyTransitionRule(obj, storageClass)
} }
return return
} }
@@ -1441,9 +1489,11 @@ func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string) {
case ILMTransition: case ILMTransition:
apiName = "ILMTransition" apiName = "ILMTransition"
} }
auditLogInternal(ctx, oi.Bucket, oi.Name, AuditLogOptions{ auditLogInternal(ctx, AuditLogOptions{
Event: event, Event: event,
APIName: apiName, APIName: apiName,
Bucket: oi.Bucket,
Object: oi.Name,
VersionID: oi.VersionID, VersionID: oi.VersionID,
}) })
} }
+10 -1
View File
@@ -29,8 +29,8 @@ import (
"testing" "testing"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/logger/message/log"
"github.com/minio/minio/internal/logger/target/types" "github.com/minio/minio/internal/logger/target/types"
"github.com/minio/pkg/logger/message/log"
) )
type testLoggerI interface { type testLoggerI interface {
@@ -62,6 +62,15 @@ func (t *testingLogger) Type() types.TargetType {
return types.TargetHTTP return types.TargetHTTP
} }
func (t *testingLogger) IsOnline() bool {
return true
}
// Stats returns the target statistics.
func (t *testingLogger) Stats() types.TargetStats {
return types.TargetStats{}
}
func (t *testingLogger) Send(entry interface{}) error { func (t *testingLogger) Send(entry interface{}) error {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
+24 -52
View File
@@ -18,6 +18,7 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@@ -31,7 +32,7 @@ import (
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
@@ -392,13 +393,6 @@ func (e *dataUsageEntry) addChild(hash dataUsageHash) {
e.Children[hash.Key()] = struct{}{} e.Children[hash.Key()] = struct{}{}
} }
// removeChild will remove a child based on its hash.
func (e *dataUsageEntry) removeChild(hash dataUsageHash) {
if len(e.Children) > 0 {
delete(e.Children, hash.Key())
}
}
// Create a clone of the entry. // Create a clone of the entry.
func (e dataUsageEntry) clone() dataUsageEntry { func (e dataUsageEntry) clone() dataUsageEntry {
// We operate on a copy from the receiver. // We operate on a copy from the receiver.
@@ -488,43 +482,6 @@ func (d *dataUsageCache) deleteRecursive(h dataUsageHash) {
} }
} }
// keepBuckets will keep only the buckets specified specified by delete all others.
func (d *dataUsageCache) keepBuckets(b []BucketInfo) {
lu := make(map[dataUsageHash]struct{})
for _, v := range b {
lu[hashPath(v.Name)] = struct{}{}
}
d.keepRootChildren(lu)
}
// keepRootChildren will keep the root children specified by delete all others.
func (d *dataUsageCache) keepRootChildren(list map[dataUsageHash]struct{}) {
root := d.root()
if root == nil {
return
}
rh := d.rootHash()
for k := range d.Cache {
h := dataUsageHash(k)
if h == rh {
continue
}
if _, ok := list[h]; !ok {
delete(d.Cache, k)
d.deleteRecursive(h)
root.removeChild(h)
}
}
// Clean up abandoned children.
for k := range root.Children {
h := dataUsageHash(k)
if _, ok := list[h]; !ok {
delete(root.Children, k)
}
}
d.Cache[rh.Key()] = *root
}
// dui converts the flattened version of the path to madmin.DataUsageInfo. // dui converts the flattened version of the path to madmin.DataUsageInfo.
// As a side effect d will be flattened, use a clone if this is not ok. // As a side effect d will be flattened, use a clone if this is not ok.
func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo { func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo {
@@ -923,13 +880,28 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name. // save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error { func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
pr, pw := io.Pipe() var r io.Reader
go func() {
pw.CloseWithError(d.serializeTo(pw))
}()
defer pr.Close()
r, err := hash.NewReader(pr, -1, "", "", -1) // If big, do streaming...
size := int64(-1)
if len(d.Cache) > 10000 {
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(d.serializeTo(pw))
}()
defer pr.Close()
r = pr
} else {
var buf bytes.Buffer
err := d.serializeTo(&buf)
if err != nil {
return err
}
r = &buf
size = int64(buf.Len())
}
hr, err := hash.NewReader(r, size, "", "", size)
if err != nil { if err != nil {
return err return err
} }
@@ -940,7 +912,7 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
_, err = store.PutObject(ctx, _, err = store.PutObject(ctx,
dataUsageBucket, dataUsageBucket,
name, name,
NewPutObjReader(r), NewPutObjReader(hr),
ObjectOptions{}) ObjectOptions{})
if isErrBucketNotFound(err) { if isErrBucketNotFound(err) {
return nil return nil
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"sort" "sort"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
) )
// BucketTargetUsageInfo - bucket target usage info provides // BucketTargetUsageInfo - bucket target usage info provides
+10 -9
View File
@@ -512,7 +512,7 @@ func (c *diskCache) statCachedMeta(ctx context.Context, cacheObjPath string) (me
return return
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer cLock.RUnlock(lkctx.Cancel) defer cLock.RUnlock(lkctx)
return c.statCache(ctx, cacheObjPath) return c.statCache(ctx, cacheObjPath)
} }
@@ -597,7 +597,7 @@ func (c *diskCache) SaveMetadata(ctx context.Context, bucket, object string, met
return err return err
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer cLock.Unlock(lkctx.Cancel) defer cLock.Unlock(lkctx)
if err = c.saveMetadata(ctx, bucket, object, meta, actualSize, rs, rsFileName, incHitsOnly); err != nil { if err = c.saveMetadata(ctx, bucket, object, meta, actualSize, rs, rsFileName, incHitsOnly); err != nil {
return err return err
} }
@@ -842,7 +842,7 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
return oi, err return oi, err
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer cLock.Unlock(lkctx.Cancel) defer cLock.Unlock(lkctx)
return c.put(ctx, bucket, object, data, size, rs, opts, incHitsOnly, writeback) return c.put(ctx, bucket, object, data, size, rs, opts, incHitsOnly, writeback)
} }
@@ -953,7 +953,7 @@ func (c *diskCache) putRange(ctx context.Context, bucket, object string, data io
objSize, _ = sio.EncryptedSize(uint64(size)) objSize, _ = sio.EncryptedSize(uint64(size))
} }
cacheFile := MustGetUUID() cacheFile := mustGetUUID()
n, _, err := c.bitrotWriteToCache(cachePath, cacheFile, reader, actualSize) n, _, err := c.bitrotWriteToCache(cachePath, cacheFile, reader, actualSize)
if IsErr(err, baseErrs...) { if IsErr(err, baseErrs...) {
// take the cache drive offline // take the cache drive offline
@@ -1003,6 +1003,7 @@ func (c *diskCache) bitrotReadFromCache(ctx context.Context, filePath string, of
if err != nil { if err != nil {
return err return err
} }
defer rc.Close()
bufp := c.pool.Get().(*[]byte) bufp := c.pool.Get().(*[]byte)
defer c.pool.Put(bufp) defer c.pool.Put(bufp)
@@ -1075,7 +1076,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
return nil, numHits, err return nil, numHits, err
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer cLock.RUnlock(lkctx.Cancel) defer cLock.RUnlock(lkctx)
var objInfo ObjectInfo var objInfo ObjectInfo
var rngInfo RangeInfo var rngInfo RangeInfo
@@ -1212,7 +1213,7 @@ func (c *diskCache) Delete(ctx context.Context, bucket, object string) (err erro
if err != nil { if err != nil {
return err return err
} }
defer cLock.Unlock(lkctx.Cancel) defer cLock.Unlock(lkctx)
return removeAll(cacheObjPath) return removeAll(cacheObjPath)
} }
@@ -1323,7 +1324,7 @@ func (c *diskCache) PutObjectPart(ctx context.Context, bucket, object, uploadID
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer partIDLock.Unlock(lkctx.Cancel) defer partIDLock.Unlock(lkctx)
meta, _, _, err := c.statCache(ctx, uploadIDDir) meta, _, _, err := c.statCache(ctx, uploadIDDir)
// Case where object not yet cached // Case where object not yet cached
if err != nil { if err != nil {
@@ -1380,7 +1381,7 @@ func (c *diskCache) SavePartMetadata(ctx context.Context, bucket, object, upload
if err != nil { if err != nil {
return err return err
} }
defer uploadLock.Unlock(ulkctx.Cancel) defer uploadLock.Unlock(ulkctx)
metaPath := pathJoin(uploadDir, cacheMetaJSONFile) metaPath := pathJoin(uploadDir, cacheMetaJSONFile)
f, err := OpenFile(metaPath, os.O_RDWR|writeMode, 0o666) f, err := OpenFile(metaPath, os.O_RDWR|writeMode, 0o666)
@@ -1479,7 +1480,7 @@ func (c *diskCache) CompleteMultipartUpload(ctx context.Context, bucket, object,
} }
ctx = lkctx.Context() ctx = lkctx.Context()
defer cLock.Unlock(lkctx.Cancel) defer cLock.Unlock(lkctx)
mpartCachePath := getMultipartCacheSHADir(c.dir, bucket, object) mpartCachePath := getMultipartCacheSHADir(c.dir, bucket, object)
uploadIDDir := path.Join(mpartCachePath, uploadID) uploadIDDir := path.Join(mpartCachePath, uploadID)
+1 -1
View File
@@ -728,7 +728,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
if cerr != nil { if cerr != nil {
return putObjectFn(ctx, bucket, object, r, opts) return putObjectFn(ctx, bucket, object, r, opts)
} }
defer cLock.Unlock(lkctx.Cancel) defer cLock.Unlock(lkctx)
// Initialize pipe to stream data to backend // Initialize pipe to stream data to backend
pipeReader, pipeWriter := io.Pipe() pipeReader, pipeWriter := io.Pipe()
hashReader, err := hash.NewReader(pipeReader, size, "", "", r.ActualSize()) hashReader, err := hash.NewReader(pipeReader, size, "", "", r.ActualSize())
+1 -1
View File
@@ -20,8 +20,8 @@ package cmd
import ( import (
"net/http" "net/http"
"github.com/gorilla/mux"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux"
"github.com/minio/pkg/bucket/policy" "github.com/minio/pkg/bucket/policy"
) )
+47 -19
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc. // Copyright (c) 2015-2023 MinIO, Inc.
// //
// This file is part of MinIO Object Storage stack // This file is part of MinIO Object Storage stack
// //
@@ -34,7 +34,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/minio/kes" "github.com/minio/kes-go"
"github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/crypto"
"github.com/minio/minio/internal/etag" "github.com/minio/minio/internal/etag"
"github.com/minio/minio/internal/fips" "github.com/minio/minio/internal/fips"
@@ -48,14 +48,16 @@ import (
var ( var (
// AWS errors for invalid SSE-C requests. // AWS errors for invalid SSE-C requests.
errEncryptedObject = errors.New("The object was stored using a form of SSE") errEncryptedObject = errors.New("The object was stored using a form of SSE")
errInvalidSSEParameters = errors.New("The SSE-C key for key-rotation is not correct") // special access denied errInvalidSSEParameters = errors.New("The SSE-C key for key-rotation is not correct") // special access denied
errKMSNotConfigured = errors.New("KMS not configured for a server side encrypted object") errKMSNotConfigured = errors.New("KMS not configured for a server side encrypted objects")
errKMSKeyNotFound = errors.New("Invalid KMS keyId") errKMSKeyNotFound = errors.New("Unknown KMS key ID")
errKMSDefaultKeyAlreadyConfigured = errors.New("A default encryption already exists on KMS")
// Additional MinIO errors for SSE-C requests. // Additional MinIO errors for SSE-C requests.
errObjectTampered = errors.New("The requested object was modified and may be compromised") errObjectTampered = errors.New("The requested object was modified and may be compromised")
// error returned when invalid encryption parameters are specified // error returned when invalid encryption parameters are specified
errInvalidEncryptionParameters = errors.New("The encryption parameters are not applicable to this object") errInvalidEncryptionParameters = errors.New("The encryption parameters are not applicable to this object")
errInvalidEncryptionParametersSSEC = errors.New("SSE-C encryption parameters are not supported on this bucket")
) )
const ( const (
@@ -1043,18 +1045,6 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e
return encrypted, nil return encrypted, nil
} }
// The customer key in the header is used by the gateway for encryption in the case of
// s3 gateway double encryption. A new client key is derived from the customer provided
// key to be sent to the s3 backend for encryption at the backend.
func deriveClientKey(clientKey [32]byte, bucket, object string) [32]byte {
var key [32]byte
mac := hmac.New(sha256.New, clientKey[:])
mac.Write([]byte(crypto.SSEC.String()))
mac.Write([]byte(path.Join(bucket, object)))
mac.Sum(key[:0])
return key
}
type ( type (
objectMetaEncryptFn func(baseKey string, data []byte) []byte objectMetaEncryptFn func(baseKey string, data []byte) []byte
objectMetaDecryptFn func(baseKey string, data []byte) ([]byte, error) objectMetaDecryptFn func(baseKey string, data []byte) ([]byte, error)
@@ -1095,6 +1085,44 @@ func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn {
} }
} }
// metadataEncryptFn provides an encryption function for metadata.
// Will return nil, nil if unencrypted.
func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn, error) {
kind, _ := crypto.IsEncrypted(o.UserDefined)
switch kind {
case crypto.SSEC:
if crypto.SSECopy.IsRequested(headers) {
key, err := crypto.SSECopy.ParseHTTP(headers)
if err != nil {
return nil, err
}
objectEncryptionKey, err := decryptObjectMeta(key[:], o.Bucket, o.Name, o.UserDefined)
if err != nil {
return nil, err
}
if len(objectEncryptionKey) == 32 {
var key crypto.ObjectKey
copy(key[:], objectEncryptionKey)
return metadataEncrypter(key), nil
}
return nil, errors.New("metadataEncryptFn: unexpected key size")
}
case crypto.S3, crypto.S3KMS:
objectEncryptionKey, err := decryptObjectMeta(nil, o.Bucket, o.Name, o.UserDefined)
if err != nil {
return nil, err
}
if len(objectEncryptionKey) == 32 {
var key crypto.ObjectKey
copy(key[:], objectEncryptionKey)
return metadataEncrypter(key), nil
}
return nil, errors.New("metadataEncryptFn: unexpected key size")
}
return nil, nil
}
// decryptChecksums will attempt to decode checksums and return it/them if set. // decryptChecksums will attempt to decode checksums and return it/them if set.
func (o *ObjectInfo) decryptChecksums() map[string]string { func (o *ObjectInfo) decryptChecksums() map[string]string {
data := o.Checksum data := o.Checksum
+15 -2
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"fmt" "fmt"
"runtime"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -207,7 +208,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
} }
// Returns all the expanded endpoints, each argument is expanded separately. // Returns all the expanded endpoints, each argument is expanded separately.
func (s endpointSet) getEndpoints() (endpoints []string) { func (s *endpointSet) getEndpoints() (endpoints []string) {
if len(s.endpoints) != 0 { if len(s.endpoints) != 0 {
return s.endpoints return s.endpoints
} }
@@ -343,7 +344,13 @@ func createServerEndpoints(serverAddr string, args ...string) (
return nil, -1, errInvalidArgument return nil, -1, errInvalidArgument
} }
if !ellipses.HasEllipses(args...) { ok := true
for _, arg := range args {
ok = ok && !ellipses.HasEllipses(arg)
}
// None of the args have ellipses use the old style.
if ok {
setArgs, err := GetAllSets(args...) setArgs, err := GetAllSets(args...)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
@@ -358,6 +365,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
DrivesPerSet: len(setArgs[0]), DrivesPerSet: len(setArgs[0]),
Endpoints: endpointList, Endpoints: endpointList,
CmdLine: strings.Join(args, " "), CmdLine: strings.Join(args, " "),
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
}) })
setupType = newSetupType setupType = newSetupType
return endpointServerPools, setupType, nil return endpointServerPools, setupType, nil
@@ -365,6 +373,10 @@ func createServerEndpoints(serverAddr string, args ...string) (
var foundPrevLocal bool var foundPrevLocal bool
for _, arg := range args { for _, arg := range args {
if !ellipses.HasEllipses(arg) && len(args) > 1 {
// TODO: support SNSD deployments to be decommissioned in future
return nil, -1, fmt.Errorf("all args must have ellipses for pool expansion (%w) args: %s", errInvalidArgument, args)
}
setArgs, err := GetAllSets(arg) setArgs, err := GetAllSets(arg)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
@@ -379,6 +391,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
DrivesPerSet: len(setArgs[0]), DrivesPerSet: len(setArgs[0]),
Endpoints: endpointList, Endpoints: endpointList,
CmdLine: arg, CmdLine: arg,
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
}); err != nil { }); err != nil {
return nil, -1, err return nil, -1, err
} }
+46 -4
View File
@@ -203,6 +203,7 @@ type PoolEndpoints struct {
DrivesPerSet int DrivesPerSet int
Endpoints Endpoints Endpoints Endpoints
CmdLine string CmdLine string
Platform string
} }
// EndpointServerPools - list of list of endpoints // EndpointServerPools - list of list of endpoints
@@ -417,6 +418,27 @@ func (endpoints Endpoints) atleastOneEndpointLocal() bool {
return false return false
} }
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
for _, hostIP := range hostIPs.ToSlice() {
if net.ParseIP(hostIP).IsLoopback() {
loopback++
}
}
return loopback == len(hostIPs)
}
// UpdateIsLocal - resolves the host and discovers the local host. // UpdateIsLocal - resolves the host and discovers the local host.
func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error { func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
orchestrated := IsDocker() || IsKubernetes() orchestrated := IsDocker() || IsKubernetes()
@@ -452,6 +474,26 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
endpoints[i].Hostname(), endpoints[i].Hostname(),
) )
if orchestrated && hostResolveToLocalhost(endpoints[i]) {
// time elapsed
timeElapsed := time.Since(startTime)
// log error only if more than a second has elapsed
if timeElapsed > time.Second {
reqInfo.AppendTags("elapsedTime",
humanize.RelTime(startTime,
startTime.Add(timeElapsed),
"elapsed",
"",
))
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)
}
continue
}
// return err if not Docker or Kubernetes // return err if not Docker or Kubernetes
// We use IsDocker() to check for Docker environment // We use IsDocker() to check for Docker environment
// We use IsKubernetes() to check for Kubernetes environment // We use IsKubernetes() to check for Kubernetes environment
@@ -465,7 +507,7 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
if err != nil { if err != nil {
// time elapsed // time elapsed
timeElapsed := time.Since(startTime) timeElapsed := time.Since(startTime)
// log error only if more than 1s elapsed // log error only if more than a second has elapsed
if timeElapsed > time.Second { if timeElapsed > time.Second {
reqInfo.AppendTags("elapsedTime", reqInfo.AppendTags("elapsedTime",
humanize.RelTime(startTime, humanize.RelTime(startTime,
@@ -475,7 +517,7 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
)) ))
ctx := logger.SetReqInfo(GlobalContext, ctx := logger.SetReqInfo(GlobalContext,
reqInfo) reqInfo)
logger.LogIf(ctx, err, logger.Application) logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.Application)
} }
} else { } else {
resolvedList[i] = true resolvedList[i] = true
@@ -592,14 +634,14 @@ func CreateEndpoints(serverAddr string, foundLocal bool, args ...[]string) (Endp
return endpoints, setupType, err return endpoints, setupType, err
} }
if endpoint.Type() != PathEndpointType { if endpoint.Type() != PathEndpointType {
return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg("use path style endpoint for FS setup") return endpoints, setupType, config.ErrInvalidEndpoint(nil).Msg("use path style endpoint for single node setup")
} }
endpoints = append(endpoints, endpoint) endpoints = append(endpoints, endpoint)
setupType = ErasureSDSetupType setupType = ErasureSDSetupType
// Check for cross device mounts if any. // Check for cross device mounts if any.
if err = checkCrossDeviceMounts(endpoints); err != nil { if err = checkCrossDeviceMounts(endpoints); err != nil {
return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg(err.Error()) return endpoints, setupType, config.ErrInvalidEndpoint(nil).Msg(err.Error())
} }
return endpoints, setupType, nil return endpoints, setupType, nil
-277
View File
@@ -1,277 +0,0 @@
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"context"
"errors"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/sync/errgroup"
)
// list all errors that can be ignore in a bucket operation.
var bucketOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errUnformattedDisk)
// list all errors that can be ignored in a bucket metadata operation.
var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound)
// Bucket operations
// MakeBucket - make a bucket.
func (er erasureObjects) MakeBucketWithLocation(ctx context.Context, bucket string, opts MakeBucketOptions) error {
defer NSUpdated(bucket, slashSeparator)
// Verify if bucket is valid.
if !isMinioMetaBucketName(bucket) {
if err := s3utils.CheckValidBucketNameStrict(bucket); err != nil {
return BucketNameInvalid{Bucket: bucket}
}
}
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
// Make a volume entry on all underlying storage disks.
for index := range storageDisks {
index := index
g.Go(func() error {
if storageDisks[index] != nil {
if err := storageDisks[index].MakeVol(ctx, bucket); err != nil {
if opts.ForceCreate && errors.Is(err, errVolumeExists) {
// No need to return error when force create was
// requested.
return nil
}
if !errors.Is(err, errVolumeExists) {
logger.LogIf(ctx, err)
}
return err
}
return nil
}
return errDiskNotFound
}, index)
}
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
return toObjectErr(err, bucket)
}
func undoDeleteBucket(storageDisks []StorageAPI, bucket string) {
g := errgroup.WithNErrs(len(storageDisks))
// Undo previous make bucket entry on all underlying storage disks.
for index := range storageDisks {
if storageDisks[index] == nil {
continue
}
index := index
g.Go(func() error {
_ = storageDisks[index].MakeVol(context.Background(), bucket)
return nil
}, index)
}
// Wait for all make vol to finish.
g.Wait()
}
// getBucketInfo - returns the BucketInfo from one of the load balanced disks.
func (er erasureObjects) getBucketInfo(ctx context.Context, bucketName string, opts BucketOptions) (bucketInfo BucketInfo, err error) {
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
bucketsInfo := make([]BucketInfo, len(storageDisks))
// Undo previous make bucket entry on all underlying storage disks.
for index := range storageDisks {
index := index
g.Go(func() error {
if storageDisks[index] == nil {
return errDiskNotFound
}
volInfo, err := storageDisks[index].StatVol(ctx, bucketName)
if err != nil {
if opts.Deleted {
dvi, derr := storageDisks[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucketName))
if derr != nil {
return err
}
bucketsInfo[index] = BucketInfo{Name: bucketName, Deleted: dvi.Created}
return nil
}
return err
}
bucketsInfo[index] = BucketInfo{Name: volInfo.Name, Created: volInfo.Created}
return nil
}, index)
}
errs := g.Wait()
for i, err := range errs {
if err == nil {
return bucketsInfo[i], nil
}
}
// If all our errors were ignored, then we try to
// reduce to one error based on read quorum.
// `nil` is deliberately passed for ignoredErrs
// because these errors were already ignored.
return BucketInfo{}, reduceReadQuorumErrs(ctx, errs, nil, er.defaultRQuorum())
}
// GetBucketInfo - returns BucketInfo for a bucket.
func (er erasureObjects) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (bi BucketInfo, e error) {
bucketInfo, err := er.getBucketInfo(ctx, bucket, opts)
if err != nil {
return bi, toObjectErr(err, bucket)
}
return bucketInfo, nil
}
// DeleteBucket - deletes a bucket.
func (er erasureObjects) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
// Collect if all disks report volume not found.
defer NSUpdated(bucket, slashSeparator)
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
for index := range storageDisks {
index := index
g.Go(func() error {
if storageDisks[index] != nil {
return storageDisks[index].DeleteVol(ctx, bucket, opts.Force)
}
return errDiskNotFound
}, index)
}
// Wait for all the delete vols to finish.
dErrs := g.Wait()
if opts.Force {
for _, err := range dErrs {
if err != nil {
undoDeleteBucket(storageDisks, bucket)
return toObjectErr(err, bucket)
}
}
return nil
}
err := reduceWriteQuorumErrs(ctx, dErrs, bucketOpIgnoredErrs, er.defaultWQuorum())
if err == errErasureWriteQuorum && !opts.NoRecreate {
undoDeleteBucket(storageDisks, bucket)
}
if err == nil || errors.Is(err, errVolumeNotFound) {
var purgedDangling bool
// At this point we have `err == nil` but some errors might be `errVolumeNotEmpty`
// we should proceed to attempt a force delete of such buckets.
for index, err := range dErrs {
if err == errVolumeNotEmpty && storageDisks[index] != nil {
storageDisks[index].RenameFile(ctx, bucket, "", minioMetaTmpDeletedBucket, mustGetUUID())
purgedDangling = true
}
}
// if we purged dangling buckets, ignore errVolumeNotFound error.
if purgedDangling {
err = nil
}
if opts.SRDeleteOp == MarkDelete {
er.markDelete(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix, bucket))
}
}
return toObjectErr(err, bucket)
}
// markDelete creates a vol entry in .minio.sys/buckets/.deleted until site replication
// syncs the delete to peers
func (er erasureObjects) markDelete(ctx context.Context, bucket, prefix string) error {
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
// Make a volume entry on all underlying storage disks.
for index := range storageDisks {
index := index
if storageDisks[index] == nil {
continue
}
g.Go(func() error {
if err := storageDisks[index].MakeVol(ctx, pathJoin(bucket, prefix)); err != nil {
if errors.Is(err, errVolumeExists) {
return nil
}
return err
}
return nil
}, index)
}
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
return toObjectErr(err, bucket)
}
// purgeDelete deletes vol entry in .minio.sys/buckets/.deleted after site replication
// syncs the delete to peers OR on a new MakeBucket call.
func (er erasureObjects) purgeDelete(ctx context.Context, bucket, prefix string) error {
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
// Make a volume entry on all underlying storage disks.
for index := range storageDisks {
index := index
g.Go(func() error {
if storageDisks[index] != nil {
return storageDisks[index].DeleteVol(ctx, pathJoin(bucket, prefix), true)
}
return errDiskNotFound
}, index)
}
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
return toObjectErr(err, bucket)
}
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
func (er erasureObjects) IsNotificationSupported() bool {
return true
}
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
func (er erasureObjects) IsListenSupported() bool {
return true
}
// IsEncryptionSupported returns whether server side encryption is implemented for this layer.
func (er erasureObjects) IsEncryptionSupported() bool {
return true
}
// IsCompressionSupported returns whether compression is applicable for this layer.
func (er erasureObjects) IsCompressionSupported() bool {
return true
}
// IsTaggingSupported indicates whether erasureObjects implements tagging support.
func (er erasureObjects) IsTaggingSupported() bool {
return true
}
+11 -3
View File
@@ -22,6 +22,7 @@ import (
"io" "io"
"sync" "sync"
"github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
) )
@@ -83,9 +84,16 @@ func (e *Erasure) Encode(ctx context.Context, src io.Reader, writers []io.Writer
for { for {
var blocks [][]byte var blocks [][]byte
n, err := io.ReadFull(src, buf) n, err := io.ReadFull(src, buf)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { if err != nil {
logger.LogIf(ctx, err) if !IsErrIgnored(err, []error{
return 0, err io.EOF,
io.ErrUnexpectedEOF,
}...) {
if !hash.IsChecksumMismatch(err) {
logger.LogIf(ctx, err)
}
return 0, err
}
} }
eof := err == io.EOF || err == io.ErrUnexpectedEOF eof := err == io.EOF || err == io.ErrUnexpectedEOF
if n == 0 && total != 0 { if n == 0 && total != 0 {
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"context" "context"
"time" "time"
"github.com/minio/madmin-go" "github.com/minio/madmin-go/v2"
) )
// commonTime returns a maximally occurring time from a list of time. // commonTime returns a maximally occurring time from a list of time.

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