mirror of
https://github.com/pgsty/minio.git
synced 2026-08-11 00:33:28 +03:00
Compare commits
493 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f79a4ef4d0 | |||
| 2d53854b19 | |||
| e5c83535af | |||
| c904ef966e | |||
| 8f266e0772 | |||
| e0fe7cc391 | |||
| 9d20dec56a | |||
| 597a785253 | |||
| 7d75b1e758 | |||
| 5f78691fcf | |||
| a591e06ae5 | |||
| 443c93c634 | |||
| 5659cddc84 | |||
| 2a03a34bde | |||
| 1654a9b7e6 | |||
| 673a521711 | |||
| 2e23076688 | |||
| b92ac55250 | |||
| 7981509cc8 | |||
| 6d5bc045bc | |||
| d38e020b29 | |||
| 7d29030292 | |||
| 7c7650b7c3 | |||
| ca80eced24 | |||
| d0e0b81d8e | |||
| 391baa1c9a | |||
| ae14681c3e | |||
| 4d698841f4 | |||
| 9906b3ade9 | |||
| bf1769d3e0 | |||
| 63e1ad9f29 | |||
| 2c7bcee53f | |||
| 1fd90c93ff | |||
| e947a844c9 | |||
| 4e2d39293a | |||
| 1228d6bf1a | |||
| fc4561c64c | |||
| 3b7747b42b | |||
| e432e79324 | |||
| 08d74819b6 | |||
| aa3fde1784 | |||
| 0b3eb7f218 | |||
| 69c9496c71 | |||
| b792b36495 | |||
| d3db7d31a3 | |||
| c05ca63158 | |||
| 6d3e0c7db6 | |||
| 0e59e50b39 | |||
| d4b391de1b | |||
| de4d3dac00 | |||
| 534e7161df | |||
| 9b219cd646 | |||
| 3bab4822f3 | |||
| 3c5f2d8916 | |||
| 5808190398 | |||
| b2a82248b1 | |||
| 4e5fcca8b9 | |||
| c36eaedb93 | |||
| 7752b03add | |||
| 01bfc78535 | |||
| 074d70112d | |||
| e8d14c0d90 | |||
| 60d7e8143a | |||
| 9667a170de | |||
| abae30f9e1 | |||
| f9311bc9d1 | |||
| b598402738 | |||
| bd026b913f | |||
| 72ff69d9bb | |||
| f30417d9a8 | |||
| 47a4ad3cd7 | |||
| 2f7a10ab31 | |||
| b534dc69ab | |||
| 7b7d2ea7d4 | |||
| e00de1c302 | |||
| 3549e583a6 | |||
| f5e3eedf34 | |||
| 519dbfebf6 | |||
| 9a267f9270 | |||
| 67bd71b7a5 | |||
| ec49fff583 | |||
| 8b660e18f2 | |||
| 981497799a | |||
| b9bdc17465 | |||
| b413ff9fdb | |||
| 6a15580817 | |||
| 39633a5581 | |||
| 1e83f15e2f | |||
| 888d2bb1d8 | |||
| 847ee5ac45 | |||
| 9a9a49aa84 | |||
| a03ca80269 | |||
| 523bd769f1 | |||
| 8ff70ea5a9 | |||
| da3e7747ca | |||
| 4afb59e63f | |||
| 1526e7ece3 | |||
| 6c07bfee8a | |||
| 446c760820 | |||
| 04f92f1291 | |||
| 4a60a7794d | |||
| e5b16adb1c | |||
| 402a3ac719 | |||
| f3d61c51fc | |||
| 0cde17ae5d | |||
| 8c1bba681b | |||
| dbfb5e797b | |||
| 08ff702434 | |||
| 0e2148264a | |||
| a75f42344b | |||
| 7926401cbd | |||
| 8161411c5d | |||
| f64dea2aac | |||
| 6579304d8c | |||
| 6bb10a81a6 | |||
| 3cf8a7c888 | |||
| 2e38bb5175 | |||
| a372c6a377 | |||
| 93b2f8a0c5 | |||
| 1a6568a25d | |||
| 9e95703efc | |||
| d8e05aca81 | |||
| 410a1ac040 | |||
| 4caa3422bd | |||
| a658b976f5 | |||
| 135874ebdc | |||
| f4f1c42cba | |||
| e7aa26dc29 | |||
| c54ffde568 | |||
| 9a3c992d7a | |||
| 0c855638de | |||
| 943d815783 | |||
| 4c0acba62d | |||
| 62c3cdee75 | |||
| 3212d0c8cd | |||
| 1d03bea965 | |||
| fbfeb59658 | |||
| 701da1282a | |||
| df93ff92ba | |||
| 77d5331e85 | |||
| 14cdadfb56 | |||
| f3a52cc195 | |||
| 7640cd24c9 | |||
| f7b665347e | |||
| 9693c382a8 | |||
| ee1047bd52 | |||
| 5ea5ab162b | |||
| b5a09ff96b | |||
| 95c65f4e8f | |||
| 6bfff7532e | |||
| 1aa8896ad6 | |||
| 3e32ceb39f | |||
| ca1350b092 | |||
| 9205434ed3 | |||
| cd50e9b4bc | |||
| ec816f3840 | |||
| 5f774951b1 | |||
| 2ca9befd2a | |||
| 72f5cb577e | |||
| 928c0181bf | |||
| 03767d26da | |||
| 108e6f92d4 | |||
| d653a59fc0 | |||
| 01bfdf949a | |||
| 98f7821eb3 | |||
| 2d3898e0d5 | |||
| ae46ce9937 | |||
| dfc112c06b | |||
| ca5fab8656 | |||
| 6df76ca73c | |||
| f65dd3e5a2 | |||
| a8d601b64a | |||
| 73b4794cf7 | |||
| e2709ea129 | |||
| 740ec80819 | |||
| d95e054282 | |||
| 7c1f9667d1 | |||
| 9246990496 | |||
| 0cf3d93360 | |||
| cb06aee5ac | |||
| 1c70e9ed1b | |||
| f3d6a2dd37 | |||
| d1c58fc2eb | |||
| b8f05b1471 | |||
| e7baf78ee8 | |||
| 87299eba10 | |||
| d3a07c29ba | |||
| 8d39b715dc | |||
| 7e3166475d | |||
| 5206c0e883 | |||
| 41ec038523 | |||
| 08d3d06a06 | |||
| 074febd9e1 | |||
| aa8d25797b | |||
| 8d7d4adb91 | |||
| ffa91f9794 | |||
| 0c31e61343 | |||
| 9b926f7dbe | |||
| 35d8728990 | |||
| f7ed9a75ba | |||
| 9496c17e13 | |||
| ed64e91f06 | |||
| a481825ae1 | |||
| 7bb0f32332 | |||
| c6f8dc431e | |||
| 78f177b8ee | |||
| 787c44c39d | |||
| f06fee0364 | |||
| c957e0d426 | |||
| 04101d472f | |||
| 51fc145161 | |||
| 9d63bb1b41 | |||
| 8ff2a7a2b9 | |||
| 91f91d8f47 | |||
| a207bd6790 | |||
| 96d226c0b1 | |||
| a86d98826d | |||
| 1bb670ecba | |||
| c9e9a8e2b9 | |||
| 272367ccd2 | |||
| 95bf4a57b6 | |||
| 2228eb61cb | |||
| 5f07eb2d17 | |||
| d96d696841 | |||
| e18c0ab9bf | |||
| faeb2b7e79 | |||
| 97ce11cb6b | |||
| d7daae4762 | |||
| 3d86ae12bc | |||
| ba46ee5dfa | |||
| 912bbb2f1d | |||
| 4f660a8eb7 | |||
| ae4fb1b72e | |||
| b435806d91 | |||
| 06929258bc | |||
| cb577835d9 | |||
| 7f35f74f14 | |||
| 3d6194e93c | |||
| 72c7845f7e | |||
| 1c99597a06 | |||
| feb9d8480b | |||
| 4e670458b8 | |||
| 48deccdc40 | |||
| 2eee744e34 | |||
| 3f72439b8a | |||
| 468a9fae83 | |||
| d87f91720b | |||
| aa0eec16ab | |||
| d63e603040 | |||
| 8222a640ac | |||
| 7e45d84ace | |||
| 139a606f0a | |||
| 289223b6de | |||
| c61dd16a1e | |||
| 3e38fa54a5 | |||
| 4a02189ba0 | |||
| 3d4fc28ec9 | |||
| ec3a3bb10d | |||
| 364d3a0ac9 | |||
| cb536a73eb | |||
| 428155add9 | |||
| 8bce123bba | |||
| 0a56dbde2f | |||
| 7ff4164d65 | |||
| 53a14c7301 | |||
| dc45a5010d | |||
| 4b9192034c | |||
| deeadd1a37 | |||
| 1fc4203c19 | |||
| 15b930be1f | |||
| 7fd76dbbb7 | |||
| da81c6cc27 | |||
| a03dac41eb | |||
| b657ffa496 | |||
| 55778ae278 | |||
| d990661d1f | |||
| 280526caf7 | |||
| 1173b26fc8 | |||
| 383489d5d9 | |||
| 9370b11684 | |||
| 999bbd3a14 | |||
| 235edd88aa | |||
| b5e074e54c | |||
| 4d7068931a | |||
| d7fb6fddf6 | |||
| 7213bd7131 | |||
| d4aac7cd72 | |||
| 741de4cf94 | |||
| f168ef9989 | |||
| a0de56abb6 | |||
| c201d8bda9 | |||
| d2373d5d6c | |||
| 93fb7d62d8 | |||
| 485298b680 | |||
| 062f0cffad | |||
| ce1c640ce0 | |||
| 5c32058ff3 | |||
| 24b4f9d748 | |||
| 81d7531f1f | |||
| b4a23f720e | |||
| a2f6252b2f | |||
| 6c964fede5 | |||
| a25a8312d8 | |||
| b2c5b75efa | |||
| 2dfa9adc5d | |||
| 88a89213ff | |||
| 8e2238ea09 | |||
| 2007dd26ae | |||
| 31e8f7c525 | |||
| 51f62a8da3 | |||
| 650efc2e96 | |||
| 1787bcfc91 | |||
| 2cc4997d24 | |||
| 934f6cabf6 | |||
| 233cc3905a | |||
| 68dd74c5ab | |||
| 48b590e14b | |||
| 8ab3dac4f2 | |||
| 3fb0cbc030 | |||
| 837a2a3d4b | |||
| e91a4a414c | |||
| 74ccee6619 | |||
| c26b8d4eb8 | |||
| dae9dc4847 | |||
| 89f759566c | |||
| 5dc1ef0b87 | |||
| cd7551031b | |||
| df57bfcd6c | |||
| dfb1f39b57 | |||
| e3e3d92241 | |||
| 1b5f28e99b | |||
| b69bcdcdc4 | |||
| e385f54185 | |||
| 9a4d003ac7 | |||
| d5656eeb65 | |||
| 8edc67b0a9 | |||
| 18b0b7299a | |||
| 09b0e7133d | |||
| 6d08af61a0 | |||
| a7577da768 | |||
| 325fd80687 | |||
| 09626d78ff | |||
| 8f03c6e0db | |||
| 2c2f5d871c | |||
| c599c11e70 | |||
| ef06644799 | |||
| 6769d4dd54 | |||
| f3e7c42425 | |||
| f46bee242c | |||
| d7520f0ae6 | |||
| 44b70eb646 | |||
| 828d4df6f0 | |||
| 467714f33b | |||
| f8696cc8f6 | |||
| 9a7c7ab2d0 | |||
| 40fb3371fa | |||
| 51874a5776 | |||
| 62ce52c8fd | |||
| 2bdb9511bd | |||
| 9a012a53ef | |||
| 0aae0180fb | |||
| 1dd8ef09a6 | |||
| 95032e4710 | |||
| b1351e2dee | |||
| 30c2596512 | |||
| 2b5e4b853c | |||
| 85bcb5874a | |||
| 92788e4cf4 | |||
| 8a698fef71 | |||
| b49ce1713f | |||
| c2b54d92f6 | |||
| f965434022 | |||
| a3ac62596c | |||
| 2faba02d6b | |||
| ee158e1610 | |||
| fa68efb1e7 | |||
| 8c53a4405a | |||
| c32f699105 | |||
| 53aa8f5650 | |||
| 56887f3208 | |||
| 92180bc793 | |||
| 22aa16ab12 | |||
| 526b829a09 | |||
| c44f311c4f | |||
| 9ea5d08ecd | |||
| 35deb1a8e2 | |||
| c7f7c47388 | |||
| cb7dab17cb | |||
| cd419a35fe | |||
| e06168596f | |||
| 4c8197a119 | |||
| 23c10350f3 | |||
| b6e98aed01 | |||
| 00dcba9ddd | |||
| 607cafadbc | |||
| 68dde2359f | |||
| f9dbf41e27 | |||
| 7405760f44 | |||
| 7e4a6b4bcd | |||
| b5791e6f28 | |||
| 00cb58eaf3 | |||
| f961ec4aaf | |||
| 134db72bb7 | |||
| 6fd0b434e2 | |||
| effe21f3eb | |||
| 1118b285d3 | |||
| 912a0031b7 | |||
| a14e192376 | |||
| f8e15e7d09 | |||
| 7b9f9e0628 | |||
| ac8e9ce04f | |||
| cfd8645843 | |||
| 0c068b15c7 | |||
| 30a466aa71 | |||
| 4d94609c44 | |||
| 6b63123ca9 | |||
| 0cc9fb73e1 | |||
| eac4e4b279 | |||
| 6d381f7c0a | |||
| 4fa06aefc6 | |||
| 0e177a44e0 | |||
| afd19de5a9 | |||
| e3fbac9e24 | |||
| a9cf32811c | |||
| 53997ecc79 | |||
| 8e69f3cb89 | |||
| 997ba3a574 | |||
| 8e68ff9321 | |||
| 62761a23e6 | |||
| 404d8b3084 | |||
| 6005ad3d48 | |||
| 035a3ea4ae | |||
| 7ec43bd177 | |||
| a29c66ed74 | |||
| e104b183d8 | |||
| 7e082f232e | |||
| d28bf71f25 | |||
| 5b1a74b6b2 | |||
| eead4db1d2 | |||
| 980fb5e2ab | |||
| 9bcc46d93d | |||
| 22687c1f50 | |||
| ebc6c9b498 | |||
| 630963fa6b | |||
| f674168b8b | |||
| 7e023f2d50 | |||
| 27d02ea6f7 | |||
| 794a7993cb | |||
| 6f16d1cb2c | |||
| 7aa00bff89 | |||
| e046eb1d17 | |||
| ba975ca320 | |||
| fec13b0ec1 | |||
| 100c35c281 | |||
| 8414aff424 | |||
| f225ca3312 | |||
| 8b68e0bfdc | |||
| 6ae97aedc9 | |||
| 960d604013 | |||
| 63bf5f42a1 | |||
| ff80cfd83d | |||
| 99fde2ba85 | |||
| ce0cb913bc | |||
| d99d16e8c3 | |||
| 31743789dc | |||
| 6fd63e920a | |||
| 59cc3e93d6 | |||
| 61a4bb38cd | |||
| b192bc348c | |||
| 6440d0fbf3 | |||
| ee0055b929 | |||
| 24ecc44bac | |||
| 0ae4915a93 | |||
| 4cd777a5e0 | |||
| 65028d4a35 | |||
| caac9d216e | |||
| 057192913c | |||
| f25cbdf43c | |||
| 6da4a9c7bb | |||
| 80ca120088 | |||
| a669946357 | |||
| 7ffc162ea8 | |||
| bcfd7fbbcf | |||
| 486e2e48ea | |||
| 2ddf2ca934 | |||
| 403ec7cf21 | |||
| 29b1a29044 | |||
| b4ab8e095a | |||
| ff4f4d4649 | |||
| 9987ff570b | |||
| cff8235068 | |||
| 9ef132c33b | |||
| ff8269575a |
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
[codespell]
|
|
||||||
# certs_test.go - has lots of ceritificates.
|
|
||||||
skip = go.mod,go.sum,*.txt,LICENSE,*.zip,.git,*.pdf,*.svg,.codespellrc,CREDITS,certs_test.go
|
|
||||||
check-hidden = true
|
|
||||||
ignore-regex = \b(newfolder/afile|filterIn|HelpES)\b
|
|
||||||
ignore-words-list = inout,bui,to,bu,te,ot,toi,ist,parms,flate
|
|
||||||
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Codespell
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
codespell:
|
|
||||||
name: Check for spelling errors
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Codespell
|
|
||||||
uses: codespell-project/actions-codespell@v2
|
|
||||||
@@ -9,6 +9,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@v1
|
uses: actions/dependency-review-action@v4
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ name: Crosscompile
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
@@ -21,11 +20,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: FIPS Build Test
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,11 +20,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Healing Functional Tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,11 +20,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ name: Linters and Tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
@@ -21,23 +20,24 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, Windows]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'Windows'
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
run: |
|
run: |
|
||||||
|
Set-MpPreference -DisableRealtimeMonitoring $true
|
||||||
netsh int ipv4 set dynamicport tcp start=60000 num=61000
|
netsh int ipv4 set dynamicport tcp start=60000 num=61000
|
||||||
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
||||||
go test -v --timeout 50m ./...
|
go test -v --timeout 120m ./...
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Functional Tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,11 +20,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Helm Chart linting
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -20,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Helm
|
- name: Install Helm
|
||||||
uses: azure/setup-helm@v3
|
uses: azure/setup-helm@v3
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: IAM integration
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -62,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.21.x]
|
go-version: [1.22.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"]
|
||||||
@@ -76,8 +75,8 @@ jobs:
|
|||||||
openid: "http://127.0.0.1:5556/dex"
|
openid: "http://127.0.0.1:5556/dex"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -112,6 +111,12 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
go run docs/iam/access-manager-plugin.go &
|
go run docs/iam/access-manager-plugin.go &
|
||||||
make test-iam
|
make test-iam
|
||||||
|
- name: Test MinIO Old Version data to IAM import current version
|
||||||
|
if: matrix.ldap == 'ldaphost:389'
|
||||||
|
env:
|
||||||
|
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
|
run: |
|
||||||
|
make test-iam-ldap-upgrade-import
|
||||||
- name: Test LDAP for automatic site replication
|
- name: Test LDAP for automatic site replication
|
||||||
if: matrix.ldap == 'localhost:389'
|
if: matrix.ldap == 'localhost:389'
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -25,12 +24,12 @@ jobs:
|
|||||||
sudo -S rm -rf ${GITHUB_WORKSPACE}
|
sudo -S rm -rf ${GITHUB_WORKSPACE}
|
||||||
mkdir ${GITHUB_WORKSPACE}
|
mkdir ${GITHUB_WORKSPACE}
|
||||||
- name: checkout-step
|
- name: checkout-step
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: setup-go-step
|
- name: setup-go-step
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.x
|
go-version: 1.22.x
|
||||||
|
|
||||||
- name: github sha short
|
- name: github sha short
|
||||||
id: vars
|
id: vars
|
||||||
@@ -56,6 +55,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
|
- name: resiliency
|
||||||
|
run: |
|
||||||
|
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "resiliency" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
- name: The job must cleanup
|
- name: The job must cleanup
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
|
command: server --console-address ":9001" http://minio{1...4}/rdata{1...2}
|
||||||
|
expose:
|
||||||
|
- "9000"
|
||||||
|
- "9001"
|
||||||
|
environment:
|
||||||
|
MINIO_CI_CD: "on"
|
||||||
|
MINIO_ROOT_USER: "minio"
|
||||||
|
MINIO_ROOT_PASSWORD: "minio123"
|
||||||
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
|
MINIO_DRIVE_MAX_TIMEOUT: "5s"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio1
|
||||||
|
volumes:
|
||||||
|
- rdata1-1:/rdata1
|
||||||
|
- rdata1-2:/rdata2
|
||||||
|
|
||||||
|
minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio2
|
||||||
|
volumes:
|
||||||
|
- rdata2-1:/rdata1
|
||||||
|
- rdata2-2:/rdata2
|
||||||
|
|
||||||
|
minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio3
|
||||||
|
volumes:
|
||||||
|
- rdata3-1:/rdata1
|
||||||
|
- rdata3-2:/rdata2
|
||||||
|
|
||||||
|
minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio4
|
||||||
|
volumes:
|
||||||
|
- rdata4-1:/rdata1
|
||||||
|
- rdata4-2:/rdata2
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-4-node.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- minio1
|
||||||
|
- minio2
|
||||||
|
- minio3
|
||||||
|
- minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
rdata1-1:
|
||||||
|
rdata1-2:
|
||||||
|
rdata2-1:
|
||||||
|
rdata2-2:
|
||||||
|
rdata3-1:
|
||||||
|
rdata3-2:
|
||||||
|
rdata4-1:
|
||||||
|
rdata4-2:
|
||||||
@@ -23,10 +23,9 @@ http {
|
|||||||
# include /etc/nginx/conf.d/*.conf;
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
upstream minio {
|
upstream minio {
|
||||||
server minio1:9000;
|
server minio1:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio2:9000;
|
server minio2:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio3:9000;
|
server minio3:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio4:9000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream console {
|
upstream console {
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ http {
|
|||||||
# include /etc/nginx/conf.d/*.conf;
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
upstream minio {
|
upstream minio {
|
||||||
server minio1:9000;
|
server minio1:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio2:9000;
|
server minio2:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio3:9000;
|
server minio3:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio4:9000;
|
server minio4:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio5:9000;
|
server minio5:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio6:9000;
|
server minio6:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio7:9000;
|
server minio7:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio8:9000;
|
server minio8:9000 max_fails=1 fail_timeout=10s;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream console {
|
upstream console {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ http {
|
|||||||
# include /etc/nginx/conf.d/*.conf;
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
upstream minio {
|
upstream minio {
|
||||||
server minio1:9000;
|
server minio1:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio2:9000;
|
server minio2:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio3:9000;
|
server minio3:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio4:9000;
|
server minio4:9000 max_fails=1 fail_timeout=10s;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream console {
|
upstream console {
|
||||||
|
|||||||
@@ -24,11 +24,6 @@ if [ ! -f ./mc ]; then
|
|||||||
chmod +x mc
|
chmod +x mc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(
|
|
||||||
cd /tmp
|
|
||||||
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
|
||||||
)
|
|
||||||
|
|
||||||
export RELEASE=RELEASE.2023-08-29T23-07-35Z
|
export RELEASE=RELEASE.2023-08-29T23-07-35Z
|
||||||
|
|
||||||
docker-compose -f docker-compose-site1.yaml up -d
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
@@ -48,10 +43,10 @@ sleep 30s
|
|||||||
|
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
s3-check-md5 -h
|
./s3-check-md5 -h
|
||||||
|
|
||||||
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
if [ $failed_count_site1 -ne 0 ]; then
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
echo "failed with multipart on site1 uploads"
|
echo "failed with multipart on site1 uploads"
|
||||||
@@ -67,8 +62,8 @@ fi
|
|||||||
|
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
## we do not need to fail here, since we are going to test
|
## we do not need to fail here, since we are going to test
|
||||||
## upgrading to master, healing and being able to recover
|
## upgrading to master, healing and being able to recover
|
||||||
@@ -96,8 +91,8 @@ for i in $(seq 1 10); do
|
|||||||
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
|
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
|
||||||
done
|
done
|
||||||
|
|
||||||
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
if [ $failed_count_site1 -ne 0 ]; then
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
echo "failed with multipart on site1 uploads"
|
echo "failed with multipart on site1 uploads"
|
||||||
@@ -109,6 +104,43 @@ if [ $failed_count_site2 -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Add user group test
|
||||||
|
./mc admin user add site1 site-replication-issue-user site-replication-issue-password
|
||||||
|
./mc admin group add site1 site-replication-issue-group site-replication-issue-user
|
||||||
|
|
||||||
|
max_wait_attempts=30
|
||||||
|
wait_interval=5
|
||||||
|
|
||||||
|
attempt=1
|
||||||
|
while true; do
|
||||||
|
diff <(./mc admin group info site1 site-replication-issue-group) <(./mc admin group info site2 site-replication-issue-group)
|
||||||
|
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
echo "Outputs are consistent."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
remaining_attempts=$((max_wait_attempts - attempt))
|
||||||
|
if ((attempt >= max_wait_attempts)); then
|
||||||
|
echo "Outputs remain inconsistent after $max_wait_attempts attempts. Exiting with error."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "Outputs are inconsistent. Waiting for $wait_interval seconds (attempt $attempt/$max_wait_attempts)."
|
||||||
|
sleep $wait_interval
|
||||||
|
fi
|
||||||
|
|
||||||
|
((attempt++))
|
||||||
|
done
|
||||||
|
|
||||||
|
status=$(./mc admin group info site1 site-replication-issue-group --json | jq .groupStatus | tr -d '"')
|
||||||
|
|
||||||
|
if [[ $status == "enabled" ]]; then
|
||||||
|
echo "Success"
|
||||||
|
else
|
||||||
|
echo "Expected status: enabled, actual status: $status"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
|
|
||||||
## change working directory
|
## change working directory
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: MinIO advanced tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -22,11 +21,11 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -36,6 +35,18 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-decom
|
make test-decom
|
||||||
|
|
||||||
|
- name: Test ILM
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
make test-ilm
|
||||||
|
|
||||||
|
- name: Test PBAC
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
make test-pbac
|
||||||
|
|
||||||
- name: Test Config File
|
- name: Test Config File
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Root lockdown tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,12 +20,12 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ docker volume rm $(docker volume ls -f dangling=true) || true
|
|||||||
cd .github/workflows/mint
|
cd .github/workflows/mint
|
||||||
|
|
||||||
docker-compose -f minio-${MODE}.yaml up -d
|
docker-compose -f minio-${MODE}.yaml up -d
|
||||||
sleep 30s
|
sleep 1m
|
||||||
|
|
||||||
docker system prune -f || true
|
docker system prune -f || true
|
||||||
docker volume prune -f || true
|
docker volume prune -f || true
|
||||||
@@ -26,6 +26,9 @@ docker volume rm $(docker volume ls -q -f dangling=true) || true
|
|||||||
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2
|
||||||
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6
|
||||||
|
|
||||||
|
# Pause one node, to check that all S3 calls work while one node goes wrong
|
||||||
|
[ "${MODE}" == "resiliency" ] && docker-compose -f minio-${MODE}.yaml pause minio4
|
||||||
|
|
||||||
docker run --rm --net=mint_default \
|
docker run --rm --net=mint_default \
|
||||||
--name="mint-${MODE}-${JOB_NAME}" \
|
--name="mint-${MODE}-${JOB_NAME}" \
|
||||||
-e SERVER_ENDPOINT="nginx:9000" \
|
-e SERVER_ENDPOINT="nginx:9000" \
|
||||||
@@ -35,6 +38,18 @@ docker run --rm --net=mint_default \
|
|||||||
-e MINT_MODE="${MINT_MODE}" \
|
-e MINT_MODE="${MINT_MODE}" \
|
||||||
docker.io/minio/mint:edge
|
docker.io/minio/mint:edge
|
||||||
|
|
||||||
|
# FIXME: enable this after fixing aws-sdk-java-v2 tests
|
||||||
|
# # unpause the node, to check that all S3 calls work while one node goes wrong
|
||||||
|
# [ "${MODE}" == "resiliency" ] && docker-compose -f minio-${MODE}.yaml unpause minio4
|
||||||
|
# [ "${MODE}" == "resiliency" ] && docker run --rm --net=mint_default \
|
||||||
|
# --name="mint-${MODE}-${JOB_NAME}" \
|
||||||
|
# -e SERVER_ENDPOINT="nginx:9000" \
|
||||||
|
# -e ACCESS_KEY="${ACCESS_KEY}" \
|
||||||
|
# -e SECRET_KEY="${SECRET_KEY}" \
|
||||||
|
# -e ENABLE_HTTPS=0 \
|
||||||
|
# -e MINT_MODE="${MINT_MODE}" \
|
||||||
|
# docker.io/minio/mint:edge
|
||||||
|
|
||||||
docker-compose -f minio-${MODE}.yaml down || true
|
docker-compose -f minio-${MODE}.yaml down || true
|
||||||
sleep 10s
|
sleep 10s
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Shell formatting checks
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -14,7 +13,7 @@ jobs:
|
|||||||
name: runner / shfmt
|
name: runner / shfmt
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: luizm/action-sh-checker@master
|
- uses: luizm/action-sh-checker@master
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: Spelling
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
name: Spell Check with Typos
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Actions Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check spelling of repo
|
||||||
|
uses: crate-ci/typos@master
|
||||||
|
|
||||||
@@ -3,8 +3,7 @@ name: Upgrade old version tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,12 +20,12 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.5
|
go-version: 1.22.3
|
||||||
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
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Run govulncheck
|
- name: Run govulncheck
|
||||||
run: govulncheck ./...
|
run: govulncheck -show verbose ./...
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
+10
-1
@@ -43,4 +43,13 @@ docs/debugging/inspect/inspect
|
|||||||
docs/debugging/pprofgoparser/pprofgoparser
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
docs/debugging/reorder-disks/reorder-disks
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
docs/debugging/populate-hard-links/populate-hardlinks
|
docs/debugging/populate-hard-links/populate-hardlinks
|
||||||
docs/debugging/xattr/xattr
|
docs/debugging/xattr/xattr
|
||||||
|
hash-set
|
||||||
|
healing-bin
|
||||||
|
inspect
|
||||||
|
pprofgoparser
|
||||||
|
reorder-disks
|
||||||
|
s3-check-md5
|
||||||
|
s3-verify
|
||||||
|
xattr
|
||||||
|
xl-meta
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
[files]
|
||||||
|
extend-exclude = [
|
||||||
|
".git/",
|
||||||
|
"docs/",
|
||||||
|
]
|
||||||
|
ignore-hidden = false
|
||||||
|
|
||||||
|
[default]
|
||||||
|
extend-ignore-re = [
|
||||||
|
"Patrick Collison",
|
||||||
|
"Copyright 2014 Unknwon",
|
||||||
|
"[0-9A-Za-z/+=]{64}",
|
||||||
|
"ZXJuZXQxDjAMBgNVBA-some-junk-Q4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF",
|
||||||
|
"eyJmb28iOiJiYXIifQ",
|
||||||
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.*",
|
||||||
|
"MIIDBTCCAe2gAwIBAgIQWHw7h.*",
|
||||||
|
'http\.Header\{"X-Amz-Server-Side-Encryptio":',
|
||||||
|
"ZoEoZdLlzVbOlT9rbhD7ZN7TLyiYXSAlB79uGEge",
|
||||||
|
]
|
||||||
|
|
||||||
|
[default.extend-words]
|
||||||
|
"encrypter" = "encrypter"
|
||||||
|
"kms" = "kms"
|
||||||
|
"requestor" = "requestor"
|
||||||
|
|
||||||
|
[default.extend-identifiers]
|
||||||
|
"HashiCorp" = "HashiCorp"
|
||||||
|
|
||||||
|
[type.go.extend-identifiers]
|
||||||
|
"bui" = "bui"
|
||||||
|
"dm2nd" = "dm2nd"
|
||||||
|
"ot" = "ot"
|
||||||
|
"ParseND" = "ParseND"
|
||||||
|
"ParseNDStream" = "ParseNDStream"
|
||||||
|
"pn" = "pn"
|
||||||
|
"TestGetPartialObjectMisAligned" = "TestGetPartialObjectMisAligned"
|
||||||
|
"thr" = "thr"
|
||||||
|
"toi" = "toi"
|
||||||
@@ -21,6 +21,11 @@ RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go
|
|||||||
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||||
chmod +x /go/bin/mc
|
chmod +x /go/bin/mc
|
||||||
|
|
||||||
|
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||||
|
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||||
|
chmod +x /go/bin/curl; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
@@ -49,6 +54,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
COPY --from=build /go/bin/mc /usr/bin/mc
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
COPY --from=build /go/bin/cur* /usr/bin/
|
||||||
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go
|
|||||||
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||||
chmod +x /go/bin/mc
|
chmod +x /go/bin/mc
|
||||||
|
|
||||||
|
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||||
|
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||||
|
chmod +x /go/bin/curl; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
@@ -49,6 +54,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
COPY --from=build /go/bin/mc /usr/bin/mc
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
COPY --from=build /go/bin/cur* /usr/bin/
|
||||||
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archiv
|
|||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /go/bin/minio.minisig && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /go/bin/minio.minisig && \
|
||||||
chmod +x /go/bin/minio
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||||
|
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||||
|
chmod +x /go/bin/curl; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
@@ -41,6 +46,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
|
|
||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
COPY --from=build /go/bin/cur* /usr/bin/
|
||||||
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go
|
|||||||
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||||
chmod +x /go/bin/mc
|
chmod +x /go/bin/mc
|
||||||
|
|
||||||
|
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||||
|
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||||
|
chmod +x /go/bin/curl; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
@@ -49,6 +54,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
COPY --from=build /go/bin/mc /usr/bin/mc
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
COPY --from=build /go/bin/cur* /usr/bin/
|
||||||
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ GOARCH := $(shell go env GOARCH)
|
|||||||
GOOS := $(shell go env GOOS)
|
GOOS := $(shell go env GOOS)
|
||||||
|
|
||||||
VERSION ?= $(shell git describe --tags)
|
VERSION ?= $(shell git describe --tags)
|
||||||
TAG ?= "quay.io/minio/minio:$(VERSION)"
|
REPO ?= quay.io/minio
|
||||||
|
TAG ?= $(REPO)/minio:$(VERSION)
|
||||||
|
|
||||||
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
||||||
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
|
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
|
||||||
@@ -23,7 +24,7 @@ help: ## print this help
|
|||||||
getdeps: ## fetch necessary dependencies
|
getdeps: ## fetch necessary dependencies
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR)
|
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR)
|
||||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@6ac204f0b4d48d17ab4fa442134c7fba13127a4e
|
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.10-0.20240227114326-6d6f813fff1b
|
||||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||||
|
|
||||||
crosscompile: ## cross compile minio
|
crosscompile: ## cross compile minio
|
||||||
@@ -44,7 +45,7 @@ lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
|||||||
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
||||||
|
|
||||||
check: test
|
check: test
|
||||||
test: verifiers build build-debugging ## builds minio, runs linters, tests
|
test: verifiers build ## builds minio, runs linters, tests
|
||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue ./...
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue ./...
|
||||||
|
|
||||||
@@ -52,12 +53,21 @@ test-root-disable: install-race
|
|||||||
@echo "Running minio root lockdown tests"
|
@echo "Running minio root lockdown tests"
|
||||||
@env bash $(PWD)/buildscripts/disable-root.sh
|
@env bash $(PWD)/buildscripts/disable-root.sh
|
||||||
|
|
||||||
|
test-ilm: install-race
|
||||||
|
@echo "Running ILM tests"
|
||||||
|
@env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh
|
||||||
|
|
||||||
|
test-pbac: install-race
|
||||||
|
@echo "Running bucket policies tests"
|
||||||
|
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh
|
||||||
|
|
||||||
test-decom: install-race
|
test-decom: install-race
|
||||||
@echo "Running minio decom tests"
|
@echo "Running minio decom tests"
|
||||||
@env bash $(PWD)/docs/distributed/decom.sh
|
@env bash $(PWD)/docs/distributed/decom.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-encrypted-kes.sh
|
||||||
|
|
||||||
test-versioning: install-race
|
test-versioning: install-race
|
||||||
@echo "Running minio versioning tests"
|
@echo "Running minio versioning tests"
|
||||||
@@ -80,6 +90,10 @@ 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-iam-ldap-upgrade-import: build ## verify IAM (external LDAP IDP)
|
||||||
|
@echo "Running upgrade tests for IAM (LDAP backend)"
|
||||||
|
@env bash $(PWD)/buildscripts/minio-iam-ldap-upgrade-import-test.sh
|
||||||
|
|
||||||
test-sio-error:
|
test-sio-error:
|
||||||
@(env bash $(PWD)/docs/bucket/replication/sio-error.sh)
|
@(env bash $(PWD)/docs/bucket/replication/sio-error.sh)
|
||||||
|
|
||||||
@@ -106,37 +120,39 @@ test-site-replication-oidc: install-race ## verify automatic site replication
|
|||||||
test-site-replication-minio: install-race ## verify automatic site replication
|
test-site-replication-minio: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects with SSE-KMS enabled for bucket"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-sse-kms-object-replication.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh)
|
||||||
|
|
||||||
verify: ## verify minio various setups
|
verify: install-race ## verify minio various setups
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||||
|
|
||||||
verify-healing: ## verify healing and replacing disks with minio binary
|
verify-healing: install-race ## verify healing and replacing disks with minio binary
|
||||||
@echo "Verify healing build with race"
|
@echo "Verify healing build with race"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
||||||
|
@(env bash $(PWD)/buildscripts/verify-healing-empty-erasure-set.sh)
|
||||||
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
|
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
|
||||||
|
|
||||||
verify-healing-with-root-disks: ## verify healing root disks
|
verify-healing-with-root-disks: install-race ## verify healing root disks
|
||||||
@echo "Verify healing with root drives"
|
@echo "Verify healing with root drives"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh)
|
@(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh)
|
||||||
|
|
||||||
verify-healing-with-rewrite: ## verify healing to rewrite old xl.meta -> new xl.meta
|
verify-healing-with-rewrite: install-race ## verify healing to rewrite old xl.meta -> new xl.meta
|
||||||
@echo "Verify healing with rewrite"
|
@echo "Verify healing with rewrite"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/rewrite-old-new.sh)
|
@(env bash $(PWD)/buildscripts/rewrite-old-new.sh)
|
||||||
|
|
||||||
verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
|
verify-healing-inconsistent-versions: install-race ## verify resolving inconsistent versions
|
||||||
@echo "Verify resolving inconsistent versions build with race"
|
@echo "Verify resolving inconsistent versions build with race"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
||||||
|
|
||||||
build-debugging:
|
build-debugging:
|
||||||
@(env bash $(PWD)/docs/debugging/build.sh)
|
@(env bash $(PWD)/docs/debugging/build.sh)
|
||||||
|
|
||||||
build: checks ## builds minio to $(PWD)
|
build: checks build-debugging ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|
||||||
@@ -144,12 +160,11 @@ hotfix-vars:
|
|||||||
$(eval LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix.$(shell git rev-parse --short HEAD)" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
|
$(eval LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix.$(shell git rev-parse --short HEAD)" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
|
||||||
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#')))
|
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#')))
|
||||||
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
|
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
|
||||||
$(eval TAG := "minio/minio:$(VERSION)")
|
|
||||||
|
|
||||||
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
|
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
|
||||||
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.0/pkger_2.2.0_linux_amd64.deb
|
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.9/pkger_2.2.9_linux_amd64.deb
|
||||||
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.0/linux-systemd/distributed/minio.service
|
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service
|
||||||
@sudo apt install ./pkger_2.2.0_linux_amd64.deb --yes
|
@sudo apt install ./pkger_2.2.9_linux_amd64.deb --yes
|
||||||
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
|
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
|
||||||
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
|
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
|
||||||
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
|
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
|
||||||
@@ -176,15 +191,15 @@ docker: build ## builds minio docker container
|
|||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
||||||
|
|
||||||
install-race: checks ## builds minio to $(PWD)
|
install-race: checks build-debugging ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary with -race to './minio'"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
@echo "Installing minio binary with -race to '$(GOPATH)/bin/minio'"
|
||||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
|
|
||||||
install: build ## builds minio and installs it to $GOPATH/bin.
|
install: build ## builds minio and installs it to $GOPATH/bin.
|
||||||
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
@echo "Installation successful. To learn more, try \"minio --help\"."
|
@echo "Installation successful. To learn more, try \"minio --help\"."
|
||||||
|
|
||||||
clean: ## cleanup all generated assets
|
clean: ## cleanup all generated assets
|
||||||
|
|||||||
@@ -123,7 +123,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.19](https://golang.org/dl/#stable)
|
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.21](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install github.com/minio/minio@latest
|
go install github.com/minio/minio@latest
|
||||||
@@ -210,10 +210,6 @@ For deployments behind a load balancer, proxy, or ingress rule where the MinIO h
|
|||||||
|
|
||||||
For example, consider a MinIO deployment behind a proxy `https://minio.example.net`, `https://console.minio.example.net` with rules for forwarding traffic on port :9000 and :9001 to MinIO and the MinIO Console respectively on the internal network. Set `MINIO_BROWSER_REDIRECT_URL` to `https://console.minio.example.net` to ensure the browser receives a valid reachable URL.
|
For example, consider a MinIO deployment behind a proxy `https://minio.example.net`, `https://console.minio.example.net` with rules for forwarding traffic on port :9000 and :9001 to MinIO and the MinIO Console respectively on the internal network. Set `MINIO_BROWSER_REDIRECT_URL` to `https://console.minio.example.net` to ensure the browser receives a valid reachable URL.
|
||||||
|
|
||||||
Similarly, if your TLS certificates do not have the IP SAN for the MinIO server host, the MinIO Console may fail to validate the connection to the server. Use the `MINIO_SERVER_URL` environment variable and specify the proxy-accessible hostname of the MinIO server to allow the Console to use the MinIO server API using the TLS certificate.
|
|
||||||
|
|
||||||
For example: `export MINIO_SERVER_URL="https://minio.example.net"`
|
|
||||||
|
|
||||||
| Dashboard | Creating a bucket |
|
| Dashboard | Creating a bucket |
|
||||||
| ------------- | ------------- |
|
| ------------- | ------------- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ fi
|
|||||||
set +e
|
set +e
|
||||||
|
|
||||||
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
|
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
|
||||||
|
./mc ready minioadm
|
||||||
|
|
||||||
./mc ls minioadm/
|
./mc ls minioadm/
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ done
|
|||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
sleep 10
|
./mc ready minioadm/
|
||||||
|
|
||||||
./mc ls minioadm/
|
./mc ls minioadm/
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
@@ -81,11 +82,12 @@ minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data
|
|||||||
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
sleep 20s
|
|
||||||
|
|
||||||
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
|
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
|
||||||
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
|
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc ready sitea
|
||||||
|
./mc ready siteb
|
||||||
|
|
||||||
./mc admin replicate add sitea siteb
|
./mc admin replicate add sitea siteb
|
||||||
|
|
||||||
./mc admin user add sitea foobar foo12345
|
./mc admin user add sitea foobar foo12345
|
||||||
@@ -109,11 +111,12 @@ minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data
|
|||||||
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
sleep 20s
|
|
||||||
|
|
||||||
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
|
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
|
||||||
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
|
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc ready sitea
|
||||||
|
./mc ready siteb
|
||||||
|
|
||||||
./mc admin user add sitea foobar-admin foo12345
|
./mc admin user add sitea foobar-admin foo12345
|
||||||
|
|
||||||
sleep 2s
|
sleep 2s
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ func main() {
|
|||||||
opts := madmin.HealOpts{
|
opts := madmin.HealOpts{
|
||||||
Recursive: true, // recursively heal all objects at 'prefix'
|
Recursive: true, // recursively heal all objects at 'prefix'
|
||||||
Remove: true, // remove content that has lost quorum and not recoverable
|
Remove: true, // remove content that has lost quorum and not recoverable
|
||||||
Recreate: true, // rewrite all old non-inlined xl.meta to new xl.meta
|
|
||||||
ScanMode: madmin.HealNormalScan, // by default do not do 'deep' scanning
|
ScanMode: madmin.HealNormalScan, // by default do not do 'deep' scanning
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script is used to test the migration of IAM content from old minio
|
||||||
|
# instance to new minio instance.
|
||||||
|
#
|
||||||
|
# To run it locally, start the LDAP server in github.com/minio/minio-iam-testing
|
||||||
|
# repo (e.g. make podman-run), and then run this script.
|
||||||
|
#
|
||||||
|
# This script assumes that LDAP server is at:
|
||||||
|
#
|
||||||
|
# `localhost:1389`
|
||||||
|
#
|
||||||
|
# if this is not the case, set the environment variable
|
||||||
|
# `_MINIO_LDAP_TEST_SERVER`.
|
||||||
|
|
||||||
|
OLD_VERSION=RELEASE.2024-03-26T22-10-45Z
|
||||||
|
OLD_BINARY_LINK=https://dl.min.io/server/minio/release/linux-amd64/archive/minio.${OLD_VERSION}
|
||||||
|
|
||||||
|
__init__() {
|
||||||
|
if which curl &>/dev/null; then
|
||||||
|
echo "curl is already installed"
|
||||||
|
else
|
||||||
|
echo "Installing curl:"
|
||||||
|
sudo apt install curl -y
|
||||||
|
fi
|
||||||
|
|
||||||
|
export GOPATH=/tmp/gopath
|
||||||
|
export PATH="${PATH}":"${GOPATH}"/bin
|
||||||
|
|
||||||
|
if which mc &>/dev/null; then
|
||||||
|
echo "mc is already installed"
|
||||||
|
else
|
||||||
|
echo "Installing mc:"
|
||||||
|
go install github.com/minio/mc@latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x ./minio.${OLD_VERSION} ]; then
|
||||||
|
echo "Downloading minio.${OLD_VERSION} binary"
|
||||||
|
curl -o minio.${OLD_VERSION} ${OLD_BINARY_LINK}
|
||||||
|
chmod +x minio.${OLD_VERSION}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$_MINIO_LDAP_TEST_SERVER" ]; then
|
||||||
|
export _MINIO_LDAP_TEST_SERVER=localhost:1389
|
||||||
|
echo "Using default LDAP endpoint: $_MINIO_LDAP_TEST_SERVER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf /tmp/data
|
||||||
|
}
|
||||||
|
|
||||||
|
create_iam_content_in_old_minio() {
|
||||||
|
echo "Creating IAM content in old minio instance."
|
||||||
|
|
||||||
|
MINIO_CI_CD=1 ./minio.${OLD_VERSION} server /tmp/data/{1...4} &
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
set -x
|
||||||
|
mc alias set old-minio http://localhost:9000 minioadmin minioadmin
|
||||||
|
mc ready old-minio
|
||||||
|
mc idp ldap add old-minio \
|
||||||
|
server_addr=localhost:1389 \
|
||||||
|
server_insecure=on \
|
||||||
|
lookup_bind_dn=cn=admin,dc=min,dc=io \
|
||||||
|
lookup_bind_password=admin \
|
||||||
|
user_dn_search_base_dn=dc=min,dc=io \
|
||||||
|
user_dn_search_filter="(uid=%s)" \
|
||||||
|
group_search_base_dn=ou=swengg,dc=min,dc=io \
|
||||||
|
group_search_filter="(&(objectclass=groupOfNames)(member=%d))"
|
||||||
|
mc admin service restart old-minio
|
||||||
|
|
||||||
|
mc idp ldap policy attach old-minio readwrite --user=UID=dillon,ou=people,ou=swengg,dc=min,dc=io
|
||||||
|
mc idp ldap policy attach old-minio readwrite --group=CN=project.c,ou=groups,ou=swengg,dc=min,dc=io
|
||||||
|
|
||||||
|
mc idp ldap policy entities old-minio
|
||||||
|
|
||||||
|
mc admin cluster iam export old-minio
|
||||||
|
set +x
|
||||||
|
|
||||||
|
mc admin service stop old-minio
|
||||||
|
}
|
||||||
|
|
||||||
|
import_iam_content_in_new_minio() {
|
||||||
|
echo "Importing IAM content in new minio instance."
|
||||||
|
# Assume current minio binary exists.
|
||||||
|
MINIO_CI_CD=1 ./minio server /tmp/data/{1...4} &
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
set -x
|
||||||
|
mc alias set new-minio http://localhost:9000 minioadmin minioadmin
|
||||||
|
echo "BEFORE IMPORT mappings:"
|
||||||
|
mc ready new-minio
|
||||||
|
mc idp ldap policy entities new-minio
|
||||||
|
mc admin cluster iam import new-minio ./old-minio-iam-info.zip
|
||||||
|
echo "AFTER IMPORT mappings:"
|
||||||
|
mc idp ldap policy entities new-minio
|
||||||
|
set +x
|
||||||
|
|
||||||
|
# mc admin service stop new-minio
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_iam_content_in_new_minio() {
|
||||||
|
output=$(mc idp ldap policy entities new-minio --json)
|
||||||
|
|
||||||
|
groups=$(echo "$output" | jq -r '.result.policyMappings[] | select(.policy == "readwrite") | .groups[]')
|
||||||
|
if [ "$groups" != "cn=project.c,ou=groups,ou=swengg,dc=min,dc=io" ]; then
|
||||||
|
echo "Failed to verify groups: $groups"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
users=$(echo "$output" | jq -r '.result.policyMappings[] | select(.policy == "readwrite") | .users[]')
|
||||||
|
if [ "$users" != "uid=dillon,ou=people,ou=swengg,dc=min,dc=io" ]; then
|
||||||
|
echo "Failed to verify users: $users"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mc admin service stop new-minio
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
create_iam_content_in_old_minio
|
||||||
|
|
||||||
|
import_iam_content_in_new_minio
|
||||||
|
|
||||||
|
verify_iam_content_in_new_minio
|
||||||
|
}
|
||||||
|
|
||||||
|
(__init__ "$@" && main "$@")
|
||||||
@@ -55,6 +55,11 @@ __init__() {
|
|||||||
|
|
||||||
go install github.com/minio/mc@latest
|
go install github.com/minio/mc@latest
|
||||||
|
|
||||||
|
## this is needed because github actions don't have
|
||||||
|
## docker-compose on all runners
|
||||||
|
go install github.com/docker/compose/v2/cmd@latest
|
||||||
|
mv -v /tmp/gopath/bin/cmd /tmp/gopath/bin/docker-compose
|
||||||
|
|
||||||
TAG=minio/minio:dev make docker
|
TAG=minio/minio:dev make docker
|
||||||
|
|
||||||
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ function verify_rewrite() {
|
|||||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
pid=$!
|
pid=$!
|
||||||
disown $pid
|
disown $pid
|
||||||
sleep 10
|
|
||||||
|
"${WORK_DIR}/mc" ready minio/
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
@@ -77,7 +78,8 @@ function verify_rewrite() {
|
|||||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
pid=$!
|
pid=$!
|
||||||
disown $pid
|
disown $pid
|
||||||
sleep 10
|
|
||||||
|
"${WORK_DIR}/mc" ready minio/
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
@@ -87,13 +89,12 @@ function verify_rewrite() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
if ! ./s3-check-md5 \
|
||||||
if ! s3-check-md5 \
|
|
||||||
-debug \
|
-debug \
|
||||||
-versions \
|
-versions \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
-secret-key minio123 \
|
-secret-key minio123 \
|
||||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
-endpoint "http://127.0.0.1:${start_port}/" 2>&1 | grep INTACT; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
cat "${WORK_DIR}/server1.log"
|
cat "${WORK_DIR}/server1.log"
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
@@ -113,7 +114,7 @@ function verify_rewrite() {
|
|||||||
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
if ! s3-check-md5 \
|
if ! ./s3-check-md5 \
|
||||||
-debug \
|
-debug \
|
||||||
-versions \
|
-versions \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ WORK_DIR="$PWD/.verify-$RANDOM"
|
|||||||
export MINT_MODE=core
|
export MINT_MODE=core
|
||||||
export MINT_DATA_DIR="$WORK_DIR/data"
|
export MINT_DATA_DIR="$WORK_DIR/data"
|
||||||
export SERVER_ENDPOINT="127.0.0.1:9000"
|
export SERVER_ENDPOINT="127.0.0.1:9000"
|
||||||
|
export MC_HOST_verify="http://minio:minio123@${SERVER_ENDPOINT}/"
|
||||||
|
export MC_HOST_verify_ipv6="http://minio:minio123@[::1]:9000/"
|
||||||
export ACCESS_KEY="minio"
|
export ACCESS_KEY="minio"
|
||||||
export SECRET_KEY="minio123"
|
export SECRET_KEY="minio123"
|
||||||
export ENABLE_HTTPS=0
|
export ENABLE_HTTPS=0
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
export GOGC=25
|
export GOGC=25
|
||||||
export ENABLE_ADMIN=1
|
export ENABLE_ADMIN=1
|
||||||
|
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
@@ -36,18 +37,21 @@ function start_minio_fs() {
|
|||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
||||||
sleep 10
|
|
||||||
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure() {
|
function start_minio_erasure() {
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
||||||
sleep 15
|
|
||||||
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure_sets() {
|
function start_minio_erasure_sets() {
|
||||||
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||||
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||||
sleep 15
|
|
||||||
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets() {
|
function start_minio_pool_erasure_sets() {
|
||||||
@@ -57,7 +61,7 @@ function start_minio_pool_erasure_sets() {
|
|||||||
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets_ipv6() {
|
function start_minio_pool_erasure_sets_ipv6() {
|
||||||
@@ -67,7 +71,7 @@ function start_minio_pool_erasure_sets_ipv6() {
|
|||||||
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify_ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_dist_erasure() {
|
function start_minio_dist_erasure() {
|
||||||
@@ -78,7 +82,7 @@ function start_minio_dist_erasure() {
|
|||||||
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||||
done
|
done
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_fs() {
|
function run_test_fs() {
|
||||||
@@ -222,7 +226,7 @@ function __init__() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
(cd "${MC_BUILD_DIR}" && go build -o "${WORK_DIR}/mc")
|
||||||
|
|
||||||
# remove mc source.
|
# remove mc source.
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
|
|||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
#
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
function start_minio_3_node() {
|
||||||
|
export MINIO_ROOT_USER=minio
|
||||||
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
start_port=$1
|
||||||
|
args=""
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
|
||||||
|
done
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||||
|
pid1=$!
|
||||||
|
disown ${pid1}
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
||||||
|
pid2=$!
|
||||||
|
disown $pid2
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
||||||
|
pid3=$!
|
||||||
|
disown $pid3
|
||||||
|
|
||||||
|
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
|
||||||
|
/tmp/mc ready myminio
|
||||||
|
|
||||||
|
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ps -p $pid2 1>&2 >/dev/null; then
|
||||||
|
echo "server2 log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server2.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ps -p $pid3 1>&2 >/dev/null; then
|
||||||
|
echo "server3 log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server3.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pkill minio; then
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
if pgrep minio; then
|
||||||
|
# forcibly killing, to proceed further properly.
|
||||||
|
if ! pkill -9 minio; then
|
||||||
|
echo "no minio process running anymore, proceed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_online() {
|
||||||
|
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
|
||||||
|
echo "1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function __init__() {
|
||||||
|
echo "Initializing environment"
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
mkdir -p "$MINIO_CONFIG_DIR"
|
||||||
|
|
||||||
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
|
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
||||||
|
|
||||||
|
if [ ! -f /tmp/mc ]; then
|
||||||
|
wget --quiet -O /tmp/mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x /tmp/mc
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function perform_test() {
|
||||||
|
start_minio_3_node $2
|
||||||
|
|
||||||
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
|
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
||||||
|
|
||||||
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
|
set -x
|
||||||
|
start_minio_3_node $2
|
||||||
|
|
||||||
|
rv=$(check_online)
|
||||||
|
if [ "$rv" == "1" ]; then
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
pkill -9 minio
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
# use same ports for all tests
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
perform_test "2" ${start_port}
|
||||||
|
perform_test "1" ${start_port}
|
||||||
|
perform_test "3" ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
(__init__ "$@" && main "$@")
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
@@ -12,17 +12,26 @@ fi
|
|||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
GOPATH=/tmp/gopath
|
||||||
|
|
||||||
function start_minio_3_node() {
|
function start_minio_3_node() {
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
rm "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
|
||||||
export MINIO_ROOT_USER=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
start_port=$2
|
first_time=$(find ${WORK_DIR}/ | grep format.json | wc -l)
|
||||||
|
|
||||||
|
start_port=$1
|
||||||
args=""
|
args=""
|
||||||
for i in $(seq 1 3); do
|
for d in $(seq 1 3 5); do
|
||||||
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
|
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
|
||||||
|
d=$((d + 1))
|
||||||
|
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
|
||||||
done
|
done
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
"${MINIO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||||
@@ -37,7 +46,11 @@ function start_minio_3_node() {
|
|||||||
pid3=$!
|
pid3=$!
|
||||||
disown $pid3
|
disown $pid3
|
||||||
|
|
||||||
sleep "$1"
|
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
|
||||||
|
/tmp/mc ready myminio
|
||||||
|
|
||||||
|
[ ${first_time} -eq 0 ] && upload_objects
|
||||||
|
[ ${first_time} -ne 0 ] && sleep 120
|
||||||
|
|
||||||
if ! ps -p $pid1 1>&2 >/dev/null; then
|
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
@@ -82,10 +95,23 @@ function start_minio_3_node() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_online() {
|
function check_heal() {
|
||||||
if grep -q 'Unable to initialize sub-systems' ${WORK_DIR}/dist-minio-*.log; then
|
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
|
||||||
echo "1"
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for ((i = 0; i < 20; i++)); do
|
||||||
|
test -f ${WORK_DIR}/$1/1/.minio.sys/format.json
|
||||||
|
v1=$?
|
||||||
|
nextInES=$(($1 + 1)) && [ $nextInES -gt 3 ] && nextInES=1
|
||||||
|
foundFiles1=$(find ${WORK_DIR}/$1/1/ | grep -v .minio.sys | grep xl.meta | wc -l)
|
||||||
|
foundFiles2=$(find ${WORK_DIR}/$nextInES/1/ | grep -v .minio.sys | grep xl.meta | wc -l)
|
||||||
|
test $foundFiles1 -eq $foundFiles2
|
||||||
|
v2=$?
|
||||||
|
[ $v1 == 0 -a $v2 == 0 ] && return 0
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge() {
|
function purge() {
|
||||||
@@ -99,19 +125,35 @@ function __init__() {
|
|||||||
|
|
||||||
## version is purposefully set to '3' for minio to migrate configuration file
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
||||||
|
|
||||||
|
if [ ! -f /tmp/mc ]; then
|
||||||
|
wget --quiet -O /tmp/mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x /tmp/mc
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload_objects() {
|
||||||
|
/tmp/mc mb myminio/testbucket/
|
||||||
|
for ((i = 0; i < 20; i++)); do
|
||||||
|
echo "my content" | /tmp/mc pipe myminio/testbucket/file-$i
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
function perform_test() {
|
function perform_test() {
|
||||||
start_minio_3_node 120 $2
|
start_port=$2
|
||||||
|
|
||||||
|
start_minio_3_node $start_port
|
||||||
|
|
||||||
echo "Testing Distributed Erasure setup healing of drives"
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
echo "Remove the contents of the disks belonging to '${1}' node"
|
||||||
|
|
||||||
rm -rf ${WORK_DIR}/${1}/*/
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
start_minio_3_node 120 $2
|
set -x
|
||||||
|
start_minio_3_node $start_port
|
||||||
|
|
||||||
rv=$(check_online)
|
check_heal ${1}
|
||||||
|
rv=$?
|
||||||
if [ "$rv" == "1" ]; then
|
if [ "$rv" == "1" ]; then
|
||||||
for i in $(seq 1 3); do
|
for i in $(seq 1 3); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy access control
|
// Data types used for returning dummy access control
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
|
|
||||||
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-go"
|
"github.com/minio/kms-go/kes"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"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"
|
||||||
@@ -39,9 +39,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/versioning"
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
"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/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -99,7 +98,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta))
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta))
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
@@ -431,7 +430,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
case bucketNotificationConfig:
|
case bucketNotificationConfig:
|
||||||
config, err := globalBucketMetadataSys.GetNotificationConfig(bucket)
|
config, err := globalBucketMetadataSys.GetNotificationConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -447,7 +446,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) {
|
if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -687,7 +686,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v := newBucketMetadata(bucket)
|
v, _ := globalBucketMetadataSys.Get(bucket)
|
||||||
bucketMap[bucket] = &v
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,7 +722,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v := newBucketMetadata(bucket)
|
v, _ := globalBucketMetadataSys.Get(bucket)
|
||||||
bucketMap[bucket] = &v
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,7 +735,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf("An Object Lock configuration is present on this bucket, so the versioning state cannot be suspended."))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("An Object Lock configuration is present on this bucket, so the versioning state cannot be suspended."))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err := getReplicationConfig(ctx, bucket); err == nil && v.Suspended() {
|
if rcfg, _ := getReplicationConfig(ctx, bucket); rcfg != nil && v.Suspended() {
|
||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf("A replication configuration is present on this bucket, so the versioning state cannot be suspended."))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("A replication configuration is present on this bucket, so the versioning state cannot be suspended."))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -776,7 +775,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
rpt.SetStatus(bucket, "", err)
|
rpt.SetStatus(bucket, "", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v := newBucketMetadata(bucket)
|
v, _ := globalBucketMetadataSys.Get(bucket)
|
||||||
bucketMap[bucket] = &v
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
@@ -784,7 +783,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
switch fileName {
|
switch fileName {
|
||||||
case bucketNotificationConfig:
|
case bucketNotificationConfig:
|
||||||
config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region, globalEventNotifier.targetList)
|
config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region(), globalEventNotifier.targetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
||||||
continue
|
continue
|
||||||
@@ -838,9 +837,13 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
rcfg, err := globalBucketObjectLockSys.Get(bucket)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Validate the received bucket policy document
|
// Validate the received bucket policy document
|
||||||
if err = bucketLifecycle.Validate(); err != nil {
|
if err = bucketLifecycle.Validate(rcfg); err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -875,8 +878,10 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
kmsKey := encConfig.KeyID()
|
kmsKey := encConfig.KeyID()
|
||||||
if kmsKey != "" {
|
if kmsKey != "" {
|
||||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
_, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{
|
||||||
_, err := GlobalKMS.GenerateKey(ctx, kmsKey, kmsContext)
|
Name: kmsKey,
|
||||||
|
AssociatedData: kms.Context{"MinIO admin API": "ServerInfoHandler"}, // Context for a test key operation
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, kes.ErrKeyNotFound) {
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
rpt.SetStatus(bucket, fileName, errKMSKeyNotFound)
|
rpt.SetStatus(bucket, fileName, errKMSKeyNotFound)
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/kes-go"
|
"github.com/minio/kms-go/kes"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validateAdminReq will validate request against and return whether it is allowed.
|
// validateAdminReq will validate request against and return whether it is allowed.
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/subnet"
|
"github.com/minio/minio/internal/config/subnet"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
||||||
@@ -58,7 +58,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,7 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -443,7 +443,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/ldap"
|
"github.com/minio/pkg/v3/ldap"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
||||||
@@ -60,7 +59,7 @@ func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.R
|
|||||||
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 {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,16 +20,15 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
xldap "github.com/minio/pkg/v3/ldap"
|
||||||
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
||||||
@@ -105,6 +104,12 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fail if ldap is not enabled
|
||||||
|
if !globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminLDAPNotEnabled), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
// More than maxConfigSize bytes were available
|
// More than maxConfigSize bytes were available
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
@@ -132,7 +137,7 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
|||||||
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 {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -192,7 +197,8 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
// fail if ldap is not enabled
|
// fail if ldap is not enabled
|
||||||
if !globalIAMSys.LDAPConfig.Enabled() {
|
if !globalIAMSys.LDAPConfig.Enabled() {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errors.New("LDAP not enabled")), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminLDAPNotEnabled), r.URL)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the user for the request sender (as it may be sent via a service
|
// Find the user for the request sender (as it may be sent via a service
|
||||||
@@ -217,9 +223,8 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
// If we are creating svc account for request sender, ensure
|
// If we are creating svc account for request sender, ensure that targetUser
|
||||||
// that targetUser is a real user (i.e. not derived
|
// is a real user (i.e. not derived credentials).
|
||||||
// credentials).
|
|
||||||
if isSvcAccForRequestor {
|
if isSvcAccForRequestor {
|
||||||
if requestorIsDerivedCredential {
|
if requestorIsDerivedCredential {
|
||||||
if requestorParentUser == "" {
|
if requestorParentUser == "" {
|
||||||
@@ -232,12 +237,12 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
targetGroups = requestorGroups
|
targetGroups = requestorGroups
|
||||||
|
|
||||||
// Deny if the target user is not LDAP
|
// Deny if the target user is not LDAP
|
||||||
isLDAP, err := globalIAMSys.LDAPConfig.DoesUsernameExist(targetUser)
|
foundResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isLDAP == "" {
|
if foundResult == nil {
|
||||||
err := errors.New("Specified user does not exist on LDAP server")
|
err := errors.New("Specified user does not exist on LDAP server")
|
||||||
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
||||||
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
||||||
@@ -253,20 +258,36 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
opts.claims[k] = v
|
opts.claims[k] = v
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
isDN := globalIAMSys.LDAPConfig.IsLDAPUserDN(targetUser)
|
// We still need to ensure that the target user is a valid LDAP user.
|
||||||
|
//
|
||||||
|
// The target user may be supplied as a (short) username or a DN.
|
||||||
|
// However, for now, we only support using the short username.
|
||||||
|
|
||||||
|
isDN := globalIAMSys.LDAPConfig.ParsesAsDN(targetUser)
|
||||||
opts.claims[ldapUserN] = targetUser // simple username
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
var lookupResult *xldap.DNSearchResult
|
||||||
|
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if not found, check if DN
|
// if not found, check if DN
|
||||||
if strings.Contains(err.Error(), "not found") && isDN {
|
if strings.Contains(err.Error(), "User DN not found for:") {
|
||||||
// warn user that DNs are not allowed
|
if isDN {
|
||||||
err = fmt.Errorf("Must use short username to add service account. %w", err)
|
// warn user that DNs are not allowed
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminLDAPExpectedLoginName, err), r.URL)
|
||||||
|
} else {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err), r.URL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
targetUser = lookupResult.NormDN
|
||||||
opts.claims[ldapUser] = targetUser // DN
|
opts.claims[ldapUser] = targetUser // DN
|
||||||
|
opts.claims[ldapActualUser] = lookupResult.ActualDN
|
||||||
|
|
||||||
|
// Add LDAP attributes that were looked up into the claims.
|
||||||
|
for attribKey, attribValue := range lookupResult.Attributes {
|
||||||
|
opts.claims[ldapAttribPrefix+attribKey] = attribValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||||
@@ -300,7 +321,7 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
// Call hook for cluster-replication if the service account is not for a
|
// Call hook for cluster-replication if the service account is not for a
|
||||||
// root user.
|
// root user.
|
||||||
if newCred.ParentUser != globalActiveCred.AccessKey {
|
if newCred.ParentUser != globalActiveCred.AccessKey {
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemSvcAcc,
|
Type: madmin.SRIAMItemSvcAcc,
|
||||||
SvcAccChange: &madmin.SRSvcAccChange{
|
SvcAccChange: &madmin.SRSvcAccChange{
|
||||||
Create: &madmin.SRSvcAccCreate{
|
Create: &madmin.SRSvcAccCreate{
|
||||||
@@ -373,14 +394,16 @@ func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAccount, err := globalIAMSys.LDAPConfig.DoesUsernameExist(userDN)
|
dnResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
} else if userDN == "" {
|
}
|
||||||
|
if dnResult == nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
targetAccount := dnResult.NormDN
|
||||||
|
|
||||||
listType := r.Form.Get("listType")
|
listType := r.Form.Get("listType")
|
||||||
if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
|
if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
|
||||||
|
|||||||
+32
-33
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -25,9 +26,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/env"
|
||||||
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -106,21 +107,12 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
poolIndices = append(poolIndices, idx)
|
poolIndices = append(poolIndices, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(poolIndices) > 0 && !globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
if len(poolIndices) == 0 || !proxyDecommissionRequest(ctx, globalEndpoints[poolIndices[0]].Endpoints[0], w, r) {
|
||||||
ep := globalEndpoints[poolIndices[0]].Endpoints[0]
|
if err := z.Decommission(r.Context(), poolIndices...); err != nil {
|
||||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
if proxyEp.Endpoint.Host == ep.Host {
|
return
|
||||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := z.Decommission(r.Context(), poolIndices...); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -162,20 +154,12 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal {
|
if !proxyDecommissionRequest(ctx, globalEndpoints[idx].Endpoints[0], w, r) {
|
||||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
if err := pools.DecommissionCancel(ctx, idx); err != nil {
|
||||||
if proxyEp.Endpoint.Host == ep.Host {
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pools.DecommissionCancel(ctx, idx); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -225,7 +209,7 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(&status))
|
adminLogIf(r.Context(), json.NewEncoder(w).Encode(&status))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -258,7 +242,7 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
|||||||
poolsStatus[idx] = status
|
poolsStatus[idx] = status
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus))
|
adminLogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -365,11 +349,11 @@ func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceNotStarted), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceNotStarted), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, fmt.Errorf("failed to fetch rebalance status: %w", err))
|
adminLogIf(ctx, fmt.Errorf("failed to fetch rebalance status: %w", err))
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(rs))
|
adminLogIf(r.Context(), json.NewEncoder(w).Encode(rs))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -389,5 +373,20 @@ func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request)
|
|||||||
// Cancel any ongoing rebalance operation
|
// Cancel any ongoing rebalance operation
|
||||||
globalNotificationSys.StopRebalance(r.Context())
|
globalNotificationSys.StopRebalance(r.Context())
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
logger.LogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
|
adminLogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxyDecommissionRequest(ctx context.Context, defaultEndPoint Endpoint, w http.ResponseWriter, r *http.Request) (proxy bool) {
|
||||||
|
host := env.Get("_MINIO_DECOM_ENDPOINT_HOST", defaultEndPoint.Host)
|
||||||
|
if host == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
|
if proxyEp.Endpoint.Host == host && !proxyEp.IsLocal {
|
||||||
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,8 @@ import (
|
|||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
||||||
@@ -55,7 +54,7 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
|
|||||||
opts := getSRAddOptions(r)
|
opts := getSRAddOptions(r)
|
||||||
status, err := globalSiteReplicationSys.AddPeerClusters(ctx, sites, opts)
|
status, err := globalSiteReplicationSys.AddPeerClusters(ctx, sites, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -93,7 +92,7 @@ func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := globalSiteReplicationSys.PeerJoinReq(ctx, joinArg); err != nil {
|
if err := globalSiteReplicationSys.PeerJoinReq(ctx, joinArg); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -140,7 +139,7 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
|||||||
globalSiteReplicationSys.purgeDeletedBucket(ctx, objectAPI, bucket)
|
globalSiteReplicationSys.purgeDeletedBucket(ctx, objectAPI, bucket)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -192,7 +191,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo, item.UpdatedAt)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -263,7 +262,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
err = globalSiteReplicationSys.PeerBucketLCConfigHandler(ctx, item.Bucket, item.ExpiryLCConfig, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerBucketLCConfigHandler(ctx, item.Bucket, item.ExpiryLCConfig, item.UpdatedAt)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -316,7 +315,6 @@ func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptio
|
|||||||
if encryptionKey != "" {
|
if encryptionKey != "" {
|
||||||
data, err = madmin.DecryptData(encryptionKey, bytes.NewReader(data))
|
data, err = madmin.DecryptData(encryptionKey, bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
return SRError{
|
return SRError{
|
||||||
Cause: err,
|
Cause: err,
|
||||||
Code: ErrSiteReplicationInvalidRequest,
|
Code: ErrSiteReplicationInvalidRequest,
|
||||||
@@ -349,6 +347,18 @@ func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.R
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Report the ILMExpiryStats only if at least one site has replication of ILM expiry enabled
|
||||||
|
var replicateILMExpiry bool
|
||||||
|
for _, site := range info.Sites {
|
||||||
|
if site.ReplicateILMExpiry {
|
||||||
|
replicateILMExpiry = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !replicateILMExpiry {
|
||||||
|
// explicitly send nil for ILMExpiryStats
|
||||||
|
info.ILMExpiryStats = nil
|
||||||
|
}
|
||||||
|
|
||||||
if err = json.NewEncoder(w).Encode(info); err != nil {
|
if err = json.NewEncoder(w).Encode(info); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -396,7 +406,7 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
|
|||||||
opts := getSREditOptions(r)
|
opts := getSREditOptions(r)
|
||||||
status, err := globalSiteReplicationSys.EditPeerCluster(ctx, site, opts)
|
status, err := globalSiteReplicationSys.EditPeerCluster(ctx, site, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -433,7 +443,7 @@ func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := globalSiteReplicationSys.PeerEditReq(ctx, pi); err != nil {
|
if err := globalSiteReplicationSys.PeerEditReq(ctx, pi); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -456,7 +466,7 @@ func (a adminAPIHandlers) SRStateEdit(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := globalSiteReplicationSys.PeerStateEditReq(ctx, state); err != nil {
|
if err := globalSiteReplicationSys.PeerStateEditReq(ctx, state); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -493,7 +503,7 @@ func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.R
|
|||||||
}
|
}
|
||||||
status, err := globalSiteReplicationSys.RemovePeerCluster(ctx, objectAPI, rreq)
|
status, err := globalSiteReplicationSys.RemovePeerCluster(ctx, objectAPI, rreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -524,7 +534,7 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := globalSiteReplicationSys.InternalRemoveReq(ctx, objectAPI, req); err != nil {
|
if err := globalSiteReplicationSys.InternalRemoveReq(ctx, objectAPI, req); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -586,7 +596,7 @@ func (a adminAPIHandlers) SiteReplicationDevNull(w http.ResponseWriter, r *http.
|
|||||||
// If there is a disconnection before globalNetPerfMinDuration (we give a margin of error of 1 sec)
|
// If there is a disconnection before globalNetPerfMinDuration (we give a margin of error of 1 sec)
|
||||||
// would mean the network is not stable. Logging here will help in debugging network issues.
|
// would mean the network is not stable. Logging here will help in debugging network issues.
|
||||||
if time.Since(connectTime) < (globalNetPerfMinDuration - time.Second) {
|
if time.Since(connectTime) < (globalNetPerfMinDuration - time.Second) {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -609,5 +619,5 @@ func (a adminAPIHandlers) SiteReplicationNetPerf(w http.ResponseWriter, r *http.
|
|||||||
duration = globalNetPerfMinDuration
|
duration = globalNetPerfMinDuration
|
||||||
}
|
}
|
||||||
result := siteNetperf(r.Context(), duration)
|
result := siteNetperf(r.Context(), duration)
|
||||||
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(result))
|
adminLogIf(r.Context(), gob.NewEncoder(w).Encode(result))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
||||||
|
|||||||
+232
-159
@@ -27,15 +27,19 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/klauspost/compress/zip"
|
"github.com/klauspost/compress/zip"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
|
"github.com/minio/minio/internal/cachevalue"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
xldap "github.com/minio/pkg/v3/ldap"
|
||||||
|
"github.com/minio/pkg/v3/policy"
|
||||||
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
||||||
@@ -72,7 +76,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemIAMUser,
|
Type: madmin.SRIAMItemIAMUser,
|
||||||
IAMUser: &madmin.SRIAMUser{
|
IAMUser: &madmin.SRIAMUser{
|
||||||
AccessKey: accessKey,
|
AccessKey: accessKey,
|
||||||
@@ -269,14 +273,21 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updatedAt, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members)
|
|
||||||
|
if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
// We don't allow internal group manipulation in this API when LDAP
|
||||||
|
// is enabled for now.
|
||||||
|
err = errIAMActionNotAllowed
|
||||||
|
} else {
|
||||||
|
updatedAt, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemGroupInfo,
|
Type: madmin.SRIAMItemGroupInfo,
|
||||||
GroupInfo: &madmin.SRGroupInfo{
|
GroupInfo: &madmin.SRGroupInfo{
|
||||||
UpdateReq: updReq,
|
UpdateReq: updReq,
|
||||||
@@ -366,7 +377,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemGroupInfo,
|
Type: madmin.SRIAMItemGroupInfo,
|
||||||
GroupInfo: &madmin.SRGroupInfo{
|
GroupInfo: &madmin.SRGroupInfo{
|
||||||
UpdateReq: madmin.GroupAddRemove{
|
UpdateReq: madmin.GroupAddRemove{
|
||||||
@@ -404,7 +415,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemIAMUser,
|
Type: madmin.SRIAMItemIAMUser,
|
||||||
IAMUser: &madmin.SRIAMUser{
|
IAMUser: &madmin.SRIAMUser{
|
||||||
AccessKey: accessKey,
|
AccessKey: accessKey,
|
||||||
@@ -464,6 +475,11 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utf8.ValidString(accessKey) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserValidUTF), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
checkDenyOnly := false
|
checkDenyOnly := false
|
||||||
if accessKey == cred.AccessKey {
|
if accessKey == cred.AccessKey {
|
||||||
// Check that there is no explicit deny - otherwise it's allowed
|
// Check that there is no explicit deny - otherwise it's allowed
|
||||||
@@ -493,25 +509,31 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ureq madmin.AddOrUpdateUserReq
|
var ureq madmin.AddOrUpdateUserReq
|
||||||
if err = json.Unmarshal(configBytes, &ureq); err != nil {
|
if err = json.Unmarshal(configBytes, &ureq); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't allow internal user creation with LDAP enabled for now.
|
||||||
|
if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
updatedAt, err := globalIAMSys.CreateUser(ctx, accessKey, ureq)
|
updatedAt, err := globalIAMSys.CreateUser(ctx, accessKey, ureq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemIAMUser,
|
Type: madmin.SRIAMItemIAMUser,
|
||||||
IAMUser: &madmin.SRIAMUser{
|
IAMUser: &madmin.SRIAMUser{
|
||||||
AccessKey: accessKey,
|
AccessKey: accessKey,
|
||||||
@@ -620,6 +642,11 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if createReq.AccessKey == globalActiveCred.AccessKey {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
targetGroups []string
|
targetGroups []string
|
||||||
err error
|
err error
|
||||||
@@ -680,12 +707,20 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||||
// query their groups:
|
// query their groups:
|
||||||
opts.claims[ldapUserN] = targetUser // simple username
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
var lookupResult *xldap.DNSearchResult
|
||||||
|
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
targetUser = lookupResult.NormDN
|
||||||
opts.claims[ldapUser] = targetUser // username DN
|
opts.claims[ldapUser] = targetUser // username DN
|
||||||
|
opts.claims[ldapActualUser] = lookupResult.ActualDN
|
||||||
|
|
||||||
|
// Add LDAP attributes that were looked up into the claims.
|
||||||
|
for attribKey, attribValue := range lookupResult.Attributes {
|
||||||
|
opts.claims[ldapAttribPrefix+attribKey] = attribValue
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: if not using LDAP, then internal IDP or open ID is
|
// NOTE: if not using LDAP, then internal IDP or open ID is
|
||||||
// being used - in the former, group info is enforced when
|
// being used - in the former, group info is enforced when
|
||||||
@@ -724,7 +759,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// Call hook for cluster-replication if the service account is not for a
|
// Call hook for cluster-replication if the service account is not for a
|
||||||
// root user.
|
// root user.
|
||||||
if newCred.ParentUser != globalActiveCred.AccessKey {
|
if newCred.ParentUser != globalActiveCred.AccessKey {
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemSvcAcc,
|
Type: madmin.SRIAMItemSvcAcc,
|
||||||
SvcAccChange: &madmin.SRSvcAccChange{
|
SvcAccChange: &madmin.SRSvcAccChange{
|
||||||
Create: &madmin.SRSvcAccCreate{
|
Create: &madmin.SRSvcAccCreate{
|
||||||
@@ -774,25 +809,6 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
|
||||||
AccountName: cred.AccessKey,
|
|
||||||
Groups: cred.Groups,
|
|
||||||
Action: policy.UpdateServiceAccountAdminAction,
|
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
|
||||||
IsOwner: owner,
|
|
||||||
Claims: cred.Claims,
|
|
||||||
}) {
|
|
||||||
requestUser := cred.AccessKey
|
|
||||||
if cred.ParentUser != "" {
|
|
||||||
requestUser = cred.ParentUser
|
|
||||||
}
|
|
||||||
|
|
||||||
if requestUser != svcAccount.ParentUser {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
password := cred.SecretKey
|
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 {
|
||||||
@@ -813,6 +829,35 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
condValues := getConditionValues(r, "", cred)
|
||||||
|
err = addExpirationToCondValues(updateReq.NewExpiration, condValues)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission checks:
|
||||||
|
//
|
||||||
|
// 1. Any type of account (i.e. access keys (previously/still called service
|
||||||
|
// accounts), STS accounts, internal IDP accounts, etc) with the
|
||||||
|
// policy.UpdateServiceAccountAdminAction permission can update any service
|
||||||
|
// account.
|
||||||
|
//
|
||||||
|
// 2. We would like to let a user update their own access keys, however it
|
||||||
|
// is currently blocked pending a re-design. Users are still able to delete
|
||||||
|
// and re-create them.
|
||||||
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.UpdateServiceAccountAdminAction,
|
||||||
|
ConditionValues: condValues,
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var sp *policy.Policy
|
var sp *policy.Policy
|
||||||
if len(updateReq.NewPolicy) > 0 {
|
if len(updateReq.NewPolicy) > 0 {
|
||||||
sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
||||||
@@ -840,7 +885,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
// Call site replication hook - non-root user accounts are replicated.
|
// Call site replication hook - non-root user accounts are replicated.
|
||||||
if svcAccount.ParentUser != globalActiveCred.AccessKey {
|
if svcAccount.ParentUser != globalActiveCred.AccessKey {
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemSvcAcc,
|
Type: madmin.SRIAMItemSvcAcc,
|
||||||
SvcAccChange: &madmin.SRSvcAccChange{
|
SvcAccChange: &madmin.SRSvcAccChange{
|
||||||
Update: &madmin.SRSvcAccUpdate{
|
Update: &madmin.SRSvcAccUpdate{
|
||||||
@@ -1013,8 +1058,13 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
|||||||
for _, svc := range serviceAccounts {
|
for _, svc := range serviceAccounts {
|
||||||
expiryTime := svc.Expiration
|
expiryTime := svc.Expiration
|
||||||
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
||||||
AccessKey: svc.AccessKey,
|
Description: svc.Description,
|
||||||
Expiration: &expiryTime,
|
ParentUser: svc.ParentUser,
|
||||||
|
Name: svc.Name,
|
||||||
|
AccountStatus: svc.Status,
|
||||||
|
AccessKey: svc.AccessKey,
|
||||||
|
ImpliedPolicy: svc.IsImpliedPolicy(),
|
||||||
|
Expiration: &expiryTime,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1060,6 +1110,10 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if serviceAccount == siteReplicatorSvcAcc && globalSiteReplicationSys.isEnabled() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
// We do not care if service account is readable or not at this point,
|
// We do not care if service account is readable or not at this point,
|
||||||
// since this is a delete call we shall allow it to be deleted if possible.
|
// since this is a delete call we shall allow it to be deleted if possible.
|
||||||
svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
||||||
@@ -1098,7 +1152,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
// Call site replication hook - non-root user accounts are replicated.
|
// Call site replication hook - non-root user accounts are replicated.
|
||||||
if svcAccount.ParentUser != "" && svcAccount.ParentUser != globalActiveCred.AccessKey {
|
if svcAccount.ParentUser != "" && svcAccount.ParentUser != globalActiveCred.AccessKey {
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemSvcAcc,
|
Type: madmin.SRIAMItemSvcAcc,
|
||||||
SvcAccChange: &madmin.SRSvcAccChange{
|
SvcAccChange: &madmin.SRSvcAccChange{
|
||||||
Delete: &madmin.SRSvcAccDelete{
|
Delete: &madmin.SRSvcAccDelete{
|
||||||
@@ -1181,26 +1235,17 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return rd, wr
|
return rd, wr
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketStorageCache.Once.Do(func() {
|
bucketStorageCache.InitOnce(10*time.Second,
|
||||||
// Set this to 10 secs since its enough, as scanner
|
cachevalue.Opts{ReturnLastGood: true},
|
||||||
// does not update the bucket usage values frequently.
|
func(ctx context.Context) (DataUsageInfo, error) {
|
||||||
bucketStorageCache.TTL = 10 * time.Second
|
ctx, done := context.WithTimeout(ctx, 2*time.Second)
|
||||||
|
|
||||||
// Rely on older value if usage loading fails from disk.
|
|
||||||
bucketStorageCache.Relax = true
|
|
||||||
bucketStorageCache.Update = func() (interface{}, error) {
|
|
||||||
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
return loadDataUsageFromBackend(ctx, objectAPI)
|
return loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
}
|
},
|
||||||
})
|
)
|
||||||
|
|
||||||
var dataUsageInfo DataUsageInfo
|
dataUsageInfo, _ := bucketStorageCache.Get()
|
||||||
v, _ := bucketStorageCache.Get()
|
|
||||||
if v != nil {
|
|
||||||
dataUsageInfo, _ = v.(DataUsageInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If etcd, dns federation configured list buckets from etcd.
|
// If etcd, dns federation configured list buckets from etcd.
|
||||||
var err error
|
var err error
|
||||||
@@ -1265,7 +1310,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
default:
|
default:
|
||||||
policies, err := globalIAMSys.PolicyDBGet(accountName, cred.Groups...)
|
policies, err := globalIAMSys.PolicyDBGet(accountName, cred.Groups...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1417,7 +1462,7 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
for name, p := range policies {
|
for name, p := range policies {
|
||||||
_, err = json.Marshal(p)
|
_, err = json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newPolicies[name] = p
|
newPolicies[name] = p
|
||||||
@@ -1447,7 +1492,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
for name, p := range policies {
|
for name, p := range policies {
|
||||||
_, err = json.Marshal(p)
|
_, err = json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newPolicies[name] = p
|
newPolicies[name] = p
|
||||||
@@ -1477,7 +1522,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// Call cluster-replication policy creation hook to replicate policy deletion to
|
// Call cluster-replication policy creation hook to replicate policy deletion to
|
||||||
// other minio clusters.
|
// other minio clusters.
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemPolicy,
|
Type: madmin.SRIAMItemPolicy,
|
||||||
Name: policyName,
|
Name: policyName,
|
||||||
UpdatedAt: UTCNow(),
|
UpdatedAt: UTCNow(),
|
||||||
@@ -1540,7 +1585,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
// Call cluster-replication policy creation hook to replicate policy to
|
// Call cluster-replication policy creation hook to replicate policy to
|
||||||
// other minio clusters.
|
// other minio clusters.
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemPolicy,
|
Type: madmin.SRIAMItemPolicy,
|
||||||
Name: policyName,
|
Name: policyName,
|
||||||
Policy: iamPolicyBytes,
|
Policy: iamPolicyBytes,
|
||||||
@@ -1548,7 +1593,12 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPolicyForUserOrGroup - PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group]
|
// SetPolicyForUserOrGroup - sets a policy on a user or a group.
|
||||||
|
//
|
||||||
|
// PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group]
|
||||||
|
//
|
||||||
|
// Deprecated: This API is replaced by attach/detach policy APIs for specific
|
||||||
|
// type of users (builtin or LDAP).
|
||||||
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
@@ -1600,6 +1650,32 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
userType := regUser
|
userType := regUser
|
||||||
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
|
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
|
||||||
userType = stsUser
|
userType = stsUser
|
||||||
|
|
||||||
|
// Validate that the user or group exists in LDAP and use the normalized
|
||||||
|
// form of the entityName (which will be an LDAP DN).
|
||||||
|
var err error
|
||||||
|
if isGroup {
|
||||||
|
var foundGroupDN *xldap.DNSearchResult
|
||||||
|
var underBaseDN bool
|
||||||
|
if foundGroupDN, underBaseDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil {
|
||||||
|
iamLogIf(ctx, err)
|
||||||
|
} else if foundGroupDN == nil || !underBaseDN {
|
||||||
|
err = errNoSuchGroup
|
||||||
|
}
|
||||||
|
entityName = foundGroupDN.NormDN
|
||||||
|
} else {
|
||||||
|
var foundUserDN *xldap.DNSearchResult
|
||||||
|
if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil {
|
||||||
|
iamLogIf(ctx, err)
|
||||||
|
} else if foundUserDN == nil {
|
||||||
|
err = errNoSuchUser
|
||||||
|
}
|
||||||
|
entityName = foundUserDN.NormDN
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, entityName, policyName, userType, isGroup)
|
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, entityName, policyName, userType, isGroup)
|
||||||
@@ -1608,7 +1684,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
replLogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
Type: madmin.SRIAMItemPolicyMapping,
|
Type: madmin.SRIAMItemPolicyMapping,
|
||||||
PolicyMapping: &madmin.SRPolicyMapping{
|
PolicyMapping: &madmin.SRPolicyMapping{
|
||||||
UserOrGroup: entityName,
|
UserOrGroup: entityName,
|
||||||
@@ -1748,17 +1824,27 @@ func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
allPoliciesFile = "policies.json"
|
allPoliciesFile = "policies.json"
|
||||||
allUsersFile = "users.json"
|
allUsersFile = "users.json"
|
||||||
allGroupsFile = "groups.json"
|
allGroupsFile = "groups.json"
|
||||||
allSvcAcctsFile = "svcaccts.json"
|
allSvcAcctsFile = "svcaccts.json"
|
||||||
userPolicyMappingsFile = "user_mappings.json"
|
userPolicyMappingsFile = "user_mappings.json"
|
||||||
groupPolicyMappingsFile = "group_mappings.json"
|
groupPolicyMappingsFile = "group_mappings.json"
|
||||||
stsUserPolicyMappingsFile = "stsuser_mappings.json"
|
stsUserPolicyMappingsFile = "stsuser_mappings.json"
|
||||||
stsGroupPolicyMappingsFile = "stsgroup_mappings.json"
|
|
||||||
iamAssetsDir = "iam-assets"
|
iamAssetsDir = "iam-assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var iamExportFiles = []string{
|
||||||
|
allPoliciesFile,
|
||||||
|
allUsersFile,
|
||||||
|
allGroupsFile,
|
||||||
|
allSvcAcctsFile,
|
||||||
|
userPolicyMappingsFile,
|
||||||
|
groupPolicyMappingsFile,
|
||||||
|
stsUserPolicyMappingsFile,
|
||||||
|
}
|
||||||
|
|
||||||
// ExportIAMHandler - exports all iam info as a zipped file
|
// ExportIAMHandler - exports all iam info as a zipped file
|
||||||
func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
@@ -1782,38 +1868,28 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
sys: nil,
|
sys: nil,
|
||||||
})
|
})
|
||||||
if zerr != nil {
|
if zerr != nil {
|
||||||
logger.LogIf(ctx, zerr)
|
adminLogIf(ctx, zerr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
header.Method = zip.Deflate
|
header.Method = zip.Deflate
|
||||||
zwriter, zerr := zipWriter.CreateHeader(header)
|
zwriter, zerr := zipWriter.CreateHeader(header)
|
||||||
if zerr != nil {
|
if zerr != nil {
|
||||||
logger.LogIf(ctx, zerr)
|
adminLogIf(ctx, zerr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, err := io.Copy(zwriter, r); err != nil {
|
if _, err := io.Copy(zwriter, r); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iamFiles := []string{
|
for _, f := range iamExportFiles {
|
||||||
allPoliciesFile,
|
|
||||||
allUsersFile,
|
|
||||||
allGroupsFile,
|
|
||||||
allSvcAcctsFile,
|
|
||||||
userPolicyMappingsFile,
|
|
||||||
groupPolicyMappingsFile,
|
|
||||||
stsUserPolicyMappingsFile,
|
|
||||||
stsGroupPolicyMappingsFile,
|
|
||||||
}
|
|
||||||
for _, f := range iamFiles {
|
|
||||||
iamFile := pathJoin(iamAssetsDir, f)
|
iamFile := pathJoin(iamAssetsDir, f)
|
||||||
switch f {
|
switch f {
|
||||||
case allPoliciesFile:
|
case allPoliciesFile:
|
||||||
allPolicies, err := globalIAMSys.ListPolicies(ctx, "")
|
allPolicies, err := globalIAMSys.ListPolicies(ctx, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
adminLogIf(ctx, err)
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1892,7 +1968,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.Credentials.AccessKey)
|
sa, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.Credentials.AccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1914,6 +1990,9 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
Claims: claims,
|
Claims: claims,
|
||||||
SessionPolicy: json.RawMessage(policyJSON),
|
SessionPolicy: json.RawMessage(policyJSON),
|
||||||
Status: acc.Credentials.Status,
|
Status: acc.Credentials.Status,
|
||||||
|
Name: sa.Name,
|
||||||
|
Description: sa.Description,
|
||||||
|
Expiration: &sa.Expiration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1928,13 +2007,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case userPolicyMappingsFile:
|
case userPolicyMappingsFile:
|
||||||
userPolicyMap := make(map[string]MappedPolicy)
|
userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||||
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap)
|
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userPolData, err := json.Marshal(userPolicyMap)
|
userPolData, err := json.Marshal(mappedPoliciesToMap(userPolicyMap))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1945,13 +2024,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case groupPolicyMappingsFile:
|
case groupPolicyMappingsFile:
|
||||||
groupPolicyMap := make(map[string]MappedPolicy)
|
groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||||
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, true, groupPolicyMap)
|
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, true, groupPolicyMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
grpPolData, err := json.Marshal(groupPolicyMap)
|
grpPolData, err := json.Marshal(mappedPoliciesToMap(groupPolicyMap))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1962,13 +2041,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case stsUserPolicyMappingsFile:
|
case stsUserPolicyMappingsFile:
|
||||||
userPolicyMap := make(map[string]MappedPolicy)
|
userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||||
err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, userPolicyMap)
|
err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, userPolicyMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userPolData, err := json.Marshal(userPolicyMap)
|
userPolData, err := json.Marshal(mappedPoliciesToMap(userPolicyMap))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1977,22 +2056,6 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case stsGroupPolicyMappingsFile:
|
|
||||||
groupPolicyMap := make(map[string]MappedPolicy)
|
|
||||||
err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
grpPolData, err := json.Marshal(groupPolicyMap)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = rawDataFn(bytes.NewReader(grpPolData), iamFile, len(grpPolData)); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2162,12 +2225,12 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
// If group does not exist, then check if the group has beginning and end space characters
|
// If group does not exist, then check if the group has beginning and end space characters
|
||||||
// we will reject such group names.
|
// we will reject such group names.
|
||||||
if errors.Is(gerr, errNoSuchGroup) && hasSpaceBE(group) {
|
if errors.Is(gerr, errNoSuchGroup) && hasSpaceBE(group) {
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allGroupsFile, group), r.URL)
|
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, gerr, allGroupsFile, group), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, gerr := globalIAMSys.AddUsersToGroup(ctx, group, grpInfo.Members); gerr != nil {
|
if _, gerr := globalIAMSys.AddUsersToGroup(ctx, group, grpInfo.Members); gerr != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, allGroupsFile, group), r.URL)
|
writeErrorResponseJSON(ctx, w, importError(ctx, gerr, allGroupsFile, group), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2194,6 +2257,16 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, allSvcAcctsFile, ""), r.URL)
|
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, allSvcAcctsFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validations for LDAP enabled deployments.
|
||||||
|
if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
err := globalIAMSys.NormalizeLDAPAccessKeypairs(ctx, serviceAcctReqs)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, ""), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for user, svcAcctReq := range serviceAcctReqs {
|
for user, svcAcctReq := range serviceAcctReqs {
|
||||||
var sp *policy.Policy
|
var sp *policy.Policy
|
||||||
var err error
|
var err error
|
||||||
@@ -2204,7 +2277,8 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// service account access key cannot have space characters beginning and end of the string.
|
// service account access key cannot have space characters
|
||||||
|
// beginning and end of the string.
|
||||||
if hasSpaceBE(svcAcctReq.AccessKey) {
|
if hasSpaceBE(svcAcctReq.AccessKey) {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2230,20 +2304,14 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
updateReq = false
|
updateReq = false
|
||||||
}
|
}
|
||||||
if updateReq {
|
if updateReq {
|
||||||
opts := updateServiceAccountOpts{
|
// If the service account exists, we remove it to ensure a
|
||||||
secretKey: svcAcctReq.SecretKey,
|
// clean import.
|
||||||
status: svcAcctReq.Status,
|
err := globalIAMSys.DeleteServiceAccount(ctx, svcAcctReq.AccessKey, true)
|
||||||
name: svcAcctReq.Name,
|
|
||||||
description: svcAcctReq.Description,
|
|
||||||
expiration: svcAcctReq.Expiration,
|
|
||||||
sessionPolicy: sp,
|
|
||||||
}
|
|
||||||
_, err = globalIAMSys.UpdateServiceAccount(ctx, svcAcctReq.AccessKey, opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
delErr := fmt.Errorf("failed to delete existing service account(%s) before importing it: %w", svcAcctReq.AccessKey, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, importError(ctx, delErr, allSvcAcctsFile, user), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
opts := newServiceAccountOpts{
|
opts := newServiceAccountOpts{
|
||||||
accessKey: user,
|
accessKey: user,
|
||||||
@@ -2256,18 +2324,6 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
allowSiteReplicatorAccount: false,
|
allowSiteReplicatorAccount: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
|
||||||
// query their groups:
|
|
||||||
if globalIAMSys.LDAPConfig.Enabled() {
|
|
||||||
opts.claims[ldapUserN] = svcAcctReq.AccessKey // simple username
|
|
||||||
targetUser, _, err := globalIAMSys.LDAPConfig.LookupUserDN(svcAcctReq.AccessKey)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
opts.claims[ldapUser] = targetUser // username DN
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, err = globalIAMSys.NewServiceAccount(ctx, svcAcctReq.Parent, svcAcctReq.Groups, opts); err != nil {
|
if _, _, err = globalIAMSys.NewServiceAccount(ctx, svcAcctReq.Parent, svcAcctReq.Groups, opts); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2336,6 +2392,17 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, groupPolicyMappingsFile, ""), r.URL)
|
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, groupPolicyMappingsFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validations for LDAP enabled deployments.
|
||||||
|
if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
isGroup := true
|
||||||
|
err := globalIAMSys.NormalizeLDAPMappingImport(ctx, isGroup, grpPolicyMap)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, groupPolicyMappingsFile, ""), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for g, pm := range grpPolicyMap {
|
for g, pm := range grpPolicyMap {
|
||||||
if _, err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, unknownIAMUserType, true); err != nil {
|
if _, err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, unknownIAMUserType, true); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, groupPolicyMappingsFile, g), r.URL)
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, groupPolicyMappingsFile, g), r.URL)
|
||||||
@@ -2365,6 +2432,16 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, stsUserPolicyMappingsFile, ""), r.URL)
|
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, stsUserPolicyMappingsFile, ""), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validations for LDAP enabled deployments.
|
||||||
|
if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
isGroup := true
|
||||||
|
err := globalIAMSys.NormalizeLDAPMappingImport(ctx, !isGroup, userPolicyMap)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, ""), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
for u, pm := range userPolicyMap {
|
for u, pm := range userPolicyMap {
|
||||||
// disallow setting policy mapping if user is a temporary user
|
// disallow setting policy mapping if user is a temporary user
|
||||||
ok, _, err := globalIAMSys.IsTempUser(u)
|
ok, _, err := globalIAMSys.IsTempUser(u)
|
||||||
@@ -2376,6 +2453,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, stsUserPolicyMappingsFile, u), r.URL)
|
writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, stsUserPolicyMappingsFile, u), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, stsUser, false); err != nil {
|
if _, err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, stsUser, false); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, u), r.URL)
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, u), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2383,35 +2461,18 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// import sts group policy mappings
|
func addExpirationToCondValues(exp *time.Time, condValues map[string][]string) error {
|
||||||
{
|
if exp == nil || exp.IsZero() || exp.Equal(timeSentinel) {
|
||||||
f, err := zr.Open(pathJoin(iamAssetsDir, stsGroupPolicyMappingsFile))
|
return nil
|
||||||
switch {
|
|
||||||
case errors.Is(err, os.ErrNotExist):
|
|
||||||
case err != nil:
|
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsGroupPolicyMappingsFile, ""), r.URL)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
defer f.Close()
|
|
||||||
var grpPolicyMap map[string]MappedPolicy
|
|
||||||
data, err := io.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsGroupPolicyMappingsFile, ""), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal(data, &grpPolicyMap); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, stsGroupPolicyMappingsFile, ""), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for g, pm := range grpPolicyMap {
|
|
||||||
if _, err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, unknownIAMUserType, true); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, stsGroupPolicyMappingsFile, g), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
dur := exp.Sub(time.Now())
|
||||||
|
if dur <= 0 {
|
||||||
|
return errors.New("unsupported expiration time")
|
||||||
|
}
|
||||||
|
condValues["DurationSeconds"] = []string{strconv.FormatInt(int64(dur.Seconds()), 10)}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
|
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
|
||||||
@@ -2439,6 +2500,12 @@ func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials
|
|||||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if createReq.Expiration != nil && !createReq.Expiration.IsZero() {
|
||||||
|
// truncate expiration at the second.
|
||||||
|
truncateTime := createReq.Expiration.Truncate(time.Second)
|
||||||
|
createReq.Expiration = &truncateTime
|
||||||
|
}
|
||||||
|
|
||||||
// service account access key cannot have space characters beginning and end of the string.
|
// service account access key cannot have space characters beginning and end of the string.
|
||||||
if hasSpaceBE(createReq.AccessKey) {
|
if hasSpaceBE(createReq.AccessKey) {
|
||||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||||
@@ -2469,13 +2536,19 @@ func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials
|
|||||||
claims: make(map[string]interface{}),
|
claims: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
condValues := getConditionValues(r, "", cred)
|
||||||
|
err = addExpirationToCondValues(createReq.Expiration, condValues)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if action is allowed if creating access key for another user
|
// Check if action is allowed if creating access key for another user
|
||||||
// Check if action is explicitly denied if for self
|
// Check if action is explicitly denied if for self
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.CreateServiceAccountAdminAction,
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: condValues,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
DenyOnly: (targetUser == cred.AccessKey || targetUser == cred.ParentUser),
|
DenyOnly: (targetUser == cred.AccessKey || targetUser == cred.ParentUser),
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/signer"
|
"github.com/minio/minio-go/v7/pkg/signer"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -206,7 +206,9 @@ func TestIAMInternalIDPServerSuite(t *testing.T) {
|
|||||||
suite.TestCannedPolicies(c)
|
suite.TestCannedPolicies(c)
|
||||||
suite.TestGroupAddRemove(c)
|
suite.TestGroupAddRemove(c)
|
||||||
suite.TestServiceAccountOpsByAdmin(c)
|
suite.TestServiceAccountOpsByAdmin(c)
|
||||||
|
suite.TestServiceAccountPrivilegeEscalationBug(c)
|
||||||
suite.TestServiceAccountOpsByUser(c)
|
suite.TestServiceAccountOpsByUser(c)
|
||||||
|
suite.TestServiceAccountDurationSecondsCondition(c)
|
||||||
suite.TestAddServiceAccountPerms(c)
|
suite.TestAddServiceAccountPerms(c)
|
||||||
suite.TearDownSuite(c)
|
suite.TearDownSuite(c)
|
||||||
},
|
},
|
||||||
@@ -896,14 +898,6 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
|
|||||||
// 3. Check S3 access
|
// 3. Check S3 access
|
||||||
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||||
|
|
||||||
// 4. Check that svc account can restrict the policy, and that the
|
|
||||||
// session policy can be updated.
|
|
||||||
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, accessKey, bucket)
|
|
||||||
|
|
||||||
// 4. Check that service account's secret key and account status can be
|
|
||||||
// updated.
|
|
||||||
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, accessKey, bucket)
|
|
||||||
|
|
||||||
// 5. Check that service account can be deleted.
|
// 5. Check that service account can be deleted.
|
||||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
||||||
|
|
||||||
@@ -911,6 +905,93 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
|
|||||||
c.mustNotCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
c.mustNotCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TestSuiteIAM) TestServiceAccountDurationSecondsCondition(c *check) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
bucket := getRandomBucketName()
|
||||||
|
err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("bucket creat error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create policy, user and associate policy
|
||||||
|
policy := "mypolicy"
|
||||||
|
policyBytes := []byte(fmt.Sprintf(`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Action": [
|
||||||
|
"admin:CreateServiceAccount",
|
||||||
|
"admin:UpdateServiceAccount"
|
||||||
|
],
|
||||||
|
"Condition": {"NumericGreaterThan": {"svc:DurationSeconds": "3600"}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:ListBucket"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::%s/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, bucket))
|
||||||
|
err = s.adm.AddCannedPolicy(ctx, policy, policyBytes)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("policy add error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessKey, secretKey := mustGenerateCredentials(c)
|
||||||
|
err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to set user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to set policy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an madmin client with user creds
|
||||||
|
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||||
|
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||||
|
Secure: s.secure,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Err creating user admin client: %v", err)
|
||||||
|
}
|
||||||
|
userAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
|
||||||
|
|
||||||
|
distantExpiration := time.Now().Add(30 * time.Minute)
|
||||||
|
cr, err := userAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
|
TargetUser: accessKey,
|
||||||
|
AccessKey: "svc-accesskey",
|
||||||
|
SecretKey: "svc-secretkey",
|
||||||
|
Expiration: &distantExpiration,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to create svc acc: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||||
|
|
||||||
|
closeExpiration := time.Now().Add(2 * time.Hour)
|
||||||
|
_, err = userAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
|
TargetUser: accessKey,
|
||||||
|
AccessKey: "svc-accesskey",
|
||||||
|
SecretKey: "svc-secretkey",
|
||||||
|
Expiration: &closeExpiration,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("Creating a svc acc with distant expiration should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -979,6 +1060,95 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
|||||||
c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket)
|
c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TestSuiteIAM) TestServiceAccountPrivilegeEscalationBug(c *check) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := s.client.MakeBucket(ctx, "public", minio.MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("bucket creat error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.client.MakeBucket(ctx, "private", minio.MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("bucket creat error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubPolicyBytes := []byte(`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:*"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::public",
|
||||||
|
"arn:aws:s3:::public/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
fullS3PolicyBytes := []byte(`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:*"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Create a service account for the root user.
|
||||||
|
cr, err := s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
|
TargetUser: globalActiveCred.AccessKey,
|
||||||
|
Policy: pubPolicyBytes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("admin should be able to create service account for themselves %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "")
|
||||||
|
|
||||||
|
// Check that the service account can access the public bucket.
|
||||||
|
buckets, err := svcClient.ListBuckets(ctx)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("err fetching buckets %s", err)
|
||||||
|
}
|
||||||
|
if len(buckets) != 1 || buckets[0].Name != "public" {
|
||||||
|
c.Fatalf("service account should only have access to public bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an madmin client with the service account creds.
|
||||||
|
svcAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||||
|
Creds: credentials.NewStaticV4(cr.AccessKey, cr.SecretKey, ""),
|
||||||
|
Secure: s.secure,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Err creating svcacct admin client: %v", err)
|
||||||
|
}
|
||||||
|
svcAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
|
||||||
|
|
||||||
|
// Attempt to update the policy on the service account.
|
||||||
|
err = svcAdmClient.UpdateServiceAccount(ctx, cr.AccessKey,
|
||||||
|
madmin.UpdateServiceAccountReq{
|
||||||
|
NewPolicy: fullS3PolicyBytes,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("service account should not be able to update policy on itself")
|
||||||
|
} else if !strings.Contains(err.Error(), "Access Denied") {
|
||||||
|
c.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
|
func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
+287
-217
File diff suppressed because it is too large
Load Diff
@@ -348,7 +348,7 @@ func TestExtractHealInitParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
qParmsArr := []url.Values{
|
qParamsArr := []url.Values{
|
||||||
// Invalid cases
|
// Invalid cases
|
||||||
mkParams("", true, true),
|
mkParams("", true, true),
|
||||||
mkParams("111", true, true),
|
mkParams("111", true, true),
|
||||||
@@ -373,9 +373,9 @@ func TestExtractHealInitParams(t *testing.T) {
|
|||||||
body := `{"recursive": false, "dryRun": true, "remove": false, "scanMode": 0}`
|
body := `{"recursive": false, "dryRun": true, "remove": false, "scanMode": 0}`
|
||||||
|
|
||||||
// Test all combinations!
|
// Test all combinations!
|
||||||
for pIdx, parms := range qParmsArr {
|
for pIdx, params := range qParamsArr {
|
||||||
for vIdx, vars := range varsArr {
|
for vIdx, vars := range varsArr {
|
||||||
_, err := extractHealInitParams(vars, parms, bytes.NewReader([]byte(body)))
|
_, err := extractHealInitParams(vars, params, bytes.NewReader([]byte(body)))
|
||||||
isErrCase := false
|
isErrCase := false
|
||||||
if pIdx < 4 || vIdx < 1 {
|
if pIdx < 4 || vIdx < 1 {
|
||||||
isErrCase = true
|
isErrCase = true
|
||||||
|
|||||||
+56
-41
@@ -63,8 +63,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errHealIdleTimeout = fmt.Errorf("healing results were not consumed for too long")
|
errHealIdleTimeout = errors.New("healing results were not consumed for too long")
|
||||||
errHealStopSignalled = fmt.Errorf("heal stop signaled")
|
errHealStopSignalled = errors.New("heal stop signaled")
|
||||||
|
|
||||||
errFnHealFromAPIErr = func(ctx context.Context, err error) error {
|
errFnHealFromAPIErr = func(ctx context.Context, err error) error {
|
||||||
apiErr := toAdminAPIErr(ctx, err)
|
apiErr := toAdminAPIErr(ctx, err)
|
||||||
@@ -133,7 +133,13 @@ func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
|
|||||||
func (ahs *allHealState) updateHealStatus(tracker *healingTracker) {
|
func (ahs *allHealState) updateHealStatus(tracker *healingTracker) {
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
defer ahs.Unlock()
|
defer ahs.Unlock()
|
||||||
ahs.healStatus[tracker.ID] = *tracker
|
|
||||||
|
tracker.mu.RLock()
|
||||||
|
t := *tracker
|
||||||
|
t.QueuedBuckets = append(make([]string, 0, len(tracker.QueuedBuckets)), tracker.QueuedBuckets...)
|
||||||
|
t.HealedBuckets = append(make([]string, 0, len(tracker.HealedBuckets)), tracker.HealedBuckets...)
|
||||||
|
ahs.healStatus[tracker.ID] = t
|
||||||
|
tracker.mu.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by zone, set and disk index
|
// Sort by zone, set and disk index
|
||||||
@@ -323,21 +329,25 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence, objAPI ObjectLay
|
|||||||
// Add heal state and start sequence
|
// Add heal state and start sequence
|
||||||
ahs.healSeqMap[hpath] = h
|
ahs.healSeqMap[hpath] = h
|
||||||
|
|
||||||
// Launch top-level background heal go-routine
|
|
||||||
go h.healSequenceStart(objAPI)
|
|
||||||
|
|
||||||
clientToken := h.clientToken
|
clientToken := h.clientToken
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
clientToken = fmt.Sprintf("%s:%d", h.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
clientToken = fmt.Sprintf("%s:%d", h.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.clientToken == bgHealingUUID {
|
||||||
|
// For background heal do nothing, do not spawn an unnecessary goroutine.
|
||||||
|
} else {
|
||||||
|
// Launch top-level background heal go-routine
|
||||||
|
go h.healSequenceStart(objAPI)
|
||||||
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(madmin.HealStartSuccess{
|
b, err := json.Marshal(madmin.HealStartSuccess{
|
||||||
ClientToken: clientToken,
|
ClientToken: clientToken,
|
||||||
ClientAddress: h.clientAddress,
|
ClientAddress: h.clientAddress,
|
||||||
StartTime: h.startTime,
|
StartTime: h.startTime,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(h.ctx, err)
|
bugLogIf(h.ctx, err)
|
||||||
return nil, toAdminAPIErr(h.ctx, err), ""
|
return nil, toAdminAPIErr(h.ctx, err), ""
|
||||||
}
|
}
|
||||||
return b, noError, ""
|
return b, noError, ""
|
||||||
@@ -384,7 +394,7 @@ func (ahs *allHealState) PopHealStatusJSON(hpath string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
h.currentStatus.Items = nil
|
h.currentStatus.Items = nil
|
||||||
|
|
||||||
logger.LogIf(h.ctx, err)
|
bugLogIf(h.ctx, err)
|
||||||
return nil, ErrInternalError
|
return nil, ErrInternalError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,8 +455,8 @@ type healSequence struct {
|
|||||||
// Number of total items healed against item type
|
// Number of total items healed against item type
|
||||||
healedItemsMap map[madmin.HealItemType]int64
|
healedItemsMap map[madmin.HealItemType]int64
|
||||||
|
|
||||||
// Number of total items where healing failed against endpoint and drive state
|
// Number of total items where healing failed against item type
|
||||||
healFailedItemsMap map[string]int64
|
healFailedItemsMap map[madmin.HealItemType]int64
|
||||||
|
|
||||||
// The time of the last scan/heal activity
|
// The time of the last scan/heal activity
|
||||||
lastHealActivity time.Time
|
lastHealActivity time.Time
|
||||||
@@ -487,7 +497,7 @@ func newHealSequence(ctx context.Context, bucket, objPrefix, clientAddr string,
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
scannedItemsMap: make(map[madmin.HealItemType]int64),
|
scannedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
healedItemsMap: make(map[madmin.HealItemType]int64),
|
healedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
healFailedItemsMap: make(map[string]int64),
|
healFailedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,14 +541,14 @@ func (h *healSequence) getHealedItemsMap() map[madmin.HealItemType]int64 {
|
|||||||
return retMap
|
return retMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// gethealFailedItemsMap - returns map of all items where heal failed against
|
// getHealFailedItemsMap - returns map of all items where heal failed against
|
||||||
// drive endpoint and status
|
// drive endpoint and status
|
||||||
func (h *healSequence) gethealFailedItemsMap() map[string]int64 {
|
func (h *healSequence) getHealFailedItemsMap() map[madmin.HealItemType]int64 {
|
||||||
h.mutex.RLock()
|
h.mutex.RLock()
|
||||||
defer h.mutex.RUnlock()
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
// Make a copy before returning the value
|
// Make a copy before returning the value
|
||||||
retMap := make(map[string]int64, len(h.healFailedItemsMap))
|
retMap := make(map[madmin.HealItemType]int64, len(h.healFailedItemsMap))
|
||||||
for k, v := range h.healFailedItemsMap {
|
for k, v := range h.healFailedItemsMap {
|
||||||
retMap[k] = v
|
retMap[k] = v
|
||||||
}
|
}
|
||||||
@@ -546,6 +556,30 @@ func (h *healSequence) gethealFailedItemsMap() map[string]int64 {
|
|||||||
return retMap
|
return retMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) countFailed(healType madmin.HealItemType) {
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
h.healFailedItemsMap[healType]++
|
||||||
|
h.lastHealActivity = UTCNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) countScanned(healType madmin.HealItemType) {
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
h.scannedItemsMap[healType]++
|
||||||
|
h.lastHealActivity = UTCNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) countHealed(healType madmin.HealItemType) {
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
h.healedItemsMap[healType]++
|
||||||
|
h.lastHealActivity = UTCNow()
|
||||||
|
}
|
||||||
|
|
||||||
// isQuitting - determines if the heal sequence is quitting (due to an
|
// isQuitting - determines if the heal sequence is quitting (due to an
|
||||||
// external signal)
|
// external signal)
|
||||||
func (h *healSequence) isQuitting() bool {
|
func (h *healSequence) isQuitting() bool {
|
||||||
@@ -698,16 +732,13 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
task.opts.ScanMode = madmin.HealNormalScan
|
task.opts.ScanMode = madmin.HealNormalScan
|
||||||
}
|
}
|
||||||
|
|
||||||
h.mutex.Lock()
|
h.countScanned(healType)
|
||||||
h.scannedItemsMap[healType]++
|
|
||||||
h.lastHealActivity = UTCNow()
|
|
||||||
h.mutex.Unlock()
|
|
||||||
|
|
||||||
if source.noWait {
|
if source.noWait {
|
||||||
select {
|
select {
|
||||||
case globalBackgroundHealRoutine.tasks <- task:
|
case globalBackgroundHealRoutine.tasks <- task:
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
logger.Info("Task in the queue: %#v", task)
|
fmt.Printf("Task in the queue: %#v\n", task)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// task queue is full, no more workers, we shall move on and heal later.
|
// task queue is full, no more workers, we shall move on and heal later.
|
||||||
@@ -724,7 +755,7 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
select {
|
select {
|
||||||
case globalBackgroundHealRoutine.tasks <- task:
|
case globalBackgroundHealRoutine.tasks <- task:
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
logger.Info("Task in the queue: %#v", task)
|
fmt.Printf("Task in the queue: %#v\n", task)
|
||||||
}
|
}
|
||||||
case <-h.ctx.Done():
|
case <-h.ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
@@ -733,37 +764,21 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
// task queued, now wait for the response.
|
// task queued, now wait for the response.
|
||||||
select {
|
select {
|
||||||
case res := <-task.respCh:
|
case res := <-task.respCh:
|
||||||
|
if res.err == nil {
|
||||||
|
h.countHealed(healType)
|
||||||
|
} else {
|
||||||
|
h.countFailed(healType)
|
||||||
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
h.mutex.Lock()
|
|
||||||
defer h.mutex.Unlock()
|
|
||||||
|
|
||||||
// Progress is not reported in case of background heal processing.
|
|
||||||
// Instead we increment relevant counter based on the heal result
|
|
||||||
// for prometheus reporting.
|
|
||||||
if res.err != nil {
|
|
||||||
for _, d := range res.result.After.Drives {
|
|
||||||
// For failed items we report the endpoint and drive state
|
|
||||||
// This will help users take corrective actions for drives
|
|
||||||
h.healFailedItemsMap[d.Endpoint+","+d.State]++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Only object type reported for successful healing
|
|
||||||
h.healedItemsMap[res.result.Type]++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report caller of any failure
|
// Report caller of any failure
|
||||||
return res.err
|
return res.err
|
||||||
}
|
}
|
||||||
res.result.Type = healType
|
res.result.Type = healType
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
// Only report object error
|
|
||||||
if healType != madmin.HealItemObject {
|
|
||||||
return res.err
|
|
||||||
}
|
|
||||||
res.result.Detail = res.err.Error()
|
res.result.Detail = res.err.Error()
|
||||||
}
|
}
|
||||||
return h.pushHealResultItem(res.result)
|
return h.pushHealResultItem(res.result)
|
||||||
|
|||||||
+10
-19
@@ -19,9 +19,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/klauspost/compress/gzip"
|
"github.com/klauspost/compress/gzip"
|
||||||
@@ -63,20 +60,12 @@ const (
|
|||||||
noObjLayerFlag
|
noObjLayerFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
// Has checks if the the given flag is enabled in `h`.
|
// Has checks if the given flag is enabled in `h`.
|
||||||
func (h hFlag) Has(flag hFlag) bool {
|
func (h hFlag) Has(flag hFlag) bool {
|
||||||
// Use bitwise-AND and check if the result is non-zero.
|
// Use bitwise-AND and check if the result is non-zero.
|
||||||
return h&flag != 0
|
return h&flag != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHandlerName(f http.HandlerFunc) string {
|
|
||||||
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
|
||||||
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.adminAPIHandlers.")
|
|
||||||
name = strings.TrimSuffix(name, "Handler-fm")
|
|
||||||
name = strings.TrimSuffix(name, "-fm")
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// adminMiddleware performs some common admin handler functionality for all
|
// adminMiddleware performs some common admin handler functionality for all
|
||||||
// handlers:
|
// handlers:
|
||||||
//
|
//
|
||||||
@@ -86,9 +75,13 @@ func getHandlerName(f http.HandlerFunc) string {
|
|||||||
//
|
//
|
||||||
// - sets up call to send AuditLog
|
// - sets up call to send AuditLog
|
||||||
//
|
//
|
||||||
// Note that, while this is a middleware function (i.e. it takes a handler
|
// While this is a middleware function (i.e. it takes a handler function and
|
||||||
// function and returns one), due to flags being passed based on required
|
// returns one), due to flags being passed based on required conditions, it is
|
||||||
// conditions, it is done per-"handler function registration" in the router.
|
// done per-"handler function registration" in the router.
|
||||||
|
//
|
||||||
|
// The passed in handler function must be a method of `adminAPIHandlers` for the
|
||||||
|
// name displayed in logs and trace to be accurate. The name is extracted via
|
||||||
|
// reflection.
|
||||||
//
|
//
|
||||||
// When no flags are passed, gzip compression, http tracing of headers and
|
// When no flags are passed, gzip compression, http tracing of headers and
|
||||||
// checking of object layer availability are all enabled. Use flags to modify
|
// checking of object layer availability are all enabled. Use flags to modify
|
||||||
@@ -100,10 +93,8 @@ func adminMiddleware(f http.HandlerFunc, flags ...hFlag) http.HandlerFunc {
|
|||||||
handlerFlags |= flag
|
handlerFlags |= flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get name of the handler using reflection. NOTE: The passed in handler
|
// Get name of the handler using reflection.
|
||||||
// function must be a method of `adminAPIHandlers` for this extraction to
|
handlerName := getHandlerName(f, "adminAPIHandlers")
|
||||||
// work as expected.
|
|
||||||
handlerName := getHandlerName(f)
|
|
||||||
|
|
||||||
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Update request context with `logger.ReqInfo`.
|
// Update request context with `logger.ReqInfo`.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -18,23 +18,24 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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, metrics bool) madmin.ServerProperties {
|
||||||
addr := globalLocalNodeName
|
addr := globalLocalNodeName
|
||||||
if r != nil {
|
if r != nil {
|
||||||
addr = r.Host
|
addr = r.Host
|
||||||
@@ -42,9 +43,13 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
addr = globalLocalNodeName
|
addr = globalLocalNodeName
|
||||||
}
|
}
|
||||||
|
poolNumbers := make(map[int]struct{})
|
||||||
network := make(map[string]string)
|
network := make(map[string]string)
|
||||||
for _, ep := range endpointServerPools {
|
for _, ep := range endpointServerPools {
|
||||||
for _, endpoint := range ep.Endpoints {
|
for _, endpoint := range ep.Endpoints {
|
||||||
|
if endpoint.IsLocal {
|
||||||
|
poolNumbers[endpoint.PoolIdx+1] = struct{}{}
|
||||||
|
}
|
||||||
nodeName := endpoint.Host
|
nodeName := endpoint.Host
|
||||||
if nodeName == "" {
|
if nodeName == "" {
|
||||||
nodeName = addr
|
nodeName = addr
|
||||||
@@ -59,9 +64,11 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
if err := isServerResolvable(endpoint, 5*time.Second); err == nil {
|
if err := isServerResolvable(endpoint, 5*time.Second); err == nil {
|
||||||
network[nodeName] = string(madmin.ItemOnline)
|
network[nodeName] = string(madmin.ItemOnline)
|
||||||
} else {
|
} else {
|
||||||
network[nodeName] = string(madmin.ItemOffline)
|
if xnet.IsNetworkOrHostDown(err, false) {
|
||||||
// log once the error
|
network[nodeName] = string(madmin.ItemOffline)
|
||||||
logger.LogOnceIf(context.Background(), err, nodeName)
|
} else if xnet.IsNetworkOrHostDown(err, true) {
|
||||||
|
network[nodeName] = "connection attempt timedout"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,6 +119,17 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
MinioEnvVars: make(map[string]string, 10),
|
MinioEnvVars: make(map[string]string, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for poolNumber := range poolNumbers {
|
||||||
|
props.PoolNumbers = append(props.PoolNumbers, poolNumber)
|
||||||
|
}
|
||||||
|
sort.Ints(props.PoolNumbers)
|
||||||
|
props.PoolNumber = func() int {
|
||||||
|
if len(props.PoolNumbers) == 1 {
|
||||||
|
return props.PoolNumbers[0]
|
||||||
|
}
|
||||||
|
return math.MaxInt // this indicates that its unset.
|
||||||
|
}()
|
||||||
|
|
||||||
sensitive := map[string]struct{}{
|
sensitive := map[string]struct{}{
|
||||||
config.EnvAccessKey: {},
|
config.EnvAccessKey: {},
|
||||||
config.EnvSecretKey: {},
|
config.EnvSecretKey: {},
|
||||||
@@ -141,7 +159,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
|
|
||||||
objLayer := newObjectLayerFn()
|
objLayer := newObjectLayerFn()
|
||||||
if objLayer != nil {
|
if objLayer != nil {
|
||||||
storageInfo := objLayer.LocalStorageInfo(GlobalContext, true)
|
storageInfo := objLayer.LocalStorageInfo(GlobalContext, metrics)
|
||||||
props.State = string(madmin.ItemOnline)
|
props.State = string(madmin.ItemOnline)
|
||||||
props.Disks = storageInfo.Disks
|
props.Disks = storageInfo.Disks
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ type ObjectToDelete struct {
|
|||||||
ReplicateDecisionStr string `xml:"-"`
|
ReplicateDecisionStr string `xml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// createBucketConfiguration container for bucket configuration request from client.
|
// createBucketLocationConfiguration container for bucket configuration request from client.
|
||||||
// Used for parsing the location from the request body for Makebucket.
|
// Used for parsing the location from the request body for Makebucket.
|
||||||
type createBucketLocationConfiguration struct {
|
type createBucketLocationConfiguration struct {
|
||||||
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
|
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
|
||||||
|
|||||||
+117
-60
@@ -24,6 +24,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ import (
|
|||||||
levent "github.com/minio/minio/internal/config/lambda/event"
|
levent "github.com/minio/minio/internal/config/lambda/event"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError structure
|
// APIError structure
|
||||||
@@ -55,19 +56,23 @@ type APIError struct {
|
|||||||
Code string
|
Code string
|
||||||
Description string
|
Description string
|
||||||
HTTPStatusCode int
|
HTTPStatusCode int
|
||||||
|
ObjectSize string
|
||||||
|
RangeRequested string
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIErrorResponse - error response format
|
// APIErrorResponse - error response format
|
||||||
type APIErrorResponse struct {
|
type APIErrorResponse struct {
|
||||||
XMLName xml.Name `xml:"Error" json:"-"`
|
XMLName xml.Name `xml:"Error" json:"-"`
|
||||||
Code string
|
Code string
|
||||||
Message string
|
Message string
|
||||||
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
||||||
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
||||||
Resource string
|
Resource string
|
||||||
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
|
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
|
||||||
RequestID string `xml:"RequestId" json:"RequestId"`
|
RequestID string `xml:"RequestId" json:"RequestId"`
|
||||||
HostID string `xml:"HostId" json:"HostId"`
|
HostID string `xml:"HostId" json:"HostId"`
|
||||||
|
ActualObjectSize string `xml:"ActualObjectSize,omitempty" json:"ActualObjectSize,omitempty"`
|
||||||
|
RangeRequested string `xml:"RangeRequested,omitempty" json:"RangeRequested,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIErrorCode type of error status.
|
// APIErrorCode type of error status.
|
||||||
@@ -262,6 +267,7 @@ const (
|
|||||||
ErrInvalidResourceName
|
ErrInvalidResourceName
|
||||||
ErrInvalidLifecycleQueryParameter
|
ErrInvalidLifecycleQueryParameter
|
||||||
ErrServerNotInitialized
|
ErrServerNotInitialized
|
||||||
|
ErrBucketMetadataNotInitialized
|
||||||
ErrRequestTimedout
|
ErrRequestTimedout
|
||||||
ErrClientDisconnected
|
ErrClientDisconnected
|
||||||
ErrTooManyRequests
|
ErrTooManyRequests
|
||||||
@@ -277,9 +283,11 @@ const (
|
|||||||
ErrMalformedJSON
|
ErrMalformedJSON
|
||||||
ErrAdminNoSuchUser
|
ErrAdminNoSuchUser
|
||||||
ErrAdminNoSuchUserLDAPWarn
|
ErrAdminNoSuchUserLDAPWarn
|
||||||
|
ErrAdminLDAPExpectedLoginName
|
||||||
ErrAdminNoSuchGroup
|
ErrAdminNoSuchGroup
|
||||||
ErrAdminGroupNotEmpty
|
ErrAdminGroupNotEmpty
|
||||||
ErrAdminGroupDisabled
|
ErrAdminGroupDisabled
|
||||||
|
ErrAdminInvalidGroupName
|
||||||
ErrAdminNoSuchJob
|
ErrAdminNoSuchJob
|
||||||
ErrAdminNoSuchPolicy
|
ErrAdminNoSuchPolicy
|
||||||
ErrAdminPolicyChangeAlreadyApplied
|
ErrAdminPolicyChangeAlreadyApplied
|
||||||
@@ -297,9 +305,9 @@ const (
|
|||||||
ErrAdminConfigLDAPValidation
|
ErrAdminConfigLDAPValidation
|
||||||
ErrAdminConfigIDPCfgNameAlreadyExists
|
ErrAdminConfigIDPCfgNameAlreadyExists
|
||||||
ErrAdminConfigIDPCfgNameDoesNotExist
|
ErrAdminConfigIDPCfgNameDoesNotExist
|
||||||
ErrAdminCredentialsMismatch
|
|
||||||
ErrInsecureClientRequest
|
ErrInsecureClientRequest
|
||||||
ErrObjectTampered
|
ErrObjectTampered
|
||||||
|
ErrAdminLDAPNotEnabled
|
||||||
|
|
||||||
// Site-Replication errors
|
// Site-Replication errors
|
||||||
ErrSiteReplicationInvalidRequest
|
ErrSiteReplicationInvalidRequest
|
||||||
@@ -390,7 +398,7 @@ const (
|
|||||||
ErrParseExpectedIdentForGroupName
|
ErrParseExpectedIdentForGroupName
|
||||||
ErrParseExpectedIdentForAlias
|
ErrParseExpectedIdentForAlias
|
||||||
ErrParseUnsupportedCallWithStar
|
ErrParseUnsupportedCallWithStar
|
||||||
ErrParseNonUnaryAgregateFunctionCall
|
ErrParseNonUnaryAggregateFunctionCall
|
||||||
ErrParseMalformedJoin
|
ErrParseMalformedJoin
|
||||||
ErrParseExpectedIdentForAt
|
ErrParseExpectedIdentForAt
|
||||||
ErrParseAsteriskIsNotAloneInSelectList
|
ErrParseAsteriskIsNotAloneInSelectList
|
||||||
@@ -418,6 +426,7 @@ const (
|
|||||||
ErrAdminProfilerNotEnabled
|
ErrAdminProfilerNotEnabled
|
||||||
ErrInvalidDecompressedSize
|
ErrInvalidDecompressedSize
|
||||||
ErrAddUserInvalidArgument
|
ErrAddUserInvalidArgument
|
||||||
|
ErrAddUserValidUTF
|
||||||
ErrAdminResourceInvalidArgument
|
ErrAdminResourceInvalidArgument
|
||||||
ErrAdminAccountNotEligible
|
ErrAdminAccountNotEligible
|
||||||
ErrAccountNotEligible
|
ErrAccountNotEligible
|
||||||
@@ -433,6 +442,9 @@ const (
|
|||||||
// New Codes for GetObjectAttributes and GetObjectVersionAttributes
|
// New Codes for GetObjectAttributes and GetObjectVersionAttributes
|
||||||
ErrInvalidAttributeName
|
ErrInvalidAttributeName
|
||||||
|
|
||||||
|
ErrAdminNoAccessKey
|
||||||
|
ErrAdminNoSecretKey
|
||||||
|
|
||||||
apiErrCodeEnd // This is used only for the testing code
|
apiErrCodeEnd // This is used only for the testing code
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -446,9 +458,9 @@ func (e errorCodeMap) ToAPIErrWithErr(errCode APIErrorCode, err error) APIError
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||||
}
|
}
|
||||||
if globalSite.Region != "" {
|
if region := globalSite.Region(); region != "" {
|
||||||
if errCode == ErrAuthorizationHeaderMalformed {
|
if errCode == ErrAuthorizationHeaderMalformed {
|
||||||
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", region)
|
||||||
return apiErr
|
return apiErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1290,7 +1302,12 @@ var errorCodes = errorCodeMap{
|
|||||||
},
|
},
|
||||||
ErrServerNotInitialized: {
|
ErrServerNotInitialized: {
|
||||||
Code: "XMinioServerNotInitialized",
|
Code: "XMinioServerNotInitialized",
|
||||||
Description: "Server not initialized, please try again.",
|
Description: "Server not initialized yet, please try again.",
|
||||||
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
|
},
|
||||||
|
ErrBucketMetadataNotInitialized: {
|
||||||
|
Code: "XMinioBucketMetadataNotInitialized",
|
||||||
|
Description: "Bucket metadata not initialized yet, please try again.",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
ErrMalformedJSON: {
|
ErrMalformedJSON: {
|
||||||
@@ -1359,6 +1376,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The secret key is invalid.",
|
Description: "The secret key is invalid.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminNoAccessKey: {
|
||||||
|
Code: "XMinioAdminNoAccessKey",
|
||||||
|
Description: "No access key was provided.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminNoSecretKey: {
|
||||||
|
Code: "XMinioAdminNoSecretKey",
|
||||||
|
Description: "No secret key was provided.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminConfigNoQuorum: {
|
ErrAdminConfigNoQuorum: {
|
||||||
Code: "XMinioAdminConfigNoQuorum",
|
Code: "XMinioAdminConfigNoQuorum",
|
||||||
Description: "Configuration update failed because server quorum was not met",
|
Description: "Configuration update failed because server quorum was not met",
|
||||||
@@ -1425,11 +1452,6 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Unable to perform the requested operation because profiling is not enabled",
|
Description: "Unable to perform the requested operation because profiling is not enabled",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrAdminCredentialsMismatch: {
|
|
||||||
Code: "XMinioAdminCredentialsMismatch",
|
|
||||||
Description: "Credentials in config mismatch with server environment variables",
|
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
|
||||||
},
|
|
||||||
ErrAdminBucketQuotaExceeded: {
|
ErrAdminBucketQuotaExceeded: {
|
||||||
Code: "XMinioAdminBucketQuotaExceeded",
|
Code: "XMinioAdminBucketQuotaExceeded",
|
||||||
Description: "Bucket quota exceeded",
|
Description: "Bucket quota exceeded",
|
||||||
@@ -1458,7 +1480,7 @@ var errorCodes = errorCodeMap{
|
|||||||
ErrTooManyRequests: {
|
ErrTooManyRequests: {
|
||||||
Code: "TooManyRequests",
|
Code: "TooManyRequests",
|
||||||
Description: "Deadline exceeded while waiting in incoming queue, please reduce your request rate",
|
Description: "Deadline exceeded while waiting in incoming queue, please reduce your request rate",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusTooManyRequests,
|
||||||
},
|
},
|
||||||
ErrUnsupportedMetadata: {
|
ErrUnsupportedMetadata: {
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -1891,8 +1913,8 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Only COUNT with (*) as a parameter is supported in the SQL expression.",
|
Description: "Only COUNT with (*) as a parameter is supported in the SQL expression.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrParseNonUnaryAgregateFunctionCall: {
|
ErrParseNonUnaryAggregateFunctionCall: {
|
||||||
Code: "ParseNonUnaryAgregateFunctionCall",
|
Code: "ParseNonUnaryAggregateFunctionCall",
|
||||||
Description: "Only one argument is supported for aggregate functions in the SQL expression.",
|
Description: "Only one argument is supported for aggregate functions in the SQL expression.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
@@ -2013,7 +2035,7 @@ var errorCodes = errorCodeMap{
|
|||||||
},
|
},
|
||||||
ErrAddUserInvalidArgument: {
|
ErrAddUserInvalidArgument: {
|
||||||
Code: "XMinioInvalidIAMCredentials",
|
Code: "XMinioInvalidIAMCredentials",
|
||||||
Description: "User is not allowed to be same as admin access key",
|
Description: "Credential is not allowed to be same as admin access key",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
ErrAdminResourceInvalidArgument: {
|
ErrAdminResourceInvalidArgument: {
|
||||||
@@ -2071,7 +2093,26 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Invalid attribute name specified.",
|
Description: "Invalid attribute name specified.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
// Add your error structure here.
|
ErrAdminLDAPNotEnabled: {
|
||||||
|
Code: "XMinioLDAPNotEnabled",
|
||||||
|
Description: "LDAP is not enabled. LDAP must be enabled to make LDAP requests.",
|
||||||
|
HTTPStatusCode: http.StatusNotImplemented,
|
||||||
|
},
|
||||||
|
ErrAdminLDAPExpectedLoginName: {
|
||||||
|
Code: "XMinioLDAPExpectedLoginName",
|
||||||
|
Description: "Expected LDAP short username but was given full DN.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminInvalidGroupName: {
|
||||||
|
Code: "XMinioInvalidGroupName",
|
||||||
|
Description: "The group name is invalid.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAddUserValidUTF: {
|
||||||
|
Code: "XMinioInvalidUTF",
|
||||||
|
Description: "Invalid UTF-8 character detected.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// toAPIErrorCode - Converts embedded errors. Convenience
|
// toAPIErrorCode - Converts embedded errors. Convenience
|
||||||
@@ -2082,6 +2123,11 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Errors that are generated by net.Conn and any context errors must be handled here.
|
||||||
|
if errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return ErrRequestTimedout
|
||||||
|
}
|
||||||
|
|
||||||
// Only return ErrClientDisconnected if the provided context is actually canceled.
|
// Only return ErrClientDisconnected if the provided context is actually canceled.
|
||||||
// This way downstream context.Canceled will still report ErrRequestTimedout
|
// This way downstream context.Canceled will still report ErrRequestTimedout
|
||||||
if contextCanceled(ctx) && errors.Is(ctx.Err(), context.Canceled) {
|
if contextCanceled(ctx) && errors.Is(ctx.Err(), context.Canceled) {
|
||||||
@@ -2106,6 +2152,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminNoSuchGroup
|
apiErr = ErrAdminNoSuchGroup
|
||||||
case errGroupNotEmpty:
|
case errGroupNotEmpty:
|
||||||
apiErr = ErrAdminGroupNotEmpty
|
apiErr = ErrAdminGroupNotEmpty
|
||||||
|
case errGroupNameContainsReservedChars:
|
||||||
|
apiErr = ErrAdminInvalidGroupName
|
||||||
case errNoSuchJob:
|
case errNoSuchJob:
|
||||||
apiErr = ErrAdminNoSuchJob
|
apiErr = ErrAdminNoSuchJob
|
||||||
case errNoPolicyToAttachOrDetach:
|
case errNoPolicyToAttachOrDetach:
|
||||||
@@ -2120,10 +2168,16 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrEntityTooSmall
|
apiErr = ErrEntityTooSmall
|
||||||
case errAuthentication:
|
case errAuthentication:
|
||||||
apiErr = ErrAccessDenied
|
apiErr = ErrAccessDenied
|
||||||
|
case auth.ErrContainsReservedChars:
|
||||||
|
apiErr = ErrAdminInvalidAccessKey
|
||||||
case auth.ErrInvalidAccessKeyLength:
|
case auth.ErrInvalidAccessKeyLength:
|
||||||
apiErr = ErrAdminInvalidAccessKey
|
apiErr = ErrAdminInvalidAccessKey
|
||||||
case auth.ErrInvalidSecretKeyLength:
|
case auth.ErrInvalidSecretKeyLength:
|
||||||
apiErr = ErrAdminInvalidSecretKey
|
apiErr = ErrAdminInvalidSecretKey
|
||||||
|
case auth.ErrNoAccessKeyWithSecretKey:
|
||||||
|
apiErr = ErrAdminNoAccessKey
|
||||||
|
case auth.ErrNoSecretKeyWithAccessKey:
|
||||||
|
apiErr = ErrAdminNoSecretKey
|
||||||
case errInvalidStorageClass:
|
case errInvalidStorageClass:
|
||||||
apiErr = ErrInvalidStorageClass
|
apiErr = ErrInvalidStorageClass
|
||||||
case errErasureReadQuorum:
|
case errErasureReadQuorum:
|
||||||
@@ -2183,6 +2237,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrInvalidMaxParts
|
apiErr = ErrInvalidMaxParts
|
||||||
case ioutil.ErrOverread:
|
case ioutil.ErrOverread:
|
||||||
apiErr = ErrExcessData
|
apiErr = ErrExcessData
|
||||||
|
case errServerNotInitialized:
|
||||||
|
apiErr = ErrServerNotInitialized
|
||||||
|
case errBucketMetadataNotInitialized:
|
||||||
|
apiErr = ErrBucketMetadataNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compression errors
|
// Compression errors
|
||||||
@@ -2351,31 +2409,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
case dns.ErrBucketConflict:
|
case dns.ErrBucketConflict:
|
||||||
apiErr = ErrBucketAlreadyExists
|
apiErr = ErrBucketAlreadyExists
|
||||||
default:
|
default:
|
||||||
if _, ok := err.(tags.Error); ok {
|
if strings.Contains(err.Error(), "request declared a Content-Length") {
|
||||||
// tag errors are not exported, so we check their custom interface to avoid logging.
|
|
||||||
// The correct type is inserted by toAPIError.
|
|
||||||
apiErr = ErrInternalError
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var ie, iw int
|
|
||||||
// This work-around is to handle the issue golang/go#30648
|
|
||||||
//nolint:gocritic
|
|
||||||
if _, ferr := fmt.Fscanf(strings.NewReader(err.Error()),
|
|
||||||
"request declared a Content-Length of %d but only wrote %d bytes",
|
|
||||||
&ie, &iw); ferr != nil {
|
|
||||||
apiErr = ErrInternalError
|
|
||||||
// Make sure to log the errors which we cannot translate
|
|
||||||
// to a meaningful S3 API errors. This is added to aid in
|
|
||||||
// debugging unexpected/unhandled errors.
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
} else if ie > iw {
|
|
||||||
apiErr = ErrIncompleteBody
|
apiErr = ErrIncompleteBody
|
||||||
} else {
|
} else {
|
||||||
apiErr = ErrInternalError
|
apiErr = ErrInternalError
|
||||||
// Make sure to log the errors which we cannot translate
|
|
||||||
// to a meaningful S3 API errors. This is added to aid in
|
|
||||||
// debugging unexpected/unhandled errors.
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2395,10 +2432,9 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
apiErr := errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
apiErr := errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
||||||
switch apiErr.Code {
|
switch apiErr.Code {
|
||||||
case "NotImplemented":
|
case "NotImplemented":
|
||||||
desc := fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: apiErr.Code,
|
Code: apiErr.Code,
|
||||||
Description: desc,
|
Description: fmt.Sprintf("%s (%v)", apiErr.Description, err),
|
||||||
HTTPStatusCode: apiErr.HTTPStatusCode,
|
HTTPStatusCode: apiErr.HTTPStatusCode,
|
||||||
}
|
}
|
||||||
case "XMinioBackendDown":
|
case "XMinioBackendDown":
|
||||||
@@ -2410,12 +2446,24 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case kms.Error:
|
case kms.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Description: e.Err.Error(),
|
|
||||||
Code: e.APICode,
|
Code: e.APICode,
|
||||||
HTTPStatusCode: e.HTTPStatusCode,
|
Description: e.Err,
|
||||||
|
HTTPStatusCode: e.Code,
|
||||||
}
|
}
|
||||||
case batchReplicationJobError:
|
case batchReplicationJobError:
|
||||||
apiErr = APIError(e)
|
apiErr = APIError{
|
||||||
|
Description: e.Description,
|
||||||
|
Code: e.Code,
|
||||||
|
HTTPStatusCode: e.HTTPStatusCode,
|
||||||
|
}
|
||||||
|
case InvalidRange:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "InvalidRange",
|
||||||
|
Description: e.Error(),
|
||||||
|
HTTPStatusCode: errorCodes[ErrInvalidRange].HTTPStatusCode,
|
||||||
|
ObjectSize: strconv.FormatInt(e.ResourceSize, 10),
|
||||||
|
RangeRequested: fmt.Sprintf("%d-%d", e.OffsetBegin, e.OffsetEnd),
|
||||||
|
}
|
||||||
case InvalidArgument:
|
case InvalidArgument:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -2519,6 +2567,13 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if apiErr.Code == "InternalError" {
|
||||||
|
// Make sure to log the errors which we cannot translate
|
||||||
|
// to a meaningful S3 API errors. This is added to aid in
|
||||||
|
// debugging unexpected/unhandled errors.
|
||||||
|
internalLogIf(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
return apiErr
|
return apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2535,13 +2590,15 @@ func getAPIError(code APIErrorCode) APIError {
|
|||||||
func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID, hostID string) APIErrorResponse {
|
func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID, hostID string) APIErrorResponse {
|
||||||
reqInfo := logger.GetReqInfo(ctx)
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
return APIErrorResponse{
|
return APIErrorResponse{
|
||||||
Code: err.Code,
|
Code: err.Code,
|
||||||
Message: err.Description,
|
Message: err.Description,
|
||||||
BucketName: reqInfo.BucketName,
|
BucketName: reqInfo.BucketName,
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
Resource: resource,
|
Resource: resource,
|
||||||
Region: globalSite.Region,
|
Region: globalSite.Region(),
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
HostID: hostID,
|
HostID: hostID,
|
||||||
|
ActualObjectSize: err.ObjectSize,
|
||||||
|
RangeRequested: err.RangeRequested,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -30,7 +31,6 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
xxml "github.com/minio/xxml"
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,11 +50,11 @@ func setEventStreamHeaders(w http.ResponseWriter) {
|
|||||||
// Write http common headers
|
// Write http common headers
|
||||||
func setCommonHeaders(w http.ResponseWriter) {
|
func setCommonHeaders(w http.ResponseWriter) {
|
||||||
// Set the "Server" http header.
|
// Set the "Server" http header.
|
||||||
w.Header().Set(xhttp.ServerInfo, "MinIO")
|
w.Header().Set(xhttp.ServerInfo, MinioStoreName)
|
||||||
|
|
||||||
// Set `x-amz-bucket-region` only if region is set on the server
|
// Set `x-amz-bucket-region` only if region is set on the server
|
||||||
// by default minio uses an empty region.
|
// by default minio uses an empty region.
|
||||||
if region := globalSite.Region; region != "" {
|
if region := globalSite.Region(); region != "" {
|
||||||
w.Header().Set(xhttp.AmzBucketRegion, region)
|
w.Header().Set(xhttp.AmzBucketRegion, region)
|
||||||
}
|
}
|
||||||
w.Header().Set(xhttp.AcceptRanges, "bytes")
|
w.Header().Set(xhttp.AcceptRanges, "bytes")
|
||||||
@@ -68,7 +68,7 @@ func encodeResponse(response interface{}) []byte {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.WriteString(xml.Header)
|
buf.WriteString(xml.Header)
|
||||||
if err := xml.NewEncoder(&buf).Encode(response); err != nil {
|
if err := xml.NewEncoder(&buf).Encode(response); err != nil {
|
||||||
logger.LogIf(GlobalContext, err)
|
bugLogIf(GlobalContext, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
@@ -86,7 +86,7 @@ func encodeResponseList(response interface{}) []byte {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.WriteString(xxml.Header)
|
buf.WriteString(xxml.Header)
|
||||||
if err := xxml.NewEncoder(&buf).Encode(response); err != nil {
|
if err := xxml.NewEncoder(&buf).Encode(response); err != nil {
|
||||||
logger.LogIf(GlobalContext, err)
|
bugLogIf(GlobalContext, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
@@ -108,7 +108,7 @@ func setPartsCountHeaders(w http.ResponseWriter, objInfo ObjectInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write object header
|
// Write object header
|
||||||
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) {
|
func setObjectHeaders(ctx context.Context, w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) {
|
||||||
// set common headers
|
// set common headers
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
// Set tag count if object has tags
|
// Set tag count if object has tags
|
||||||
if len(objInfo.UserTags) > 0 {
|
if len(objInfo.UserTags) > 0 {
|
||||||
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
if tags.Count() > 0 {
|
if tags != nil && tags.Count() > 0 {
|
||||||
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())}
|
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())}
|
||||||
if opts.Tagging {
|
if opts.Tagging {
|
||||||
// This is MinIO only extension to return back tags along with the count.
|
// This is MinIO only extension to return back tags along with the count.
|
||||||
@@ -213,7 +213,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
if objInfo.IsRemote() {
|
if objInfo.IsRemote() {
|
||||||
// Check if object is being restored. For more information on x-amz-restore header see
|
// Check if object is being restored. For more information on x-amz-restore header see
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
|
||||||
w.Header()[xhttp.AmzStorageClass] = []string{objInfo.TransitionedObject.Tier}
|
w.Header()[xhttp.AmzStorageClass] = []string{filterStorageClass(ctx, objInfo.TransitionedObject.Tier)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
||||||
|
|||||||
+16
-14
@@ -35,7 +35,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
xxml "github.com/minio/xxml"
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -544,7 +544,7 @@ func cleanReservedKeys(metadata map[string]string) map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
func generateListVersionsResponse(ctx context.Context, bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
|
|
||||||
owner := &Owner{
|
owner := &Owner{
|
||||||
@@ -573,7 +573,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
}
|
}
|
||||||
content.Size = object.Size
|
content.Size = object.Size
|
||||||
if object.StorageClass != "" {
|
if object.StorageClass != "" {
|
||||||
content.StorageClass = object.StorageClass
|
content.StorageClass = filterStorageClass(ctx, object.StorageClass)
|
||||||
} else {
|
} else {
|
||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
@@ -634,7 +634,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
func generateListObjectsV1Response(ctx context.Context, bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
||||||
contents := make([]Object, 0, len(resp.Objects))
|
contents := make([]Object, 0, len(resp.Objects))
|
||||||
owner := &Owner{
|
owner := &Owner{
|
||||||
ID: globalMinioDefaultOwnerID,
|
ID: globalMinioDefaultOwnerID,
|
||||||
@@ -654,7 +654,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
}
|
}
|
||||||
content.Size = object.Size
|
content.Size = object.Size
|
||||||
if object.StorageClass != "" {
|
if object.StorageClass != "" {
|
||||||
content.StorageClass = object.StorageClass
|
content.StorageClass = filterStorageClass(ctx, object.StorageClass)
|
||||||
} else {
|
} else {
|
||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
@@ -683,7 +683,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
|
func generateListObjectsV2Response(ctx context.Context, bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
|
||||||
contents := make([]Object, 0, len(objects))
|
contents := make([]Object, 0, len(objects))
|
||||||
var owner *Owner
|
var owner *Owner
|
||||||
if fetchOwner {
|
if fetchOwner {
|
||||||
@@ -707,7 +707,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
}
|
}
|
||||||
content.Size = object.Size
|
content.Size = object.Size
|
||||||
if object.StorageClass != "" {
|
if object.StorageClass != "" {
|
||||||
content.StorageClass = object.StorageClass
|
content.StorageClass = filterStorageClass(ctx, object.StorageClass)
|
||||||
} else {
|
} else {
|
||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
@@ -891,7 +891,7 @@ func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType
|
|||||||
}
|
}
|
||||||
// Similar check to http.checkWriteHeaderCode
|
// Similar check to http.checkWriteHeaderCode
|
||||||
if statusCode < 100 || statusCode > 999 {
|
if statusCode < 100 || statusCode > 999 {
|
||||||
logger.Error(fmt.Sprintf("invalid WriteHeader code %v", statusCode))
|
bugLogIf(context.Background(), fmt.Errorf("invalid WriteHeader code %v", statusCode))
|
||||||
statusCode = http.StatusInternalServerError
|
statusCode = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
@@ -944,22 +944,24 @@ func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
|
|||||||
writeResponse(w, http.StatusOK, nil, mimeNone)
|
writeResponse(w, http.StatusOK, nil, mimeNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeErrorRespone writes error headers
|
// writeErrorResponse writes error headers
|
||||||
func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
||||||
switch err.Code {
|
if err.HTTPStatusCode == http.StatusServiceUnavailable {
|
||||||
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
|
|
||||||
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||||
w.Header().Set(xhttp.RetryAfter, "120")
|
w.Header().Set(xhttp.RetryAfter, "120")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch err.Code {
|
||||||
case "InvalidRegion":
|
case "InvalidRegion":
|
||||||
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region)
|
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region())
|
||||||
case "AuthorizationHeaderMalformed":
|
case "AuthorizationHeaderMalformed":
|
||||||
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similar check to http.checkWriteHeaderCode
|
// Similar check to http.checkWriteHeaderCode
|
||||||
if err.HTTPStatusCode < 100 || err.HTTPStatusCode > 999 {
|
if err.HTTPStatusCode < 100 || err.HTTPStatusCode > 999 {
|
||||||
logger.Error(fmt.Sprintf("invalid WriteHeader code %v from %v", err.HTTPStatusCode, err.Code))
|
bugLogIf(ctx, fmt.Errorf("invalid WriteHeader code %v from %v", err.HTTPStatusCode, err.Code))
|
||||||
err.HTTPStatusCode = http.StatusInternalServerError
|
err.HTTPStatusCode = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+304
-164
@@ -18,16 +18,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
|
||||||
consoleapi "github.com/minio/console/api"
|
consoleapi "github.com/minio/console/api"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/wildcard"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ func setObjectLayer(o ObjectLayer) {
|
|||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectAPIHandler implements and provides http handlers for S3 API.
|
// objectAPIHandlers implements and provides http handlers for S3 API.
|
||||||
type objectAPIHandlers struct {
|
type objectAPIHandlers struct {
|
||||||
ObjectAPI func() ObjectLayer
|
ObjectAPI func() ObjectLayer
|
||||||
}
|
}
|
||||||
@@ -171,6 +168,87 @@ var rejectedBucketAPIs = []rejectedAPI{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set of s3 handler options as bit flags.
|
||||||
|
type s3HFlag uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// when provided, disables Gzip compression.
|
||||||
|
noGZS3HFlag = 1 << iota
|
||||||
|
|
||||||
|
// when provided, enables only tracing of headers. Otherwise, both headers
|
||||||
|
// and body are traced.
|
||||||
|
traceHdrsS3HFlag
|
||||||
|
|
||||||
|
// when provided, disables throttling via the `maxClients` middleware.
|
||||||
|
noThrottleS3HFlag
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h s3HFlag) has(flag s3HFlag) bool {
|
||||||
|
// Use bitwise-AND and check if the result is non-zero.
|
||||||
|
return h&flag != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// s3APIMiddleware - performs some common handler functionality for S3 API
|
||||||
|
// handlers.
|
||||||
|
//
|
||||||
|
// It is set per-"handler function registration" in the router to allow for
|
||||||
|
// behavior modification via flags.
|
||||||
|
//
|
||||||
|
// This middleware always calls `collectAPIStats` to collect API stats.
|
||||||
|
//
|
||||||
|
// The passed in handler function must be a method of `objectAPIHandlers` for
|
||||||
|
// the name displayed in logs and trace to be accurate. The name is extracted
|
||||||
|
// via reflection.
|
||||||
|
//
|
||||||
|
// When **no** flags are passed, the behavior is to trace both headers and body,
|
||||||
|
// gzip the response and throttle the handler via `maxClients`. Each of these
|
||||||
|
// can be disabled via the corresponding `s3HFlag`.
|
||||||
|
//
|
||||||
|
// CAUTION: for requests involving large req/resp bodies ensure to pass the
|
||||||
|
// `traceHdrsS3HFlag`, otherwise both headers and body will be traced, causing
|
||||||
|
// high memory usage!
|
||||||
|
func s3APIMiddleware(f http.HandlerFunc, flags ...s3HFlag) http.HandlerFunc {
|
||||||
|
// Collect all flags with bitwise-OR and assign operator
|
||||||
|
var handlerFlags s3HFlag
|
||||||
|
for _, flag := range flags {
|
||||||
|
handlerFlags |= flag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get name of the handler using reflection.
|
||||||
|
handlerName := getHandlerName(f, "objectAPIHandlers")
|
||||||
|
|
||||||
|
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Wrap the actual handler with the appropriate tracing middleware.
|
||||||
|
var tracedHandler http.HandlerFunc
|
||||||
|
if handlerFlags.has(traceHdrsS3HFlag) {
|
||||||
|
tracedHandler = httpTraceHdrs(f)
|
||||||
|
} else {
|
||||||
|
tracedHandler = httpTraceAll(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip wrapping with the gzip middleware if specified.
|
||||||
|
var gzippedHandler http.HandlerFunc = tracedHandler
|
||||||
|
if !handlerFlags.has(noGZS3HFlag) {
|
||||||
|
gzippedHandler = gzipHandler(gzippedHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip wrapping with throttling middleware if specified.
|
||||||
|
var throttledHandler http.HandlerFunc = gzippedHandler
|
||||||
|
if !handlerFlags.has(noThrottleS3HFlag) {
|
||||||
|
throttledHandler = maxClients(throttledHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect API stats using the API name got from reflection in
|
||||||
|
// `getHandlerName`.
|
||||||
|
statsCollectedHandler := collectAPIStats(handlerName, throttledHandler)
|
||||||
|
|
||||||
|
// Call the final handler.
|
||||||
|
statsCollectedHandler(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
// registerAPIRouter - registers S3 compatible APIs.
|
// registerAPIRouter - registers S3 compatible APIs.
|
||||||
func registerAPIRouter(router *mux.Router) {
|
func registerAPIRouter(router *mux.Router) {
|
||||||
// Initialize API.
|
// Initialize API.
|
||||||
@@ -208,12 +286,6 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
}
|
}
|
||||||
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
||||||
|
|
||||||
gz, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed))
|
|
||||||
if err != nil {
|
|
||||||
// Static params, so this is very unlikely.
|
|
||||||
logger.Fatal(err, "Unable to initialize server")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, router := range routers {
|
for _, router := range routers {
|
||||||
// Register all rejected object APIs
|
// Register all rejected object APIs
|
||||||
for _, r := range rejectedObjAPIs {
|
for _, r := range rejectedObjAPIs {
|
||||||
@@ -225,240 +297,307 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
|
|
||||||
// Object operations
|
// Object operations
|
||||||
// HeadObject
|
// HeadObject
|
||||||
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodHead).Path("/{object:.+}").
|
||||||
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
|
HandlerFunc(s3APIMiddleware(api.HeadObjectHandler))
|
||||||
// GetObjectAttribytes
|
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
// GetObjectAttributes
|
||||||
collectAPIStats("getobjectattributes", maxClients(gz(httpTraceHdrs(api.GetObjectAttributesHandler))))).Queries("attributes", "")
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
|
HandlerFunc(s3APIMiddleware(api.GetObjectAttributesHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("attributes", "")
|
||||||
|
|
||||||
// CopyObjectPart
|
// CopyObjectPart
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
||||||
HandlerFunc(collectAPIStats("copyobjectpart", maxClients(gz(httpTraceAll(api.CopyObjectPartHandler))))).
|
HandlerFunc(s3APIMiddleware(api.CopyObjectPartHandler)).
|
||||||
Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
||||||
// PutObjectPart
|
// PutObjectPart
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("putobjectpart", maxClients(gz(httpTraceHdrs(api.PutObjectPartHandler))))).Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
HandlerFunc(s3APIMiddleware(api.PutObjectPartHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
||||||
// ListObjectParts
|
// ListObjectParts
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
collectAPIStats("listobjectparts", maxClients(gz(httpTraceAll(api.ListObjectPartsHandler))))).Queries("uploadId", "{uploadId:.*}")
|
HandlerFunc(s3APIMiddleware(api.ListObjectPartsHandler)).
|
||||||
|
Queries("uploadId", "{uploadId:.*}")
|
||||||
// CompleteMultipartUpload
|
// CompleteMultipartUpload
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").
|
||||||
collectAPIStats("completemultipartupload", maxClients(gz(httpTraceAll(api.CompleteMultipartUploadHandler))))).Queries("uploadId", "{uploadId:.*}")
|
HandlerFunc(s3APIMiddleware(api.CompleteMultipartUploadHandler)).
|
||||||
|
Queries("uploadId", "{uploadId:.*}")
|
||||||
// NewMultipartUpload
|
// NewMultipartUpload
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").
|
||||||
collectAPIStats("newmultipartupload", maxClients(gz(httpTraceAll(api.NewMultipartUploadHandler))))).Queries("uploads", "")
|
HandlerFunc(s3APIMiddleware(api.NewMultipartUploadHandler)).
|
||||||
|
Queries("uploads", "")
|
||||||
// AbortMultipartUpload
|
// AbortMultipartUpload
|
||||||
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodDelete).Path("/{object:.+}").
|
||||||
collectAPIStats("abortmultipartupload", maxClients(gz(httpTraceAll(api.AbortMultipartUploadHandler))))).Queries("uploadId", "{uploadId:.*}")
|
HandlerFunc(s3APIMiddleware(api.AbortMultipartUploadHandler)).
|
||||||
|
Queries("uploadId", "{uploadId:.*}")
|
||||||
// GetObjectACL - this is a dummy call.
|
// GetObjectACL - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
collectAPIStats("getobjectacl", maxClients(gz(httpTraceHdrs(api.GetObjectACLHandler))))).Queries("acl", "")
|
HandlerFunc(s3APIMiddleware(api.GetObjectACLHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("acl", "")
|
||||||
// PutObjectACL - this is a dummy call.
|
// PutObjectACL - this is a dummy call.
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("putobjectacl", maxClients(gz(httpTraceHdrs(api.PutObjectACLHandler))))).Queries("acl", "")
|
HandlerFunc(s3APIMiddleware(api.PutObjectACLHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("acl", "")
|
||||||
// GetObjectTagging
|
// GetObjectTagging
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
collectAPIStats("getobjecttagging", maxClients(gz(httpTraceHdrs(api.GetObjectTaggingHandler))))).Queries("tagging", "")
|
HandlerFunc(s3APIMiddleware(api.GetObjectTaggingHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("tagging", "")
|
||||||
// PutObjectTagging
|
// PutObjectTagging
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("putobjecttagging", maxClients(gz(httpTraceHdrs(api.PutObjectTaggingHandler))))).Queries("tagging", "")
|
HandlerFunc(s3APIMiddleware(api.PutObjectTaggingHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("tagging", "")
|
||||||
// DeleteObjectTagging
|
// DeleteObjectTagging
|
||||||
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodDelete).Path("/{object:.+}").
|
||||||
collectAPIStats("deleteobjecttagging", maxClients(gz(httpTraceHdrs(api.DeleteObjectTaggingHandler))))).Queries("tagging", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteObjectTaggingHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("tagging", "")
|
||||||
// SelectObjectContent
|
// SelectObjectContent
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").
|
||||||
collectAPIStats("selectobjectcontent", maxClients(gz(httpTraceHdrs(api.SelectObjectContentHandler))))).Queries("select", "").Queries("select-type", "2")
|
HandlerFunc(s3APIMiddleware(api.SelectObjectContentHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("select", "").Queries("select-type", "2")
|
||||||
// GetObjectRetention
|
// GetObjectRetention
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
collectAPIStats("getobjectretention", maxClients(gz(httpTraceAll(api.GetObjectRetentionHandler))))).Queries("retention", "")
|
HandlerFunc(s3APIMiddleware(api.GetObjectRetentionHandler)).
|
||||||
|
Queries("retention", "")
|
||||||
// GetObjectLegalHold
|
// GetObjectLegalHold
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
collectAPIStats("getobjectlegalhold", maxClients(gz(httpTraceAll(api.GetObjectLegalHoldHandler))))).Queries("legal-hold", "")
|
HandlerFunc(s3APIMiddleware(api.GetObjectLegalHoldHandler)).
|
||||||
|
Queries("legal-hold", "")
|
||||||
// GetObject with lambda ARNs
|
// GetObject with lambda ARNs
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
collectAPIStats("getobject", maxClients(gz(httpTraceHdrs(api.GetObjectLambdaHandler))))).Queries("lambdaArn", "{lambdaArn:.*}")
|
HandlerFunc(s3APIMiddleware(api.GetObjectLambdaHandler, traceHdrsS3HFlag)).
|
||||||
|
Queries("lambdaArn", "{lambdaArn:.*}")
|
||||||
// GetObject
|
// GetObject
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").
|
||||||
collectAPIStats("getobject", maxClients(gz(httpTraceHdrs(api.GetObjectHandler)))))
|
HandlerFunc(s3APIMiddleware(api.GetObjectHandler, traceHdrsS3HFlag))
|
||||||
// CopyObject
|
// CopyObject
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("copyobject", maxClients(gz(httpTraceAll(api.CopyObjectHandler)))))
|
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
||||||
|
HandlerFunc(s3APIMiddleware(api.CopyObjectHandler))
|
||||||
// PutObjectRetention
|
// PutObjectRetention
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("putobjectretention", maxClients(gz(httpTraceAll(api.PutObjectRetentionHandler))))).Queries("retention", "")
|
HandlerFunc(s3APIMiddleware(api.PutObjectRetentionHandler)).
|
||||||
|
Queries("retention", "")
|
||||||
// PutObjectLegalHold
|
// PutObjectLegalHold
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("putobjectlegalhold", maxClients(gz(httpTraceAll(api.PutObjectLegalHoldHandler))))).Queries("legal-hold", "")
|
HandlerFunc(s3APIMiddleware(api.PutObjectLegalHoldHandler)).
|
||||||
|
Queries("legal-hold", "")
|
||||||
|
|
||||||
// PutObject with auto-extract support for zip
|
// PutObject with auto-extract support for zip
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzSnowballExtract, "true").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("putobject", maxClients(gz(httpTraceHdrs(api.PutObjectExtractHandler)))))
|
HeadersRegexp(xhttp.AmzSnowballExtract, "true").
|
||||||
|
HandlerFunc(s3APIMiddleware(api.PutObjectExtractHandler, traceHdrsS3HFlag))
|
||||||
|
|
||||||
// PutObject
|
// PutObject
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
collectAPIStats("putobject", maxClients(gz(httpTraceHdrs(api.PutObjectHandler)))))
|
HandlerFunc(s3APIMiddleware(api.PutObjectHandler, traceHdrsS3HFlag))
|
||||||
|
|
||||||
// DeleteObject
|
// DeleteObject
|
||||||
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodDelete).Path("/{object:.+}").
|
||||||
collectAPIStats("deleteobject", maxClients(gz(httpTraceAll(api.DeleteObjectHandler)))))
|
HandlerFunc(s3APIMiddleware(api.DeleteObjectHandler))
|
||||||
|
|
||||||
// PostRestoreObject
|
// PostRestoreObject
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").
|
||||||
collectAPIStats("restoreobject", maxClients(gz(httpTraceAll(api.PostRestoreObjectHandler))))).Queries("restore", "")
|
HandlerFunc(s3APIMiddleware(api.PostRestoreObjectHandler)).
|
||||||
|
Queries("restore", "")
|
||||||
|
|
||||||
// Bucket operations
|
// Bucket operations
|
||||||
|
|
||||||
// GetBucketLocation
|
// GetBucketLocation
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketlocation", maxClients(gz(httpTraceAll(api.GetBucketLocationHandler))))).Queries("location", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketLocationHandler)).
|
||||||
|
Queries("location", "")
|
||||||
// GetBucketPolicy
|
// GetBucketPolicy
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketpolicy", maxClients(gz(httpTraceAll(api.GetBucketPolicyHandler))))).Queries("policy", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketPolicyHandler)).
|
||||||
|
Queries("policy", "")
|
||||||
// GetBucketLifecycle
|
// GetBucketLifecycle
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketlifecycle", maxClients(gz(httpTraceAll(api.GetBucketLifecycleHandler))))).Queries("lifecycle", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketLifecycleHandler)).
|
||||||
|
Queries("lifecycle", "")
|
||||||
// GetBucketEncryption
|
// GetBucketEncryption
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketencryption", maxClients(gz(httpTraceAll(api.GetBucketEncryptionHandler))))).Queries("encryption", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketEncryptionHandler)).
|
||||||
|
Queries("encryption", "")
|
||||||
// GetBucketObjectLockConfig
|
// GetBucketObjectLockConfig
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketobjectlockconfiguration", maxClients(gz(httpTraceAll(api.GetBucketObjectLockConfigHandler))))).Queries("object-lock", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketObjectLockConfigHandler)).
|
||||||
|
Queries("object-lock", "")
|
||||||
// GetBucketReplicationConfig
|
// GetBucketReplicationConfig
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.GetBucketReplicationConfigHandler))))).Queries("replication", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketReplicationConfigHandler)).
|
||||||
|
Queries("replication", "")
|
||||||
// GetBucketVersioning
|
// GetBucketVersioning
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketversioning", maxClients(gz(httpTraceAll(api.GetBucketVersioningHandler))))).Queries("versioning", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketVersioningHandler)).
|
||||||
|
Queries("versioning", "")
|
||||||
// GetBucketNotification
|
// GetBucketNotification
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketnotification", maxClients(gz(httpTraceAll(api.GetBucketNotificationHandler))))).Queries("notification", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketNotificationHandler)).
|
||||||
|
Queries("notification", "")
|
||||||
// ListenNotification
|
// ListenNotification
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("listennotification", gz(httpTraceAll(api.ListenNotificationHandler)))).Queries("events", "{events:.*}")
|
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag)).
|
||||||
|
Queries("events", "{events:.*}")
|
||||||
// ResetBucketReplicationStatus - MinIO extension API
|
// ResetBucketReplicationStatus - MinIO extension API
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("resetbucketreplicationstatus", maxClients(gz(httpTraceAll(api.ResetBucketReplicationStatusHandler))))).Queries("replication-reset-status", "")
|
HandlerFunc(s3APIMiddleware(api.ResetBucketReplicationStatusHandler)).
|
||||||
|
Queries("replication-reset-status", "")
|
||||||
|
|
||||||
// Dummy Bucket Calls
|
// Dummy Bucket Calls
|
||||||
// GetBucketACL -- this is a dummy call.
|
// GetBucketACL -- this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketacl", maxClients(gz(httpTraceAll(api.GetBucketACLHandler))))).Queries("acl", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketACLHandler)).
|
||||||
|
Queries("acl", "")
|
||||||
// PutBucketACL -- this is a dummy call.
|
// PutBucketACL -- this is a dummy call.
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketacl", maxClients(gz(httpTraceAll(api.PutBucketACLHandler))))).Queries("acl", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketACLHandler)).
|
||||||
|
Queries("acl", "")
|
||||||
// GetBucketCors - this is a dummy call.
|
// GetBucketCors - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketcors", maxClients(gz(httpTraceAll(api.GetBucketCorsHandler))))).Queries("cors", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketCorsHandler)).
|
||||||
|
Queries("cors", "")
|
||||||
// GetBucketWebsiteHandler - this is a dummy call.
|
// GetBucketWebsiteHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketwebsite", maxClients(gz(httpTraceAll(api.GetBucketWebsiteHandler))))).Queries("website", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketWebsiteHandler)).
|
||||||
|
Queries("website", "")
|
||||||
// GetBucketAccelerateHandler - this is a dummy call.
|
// GetBucketAccelerateHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketaccelerate", maxClients(gz(httpTraceAll(api.GetBucketAccelerateHandler))))).Queries("accelerate", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketAccelerateHandler)).
|
||||||
|
Queries("accelerate", "")
|
||||||
// GetBucketRequestPaymentHandler - this is a dummy call.
|
// GetBucketRequestPaymentHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketrequestpayment", maxClients(gz(httpTraceAll(api.GetBucketRequestPaymentHandler))))).Queries("requestPayment", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketRequestPaymentHandler)).
|
||||||
|
Queries("requestPayment", "")
|
||||||
// GetBucketLoggingHandler - this is a dummy call.
|
// GetBucketLoggingHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketlogging", maxClients(gz(httpTraceAll(api.GetBucketLoggingHandler))))).Queries("logging", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketLoggingHandler)).
|
||||||
|
Queries("logging", "")
|
||||||
// GetBucketTaggingHandler
|
// GetBucketTaggingHandler
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbuckettagging", maxClients(gz(httpTraceAll(api.GetBucketTaggingHandler))))).Queries("tagging", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketTaggingHandler)).
|
||||||
|
Queries("tagging", "")
|
||||||
// DeleteBucketWebsiteHandler
|
// DeleteBucketWebsiteHandler
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).
|
||||||
collectAPIStats("deletebucketwebsite", maxClients(gz(httpTraceAll(api.DeleteBucketWebsiteHandler))))).Queries("website", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteBucketWebsiteHandler)).
|
||||||
|
Queries("website", "")
|
||||||
// DeleteBucketTaggingHandler
|
// DeleteBucketTaggingHandler
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).
|
||||||
collectAPIStats("deletebuckettagging", maxClients(gz(httpTraceAll(api.DeleteBucketTaggingHandler))))).Queries("tagging", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteBucketTaggingHandler)).
|
||||||
|
Queries("tagging", "")
|
||||||
|
|
||||||
// ListMultipartUploads
|
// ListMultipartUploads
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("listmultipartuploads", maxClients(gz(httpTraceAll(api.ListMultipartUploadsHandler))))).Queries("uploads", "")
|
HandlerFunc(s3APIMiddleware(api.ListMultipartUploadsHandler)).
|
||||||
|
Queries("uploads", "")
|
||||||
// ListObjectsV2M
|
// ListObjectsV2M
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("listobjectsv2M", maxClients(gz(httpTraceAll(api.ListObjectsV2MHandler))))).Queries("list-type", "2", "metadata", "true")
|
HandlerFunc(s3APIMiddleware(api.ListObjectsV2MHandler)).
|
||||||
|
Queries("list-type", "2", "metadata", "true")
|
||||||
// ListObjectsV2
|
// ListObjectsV2
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("listobjectsv2", maxClients(gz(httpTraceAll(api.ListObjectsV2Handler))))).Queries("list-type", "2")
|
HandlerFunc(s3APIMiddleware(api.ListObjectsV2Handler)).
|
||||||
|
Queries("list-type", "2")
|
||||||
// ListObjectVersions
|
// ListObjectVersions
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("listobjectversions", maxClients(gz(httpTraceAll(api.ListObjectVersionsMHandler))))).Queries("versions", "", "metadata", "true")
|
HandlerFunc(s3APIMiddleware(api.ListObjectVersionsMHandler)).
|
||||||
|
Queries("versions", "", "metadata", "true")
|
||||||
// ListObjectVersions
|
// ListObjectVersions
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("listobjectversions", maxClients(gz(httpTraceAll(api.ListObjectVersionsHandler))))).Queries("versions", "")
|
HandlerFunc(s3APIMiddleware(api.ListObjectVersionsHandler)).
|
||||||
|
Queries("versions", "")
|
||||||
// GetBucketPolicyStatus
|
// GetBucketPolicyStatus
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getpolicystatus", maxClients(gz(httpTraceAll(api.GetBucketPolicyStatusHandler))))).Queries("policyStatus", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketPolicyStatusHandler)).
|
||||||
|
Queries("policyStatus", "")
|
||||||
// PutBucketLifecycle
|
// PutBucketLifecycle
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketlifecycle", maxClients(gz(httpTraceAll(api.PutBucketLifecycleHandler))))).Queries("lifecycle", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketLifecycleHandler)).
|
||||||
|
Queries("lifecycle", "")
|
||||||
// PutBucketReplicationConfig
|
// PutBucketReplicationConfig
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.PutBucketReplicationConfigHandler))))).Queries("replication", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketReplicationConfigHandler)).
|
||||||
|
Queries("replication", "")
|
||||||
// PutBucketEncryption
|
// PutBucketEncryption
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketencryption", maxClients(gz(httpTraceAll(api.PutBucketEncryptionHandler))))).Queries("encryption", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketEncryptionHandler)).
|
||||||
|
Queries("encryption", "")
|
||||||
|
|
||||||
// PutBucketPolicy
|
// PutBucketPolicy
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketpolicy", maxClients(gz(httpTraceAll(api.PutBucketPolicyHandler))))).Queries("policy", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketPolicyHandler)).
|
||||||
|
Queries("policy", "")
|
||||||
|
|
||||||
// PutBucketObjectLockConfig
|
// PutBucketObjectLockConfig
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketobjectlockconfig", maxClients(gz(httpTraceAll(api.PutBucketObjectLockConfigHandler))))).Queries("object-lock", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketObjectLockConfigHandler)).
|
||||||
|
Queries("object-lock", "")
|
||||||
// PutBucketTaggingHandler
|
// PutBucketTaggingHandler
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbuckettagging", maxClients(gz(httpTraceAll(api.PutBucketTaggingHandler))))).Queries("tagging", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketTaggingHandler)).
|
||||||
|
Queries("tagging", "")
|
||||||
// PutBucketVersioning
|
// PutBucketVersioning
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketversioning", maxClients(gz(httpTraceAll(api.PutBucketVersioningHandler))))).Queries("versioning", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketVersioningHandler)).
|
||||||
|
Queries("versioning", "")
|
||||||
// PutBucketNotification
|
// PutBucketNotification
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucketnotification", maxClients(gz(httpTraceAll(api.PutBucketNotificationHandler))))).Queries("notification", "")
|
HandlerFunc(s3APIMiddleware(api.PutBucketNotificationHandler)).
|
||||||
|
Queries("notification", "")
|
||||||
// ResetBucketReplicationStart - MinIO extension API
|
// ResetBucketReplicationStart - MinIO extension API
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("resetbucketreplicationstart", maxClients(gz(httpTraceAll(api.ResetBucketReplicationStartHandler))))).Queries("replication-reset", "")
|
HandlerFunc(s3APIMiddleware(api.ResetBucketReplicationStartHandler)).
|
||||||
|
Queries("replication-reset", "")
|
||||||
|
|
||||||
// PutBucket
|
// PutBucket
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).
|
||||||
collectAPIStats("putbucket", maxClients(gz(httpTraceAll(api.PutBucketHandler)))))
|
HandlerFunc(s3APIMiddleware(api.PutBucketHandler))
|
||||||
// HeadBucket
|
// HeadBucket
|
||||||
router.Methods(http.MethodHead).HandlerFunc(
|
router.Methods(http.MethodHead).
|
||||||
collectAPIStats("headbucket", maxClients(gz(httpTraceAll(api.HeadBucketHandler)))))
|
HandlerFunc(s3APIMiddleware(api.HeadBucketHandler))
|
||||||
// PostPolicy
|
// PostPolicy
|
||||||
router.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, _ *mux.RouteMatch) bool {
|
router.Methods(http.MethodPost).
|
||||||
return isRequestPostPolicySignatureV4(r)
|
MatcherFunc(func(r *http.Request, _ *mux.RouteMatch) bool {
|
||||||
}).HandlerFunc(collectAPIStats("postpolicybucket", maxClients(gz(httpTraceHdrs(api.PostPolicyBucketHandler)))))
|
return isRequestPostPolicySignatureV4(r)
|
||||||
|
}).
|
||||||
|
HandlerFunc(s3APIMiddleware(api.PostPolicyBucketHandler, traceHdrsS3HFlag))
|
||||||
// DeleteMultipleObjects
|
// DeleteMultipleObjects
|
||||||
router.Methods(http.MethodPost).HandlerFunc(
|
router.Methods(http.MethodPost).
|
||||||
collectAPIStats("deletemultipleobjects", maxClients(gz(httpTraceAll(api.DeleteMultipleObjectsHandler))))).Queries("delete", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteMultipleObjectsHandler)).
|
||||||
|
Queries("delete", "")
|
||||||
// DeleteBucketPolicy
|
// DeleteBucketPolicy
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).
|
||||||
collectAPIStats("deletebucketpolicy", maxClients(gz(httpTraceAll(api.DeleteBucketPolicyHandler))))).Queries("policy", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteBucketPolicyHandler)).
|
||||||
|
Queries("policy", "")
|
||||||
// DeleteBucketReplication
|
// DeleteBucketReplication
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).
|
||||||
collectAPIStats("deletebucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.DeleteBucketReplicationConfigHandler))))).Queries("replication", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteBucketReplicationConfigHandler)).
|
||||||
|
Queries("replication", "")
|
||||||
// DeleteBucketLifecycle
|
// DeleteBucketLifecycle
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).
|
||||||
collectAPIStats("deletebucketlifecycle", maxClients(gz(httpTraceAll(api.DeleteBucketLifecycleHandler))))).Queries("lifecycle", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteBucketLifecycleHandler)).
|
||||||
|
Queries("lifecycle", "")
|
||||||
// DeleteBucketEncryption
|
// DeleteBucketEncryption
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).
|
||||||
collectAPIStats("deletebucketencryption", maxClients(gz(httpTraceAll(api.DeleteBucketEncryptionHandler))))).Queries("encryption", "")
|
HandlerFunc(s3APIMiddleware(api.DeleteBucketEncryptionHandler)).
|
||||||
|
Queries("encryption", "")
|
||||||
// DeleteBucket
|
// DeleteBucket
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).
|
||||||
collectAPIStats("deletebucket", maxClients(gz(httpTraceAll(api.DeleteBucketHandler)))))
|
HandlerFunc(s3APIMiddleware(api.DeleteBucketHandler))
|
||||||
|
|
||||||
// MinIO extension API for replication.
|
// MinIO extension API for replication.
|
||||||
//
|
//
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsV2Handler))))).Queries("replication-metrics", "2")
|
HandlerFunc(s3APIMiddleware(api.GetBucketReplicationMetricsV2Handler)).
|
||||||
|
Queries("replication-metrics", "2")
|
||||||
// deprecated handler
|
// deprecated handler
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsHandler))))).Queries("replication-metrics", "")
|
HandlerFunc(s3APIMiddleware(api.GetBucketReplicationMetricsHandler)).
|
||||||
|
Queries("replication-metrics", "")
|
||||||
|
|
||||||
// ValidateBucketReplicationCreds
|
// ValidateBucketReplicationCreds
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("checkbucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.ValidateBucketReplicationCredsHandler))))).Queries("replication-check", "")
|
HandlerFunc(s3APIMiddleware(api.ValidateBucketReplicationCredsHandler)).
|
||||||
|
Queries("replication-check", "")
|
||||||
|
|
||||||
// Register rejected bucket APIs
|
// Register rejected bucket APIs
|
||||||
for _, r := range rejectedBucketAPIs {
|
for _, r := range rejectedBucketAPIs {
|
||||||
@@ -468,24 +607,25 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// S3 ListObjectsV1 (Legacy)
|
// S3 ListObjectsV1 (Legacy)
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).
|
||||||
collectAPIStats("listobjectsv1", maxClients(gz(httpTraceAll(api.ListObjectsV1Handler)))))
|
HandlerFunc(s3APIMiddleware(api.ListObjectsV1Handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root operation
|
// Root operation
|
||||||
|
|
||||||
// ListenNotification
|
// ListenNotification
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).
|
||||||
collectAPIStats("listennotification", gz(httpTraceAll(api.ListenNotificationHandler)))).Queries("events", "{events:.*}")
|
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag)).
|
||||||
|
Queries("events", "{events:.*}")
|
||||||
|
|
||||||
// ListBuckets
|
// ListBuckets
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).
|
||||||
collectAPIStats("listbuckets", maxClients(gz(httpTraceAll(api.ListBucketsHandler)))))
|
HandlerFunc(s3APIMiddleware(api.ListBucketsHandler))
|
||||||
|
|
||||||
// S3 browser with signature v4 adds '//' for ListBuckets request, so rather
|
// S3 browser with signature v4 adds '//' for ListBuckets request, so rather
|
||||||
// than failing with UnknownAPIRequest we simply handle it for now.
|
// than failing with UnknownAPIRequest we simply handle it for now.
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator + SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator + SlashSeparator).
|
||||||
collectAPIStats("listbuckets", maxClients(gz(httpTraceAll(api.ListBucketsHandler)))))
|
HandlerFunc(s3APIMiddleware(api.ListBucketsHandler))
|
||||||
|
|
||||||
// If none of the routes match add default error handler routes
|
// If none of the routes match add default error handler routes
|
||||||
apiRouter.NotFoundHandler = collectAPIStats("notfound", httpTraceAll(errorResponseHandler))
|
apiRouter.NotFoundHandler = collectAPIStats("notfound", httpTraceAll(errorResponseHandler))
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,3 +104,16 @@ func s3EncodeName(name, encodingType string) string {
|
|||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getHandlerName returns the name of the handler function. It takes the type
|
||||||
|
// name as a string to clean up the name retrieved via reflection. This function
|
||||||
|
// only works correctly when the type is present in the cmd package.
|
||||||
|
func getHandlerName(f http.HandlerFunc, cmdType string) string {
|
||||||
|
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||||
|
|
||||||
|
packageName := fmt.Sprintf("github.com/minio/minio/cmd.%s.", cmdType)
|
||||||
|
name = strings.TrimPrefix(name, packageName)
|
||||||
|
name = strings.TrimSuffix(name, "Handler-fm")
|
||||||
|
name = strings.TrimSuffix(name, "-fm")
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|||||||
+159
-153
File diff suppressed because one or more lines are too long
+26
-11
@@ -41,7 +41,7 @@ import (
|
|||||||
xjwt "github.com/minio/minio/internal/jwt"
|
xjwt "github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/mcontext"
|
"github.com/minio/minio/internal/mcontext"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify if request has JWT.
|
// Verify if request has JWT.
|
||||||
@@ -126,7 +126,7 @@ func getRequestAuthType(r *http.Request) (at authType) {
|
|||||||
var err error
|
var err error
|
||||||
r.Form, err = url.ParseQuery(r.URL.RawQuery)
|
r.Form, err = url.ParseQuery(r.URL.RawQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(r.Context(), err)
|
authNLogIf(r.Context(), err)
|
||||||
return authTypeUnknown
|
return authTypeUnknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,8 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
s3Err := ErrAccessDenied
|
s3Err := ErrAccessDenied
|
||||||
if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
|
if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
|
||||||
getRequestAuthType(r) == authTypeSigned {
|
getRequestAuthType(r) == authTypeSigned {
|
||||||
// We only support admin credentials to access admin APIs.
|
|
||||||
|
// Get credential information from the request.
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return cred, owner, s3Err
|
||||||
@@ -177,7 +178,7 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
|
|
||||||
logger.GetReqInfo(ctx).Cred = cred
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
logger.GetReqInfo(ctx).Owner = owner
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
logger.GetReqInfo(ctx).Region = globalSite.Region
|
logger.GetReqInfo(ctx).Region = globalSite.Region()
|
||||||
|
|
||||||
return cred, owner, ErrNone
|
return cred, owner, ErrNone
|
||||||
}
|
}
|
||||||
@@ -256,7 +257,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Base64 decoding fails, we should log to indicate
|
// Base64 decoding fails, we should log to indicate
|
||||||
// something is malforming the request sent by client.
|
// something is malforming the request sent by client.
|
||||||
logger.LogIf(GlobalContext, err, logger.Application)
|
authNLogIf(GlobalContext, err, logger.ErrorKind)
|
||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
claims.MapClaims[sessionPolicyNameExtracted] = string(spBytes)
|
claims.MapClaims[sessionPolicyNameExtracted] = string(spBytes)
|
||||||
@@ -293,7 +294,21 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
|||||||
return nil, ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expired credentials must return error right away.
|
||||||
|
if cred.IsTemp() && cred.IsExpired() {
|
||||||
|
return nil, toAPIErrorCode(r.Context(), errInvalidAccessKeyID)
|
||||||
|
}
|
||||||
secret := globalActiveCred.SecretKey
|
secret := globalActiveCred.SecretKey
|
||||||
|
if globalSiteReplicationSys.isEnabled() && cred.AccessKey != siteReplicatorSvcAcc {
|
||||||
|
nsecret, err := getTokenSigningKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, toAPIErrorCode(r.Context(), err)
|
||||||
|
}
|
||||||
|
// sign root's temporary accounts also with site replicator creds
|
||||||
|
if cred.ParentUser != globalActiveCred.AccessKey || cred.IsTemp() {
|
||||||
|
secret = nsecret
|
||||||
|
}
|
||||||
|
}
|
||||||
if cred.IsServiceAccount() {
|
if cred.IsServiceAccount() {
|
||||||
token = cred.SessionToken
|
token = cred.SessionToken
|
||||||
secret = cred.SecretKey
|
secret = cred.SecretKey
|
||||||
@@ -338,7 +353,7 @@ func checkRequestAuthTypeWithVID(ctx context.Context, r *http.Request, action po
|
|||||||
|
|
||||||
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)
|
bugLogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.ErrorKind)
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +368,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
|
|||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypeSigned, authTypePresigned:
|
case authTypeSigned, authTypePresigned:
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
switch action {
|
switch action {
|
||||||
case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
|
case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
|
||||||
region = ""
|
region = ""
|
||||||
@@ -369,7 +384,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
|
|||||||
|
|
||||||
logger.GetReqInfo(ctx).Cred = cred
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
logger.GetReqInfo(ctx).Owner = owner
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
logger.GetReqInfo(ctx).Region = globalSite.Region
|
logger.GetReqInfo(ctx).Region = globalSite.Region()
|
||||||
|
|
||||||
// region is valid only for CreateBucketAction.
|
// region is valid only for CreateBucketAction.
|
||||||
var region string
|
var region string
|
||||||
@@ -377,7 +392,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
|
|||||||
// To extract region from XML in request body, get copy of request body.
|
// To extract region from XML in request body, get copy of request body.
|
||||||
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
authZLogIf(ctx, err, logger.ErrorKind)
|
||||||
return ErrMalformedXML
|
return ErrMalformedXML
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,7 +684,7 @@ func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool,
|
|||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypePresigned, authTypeSigned:
|
case authTypePresigned, authTypeSigned:
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
|
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return cred, owner, s3Err
|
||||||
}
|
}
|
||||||
@@ -730,7 +745,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
switch atype {
|
switch atype {
|
||||||
case authTypeUnknown:
|
case authTypeUnknown:
|
||||||
return ErrSignatureVersionNotSupported
|
return ErrSignatureVersionNotSupported
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nullReader struct{}
|
type nullReader struct{}
|
||||||
@@ -237,7 +237,7 @@ func TestIsRequestPresignedSignatureV2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection.
|
// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature version v4 detection.
|
||||||
func TestIsRequestPresignedSignatureV4(t *testing.T) {
|
func TestIsRequestPresignedSignatureV4(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
inputQueryKey string
|
inputQueryKey string
|
||||||
@@ -403,7 +403,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
|
|
||||||
// Validates all testcases.
|
// Validates all testcases.
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3)
|
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region(), serviceS3)
|
||||||
if s3Error != testCase.s3Error {
|
if s3Error != testCase.s3Error {
|
||||||
if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
||||||
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
|
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
|
||||||
@@ -443,7 +443,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region()); s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// healTask represents what to heal along with options
|
// healTask represents what to heal along with options
|
||||||
@@ -101,17 +100,17 @@ func waitForLowHTTPReq() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
||||||
|
bgSeq := newBgHealSequence()
|
||||||
// Run the background healer
|
// Run the background healer
|
||||||
globalBackgroundHealRoutine = newHealRoutine()
|
|
||||||
for i := 0; i < globalBackgroundHealRoutine.workers; i++ {
|
for i := 0; i < globalBackgroundHealRoutine.workers; i++ {
|
||||||
go globalBackgroundHealRoutine.AddWorker(ctx, objAPI)
|
go globalBackgroundHealRoutine.AddWorker(ctx, objAPI, bgSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
globalBackgroundHealState.LaunchNewHealSequence(newBgHealSequence(), objAPI)
|
globalBackgroundHealState.LaunchNewHealSequence(bgSeq, objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for heal requests and process them
|
// Wait for heal requests and process them
|
||||||
func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer, bgSeq *healSequence) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case task, ok := <-h.tasks:
|
case task, ok := <-h.tasks:
|
||||||
@@ -136,8 +135,18 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
if task.respCh != nil {
|
if task.respCh != nil {
|
||||||
task.respCh <- healResult{result: res, err: err}
|
task.respCh <- healResult{result: res, err: err}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when respCh is not set caller is not waiting but we
|
||||||
|
// update the relevant metrics for them
|
||||||
|
if bgSeq != nil {
|
||||||
|
if err == nil {
|
||||||
|
bgSeq.countHealed(res.Type)
|
||||||
|
} else {
|
||||||
|
bgSeq.countFailed(res.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -149,7 +158,7 @@ func newHealRoutine() *healRoutine {
|
|||||||
|
|
||||||
if envHealWorkers := env.Get("_MINIO_HEAL_WORKERS", ""); envHealWorkers != "" {
|
if envHealWorkers := env.Get("_MINIO_HEAL_WORKERS", ""); envHealWorkers != "" {
|
||||||
if numHealers, err := strconv.Atoi(envHealWorkers); err != nil {
|
if numHealers, err := strconv.Atoi(envHealWorkers); err != nil {
|
||||||
logger.LogIf(context.Background(), fmt.Errorf("invalid _MINIO_HEAL_WORKERS value: %w", err))
|
bugLogIf(context.Background(), fmt.Errorf("invalid _MINIO_HEAL_WORKERS value: %w", err))
|
||||||
} else {
|
} else {
|
||||||
workers = numHealers
|
workers = numHealers
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -87,6 +86,8 @@ type healingTracker struct {
|
|||||||
// ID of the current healing operation
|
// ID of the current healing operation
|
||||||
HealID string
|
HealID string
|
||||||
|
|
||||||
|
ItemsSkipped uint64
|
||||||
|
BytesSkipped uint64
|
||||||
// Add future tracking capabilities
|
// Add future tracking capabilities
|
||||||
// Be sure that they are included in toHealingDisk
|
// Be sure that they are included in toHealingDisk
|
||||||
}
|
}
|
||||||
@@ -175,14 +176,18 @@ func (h *healingTracker) setObject(object string) {
|
|||||||
h.Object = object
|
h.Object = object
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healingTracker) updateProgress(success bool, bytes uint64) {
|
func (h *healingTracker) updateProgress(success, skipped bool, bytes uint64) {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
if success {
|
switch {
|
||||||
|
case success:
|
||||||
h.ItemsHealed++
|
h.ItemsHealed++
|
||||||
h.BytesDone += bytes
|
h.BytesDone += bytes
|
||||||
} else {
|
case skipped:
|
||||||
|
h.ItemsSkipped++
|
||||||
|
h.BytesSkipped += bytes
|
||||||
|
default:
|
||||||
h.ItemsFailed++
|
h.ItemsFailed++
|
||||||
h.BytesFailed += bytes
|
h.BytesFailed += bytes
|
||||||
}
|
}
|
||||||
@@ -323,8 +328,10 @@ func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
|||||||
ObjectsTotalCount: h.ObjectsTotalCount,
|
ObjectsTotalCount: h.ObjectsTotalCount,
|
||||||
ObjectsTotalSize: h.ObjectsTotalSize,
|
ObjectsTotalSize: h.ObjectsTotalSize,
|
||||||
ItemsHealed: h.ItemsHealed,
|
ItemsHealed: h.ItemsHealed,
|
||||||
|
ItemsSkipped: h.ItemsSkipped,
|
||||||
ItemsFailed: h.ItemsFailed,
|
ItemsFailed: h.ItemsFailed,
|
||||||
BytesDone: h.BytesDone,
|
BytesDone: h.BytesDone,
|
||||||
|
BytesSkipped: h.BytesSkipped,
|
||||||
BytesFailed: h.BytesFailed,
|
BytesFailed: h.BytesFailed,
|
||||||
Bucket: h.Bucket,
|
Bucket: h.Bucket,
|
||||||
Object: h.Object,
|
Object: h.Object,
|
||||||
@@ -353,7 +360,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := globalLocalDrives
|
localDrives := cloneDrives(globalLocalDrives)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
for _, disk := range localDrives {
|
for _, disk := range localDrives {
|
||||||
_, err := disk.GetDiskID()
|
_, err := disk.GetDiskID()
|
||||||
@@ -376,25 +383,10 @@ 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 {
|
||||||
disk, format, _, err := connectEndpoint(endpoint)
|
poolIdx, setIdx := endpoint.PoolIdx, endpoint.SetIdx
|
||||||
if err != nil {
|
disk := getStorageViaEndpoint(endpoint)
|
||||||
return fmt.Errorf("Error: %w, %s", err, endpoint)
|
if disk == nil {
|
||||||
}
|
return fmt.Errorf("Unexpected error disk must be initialized by now after formatting: %s", endpoint)
|
||||||
defer disk.Close()
|
|
||||||
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
|
||||||
if poolIdx < 0 {
|
|
||||||
return fmt.Errorf("unexpected pool index (%d) found for %s", poolIdx, disk.Endpoint())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the set index where the current endpoint belongs
|
|
||||||
z.serverPools[poolIdx].erasureDisksMu.RLock()
|
|
||||||
setIdx, _, err := findDiskIndex(z.serverPools[poolIdx].format, format)
|
|
||||||
z.serverPools[poolIdx].erasureDisksMu.RUnlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if setIdx < 0 {
|
|
||||||
return fmt.Errorf("unexpected set index (%d) found for %s", setIdx, disk.Endpoint())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent parallel erasure set healing
|
// Prevent parallel erasure set healing
|
||||||
@@ -416,11 +408,11 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
if errors.Is(err, errFileNotFound) {
|
if errors.Is(err, errFileNotFound) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to load healing tracker on '%s': %w, re-initializing..", disk, err))
|
healingLogIf(ctx, fmt.Errorf("Unable to load healing tracker on '%s': %w, re-initializing..", disk, err))
|
||||||
tracker = initHealingTracker(disk, mustGetUUID())
|
tracker = initHealingTracker(disk, mustGetUUID())
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("Healing drive '%s' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint))
|
healingLogEvent(ctx, "Healing drive '%s' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint)
|
||||||
|
|
||||||
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
|
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
|
||||||
// Buckets data are dispersed in multiple pools/sets, make
|
// Buckets data are dispersed in multiple pools/sets, make
|
||||||
@@ -440,10 +432,6 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
return buckets[i].Created.After(buckets[j].Created)
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
})
|
})
|
||||||
|
|
||||||
if serverDebugLog {
|
|
||||||
logger.Info("Healing drive '%v' on %s pool, belonging to %s erasure set", disk, humanize.Ordinal(poolIdx+1), humanize.Ordinal(setIdx+1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load bucket totals
|
// 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 {
|
||||||
@@ -463,11 +451,7 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tracker.ItemsFailed > 0 {
|
healingLogEvent(ctx, "Healing of drive '%s' is finished (healed: %d, skipped: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
|
||||||
logger.Info("Healing of drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
|
||||||
} else {
|
|
||||||
logger.Info("Healing of drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tracker.QueuedBuckets) > 0 {
|
if len(tracker.QueuedBuckets) > 0 {
|
||||||
return fmt.Errorf("not all buckets were healed: %v", tracker.QueuedBuckets)
|
return fmt.Errorf("not all buckets were healed: %v", tracker.QueuedBuckets)
|
||||||
@@ -475,11 +459,11 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
|
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
tracker.printTo(os.Stdout)
|
tracker.printTo(os.Stdout)
|
||||||
logger.Info("\n")
|
fmt.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if tracker.HealID == "" { // HealID was empty only before Feb 2023
|
if tracker.HealID == "" { // HealID was empty only before Feb 2023
|
||||||
logger.LogIf(ctx, tracker.delete(ctx))
|
bugLogIf(ctx, tracker.delete(ctx))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +481,7 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
t, err := loadHealingTracker(ctx, disk)
|
t, err := loadHealingTracker(ctx, disk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errFileNotFound) {
|
if !errors.Is(err, errFileNotFound) {
|
||||||
logger.LogIf(ctx, err)
|
healingLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -532,7 +516,7 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
|
|||||||
// Reformat disks immediately
|
// Reformat disks immediately
|
||||||
_, err := z.HealFormat(context.Background(), false)
|
_, err := z.HealFormat(context.Background(), false)
|
||||||
if err != nil && !errors.Is(err, errNoHealRequired) {
|
if err != nil && !errors.Is(err, errNoHealRequired) {
|
||||||
logger.LogIf(ctx, err)
|
healingLogIf(ctx, err)
|
||||||
// Reset for next interval.
|
// Reset for next interval.
|
||||||
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -188,6 +188,18 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "HealID")
|
err = msgp.WrapError(err, "HealID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "ItemsSkipped":
|
||||||
|
z.ItemsSkipped, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsSkipped")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "BytesSkipped":
|
||||||
|
z.BytesSkipped, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesSkipped")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -201,9 +213,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 23
|
// map header, size 25
|
||||||
// write "ID"
|
// write "ID"
|
||||||
err = en.Append(0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
|
err = en.Append(0xde, 0x0, 0x19, 0xa2, 0x49, 0x44)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -446,15 +458,35 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "HealID")
|
err = msgp.WrapError(err, "HealID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "ItemsSkipped"
|
||||||
|
err = en.Append(0xac, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ItemsSkipped)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsSkipped")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "BytesSkipped"
|
||||||
|
err = en.Append(0xac, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.BytesSkipped)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesSkipped")
|
||||||
|
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 23
|
// map header, size 25
|
||||||
// string "ID"
|
// string "ID"
|
||||||
o = append(o, 0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
|
o = append(o, 0xde, 0x0, 0x19, 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)
|
||||||
@@ -528,6 +560,12 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "HealID"
|
// string "HealID"
|
||||||
o = append(o, 0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
|
o = append(o, 0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
|
||||||
o = msgp.AppendString(o, z.HealID)
|
o = msgp.AppendString(o, z.HealID)
|
||||||
|
// string "ItemsSkipped"
|
||||||
|
o = append(o, 0xac, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ItemsSkipped)
|
||||||
|
// string "BytesSkipped"
|
||||||
|
o = append(o, 0xac, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.BytesSkipped)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,6 +751,18 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "HealID")
|
err = msgp.WrapError(err, "HealID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "ItemsSkipped":
|
||||||
|
z.ItemsSkipped, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsSkipped")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "BytesSkipped":
|
||||||
|
z.BytesSkipped, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesSkipped")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -735,6 +785,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)
|
s += 7 + msgp.StringPrefixSize + len(z.HealID) + 13 + msgp.Uint64Size + 13 + msgp.Uint64Size
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-31
@@ -33,10 +33,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/versioning"
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"github.com/minio/pkg/v2/wildcard"
|
"github.com/minio/pkg/v3/workers"
|
||||||
"github.com/minio/pkg/v2/workers"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -156,7 +155,7 @@ func (ef BatchJobExpireFilter) Matches(obj ObjectInfo, now time.Time) bool {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// we should never come here, Validate should have caught this.
|
// we should never come here, Validate should have caught this.
|
||||||
logger.LogOnceIf(context.Background(), fmt.Errorf("invalid filter type: %s", ef.Type), ef.Type)
|
batchLogOnceIf(context.Background(), fmt.Errorf("invalid filter type: %s", ef.Type), ef.Type)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +320,7 @@ func (r BatchJobExpire) Notify(ctx context.Context, body io.Reader) error {
|
|||||||
req.Header.Set("Authorization", r.NotificationCfg.Token)
|
req.Header.Set("Authorization", r.NotificationCfg.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
clnt := http.Client{Transport: getRemoteInstanceTransport}
|
clnt := http.Client{Transport: getRemoteInstanceTransport()}
|
||||||
resp, err := clnt.Do(req)
|
resp, err := clnt.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -427,12 +426,13 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
stopFn := globalBatchJobsMetrics.trace(batchJobMetricExpire, ri.JobID, attempts)
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricExpire, ri.JobID, attempts)
|
||||||
_, err := api.DeleteObject(ctx, exp.Bucket, exp.Name, ObjectOptions{
|
_, err := api.DeleteObject(ctx, exp.Bucket, encodeDirObject(exp.Name), ObjectOptions{
|
||||||
DeletePrefix: true,
|
DeletePrefix: true,
|
||||||
|
DeletePrefixObject: true, // use prefix delete on exact object (this is an optimization to avoid fan-out calls)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stopFn(exp, err)
|
stopFn(exp, err)
|
||||||
logger.LogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", toExpire[i].Bucket, toExpire[i].Name, toExpire[i].VersionID, err, attempts))
|
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", toExpire[i].Bucket, toExpire[i].Name, toExpire[i].VersionID, err, attempts))
|
||||||
} else {
|
} else {
|
||||||
stopFn(exp, err)
|
stopFn(exp, err)
|
||||||
success = true
|
success = true
|
||||||
@@ -470,19 +470,19 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
|
|||||||
for i, err := range errs {
|
for i, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stopFn(toDelCopy[i], err)
|
stopFn(toDelCopy[i], err)
|
||||||
logger.LogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", ri.Bucket, toDelCopy[i].ObjectName, toDelCopy[i].VersionID, err, attempts))
|
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", ri.Bucket, toDelCopy[i].ObjectName, toDelCopy[i].VersionID, err, attempts))
|
||||||
failed++
|
failed++
|
||||||
if attempts == retryAttempts { // all retry attempts failed, record failure
|
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
||||||
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
ri.trackCurrentBucketObject(r.Bucket, *oi, false, attempts)
|
||||||
ri.trackCurrentBucketObject(r.Bucket, *oi, false)
|
}
|
||||||
}
|
if attempts != retryAttempts {
|
||||||
} else {
|
// retry
|
||||||
toDel = append(toDel, toDelCopy[i])
|
toDel = append(toDel, toDelCopy[i])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stopFn(toDelCopy[i], nil)
|
stopFn(toDelCopy[i], nil)
|
||||||
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
||||||
ri.trackCurrentBucketObject(r.Bucket, *oi, true)
|
ri.trackCurrentBucketObject(r.Bucket, *oi, true, attempts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,7 +537,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
results := make(chan ObjectInfo, workerSize)
|
results := make(chan itemOrErr[ObjectInfo], workerSize)
|
||||||
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
||||||
Marker: lastObject,
|
Marker: lastObject,
|
||||||
LatestOnly: false, // we need to visit all versions of the object to implement purge: retainVersions
|
LatestOnly: false, // we need to visit all versions of the object to implement purge: retainVersions
|
||||||
@@ -556,23 +556,27 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
select {
|
select {
|
||||||
case <-saveTicker.C:
|
case <-saveTicker.C:
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// persist in-memory state immediately before exiting due to context cancellation.
|
// persist in-memory state immediately before exiting due to context cancellation.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-saverQuitCh:
|
case <-saverQuitCh:
|
||||||
// persist in-memory state immediately to disk.
|
// persist in-memory state immediately to disk.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
expireCh := make(chan []expireObjInfo, workerSize)
|
expireCh := make(chan []expireObjInfo, workerSize)
|
||||||
go batchObjsForDelete(ctx, r, ri, job, api, wk, expireCh)
|
expireDoneCh := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(expireDoneCh)
|
||||||
|
batchObjsForDelete(ctx, r, ri, job, api, wk, expireCh)
|
||||||
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
prevObj ObjectInfo
|
prevObj ObjectInfo
|
||||||
@@ -580,11 +584,18 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
versionsCount int
|
versionsCount int
|
||||||
toDel []expireObjInfo
|
toDel []expireObjInfo
|
||||||
)
|
)
|
||||||
|
failed := true
|
||||||
for result := range results {
|
for result := range results {
|
||||||
|
if result.Err != nil {
|
||||||
|
failed = true
|
||||||
|
batchLogIf(ctx, result.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Apply filter to find the matching rule to apply expiry
|
// Apply filter to find the matching rule to apply expiry
|
||||||
// actions accordingly.
|
// actions accordingly.
|
||||||
// nolint:gocritic
|
// nolint:gocritic
|
||||||
if result.IsLatest {
|
if result.Item.IsLatest {
|
||||||
// send down filtered entries to be deleted using
|
// send down filtered entries to be deleted using
|
||||||
// DeleteObjects method
|
// DeleteObjects method
|
||||||
if len(toDel) > 10 { // batch up to 10 objects/versions to be expired simultaneously.
|
if len(toDel) > 10 { // batch up to 10 objects/versions to be expired simultaneously.
|
||||||
@@ -605,7 +616,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
var match BatchJobExpireFilter
|
var match BatchJobExpireFilter
|
||||||
var found bool
|
var found bool
|
||||||
for _, rule := range r.Rules {
|
for _, rule := range r.Rules {
|
||||||
if rule.Matches(result, now) {
|
if rule.Matches(result.Item, now) {
|
||||||
match = rule
|
match = rule
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
@@ -615,18 +626,18 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prevObj = result
|
prevObj = result.Item
|
||||||
matchedFilter = match
|
matchedFilter = match
|
||||||
versionsCount = 1
|
versionsCount = 1
|
||||||
// Include the latest version
|
// Include the latest version
|
||||||
if matchedFilter.Purge.RetainVersions == 0 {
|
if matchedFilter.Purge.RetainVersions == 0 {
|
||||||
toDel = append(toDel, expireObjInfo{
|
toDel = append(toDel, expireObjInfo{
|
||||||
ObjectInfo: result,
|
ObjectInfo: result.Item,
|
||||||
ExpireAll: true,
|
ExpireAll: true,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if prevObj.Name == result.Name {
|
} else if prevObj.Name == result.Item.Name {
|
||||||
if matchedFilter.Purge.RetainVersions == 0 {
|
if matchedFilter.Purge.RetainVersions == 0 {
|
||||||
continue // including latest version in toDel suffices, skipping other versions
|
continue // including latest version in toDel suffices, skipping other versions
|
||||||
}
|
}
|
||||||
@@ -639,7 +650,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
continue // retain versions
|
continue // retain versions
|
||||||
}
|
}
|
||||||
toDel = append(toDel, expireObjInfo{
|
toDel = append(toDel, expireObjInfo{
|
||||||
ObjectInfo: result,
|
ObjectInfo: result.Item,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Send any remaining objects downstream
|
// Send any remaining objects downstream
|
||||||
@@ -651,10 +662,11 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
}
|
}
|
||||||
xioutil.SafeClose(expireCh)
|
xioutil.SafeClose(expireCh)
|
||||||
|
|
||||||
wk.Wait() // waits for all expire goroutines to complete
|
<-expireDoneCh // waits for the expire goroutine to complete
|
||||||
|
wk.Wait() // waits for all expire workers to retire
|
||||||
|
|
||||||
ri.Complete = ri.ObjectsFailed == 0
|
ri.Complete = !failed && ri.ObjectsFailed == 0
|
||||||
ri.Failed = ri.ObjectsFailed > 0
|
ri.Failed = failed || ri.ObjectsFailed > 0
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
|
||||||
// Close the saverQuitCh - this also triggers saving in-memory state
|
// Close the saverQuitCh - this also triggers saving in-memory state
|
||||||
@@ -664,7 +676,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
// Notify expire jobs final status to the configured endpoint
|
// Notify expire jobs final status to the configured endpoint
|
||||||
buf, _ := json.Marshal(ri)
|
buf, _ := json.Marshal(ri)
|
||||||
if err := r.Notify(context.Background(), bytes.NewReader(buf)); err != nil {
|
if err := r.Notify(context.Background(), bytes.NewReader(buf)); err != nil {
|
||||||
logger.LogIf(context.Background(), fmt.Errorf("unable to notify %v", err))
|
batchLogIf(context.Background(), fmt.Errorf("unable to notify %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+159
-121
@@ -48,11 +48,10 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/pkg/v3/console"
|
||||||
"github.com/minio/pkg/v2/console"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/workers"
|
||||||
"github.com/minio/pkg/v2/workers"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,7 +62,6 @@ type BatchJobRequest struct {
|
|||||||
ID string `yaml:"-" json:"name"`
|
ID string `yaml:"-" json:"name"`
|
||||||
User string `yaml:"-" json:"user"`
|
User string `yaml:"-" json:"user"`
|
||||||
Started time.Time `yaml:"-" json:"started"`
|
Started time.Time `yaml:"-" json:"started"`
|
||||||
Location string `yaml:"-" json:"location"`
|
|
||||||
Replicate *BatchJobReplicateV1 `yaml:"replicate" json:"replicate"`
|
Replicate *BatchJobReplicateV1 `yaml:"replicate" json:"replicate"`
|
||||||
KeyRotate *BatchJobKeyRotateV1 `yaml:"keyrotate" json:"keyrotate"`
|
KeyRotate *BatchJobKeyRotateV1 `yaml:"keyrotate" json:"keyrotate"`
|
||||||
Expire *BatchJobExpire `yaml:"expire" json:"expire"`
|
Expire *BatchJobExpire `yaml:"expire" json:"expire"`
|
||||||
@@ -93,7 +91,7 @@ func notifyEndpoint(ctx context.Context, ri *batchJobInfo, endpoint, token strin
|
|||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
clnt := http.Client{Transport: getRemoteInstanceTransport}
|
clnt := http.Client{Transport: getRemoteInstanceTransport()}
|
||||||
resp, err := clnt.Do(req)
|
resp, err := clnt.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -207,7 +205,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
|||||||
if aerr == nil {
|
if aerr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx,
|
batchLogIf(ctx,
|
||||||
fmt.Errorf("trying %s: Unable to cleanup failed multipart replication %s on remote %s/%s: %w - this may consume space on remote cluster",
|
fmt.Errorf("trying %s: Unable to cleanup failed multipart replication %s on remote %s/%s: %w - this may consume space on remote cluster",
|
||||||
humanize.Ordinal(attempts), res.UploadID, tgtBucket, tgtObject, aerr))
|
humanize.Ordinal(attempts), res.UploadID, tgtBucket, tgtObject, aerr))
|
||||||
attempts++
|
attempts++
|
||||||
@@ -278,7 +276,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
}
|
}
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
isTags := len(r.Flags.Filter.Tags) != 0
|
hasTags := len(r.Flags.Filter.Tags) != 0
|
||||||
isMetadata := len(r.Flags.Filter.Metadata) != 0
|
isMetadata := len(r.Flags.Filter.Metadata) != 0
|
||||||
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
|
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
|
||||||
|
|
||||||
@@ -303,7 +301,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTags {
|
if hasTags {
|
||||||
// Only parse object tags if tags filter is specified.
|
// Only parse object tags if tags filter is specified.
|
||||||
tagMap := map[string]string{}
|
tagMap := map[string]string{}
|
||||||
tagStr := oi.UserTags
|
tagStr := oi.UserTags
|
||||||
@@ -352,7 +350,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
c, err := miniogo.New(u.Host, &miniogo.Options{
|
c, err := miniogo.New(u.Host, &miniogo.Options{
|
||||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||||
Secure: u.Scheme == "https",
|
Secure: u.Scheme == "https",
|
||||||
Transport: getRemoteInstanceTransport,
|
Transport: getRemoteInstanceTransport(),
|
||||||
BucketLookup: lookupStyle(r.Source.Path),
|
BucketLookup: lookupStyle(r.Source.Path),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -403,19 +401,19 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
} else {
|
} else {
|
||||||
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
|
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
|
||||||
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isTags {
|
if hasTags {
|
||||||
tags, err := c.GetObjectTagging(ctx, r.Source.Bucket, obj.Key, minio.GetObjectTaggingOptions{})
|
tags, err := c.GetObjectTagging(ctx, r.Source.Bucket, obj.Key, minio.GetObjectTaggingOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
oi.UserTags = tags.String()
|
oi.UserTags = tags.String()
|
||||||
} else {
|
} else {
|
||||||
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
|
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
|
||||||
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -444,15 +442,15 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
stopFn(oi, err)
|
stopFn(oi, err)
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
success = false
|
success = false
|
||||||
} else {
|
} else {
|
||||||
stopFn(oi, nil)
|
stopFn(oi, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Target.Bucket, oi, success)
|
ri.trackCurrentBucketObject(r.Target.Bucket, oi, success, attempts)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
|
||||||
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
||||||
time.Sleep(wait)
|
time.Sleep(wait)
|
||||||
@@ -467,10 +465,10 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
|
||||||
if err := r.Notify(ctx, ri); err != nil {
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
batchLogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
@@ -554,7 +552,7 @@ func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLa
|
|||||||
VersionID: entry.VersionID,
|
VersionID: entry.VersionID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,7 +571,7 @@ func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLa
|
|||||||
|
|
||||||
opts, err := batchReplicationOpts(ctx, "", gr.ObjInfo)
|
opts, err := batchReplicationOpts(ctx, "", gr.ObjInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,6 +690,7 @@ type batchJobInfo struct {
|
|||||||
StartTime time.Time `json:"startTime" msg:"st"`
|
StartTime time.Time `json:"startTime" msg:"st"`
|
||||||
LastUpdate time.Time `json:"lastUpdate" msg:"lu"`
|
LastUpdate time.Time `json:"lastUpdate" msg:"lu"`
|
||||||
RetryAttempts int `json:"retryAttempts" msg:"ra"`
|
RetryAttempts int `json:"retryAttempts" msg:"ra"`
|
||||||
|
Attempts int `json:"attempts" msg:"at"`
|
||||||
|
|
||||||
Complete bool `json:"complete" msg:"cmp"`
|
Complete bool `json:"complete" msg:"cmp"`
|
||||||
Failed bool `json:"failed" msg:"fld"`
|
Failed bool `json:"failed" msg:"fld"`
|
||||||
@@ -710,38 +709,52 @@ type batchJobInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
batchReplName = "batch-replicate.bin"
|
batchReplName = "batch-replicate.bin"
|
||||||
batchReplFormat = 1
|
batchReplFormat = 1
|
||||||
batchReplVersionV1 = 1
|
batchReplVersionV1 = 1
|
||||||
batchReplVersion = batchReplVersionV1
|
batchReplVersion = batchReplVersionV1
|
||||||
batchJobName = "job.bin"
|
batchJobName = "job.bin"
|
||||||
batchJobPrefix = "batch-jobs"
|
batchJobPrefix = "batch-jobs"
|
||||||
|
batchJobReportsPrefix = batchJobPrefix + "/reports"
|
||||||
|
|
||||||
batchReplJobAPIVersion = "v1"
|
batchReplJobAPIVersion = "v1"
|
||||||
batchReplJobDefaultRetries = 3
|
batchReplJobDefaultRetries = 3
|
||||||
batchReplJobDefaultRetryDelay = 250 * time.Millisecond
|
batchReplJobDefaultRetryDelay = 250 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
|
func getJobReportPath(job BatchJobRequest) string {
|
||||||
var fileName string
|
var fileName string
|
||||||
var format, version uint16
|
|
||||||
switch {
|
switch {
|
||||||
case job.Replicate != nil:
|
case job.Replicate != nil:
|
||||||
fileName = batchReplName
|
fileName = batchReplName
|
||||||
|
case job.KeyRotate != nil:
|
||||||
|
fileName = batchKeyRotationName
|
||||||
|
case job.Expire != nil:
|
||||||
|
fileName = batchExpireName
|
||||||
|
}
|
||||||
|
return pathJoin(batchJobReportsPrefix, job.ID, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJobPath(job BatchJobRequest) string {
|
||||||
|
return pathJoin(batchJobPrefix, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
|
||||||
|
var format, version uint16
|
||||||
|
switch {
|
||||||
|
case job.Replicate != nil:
|
||||||
version = batchReplVersionV1
|
version = batchReplVersionV1
|
||||||
format = batchReplFormat
|
format = batchReplFormat
|
||||||
case job.KeyRotate != nil:
|
case job.KeyRotate != nil:
|
||||||
fileName = batchKeyRotationName
|
|
||||||
version = batchKeyRotateVersionV1
|
version = batchKeyRotateVersionV1
|
||||||
format = batchKeyRotationFormat
|
format = batchKeyRotationFormat
|
||||||
case job.Expire != nil:
|
case job.Expire != nil:
|
||||||
fileName = batchExpireName
|
|
||||||
version = batchExpireVersionV1
|
version = batchExpireVersionV1
|
||||||
format = batchExpireFormat
|
format = batchExpireFormat
|
||||||
default:
|
default:
|
||||||
return errors.New("no supported batch job request specified")
|
return errors.New("no supported batch job request specified")
|
||||||
}
|
}
|
||||||
data, err := readConfig(ctx, api, pathJoin(job.Location, fileName))
|
data, err := readConfig(ctx, api, getJobReportPath(job))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) || isErrObjectNotFound(err) {
|
if errors.Is(err, errConfigNotFound) || isErrObjectNotFound(err) {
|
||||||
ri.Version = int(version)
|
ri.Version = int(version)
|
||||||
@@ -821,13 +834,15 @@ func (ri *batchJobInfo) clone() *batchJobInfo {
|
|||||||
ObjectsFailed: ri.ObjectsFailed,
|
ObjectsFailed: ri.ObjectsFailed,
|
||||||
BytesTransferred: ri.BytesTransferred,
|
BytesTransferred: ri.BytesTransferred,
|
||||||
BytesFailed: ri.BytesFailed,
|
BytesFailed: ri.BytesFailed,
|
||||||
|
Attempts: ri.Attempts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) {
|
func (ri *batchJobInfo) countItem(size int64, dmarker, success bool, attempt int) {
|
||||||
if ri == nil {
|
if ri == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ri.Attempts++
|
||||||
if success {
|
if success {
|
||||||
if dmarker {
|
if dmarker {
|
||||||
ri.DeleteMarkers++
|
ri.DeleteMarkers++
|
||||||
@@ -835,7 +850,19 @@ func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) {
|
|||||||
ri.Objects++
|
ri.Objects++
|
||||||
ri.BytesTransferred += size
|
ri.BytesTransferred += size
|
||||||
}
|
}
|
||||||
|
if attempt > 1 {
|
||||||
|
if dmarker {
|
||||||
|
ri.DeleteMarkersFailed--
|
||||||
|
} else {
|
||||||
|
ri.ObjectsFailed--
|
||||||
|
ri.BytesFailed += size
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if attempt > 1 {
|
||||||
|
// Only count first attempt
|
||||||
|
return
|
||||||
|
}
|
||||||
if dmarker {
|
if dmarker {
|
||||||
ri.DeleteMarkersFailed++
|
ri.DeleteMarkersFailed++
|
||||||
} else {
|
} else {
|
||||||
@@ -852,8 +879,8 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
|
|||||||
now := UTCNow()
|
now := UTCNow()
|
||||||
ri.mu.Lock()
|
ri.mu.Lock()
|
||||||
var (
|
var (
|
||||||
format, version uint16
|
format, version uint16
|
||||||
jobTyp, fileName string
|
jobTyp string
|
||||||
)
|
)
|
||||||
|
|
||||||
if now.Sub(ri.LastUpdate) >= duration {
|
if now.Sub(ri.LastUpdate) >= duration {
|
||||||
@@ -862,19 +889,16 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
|
|||||||
format = batchReplFormat
|
format = batchReplFormat
|
||||||
version = batchReplVersion
|
version = batchReplVersion
|
||||||
jobTyp = string(job.Type())
|
jobTyp = string(job.Type())
|
||||||
fileName = batchReplName
|
|
||||||
ri.Version = batchReplVersionV1
|
ri.Version = batchReplVersionV1
|
||||||
case madmin.BatchJobKeyRotate:
|
case madmin.BatchJobKeyRotate:
|
||||||
format = batchKeyRotationFormat
|
format = batchKeyRotationFormat
|
||||||
version = batchKeyRotateVersion
|
version = batchKeyRotateVersion
|
||||||
jobTyp = string(job.Type())
|
jobTyp = string(job.Type())
|
||||||
fileName = batchKeyRotationName
|
|
||||||
ri.Version = batchKeyRotateVersionV1
|
ri.Version = batchKeyRotateVersionV1
|
||||||
case madmin.BatchJobExpire:
|
case madmin.BatchJobExpire:
|
||||||
format = batchExpireFormat
|
format = batchExpireFormat
|
||||||
version = batchExpireVersion
|
version = batchExpireVersion
|
||||||
jobTyp = string(job.Type())
|
jobTyp = string(job.Type())
|
||||||
fileName = batchExpireName
|
|
||||||
ri.Version = batchExpireVersionV1
|
ri.Version = batchExpireVersionV1
|
||||||
default:
|
default:
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
@@ -895,7 +919,7 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return saveConfig(ctx, api, pathJoin(job.Location, fileName), buf)
|
return saveConfig(ctx, api, getJobReportPath(job), buf)
|
||||||
}
|
}
|
||||||
ri.mu.Unlock()
|
ri.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
@@ -912,7 +936,7 @@ func (ri *batchJobInfo) trackMultipleObjectVersions(bucket string, info ObjectIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo, success bool) {
|
func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo, success bool, attempt int) {
|
||||||
if ri == nil {
|
if ri == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -922,7 +946,22 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
|
|||||||
|
|
||||||
ri.Bucket = bucket
|
ri.Bucket = bucket
|
||||||
ri.Object = info.Name
|
ri.Object = info.Name
|
||||||
ri.countItem(info.Size, info.DeleteMarker, success)
|
ri.countItem(info.Size, info.DeleteMarker, success, attempt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ri *batchJobInfo) trackCurrentBucketBatch(bucket string, batch []ObjectInfo) {
|
||||||
|
if ri == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ri.mu.Lock()
|
||||||
|
defer ri.mu.Unlock()
|
||||||
|
|
||||||
|
ri.Bucket = bucket
|
||||||
|
for i := range batch {
|
||||||
|
ri.Object = batch[i].Name
|
||||||
|
ri.countItem(batch[i].Size, batch[i].DeleteMarker, true, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start start the batch replication job, resumes if there was a pending job via "job.ID"
|
// Start start the batch replication job, resumes if there was a pending job via "job.ID"
|
||||||
@@ -1023,7 +1062,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
c, err := miniogo.NewCore(u.Host, &miniogo.Options{
|
c, err := miniogo.NewCore(u.Host, &miniogo.Options{
|
||||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||||
Secure: u.Scheme == "https",
|
Secure: u.Scheme == "https",
|
||||||
Transport: getRemoteInstanceTransport,
|
Transport: getRemoteInstanceTransport(),
|
||||||
BucketLookup: lookupStyle(r.Target.Path),
|
BucketLookup: lookupStyle(r.Target.Path),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1033,64 +1072,59 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
walkCh = make(chan ObjectInfo, 100)
|
walkCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||||
slowCh = make(chan ObjectInfo, 100)
|
slowCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||||
)
|
)
|
||||||
|
|
||||||
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
||||||
go func() {
|
go func() {
|
||||||
defer xioutil.SafeClose(slowCh)
|
|
||||||
|
|
||||||
// Snowball currently needs the high level minio-go Client, not the Core one
|
// Snowball currently needs the high level minio-go Client, not the Core one
|
||||||
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
||||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||||
Secure: u.Scheme == "https",
|
Secure: u.Scheme == "https",
|
||||||
Transport: getRemoteInstanceTransport,
|
Transport: getRemoteInstanceTransport(),
|
||||||
BucketLookup: lookupStyle(r.Target.Path),
|
BucketLookup: lookupStyle(r.Target.Path),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Already validated before arriving here
|
// Already validated before arriving here
|
||||||
smallerThan, _ := humanize.ParseBytes(*r.Source.Snowball.SmallerThan)
|
smallerThan, _ := humanize.ParseBytes(*r.Source.Snowball.SmallerThan)
|
||||||
|
|
||||||
var (
|
batch := make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
||||||
obj = ObjectInfo{}
|
writeFn := func(batch []ObjectInfo) {
|
||||||
batch = make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
if len(batch) > 0 {
|
||||||
valid = true
|
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
||||||
)
|
batchLogIf(ctx, err)
|
||||||
|
for _, b := range batch {
|
||||||
for valid {
|
slowCh <- itemOrErr[ObjectInfo]{Item: b}
|
||||||
obj, valid = <-walkCh
|
}
|
||||||
|
} else {
|
||||||
if !valid {
|
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
|
||||||
goto write
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
// persist in-memory state to disk after every 10secs.
|
||||||
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if obj.DeleteMarker || !obj.VersionPurgeStatus.Empty() || obj.Size >= int64(smallerThan) {
|
for obj := range walkCh {
|
||||||
|
if obj.Item.DeleteMarker || !obj.Item.VersionPurgeStatus.Empty() || obj.Item.Size >= int64(smallerThan) {
|
||||||
slowCh <- obj
|
slowCh <- obj
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
batch = append(batch, obj)
|
batch = append(batch, obj.Item)
|
||||||
|
|
||||||
if len(batch) < *r.Source.Snowball.Batch {
|
if len(batch) < *r.Source.Snowball.Batch {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
writeFn(batch)
|
||||||
write:
|
batch = batch[:0]
|
||||||
if len(batch) > 0 {
|
|
||||||
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
for _, b := range batch {
|
|
||||||
slowCh <- b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
batch = batch[:0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
writeFn(batch)
|
||||||
|
xioutil.SafeClose(slowCh)
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
slowCh = walkCh
|
slowCh = walkCh
|
||||||
@@ -1134,8 +1168,13 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
prevObj := ""
|
prevObj := ""
|
||||||
|
|
||||||
skipReplicate := false
|
skipReplicate := false
|
||||||
for result := range slowCh {
|
for res := range slowCh {
|
||||||
result := result
|
if res.Err != nil {
|
||||||
|
ri.Failed = true
|
||||||
|
batchLogIf(ctx, res.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result := res.Item
|
||||||
if result.Name != prevObj {
|
if result.Name != prevObj {
|
||||||
prevObj = result.Name
|
prevObj = result.Name
|
||||||
skipReplicate = result.DeleteMarker && s3Type
|
skipReplicate = result.DeleteMarker && s3Type
|
||||||
@@ -1159,15 +1198,15 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
stopFn(result, err)
|
stopFn(result, err)
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
success = false
|
success = false
|
||||||
} else {
|
} else {
|
||||||
stopFn(result, nil)
|
stopFn(result, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
|
ri.trackCurrentBucketObject(r.Source.Bucket, result, success, attempts)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
|
||||||
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
||||||
time.Sleep(wait)
|
time.Sleep(wait)
|
||||||
@@ -1182,10 +1221,10 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
|
||||||
if err := r.Notify(ctx, ri); err != nil {
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
batchLogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
@@ -1212,6 +1251,7 @@ type batchReplicationJobError struct {
|
|||||||
Code string
|
Code string
|
||||||
Description string
|
Description string
|
||||||
HTTPStatusCode int
|
HTTPStatusCode int
|
||||||
|
ObjectSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e batchReplicationJobError) Error() string {
|
func (e batchReplicationJobError) Error() string {
|
||||||
@@ -1228,9 +1268,18 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Source.Bucket == "" {
|
if r.Source.Endpoint != "" && r.Target.Endpoint != "" {
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.Source.Creds.Empty() && r.Target.Creds.Empty() {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Source.Bucket == "" || r.Target.Bucket == "" {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
var isRemoteToLocal bool
|
var isRemoteToLocal bool
|
||||||
localBkt := r.Source.Bucket
|
localBkt := r.Source.Bucket
|
||||||
if r.Source.Endpoint != "" {
|
if r.Source.Endpoint != "" {
|
||||||
@@ -1255,9 +1304,6 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
if err := r.Source.Snowball.Validate(); err != nil {
|
if err := r.Source.Snowball.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if r.Source.Creds.Empty() && r.Target.Creds.Empty() {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.Source.Creds.Empty() {
|
if !r.Source.Creds.Empty() {
|
||||||
if err := r.Source.Creds.Validate(); err != nil {
|
if err := r.Source.Creds.Validate(); err != nil {
|
||||||
@@ -1279,9 +1325,6 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
if r.Target.Endpoint != "" && !r.Target.Type.isMinio() && !r.Target.ValidPath() {
|
if r.Target.Endpoint != "" && !r.Target.Type.isMinio() && !r.Target.ValidPath() {
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
if r.Target.Bucket == "" {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.Target.Creds.Empty() {
|
if !r.Target.Creds.Empty() {
|
||||||
if err := r.Target.Creds.Validate(); err != nil {
|
if err := r.Target.Creds.Validate(); err != nil {
|
||||||
@@ -1289,10 +1332,6 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Source.Creds.Empty() && r.Target.Creds.Empty() {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.Target.Type.Validate(); err != nil {
|
if err := r.Target.Type.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1334,7 +1373,7 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
c, err := miniogo.NewCore(u.Host, &miniogo.Options{
|
c, err := miniogo.NewCore(u.Host, &miniogo.Options{
|
||||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||||
Secure: u.Scheme == "https",
|
Secure: u.Scheme == "https",
|
||||||
Transport: getRemoteInstanceTransport,
|
Transport: getRemoteInstanceTransport(),
|
||||||
BucketLookup: lookupStyle(pathStyle),
|
BucketLookup: lookupStyle(pathStyle),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1397,15 +1436,8 @@ func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j BatchJobRequest) delete(ctx context.Context, api ObjectLayer) {
|
func (j BatchJobRequest) delete(ctx context.Context, api ObjectLayer) {
|
||||||
switch {
|
deleteConfig(ctx, api, getJobReportPath(j))
|
||||||
case j.Replicate != nil:
|
deleteConfig(ctx, api, getJobPath(j))
|
||||||
deleteConfig(ctx, api, pathJoin(j.Location, batchReplName))
|
|
||||||
case j.KeyRotate != nil:
|
|
||||||
deleteConfig(ctx, api, pathJoin(j.Location, batchKeyRotationName))
|
|
||||||
case j.Expire != nil:
|
|
||||||
deleteConfig(ctx, api, pathJoin(j.Location, batchExpireName))
|
|
||||||
}
|
|
||||||
deleteConfig(ctx, api, j.Location)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error {
|
func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error {
|
||||||
@@ -1417,13 +1449,12 @@ func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
j.Location = pathJoin(batchJobPrefix, j.ID)
|
|
||||||
job, err := j.MarshalMsg(nil)
|
job, err := j.MarshalMsg(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveConfig(ctx, api, j.Location, job)
|
return saveConfig(ctx, api, getJobPath(*j), job)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *BatchJobRequest) load(ctx context.Context, api ObjectLayer, name string) error {
|
func (j *BatchJobRequest) load(ctx context.Context, api ObjectLayer, name string) error {
|
||||||
@@ -1473,7 +1504,7 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
|||||||
jobType = string(madmin.BatchJobReplicate)
|
jobType = string(madmin.BatchJobReplicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultCh := make(chan ObjectInfo)
|
resultCh := make(chan itemOrErr[ObjectInfo])
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -1485,10 +1516,14 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
listResult := madmin.ListBatchJobsResult{}
|
listResult := madmin.ListBatchJobsResult{}
|
||||||
for result := range resultCh {
|
for result := range resultCh {
|
||||||
|
if result.Err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, result.Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
req := &BatchJobRequest{}
|
req := &BatchJobRequest{}
|
||||||
if err := req.load(ctx, objectAPI, result.Name); err != nil {
|
if err := req.load(ctx, objectAPI, result.Item.Name); err != nil {
|
||||||
if !errors.Is(err, errNoSuchJob) {
|
if !errors.Is(err, errNoSuchJob) {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1504,7 +1539,7 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, json.NewEncoder(w).Encode(&listResult))
|
batchLogIf(ctx, json.NewEncoder(w).Encode(&listResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
var errNoSuchJob = errors.New("no such job")
|
var errNoSuchJob = errors.New("no such job")
|
||||||
@@ -1527,7 +1562,7 @@ func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Reques
|
|||||||
req := &BatchJobRequest{}
|
req := &BatchJobRequest{}
|
||||||
if err := req.load(ctx, objectAPI, pathJoin(batchJobPrefix, jobID)); err != nil {
|
if err := req.load(ctx, objectAPI, pathJoin(batchJobPrefix, jobID)); err != nil {
|
||||||
if !errors.Is(err, errNoSuchJob) {
|
if !errors.Is(err, errNoSuchJob) {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -1536,7 +1571,7 @@ func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
buf, err := yaml.Marshal(req)
|
buf, err := yaml.Marshal(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1651,8 +1686,7 @@ func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
j := BatchJobRequest{
|
j := BatchJobRequest{
|
||||||
ID: jobID,
|
ID: jobID,
|
||||||
Location: pathJoin(batchJobPrefix, jobID),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
j.delete(ctx, objectAPI)
|
j.delete(ctx, objectAPI)
|
||||||
@@ -1692,21 +1726,25 @@ func newBatchJobPool(ctx context.Context, o ObjectLayer, workers int) *BatchJobP
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *BatchJobPool) resume() {
|
func (j *BatchJobPool) resume() {
|
||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan itemOrErr[ObjectInfo], 100)
|
||||||
ctx, cancel := context.WithCancel(j.ctx)
|
ctx, cancel := context.WithCancel(j.ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobPrefix, results, WalkOptions{}); err != nil {
|
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobPrefix, results, WalkOptions{}); err != nil {
|
||||||
logger.LogIf(j.ctx, err)
|
batchLogIf(j.ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for result := range results {
|
for result := range results {
|
||||||
|
if result.Err != nil {
|
||||||
|
batchLogIf(j.ctx, result.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// ignore batch-replicate.bin and batch-rotate.bin entries
|
// ignore batch-replicate.bin and batch-rotate.bin entries
|
||||||
if strings.HasSuffix(result.Name, slashSeparator) {
|
if strings.HasSuffix(result.Item.Name, slashSeparator) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
req := &BatchJobRequest{}
|
req := &BatchJobRequest{}
|
||||||
if err := req.load(ctx, j.objLayer, result.Name); err != nil {
|
if err := req.load(ctx, j.objLayer, result.Item.Name); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, nodeIdx := parseRequestToken(req.ID)
|
_, nodeIdx := parseRequestToken(req.ID)
|
||||||
@@ -1715,7 +1753,7 @@ func (j *BatchJobPool) resume() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := j.queueJob(req); err != nil {
|
if err := j.queueJob(req); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1739,7 +1777,7 @@ func (j *BatchJobPool) AddWorker() {
|
|||||||
if job.Replicate.RemoteToLocal() {
|
if job.Replicate.RemoteToLocal() {
|
||||||
if err := job.Replicate.StartFromSource(job.ctx, j.objLayer, *job); err != nil {
|
if err := job.Replicate.StartFromSource(job.ctx, j.objLayer, *job); err != nil {
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(j.ctx, err)
|
batchLogIf(j.ctx, err)
|
||||||
j.canceler(job.ID, false)
|
j.canceler(job.ID, false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1748,7 +1786,7 @@ func (j *BatchJobPool) AddWorker() {
|
|||||||
} else {
|
} else {
|
||||||
if err := job.Replicate.Start(job.ctx, j.objLayer, *job); err != nil {
|
if err := job.Replicate.Start(job.ctx, j.objLayer, *job); err != nil {
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(j.ctx, err)
|
batchLogIf(j.ctx, err)
|
||||||
j.canceler(job.ID, false)
|
j.canceler(job.ID, false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1758,14 +1796,14 @@ func (j *BatchJobPool) AddWorker() {
|
|||||||
case job.KeyRotate != nil:
|
case job.KeyRotate != nil:
|
||||||
if err := job.KeyRotate.Start(job.ctx, j.objLayer, *job); err != nil {
|
if err := job.KeyRotate.Start(job.ctx, j.objLayer, *job); err != nil {
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(j.ctx, err)
|
batchLogIf(j.ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case job.Expire != nil:
|
case job.Expire != nil:
|
||||||
if err := job.Expire.Start(job.ctx, j.objLayer, *job); err != nil {
|
if err := job.Expire.Start(job.ctx, j.objLayer, *job); err != nil {
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(j.ctx, err)
|
batchLogIf(j.ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-35
@@ -42,12 +42,6 @@ func (z *BatchJobRequest) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Started")
|
err = msgp.WrapError(err, "Started")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Location":
|
|
||||||
z.Location, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Location")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Replicate":
|
case "Replicate":
|
||||||
if dc.IsNil() {
|
if dc.IsNil() {
|
||||||
err = dc.ReadNil()
|
err = dc.ReadNil()
|
||||||
@@ -115,9 +109,9 @@ func (z *BatchJobRequest) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 7
|
// map header, size 6
|
||||||
// write "ID"
|
// write "ID"
|
||||||
err = en.Append(0x87, 0xa2, 0x49, 0x44)
|
err = en.Append(0x86, 0xa2, 0x49, 0x44)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -146,16 +140,6 @@ func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Started")
|
err = msgp.WrapError(err, "Started")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Location"
|
|
||||||
err = en.Append(0xa8, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Location)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Location")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Replicate"
|
// write "Replicate"
|
||||||
err = en.Append(0xa9, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65)
|
err = en.Append(0xa9, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -213,9 +197,9 @@ func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *BatchJobRequest) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *BatchJobRequest) 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 6
|
||||||
// string "ID"
|
// string "ID"
|
||||||
o = append(o, 0x87, 0xa2, 0x49, 0x44)
|
o = append(o, 0x86, 0xa2, 0x49, 0x44)
|
||||||
o = msgp.AppendString(o, z.ID)
|
o = msgp.AppendString(o, z.ID)
|
||||||
// string "User"
|
// string "User"
|
||||||
o = append(o, 0xa4, 0x55, 0x73, 0x65, 0x72)
|
o = append(o, 0xa4, 0x55, 0x73, 0x65, 0x72)
|
||||||
@@ -223,9 +207,6 @@ func (z *BatchJobRequest) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "Started"
|
// string "Started"
|
||||||
o = append(o, 0xa7, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64)
|
o = append(o, 0xa7, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64)
|
||||||
o = msgp.AppendTime(o, z.Started)
|
o = msgp.AppendTime(o, z.Started)
|
||||||
// string "Location"
|
|
||||||
o = append(o, 0xa8, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e)
|
|
||||||
o = msgp.AppendString(o, z.Location)
|
|
||||||
// string "Replicate"
|
// string "Replicate"
|
||||||
o = append(o, 0xa9, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65)
|
o = append(o, 0xa9, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65)
|
||||||
if z.Replicate == nil {
|
if z.Replicate == nil {
|
||||||
@@ -298,12 +279,6 @@ func (z *BatchJobRequest) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Started")
|
err = msgp.WrapError(err, "Started")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Location":
|
|
||||||
z.Location, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Location")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Replicate":
|
case "Replicate":
|
||||||
if msgp.IsNil(bts) {
|
if msgp.IsNil(bts) {
|
||||||
bts, err = msgp.ReadNilBytes(bts)
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
@@ -369,7 +344,7 @@ func (z *BatchJobRequest) 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 *BatchJobRequest) Msgsize() (s int) {
|
func (z *BatchJobRequest) Msgsize() (s int) {
|
||||||
s = 1 + 3 + msgp.StringPrefixSize + len(z.ID) + 5 + msgp.StringPrefixSize + len(z.User) + 8 + msgp.TimeSize + 9 + msgp.StringPrefixSize + len(z.Location) + 10
|
s = 1 + 3 + msgp.StringPrefixSize + len(z.ID) + 5 + msgp.StringPrefixSize + len(z.User) + 8 + msgp.TimeSize + 10
|
||||||
if z.Replicate == nil {
|
if z.Replicate == nil {
|
||||||
s += msgp.NilSize
|
s += msgp.NilSize
|
||||||
} else {
|
} else {
|
||||||
@@ -444,6 +419,12 @@ func (z *batchJobInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "RetryAttempts")
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "at":
|
||||||
|
z.Attempts, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
case "cmp":
|
case "cmp":
|
||||||
z.Complete, err = dc.ReadBool()
|
z.Complete, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -517,9 +498,9 @@ func (z *batchJobInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 16
|
// map header, size 17
|
||||||
// write "v"
|
// write "v"
|
||||||
err = en.Append(0xde, 0x0, 0x10, 0xa1, 0x76)
|
err = en.Append(0xde, 0x0, 0x11, 0xa1, 0x76)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -578,6 +559,16 @@ func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "RetryAttempts")
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "at"
|
||||||
|
err = en.Append(0xa2, 0x61, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.Attempts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
// write "cmp"
|
// write "cmp"
|
||||||
err = en.Append(0xa3, 0x63, 0x6d, 0x70)
|
err = en.Append(0xa3, 0x63, 0x6d, 0x70)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -684,9 +675,9 @@ func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *batchJobInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *batchJobInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 16
|
// map header, size 17
|
||||||
// string "v"
|
// string "v"
|
||||||
o = append(o, 0xde, 0x0, 0x10, 0xa1, 0x76)
|
o = append(o, 0xde, 0x0, 0x11, 0xa1, 0x76)
|
||||||
o = msgp.AppendInt(o, z.Version)
|
o = msgp.AppendInt(o, z.Version)
|
||||||
// string "jid"
|
// string "jid"
|
||||||
o = append(o, 0xa3, 0x6a, 0x69, 0x64)
|
o = append(o, 0xa3, 0x6a, 0x69, 0x64)
|
||||||
@@ -703,6 +694,9 @@ func (z *batchJobInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "ra"
|
// string "ra"
|
||||||
o = append(o, 0xa2, 0x72, 0x61)
|
o = append(o, 0xa2, 0x72, 0x61)
|
||||||
o = msgp.AppendInt(o, z.RetryAttempts)
|
o = msgp.AppendInt(o, z.RetryAttempts)
|
||||||
|
// string "at"
|
||||||
|
o = append(o, 0xa2, 0x61, 0x74)
|
||||||
|
o = msgp.AppendInt(o, z.Attempts)
|
||||||
// string "cmp"
|
// string "cmp"
|
||||||
o = append(o, 0xa3, 0x63, 0x6d, 0x70)
|
o = append(o, 0xa3, 0x63, 0x6d, 0x70)
|
||||||
o = msgp.AppendBool(o, z.Complete)
|
o = msgp.AppendBool(o, z.Complete)
|
||||||
@@ -790,6 +784,12 @@ func (z *batchJobInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "RetryAttempts")
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "at":
|
||||||
|
z.Attempts, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
case "cmp":
|
case "cmp":
|
||||||
z.Complete, bts, err = msgp.ReadBoolBytes(bts)
|
z.Complete, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -864,6 +864,6 @@ func (z *batchJobInfo) 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 *batchJobInfo) Msgsize() (s int) {
|
func (z *batchJobInfo) Msgsize() (s int) {
|
||||||
s = 3 + 2 + msgp.IntSize + 4 + msgp.StringPrefixSize + len(z.JobID) + 3 + msgp.StringPrefixSize + len(z.JobType) + 3 + msgp.TimeSize + 3 + msgp.TimeSize + 3 + msgp.IntSize + 4 + msgp.BoolSize + 4 + msgp.BoolSize + 5 + msgp.StringPrefixSize + len(z.Bucket) + 5 + msgp.StringPrefixSize + len(z.Object) + 3 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 3 + msgp.Int64Size
|
s = 3 + 2 + msgp.IntSize + 4 + msgp.StringPrefixSize + len(z.JobID) + 3 + msgp.StringPrefixSize + len(z.JobType) + 3 + msgp.TimeSize + 3 + msgp.TimeSize + 3 + msgp.IntSize + 3 + msgp.IntSize + 4 + msgp.BoolSize + 4 + msgp.BoolSize + 5 + msgp.StringPrefixSize + len(z.Bucket) + 5 + msgp.StringPrefixSize + len(z.Object) + 3 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 3 + msgp.Int64Size
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/pkg/v2/wildcard"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -213,11 +213,14 @@ func (b BatchJobSnowball) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := humanize.ParseBytes(*b.SmallerThan)
|
_, err := humanize.ParseBytes(*b.SmallerThan)
|
||||||
return BatchJobYamlErr{
|
if err != nil {
|
||||||
line: b.line,
|
return BatchJobYamlErr{
|
||||||
col: b.col,
|
line: b.line,
|
||||||
msg: err.Error(),
|
col: b.col,
|
||||||
|
msg: err.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchJobSizeFilter supports size based filters - LesserThan and GreaterThan
|
// BatchJobSizeFilter supports size based filters - LesserThan and GreaterThan
|
||||||
|
|||||||
+23
-23
@@ -33,9 +33,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/workers"
|
||||||
"github.com/minio/pkg/v2/workers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// keyrotate:
|
// keyrotate:
|
||||||
@@ -96,6 +95,7 @@ func (e BatchJobKeyRotateEncryption) Validate() error {
|
|||||||
if e.Type == ssekms && spaces {
|
if e.Type == ssekms && spaces {
|
||||||
return crypto.ErrInvalidEncryptionKeyID
|
return crypto.ErrInvalidEncryptionKeyID
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Type == ssekms && GlobalKMS != nil {
|
if e.Type == ssekms && GlobalKMS != nil {
|
||||||
ctx := kms.Context{}
|
ctx := kms.Context{}
|
||||||
if e.Context != "" {
|
if e.Context != "" {
|
||||||
@@ -114,7 +114,7 @@ func (e BatchJobKeyRotateEncryption) Validate() error {
|
|||||||
e.kmsContext[k] = v
|
e.kmsContext[k] = v
|
||||||
}
|
}
|
||||||
ctx["MinIO batch API"] = "batchrotate" // Context for a test key operation
|
ctx["MinIO batch API"] = "batchrotate" // Context for a test key operation
|
||||||
if _, err := GlobalKMS.GenerateKey(GlobalContext, e.Key, ctx); err != nil {
|
if _, err := GlobalKMS.GenerateKey(GlobalContext, &kms.GenerateKeyRequest{Name: e.Key, AssociatedData: ctx}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +274,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
|
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
skip := func(info FileInfo) (ok bool) {
|
selectObj := func(info FileInfo) (ok bool) {
|
||||||
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
|
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
|
||||||
// skip all objects that are newer than specified older duration
|
// skip all objects that are newer than specified older duration
|
||||||
return false
|
return false
|
||||||
@@ -357,18 +357,23 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
retryAttempts := ri.RetryAttempts
|
retryAttempts := ri.RetryAttempts
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan itemOrErr[ObjectInfo], 100)
|
||||||
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
||||||
Marker: lastObject,
|
Marker: lastObject,
|
||||||
Filter: skip,
|
Filter: selectObj,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
// Do not need to retry if we can't list objects on source.
|
// Do not need to retry if we can't list objects on source.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
failed := false
|
||||||
for result := range results {
|
for res := range results {
|
||||||
result := result
|
if res.Err != nil {
|
||||||
|
failed = true
|
||||||
|
batchLogIf(ctx, res.Err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result := res.Item
|
||||||
sseKMS := crypto.S3KMS.IsEncrypted(result.UserDefined)
|
sseKMS := crypto.S3KMS.IsEncrypted(result.UserDefined)
|
||||||
sseS3 := crypto.S3.IsEncrypted(result.UserDefined)
|
sseS3 := crypto.S3.IsEncrypted(result.UserDefined)
|
||||||
if !sseKMS && !sseS3 { // neither sse-s3 nor sse-kms disallowed
|
if !sseKMS && !sseS3 { // neither sse-s3 nor sse-kms disallowed
|
||||||
@@ -378,21 +383,19 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
go func() {
|
go func() {
|
||||||
defer wk.Give()
|
defer wk.Give()
|
||||||
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
attempts := attempts
|
|
||||||
stopFn := globalBatchJobsMetrics.trace(batchJobMetricKeyRotation, job.ID, attempts)
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricKeyRotation, job.ID, attempts)
|
||||||
success := true
|
success := true
|
||||||
if err := r.KeyRotate(ctx, api, result); err != nil {
|
if err := r.KeyRotate(ctx, api, result); err != nil {
|
||||||
stopFn(result, err)
|
stopFn(result, err)
|
||||||
logger.LogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
success = false
|
success = false
|
||||||
} else {
|
} else {
|
||||||
stopFn(result, nil)
|
stopFn(result, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Bucket, result, success)
|
ri.trackCurrentBucketObject(r.Bucket, result, success, attempts)
|
||||||
ri.RetryAttempts = attempts
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
if success {
|
if success {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -408,14 +411,14 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
}
|
}
|
||||||
wk.Wait()
|
wk.Wait()
|
||||||
|
|
||||||
ri.Complete = ri.ObjectsFailed == 0
|
ri.Complete = !failed && ri.ObjectsFailed == 0
|
||||||
ri.Failed = ri.ObjectsFailed > 0
|
ri.Failed = failed || ri.ObjectsFailed > 0
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
|
||||||
if err := r.Notify(ctx, ri); err != nil {
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
batchLogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
@@ -476,8 +479,5 @@ func (r *BatchJobKeyRotateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.Flags.Retry.Validate(); err != nil {
|
return r.Flags.Retry.Validate()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-34
@@ -20,25 +20,23 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/ringbuffer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Calculates bitrot in chunks and writes the hash into the stream.
|
// Calculates bitrot in chunks and writes the hash into the stream.
|
||||||
type streamingBitrotWriter struct {
|
type streamingBitrotWriter struct {
|
||||||
iow io.WriteCloser
|
iow io.WriteCloser
|
||||||
closeWithErr func(err error) error
|
closeWithErr func(err error)
|
||||||
h hash.Hash
|
h hash.Hash
|
||||||
shardSize int64
|
shardSize int64
|
||||||
canClose *sync.WaitGroup
|
canClose *sync.WaitGroup
|
||||||
|
byteBuf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
||||||
@@ -66,7 +64,10 @@ func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *streamingBitrotWriter) Close() error {
|
func (b *streamingBitrotWriter) Close() error {
|
||||||
|
// Close the underlying writer.
|
||||||
|
// This will also flush the ring buffer if used.
|
||||||
err := b.iow.Close()
|
err := b.iow.Close()
|
||||||
|
|
||||||
// Wait for all data to be written before returning else it causes race conditions.
|
// Wait for all data to be written before returning else it causes race conditions.
|
||||||
// Race condition is because of io.PipeWriter implementation. i.e consider the following
|
// Race condition is because of io.PipeWriter implementation. i.e consider the following
|
||||||
// sequent of operations:
|
// sequent of operations:
|
||||||
@@ -77,29 +78,34 @@ func (b *streamingBitrotWriter) Close() error {
|
|||||||
if b.canClose != nil {
|
if b.canClose != nil {
|
||||||
b.canClose.Wait()
|
b.canClose.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recycle the buffer.
|
||||||
|
if b.byteBuf != nil {
|
||||||
|
globalBytePoolCap.Load().Put(b.byteBuf)
|
||||||
|
b.byteBuf = nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStreamingBitrotWriterBuffer returns streaming bitrot writer implementation.
|
// newStreamingBitrotWriterBuffer returns streaming bitrot writer implementation.
|
||||||
// The output is written to the supplied writer w.
|
// The output is written to the supplied writer w.
|
||||||
func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||||
return &streamingBitrotWriter{iow: ioutil.NopCloser(w), h: algo.New(), shardSize: shardSize, canClose: nil, closeWithErr: func(err error) error {
|
return &streamingBitrotWriter{iow: ioutil.NopCloser(w), h: algo.New(), shardSize: shardSize, canClose: nil, closeWithErr: func(err error) {}}
|
||||||
// Similar to CloseWithError on pipes we always return nil.
|
|
||||||
return nil
|
|
||||||
}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns streaming bitrot writer implementation.
|
// Returns streaming bitrot writer implementation.
|
||||||
func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||||
r, w := io.Pipe()
|
|
||||||
h := algo.New()
|
h := algo.New()
|
||||||
|
buf := globalBytePoolCap.Load().Get()
|
||||||
|
rb := ringbuffer.NewBuffer(buf[:cap(buf)]).SetBlocking(true)
|
||||||
|
|
||||||
bw := &streamingBitrotWriter{
|
bw := &streamingBitrotWriter{
|
||||||
iow: ioutil.NewDeadlineWriter(w, globalDriveConfig.GetMaxTimeout()),
|
iow: ioutil.NewDeadlineWriter(rb.WriteCloser(), globalDriveConfig.GetMaxTimeout()),
|
||||||
closeWithErr: w.CloseWithError,
|
closeWithErr: rb.CloseWithError,
|
||||||
h: h,
|
h: h,
|
||||||
shardSize: shardSize,
|
shardSize: shardSize,
|
||||||
canClose: &sync.WaitGroup{},
|
canClose: &sync.WaitGroup{},
|
||||||
|
byteBuf: buf,
|
||||||
}
|
}
|
||||||
bw.canClose.Add(1)
|
bw.canClose.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -110,7 +116,7 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
|
|||||||
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
||||||
totalFileSize = bitrotSumsTotalSize + length
|
totalFileSize = bitrotSumsTotalSize + length
|
||||||
}
|
}
|
||||||
r.CloseWithError(disk.CreateFile(context.TODO(), volume, filePath, totalFileSize, r))
|
rb.CloseWithError(disk.CreateFile(context.TODO(), origvolume, volume, filePath, totalFileSize, rb))
|
||||||
}()
|
}()
|
||||||
return bw
|
return bw
|
||||||
}
|
}
|
||||||
@@ -154,29 +160,12 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
// Can never happen unless there are programmer bugs
|
// Can never happen unless there are programmer bugs
|
||||||
return 0, errUnexpected
|
return 0, errUnexpected
|
||||||
}
|
}
|
||||||
ignoredErrs := []error{
|
|
||||||
errDiskNotFound,
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(b.volume, minioMetaBucket) {
|
|
||||||
ignoredErrs = append(ignoredErrs,
|
|
||||||
errFileNotFound,
|
|
||||||
errVolumeNotFound,
|
|
||||||
errFileVersionNotFound,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if b.rc == nil {
|
if b.rc == nil {
|
||||||
// For the first ReadAt() call we need to open the stream for reading.
|
// For the first ReadAt() call we need to open the stream for reading.
|
||||||
b.currOffset = offset
|
b.currOffset = offset
|
||||||
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
||||||
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
||||||
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
||||||
if err != nil {
|
|
||||||
if !IsErr(err, ignoredErrs...) {
|
|
||||||
logger.LogOnceIf(GlobalContext,
|
|
||||||
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
|
||||||
b.disk, b.volume, b.filePath, err), "bitrot-read-file-stream-"+b.volume+"-"+b.filePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
||||||
}
|
}
|
||||||
@@ -198,10 +187,7 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
b.h.Write(buf)
|
b.h.Write(buf)
|
||||||
|
|
||||||
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s - content hash does not match - expected %s, got %s",
|
|
||||||
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))))
|
|
||||||
return 0, errFileCorrupt
|
return 0, errFileCorrupt
|
||||||
}
|
}
|
||||||
b.currOffset += int64(len(buf))
|
b.currOffset += int64(len(buf))
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Implementation to calculate bitrot for the whole file.
|
// Implementation to calculate bitrot for the whole file.
|
||||||
@@ -38,12 +35,10 @@ type wholeBitrotWriter struct {
|
|||||||
func (b *wholeBitrotWriter) Write(p []byte) (int, error) {
|
func (b *wholeBitrotWriter) Write(p []byte) (int, error) {
|
||||||
err := b.disk.AppendFile(context.TODO(), b.volume, b.filePath, p)
|
err := b.disk.AppendFile(context.TODO(), b.volume, b.filePath, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
_, err = b.Hash.Write(p)
|
_, err = b.Hash.Write(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
@@ -72,12 +67,10 @@ func (b *wholeBitrotReader) ReadAt(buf []byte, offset int64) (n int, err error)
|
|||||||
if b.buf == nil {
|
if b.buf == nil {
|
||||||
b.buf = make([]byte, b.tillOffset-offset)
|
b.buf = make([]byte, b.tillOffset-offset)
|
||||||
if _, err := b.disk.ReadFile(context.TODO(), b.volume, b.filePath, offset, b.buf, b.verifier); err != nil {
|
if _, err := b.disk.ReadFile(context.TODO(), b.volume, b.filePath, offset, b.buf, b.verifier); err != nil {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, err))
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(b.buf) < len(buf) {
|
if len(b.buf) < len(buf) {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, errLessData))
|
|
||||||
return 0, errLessData
|
return 0, errLessData
|
||||||
}
|
}
|
||||||
n = copy(buf, b.buf)
|
n = copy(buf, b.buf)
|
||||||
|
|||||||
+2
-2
@@ -102,9 +102,9 @@ func BitrotAlgorithmFromString(s string) (a BitrotAlgorithm) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
func newBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||||
if algo == HighwayHash256S {
|
if algo == HighwayHash256S {
|
||||||
return newStreamingBitrotWriter(disk, volume, filePath, length, algo, shardSize)
|
return newStreamingBitrotWriter(disk, origvolume, volume, filePath, length, algo, shardSize)
|
||||||
}
|
}
|
||||||
return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize)
|
return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
|||||||
|
|
||||||
disk.MakeVol(context.Background(), volume)
|
disk.MakeVol(context.Background(), volume)
|
||||||
|
|
||||||
writer := newBitrotWriter(disk, volume, filePath, 35, bitrotAlgo, 10)
|
writer := newBitrotWriter(disk, "", volume, filePath, 35, bitrotAlgo, 10)
|
||||||
|
|
||||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/grid"
|
"github.com/minio/minio/internal/grid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// To abstract a node over network.
|
// To abstract a node over network.
|
||||||
@@ -152,10 +152,12 @@ func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg *ServerSys
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSSWith(map[string]string{}))
|
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSS())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// We do not need the response after returning.
|
||||||
|
defer serverVerifyHandler.PutResponse(recvCfg)
|
||||||
|
|
||||||
return srcCfg.Diff(recvCfg)
|
return srcCfg.Diff(recvCfg)
|
||||||
}
|
}
|
||||||
@@ -196,7 +198,7 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
bootstrapTraceMsg(fmt.Sprintf("clnt.Verify: %v, endpoint: %s", err, clnt))
|
bootstrapTraceMsg(fmt.Sprintf("clnt.Verify: %v, endpoint: %s", err, clnt))
|
||||||
if !isNetworkError(err) {
|
if !isNetworkError(err) {
|
||||||
logger.LogOnceIf(context.Background(), fmt.Errorf("%s has incorrect configuration: %w", clnt, err), "incorrect_"+clnt.String())
|
bootLogOnceIf(context.Background(), fmt.Errorf("%s has incorrect configuration: %w", clnt, err), "incorrect_"+clnt.String())
|
||||||
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt, err))
|
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt, err))
|
||||||
} else {
|
} else {
|
||||||
offlineEndpoints = append(offlineEndpoints, fmt.Errorf("%s is unreachable: %w", clnt, err))
|
offlineEndpoints = append(offlineEndpoints, fmt.Errorf("%s is unreachable: %w", clnt, err))
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/kes-go"
|
"github.com/minio/kms-go/kes"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -85,7 +85,7 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
kmsKey := encConfig.KeyID()
|
kmsKey := encConfig.KeyID()
|
||||||
if kmsKey != "" {
|
if kmsKey != "" {
|
||||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
||||||
_, err := GlobalKMS.GenerateKey(ctx, kmsKey, kmsContext)
|
_, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{Name: kmsKey, AssociatedData: kmsContext})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, kes.ErrKeyNotFound) {
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errKMSKeyNotFound), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, errKMSKeyNotFound), r.URL)
|
||||||
@@ -114,7 +114,7 @@ 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)
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
SSEConfig: &cfgStr,
|
SSEConfig: &cfgStr,
|
||||||
@@ -203,7 +203,7 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
SSEConfig: nil,
|
SSEConfig: nil,
|
||||||
|
|||||||
+57
-30
@@ -61,8 +61,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -72,6 +72,8 @@ const (
|
|||||||
|
|
||||||
xMinIOErrCodeHeader = "x-minio-error-code"
|
xMinIOErrCodeHeader = "x-minio-error-code"
|
||||||
xMinIOErrDescHeader = "x-minio-error-desc"
|
xMinIOErrDescHeader = "x-minio-error-desc"
|
||||||
|
|
||||||
|
postPolicyBucketTagging = "tagging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if there are buckets on server without corresponding entry in etcd backend and
|
// Check if there are buckets on server without corresponding entry in etcd backend and
|
||||||
@@ -98,7 +100,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
// Get buckets in the DNS
|
// Get buckets in the DNS
|
||||||
dnsBuckets, err := globalDNSConfig.List()
|
dnsBuckets, err := globalDNSConfig.List()
|
||||||
if err != nil && !IsErrIgnored(err, dns.ErrNoEntriesFound, dns.ErrNotImplemented, dns.ErrDomainMissing) {
|
if err != nil && !IsErrIgnored(err, dns.ErrNoEntriesFound, dns.ErrNotImplemented, dns.ErrDomainMissing) {
|
||||||
logger.LogIf(GlobalContext, err)
|
dnsLogIf(GlobalContext, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,13 +162,13 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
ctx := GlobalContext
|
ctx := GlobalContext
|
||||||
for _, err := range g.Wait() {
|
for _, err := range g.Wait() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
dnsLogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range bucketsInConflict.ToSlice() {
|
for _, bucket := range bucketsInConflict.ToSlice() {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to add bucket DNS entry for bucket %s, an entry exists for the same bucket by a different tenant. This local bucket will be ignored. Bucket names are globally unique in federated deployments. Use path style requests on following addresses '%v' to access this bucket", bucket, globalDomainIPs.ToSlice()))
|
dnsLogIf(ctx, fmt.Errorf("Unable to add bucket DNS entry for bucket %s, an entry exists for the same bucket by a different tenant. This local bucket will be ignored. Bucket names are globally unique in federated deployments. Use path style requests on following addresses '%v' to access this bucket", bucket, globalDomainIPs.ToSlice()))
|
||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -187,7 +189,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
// We go to here, so we know the bucket no longer exists,
|
// We go to here, so we know the bucket no longer exists,
|
||||||
// but is registered in DNS to this server
|
// but is registered in DNS to this server
|
||||||
if err := globalDNSConfig.Delete(bucket); err != nil {
|
if err := globalDNSConfig.Delete(bucket); err != nil {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Failed to remove DNS entry for %s due to %w",
|
dnsLogIf(GlobalContext, fmt.Errorf("Failed to remove DNS entry for %s due to %w",
|
||||||
bucket, err))
|
bucket, err))
|
||||||
}
|
}
|
||||||
}(bucket)
|
}(bucket)
|
||||||
@@ -227,7 +229,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
|||||||
// Generate response.
|
// Generate response.
|
||||||
encodedSuccessResponse := encodeResponse(LocationResponse{})
|
encodedSuccessResponse := encodeResponse(LocationResponse{})
|
||||||
// Get current region.
|
// Get current region.
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
if region != globalMinioDefaultRegion {
|
if region != globalMinioDefaultRegion {
|
||||||
encodedSuccessResponse = encodeResponse(LocationResponse{
|
encodedSuccessResponse = encodeResponse(LocationResponse{
|
||||||
Location: region,
|
Location: region,
|
||||||
@@ -467,13 +469,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
// Ignore errors here to preserve the S3 error behavior of GetBucketInfo()
|
// Ignore errors here to preserve the S3 error behavior of GetBucketInfo()
|
||||||
checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, "")
|
checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, "")
|
||||||
|
|
||||||
// Before proceeding validate if bucket exists.
|
|
||||||
_, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{})
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteObjectsFn := objectAPI.DeleteObjects
|
deleteObjectsFn := objectAPI.DeleteObjects
|
||||||
|
|
||||||
// Return Malformed XML as S3 spec if the number of objects is empty
|
// Return Malformed XML as S3 spec if the number of objects is empty
|
||||||
@@ -611,6 +606,12 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
VersionSuspended: vc.Suspended(),
|
VersionSuspended: vc.Suspended(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Are all objects saying bucket not found?
|
||||||
|
if isAllBucketsNotFound(errs) {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, errs[0]), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
// DeleteMarkerVersionID is not used specifically to avoid
|
// DeleteMarkerVersionID is not used specifically to avoid
|
||||||
// lookup errors, since DeleteMarkerVersionID is only
|
// lookup errors, since DeleteMarkerVersionID is only
|
||||||
@@ -618,7 +619,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
// specify a versionID.
|
// specify a versionID.
|
||||||
objToDel := ObjectToDelete{
|
objToDel := ObjectToDelete{
|
||||||
ObjectV: ObjectV{
|
ObjectV: ObjectV{
|
||||||
ObjectName: dObjects[i].ObjectName,
|
ObjectName: decodeDirObject(dObjects[i].ObjectName),
|
||||||
VersionID: dObjects[i].VersionID,
|
VersionID: dObjects[i].VersionID,
|
||||||
},
|
},
|
||||||
VersionPurgeStatus: dObjects[i].VersionPurgeStatus(),
|
VersionPurgeStatus: dObjects[i].VersionPurgeStatus(),
|
||||||
@@ -791,7 +792,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
// check if client is attempting to create more buckets, complain about it.
|
// 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 {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Please avoid creating more buckets %d beyond recommended %d", currBuckets+1, maxBuckets))
|
internalLogIf(ctx, fmt.Errorf("Please avoid creating more buckets %d beyond recommended %d", currBuckets+1, maxBuckets), logger.WarningKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := MakeBucketOptions{
|
opts := MakeBucketOptions{
|
||||||
@@ -872,7 +873,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
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts))
|
replLogIf(ctx, globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts))
|
||||||
|
|
||||||
// Make sure to add Location information here only for bucket
|
// Make sure to add Location information here only for bucket
|
||||||
w.Header().Set(xhttp.Location, pathJoin(SlashSeparator, bucket))
|
w.Header().Set(xhttp.Location, pathJoin(SlashSeparator, bucket))
|
||||||
@@ -1219,11 +1220,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reader io.Reader
|
reader io.Reader
|
||||||
keyID string
|
keyID string
|
||||||
@@ -1395,7 +1391,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
// Notify object created events.
|
// Notify object created events.
|
||||||
sendEvent(eventArgsList[i])
|
sendEvent(eventArgsList[i])
|
||||||
|
|
||||||
if eventArgsList[i].Object.NumVersions > dataScannerExcessiveVersionsThreshold {
|
if eventArgsList[i].Object.NumVersions > int(scannerExcessObjectVersions.Load()) {
|
||||||
// Send events for excessive versions.
|
// Send events for excessive versions.
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectManyVersions,
|
EventName: event.ObjectManyVersions,
|
||||||
@@ -1406,12 +1402,34 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
|
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
|
||||||
Host: handlers.GetSourceIP(r),
|
Host: handlers.GetSourceIP(r),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
auditLogInternal(context.Background(), AuditLogOptions{
|
||||||
|
Event: "scanner:manyversions",
|
||||||
|
APIName: "PostPolicyBucket",
|
||||||
|
Bucket: eventArgsList[i].Object.Bucket,
|
||||||
|
Object: eventArgsList[i].Object.Name,
|
||||||
|
VersionID: eventArgsList[i].Object.VersionID,
|
||||||
|
Status: http.StatusText(http.StatusOK),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if formValues.Get(postPolicyBucketTagging) != "" {
|
||||||
|
tags, err := tags.ParseObjectXML(strings.NewReader(formValues.Get(postPolicyBucketTagging)))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tagsStr := tags.String()
|
||||||
|
opts.UserDefined[xhttp.AmzObjectTagging] = tagsStr
|
||||||
|
} else {
|
||||||
|
// avoid user set an invalid tag using `X-Amz-Tagging`
|
||||||
|
delete(opts.UserDefined, xhttp.AmzObjectTagging)
|
||||||
|
}
|
||||||
|
|
||||||
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -1461,7 +1479,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
Host: handlers.GetSourceIP(r),
|
Host: handlers.GetSourceIP(r),
|
||||||
})
|
})
|
||||||
|
|
||||||
if objInfo.NumVersions > dataScannerExcessiveVersionsThreshold {
|
if objInfo.NumVersions > int(scannerExcessObjectVersions.Load()) {
|
||||||
defer sendEvent(eventArgs{
|
defer sendEvent(eventArgs{
|
||||||
EventName: event.ObjectManyVersions,
|
EventName: event.ObjectManyVersions,
|
||||||
BucketName: objInfo.Bucket,
|
BucketName: objInfo.Bucket,
|
||||||
@@ -1471,6 +1489,15 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
UserAgent: r.UserAgent(),
|
UserAgent: r.UserAgent(),
|
||||||
Host: handlers.GetSourceIP(r),
|
Host: handlers.GetSourceIP(r),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
auditLogInternal(context.Background(), AuditLogOptions{
|
||||||
|
Event: "scanner:manyversions",
|
||||||
|
APIName: "PostPolicyBucket",
|
||||||
|
Bucket: objInfo.Bucket,
|
||||||
|
Object: objInfo.Name,
|
||||||
|
VersionID: objInfo.VersionID,
|
||||||
|
Status: http.StatusText(http.StatusOK),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if redirectURL != nil { // success_action_redirect is valid and set.
|
if redirectURL != nil { // success_action_redirect is valid and set.
|
||||||
@@ -1649,7 +1676,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case rcfg.HasActiveRules("", true):
|
case rcfg != nil && rcfg.HasActiveRules("", true):
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1681,7 +1708,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
if globalDNSConfig != nil {
|
if globalDNSConfig != nil {
|
||||||
if err := globalDNSConfig.Delete(bucket); err != nil {
|
if err := globalDNSConfig.Delete(bucket); err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to delete bucket DNS entry %w, please delete it manually, bucket on MinIO no longer exists", err))
|
dnsLogIf(ctx, fmt.Errorf("Unable to delete bucket DNS entry %w, please delete it manually, bucket on MinIO no longer exists", err))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1691,7 +1718,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
globalReplicationPool.deleteResyncMetadata(ctx, bucket)
|
globalReplicationPool.deleteResyncMetadata(ctx, bucket)
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete))
|
replLogIf(ctx, globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete))
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
@@ -1764,7 +1791,7 @@ 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)
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
ObjectLockConfig: &cfgStr,
|
ObjectLockConfig: &cfgStr,
|
||||||
@@ -1868,7 +1895,7 @@ 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)
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
Type: madmin.SRBucketMetaTypeTags,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Tags: &cfgStr,
|
Tags: &cfgStr,
|
||||||
@@ -1944,7 +1971,7 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
Type: madmin.SRBucketMetaTypeTags,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
|
|||||||
@@ -580,7 +580,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
|||||||
expectedRespStatus: http.StatusOK,
|
expectedRespStatus: http.StatusOK,
|
||||||
},
|
},
|
||||||
// Test case - 2.
|
// Test case - 2.
|
||||||
// Test case with invalid accessKey to produce and validate Signature MisMatch error.
|
// Test case with invalid accessKey to produce and validate Signature Mismatch error.
|
||||||
{
|
{
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
accessKey: "abcd",
|
accessKey: "abcd",
|
||||||
@@ -880,6 +880,15 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
expectedContent: encodedAnonResponseWithPartialPublicAccess,
|
expectedContent: encodedAnonResponseWithPartialPublicAccess,
|
||||||
expectedRespStatus: http.StatusOK,
|
expectedRespStatus: http.StatusOK,
|
||||||
},
|
},
|
||||||
|
// Test case - 7.
|
||||||
|
// Bucket does not exist.
|
||||||
|
7: {
|
||||||
|
bucket: "unknown-bucket-name",
|
||||||
|
objects: successRequest0,
|
||||||
|
accessKey: credentials.AccessKey,
|
||||||
|
secretKey: credentials.SecretKey,
|
||||||
|
expectedRespStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
@@ -888,13 +897,12 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
|
|
||||||
// Generate a signed or anonymous request based on the testCase
|
// Generate a signed or anonymous request based on the testCase
|
||||||
if testCase.accessKey != "" {
|
if testCase.accessKey != "" {
|
||||||
req, err = newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
|
req, err = newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", testCase.bucket),
|
||||||
int64(len(testCase.objects)), bytes.NewReader(testCase.objects), testCase.accessKey, testCase.secretKey, nil)
|
int64(len(testCase.objects)), bytes.NewReader(testCase.objects), testCase.accessKey, testCase.secretKey, nil)
|
||||||
} else {
|
} else {
|
||||||
req, err = newTestRequest(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
|
req, err = newTestRequest(http.MethodPost, getDeleteMultipleObjectsURL("", testCase.bucket),
|
||||||
int64(len(testCase.objects)), bytes.NewReader(testCase.objects))
|
int64(len(testCase.objects)), bytes.NewReader(testCase.objects))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create HTTP request for DeleteMultipleObjects: <ERROR> %v", err)
|
t.Fatalf("Failed to create HTTP request for DeleteMultipleObjects: <ERROR> %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -64,7 +64,8 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
rcfg, err := globalBucketObjectLockSys.Get(bucket)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,7 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate the received bucket policy document
|
// Validate the received bucket policy document
|
||||||
if err = bucketLifecycle.Validate(); err != nil {
|
if err = bucketLifecycle.Validate(rcfg); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+287
-88
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -39,12 +38,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/s3select"
|
"github.com/minio/minio/internal/s3select"
|
||||||
"github.com/minio/pkg/v2/env"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
|
||||||
"github.com/minio/pkg/v2/workers"
|
|
||||||
"github.com/zeebo/xxh3"
|
"github.com/zeebo/xxh3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,96 +101,284 @@ type expiryTask struct {
|
|||||||
src lcEventSrc
|
src lcEventSrc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expiryStats records metrics related to ILM expiry activities
|
||||||
|
type expiryStats struct {
|
||||||
|
missedExpiryTasks atomic.Int64
|
||||||
|
missedFreeVersTasks atomic.Int64
|
||||||
|
missedTierJournalTasks atomic.Int64
|
||||||
|
workers atomic.Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MissedTasks returns the number of ILM expiry tasks that were missed since
|
||||||
|
// there were no available workers.
|
||||||
|
func (e *expiryStats) MissedTasks() int64 {
|
||||||
|
return e.missedExpiryTasks.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MissedFreeVersTasks returns the number of free version collection tasks that
|
||||||
|
// were missed since there were no available workers.
|
||||||
|
func (e *expiryStats) MissedFreeVersTasks() int64 {
|
||||||
|
return e.missedFreeVersTasks.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MissedTierJournalTasks returns the number of tasks to remove tiered objects
|
||||||
|
// that were missed since there were no available workers.
|
||||||
|
func (e *expiryStats) MissedTierJournalTasks() int64 {
|
||||||
|
return e.missedTierJournalTasks.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumWorkers returns the number of active workers executing one of ILM expiry
|
||||||
|
// tasks or free version collection tasks.
|
||||||
|
func (e *expiryStats) NumWorkers() int32 {
|
||||||
|
return e.workers.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
type expiryOp interface {
|
||||||
|
OpHash() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type freeVersionTask struct {
|
||||||
|
ObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f freeVersionTask) OpHash() uint64 {
|
||||||
|
return xxh3.HashString(f.TransitionedObject.Tier + f.TransitionedObject.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n newerNoncurrentTask) OpHash() uint64 {
|
||||||
|
return xxh3.HashString(n.bucket + n.versions[0].ObjectV.ObjectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jentry) OpHash() uint64 {
|
||||||
|
return xxh3.HashString(j.TierName + j.ObjName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e expiryTask) OpHash() uint64 {
|
||||||
|
return xxh3.HashString(e.objInfo.Bucket + e.objInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expiryState manages all ILM related expiration activities.
|
||||||
type expiryState struct {
|
type expiryState struct {
|
||||||
once sync.Once
|
mu sync.RWMutex
|
||||||
byDaysCh chan expiryTask
|
workers atomic.Pointer[[]chan expiryOp]
|
||||||
byNewerNoncurrentCh chan newerNoncurrentTask
|
|
||||||
|
ctx context.Context
|
||||||
|
objAPI ObjectLayer
|
||||||
|
|
||||||
|
stats expiryStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingTasks returns the number of pending ILM expiry tasks.
|
// PendingTasks returns the number of pending ILM expiry tasks.
|
||||||
func (es *expiryState) PendingTasks() int {
|
func (es *expiryState) PendingTasks() int {
|
||||||
return len(es.byDaysCh) + len(es.byNewerNoncurrentCh)
|
w := es.workers.Load()
|
||||||
|
if w == nil || len(*w) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var tasks int
|
||||||
|
for _, wrkr := range *w {
|
||||||
|
tasks += len(wrkr)
|
||||||
|
}
|
||||||
|
return tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
// close closes work channels exactly once.
|
// enqueueTierJournalEntry enqueues a tier journal entry referring to a remote
|
||||||
func (es *expiryState) close() {
|
// object corresponding to a 'replaced' object versions. This applies only to
|
||||||
es.once.Do(func() {
|
// non-versioned or version suspended buckets.
|
||||||
xioutil.SafeClose(es.byDaysCh)
|
func (es *expiryState) enqueueTierJournalEntry(je jentry) {
|
||||||
xioutil.SafeClose(es.byNewerNoncurrentCh)
|
wrkr := es.getWorkerCh(je.OpHash())
|
||||||
})
|
if wrkr == nil {
|
||||||
|
es.stats.missedTierJournalTasks.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-GlobalContext.Done():
|
||||||
|
case wrkr <- je:
|
||||||
|
default:
|
||||||
|
es.stats.missedTierJournalTasks.Add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enqueueFreeVersion enqueues a free version to be deleted
|
||||||
|
func (es *expiryState) enqueueFreeVersion(oi ObjectInfo) {
|
||||||
|
task := freeVersionTask{ObjectInfo: oi}
|
||||||
|
wrkr := es.getWorkerCh(task.OpHash())
|
||||||
|
if wrkr == nil {
|
||||||
|
es.stats.missedFreeVersTasks.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-GlobalContext.Done():
|
||||||
|
case wrkr <- task:
|
||||||
|
default:
|
||||||
|
es.stats.missedFreeVersTasks.Add(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// enqueueByDays enqueues object versions expired by days for expiry.
|
// enqueueByDays enqueues object versions expired by days for expiry.
|
||||||
func (es *expiryState) enqueueByDays(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) {
|
func (es *expiryState) enqueueByDays(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) {
|
||||||
|
task := expiryTask{objInfo: oi, event: event, src: src}
|
||||||
|
wrkr := es.getWorkerCh(task.OpHash())
|
||||||
|
if wrkr == nil {
|
||||||
|
es.stats.missedExpiryTasks.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-GlobalContext.Done():
|
case <-GlobalContext.Done():
|
||||||
es.close()
|
case wrkr <- task:
|
||||||
case es.byDaysCh <- expiryTask{objInfo: oi, event: event, src: src}:
|
|
||||||
default:
|
default:
|
||||||
|
es.stats.missedExpiryTasks.Add(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// enqueueByNewerNoncurrent enqueues object versions expired by
|
// enqueueByNewerNoncurrent enqueues object versions expired by
|
||||||
// NewerNoncurrentVersions limit for expiry.
|
// NewerNoncurrentVersions limit for expiry.
|
||||||
func (es *expiryState) enqueueByNewerNoncurrent(bucket string, versions []ObjectToDelete, lcEvent lifecycle.Event) {
|
func (es *expiryState) enqueueByNewerNoncurrent(bucket string, versions []ObjectToDelete, lcEvent lifecycle.Event) {
|
||||||
|
if len(versions) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task := newerNoncurrentTask{bucket: bucket, versions: versions, event: lcEvent}
|
||||||
|
wrkr := es.getWorkerCh(task.OpHash())
|
||||||
|
if wrkr == nil {
|
||||||
|
es.stats.missedExpiryTasks.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-GlobalContext.Done():
|
case <-GlobalContext.Done():
|
||||||
es.close()
|
case wrkr <- task:
|
||||||
case es.byNewerNoncurrentCh <- newerNoncurrentTask{bucket: bucket, versions: versions, event: lcEvent}:
|
|
||||||
default:
|
default:
|
||||||
|
es.stats.missedExpiryTasks.Add(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// globalExpiryState is the per-node instance which manages all ILM expiry tasks.
|
||||||
var globalExpiryState *expiryState
|
var globalExpiryState *expiryState
|
||||||
|
|
||||||
func newExpiryState() *expiryState {
|
// newExpiryState creates an expiryState with buffered channels allocated for
|
||||||
return &expiryState{
|
// each ILM expiry task type.
|
||||||
byDaysCh: make(chan expiryTask, 100000),
|
func newExpiryState(ctx context.Context, objAPI ObjectLayer, n int) *expiryState {
|
||||||
byNewerNoncurrentCh: make(chan newerNoncurrentTask, 100000),
|
es := &expiryState{
|
||||||
|
ctx: ctx,
|
||||||
|
objAPI: objAPI,
|
||||||
|
}
|
||||||
|
workers := make([]chan expiryOp, 0, n)
|
||||||
|
es.workers.Store(&workers)
|
||||||
|
es.ResizeWorkers(n)
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *expiryState) getWorkerCh(h uint64) chan<- expiryOp {
|
||||||
|
w := es.workers.Load()
|
||||||
|
if w == nil || len(*w) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
workers := *w
|
||||||
|
return workers[h%uint64(len(workers))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *expiryState) ResizeWorkers(n int) {
|
||||||
|
// Lock to avoid multiple resizes to happen at the same time.
|
||||||
|
es.mu.Lock()
|
||||||
|
defer es.mu.Unlock()
|
||||||
|
var workers []chan expiryOp
|
||||||
|
if v := es.workers.Load(); v != nil {
|
||||||
|
// Copy to new array.
|
||||||
|
workers = append(workers, *v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == len(workers) || n < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(workers) < n {
|
||||||
|
input := make(chan expiryOp, 10000)
|
||||||
|
workers = append(workers, input)
|
||||||
|
go es.Worker(input)
|
||||||
|
es.stats.workers.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(workers) > n {
|
||||||
|
worker := workers[len(workers)-1]
|
||||||
|
workers = workers[:len(workers)-1]
|
||||||
|
worker <- expiryOp(nil)
|
||||||
|
es.stats.workers.Add(-1)
|
||||||
|
}
|
||||||
|
// Atomically replace workers.
|
||||||
|
es.workers.Store(&workers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worker handles 4 types of expiration tasks.
|
||||||
|
// 1. Expiry of objects, includes regular and transitioned objects
|
||||||
|
// 2. Expiry of noncurrent versions due to NewerNoncurrentVersions
|
||||||
|
// 3. Expiry of free-versions, for remote objects of transitioned object which have been expired since.
|
||||||
|
// 4. Expiry of remote objects corresponding to objects in a
|
||||||
|
// non-versioned/version suspended buckets
|
||||||
|
func (es *expiryState) Worker(input <-chan expiryOp) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-es.ctx.Done():
|
||||||
|
return
|
||||||
|
case v, ok := <-input:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v == nil {
|
||||||
|
// ResizeWorkers signaling worker to quit
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case expiryTask:
|
||||||
|
if v.objInfo.TransitionedObject.Status != "" {
|
||||||
|
applyExpiryOnTransitionedObject(es.ctx, es.objAPI, v.objInfo, v.event, v.src)
|
||||||
|
} else {
|
||||||
|
applyExpiryOnNonTransitionedObjects(es.ctx, es.objAPI, v.objInfo, v.event, v.src)
|
||||||
|
}
|
||||||
|
case newerNoncurrentTask:
|
||||||
|
deleteObjectVersions(es.ctx, es.objAPI, v.bucket, v.versions, v.event)
|
||||||
|
case jentry:
|
||||||
|
transitionLogIf(es.ctx, deleteObjectFromRemoteTier(es.ctx, v.ObjName, v.VersionID, v.TierName))
|
||||||
|
case freeVersionTask:
|
||||||
|
oi := v.ObjectInfo
|
||||||
|
traceFn := globalLifecycleSys.trace(oi)
|
||||||
|
if !oi.TransitionedObject.FreeVersion {
|
||||||
|
// nothing to be done
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreNotFoundErr := func(err error) error {
|
||||||
|
switch {
|
||||||
|
case isErrVersionNotFound(err), isErrObjectNotFound(err):
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Remove the remote object
|
||||||
|
err := deleteObjectFromRemoteTier(es.ctx, oi.TransitionedObject.Name, oi.TransitionedObject.VersionID, oi.TransitionedObject.Tier)
|
||||||
|
if ignoreNotFoundErr(err) != nil {
|
||||||
|
transitionLogIf(es.ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove this free version
|
||||||
|
_, err = es.objAPI.DeleteObject(es.ctx, oi.Bucket, oi.Name, ObjectOptions{
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
InclFreeVersions: true,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
auditLogLifecycle(es.ctx, oi, ILMFreeVersionDelete, nil, traceFn)
|
||||||
|
}
|
||||||
|
if ignoreNotFoundErr(err) != nil {
|
||||||
|
transitionLogIf(es.ctx, err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bugLogIf(es.ctx, fmt.Errorf("Invalid work type - %v", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) {
|
func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
globalExpiryState = newExpiryState()
|
globalExpiryState = newExpiryState(ctx, objectAPI, globalILMConfig.getExpirationWorkers())
|
||||||
|
|
||||||
workerSize, _ := strconv.Atoi(env.Get("_MINIO_ILM_EXPIRY_WORKERS", strconv.Itoa((runtime.GOMAXPROCS(0)+1)/2)))
|
|
||||||
if workerSize == 0 {
|
|
||||||
workerSize = 4
|
|
||||||
}
|
|
||||||
ewk, err := workers.New(workerSize)
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nwk, err := workers.New(workerSize)
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for t := range globalExpiryState.byDaysCh {
|
|
||||||
ewk.Take()
|
|
||||||
go func(t expiryTask) {
|
|
||||||
defer ewk.Give()
|
|
||||||
if t.objInfo.TransitionedObject.Status != "" {
|
|
||||||
applyExpiryOnTransitionedObject(ctx, objectAPI, t.objInfo, t.event, t.src)
|
|
||||||
} else {
|
|
||||||
applyExpiryOnNonTransitionedObjects(ctx, objectAPI, t.objInfo, t.event, t.src)
|
|
||||||
}
|
|
||||||
}(t)
|
|
||||||
}
|
|
||||||
ewk.Wait()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for t := range globalExpiryState.byNewerNoncurrentCh {
|
|
||||||
nwk.Take()
|
|
||||||
go func(t newerNoncurrentTask) {
|
|
||||||
defer nwk.Give()
|
|
||||||
deleteObjectVersions(ctx, objectAPI, t.bucket, t.versions, t.event)
|
|
||||||
}(t)
|
|
||||||
}
|
|
||||||
nwk.Wait()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newerNoncurrentTask encapsulates arguments required by worker to expire objects
|
// newerNoncurrentTask encapsulates arguments required by worker to expire objects
|
||||||
@@ -258,6 +442,10 @@ func newTransitionState(ctx context.Context) *transitionState {
|
|||||||
// of transition workers.
|
// of transition workers.
|
||||||
func (t *transitionState) Init(objAPI ObjectLayer) {
|
func (t *transitionState) Init(objAPI ObjectLayer) {
|
||||||
n := globalAPIConfig.getTransitionWorkers()
|
n := globalAPIConfig.getTransitionWorkers()
|
||||||
|
// Prefer ilm.transition_workers over now deprecated api.transition_workers
|
||||||
|
if tw := globalILMConfig.getTransitionWorkers(); tw > 0 {
|
||||||
|
n = tw
|
||||||
|
}
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
@@ -298,7 +486,7 @@ func (t *transitionState) worker(objectAPI ObjectLayer) {
|
|||||||
if err := transitionObject(t.ctx, objectAPI, task.objInfo, newLifecycleAuditEvent(task.src, task.event)); err != nil {
|
if err := transitionObject(t.ctx, objectAPI, task.objInfo, newLifecycleAuditEvent(task.src, task.event)); err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !xnet.IsNetworkOrHostDown(err, false) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !xnet.IsNetworkOrHostDown(err, false) {
|
||||||
if !strings.Contains(err.Error(), "use of closed network connection") {
|
if !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
logger.LogIf(t.ctx, fmt.Errorf("Transition to %s failed for %s/%s version:%s with %w",
|
transitionLogIf(t.ctx, fmt.Errorf("Transition to %s failed for %s/%s version:%s with %w",
|
||||||
task.event.StorageClass, task.objInfo.Bucket, task.objInfo.Name, task.objInfo.VersionID, err))
|
task.event.StorageClass, task.objInfo.Bucket, task.objInfo.Name, task.objInfo.VersionID, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,12 +583,15 @@ func enqueueTransitionImmediate(obj ObjectInfo, src lcEventSrc) {
|
|||||||
//
|
//
|
||||||
// 1. when a restored (via PostRestoreObject API) object expires.
|
// 1. when a restored (via PostRestoreObject API) object expires.
|
||||||
// 2. when a transitioned object expires (based on an ILM rule).
|
// 2. when a transitioned object expires (based on an ILM rule).
|
||||||
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, lcEvent lifecycle.Event, src lcEventSrc) error {
|
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) error {
|
||||||
traceFn := globalLifecycleSys.trace(*oi)
|
traceFn := globalLifecycleSys.trace(*oi)
|
||||||
var opts ObjectOptions
|
opts := ObjectOptions{
|
||||||
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name)
|
Versioned: globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name),
|
||||||
opts.VersionID = lcOpts.VersionID
|
Expiration: ExpirationOptions{Expire: true},
|
||||||
opts.Expiration = ExpirationOptions{Expire: true}
|
}
|
||||||
|
if lcEvent.Action.DeleteVersioned() {
|
||||||
|
opts.VersionID = oi.VersionID
|
||||||
|
}
|
||||||
tags := newLifecycleAuditEvent(src, lcEvent).Tags()
|
tags := newLifecycleAuditEvent(src, lcEvent).Tags()
|
||||||
if lcEvent.Action.DeleteRestored() {
|
if lcEvent.Action.DeleteRestored() {
|
||||||
// delete locally restored copy of object or object version
|
// delete locally restored copy of object or object version
|
||||||
@@ -415,18 +606,18 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// When an object is past expiry or when a transitioned object is being
|
|
||||||
// deleted, 'mark' the data in the remote tier for delete.
|
// Delete remote object from warm-tier
|
||||||
entry := jentry{
|
err := deleteObjectFromRemoteTier(ctx, oi.TransitionedObject.Name, oi.TransitionedObject.VersionID, oi.TransitionedObject.Tier)
|
||||||
ObjName: oi.TransitionedObject.Name,
|
if err == nil {
|
||||||
VersionID: oi.TransitionedObject.VersionID,
|
// Skip adding free version since we successfully deleted the
|
||||||
TierName: oi.TransitionedObject.Tier,
|
// remote object
|
||||||
|
opts.SkipFreeVersion = true
|
||||||
|
} else {
|
||||||
|
transitionLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
if err := globalTierJournal.AddEntry(entry); err != nil {
|
|
||||||
return err
|
// Now, delete object from hot-tier namespace
|
||||||
}
|
|
||||||
// Delete metadata on source, now that data in remote tier has been
|
|
||||||
// marked for deletion.
|
|
||||||
if _, err := objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts); err != nil {
|
if _, err := objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -435,13 +626,13 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
|
|||||||
defer auditLogLifecycle(ctx, *oi, ILMExpiry, tags, traceFn)
|
defer auditLogLifecycle(ctx, *oi, ILMExpiry, tags, traceFn)
|
||||||
|
|
||||||
eventName := event.ObjectRemovedDelete
|
eventName := event.ObjectRemovedDelete
|
||||||
if lcOpts.DeleteMarker {
|
if oi.DeleteMarker {
|
||||||
eventName = event.ObjectRemovedDeleteMarkerCreated
|
eventName = event.ObjectRemovedDeleteMarkerCreated
|
||||||
}
|
}
|
||||||
objInfo := ObjectInfo{
|
objInfo := ObjectInfo{
|
||||||
Name: oi.Name,
|
Name: oi.Name,
|
||||||
VersionID: lcOpts.VersionID,
|
VersionID: oi.VersionID,
|
||||||
DeleteMarker: lcOpts.DeleteMarker,
|
DeleteMarker: oi.DeleteMarker,
|
||||||
}
|
}
|
||||||
// Notify object deleted event.
|
// Notify object deleted event.
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
@@ -471,7 +662,15 @@ 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, lae lcAuditEvent) error {
|
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) (err error) {
|
||||||
|
timeILM := globalScannerMetrics.timeILM(lae.Action)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeILM(1)
|
||||||
|
}()
|
||||||
|
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
Transition: TransitionOptions{
|
Transition: TransitionOptions{
|
||||||
Status: lifecycle.TransitionPending,
|
Status: lifecycle.TransitionPending,
|
||||||
@@ -523,9 +722,9 @@ func auditTierActions(ctx context.Context, tier string, bytes int64) func(err er
|
|||||||
|
|
||||||
// getTransitionedObjectReader returns a reader from the transitioned tier.
|
// getTransitionedObjectReader returns a reader from the transitioned tier.
|
||||||
func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, oi ObjectInfo, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, oi ObjectInfo, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
||||||
tgtClient, err := globalTierConfigMgr.getDriver(oi.TransitionedObject.Tier)
|
tgtClient, err := globalTierConfigMgr.getDriver(ctx, oi.TransitionedObject.Tier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("transition storage class not configured")
|
return nil, fmt.Errorf("transition storage class not configured: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn, off, length, err := NewGetObjectReader(rs, oi, opts)
|
fn, off, length, err := NewGetObjectReader(rs, oi, opts)
|
||||||
@@ -681,7 +880,7 @@ func postRestoreOpts(ctx context.Context, r *http.Request, bucket, object string
|
|||||||
if vid != "" && vid != nullVersionID {
|
if vid != "" && vid != nullVersionID {
|
||||||
_, err := uuid.Parse(vid)
|
_, err := uuid.Parse(vid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
s3LogIf(ctx, err)
|
||||||
return opts, InvalidVersionID{
|
return opts, InvalidVersionID{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: object,
|
Object: object,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
|
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
||||||
@@ -124,7 +124,7 @@ func (api objectAPIHandlers) listObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo, checkObjMeta)
|
response := generateListVersionsResponse(ctx, bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo, checkObjMeta)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
@@ -219,7 +219,7 @@ func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := generateListObjectsV2Response(bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter,
|
response := generateListObjectsV2Response(ctx, bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter,
|
||||||
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
||||||
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, checkObjMeta)
|
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, checkObjMeta)
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract all the litsObjectsV1 query params to their native values.
|
// Extract all the listObjectsV1 query params to their native values.
|
||||||
prefix, marker, delimiter, maxKeys, encodingType, s3Error := getListObjectsV1Args(r.Form)
|
prefix, marker, delimiter, maxKeys, encodingType, s3Error := getListObjectsV1Args(r.Form)
|
||||||
if s3Error != ErrNone {
|
if s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
@@ -318,7 +318,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
|
response := generateListObjectsV1Response(ctx, bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
|
|||||||
+32
-11
@@ -22,6 +22,7 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -36,8 +37,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
||||||
@@ -45,6 +46,7 @@ type BucketMetadataSys struct {
|
|||||||
objAPI ObjectLayer
|
objAPI ObjectLayer
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
initialized bool
|
||||||
metadataMap map[string]BucketMetadata
|
metadataMap map[string]BucketMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,6 +434,8 @@ func (sys *BucketMetadataSys) GetConfigFromDisk(ctx context.Context, bucket stri
|
|||||||
return loadBucketMetadata(ctx, objAPI, bucket)
|
return loadBucketMetadata(ctx, objAPI, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errBucketMetadataNotInitialized = errors.New("bucket metadata not initialized yet")
|
||||||
|
|
||||||
// GetConfig returns a specific configuration from the bucket metadata.
|
// GetConfig returns a specific configuration from the bucket metadata.
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
// reloaded will be true if metadata refreshed from disk
|
// reloaded will be true if metadata refreshed from disk
|
||||||
@@ -453,6 +457,10 @@ func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (met
|
|||||||
}
|
}
|
||||||
meta, err = loadBucketMetadata(ctx, objAPI, bucket)
|
meta, err = loadBucketMetadata(ctx, objAPI, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !sys.Initialized() {
|
||||||
|
// bucket metadata not yet initialized
|
||||||
|
return newBucketMetadata(bucket), reloaded, errBucketMetadataNotInitialized
|
||||||
|
}
|
||||||
return meta, reloaded, err
|
return meta, reloaded, err
|
||||||
}
|
}
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
@@ -482,11 +490,11 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
for index := range buckets {
|
for index := range buckets {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
|
// Sleep and stagger to avoid blocked CPU and thundering
|
||||||
// Ensure heal opts for bucket metadata be deep healed all the time.
|
// herd upon start up sequence.
|
||||||
ScanMode: madmin.HealDeepScan,
|
time.Sleep(25*time.Millisecond + time.Duration(rand.Int63n(int64(100*time.Millisecond))))
|
||||||
Recreate: true,
|
|
||||||
})
|
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{Recreate: true})
|
||||||
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name)
|
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -497,9 +505,10 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
}
|
}
|
||||||
|
|
||||||
errs := g.Wait()
|
errs := g.Wait()
|
||||||
for _, err := range errs {
|
for index, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogOnceIf(ctx, fmt.Errorf("Unable to load bucket metadata, will be retried: %w", err),
|
||||||
|
"load-bucket-metadata-"+buckets[index].Name, logger.WarningKind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +550,7 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, fa
|
|||||||
case <-t.C:
|
case <-t.C:
|
||||||
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
|
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +568,7 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, fa
|
|||||||
|
|
||||||
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[i].Name)
|
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[i].Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
wait() // wait to proceed to next entry.
|
wait() // wait to proceed to next entry.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -582,6 +591,14 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, fa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialized indicates if bucket metadata sys is initialized atleast once.
|
||||||
|
func (sys *BucketMetadataSys) Initialized() bool {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
|
||||||
|
return sys.initialized
|
||||||
|
}
|
||||||
|
|
||||||
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
||||||
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
||||||
count := 100 // load 100 bucket metadata at a time.
|
count := 100 // load 100 bucket metadata at a time.
|
||||||
@@ -595,6 +612,10 @@ func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
|||||||
buckets = buckets[count:]
|
buckets = buckets[count:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
sys.initialized = true
|
||||||
|
sys.Unlock()
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
go sys.refreshBucketsMetadataLoop(ctx, failedBuckets)
|
go sys.refreshBucketsMetadataLoop(ctx, failedBuckets)
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-20
@@ -41,7 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/fips"
|
"github.com/minio/minio/internal/fips"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
|
|||||||
// If an error is returned the returned metadata will be default initialized.
|
// If an error is returned the returned metadata will be default initialized.
|
||||||
func readBucketMetadata(ctx context.Context, api ObjectLayer, name string) (BucketMetadata, error) {
|
func readBucketMetadata(ctx context.Context, api ObjectLayer, name string) (BucketMetadata, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
|
internalLogIf(ctx, errors.New("bucket name cannot be empty"), logger.WarningKind)
|
||||||
return BucketMetadata{}, errInvalidArgument
|
return BucketMetadata{}, errInvalidArgument
|
||||||
}
|
}
|
||||||
b := newBucketMetadata(name)
|
b := newBucketMetadata(name)
|
||||||
@@ -182,26 +182,34 @@ func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket
|
|||||||
b.defaultTimestamps()
|
b.defaultTimestamps()
|
||||||
}
|
}
|
||||||
|
|
||||||
configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
|
// If bucket metadata is missing look for legacy files,
|
||||||
if err != nil {
|
// since we only ever had b.Created as non-zero when
|
||||||
return b, err
|
// migration was complete in 2020-May release. So this
|
||||||
|
// a check to avoid migrating for buckets that already
|
||||||
|
// have this field set.
|
||||||
|
if b.Created.IsZero() {
|
||||||
|
configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(configs) > 0 {
|
||||||
|
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
||||||
|
if err = b.convertLegacyConfigs(ctx, objectAPI, configs); err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(configs) == 0 {
|
if parse {
|
||||||
if parse {
|
// nothing to update, parse and proceed.
|
||||||
// nothing to update, parse and proceed.
|
if err = b.parseAllConfigs(ctx, objectAPI); err != nil {
|
||||||
err = b.parseAllConfigs(ctx, objectAPI)
|
return b, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
|
||||||
err = b.convertLegacyConfigs(ctx, objectAPI, configs)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return b, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrate unencrypted remote targets
|
// migrate unencrypted remote targets
|
||||||
if err := b.migrateTargetConfig(ctx, objectAPI); err != nil {
|
if err = b.migrateTargetConfig(ctx, objectAPI); err != nil {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +339,7 @@ func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI Obje
|
|||||||
for _, legacyFile := range legacyConfigs {
|
for _, legacyFile := range legacyConfigs {
|
||||||
configFile := path.Join(bucketMetaPrefix, b.Name, legacyFile)
|
configFile := path.Join(bucketMetaPrefix, b.Name, legacyFile)
|
||||||
|
|
||||||
configData, err := readConfig(ctx, objectAPI, configFile)
|
configData, info, err := readConfigWithMetadata(ctx, objectAPI, configFile, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(ObjectExistsAsDirectory); ok {
|
if _, ok := err.(ObjectExistsAsDirectory); ok {
|
||||||
// in FS mode it possible that we have actual
|
// in FS mode it possible that we have actual
|
||||||
@@ -346,6 +354,7 @@ func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI Obje
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
configs[legacyFile] = configData
|
configs[legacyFile] = configData
|
||||||
|
b.Created = info.ModTime
|
||||||
}
|
}
|
||||||
|
|
||||||
return configs, nil
|
return configs, nil
|
||||||
@@ -391,7 +400,7 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
|||||||
for legacyFile := range configs {
|
for legacyFile := range configs {
|
||||||
configFile := path.Join(bucketMetaPrefix, b.Name, legacyFile)
|
configFile := path.Join(bucketMetaPrefix, b.Name, legacyFile)
|
||||||
if err := deleteConfig(ctx, objectAPI, configFile); err != nil && !errors.Is(err, errConfigNotFound) {
|
if err := deleteConfig(ctx, objectAPI, configFile); err != nil && !errors.Is(err, errConfigNotFound) {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +490,7 @@ func encryptBucketMetadata(ctx context.Context, bucket string, input []byte, kms
|
|||||||
}
|
}
|
||||||
|
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
key, err := GlobalKMS.GenerateKey(ctx, "", kmsContext)
|
key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{AssociatedData: kmsContext})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -510,7 +519,11 @@ func decryptBucketMetadata(input []byte, bucket string, meta map[string]string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extKey, err := GlobalKMS.DecryptKey(keyID, kmsKey, kmsContext)
|
extKey, err := GlobalKMS.Decrypt(context.TODO(), &kms.DecryptRequest{
|
||||||
|
Name: keyID,
|
||||||
|
Ciphertext: kmsKey,
|
||||||
|
AssociatedData: kmsContext,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -66,8 +66,9 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.SetRegion(globalSite.Region)
|
region := globalSite.Region()
|
||||||
if err = config.Validate(globalSite.Region, globalEventNotifier.targetList); err != nil {
|
config.SetRegion(region)
|
||||||
|
if err = config.Validate(region, globalEventNotifier.targetList); err != nil {
|
||||||
arnErr, ok := err.(*event.ErrARNNotFound)
|
arnErr, ok := err.(*event.ErrARNNotFound)
|
||||||
if ok {
|
if ok {
|
||||||
for i, queue := range config.QueueList {
|
for i, queue := range config.QueueList {
|
||||||
@@ -134,7 +135,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region, globalEventNotifier.targetList)
|
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region(), globalEventNotifier.targetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
||||||
if event.IsEventError(err) {
|
if event.IsEventError(err) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketObjectLockSys - map of bucket and retention configuration.
|
// BucketObjectLockSys - map of bucket and retention configuration.
|
||||||
@@ -44,7 +44,6 @@ func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention,
|
|||||||
if errors.Is(err, errInvalidArgument) {
|
if errors.Is(err, errInvalidArgument) {
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
logger.CriticalIf(context.Background(), err)
|
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
return config.ToRetention(), nil
|
return config.ToRetention(), nil
|
||||||
@@ -66,7 +65,7 @@ func enforceRetentionForDeletion(ctx context.Context, objInfo ObjectInfo) (locke
|
|||||||
if ret.Mode.Valid() && (ret.Mode == objectlock.RetCompliance || ret.Mode == objectlock.RetGovernance) {
|
if ret.Mode.Valid() && (ret.Mode == objectlock.RetCompliance || ret.Mode == objectlock.RetGovernance) {
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if ret.RetainUntilDate.After(t) {
|
if ret.RetainUntilDate.After(t) {
|
||||||
@@ -114,7 +113,7 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
|||||||
// duration of the retention period.
|
// duration of the retention period.
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
return ObjectLocked{}
|
return ObjectLocked{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +139,7 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
|||||||
if !byPassSet {
|
if !byPassSet {
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
return ObjectLocked{}
|
return ObjectLocked{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +169,7 @@ func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, oi Objec
|
|||||||
|
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
|
return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +276,7 @@ func checkPutObjectLockAllowed(ctx context.Context, rq *http.Request, bucket, ob
|
|||||||
r := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
r := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
if r.Mode == objectlock.RetCompliance && r.RetainUntilDate.After(t) {
|
if r.Mode == objectlock.RetCompliance && r.RetainUntilDate.After(t) {
|
||||||
@@ -324,7 +323,7 @@ func checkPutObjectLockAllowed(ctx context.Context, rq *http.Request, bucket, ob
|
|||||||
|
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
internalLogIf(ctx, err, logger.WarningKind)
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -113,7 +113,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
Type: madmin.SRBucketMetaTypePolicy,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Policy: bucketPolicyBytes,
|
Policy: bucketPolicyBytes,
|
||||||
@@ -157,7 +157,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
replLogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
Type: madmin.SRBucketMetaTypePolicy,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/policy/condition"
|
"github.com/minio/pkg/v3/policy/condition"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
||||||
|
|||||||
+10
-2
@@ -32,7 +32,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolicySys - policy subsystem.
|
// PolicySys - policy subsystem.
|
||||||
@@ -53,7 +53,7 @@ func (sys *PolicySys) IsAllowed(args policy.BucketPolicyArgs) bool {
|
|||||||
|
|
||||||
// Log unhandled errors.
|
// Log unhandled errors.
|
||||||
if _, ok := err.(BucketPolicyNotFound); !ok {
|
if _, ok := err.(BucketPolicyNotFound); !ok {
|
||||||
logger.LogIf(GlobalContext, err)
|
internalLogIf(GlobalContext, err, logger.WarningKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// As policy is not available for given bucket name, returns IsOwner i.e.
|
// As policy is not available for given bucket name, returns IsOwner i.e.
|
||||||
@@ -66,6 +66,14 @@ func NewPolicySys() *PolicySys {
|
|||||||
return &PolicySys{}
|
return &PolicySys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSTSConditionValues(r *http.Request, lc string, cred auth.Credentials) map[string][]string {
|
||||||
|
m := make(map[string][]string)
|
||||||
|
if d := r.Form.Get("DurationSeconds"); d != "" {
|
||||||
|
m["DurationSeconds"] = []string{d}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[string][]string {
|
func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[string][]string {
|
||||||
currTime := UTCNow()
|
currTime := UTCNow()
|
||||||
|
|
||||||
|
|||||||
+21
-24
@@ -25,6 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/cachevalue"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,44 +43,40 @@ func NewBucketQuotaSys() *BucketQuotaSys {
|
|||||||
return &BucketQuotaSys{}
|
return &BucketQuotaSys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var bucketStorageCache timedValue
|
var bucketStorageCache = cachevalue.New[DataUsageInfo]()
|
||||||
|
|
||||||
// Init initialize bucket quota.
|
// Init initialize bucket quota.
|
||||||
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
||||||
bucketStorageCache.Once.Do(func() {
|
bucketStorageCache.InitOnce(10*time.Second,
|
||||||
// Set this to 10 secs since its enough, as scanner
|
cachevalue.Opts{ReturnLastGood: true, NoWait: true},
|
||||||
// does not update the bucket usage values frequently.
|
func(ctx context.Context) (DataUsageInfo, error) {
|
||||||
bucketStorageCache.TTL = 10 * time.Second
|
ctx, done := context.WithTimeout(ctx, 2*time.Second)
|
||||||
// Rely on older value if usage loading fails from disk.
|
|
||||||
bucketStorageCache.Relax = true
|
|
||||||
bucketStorageCache.Update = func() (interface{}, error) {
|
|
||||||
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
return loadDataUsageFromBackend(ctx, objAPI)
|
return loadDataUsageFromBackend(ctx, objAPI)
|
||||||
}
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketUsageInfo return bucket usage info for a given bucket
|
// GetBucketUsageInfo return bucket usage info for a given bucket
|
||||||
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
|
func (sys *BucketQuotaSys) GetBucketUsageInfo(ctx context.Context, bucket string) (BucketUsageInfo, error) {
|
||||||
v, err := bucketStorageCache.Get()
|
dui, err := bucketStorageCache.GetWithCtx(ctx)
|
||||||
timedout := OperationTimedOut{}
|
timedout := OperationTimedOut{}
|
||||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
|
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
|
||||||
if v != nil {
|
if len(dui.BucketsUsage) > 0 {
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
|
internalLogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
|
||||||
} else {
|
} else {
|
||||||
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
|
internalLogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var bui BucketUsageInfo
|
if len(dui.BucketsUsage) > 0 {
|
||||||
dui, ok := v.(DataUsageInfo)
|
bui, ok := dui.BucketsUsage[bucket]
|
||||||
if ok {
|
if ok {
|
||||||
bui = dui.BucketsUsage[bucket]
|
return bui, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return BucketUsageInfo{}, nil
|
||||||
return bui, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseBucketQuota parses BucketQuota from json
|
// parseBucketQuota parses BucketQuota from json
|
||||||
@@ -90,7 +87,7 @@ func parseBucketQuota(bucket string, data []byte) (quotaCfg *madmin.BucketQuota,
|
|||||||
}
|
}
|
||||||
if !quotaCfg.IsValid() {
|
if !quotaCfg.IsValid() {
|
||||||
if quotaCfg.Type == "fifo" {
|
if quotaCfg.Type == "fifo" {
|
||||||
logger.LogIf(GlobalContext, errors.New("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore. Please clear your quota configs using 'mc admin bucket quota alias/bucket --clear' and use 'mc ilm add' for expiration of objects"))
|
internalLogIf(GlobalContext, errors.New("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore. Please clear your quota configs using 'mc admin bucket quota alias/bucket --clear' and use 'mc ilm add' for expiration of objects"), logger.WarningKind)
|
||||||
return quotaCfg, fmt.Errorf("invalid quota type 'fifo'")
|
return quotaCfg, fmt.Errorf("invalid quota type 'fifo'")
|
||||||
}
|
}
|
||||||
return quotaCfg, fmt.Errorf("Invalid quota config %#v", quotaCfg)
|
return quotaCfg, fmt.Errorf("Invalid quota config %#v", quotaCfg)
|
||||||
@@ -121,7 +118,7 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
|
|||||||
return BucketQuotaExceeded{Bucket: bucket}
|
return BucketQuotaExceeded{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
bui, err := sys.GetBucketUsageInfo(bucket)
|
bui, err := sys.GetBucketUsageInfo(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
||||||
|
|||||||
@@ -379,3 +379,145 @@ func (s *SMA) simpleMovingAvg() float64 {
|
|||||||
const (
|
const (
|
||||||
defaultWindowSize = 10
|
defaultWindowSize = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type proxyStatsCache struct {
|
||||||
|
srProxyStats ProxyMetric
|
||||||
|
bucketStats map[string]ProxyMetric
|
||||||
|
sync.RWMutex // mutex for proxy stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProxyStatsCache() proxyStatsCache {
|
||||||
|
return proxyStatsCache{
|
||||||
|
bucketStats: make(map[string]ProxyMetric),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxyStatsCache) inc(bucket string, api replProxyAPI, isErr bool) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
v, ok := p.bucketStats[bucket]
|
||||||
|
if !ok {
|
||||||
|
v = ProxyMetric{}
|
||||||
|
}
|
||||||
|
switch api {
|
||||||
|
case putObjectTaggingAPI:
|
||||||
|
if !isErr {
|
||||||
|
atomic.AddUint64(&v.PutTagTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.PutTagTotal, 1)
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&v.PutTagFailedTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.PutTagFailedTotal, 1)
|
||||||
|
}
|
||||||
|
case getObjectTaggingAPI:
|
||||||
|
if !isErr {
|
||||||
|
atomic.AddUint64(&v.GetTagTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.GetTagTotal, 1)
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&v.GetTagFailedTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.GetTagFailedTotal, 1)
|
||||||
|
}
|
||||||
|
case removeObjectTaggingAPI:
|
||||||
|
if !isErr {
|
||||||
|
atomic.AddUint64(&v.RmvTagTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.RmvTagTotal, 1)
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&v.RmvTagFailedTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.RmvTagFailedTotal, 1)
|
||||||
|
}
|
||||||
|
case headObjectAPI:
|
||||||
|
if !isErr {
|
||||||
|
atomic.AddUint64(&v.HeadTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.HeadTotal, 1)
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&v.HeadFailedTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.HeadFailedTotal, 1)
|
||||||
|
}
|
||||||
|
case getObjectAPI:
|
||||||
|
if !isErr {
|
||||||
|
atomic.AddUint64(&v.GetTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.GetTotal, 1)
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&v.GetFailedTotal, 1)
|
||||||
|
atomic.AddUint64(&p.srProxyStats.GetFailedTotal, 1)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.bucketStats[bucket] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxyStatsCache) getBucketStats(bucket string) ProxyMetric {
|
||||||
|
p.RLock()
|
||||||
|
defer p.RUnlock()
|
||||||
|
v, ok := p.bucketStats[bucket]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return ProxyMetric{}
|
||||||
|
}
|
||||||
|
return ProxyMetric{
|
||||||
|
PutTagTotal: atomic.LoadUint64(&v.PutTagTotal),
|
||||||
|
GetTagTotal: atomic.LoadUint64(&v.GetTagTotal),
|
||||||
|
RmvTagTotal: atomic.LoadUint64(&v.RmvTagTotal),
|
||||||
|
HeadTotal: atomic.LoadUint64(&v.HeadTotal),
|
||||||
|
GetTotal: atomic.LoadUint64(&v.GetTotal),
|
||||||
|
|
||||||
|
PutTagFailedTotal: atomic.LoadUint64(&v.PutTagFailedTotal),
|
||||||
|
GetTagFailedTotal: atomic.LoadUint64(&v.GetTagFailedTotal),
|
||||||
|
RmvTagFailedTotal: atomic.LoadUint64(&v.RmvTagFailedTotal),
|
||||||
|
HeadFailedTotal: atomic.LoadUint64(&v.HeadFailedTotal),
|
||||||
|
GetFailedTotal: atomic.LoadUint64(&v.GetFailedTotal),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxyStatsCache) getSiteStats() ProxyMetric {
|
||||||
|
v := p.srProxyStats
|
||||||
|
return ProxyMetric{
|
||||||
|
PutTagTotal: atomic.LoadUint64(&v.PutTagTotal),
|
||||||
|
GetTagTotal: atomic.LoadUint64(&v.GetTagTotal),
|
||||||
|
RmvTagTotal: atomic.LoadUint64(&v.RmvTagTotal),
|
||||||
|
HeadTotal: atomic.LoadUint64(&v.HeadTotal),
|
||||||
|
GetTotal: atomic.LoadUint64(&v.GetTotal),
|
||||||
|
PutTagFailedTotal: atomic.LoadUint64(&v.PutTagFailedTotal),
|
||||||
|
GetTagFailedTotal: atomic.LoadUint64(&v.GetTagFailedTotal),
|
||||||
|
RmvTagFailedTotal: atomic.LoadUint64(&v.RmvTagFailedTotal),
|
||||||
|
HeadFailedTotal: atomic.LoadUint64(&v.HeadFailedTotal),
|
||||||
|
GetFailedTotal: atomic.LoadUint64(&v.GetFailedTotal),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type replProxyAPI string
|
||||||
|
|
||||||
|
const (
|
||||||
|
putObjectTaggingAPI replProxyAPI = "PutObjectTagging"
|
||||||
|
getObjectTaggingAPI replProxyAPI = "GetObjectTagging"
|
||||||
|
removeObjectTaggingAPI replProxyAPI = "RemoveObjectTagging"
|
||||||
|
headObjectAPI replProxyAPI = "HeadObject"
|
||||||
|
getObjectAPI replProxyAPI = "GetObject"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProxyMetric holds stats for replication proxying
|
||||||
|
type ProxyMetric struct {
|
||||||
|
PutTagTotal uint64 `json:"putTaggingProxyTotal" msg:"ptc"`
|
||||||
|
GetTagTotal uint64 `json:"getTaggingProxyTotal" msg:"gtc"`
|
||||||
|
RmvTagTotal uint64 `json:"removeTaggingProxyTotal" msg:"rtc"`
|
||||||
|
GetTotal uint64 `json:"getProxyTotal" msg:"gc"`
|
||||||
|
HeadTotal uint64 `json:"headProxyTotal" msg:"hc"`
|
||||||
|
PutTagFailedTotal uint64 `json:"putTaggingProxyFailed" msg:"ptf"`
|
||||||
|
GetTagFailedTotal uint64 `json:"getTaggingProxyFailed" msg:"gtf"`
|
||||||
|
RmvTagFailedTotal uint64 `json:"removeTaggingProxyFailed" msg:"rtf"`
|
||||||
|
GetFailedTotal uint64 `json:"getProxyFailed" msg:"gf"`
|
||||||
|
HeadFailedTotal uint64 `json:"headProxyFailed" msg:"hf"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProxyMetric) add(p2 ProxyMetric) {
|
||||||
|
atomic.AddUint64(&p.GetTotal, p2.GetTotal)
|
||||||
|
atomic.AddUint64(&p.HeadTotal, p2.HeadTotal)
|
||||||
|
atomic.AddUint64(&p.GetTagTotal, p2.GetTagTotal)
|
||||||
|
atomic.AddUint64(&p.PutTagTotal, p2.PutTagTotal)
|
||||||
|
atomic.AddUint64(&p.RmvTagTotal, p2.RmvTagTotal)
|
||||||
|
atomic.AddUint64(&p.GetFailedTotal, p2.GetFailedTotal)
|
||||||
|
atomic.AddUint64(&p.HeadFailedTotal, p2.HeadFailedTotal)
|
||||||
|
atomic.AddUint64(&p.GetTagFailedTotal, p2.GetTagFailedTotal)
|
||||||
|
atomic.AddUint64(&p.PutTagFailedTotal, p2.PutTagFailedTotal)
|
||||||
|
atomic.AddUint64(&p.RmvTagFailedTotal, p2.RmvTagFailedTotal)
|
||||||
|
}
|
||||||
|
|||||||
@@ -584,6 +584,7 @@ func (z *InQueueStats) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z InQueueStats) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z InQueueStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 0
|
// map header, size 0
|
||||||
|
_ = z
|
||||||
err = en.Append(0x80)
|
err = en.Append(0x80)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -595,6 +596,7 @@ func (z InQueueStats) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
func (z InQueueStats) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z InQueueStats) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 0
|
// map header, size 0
|
||||||
|
_ = z
|
||||||
o = append(o, 0x80)
|
o = append(o, 0x80)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -635,6 +637,334 @@ func (z InQueueStats) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *ProxyMetric) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ptc":
|
||||||
|
z.PutTagTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PutTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gtc":
|
||||||
|
z.GetTagTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "rtc":
|
||||||
|
z.RmvTagTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RmvTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gc":
|
||||||
|
z.GetTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "hc":
|
||||||
|
z.HeadTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HeadTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ptf":
|
||||||
|
z.PutTagFailedTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PutTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gtf":
|
||||||
|
z.GetTagFailedTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "rtf":
|
||||||
|
z.RmvTagFailedTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RmvTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gf":
|
||||||
|
z.GetFailedTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "hf":
|
||||||
|
z.HeadFailedTotal, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HeadFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *ProxyMetric) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 10
|
||||||
|
// write "ptc"
|
||||||
|
err = en.Append(0x8a, 0xa3, 0x70, 0x74, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.PutTagTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PutTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "gtc"
|
||||||
|
err = en.Append(0xa3, 0x67, 0x74, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.GetTagTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "rtc"
|
||||||
|
err = en.Append(0xa3, 0x72, 0x74, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.RmvTagTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RmvTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "gc"
|
||||||
|
err = en.Append(0xa2, 0x67, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.GetTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "hc"
|
||||||
|
err = en.Append(0xa2, 0x68, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.HeadTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HeadTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ptf"
|
||||||
|
err = en.Append(0xa3, 0x70, 0x74, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.PutTagFailedTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PutTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "gtf"
|
||||||
|
err = en.Append(0xa3, 0x67, 0x74, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.GetTagFailedTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "rtf"
|
||||||
|
err = en.Append(0xa3, 0x72, 0x74, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.RmvTagFailedTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RmvTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "gf"
|
||||||
|
err = en.Append(0xa2, 0x67, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.GetFailedTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "hf"
|
||||||
|
err = en.Append(0xa2, 0x68, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.HeadFailedTotal)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HeadFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *ProxyMetric) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 10
|
||||||
|
// string "ptc"
|
||||||
|
o = append(o, 0x8a, 0xa3, 0x70, 0x74, 0x63)
|
||||||
|
o = msgp.AppendUint64(o, z.PutTagTotal)
|
||||||
|
// string "gtc"
|
||||||
|
o = append(o, 0xa3, 0x67, 0x74, 0x63)
|
||||||
|
o = msgp.AppendUint64(o, z.GetTagTotal)
|
||||||
|
// string "rtc"
|
||||||
|
o = append(o, 0xa3, 0x72, 0x74, 0x63)
|
||||||
|
o = msgp.AppendUint64(o, z.RmvTagTotal)
|
||||||
|
// string "gc"
|
||||||
|
o = append(o, 0xa2, 0x67, 0x63)
|
||||||
|
o = msgp.AppendUint64(o, z.GetTotal)
|
||||||
|
// string "hc"
|
||||||
|
o = append(o, 0xa2, 0x68, 0x63)
|
||||||
|
o = msgp.AppendUint64(o, z.HeadTotal)
|
||||||
|
// string "ptf"
|
||||||
|
o = append(o, 0xa3, 0x70, 0x74, 0x66)
|
||||||
|
o = msgp.AppendUint64(o, z.PutTagFailedTotal)
|
||||||
|
// string "gtf"
|
||||||
|
o = append(o, 0xa3, 0x67, 0x74, 0x66)
|
||||||
|
o = msgp.AppendUint64(o, z.GetTagFailedTotal)
|
||||||
|
// string "rtf"
|
||||||
|
o = append(o, 0xa3, 0x72, 0x74, 0x66)
|
||||||
|
o = msgp.AppendUint64(o, z.RmvTagFailedTotal)
|
||||||
|
// string "gf"
|
||||||
|
o = append(o, 0xa2, 0x67, 0x66)
|
||||||
|
o = msgp.AppendUint64(o, z.GetFailedTotal)
|
||||||
|
// string "hf"
|
||||||
|
o = append(o, 0xa2, 0x68, 0x66)
|
||||||
|
o = msgp.AppendUint64(o, z.HeadFailedTotal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *ProxyMetric) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ptc":
|
||||||
|
z.PutTagTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PutTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gtc":
|
||||||
|
z.GetTagTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "rtc":
|
||||||
|
z.RmvTagTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RmvTagTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gc":
|
||||||
|
z.GetTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "hc":
|
||||||
|
z.HeadTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HeadTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ptf":
|
||||||
|
z.PutTagFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PutTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gtf":
|
||||||
|
z.GetTagFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "rtf":
|
||||||
|
z.RmvTagFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RmvTagFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "gf":
|
||||||
|
z.GetFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "GetFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "hf":
|
||||||
|
z.HeadFailedTotal, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HeadFailedTotal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *ProxyMetric) Msgsize() (s int) {
|
||||||
|
s = 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 3 + msgp.Uint64Size + 3 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 3 + msgp.Uint64Size + 3 + msgp.Uint64Size
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *QStat) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *QStat) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
var field []byte
|
var field []byte
|
||||||
|
|||||||
@@ -348,6 +348,119 @@ func BenchmarkDecodeInQueueStats(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalProxyMetric(t *testing.T) {
|
||||||
|
v := ProxyMetric{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgProxyMetric(b *testing.B) {
|
||||||
|
v := ProxyMetric{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgProxyMetric(b *testing.B) {
|
||||||
|
v := ProxyMetric{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalProxyMetric(b *testing.B) {
|
||||||
|
v := ProxyMetric{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeProxyMetric(t *testing.T) {
|
||||||
|
v := ProxyMetric{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeProxyMetric Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := ProxyMetric{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeProxyMetric(b *testing.B) {
|
||||||
|
v := ProxyMetric{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeProxyMetric(b *testing.B) {
|
||||||
|
v := ProxyMetric{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalQStat(t *testing.T) {
|
func TestMarshalUnmarshalQStat(t *testing.T) {
|
||||||
v := QStat{}
|
v := QStat{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type ReplicationStats struct {
|
|||||||
workers *ActiveWorkerStat
|
workers *ActiveWorkerStat
|
||||||
// queue stats cache
|
// queue stats cache
|
||||||
qCache queueCache
|
qCache queueCache
|
||||||
|
|
||||||
|
pCache proxyStatsCache
|
||||||
// mrf backlog stats
|
// mrf backlog stats
|
||||||
mrfStats ReplicationMRFStats
|
mrfStats ReplicationMRFStats
|
||||||
// for bucket replication, continue to use existing cache
|
// for bucket replication, continue to use existing cache
|
||||||
@@ -305,6 +307,7 @@ func (r *ReplicationStats) getSRMetricsForNode() SRMetricsSummary {
|
|||||||
Queued: r.qCache.getSiteStats(),
|
Queued: r.qCache.getSiteStats(),
|
||||||
ActiveWorkers: r.ActiveWorkers(),
|
ActiveWorkers: r.ActiveWorkers(),
|
||||||
Metrics: r.srStats.get(),
|
Metrics: r.srStats.get(),
|
||||||
|
Proxied: r.pCache.getSiteStats(),
|
||||||
ReplicaSize: atomic.LoadInt64(&r.srStats.ReplicaSize),
|
ReplicaSize: atomic.LoadInt64(&r.srStats.ReplicaSize),
|
||||||
ReplicaCount: atomic.LoadInt64(&r.srStats.ReplicaCount),
|
ReplicaCount: atomic.LoadInt64(&r.srStats.ReplicaCount),
|
||||||
}
|
}
|
||||||
@@ -333,6 +336,7 @@ func NewReplicationStats(ctx context.Context, objectAPI ObjectLayer) *Replicatio
|
|||||||
rs := ReplicationStats{
|
rs := ReplicationStats{
|
||||||
Cache: make(map[string]*BucketReplicationStats),
|
Cache: make(map[string]*BucketReplicationStats),
|
||||||
qCache: newQueueCache(r),
|
qCache: newQueueCache(r),
|
||||||
|
pCache: newProxyStatsCache(),
|
||||||
srStats: newSRStats(),
|
srStats: newSRStats(),
|
||||||
movingAvgTicker: time.NewTicker(2 * time.Second),
|
movingAvgTicker: time.NewTicker(2 * time.Second),
|
||||||
wTimer: time.NewTicker(2 * time.Second),
|
wTimer: time.NewTicker(2 * time.Second),
|
||||||
@@ -371,6 +375,7 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, bucket
|
|||||||
Stats: make(map[string]*BucketReplicationStat),
|
Stats: make(map[string]*BucketReplicationStat),
|
||||||
},
|
},
|
||||||
QueueStats: ReplicationQueueStats{},
|
QueueStats: ReplicationQueueStats{},
|
||||||
|
ProxyStats: ProxyMetric{},
|
||||||
}
|
}
|
||||||
return bs
|
return bs
|
||||||
}
|
}
|
||||||
@@ -430,11 +435,16 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, bucket
|
|||||||
for _, bs := range bucketStats {
|
for _, bs := range bucketStats {
|
||||||
qs.Nodes = append(qs.Nodes, bs.QueueStats.Nodes...)
|
qs.Nodes = append(qs.Nodes, bs.QueueStats.Nodes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
qs.Uptime = UTCNow().Unix() - globalBootTime.Unix()
|
qs.Uptime = UTCNow().Unix() - globalBootTime.Unix()
|
||||||
|
|
||||||
|
var ps ProxyMetric
|
||||||
|
for _, bs := range bucketStats {
|
||||||
|
ps.add(bs.ProxyStats)
|
||||||
|
}
|
||||||
bs = BucketStats{
|
bs = BucketStats{
|
||||||
ReplicationStats: s,
|
ReplicationStats: s,
|
||||||
QueueStats: qs,
|
QueueStats: qs,
|
||||||
|
ProxyStats: ps,
|
||||||
}
|
}
|
||||||
r.mostRecentStatsMu.Lock()
|
r.mostRecentStatsMu.Lock()
|
||||||
if len(r.mostRecentStats.Stats) == 0 {
|
if len(r.mostRecentStats.Stats) == 0 {
|
||||||
@@ -454,7 +464,7 @@ func (r *ReplicationStats) getLatestReplicationStats(bucket string) (s BucketSta
|
|||||||
return r.calculateBucketReplicationStats(bucket, bucketStats)
|
return r.calculateBucketReplicationStats(bucket, bucketStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReplicationStats) incQ(bucket string, sz int64, isDeleleRepl bool, opType replication.Type) {
|
func (r *ReplicationStats) incQ(bucket string, sz int64, isDeleteRepl bool, opType replication.Type) {
|
||||||
r.qCache.Lock()
|
r.qCache.Lock()
|
||||||
defer r.qCache.Unlock()
|
defer r.qCache.Unlock()
|
||||||
v, ok := r.qCache.bucketStats[bucket]
|
v, ok := r.qCache.bucketStats[bucket]
|
||||||
@@ -482,3 +492,12 @@ func (r *ReplicationStats) decQ(bucket string, sz int64, isDelMarker bool, opTyp
|
|||||||
atomic.AddInt64(&r.qCache.srQueueStats.nowBytes, -1*sz)
|
atomic.AddInt64(&r.qCache.srQueueStats.nowBytes, -1*sz)
|
||||||
atomic.AddInt64(&r.qCache.srQueueStats.nowCount, -1)
|
atomic.AddInt64(&r.qCache.srQueueStats.nowCount, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// incProxy increments proxy metrics for proxied calls
|
||||||
|
func (r *ReplicationStats) incProxy(bucket string, api replProxyAPI, isErr bool) {
|
||||||
|
r.pCache.inc(bucket, api, isErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) getProxyStats(bucket string) ProxyMetric {
|
||||||
|
return r.pCache.getBucketStats(bucket)
|
||||||
|
}
|
||||||
|
|||||||
@@ -749,6 +749,7 @@ func (z *ReplicateDecision) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z ReplicateDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z ReplicateDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 0
|
// map header, size 0
|
||||||
|
_ = z
|
||||||
err = en.Append(0x80)
|
err = en.Append(0x80)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -760,6 +761,7 @@ func (z ReplicateDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
func (z ReplicateDecision) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z ReplicateDecision) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 0
|
// map header, size 0
|
||||||
|
_ = z
|
||||||
o = append(o, 0x80)
|
o = append(o, 0x80)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1388,6 +1390,7 @@ func (z *ResyncDecision) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z ResyncDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z ResyncDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 0
|
// map header, size 0
|
||||||
|
_ = z
|
||||||
err = en.Append(0x80)
|
err = en.Append(0x80)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -1399,6 +1402,7 @@ func (z ResyncDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
func (z ResyncDecision) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z ResyncDecision) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 0
|
// map header, size 0
|
||||||
|
_ = z
|
||||||
o = append(o, 0x80)
|
o = append(o, 0x80)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+168
-120
@@ -51,6 +51,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/tinylib/msgp/msgp"
|
"github.com/tinylib/msgp/msgp"
|
||||||
"github.com/zeebo/xxh3"
|
"github.com/zeebo/xxh3"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -76,21 +78,13 @@ const (
|
|||||||
ReplicationWorkerMultiplier = 1.5
|
ReplicationWorkerMultiplier = 1.5
|
||||||
)
|
)
|
||||||
|
|
||||||
func isReplicationEnabled(ctx context.Context, bucketName string) bool {
|
|
||||||
rc, _ := getReplicationConfig(ctx, bucketName)
|
|
||||||
return rc != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets replication config associated to a given bucket name.
|
// gets replication config associated to a given bucket name.
|
||||||
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||||
rCfg, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName)
|
rCfg, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, BucketReplicationConfigNotFound{Bucket: bucketName}) {
|
||||||
if errors.Is(err, BucketReplicationConfigNotFound{Bucket: bucketName}) || errors.Is(err, errInvalidArgument) {
|
return rCfg, err
|
||||||
return rCfg, err
|
|
||||||
}
|
|
||||||
logger.CriticalIf(ctx, err)
|
|
||||||
}
|
}
|
||||||
return rCfg, err
|
return rCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateReplicationDestination returns error if replication destination bucket missing or not configured
|
// validateReplicationDestination returns error if replication destination bucket missing or not configured
|
||||||
@@ -179,7 +173,7 @@ func checkRemoteEndpoint(ctx context.Context, epURL *url.URL) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: NewHTTPTransport(),
|
Transport: globalRemoteTargetTransport,
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,10 +258,16 @@ func mustReplicate(ctx context.Context, bucket, object string, mopts mustReplica
|
|||||||
if mopts.replicationRequest { // incoming replication request on target cluster
|
if mopts.replicationRequest { // incoming replication request on target cluster
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
replLogOnceIf(ctx, err, bucket)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
opts := replication.ObjectOpts{
|
opts := replication.ObjectOpts{
|
||||||
Name: object,
|
Name: object,
|
||||||
SSEC: crypto.SSEC.IsEncrypted(mopts.meta),
|
SSEC: crypto.SSEC.IsEncrypted(mopts.meta),
|
||||||
@@ -315,6 +315,7 @@ var standardHeaders = []string{
|
|||||||
func hasReplicationRules(ctx context.Context, bucket string, objects []ObjectToDelete) bool {
|
func hasReplicationRules(ctx context.Context, bucket string, objects []ObjectToDelete) bool {
|
||||||
c, err := getReplicationConfig(ctx, bucket)
|
c, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil || c == nil {
|
if err != nil || c == nil {
|
||||||
|
replLogOnceIf(ctx, err, bucket)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, obj := range objects {
|
for _, obj := range objects {
|
||||||
@@ -334,6 +335,7 @@ func isStandardHeader(matchHeaderKey string) bool {
|
|||||||
func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelete, oi ObjectInfo, delOpts ObjectOptions, gerr error) (dsc ReplicateDecision) {
|
func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelete, oi ObjectInfo, delOpts ObjectOptions, gerr error) (dsc ReplicateDecision) {
|
||||||
rcfg, err := getReplicationConfig(ctx, bucket)
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil || rcfg == nil {
|
if err != nil || rcfg == nil {
|
||||||
|
replLogOnceIf(ctx, err, bucket)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If incoming request is a replication request, it does not need to be re-replicated.
|
// If incoming request is a replication request, it does not need to be re-replicated.
|
||||||
@@ -426,7 +428,7 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
|||||||
|
|
||||||
rcfg, err := getReplicationConfig(ctx, bucket)
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil || rcfg == nil {
|
if err != nil || rcfg == nil {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("unable to obtain replication config for bucket: %s: err: %s", bucket, err), bucket)
|
replLogOnceIf(ctx, fmt.Errorf("unable to obtain replication config for bucket: %s: err: %s", bucket, err), bucket)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: ObjectInfo{
|
Object: ObjectInfo{
|
||||||
@@ -443,7 +445,7 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
|||||||
}
|
}
|
||||||
dsc, err := parseReplicateDecision(ctx, bucket, dobj.ReplicationState.ReplicateDecisionStr)
|
dsc, err := parseReplicateDecision(ctx, bucket, dobj.ReplicationState.ReplicateDecisionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("unable to parse replication decision parameters for bucket: %s, err: %s, decision: %s",
|
replLogOnceIf(ctx, fmt.Errorf("unable to parse replication decision parameters for bucket: %s, err: %s, decision: %s",
|
||||||
bucket, err, dobj.ReplicationState.ReplicateDecisionStr), dobj.ReplicationState.ReplicateDecisionStr)
|
bucket, err, dobj.ReplicationState.ReplicateDecisionStr), dobj.ReplicationState.ReplicateDecisionStr)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -497,7 +499,7 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
|||||||
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtEntry.Arn)
|
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtEntry.Arn)
|
||||||
if tgtClnt == nil {
|
if tgtClnt == nil {
|
||||||
// Skip stale targets if any and log them to be missing at least once.
|
// Skip stale targets if any and log them to be missing at least once.
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtEntry.Arn), tgtEntry.Arn)
|
replLogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtEntry.Arn), tgtEntry.Arn)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -609,7 +611,7 @@ func replicateDeleteToTarget(ctx context.Context, dobj DeletedObjectReplicationI
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("remote target is offline for bucket:%s arn:%s", dobj.Bucket, tgt.ARN), "replication-target-offline-delete-"+tgt.ARN)
|
replLogOnceIf(ctx, fmt.Errorf("remote target is offline for bucket:%s arn:%s", dobj.Bucket, tgt.ARN), "replication-target-offline-delete-"+tgt.ARN)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
BucketName: dobj.Bucket,
|
BucketName: dobj.Bucket,
|
||||||
Object: ObjectInfo{
|
Object: ObjectInfo{
|
||||||
@@ -684,7 +686,7 @@ func replicateDeleteToTarget(ctx context.Context, dobj DeletedObjectReplicationI
|
|||||||
} else {
|
} else {
|
||||||
rinfo.VersionPurgeStatus = Failed
|
rinfo.VersionPurgeStatus = Failed
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate delete marker to %s: %s/%s(%s): %w", tgt.EndpointURL(), tgt.Bucket, dobj.ObjectName, versionID, rmErr))
|
replLogIf(ctx, fmt.Errorf("unable to replicate delete marker to %s: %s/%s(%s): %w", tgt.EndpointURL(), tgt.Bucket, dobj.ObjectName, versionID, rmErr))
|
||||||
if rmErr != nil && minio.IsNetworkOrHostDown(rmErr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if rmErr != nil && minio.IsNetworkOrHostDown(rmErr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||||
}
|
}
|
||||||
@@ -764,13 +766,20 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
|
|||||||
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) {
|
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
// In case of SSE-C objects copy the allowed internal headers as well
|
||||||
continue
|
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||||
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isStandardHeader(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if isStandardHeader(k) {
|
if slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||||
continue
|
meta[validSSEReplicationHeaders[k]] = v
|
||||||
|
} else {
|
||||||
|
meta[k] = v
|
||||||
}
|
}
|
||||||
meta[k] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
||||||
@@ -989,8 +998,8 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
object := ri.Name
|
object := ri.Name
|
||||||
|
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil || cfg == nil {
|
||||||
logger.LogOnceIf(ctx, err, "get-replication-config-"+bucket)
|
replLogOnceIf(ctx, err, "get-replication-config-"+bucket)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1029,7 +1038,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
for _, tgtArn := range tgtArns {
|
for _, tgtArn := range tgtArns {
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtArn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtArn)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn)
|
replLogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1151,7 +1160,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
}
|
}
|
||||||
|
|
||||||
if globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("remote target is offline for bucket:%s arn:%s retry:%d", bucket, tgt.ARN, ri.RetryCount), "replication-target-offline"+tgt.ARN)
|
replLogOnceIf(ctx, fmt.Errorf("remote target is offline for bucket:%s arn:%s retry:%d", bucket, tgt.ARN, ri.RetryCount), "replication-target-offline"+tgt.ARN)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1166,9 +1175,10 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
|
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
||||||
VersionID: ri.VersionID,
|
VersionID: ri.VersionID,
|
||||||
Versioned: versioned,
|
Versioned: versioned,
|
||||||
VersionSuspended: versionSuspended,
|
VersionSuspended: versionSuspended,
|
||||||
|
ReplicationRequest: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
@@ -1180,7 +1190,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
UserAgent: "Internal: [Replication]",
|
UserAgent: "Internal: [Replication]",
|
||||||
Host: globalLocalNodeName,
|
Host: globalLocalNodeName,
|
||||||
})
|
})
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("unable to read source object %s/%s(%s): %w", bucket, object, objInfo.VersionID, err), object+":"+objInfo.VersionID)
|
replLogOnceIf(ctx, fmt.Errorf("unable to read source object %s/%s(%s): %w", bucket, object, objInfo.VersionID, err), object+":"+objInfo.VersionID)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1193,7 +1203,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
|
|
||||||
size, err := objInfo.GetActualSize()
|
size, err := objInfo.GetActualSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
replLogIf(ctx, err)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1205,7 +1215,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tgt.Bucket == "" {
|
if tgt.Bucket == "" {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s), bucket is empty for target %s", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
replLogIf(ctx, fmt.Errorf("unable to replicate object %s(%s), bucket is empty for target %s", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1231,7 +1241,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
|
|
||||||
putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err))
|
replLogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1262,23 +1272,19 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
}
|
}
|
||||||
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
|
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
|
||||||
if objInfo.isMultipart() {
|
if objInfo.isMultipart() {
|
||||||
if rinfo.Err = replicateObjectWithMultipart(ctx, c, tgt.Bucket, object,
|
rinfo.Err = replicateObjectWithMultipart(ctx, c, tgt.Bucket, object, r, objInfo, putOpts)
|
||||||
r, objInfo, putOpts); rinfo.Err != nil {
|
|
||||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s (target: %s)", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err, tgt.EndpointURL()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if _, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts); rinfo.Err != nil {
|
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
|
||||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s (target: %s)", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err, tgt.EndpointURL()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if rinfo.Err != nil && minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if rinfo.Err != nil {
|
||||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||||
|
rinfo.ReplicationStatus = replication.Failed
|
||||||
|
replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): to (target: %s): %w",
|
||||||
|
bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||||
|
}
|
||||||
|
if minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
|
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1308,7 +1314,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
|
|
||||||
if globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("remote target is offline for bucket:%s arn:%s retry:%d", bucket, tgt.ARN, ri.RetryCount), "replication-target-offline-heal"+tgt.ARN)
|
replLogOnceIf(ctx, fmt.Errorf("remote target is offline for bucket:%s arn:%s retry:%d", bucket, tgt.ARN, ri.RetryCount), "replication-target-offline-heal"+tgt.ARN)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1322,11 +1328,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
||||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
|
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{},
|
||||||
VersionID: ri.VersionID,
|
ObjectOptions{
|
||||||
Versioned: versioned,
|
VersionID: ri.VersionID,
|
||||||
VersionSuspended: versionSuspended,
|
Versioned: versioned,
|
||||||
})
|
VersionSuspended: versionSuspended,
|
||||||
|
ReplicationRequest: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
objInfo := ri.ToObjectInfo()
|
objInfo := ri.ToObjectInfo()
|
||||||
@@ -1337,13 +1345,14 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
UserAgent: "Internal: [Replication]",
|
UserAgent: "Internal: [Replication]",
|
||||||
Host: globalLocalNodeName,
|
Host: globalLocalNodeName,
|
||||||
})
|
})
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate to target %s for %s/%s(%s): %w", tgt.EndpointURL(), bucket, object, objInfo.VersionID, err))
|
replLogIf(ctx, fmt.Errorf("unable to replicate to target %s for %s/%s(%s): %w", tgt.EndpointURL(), bucket, object, objInfo.VersionID, err))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer gr.Close()
|
defer gr.Close()
|
||||||
|
|
||||||
objInfo := gr.ObjInfo
|
objInfo := gr.ObjInfo
|
||||||
|
|
||||||
// make sure we have the latest metadata for metrics calculation
|
// make sure we have the latest metadata for metrics calculation
|
||||||
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
||||||
|
|
||||||
@@ -1356,7 +1365,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
|
|
||||||
size, err := objInfo.GetActualSize()
|
size, err := objInfo.GetActualSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
replLogIf(ctx, err)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1367,8 +1376,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the encrypted size for SSE-C objects
|
||||||
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||||
|
size = objInfo.Size
|
||||||
|
}
|
||||||
|
|
||||||
if tgt.Bucket == "" {
|
if tgt.Bucket == "" {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s) to %s, target bucket is missing", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
replLogIf(ctx, fmt.Errorf("unable to replicate object %s(%s) to %s, target bucket is missing", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1398,7 +1412,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
if rAction == replicateNone {
|
if rAction == replicateNone {
|
||||||
if ri.OpType == replication.ExistingObjectReplicationType &&
|
if ri.OpType == replication.ExistingObjectReplicationType &&
|
||||||
objInfo.ModTime.Unix() > oi.LastModified.Unix() && objInfo.VersionID == nullVersionID {
|
objInfo.ModTime.Unix() > oi.LastModified.Unix() && objInfo.VersionID == nullVersionID {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate %s/%s (null). Newer version exists on target %s", bucket, object, tgt.EndpointURL()))
|
replLogIf(ctx, fmt.Errorf("unable to replicate %s/%s (null). Newer version exists on target %s", bucket, object, tgt.EndpointURL()))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1438,7 +1452,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
rAction = replicateAll
|
rAction = replicateAll
|
||||||
default:
|
default:
|
||||||
rinfo.Err = cerr
|
rinfo.Err = cerr
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate %s/%s (%s). Target (%s) returned %s error on HEAD",
|
replLogIf(ctx, fmt.Errorf("unable to replicate %s/%s (%s). Target (%s) returned %s error on HEAD",
|
||||||
bucket, object, objInfo.VersionID, tgt.EndpointURL(), cerr))
|
bucket, object, objInfo.VersionID, tgt.EndpointURL(), cerr))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
@@ -1488,13 +1502,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
if _, rinfo.Err = c.CopyObject(ctx, tgt.Bucket, object, tgt.Bucket, object, getCopyObjMetadata(objInfo, tgt.StorageClass), srcOpts, dstOpts); rinfo.Err != nil {
|
if _, rinfo.Err = c.CopyObject(ctx, tgt.Bucket, object, tgt.Bucket, object, getCopyObjMetadata(objInfo, tgt.StorageClass), srcOpts, dstOpts); rinfo.Err != nil {
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
rinfo.ReplicationStatus = replication.Failed
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
replLogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var putOpts minio.PutObjectOptions
|
var putOpts minio.PutObjectOptions
|
||||||
putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err))
|
replLogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1524,27 +1538,19 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
|
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
|
||||||
if objInfo.isMultipart() {
|
if objInfo.isMultipart() {
|
||||||
if rinfo.Err = replicateObjectWithMultipart(ctx, c, tgt.Bucket, object,
|
rinfo.Err = replicateObjectWithMultipart(ctx, c, tgt.Bucket, object, r, objInfo, putOpts)
|
||||||
r, objInfo, putOpts); rinfo.Err != nil {
|
|
||||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
|
||||||
} else {
|
|
||||||
rinfo.ReplicationStatus = replication.Completed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if _, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts); rinfo.Err != nil {
|
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
|
||||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
|
||||||
} else {
|
|
||||||
rinfo.ReplicationStatus = replication.Completed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if rinfo.Err != nil && minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if rinfo.Err != nil {
|
||||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||||
|
rinfo.ReplicationStatus = replication.Failed
|
||||||
|
replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w",
|
||||||
|
bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||||
|
}
|
||||||
|
if minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
|
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -1563,6 +1569,9 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if minio.ToErrorResponse(err).Code == "PreconditionFailed" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
attempts++
|
attempts++
|
||||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
}
|
}
|
||||||
@@ -1577,14 +1586,10 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
for attempts <= 3 {
|
for attempts <= 3 {
|
||||||
actx, acancel := context.WithTimeout(ctx, time.Minute)
|
actx, acancel := context.WithTimeout(ctx, time.Minute)
|
||||||
aerr := c.AbortMultipartUpload(actx, bucket, object, uploadID)
|
aerr := c.AbortMultipartUpload(actx, bucket, object, uploadID)
|
||||||
|
acancel()
|
||||||
if aerr == nil {
|
if aerr == nil {
|
||||||
acancel()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
acancel()
|
|
||||||
logger.LogIf(actx,
|
|
||||||
fmt.Errorf("trying %s: Unable to cleanup failed multipart replication %s on remote %s/%s: %w - this may consume space on remote cluster",
|
|
||||||
humanize.Ordinal(attempts), uploadID, bucket, object, aerr))
|
|
||||||
attempts++
|
attempts++
|
||||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
}
|
}
|
||||||
@@ -1596,21 +1601,35 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
pInfo minio.ObjectPart
|
pInfo minio.ObjectPart
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var objectSize int64
|
||||||
for _, partInfo := range objInfo.Parts {
|
for _, partInfo := range objInfo.Parts {
|
||||||
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||||
|
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize)
|
||||||
|
} else {
|
||||||
|
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cHeader := http.Header{}
|
||||||
|
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
|
||||||
popts := minio.PutObjectPartOptions{
|
popts := minio.PutObjectPartOptions{
|
||||||
SSE: opts.ServerSideEncryption,
|
SSE: opts.ServerSideEncryption,
|
||||||
|
CustomHeader: cHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.ActualSize, popts)
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||||
|
objectSize += partInfo.Size
|
||||||
|
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts)
|
||||||
|
} else {
|
||||||
|
objectSize += partInfo.ActualSize
|
||||||
|
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.ActualSize, popts)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if pInfo.Size != partInfo.ActualSize {
|
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) && pInfo.Size != partInfo.ActualSize {
|
||||||
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
|
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
|
||||||
}
|
}
|
||||||
uploadedParts = append(uploadedParts, minio.CompletePart{
|
uploadedParts = append(uploadedParts, minio.CompletePart{
|
||||||
@@ -1618,11 +1637,15 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
ETag: pInfo.ETag,
|
ETag: pInfo.ETag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// really big value but its okay on heavily loaded systems. This is just tail end timeout.
|
||||||
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
|
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||||
defer ccancel()
|
defer ccancel()
|
||||||
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
|
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
|
||||||
|
UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]},
|
||||||
Internal: minio.AdvancedPutOptions{
|
Internal: minio.AdvancedPutOptions{
|
||||||
SourceMTime: objInfo.ModTime,
|
SourceMTime: objInfo.ModTime,
|
||||||
|
SourceETag: objInfo.ETag,
|
||||||
// always set this to distinguish between `mc mirror` replication and serverside
|
// always set this to distinguish between `mc mirror` replication and serverside
|
||||||
ReplicationRequest: true,
|
ReplicationRequest: true,
|
||||||
},
|
},
|
||||||
@@ -1835,7 +1858,7 @@ func (p *ReplicationPool) AddMRFWorker() {
|
|||||||
globalReplicationStats.decQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
globalReplicationStats.decQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.LogOnceIf(p.ctx, fmt.Errorf("unknown mrf replication type: %T", oi), "unknown-mrf-replicate-type")
|
bugLogIf(p.ctx, fmt.Errorf("unknown mrf replication type: %T", oi), "unknown-mrf-replicate-type")
|
||||||
}
|
}
|
||||||
case <-p.mrfWorkerKillCh:
|
case <-p.mrfWorkerKillCh:
|
||||||
return
|
return
|
||||||
@@ -1879,7 +1902,7 @@ func (p *ReplicationPool) AddWorker(input <-chan ReplicationWorkerOperation, opT
|
|||||||
atomic.AddInt32(opTracker, -1)
|
atomic.AddInt32(opTracker, -1)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
logger.LogOnceIf(p.ctx, fmt.Errorf("unknown replication type: %T", oi), "unknown-replicate-type")
|
bugLogIf(p.ctx, fmt.Errorf("unknown replication type: %T", oi), "unknown-replicate-type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1918,7 +1941,7 @@ func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation
|
|||||||
case DeletedObjectReplicationInfo:
|
case DeletedObjectReplicationInfo:
|
||||||
replicateDelete(p.ctx, v, p.objLayer)
|
replicateDelete(p.ctx, v, p.objLayer)
|
||||||
default:
|
default:
|
||||||
logger.LogOnceIf(p.ctx, fmt.Errorf("unknown replication type: %T", oi), "unknown-replicate-type")
|
bugLogIf(p.ctx, fmt.Errorf("unknown replication type: %T", oi), "unknown-replicate-type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2065,9 +2088,9 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
|||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
switch prio {
|
switch prio {
|
||||||
case "fast":
|
case "fast":
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic"), string(replicationSubsystem))
|
replLogOnceIf(GlobalContext, fmt.Errorf("Unable to keep up with incoming traffic"), string(replicationSubsystem), logger.WarningKind)
|
||||||
case "slow":
|
case "slow":
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
replLogOnceIf(GlobalContext, fmt.Errorf("Unable to keep up with incoming traffic - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem), logger.WarningKind)
|
||||||
default:
|
default:
|
||||||
maxWorkers = min(maxWorkers, WorkerMaxLimit)
|
maxWorkers = min(maxWorkers, WorkerMaxLimit)
|
||||||
if p.ActiveWorkers() < maxWorkers {
|
if p.ActiveWorkers() < maxWorkers {
|
||||||
@@ -2122,9 +2145,9 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
|||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
switch prio {
|
switch prio {
|
||||||
case "fast":
|
case "fast":
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes"), string(replicationSubsystem))
|
replLogOnceIf(GlobalContext, fmt.Errorf("Unable to keep up with incoming deletes"), string(replicationSubsystem), logger.WarningKind)
|
||||||
case "slow":
|
case "slow":
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
replLogOnceIf(GlobalContext, fmt.Errorf("Unable to keep up with incoming deletes - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem), logger.WarningKind)
|
||||||
default:
|
default:
|
||||||
maxWorkers = min(maxWorkers, WorkerMaxLimit)
|
maxWorkers = min(maxWorkers, WorkerMaxLimit)
|
||||||
if p.ActiveWorkers() < maxWorkers {
|
if p.ActiveWorkers() < maxWorkers {
|
||||||
@@ -2216,6 +2239,8 @@ func getProxyTargets(ctx context.Context, bucket, object string, opts ObjectOpti
|
|||||||
}
|
}
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil || cfg == nil {
|
if err != nil || cfg == nil {
|
||||||
|
replLogOnceIf(ctx, err, bucket)
|
||||||
|
|
||||||
return &madmin.BucketTargets{}
|
return &madmin.BucketTargets{}
|
||||||
}
|
}
|
||||||
topts := replication.ObjectOpts{Name: object}
|
topts := replication.ObjectOpts{Name: object}
|
||||||
@@ -2235,6 +2260,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
|
|||||||
if opts.ProxyRequest || (opts.ProxyHeaderSet && !opts.ProxyRequest) { // true only when site B sets MinIOSourceProxyRequest header
|
if opts.ProxyRequest || (opts.ProxyHeaderSet && !opts.ProxyRequest) { // true only when site B sets MinIOSourceProxyRequest header
|
||||||
return nil, oi, proxy
|
return nil, oi, proxy
|
||||||
}
|
}
|
||||||
|
var perr error
|
||||||
for _, t := range proxyTargets.Targets {
|
for _, t := range proxyTargets.Targets {
|
||||||
tgt = globalBucketTargetSys.GetRemoteTargetClient(bucket, t.Arn)
|
tgt = globalBucketTargetSys.GetRemoteTargetClient(bucket, t.Arn)
|
||||||
if tgt == nil || globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if tgt == nil || globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
@@ -2256,7 +2282,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
|
|||||||
if rs != nil {
|
if rs != nil {
|
||||||
h, err := rs.ToHeader()
|
h, err := rs.ToHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("invalid range header for %s/%s(%s) - %w", bucket, object, opts.VersionID, err))
|
replLogIf(ctx, fmt.Errorf("invalid range header for %s/%s(%s) - %w", bucket, object, opts.VersionID, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gopts.Set(xhttp.Range, h)
|
gopts.Set(xhttp.Range, h)
|
||||||
@@ -2264,6 +2290,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
|
|||||||
|
|
||||||
objInfo, err := tgt.StatObject(ctx, t.TargetBucket, object, gopts)
|
objInfo, err := tgt.StatObject(ctx, t.TargetBucket, object, gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
perr = err
|
||||||
if isErrInvalidRange(ErrorRespToObjectError(err, bucket, object)) {
|
if isErrInvalidRange(ErrorRespToObjectError(err, bucket, object)) {
|
||||||
return nil, oi, proxyResult{Err: err}
|
return nil, oi, proxyResult{Err: err}
|
||||||
}
|
}
|
||||||
@@ -2300,6 +2327,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
|
|||||||
}
|
}
|
||||||
return tgt, oi, proxyResult{Proxy: true}
|
return tgt, oi, proxyResult{Proxy: true}
|
||||||
}
|
}
|
||||||
|
proxy.Err = perr
|
||||||
return nil, oi, proxy
|
return nil, oi, proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2406,8 +2434,7 @@ func proxyTaggingToRepTarget(ctx context.Context, bucket, object string, tags *t
|
|||||||
taggedCount++
|
taggedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errCode := minio.ToErrorResponse(err).Code
|
if err != nil {
|
||||||
if errCode != "NoSuchKey" && errCode != "NoSuchVersion" {
|
|
||||||
terr = err
|
terr = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2467,6 +2494,9 @@ func proxyGetTaggingToRepTarget(ctx context.Context, bucket, object string, opts
|
|||||||
tgs, _ = tags.MapToObjectTags(tagSlc[idx])
|
tgs, _ = tags.MapToObjectTags(tagSlc[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(errs) == 1 {
|
||||||
|
proxy.Err = errs[0]
|
||||||
|
}
|
||||||
return tgs, proxy
|
return tgs, proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2620,7 +2650,7 @@ func (s *replicationResyncer) PersistToDisk(ctx context.Context, objectAPI Objec
|
|||||||
}
|
}
|
||||||
if updt {
|
if updt {
|
||||||
if err := saveResyncStatus(ctx, bucket, brs, objectAPI); err != nil {
|
if err := saveResyncStatus(ctx, bucket, brs, objectAPI); err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("could not save resync metadata to drive for %s - %w", bucket, err))
|
replLogIf(ctx, fmt.Errorf("could not save resync metadata to drive for %s - %w", bucket, err))
|
||||||
} else {
|
} else {
|
||||||
lastResyncStatusSave[bucket] = brs.LastUpdate
|
lastResyncStatusSave[bucket] = brs.LastUpdate
|
||||||
}
|
}
|
||||||
@@ -2705,15 +2735,15 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
s.workerCh <- struct{}{}
|
s.workerCh <- struct{}{}
|
||||||
}()
|
}()
|
||||||
// Allocate new results channel to receive ObjectInfo.
|
// Allocate new results channel to receive ObjectInfo.
|
||||||
objInfoCh := make(chan ObjectInfo)
|
objInfoCh := make(chan itemOrErr[ObjectInfo])
|
||||||
cfg, err := getReplicationConfig(ctx, opts.bucket)
|
cfg, err := getReplicationConfig(ctx, opts.bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("replication resync of %s for arn %s failed with %w", opts.bucket, opts.arn, err))
|
replLogIf(ctx, fmt.Errorf("replication resync of %s for arn %s failed with %w", opts.bucket, opts.arn, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgts, err := globalBucketTargetSys.ListBucketTargets(ctx, opts.bucket)
|
tgts, err := globalBucketTargetSys.ListBucketTargets(ctx, opts.bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("replication resync of %s for arn %s failed %w", opts.bucket, opts.arn, err))
|
replLogIf(ctx, fmt.Errorf("replication resync of %s for arn %s failed %w", opts.bucket, opts.arn, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rcfg := replicationConfig{
|
rcfg := replicationConfig{
|
||||||
@@ -2726,12 +2756,12 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
TargetArn: opts.arn,
|
TargetArn: opts.arn,
|
||||||
})
|
})
|
||||||
if len(tgtArns) != 1 {
|
if len(tgtArns) != 1 {
|
||||||
logger.LogIf(ctx, fmt.Errorf("replication resync failed for %s - arn specified %s is missing in the replication config", opts.bucket, opts.arn))
|
replLogIf(ctx, fmt.Errorf("replication resync failed for %s - arn specified %s is missing in the replication config", opts.bucket, opts.arn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(opts.bucket, opts.arn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(opts.bucket, opts.arn)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("replication resync failed for %s - target could not be created for arn %s", opts.bucket, opts.arn))
|
replLogIf(ctx, fmt.Errorf("replication resync failed for %s - target could not be created for arn %s", opts.bucket, opts.arn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// mark resync status as resync started
|
// mark resync status as resync started
|
||||||
@@ -2742,7 +2772,7 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
// Walk through all object versions - Walk() is always in ascending order needed to ensure
|
// Walk through all object versions - Walk() is always in ascending order needed to ensure
|
||||||
// delete marker replicated to target after object version is first created.
|
// delete marker replicated to target after object version is first created.
|
||||||
if err := objectAPI.Walk(ctx, opts.bucket, "", objInfoCh, WalkOptions{}); err != nil {
|
if err := objectAPI.Walk(ctx, opts.bucket, "", objInfoCh, WalkOptions{}); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
replLogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2840,7 +2870,12 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
}(ctx, i)
|
}(ctx, i)
|
||||||
}
|
}
|
||||||
for obj := range objInfoCh {
|
for res := range objInfoCh {
|
||||||
|
if res.Err != nil {
|
||||||
|
resyncStatus = ResyncFailed
|
||||||
|
replLogIf(ctx, res.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-s.resyncCancelCh:
|
case <-s.resyncCancelCh:
|
||||||
resyncStatus = ResyncCanceled
|
resyncStatus = ResyncCanceled
|
||||||
@@ -2849,11 +2884,11 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if heal && lastCheckpoint != "" && lastCheckpoint != obj.Name {
|
if heal && lastCheckpoint != "" && lastCheckpoint != res.Item.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastCheckpoint = ""
|
lastCheckpoint = ""
|
||||||
roi := getHealReplicateObjectInfo(obj, rcfg)
|
roi := getHealReplicateObjectInfo(res.Item, rcfg)
|
||||||
if !roi.ExistingObjResync.mustResync() {
|
if !roi.ExistingObjResync.mustResync() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -3017,7 +3052,7 @@ func (p *ReplicationPool) loadResync(ctx context.Context, buckets []BucketInfo,
|
|||||||
meta, err := loadBucketResyncMetadata(ctx, bucket, objAPI)
|
meta, err := loadBucketResyncMetadata(ctx, bucket, objAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errVolumeNotFound) {
|
if !errors.Is(err, errVolumeNotFound) {
|
||||||
logger.LogIf(ctx, err)
|
replLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -3104,18 +3139,18 @@ func saveResyncStatus(ctx context.Context, bucket string, brs BucketReplicationR
|
|||||||
func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string, opts madmin.ReplDiffOpts) (chan madmin.DiffInfo, error) {
|
func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string, opts madmin.ReplDiffOpts) (chan madmin.DiffInfo, error) {
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
replLogOnceIf(ctx, err, bucket)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tgts, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
tgts, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
replLogIf(ctx, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfoCh := make(chan ObjectInfo, 10)
|
objInfoCh := make(chan itemOrErr[ObjectInfo], 10)
|
||||||
if err := objAPI.Walk(ctx, bucket, opts.Prefix, objInfoCh, WalkOptions{}); err != nil {
|
if err := objAPI.Walk(ctx, bucket, opts.Prefix, objInfoCh, WalkOptions{}); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
replLogIf(ctx, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rcfg := replicationConfig{
|
rcfg := replicationConfig{
|
||||||
@@ -3125,11 +3160,17 @@ func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string,
|
|||||||
diffCh := make(chan madmin.DiffInfo, 4000)
|
diffCh := make(chan madmin.DiffInfo, 4000)
|
||||||
go func() {
|
go func() {
|
||||||
defer xioutil.SafeClose(diffCh)
|
defer xioutil.SafeClose(diffCh)
|
||||||
for obj := range objInfoCh {
|
for res := range objInfoCh {
|
||||||
|
if res.Err != nil {
|
||||||
|
diffCh <- madmin.DiffInfo{Err: res.Err}
|
||||||
|
return
|
||||||
|
}
|
||||||
if contextCanceled(ctx) {
|
if contextCanceled(ctx) {
|
||||||
// Just consume input...
|
// Just consume input...
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
obj := res.Item
|
||||||
|
|
||||||
// Ignore object prefixes which are excluded
|
// Ignore object prefixes which are excluded
|
||||||
// from versioning via the MinIO bucket versioning extension.
|
// from versioning via the MinIO bucket versioning extension.
|
||||||
if globalBucketVersioningSys.PrefixSuspended(bucket, obj.Name) {
|
if globalBucketVersioningSys.PrefixSuspended(bucket, obj.Name) {
|
||||||
@@ -3197,7 +3238,11 @@ func QueueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, ret
|
|||||||
if oi.ModTime.IsZero() {
|
if oi.ModTime.IsZero() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rcfg, _ := getReplicationConfig(ctx, bucket)
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
replLogOnceIf(ctx, err, bucket)
|
||||||
|
return
|
||||||
|
}
|
||||||
tgts, _ := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
tgts, _ := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
||||||
queueReplicationHeal(ctx, bucket, oi, replicationConfig{
|
queueReplicationHeal(ctx, bucket, oi, replicationConfig{
|
||||||
Config: rcfg,
|
Config: rcfg,
|
||||||
@@ -3213,6 +3258,9 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
|
|||||||
return roi
|
return roi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isVeeamSOSAPIObject(oi.Name) {
|
||||||
|
return roi
|
||||||
|
}
|
||||||
if rcfg.Config == nil || rcfg.remotes == nil {
|
if rcfg.Config == nil || rcfg.remotes == nil {
|
||||||
return roi
|
return roi
|
||||||
}
|
}
|
||||||
@@ -3385,12 +3433,12 @@ func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntr
|
|||||||
}
|
}
|
||||||
|
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := globalLocalDrives
|
localDrives := cloneDrives(globalLocalDrives)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
for _, localDrive := range localDrives {
|
for _, localDrive := range localDrives {
|
||||||
r := newReader()
|
r := newReader()
|
||||||
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
err := localDrive.CreateFile(ctx, "", minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
||||||
r.Close()
|
r.Close()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
@@ -3452,7 +3500,7 @@ func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := globalLocalDrives
|
localDrives := cloneDrives(globalLocalDrives)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
for _, localDrive := range localDrives {
|
for _, localDrive := range localDrives {
|
||||||
@@ -3496,7 +3544,7 @@ func (p *ReplicationPool) processMRF() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := p.queueMRFHeal(); err != nil && !osIsNotExist(err) {
|
if err := p.queueMRFHeal(); err != nil && !osIsNotExist(err) {
|
||||||
logger.LogIf(p.ctx, err)
|
replLogIf(p.ctx, err)
|
||||||
}
|
}
|
||||||
pTimer.Reset(mrfQueueInterval)
|
pTimer.Reset(mrfQueueInterval)
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ type BucketStats struct {
|
|||||||
Uptime int64 `json:"uptime"`
|
Uptime int64 `json:"uptime"`
|
||||||
ReplicationStats BucketReplicationStats `json:"currStats"` // current replication stats since cluster startup
|
ReplicationStats BucketReplicationStats `json:"currStats"` // current replication stats since cluster startup
|
||||||
QueueStats ReplicationQueueStats `json:"queueStats"` // replication queue stats
|
QueueStats ReplicationQueueStats `json:"queueStats"` // replication queue stats
|
||||||
|
ProxyStats ProxyMetric `json:"proxyStats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketReplicationStats represents inline replication statistics
|
// BucketReplicationStats represents inline replication statistics
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user