mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 10:43:15 +03:00
Compare commits
336 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c27d0583d4 | |||
| 0de2b9a1b2 | |||
| 9dc29d7687 | |||
| 72871dbb9a | |||
| 4bda4e4e2b | |||
| 1971c54a50 | |||
| bb77b89da0 | |||
| b336e9a79f | |||
| a2ab21e91c | |||
| 603437e70f | |||
| db3a9a5990 | |||
| 24c7e73b4e | |||
| c053e57068 | |||
| 6d20ec3bea | |||
| c50627ee3e | |||
| b3cd893f93 | |||
| 22d2dbc4e6 | |||
| 2b5d9428b1 | |||
| d6446cb096 | |||
| c34bdc33fb | |||
| dd8547e51c | |||
| aec023f537 | |||
| e101eeeda9 | |||
| f29522269d | |||
| 3c470a6b8b | |||
| 6bc7d711b3 | |||
| 10d5dd3a67 | |||
| d9f1df01eb | |||
| cdeab19673 | |||
| 22ee678136 | |||
| 50a8f13e85 | |||
| 6dec60b6e6 | |||
| ac3a19138a | |||
| 21e8e071d7 | |||
| 57f84a8b4c | |||
| 8a672e70a7 | |||
| 22041bbcc4 | |||
| 5afb459113 | |||
| 91ebac0a00 | |||
| 5fcb1cfd31 | |||
| 3a90fb108c | |||
| 4eeb48f8e0 | |||
| 373d48c8a3 | |||
| 1472875670 | |||
| 74cfb207c1 | |||
| 6a096e7dc7 | |||
| 9788d85ea3 | |||
| 69c0e18685 | |||
| 3cac927348 | |||
| 9081346c40 | |||
| fcfadb0e51 | |||
| 2add57cfed | |||
| c5279ec630 | |||
| b73699fad8 | |||
| b8ebe54e53 | |||
| c3d70e0795 | |||
| 8c4561b8da | |||
| fd421ddd6f | |||
| 9947c01c8e | |||
| a00db4267c | |||
| 36385010f5 | |||
| fa6d082bfd | |||
| 9fab91852a | |||
| b733e6e83c | |||
| ce05bb69dc | |||
| 37aa5934a1 | |||
| 1647fc7edc | |||
| 7b92687397 | |||
| 419e5baf16 | |||
| dc48cd841a | |||
| b0e1776d6d | |||
| 7a7068ee47 | |||
| cbc0ef459b | |||
| a2aabfabd9 | |||
| 822cbd4b43 | |||
| 3c19a9308d | |||
| 32890342ce | |||
| ed2c2a285f | |||
| 18e23bafd9 | |||
| 8b8be2695f | |||
| 96fbf18201 | |||
| c8a57a8fa2 | |||
| b1c2dacab3 | |||
| 65939913b4 | |||
| 08b3a466e8 | |||
| 5aa7c38035 | |||
| 1df5e31706 | |||
| 9f7044aed0 | |||
| 41de53996b | |||
| 9878031cfd | |||
| e3fbcaeb72 | |||
| ca6dd8be5e | |||
| fba0924b1d | |||
| 703ed46d79 | |||
| f7ca6c63c2 | |||
| ad69b9907f | |||
| b9269151a4 | |||
| bfddbb8b40 | |||
| 13a2dc8485 | |||
| 1e51424e8a | |||
| 5b114b43f7 | |||
| 812f5a02d7 | |||
| 19f70dbfbf | |||
| 1c99fb106c | |||
| 71c32e9b48 | |||
| 380a59520b | |||
| 3995355150 | |||
| 8208bcb896 | |||
| d665e855de | |||
| 18b3655c99 | |||
| 6a8d8f34a5 | |||
| b1c1f02132 | |||
| ea93643e6a | |||
| e47e625f73 | |||
| b13fcaf666 | |||
| 9458485e43 | |||
| 0ce9e00ffa | |||
| c778c381b5 | |||
| 0d1fbef751 | |||
| b48bbe08b2 | |||
| cce90cb2b7 | |||
| 07b1281046 | |||
| 3515b99671 | |||
| 6a67c277eb | |||
| 1067dd3011 | |||
| 7cafdc0512 | |||
| 8a57b6bced | |||
| 6f0ed2a091 | |||
| 53abd25116 | |||
| 1ea7826c0e | |||
| 97f4cf48f8 | |||
| 0cde37be50 | |||
| 6aeca54ece | |||
| 124e28578c | |||
| 62c9e500de | |||
| 02cc18ff29 | |||
| ba4566e86d | |||
| 87cb0081ec | |||
| 4a6af93c83 | |||
| a2f0771fd3 | |||
| af564b8ba0 | |||
| adb8be069e | |||
| 7c8746732b | |||
| f506117edb | |||
| 1c5af7c31a | |||
| 3a0125fa1f | |||
| 328cb0a076 | |||
| c3c8441a1d | |||
| fa2a8d7209 | |||
| e3ea97c964 | |||
| 7219ae530e | |||
| 8f8f8854f0 | |||
| 4c6869cd9a | |||
| bc7c0d8624 | |||
| 11dfc817f3 | |||
| dde1a12819 | |||
| 065fd094d1 | |||
| d09351bb10 | |||
| 25d38e030b | |||
| 9ebd10d3f4 | |||
| 8a9b886011 | |||
| 21f0d6b549 | |||
| 3ba927edae | |||
| c4ca0a5a57 | |||
| 406ea4f281 | |||
| 64aa7feabd | |||
| 875f4076ec | |||
| 4643efe6be | |||
| b760137e1d | |||
| 5f56f441bf | |||
| 96a22bfcbb | |||
| 6c59b33fb1 | |||
| dfaf735073 | |||
| 0d2b7bf94d | |||
| 7fcfde7f07 | |||
| b1391d1991 | |||
| 49c8e16410 | |||
| 0e93681589 | |||
| eb55034dfe | |||
| c45bc32d98 | |||
| 6e860b6dc5 | |||
| b732a673dc | |||
| b6b6d6e8d8 | |||
| 23e4895dfc | |||
| 8666c55ca6 | |||
| a3f00c5d5e | |||
| 26c23b30f4 | |||
| a436fd513b | |||
| 3bc34ffd94 | |||
| 533cd8d6df | |||
| cb089dcb52 | |||
| e0329cfdbb | |||
| 239ccc9c40 | |||
| b762fbaf21 | |||
| 0285df5a02 | |||
| 45fb375c41 | |||
| 4a4950fe41 | |||
| 1664fd8bb1 | |||
| 21cdd2bf5d | |||
| 0153f96a20 | |||
| a7a7533190 | |||
| 311380f8cb | |||
| b0f0e53bba | |||
| 004f1e2f66 | |||
| 2fa561f22e | |||
| 81be718674 | |||
| 8162fd1e20 | |||
| 49a1e2f98e | |||
| 684c46369c | |||
| 715c9e3ca9 | |||
| 73edd5b8fd | |||
| 5e5bdf5432 | |||
| 48a3e9bc82 | |||
| f13cfcb83e | |||
| 9c0e8cd15b | |||
| ad2a70ba06 | |||
| 731e03fe5a | |||
| f9d029c8fa | |||
| 7057d00a28 | |||
| 114fab4c70 | |||
| c2edbfae55 | |||
| a92cb66468 | |||
| 535f97ba61 | |||
| 14ebd82dbd | |||
| aea7b08a47 | |||
| 47dcfcbdd4 | |||
| bf3901342c | |||
| e1731d9403 | |||
| b28bcad11b | |||
| a7c71e4c6b | |||
| 1a42693d68 | |||
| e7b60c4d65 | |||
| f95129894d | |||
| c32c71c836 | |||
| 14e1ace552 | |||
| a7fb3a3853 | |||
| 2da4bd5f1a | |||
| 7e76d66184 | |||
| 7764f4a8e3 | |||
| e1094dde08 | |||
| 4894c67196 | |||
| d004c45386 | |||
| 6624f970c0 | |||
| de684dc122 | |||
| 331bdc2245 | |||
| e12ab486a2 | |||
| 9eeee92d36 | |||
| 756d6aa729 | |||
| bddd53d6d2 | |||
| c0a5bdaed9 | |||
| a99cd825ab | |||
| 6426b74770 | |||
| 4f257bf1e6 | |||
| 73a056999c | |||
| 0120ff93bc | |||
| 49638fa533 | |||
| 76510dac8a | |||
| 7a3a7b19e5 | |||
| 24e86d0c59 | |||
| 9b5c2c386a | |||
| d118031ed6 | |||
| 341a89c00d | |||
| df29d25e6b | |||
| 3e196fa7b3 | |||
| 04c792476f | |||
| 005a4a275a | |||
| bdddf597f6 | |||
| bb6921bf9c | |||
| bb63375f1b | |||
| 4f89e5bba9 | |||
| 183428db03 | |||
| fc6d873758 | |||
| 5e2f8d7a42 | |||
| 9b9871cfbb | |||
| f80b6926d3 | |||
| 6dc55fe5ed | |||
| 2d1cda2061 | |||
| a566bcf613 | |||
| f6040dffaf | |||
| 9885a0a6af | |||
| f64d62b01d | |||
| 82075e8e3a | |||
| 5b7c83341b | |||
| 8522905d97 | |||
| 524ed7ccd0 | |||
| fb49aead9b | |||
| 85f5700e4e | |||
| 43b3c093ef | |||
| bd6842d917 | |||
| e8c98c3246 | |||
| dfd7cca0d2 | |||
| af3d99e35f | |||
| f6186965c3 | |||
| 90c2129f44 | |||
| 69e131ee69 | |||
| 28a01f0320 | |||
| 6d0bc5ab1e | |||
| 7af78af1f0 | |||
| 45a717a142 | |||
| cb1ec0a0d9 | |||
| abb1f22057 | |||
| 73efe436a5 | |||
| f41edb23e2 | |||
| 6335a48a53 | |||
| e20aab25ec | |||
| 66bea3942a | |||
| 08acd9c43d | |||
| ff5988f4e0 | |||
| 1bf23374a3 | |||
| 899b429094 | |||
| c47ff44f5e | |||
| 8af0773baf | |||
| 37cbd114de | |||
| 2dbb1cff4a | |||
| 6efcf9c982 | |||
| 0bc34952eb | |||
| f6b48ed02a | |||
| e37c4efc6e | |||
| 22f5bc643c | |||
| 15fd5ce2fa | |||
| 7f782983ca | |||
| 9d628346eb | |||
| bde533a9c7 | |||
| 2fcb75d86d | |||
| aae6846413 | |||
| 5317a0b755 | |||
| 73de721a63 | |||
| d2f5c3621f | |||
| 1818764840 | |||
| d3e5e607a7 | |||
| c1943ea3af | |||
| 2a82c15bf1 | |||
| 87b6fb37d6 | |||
| 21fbe88e1f | |||
| 1f8b9b4bd5 | |||
| fcbed41cc3 |
+8
-1
@@ -1,6 +1,5 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
docs
|
|
||||||
default.etcd
|
default.etcd
|
||||||
*.gz
|
*.gz
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
@@ -8,3 +7,11 @@ default.etcd
|
|||||||
*.zip
|
*.zip
|
||||||
browser/node_modules
|
browser/node_modules
|
||||||
node_modules
|
node_modules
|
||||||
|
docs/debugging/s3-verify/s3-verify
|
||||||
|
docs/debugging/xl-meta/xl-meta
|
||||||
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
|
docs/debugging/hash-set/hash-set
|
||||||
|
docs/debugging/healing-bin/healing-bin
|
||||||
|
docs/debugging/inspect/inspect
|
||||||
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
## Community Contribution License
|
||||||
|
All community contributions in this pull request are licensed to the project maintainers
|
||||||
|
under the terms of the [Apache 2 license] (https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
By creating this pull request I represent that I have the right to license the
|
||||||
|
contributions to the project maintainers under the Apache 2 license.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: Helm Chart linting
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
|
# updated.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
|
||||||
|
- name: Run helm lint
|
||||||
|
run: |
|
||||||
|
cd helm/minio
|
||||||
|
helm lint .
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
||||||
# the tests.
|
# the tests.
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.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"]
|
||||||
@@ -82,9 +82,9 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Test LDAP/OpenID/Etcd combo
|
- name: Test LDAP/OpenID/Etcd combo
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
ETCD_SERVER: ${{ matrix.etcd }}
|
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
@@ -92,20 +92,20 @@ jobs:
|
|||||||
- name: Test with multiple OpenID providers
|
- name: Test with multiple OpenID providers
|
||||||
if: matrix.openid == 'http://127.0.0.1:5556/dex'
|
if: matrix.openid == 'http://127.0.0.1:5556/dex'
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
ETCD_SERVER: ${{ matrix.etcd }}
|
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
OPENID_TEST_SERVER_2: "http://127.0.0.1:5557/dex"
|
_MINIO_OPENID_TEST_SERVER_2: "http://127.0.0.1:5557/dex"
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-iam
|
make test-iam
|
||||||
- name: Test with Access Management Plugin enabled
|
- name: Test with Access Management Plugin enabled
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
ETCD_SERVER: ${{ matrix.etcd }}
|
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
POLICY_PLUGIN_ENDPOINT: "http://127.0.0.1:8080"
|
_MINIO_POLICY_PLUGIN_TEST_ENDPOINT: "http://127.0.0.1:8080"
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: setup-go-step
|
- name: setup-go-step
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.x
|
go-version: 1.21.x
|
||||||
|
|
||||||
- name: github sha short
|
- name: github sha short
|
||||||
id: vars
|
id: vars
|
||||||
@@ -37,8 +37,11 @@ jobs:
|
|||||||
|
|
||||||
- name: build-minio
|
- name: build-minio
|
||||||
run: |
|
run: |
|
||||||
make install
|
TAG="quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}" make docker
|
||||||
docker build . -t "minio/minio:${{ steps.vars.outputs.sha_short }}"
|
|
||||||
|
- name: multipart uploads test
|
||||||
|
run: |
|
||||||
|
${GITHUB_WORKSPACE}/.github/workflows/multipart/migrate.sh "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
- name: compress and encrypt
|
- name: compress and encrypt
|
||||||
run: |
|
run: |
|
||||||
@@ -51,7 +54,23 @@ jobs:
|
|||||||
- name: standalone erasure
|
- name: standalone erasure
|
||||||
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 }}"
|
||||||
docker rmi -f minio/minio:${{ steps.vars.outputs.sha_short }}
|
|
||||||
|
|
||||||
|
- name: The job must cleanup
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
export JOB_NAME=${{ steps.vars.outputs.sha_short }}
|
||||||
|
for mode in $(echo compress-encrypt pools erasure); do
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml down || true
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml rm || true
|
||||||
|
done
|
||||||
|
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site1.yaml rm -s -f || true
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site2.yaml rm -s -f || true
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume} || true
|
||||||
|
done
|
||||||
|
|
||||||
|
docker rmi -f quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
# Settings and configurations that are common for all containers
|
# Settings and configurations that are common for all containers
|
||||||
x-minio-common: &minio-common
|
x-minio-common: &minio-common
|
||||||
image: minio/minio:${JOB_NAME}
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
command: server --console-address ":9001" http://minio{1...4}/cdata{1...2}
|
command: server --console-address ":9001" http://minio{1...4}/cdata{1...2}
|
||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
# Settings and configurations that are common for all containers
|
# Settings and configurations that are common for all containers
|
||||||
x-minio-common: &minio-common
|
x-minio-common: &minio-common
|
||||||
image: minio/minio:${JOB_NAME}
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
command: server --console-address ":9001" edata{1...4}
|
command: server --console-address ":9001" edata{1...4}
|
||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
# Settings and configurations that are common for all containers
|
# Settings and configurations that are common for all containers
|
||||||
x-minio-common: &minio-common
|
x-minio-common: &minio-common
|
||||||
image: minio/minio:${JOB_NAME}
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
command: server --console-address ":9001" http://minio{1...4}/pdata{1...2} http://minio{5...8}/pdata{1...2}
|
command: server --console-address ":9001" http://minio{1...4}/pdata{1...2} http://minio{5...8}/pdata{1...2}
|
||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${RELEASE}
|
||||||
|
command: server http://site1-minio{1...4}/data{1...2}
|
||||||
|
environment:
|
||||||
|
- MINIO_PROMETHEUS_AUTH_TYPE=public
|
||||||
|
- CI=true
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
site1-minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio1
|
||||||
|
volumes:
|
||||||
|
- site1-data1-1:/data1
|
||||||
|
- site1-data1-2:/data2
|
||||||
|
|
||||||
|
site1-minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio2
|
||||||
|
volumes:
|
||||||
|
- site1-data2-1:/data1
|
||||||
|
- site1-data2-2:/data2
|
||||||
|
|
||||||
|
site1-minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio3
|
||||||
|
volumes:
|
||||||
|
- site1-data3-1:/data1
|
||||||
|
- site1-data3-2:/data2
|
||||||
|
|
||||||
|
site1-minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio4
|
||||||
|
volumes:
|
||||||
|
- site1-data4-1:/data1
|
||||||
|
- site1-data4-2:/data2
|
||||||
|
|
||||||
|
site1-nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: site1-nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-site1.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- site1-minio1
|
||||||
|
- site1-minio2
|
||||||
|
- site1-minio3
|
||||||
|
- site1-minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
site1-data1-1:
|
||||||
|
site1-data1-2:
|
||||||
|
site1-data2-1:
|
||||||
|
site1-data2-2:
|
||||||
|
site1-data3-1:
|
||||||
|
site1-data3-2:
|
||||||
|
site1-data4-1:
|
||||||
|
site1-data4-2:
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${RELEASE}
|
||||||
|
command: server http://site2-minio{1...4}/data{1...2}
|
||||||
|
environment:
|
||||||
|
- MINIO_PROMETHEUS_AUTH_TYPE=public
|
||||||
|
- CI=true
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
site2-minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio1
|
||||||
|
volumes:
|
||||||
|
- site2-data1-1:/data1
|
||||||
|
- site2-data1-2:/data2
|
||||||
|
|
||||||
|
site2-minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio2
|
||||||
|
volumes:
|
||||||
|
- site2-data2-1:/data1
|
||||||
|
- site2-data2-2:/data2
|
||||||
|
|
||||||
|
site2-minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio3
|
||||||
|
volumes:
|
||||||
|
- site2-data3-1:/data1
|
||||||
|
- site2-data3-2:/data2
|
||||||
|
|
||||||
|
site2-minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio4
|
||||||
|
volumes:
|
||||||
|
- site2-data4-1:/data1
|
||||||
|
- site2-data4-2:/data2
|
||||||
|
|
||||||
|
site2-nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: site2-nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-site2.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9002:9002"
|
||||||
|
depends_on:
|
||||||
|
- site2-minio1
|
||||||
|
- site2-minio2
|
||||||
|
- site2-minio3
|
||||||
|
- site2-minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
site2-data1-1:
|
||||||
|
site2-data1-2:
|
||||||
|
site2-data2-1:
|
||||||
|
site2-data2-2:
|
||||||
|
site2-data3-1:
|
||||||
|
site2-data3-2:
|
||||||
|
site2-data4-1:
|
||||||
|
site2-data4-2:
|
||||||
Executable
+115
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd .github/workflows/multipart/
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml rm -s -f
|
||||||
|
docker-compose -f docker-compose-site2.yaml rm -s -f
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume}
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
wget --quiet -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x mc
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
cd /tmp
|
||||||
|
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||||
|
)
|
||||||
|
|
||||||
|
export RELEASE=RELEASE.2023-08-29T23-07-35Z
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
|
docker-compose -f docker-compose-site2.yaml up -d
|
||||||
|
|
||||||
|
sleep 30s
|
||||||
|
|
||||||
|
./mc alias set site1 http://site1-nginx:9001 minioadmin minioadmin --api s3v4
|
||||||
|
./mc alias set site2 http://site2-nginx:9002 minioadmin minioadmin --api s3v4
|
||||||
|
|
||||||
|
./mc ready site1/
|
||||||
|
./mc ready site2/
|
||||||
|
|
||||||
|
./mc admin replicate add site1 site2
|
||||||
|
./mc mb site1/testbucket/
|
||||||
|
./mc cp -r --quiet /usr/bin site1/testbucket/
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
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_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
|
||||||
|
echo "failed with multipart on site1 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
./mc cp -r --quiet /usr/bin site1/testbucket/
|
||||||
|
|
||||||
|
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_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
|
||||||
|
## upgrading to master, healing and being able to recover
|
||||||
|
## the last version.
|
||||||
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site1 uploads ${failed_count_site1}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads ${failed_count_site2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export RELEASE=${1}
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
|
docker-compose -f docker-compose-site2.yaml up -d
|
||||||
|
|
||||||
|
./mc ready site1/
|
||||||
|
./mc ready site2/
|
||||||
|
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
# mc admin heal -r --remove when used against a LB endpoint
|
||||||
|
# behaves flaky, let this run 10 times before giving up
|
||||||
|
./mc admin heal -r --remove --json site1/ 2>&1 >/dev/null
|
||||||
|
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
|
||||||
|
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_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
|
||||||
|
echo "failed with multipart on site1 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml rm -s -f
|
||||||
|
docker-compose -f docker-compose-site2.yaml rm -s -f
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume}
|
||||||
|
done
|
||||||
|
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd ../../../
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server site1-minio1:9000;
|
||||||
|
server site1-minio2:9000;
|
||||||
|
server site1-minio3:9000;
|
||||||
|
server site1-minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server site2-minio1:9000;
|
||||||
|
server site2-minio2:9000;
|
||||||
|
server site2-minio3:9000;
|
||||||
|
server site2-minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9002;
|
||||||
|
listen [::]:9002;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -16,11 +16,19 @@ 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 5m
|
sleep 30s
|
||||||
|
|
||||||
docker run --rm --net=host \
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|
||||||
|
# Stop two nodes, one of each pool, to check that all S3 calls work while quorum is still there
|
||||||
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2
|
||||||
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6
|
||||||
|
|
||||||
|
docker run --rm --net=mint_default \
|
||||||
--name="mint-${MODE}-${JOB_NAME}" \
|
--name="mint-${MODE}-${JOB_NAME}" \
|
||||||
-e SERVER_ENDPOINT="127.0.0.1:9000" \
|
-e SERVER_ENDPOINT="nginx:9000" \
|
||||||
-e ACCESS_KEY="${ACCESS_KEY}" \
|
-e ACCESS_KEY="${ACCESS_KEY}" \
|
||||||
-e SECRET_KEY="${SECRET_KEY}" \
|
-e SECRET_KEY="${SECRET_KEY}" \
|
||||||
-e ENABLE_HTTPS=0 \
|
-e ENABLE_HTTPS=0 \
|
||||||
@@ -32,7 +40,7 @@ sleep 10s
|
|||||||
|
|
||||||
docker system prune -f || true
|
docker system prune -f || true
|
||||||
docker volume prune -f || true
|
docker volume prune -f || true
|
||||||
docker volume rm $(docker volume ls -f dangling=true) || true
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|
||||||
## change working directory
|
## change working directory
|
||||||
cd ../../../
|
cd ../../../
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.20.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.19.10
|
go-version: 1.21.1
|
||||||
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
|
||||||
|
|||||||
+4
-1
@@ -32,10 +32,13 @@ minio.RELEASE*
|
|||||||
mc
|
mc
|
||||||
nancy
|
nancy
|
||||||
inspects/*
|
inspects/*
|
||||||
|
.bin/
|
||||||
|
*.gz
|
||||||
docs/debugging/s3-verify/s3-verify
|
docs/debugging/s3-verify/s3-verify
|
||||||
docs/debugging/xl-meta/xl-meta
|
docs/debugging/xl-meta/xl-meta
|
||||||
docs/debugging/s3-check-md5/s3-check-md5
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
docs/debugging/hash-set/hash-set
|
docs/debugging/hash-set/hash-set
|
||||||
docs/debugging/healing-bin/healing-bin
|
docs/debugging/healing-bin/healing-bin
|
||||||
docs/debugging/inspect/inspect
|
docs/debugging/inspect/inspect
|
||||||
.bin/
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- goimports
|
- goimports
|
||||||
- gomodguard
|
- gomodguard
|
||||||
|
|||||||
+1
-1
@@ -36,9 +36,9 @@ RUN \
|
|||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
|
||||||
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /opt/bin/mc && \
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /opt/bin/mc && \
|
||||||
gzip /opt/bin/mc && \
|
|
||||||
microdnf clean all && \
|
microdnf clean all && \
|
||||||
chmod +x /opt/bin/minio && \
|
chmod +x /opt/bin/minio && \
|
||||||
|
chmod +x /opt/bin/mc && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
/usr/bin/verify-minio.sh && \
|
/usr/bin/verify-minio.sh && \
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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 ?= "minio/minio:$(VERSION)"
|
TAG ?= "quay.io/minio/minio:$(VERSION)"
|
||||||
|
|
||||||
GOLANGCI_VERSION = v1.51.2
|
GOLANGCI_VERSION = v1.51.2
|
||||||
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
||||||
@@ -45,22 +45,22 @@ 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 ## builds minio, runs linters, tests
|
test: verifiers build build-debugging ## builds minio, runs linters, tests
|
||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
||||||
|
|
||||||
test-root-disable: install
|
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-decom: install
|
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
|
||||||
|
|
||||||
test-upgrade: build
|
test-upgrade: install-race
|
||||||
@echo "Running minio upgrade tests"
|
@echo "Running minio upgrade tests"
|
||||||
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
||||||
|
|
||||||
@@ -86,18 +86,18 @@ test-replication-3site:
|
|||||||
test-delete-replication:
|
test-delete-replication:
|
||||||
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||||
|
|
||||||
test-replication: install test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
|
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
|
||||||
@echo "Running tests for replicating three sites"
|
@echo "Running tests for replicating three sites"
|
||||||
|
|
||||||
test-site-replication-ldap: install ## verify automatic site replication
|
test-site-replication-ldap: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
||||||
|
|
||||||
test-site-replication-oidc: install ## verify automatic site replication
|
test-site-replication-oidc: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with OIDC)"
|
@echo "Running tests for automatic site replication of IAM (with OIDC)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
|
||||||
|
|
||||||
test-site-replication-minio: install ## 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)
|
||||||
|
|
||||||
@@ -128,6 +128,9 @@ verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
|
|||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@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:
|
||||||
|
@(env bash $(PWD)/docs/debugging/build.sh)
|
||||||
|
|
||||||
build: checks ## builds minio to $(PWD)
|
build: checks ## 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
|
||||||
@@ -159,6 +162,12 @@ 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)
|
||||||
|
@echo "Building minio binary to './minio'"
|
||||||
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||||
|
@mkdir -p $(GOPATH)/bin && cp -f $(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 -f $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ service iptables restart
|
|||||||
|
|
||||||
MinIO Server comes with an embedded web based object browser. Point your web browser to <http://127.0.0.1:9000> to ensure your server has started successfully.
|
MinIO Server comes with an embedded web based object browser. Point your web browser to <http://127.0.0.1:9000> to ensure your server has started successfully.
|
||||||
|
|
||||||
> NOTE: MinIO runs console on random port by default if you wish choose a specific port use `--console-address` to pick a specific interface and port.
|
> NOTE: MinIO runs console on random port by default, if you wish to choose a specific port use `--console-address` to pick a specific interface and port.
|
||||||
|
|
||||||
### Things to consider
|
### Things to consider
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
### Upgrade Checklist
|
### Upgrade Checklist
|
||||||
|
|
||||||
- Test all upgrades in a lower environment (DEV, QA, UAT) before applying to production. Performing blind upgrades in production environments carries significant risk.
|
- Test all upgrades in a lower environment (DEV, QA, UAT) before applying to production. Performing blind upgrades in production environments carries significant risk.
|
||||||
- Read the release notes for MinIO *before* performing any upgrade, there is no forced requirement to upgrade to latest releases upon every releases. Some releases may not be relevant to your setup, avoid upgrading production environments unnecessarily.
|
- Read the release notes for MinIO *before* performing any upgrade, there is no forced requirement to upgrade to latest release upon every release. Some release may not be relevant to your setup, avoid upgrading production environments unnecessarily.
|
||||||
- If you plan to use `mc admin update`, MinIO process must have write access to the parent directory where the binary is present on the host system.
|
- If you plan to use `mc admin update`, MinIO process must have write access to the parent directory where the binary is present on the host system.
|
||||||
- `mc admin update` is not supported and should be avoided in kubernetes/container environments, please upgrade containers by upgrading relevant container images.
|
- `mc admin update` is not supported and should be avoided in kubernetes/container environments, please upgrade containers by upgrading relevant container images.
|
||||||
- **We do not recommend upgrading one MinIO server at a time, the product is designed to support parallel upgrades please follow our recommended guidelines.**
|
- **We do not recommend upgrading one MinIO server at a time, the product is designed to support parallel upgrades please follow our recommended guidelines.**
|
||||||
@@ -259,6 +259,6 @@ Please follow MinIO [Contributor's Guide](https://github.com/minio/minio/blob/ma
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
- MinIO source is licensed under the GNU AGPLv3 license that can be found in the [LICENSE](https://github.com/minio/minio/blob/master/LICENSE) file.
|
- MinIO source is licensed under the [GNU AGPLv3](https://github.com/minio/minio/blob/master/LICENSE).
|
||||||
- MinIO [Documentation](https://github.com/minio/minio/tree/master/docs) © 2021 by MinIO, Inc is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
- MinIO [documentation](https://github.com/minio/minio/tree/master/docs) is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
||||||
- [License Compliance](https://github.com/minio/minio/blob/master/COMPLIANCE.md)
|
- [License Compliance](https://github.com/minio/minio/blob/master/COMPLIANCE.md)
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ done
|
|||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
|
||||||
./mc ls minioadm/
|
./mc ls minioadm/
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "listing failed, 'minioadmin' should be enabled"
|
echo "listing failed, 'minioadmin' should be enabled"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
set -E
|
set -E
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
set -x
|
set -x
|
||||||
|
set -e
|
||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
|||||||
+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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy access control
|
// Data types used for returning dummy access control
|
||||||
|
|||||||
+147
-143
@@ -41,8 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,11 +55,9 @@ const (
|
|||||||
// specified in the quota configuration will be applied by default
|
// specified in the quota configuration will be applied by default
|
||||||
// to enforce total quota for the specified bucket.
|
// to enforce total quota for the specified bucket.
|
||||||
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketQuotaConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketQuotaAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,7 +94,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
Quota: data,
|
Quota: data,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}
|
}
|
||||||
if quotaConfig.Quota == 0 {
|
if quotaConfig.Size == 0 || quotaConfig.Quota == 0 {
|
||||||
bucketMeta.Quota = nil
|
bucketMeta.Quota = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,11 +107,9 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// GetBucketQuotaConfigHandler - gets bucket quota configuration
|
// GetBucketQuotaConfigHandler - gets bucket quota configuration
|
||||||
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketQuotaConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketQuotaAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -145,15 +140,14 @@ func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// SetRemoteTargetHandler - sets a remote target for bucket
|
// SetRemoteTargetHandler - sets a remote target for bucket
|
||||||
func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetBucketTarget")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
update := r.Form.Get("update") == "true"
|
update := r.Form.Get("update") == "true"
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -289,15 +283,14 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
// ListRemoteTargetsHandler - lists remote target(s) for a bucket or gets a target
|
// ListRemoteTargetsHandler - lists remote target(s) for a bucket or gets a target
|
||||||
// for a particular ARN type
|
// for a particular ARN type
|
||||||
func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListBucketTargets")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arnType := vars["type"]
|
arnType := vars["type"]
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -324,15 +317,14 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
// RemoveRemoteTargetHandler - removes a remote target for bucket with specified ARN
|
// RemoveRemoteTargetHandler - removes a remote target for bucket with specified ARN
|
||||||
func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RemoveBucketTarget")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arn := vars["arn"]
|
arn := vars["arn"]
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -368,12 +360,11 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
// ExportBucketMetadataHandler - exports all bucket metadata as a zipped file
|
// ExportBucketMetadataHandler - exports all bucket metadata as a zipped file
|
||||||
func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ExportBucketMetadata")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
bucket := pathClean(r.Form.Get("bucket"))
|
bucket := pathClean(r.Form.Get("bucket"))
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -652,12 +643,10 @@ func (i *importMetaReport) SetStatus(bucket, fname string, err error) {
|
|||||||
// 2. Replication config - is omitted from import as remote target credentials are not available from exported data for security reasons.
|
// 2. Replication config - is omitted from import as remote target credentials are not available from exported data for security reasons.
|
||||||
// 3. lifecycle config - if transition rules are present, tier name needs to have been defined.
|
// 3. lifecycle config - if transition rules are present, tier name needs to have been defined.
|
||||||
func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ImportBucketMetadata")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -672,12 +661,31 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bucketMap := make(map[string]struct{}, 1)
|
|
||||||
rpt := importMetaReport{
|
rpt := importMetaReport{
|
||||||
madmin.BucketMetaImportErrs{
|
madmin.BucketMetaImportErrs{
|
||||||
Buckets: make(map[string]madmin.BucketStatus, len(zr.File)),
|
Buckets: make(map[string]madmin.BucketStatus, len(zr.File)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bucketMap := make(map[string]*BucketMetadata, len(zr.File))
|
||||||
|
|
||||||
|
updatedAt := UTCNow()
|
||||||
|
|
||||||
|
for _, file := range zr.File {
|
||||||
|
slc := strings.Split(file.Name, slashSeparator)
|
||||||
|
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
|
||||||
|
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bucket := slc[0]
|
||||||
|
meta, err := readBucketMetadata(ctx, objectAPI, bucket)
|
||||||
|
if err == nil {
|
||||||
|
bucketMap[bucket] = &meta
|
||||||
|
} else if err != errConfigNotFound {
|
||||||
|
rpt.SetStatus(bucket, "", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// import object lock config if any - order of import matters here.
|
// import object lock config if any - order of import matters here.
|
||||||
for _, file := range zr.File {
|
for _, file := range zr.File {
|
||||||
slc := strings.Split(file.Name, slashSeparator)
|
slc := strings.Split(file.Name, slashSeparator)
|
||||||
@@ -714,7 +722,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bucketMap[bucket] = struct{}{}
|
v := newBucketMetadata(bucket)
|
||||||
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
||||||
@@ -723,27 +732,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, configData)
|
bucketMap[bucket].ObjectLockConfigXML = configData
|
||||||
if err != nil {
|
bucketMap[bucket].ObjectLockConfigUpdatedAt = updatedAt
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
// Call site replication hook.
|
|
||||||
//
|
|
||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
|
||||||
// errors.
|
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
|
||||||
Bucket: bucket,
|
|
||||||
ObjectLockConfig: &cfgStr,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,7 +764,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bucketMap[bucket] = struct{}{}
|
v := newBucketMetadata(bucket)
|
||||||
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalSiteReplicationSys.isEnabled() && v.Suspended() {
|
if globalSiteReplicationSys.isEnabled() && v.Suspended() {
|
||||||
@@ -796,10 +788,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, configData); err != nil {
|
bucketMap[bucket].VersioningConfigXML = configData
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
bucketMap[bucket].VersioningConfigUpdatedAt = updatedAt
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -817,6 +807,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bucket, fileName := slc[0], slc[1]
|
bucket, fileName := slc[0], slc[1]
|
||||||
|
|
||||||
// create bucket if it does not exist yet.
|
// create bucket if it does not exist yet.
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{})
|
err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{})
|
||||||
@@ -826,7 +817,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bucketMap[bucket] = struct{}{}
|
v := newBucketMetadata(bucket)
|
||||||
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
continue
|
continue
|
||||||
@@ -845,12 +837,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketNotificationConfig, configData); err != nil {
|
bucketMap[bucket].NotificationConfigXML = configData
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rulesMap := config.ToRulesMap()
|
|
||||||
globalEventNotifier.AddRulesMap(bucket, rulesMap)
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
case bucketPolicyConfig:
|
case bucketPolicyConfig:
|
||||||
// Error out if Content-Length is beyond allowed size.
|
// Error out if Content-Length is beyond allowed size.
|
||||||
@@ -865,7 +852,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
bucketPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
@@ -883,22 +870,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, configData)
|
bucketMap[bucket].PolicyConfigJSON = configData
|
||||||
if err != nil {
|
bucketMap[bucket].PolicyConfigUpdatedAt = updatedAt
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
// Call site replication hook.
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
|
||||||
Bucket: bucket,
|
|
||||||
Policy: bucketPolicyBytes,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case bucketLifecycleConfig:
|
case bucketLifecycleConfig:
|
||||||
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(reader, sz))
|
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(reader, sz))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -924,10 +898,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, configData); err != nil {
|
bucketMap[bucket].LifecycleConfigXML = configData
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
bucketMap[bucket].LifecycleConfigUpdatedAt = updatedAt
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
case bucketSSEConfig:
|
case bucketSSEConfig:
|
||||||
// Parse bucket encryption xml
|
// Parse bucket encryption xml
|
||||||
@@ -962,29 +934,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the bucket encryption configuration in the object layer
|
bucketMap[bucket].EncryptionConfigXML = configData
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, configData)
|
bucketMap[bucket].EncryptionConfigUpdatedAt = updatedAt
|
||||||
if err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
// Call site replication hook.
|
|
||||||
//
|
|
||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
|
||||||
// errors.
|
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
|
||||||
Bucket: bucket,
|
|
||||||
SSEConfig: &cfgStr,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
case bucketTaggingConfig:
|
case bucketTaggingConfig:
|
||||||
tags, err := tags.ParseBucketXML(io.LimitReader(reader, sz))
|
tags, err := tags.ParseBucketXML(io.LimitReader(reader, sz))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -998,27 +950,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, configData)
|
bucketMap[bucket].TaggingConfigXML = configData
|
||||||
if err != nil {
|
bucketMap[bucket].TaggingConfigUpdatedAt = updatedAt
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
// Call site replication hook.
|
|
||||||
//
|
|
||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
|
||||||
// errors.
|
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
|
||||||
Bucket: bucket,
|
|
||||||
Tags: &cfgStr,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case bucketQuotaConfigFile:
|
case bucketQuotaConfigFile:
|
||||||
data, err := io.ReadAll(reader)
|
data, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1026,35 +960,47 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
quotaConfig, err := parseBucketQuota(bucket, data)
|
_, err = parseBucketQuota(bucket, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
|
bucketMap[bucket].QuotaConfigJSON = data
|
||||||
if err != nil {
|
bucketMap[bucket].QuotaConfigUpdatedAt = updatedAt
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
bucketMeta := madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeQuotaConfig,
|
|
||||||
Bucket: bucket,
|
|
||||||
Quota: data,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}
|
}
|
||||||
if quotaConfig.Quota == 0 {
|
|
||||||
bucketMeta.Quota = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
enc := func(b []byte) *string {
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil {
|
if b == nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
return nil
|
||||||
|
}
|
||||||
|
v := base64.StdEncoding.EncodeToString(b)
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
for bucket, meta := range bucketMap {
|
||||||
|
err := globalBucketMetadataSys.save(ctx, *meta)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, "", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Call site replication hook.
|
||||||
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
|
Bucket: bucket,
|
||||||
|
Quota: meta.QuotaConfigJSON,
|
||||||
|
Policy: meta.PolicyConfigJSON,
|
||||||
|
Versioning: enc(meta.VersioningConfigXML),
|
||||||
|
Tags: enc(meta.TaggingConfigXML),
|
||||||
|
ObjectLockConfig: enc(meta.ObjectLockConfigXML),
|
||||||
|
SSEConfig: enc(meta.EncryptionConfigXML),
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
rpt.SetStatus(bucket, "", err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rptData, err := json.Marshal(rpt.BucketMetaImportErrs)
|
rptData, err := json.Marshal(rpt.BucketMetaImportErrs)
|
||||||
@@ -1069,13 +1015,12 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
||||||
// to the connected HTTP client.
|
// to the connected HTTP client.
|
||||||
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ReplicationDiff")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1129,3 +1074,62 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReplicationMRFHandler - POST returns info on entries in the MRF backlog for a node or all nodes
|
||||||
|
func (a adminAPIHandlers) ReplicationMRFHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if bucket != "" {
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
q := r.Form
|
||||||
|
node := q.Get("node")
|
||||||
|
|
||||||
|
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer keepAliveTicker.Stop()
|
||||||
|
|
||||||
|
mrfCh, err := globalNotificationSys.GetReplicationMRF(ctx, bucket, node)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case entry, ok := <-mrfCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := enc.Encode(entry); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(mrfCh) == 0 {
|
||||||
|
// Flush if nothing is queued
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
case <-keepAliveTicker.C:
|
||||||
|
if len(mrfCh) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(" ")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validateAdminReq will validate request against and return whether it is allowed.
|
// validateAdminReq will validate request against and return whether it is allowed.
|
||||||
// If any of the supplied actions are allowed it will be successful.
|
// If any of the supplied actions are allowed it will be successful.
|
||||||
// If nil ObjectLayer is returned, the operation is not permitted.
|
// If nil ObjectLayer is returned, the operation is not permitted.
|
||||||
// When nil ObjectLayer has been returned an error has always been sent to w.
|
// When nil ObjectLayer has been returned an error has always been sent to w.
|
||||||
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
|
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...policy.AdminAction) (ObjectLayer, auth.Credentials) {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
@@ -78,7 +78,7 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
|
|
||||||
var apiErr APIError
|
var apiErr APIError
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case iampolicy.Error:
|
case policy.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioMalformedIAMPolicy",
|
Code: "XMinioMalformedIAMPolicy",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
|
|||||||
@@ -38,16 +38,14 @@ 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"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
||||||
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -149,11 +147,9 @@ type setConfigResult struct {
|
|||||||
|
|
||||||
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
||||||
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -244,11 +240,9 @@ func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (re
|
|||||||
//
|
//
|
||||||
// This is a reporting API and config secrets are redacted in the response.
|
// This is a reporting API and config secrets are redacted in the response.
|
||||||
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -292,11 +286,9 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ClearConfigHistoryKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -327,11 +319,9 @@ func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// RestoreConfigHistoryKVHandler - restores a config with KV settings for the given KV id.
|
// RestoreConfigHistoryKVHandler - restores a config with KV settings for the given KV id.
|
||||||
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RestoreConfigHistoryKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -375,11 +365,9 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
|
|
||||||
// ListConfigHistoryKVHandler - lists all the KV ids.
|
// ListConfigHistoryKVHandler - lists all the KV ids.
|
||||||
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListConfigHistoryKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -415,11 +403,9 @@ func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *h
|
|||||||
|
|
||||||
// HelpConfigKVHandler - GET /minio/admin/v3/help-config-kv?subSys={subSys}&key={key}
|
// HelpConfigKVHandler - GET /minio/admin/v3/help-config-kv?subSys={subSys}&key={key}
|
||||||
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HelpConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -442,11 +428,9 @@ func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
// SetConfigHandler - PUT /minio/admin/v3/config
|
// SetConfigHandler - PUT /minio/admin/v3/config
|
||||||
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -496,11 +480,9 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
// This endpoint is mainly for exporting and backing up the configuration.
|
// This endpoint is mainly for exporting and backing up the configuration.
|
||||||
// Secrets are not redacted.
|
// Secrets are not redacted.
|
||||||
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/ldap"
|
||||||
"github.com/minio/pkg/ldap"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a adminAPIHandlers) 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) {
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -198,32 +198,29 @@ func handleCreateUpdateValidation(s config.Config, subSys, cfgTarget string, isU
|
|||||||
//
|
//
|
||||||
// PUT <admin-prefix>/idp-cfg/openid/_ -> create (default) named config `_`
|
// PUT <admin-prefix>/idp-cfg/openid/_ -> create (default) named config `_`
|
||||||
func (a adminAPIHandlers) AddIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddIdentityProviderCfg")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
a.addOrUpdateIDPHandler(ctx, w, r, false)
|
addOrUpdateIDPHandler(ctx, w, r, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateIdentityProviderCfg: updates an existing IDP config for openid/ldap.
|
// UpdateIdentityProviderCfg: updates an existing IDP config for openid/ldap.
|
||||||
//
|
//
|
||||||
// PATCH <admin-prefix>/idp-cfg/openid/dex1 -> update named config `dex1`
|
// POST <admin-prefix>/idp-cfg/openid/dex1 -> update named config `dex1`
|
||||||
//
|
//
|
||||||
// PATCH <admin-prefix>/idp-cfg/openid/_ -> update (default) named config `_`
|
// POST <admin-prefix>/idp-cfg/openid/_ -> update (default) named config `_`
|
||||||
func (a adminAPIHandlers) UpdateIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) UpdateIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "UpdateIdentityProviderCfg")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
a.addOrUpdateIDPHandler(ctx, w, r, true)
|
addOrUpdateIDPHandler(ctx, w, r, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListIdentityProviderCfg:
|
// ListIdentityProviderCfg:
|
||||||
//
|
//
|
||||||
// GET <admin-prefix>/idp-cfg/openid -> lists openid provider configs.
|
// GET <admin-prefix>/idp-cfg/openid -> lists openid provider configs.
|
||||||
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListIdentityProviderCfg")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -274,10 +271,9 @@ func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http
|
|||||||
//
|
//
|
||||||
// GET <admin-prefix>/idp-cfg/openid/dex_test
|
// GET <admin-prefix>/idp-cfg/openid/dex_test
|
||||||
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetIdentityProviderCfg")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -334,11 +330,9 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
//
|
//
|
||||||
// DELETE <admin-prefix>/idp-cfg/openid/dex_test
|
// DELETE <admin-prefix>/idp-cfg/openid/dex_test
|
||||||
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteIdentityProviderCfg")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,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"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
||||||
@@ -45,14 +45,12 @@ import (
|
|||||||
//
|
//
|
||||||
// When all query parameters are omitted, returns mappings for all policies.
|
// When all query parameters are omitted, returns mappings for all policies.
|
||||||
func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListLDAPPolicyMappingEntities")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Check authorization.
|
// Check authorization.
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r,
|
objectAPI, cred := validateAdminReq(ctx, w, r,
|
||||||
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction)
|
policy.ListGroupsAdminAction, policy.ListUsersAdminAction, policy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -94,13 +92,11 @@ func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r
|
|||||||
//
|
//
|
||||||
// POST <admin-prefix>/idp/ldap/policy/{operation}
|
// POST <admin-prefix>/idp/ldap/policy/{operation}
|
||||||
func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AttachDetachPolicyLDAP")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Check authorization.
|
// Check authorization.
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.UpdatePolicyAssociationAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-27
@@ -26,20 +26,18 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errRebalanceDecommissionAlreadyRunning = errors.New("Rebalance cannot be started, decommission is aleady in progress")
|
errRebalanceDecommissionAlreadyRunning = errors.New("Rebalance cannot be started, decommission is already in progress")
|
||||||
errDecommissionRebalanceAlreadyRunning = errors.New("Decommission cannot be started, rebalance is already in progress")
|
errDecommissionRebalanceAlreadyRunning = errors.New("Decommission cannot be started, rebalance is already in progress")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StartDecommission")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -113,11 +111,9 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "CancelDecommission")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -161,11 +157,9 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StatusPool")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -204,11 +198,9 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListPools")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -239,10 +231,9 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RebalanceStart")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -311,10 +302,9 @@ func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RebalanceStatus")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -352,10 +342,9 @@ func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RebalanceStop")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,27 +20,27 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/mux"
|
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
||||||
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationAdd")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -72,11 +72,9 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
|
|||||||
// used internally to tell current cluster to enable SR with
|
// used internally to tell current cluster to enable SR with
|
||||||
// the provided peer clusters and service account.
|
// the provided peer clusters and service account.
|
||||||
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerJoin")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -96,11 +94,9 @@ func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// SRPeerBucketOps - PUT /minio/admin/v3/site-replication/bucket-ops?bucket=x&operation=y
|
// SRPeerBucketOps - PUT /minio/admin/v3/site-replication/bucket-ops?bucket=x&operation=y
|
||||||
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerBucketOps")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -145,11 +141,9 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
// SRPeerReplicateIAMItem - PUT /minio/admin/v3/site-replication/iam-item
|
// SRPeerReplicateIAMItem - PUT /minio/admin/v3/site-replication/iam-item
|
||||||
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerReplicateIAMItem")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -168,7 +162,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy))
|
policy, perr := policy.ParseConfig(bytes.NewReader(item.Policy))
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -199,11 +193,9 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/bucket-meta
|
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/bucket-meta
|
||||||
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerReplicateBucketItem")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -214,15 +206,20 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item.Bucket == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errSRInvalidRequest(errInvalidArgument)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch item.Type {
|
switch item.Type {
|
||||||
default:
|
default:
|
||||||
err = errSRInvalidRequest(errInvalidArgument)
|
err = globalSiteReplicationSys.PeerBucketMetadataUpdateHandler(ctx, item)
|
||||||
case madmin.SRBucketMetaTypePolicy:
|
case madmin.SRBucketMetaTypePolicy:
|
||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
bktPolicy, berr := policy.ParseConfig(bytes.NewReader(item.Policy), item.Bucket)
|
bktPolicy, berr := policy.ParseBucketPolicyConfig(bytes.NewReader(item.Policy), item.Bucket)
|
||||||
if berr != nil {
|
if berr != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -243,7 +240,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, quotaConfig, item.UpdatedAt); err != nil {
|
if err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, quotaConfig, item.UpdatedAt); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,11 +262,9 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
// SiteReplicationInfo - GET /minio/admin/v3/site-replication/info
|
// SiteReplicationInfo - GET /minio/admin/v3/site-replication/info
|
||||||
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -287,11 +282,9 @@ func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationGetIDPSettings")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -326,11 +319,9 @@ func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptio
|
|||||||
|
|
||||||
// SiteReplicationStatus - GET /minio/admin/v3/site-replication/status
|
// SiteReplicationStatus - GET /minio/admin/v3/site-replication/status
|
||||||
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationStatus")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -357,11 +348,9 @@ func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
// SiteReplicationMetaInfo - GET /minio/admin/v3/site-replication/metainfo
|
// SiteReplicationMetaInfo - GET /minio/admin/v3/site-replication/metainfo
|
||||||
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationMetaInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -381,10 +370,9 @@ func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http
|
|||||||
|
|
||||||
// SiteReplicationEdit - PUT /minio/admin/v3/site-replication/edit
|
// SiteReplicationEdit - PUT /minio/admin/v3/site-replication/edit
|
||||||
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationEdit")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -413,10 +401,9 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
|
|||||||
//
|
//
|
||||||
// used internally to tell current cluster to update endpoint for peer
|
// used internally to tell current cluster to update endpoint for peer
|
||||||
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerEdit")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -443,16 +430,15 @@ func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
|
|||||||
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
||||||
opts.EntityValue = q.Get("entityvalue")
|
opts.EntityValue = q.Get("entityvalue")
|
||||||
opts.ShowDeleted = q.Get("showDeleted") == "true"
|
opts.ShowDeleted = q.Get("showDeleted") == "true"
|
||||||
|
opts.Metrics = q.Get("metrics") == "true"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SiteReplicationRemove - PUT /minio/admin/v3/site-replication/remove
|
// SiteReplicationRemove - PUT /minio/admin/v3/site-replication/remove
|
||||||
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationRemove")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -481,10 +467,9 @@ func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.R
|
|||||||
//
|
//
|
||||||
// used internally to tell current cluster to update endpoint for peer
|
// used internally to tell current cluster to update endpoint for peer
|
||||||
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerRemove")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -504,11 +489,9 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// SiteReplicationResyncOp - PUT /minio/admin/v3/site-replication/resync/op
|
// SiteReplicationResyncOp - PUT /minio/admin/v3/site-replication/resync/op
|
||||||
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationResyncOp")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationResyncAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationResyncAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -543,3 +526,45 @@ func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http
|
|||||||
}
|
}
|
||||||
writeSuccessResponseJSON(w, body)
|
writeSuccessResponseJSON(w, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SiteReplicationDevNull - everything goes to io.Discard
|
||||||
|
// [POST] /minio/admin/v3/site-replication/devnull
|
||||||
|
func (a adminAPIHandlers) SiteReplicationDevNull(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
globalSiteNetPerfRX.Connect()
|
||||||
|
defer globalSiteNetPerfRX.Disconnect()
|
||||||
|
|
||||||
|
connectTime := time.Now()
|
||||||
|
for {
|
||||||
|
n, err := io.CopyN(io.Discard, r.Body, 128*humanize.KiByte)
|
||||||
|
atomic.AddUint64(&globalSiteNetPerfRX.RX, uint64(n))
|
||||||
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
|
// 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.
|
||||||
|
if time.Since(connectTime) < (globalNetPerfMinDuration - time.Second) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SiteReplicationNetPerf - everything goes to io.Discard
|
||||||
|
// [POST] /minio/admin/v3/site-replication/netperf
|
||||||
|
func (a adminAPIHandlers) SiteReplicationNetPerf(w http.ResponseWriter, r *http.Request) {
|
||||||
|
durationStr := r.Form.Get(peerRESTDuration)
|
||||||
|
duration, _ := time.ParseDuration(durationStr)
|
||||||
|
if duration < globalNetPerfMinDuration {
|
||||||
|
duration = globalNetPerfMinDuration
|
||||||
|
}
|
||||||
|
result := siteNetperf(r.Context(), duration)
|
||||||
|
logger.LogIf(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/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
||||||
|
|||||||
+123
-157
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -34,16 +35,14 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
||||||
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RemoveUser")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.DeleteUserAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -85,11 +84,9 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// ListBucketUsers - GET /minio/admin/v3/list-users?bucket={bucket}
|
// ListBucketUsers - GET /minio/admin/v3/list-users?bucket={bucket}
|
||||||
func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListBucketUsers")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ListUsersAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -121,11 +118,9 @@ func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
// ListUsers - GET /minio/admin/v3/list-users
|
// ListUsers - GET /minio/admin/v3/list-users
|
||||||
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListUsers")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ListUsersAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -167,9 +162,7 @@ func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// GetUserInfo - GET /minio/admin/v3/user-info
|
// GetUserInfo - GET /minio/admin/v3/user-info
|
||||||
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetUserInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
name := vars["accessKey"]
|
name := vars["accessKey"]
|
||||||
@@ -194,10 +187,10 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
checkDenyOnly = true
|
checkDenyOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.GetUserAdminAction,
|
Action: policy.GetUserAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -224,11 +217,9 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// UpdateGroupMembers - PUT /minio/admin/v3/update-group-members
|
// UpdateGroupMembers - PUT /minio/admin/v3/update-group-members
|
||||||
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "UpdateGroupMembers")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.AddUserToGroupAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -296,11 +287,9 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// GetGroup - /minio/admin/v3/group?group=mygroup1
|
// GetGroup - /minio/admin/v3/group?group=mygroup1
|
||||||
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetGroup")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetGroupAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetGroupAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -325,11 +314,9 @@ func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// ListGroups - GET /minio/admin/v3/groups
|
// ListGroups - GET /minio/admin/v3/groups
|
||||||
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListGroups")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListGroupsAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -351,11 +338,9 @@ func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// SetGroupStatus - PUT /minio/admin/v3/set-group-status?group=mygroup1&status=enabled
|
// SetGroupStatus - PUT /minio/admin/v3/set-group-status?group=mygroup1&status=enabled
|
||||||
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetGroupStatus")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.EnableGroupAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -396,11 +381,9 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
// SetUserStatus - PUT /minio/admin/v3/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
|
// SetUserStatus - PUT /minio/admin/v3/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
|
||||||
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetUserStatus")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, creds := validateAdminReq(ctx, w, r, policy.EnableUserAdminAction)
|
||||||
|
|
||||||
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -436,9 +419,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
// AddUser - PUT /minio/admin/v3/add-user?accessKey=<access_key>
|
// AddUser - PUT /minio/admin/v3/add-user?accessKey=<access_key>
|
||||||
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddUser")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
accessKey := vars["accessKey"]
|
accessKey := vars["accessKey"]
|
||||||
@@ -490,10 +471,10 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
checkDenyOnly = true
|
checkDenyOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.CreateUserAdminAction,
|
Action: policy.CreateUserAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -543,9 +524,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// TemporaryAccountInfo - GET /minio/admin/v3/temporary-account-info
|
// TemporaryAccountInfo - GET /minio/admin/v3/temporary-account-info
|
||||||
func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "TemporaryAccountInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -566,10 +545,10 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListTemporaryAccountsAdminAction,
|
Action: policy.ListTemporaryAccountsAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -578,16 +557,16 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stsAccount, policy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey)
|
stsAccount, sessionPolicy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var stsAccountPolicy iampolicy.Policy
|
var stsAccountPolicy policy.Policy
|
||||||
|
|
||||||
if policy != nil {
|
if sessionPolicy != nil {
|
||||||
stsAccountPolicy = *policy
|
stsAccountPolicy = *sessionPolicy
|
||||||
} else {
|
} else {
|
||||||
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false)
|
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -606,7 +585,7 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
infoResp := madmin.TemporaryAccountInfoResp{
|
infoResp := madmin.TemporaryAccountInfoResp{
|
||||||
ParentUser: stsAccount.ParentUser,
|
ParentUser: stsAccount.ParentUser,
|
||||||
AccountStatus: stsAccount.Status,
|
AccountStatus: stsAccount.Status,
|
||||||
ImpliedPolicy: policy == nil,
|
ImpliedPolicy: sessionPolicy == nil,
|
||||||
Policy: string(policyJSON),
|
Policy: string(policyJSON),
|
||||||
Expiration: &stsAccount.Expiration,
|
Expiration: &stsAccount.Expiration,
|
||||||
}
|
}
|
||||||
@@ -628,9 +607,7 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||||
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddServiceAccount")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -733,10 +710,10 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
//
|
//
|
||||||
// This allows turning off service accounts for request sender,
|
// This allows turning off service accounts for request sender,
|
||||||
// if there is no deny statement this call is implicitly enabled.
|
// if there is no deny statement this call is implicitly enabled.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: requestorUser,
|
AccountName: requestorUser,
|
||||||
Groups: requestorGroups,
|
Groups: requestorGroups,
|
||||||
Action: iampolicy.CreateServiceAccountAdminAction,
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -767,10 +744,10 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
} else {
|
} else {
|
||||||
// Need permission if we are creating a service account for a
|
// Need permission if we are creating a service account for a
|
||||||
// user <> to the request sender
|
// user <> to the request sender
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: requestorUser,
|
AccountName: requestorUser,
|
||||||
Groups: requestorGroups,
|
Groups: requestorGroups,
|
||||||
Action: iampolicy.CreateServiceAccountAdminAction,
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -797,9 +774,9 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// latter, a group notion is not supported.
|
// latter, a group notion is not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
var sp *iampolicy.Policy
|
var sp *policy.Policy
|
||||||
if len(createReq.Policy) > 0 {
|
if len(createReq.Policy) > 0 {
|
||||||
sp, err = iampolicy.ParseConfig(bytes.NewReader(createReq.Policy))
|
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -861,9 +838,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
// UpdateServiceAccount - POST /minio/admin/v3/update-service-account
|
// UpdateServiceAccount - POST /minio/admin/v3/update-service-account
|
||||||
func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "UpdateServiceAccount")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -890,10 +865,10 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.UpdateServiceAccountAdminAction,
|
Action: policy.UpdateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -929,9 +904,9 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var sp *iampolicy.Policy
|
var sp *policy.Policy
|
||||||
if len(updateReq.NewPolicy) > 0 {
|
if len(updateReq.NewPolicy) > 0 {
|
||||||
sp, err = iampolicy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -975,9 +950,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
// InfoServiceAccount - GET /minio/admin/v3/info-service-account
|
// InfoServiceAccount - GET /minio/admin/v3/info-service-account
|
||||||
func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "InfoServiceAccount")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -998,16 +971,16 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
svcAccount, policy, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
|
svcAccount, sessionPolicy, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
|
||||||
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 !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListServiceAccountsAdminAction,
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -1023,10 +996,10 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var svcAccountPolicy iampolicy.Policy
|
var svcAccountPolicy policy.Policy
|
||||||
|
|
||||||
if policy != nil {
|
if sessionPolicy != nil {
|
||||||
svcAccountPolicy = *policy
|
svcAccountPolicy = *sessionPolicy
|
||||||
} else {
|
} else {
|
||||||
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false)
|
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1052,7 +1025,7 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
Name: svcAccount.Name,
|
Name: svcAccount.Name,
|
||||||
Description: svcAccount.Description,
|
Description: svcAccount.Description,
|
||||||
AccountStatus: svcAccount.Status,
|
AccountStatus: svcAccount.Status,
|
||||||
ImpliedPolicy: policy == nil,
|
ImpliedPolicy: sessionPolicy == nil,
|
||||||
Policy: string(policyJSON),
|
Policy: string(policyJSON),
|
||||||
Expiration: expiration,
|
Expiration: expiration,
|
||||||
}
|
}
|
||||||
@@ -1074,9 +1047,7 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// ListServiceAccounts - GET /minio/admin/v3/list-service-accounts
|
// ListServiceAccounts - GET /minio/admin/v3/list-service-accounts
|
||||||
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListServiceAccounts")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -1097,10 +1068,10 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
|||||||
// sender), check that the user has permissions.
|
// sender), check that the user has permissions.
|
||||||
user := r.Form.Get("user")
|
user := r.Form.Get("user")
|
||||||
if user != "" && user != cred.AccessKey {
|
if user != "" && user != cred.AccessKey {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListServiceAccountsAdminAction,
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -1153,9 +1124,7 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
// DeleteServiceAccount - DELETE /minio/admin/v3/delete-service-account
|
// DeleteServiceAccount - DELETE /minio/admin/v3/delete-service-account
|
||||||
func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteServiceAccount")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -1180,10 +1149,10 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
// 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, _, _ := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
svcAccount, _, _ := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
||||||
|
|
||||||
adminPrivilege := globalIAMSys.IsAllowed(iampolicy.Args{
|
adminPrivilege := globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.RemoveServiceAccountAdminAction,
|
Action: policy.RemoveServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -1224,11 +1193,9 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountInfoHandler returns usage
|
// AccountInfoHandler returns usage, permissions and other bucket metadata for incoming us
|
||||||
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AccountInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -1253,10 +1220,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
enablePrefixUsage := r.Form.Get("prefix-usage") == "true"
|
enablePrefixUsage := r.Form.Get("prefix-usage") == "true"
|
||||||
|
|
||||||
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -1266,10 +1233,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
rd = true
|
rd = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.GetBucketLocationAction,
|
Action: policy.GetBucketLocationAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -1279,10 +1246,10 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
rd = true
|
rd = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectAction,
|
Action: policy.PutObjectAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -1295,12 +1262,30 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return rd, wr
|
return rd, wr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the latest calculated data usage
|
bucketStorageCache.Once.Do(func() {
|
||||||
dataUsageInfo, _ := loadDataUsageFromBackend(ctx, objectAPI)
|
// Set this to 10 secs since its enough, as scanner
|
||||||
|
// does not update the bucket usage values frequently.
|
||||||
|
bucketStorageCache.TTL = 10 * 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()
|
||||||
|
|
||||||
|
return loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var dataUsageInfo DataUsageInfo
|
||||||
|
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 buckets []BucketInfo
|
|
||||||
var err error
|
var err error
|
||||||
|
var buckets []BucketInfo
|
||||||
if globalDNSConfig != nil && globalBucketFederation {
|
if globalDNSConfig != nil && globalBucketFederation {
|
||||||
dnsBuckets, err := globalDNSConfig.List()
|
dnsBuckets, err := globalDNSConfig.List()
|
||||||
if err != nil && !IsErrIgnored(err,
|
if err != nil && !IsErrIgnored(err,
|
||||||
@@ -1319,7 +1304,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return buckets[i].Name < buckets[j].Name
|
return buckets[i].Name < buckets[j].Name
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{})
|
buckets, err = objectAPI.ListBuckets(ctx, BucketOptions{Cached: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1332,14 +1317,14 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
accountName = cred.ParentUser
|
accountName = cred.ParentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
roleArn := iampolicy.Args{Claims: cred.Claims}.GetRoleArn()
|
roleArn := policy.Args{Claims: cred.Claims}.GetRoleArn()
|
||||||
policySetFromClaims, hasPolicyClaim := iampolicy.GetPoliciesFromClaims(cred.Claims, iamPolicyClaimNameOpenID())
|
policySetFromClaims, hasPolicyClaim := policy.GetPoliciesFromClaims(cred.Claims, iamPolicyClaimNameOpenID())
|
||||||
var effectivePolicy iampolicy.Policy
|
var effectivePolicy policy.Policy
|
||||||
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
switch {
|
switch {
|
||||||
case accountName == globalActiveCred.AccessKey:
|
case accountName == globalActiveCred.AccessKey:
|
||||||
for _, policy := range iampolicy.DefaultPolicies {
|
for _, policy := range policy.DefaultPolicies {
|
||||||
if policy.Name == "consoleAdmin" {
|
if policy.Name == "consoleAdmin" {
|
||||||
effectivePolicy = policy.Definition
|
effectivePolicy = policy.Definition
|
||||||
break
|
break
|
||||||
@@ -1449,11 +1434,9 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
// timestamps along with the policy JSON. Both versions are supported for now,
|
// timestamps along with the policy JSON. Both versions are supported for now,
|
||||||
// for smooth transition to new API.
|
// for smooth transition to new API.
|
||||||
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "InfoCannedPolicy")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetPolicyAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1497,11 +1480,9 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
// ListBucketPolicies - GET /minio/admin/v3/list-canned-policies?bucket={bucket}
|
// ListBucketPolicies - GET /minio/admin/v3/list-canned-policies?bucket={bucket}
|
||||||
func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListBucketPolicies")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListUserPoliciesAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1513,7 +1494,7 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPolicies := make(map[string]iampolicy.Policy)
|
newPolicies := make(map[string]policy.Policy)
|
||||||
for name, p := range policies {
|
for name, p := range policies {
|
||||||
_, err = json.Marshal(p)
|
_, err = json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1530,11 +1511,9 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// ListCannedPolicies - GET /minio/admin/v3/list-canned-policies
|
// ListCannedPolicies - GET /minio/admin/v3/list-canned-policies
|
||||||
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListCannedPolicies")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListUserPoliciesAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1545,7 +1524,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPolicies := make(map[string]iampolicy.Policy)
|
newPolicies := make(map[string]policy.Policy)
|
||||||
for name, p := range policies {
|
for name, p := range policies {
|
||||||
_, err = json.Marshal(p)
|
_, err = json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1562,11 +1541,9 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// RemoveCannedPolicy - DELETE /minio/admin/v3/remove-canned-policy?name=<policy_name>
|
// RemoveCannedPolicy - DELETE /minio/admin/v3/remove-canned-policy?name=<policy_name>
|
||||||
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RemoveCannedPolicy")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DeletePolicyAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1590,11 +1567,9 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// AddCannedPolicy - PUT /minio/admin/v3/add-canned-policy?name=<policy_name>
|
// AddCannedPolicy - PUT /minio/admin/v3/add-canned-policy?name=<policy_name>
|
||||||
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddCannedPolicy")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.CreatePolicyAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1626,7 +1601,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iamPolicy, err := iampolicy.ParseConfig(bytes.NewReader(iamPolicyBytes))
|
iamPolicy, err := policy.ParseConfig(bytes.NewReader(iamPolicyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1656,11 +1631,9 @@ 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 - PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group]
|
||||||
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.AttachPolicyAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1730,13 +1703,11 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
|
|
||||||
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx
|
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx
|
||||||
func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListPolicyMappingEntities")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Check authorization.
|
// Check authorization.
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r,
|
objectAPI, cred := validateAdminReq(ctx, w, r,
|
||||||
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction)
|
policy.ListGroupsAdminAction, policy.ListUsersAdminAction, policy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1772,12 +1743,10 @@ func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
// AttachDetachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/policy/{operation}
|
// AttachDetachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/policy/{operation}
|
||||||
func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AttachPolicyBuiltin")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.UpdatePolicyAssociationAction,
|
||||||
|
policy.AttachPolicyAdminAction)
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction,
|
|
||||||
iampolicy.AttachPolicyAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1873,11 +1842,10 @@ const (
|
|||||||
|
|
||||||
// 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 := newContext(r, w, "ExportIAM")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportIAMAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2112,9 +2080,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// ImportIAM - imports all IAM info into MinIO
|
// ImportIAM - imports all IAM info into MinIO
|
||||||
func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ImportIAM")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -2149,7 +2115,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
var allPolicies map[string]iampolicy.Policy
|
var allPolicies map[string]policy.Policy
|
||||||
data, err = io.ReadAll(f)
|
data, err = io.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
|
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
|
||||||
@@ -2230,10 +2196,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
checkDenyOnly = true
|
checkDenyOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.CreateUserAdminAction,
|
Action: policy.CreateUserAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -2310,10 +2276,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for user, svcAcctReq := range serviceAcctReqs {
|
for user, svcAcctReq := range serviceAcctReqs {
|
||||||
var sp *iampolicy.Policy
|
var sp *policy.Policy
|
||||||
var err error
|
var err error
|
||||||
if len(svcAcctReq.SessionPolicy) > 0 {
|
if len(svcAcctReq.SessionPolicy) > 0 {
|
||||||
sp, err = iampolicy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy))
|
sp, err = policy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2324,10 +2290,10 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.CreateServiceAccountAdminAction,
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -36,11 +35,11 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -122,7 +121,7 @@ var iamTestSuites = func() []*TestSuiteIAM {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnvTestEtcdBackend = "ETCD_SERVER"
|
EnvTestEtcdBackend = "_MINIO_ETCD_TEST_SERVER"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *TestSuiteIAM) setUpEtcd(c *check, etcdServer string) {
|
func (s *TestSuiteIAM) setUpEtcd(c *check, etcdServer string) {
|
||||||
@@ -145,7 +144,7 @@ func (s *TestSuiteIAM) setUpEtcd(c *check, etcdServer string) {
|
|||||||
func (s *TestSuiteIAM) SetUpSuite(c *check) {
|
func (s *TestSuiteIAM) SetUpSuite(c *check) {
|
||||||
// If etcd backend is specified and etcd server is not present, the test
|
// If etcd backend is specified and etcd server is not present, the test
|
||||||
// is skipped.
|
// is skipped.
|
||||||
etcdServer := os.Getenv(EnvTestEtcdBackend)
|
etcdServer := env.Get(EnvTestEtcdBackend, "")
|
||||||
if s.withEtcdBackend && etcdServer == "" {
|
if s.withEtcdBackend && etcdServer == "" {
|
||||||
c.Skip("Skipping etcd backend IAM test as no etcd server is configured.")
|
c.Skip("Skipping etcd backend IAM test as no etcd server is configured.")
|
||||||
}
|
}
|
||||||
@@ -877,7 +876,7 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
|
|||||||
|
|
||||||
// Create an madmin client with user creds
|
// Create an madmin client with user creds
|
||||||
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||||
Creds: cr.NewStaticV4(accessKey, secretKey, ""),
|
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||||
Secure: s.secure,
|
Secure: s.secure,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -984,9 +983,9 @@ func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
pluginEndpoint := os.Getenv("POLICY_PLUGIN_ENDPOINT")
|
pluginEndpoint := env.Get("_MINIO_POLICY_PLUGIN_ENDPOINT", "")
|
||||||
if pluginEndpoint == "" {
|
if pluginEndpoint == "" {
|
||||||
c.Skip("POLICY_PLUGIN_ENDPOINT not given - skipping.")
|
c.Skip("_MINIO_POLICY_PLUGIN_ENDPOINT not given - skipping.")
|
||||||
}
|
}
|
||||||
|
|
||||||
configCmds := []string{
|
configCmds := []string{
|
||||||
@@ -1072,7 +1071,7 @@ func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
|
|||||||
|
|
||||||
// Create an madmin client with user creds
|
// Create an madmin client with user creds
|
||||||
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||||
Creds: cr.NewStaticV4(accessKey, secretKey, ""),
|
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||||
Secure: s.secure,
|
Secure: s.secure,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+260
-122
@@ -41,6 +41,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
@@ -54,9 +55,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/logger/message/log"
|
||||||
"github.com/minio/pkg/logger/message/log"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
xnet "github.com/minio/pkg/net"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/secure-io/sio-go"
|
"github.com/secure-io/sio-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,11 +79,9 @@ const (
|
|||||||
// ----------
|
// ----------
|
||||||
// updates all minio servers and restarts them gracefully.
|
// updates all minio servers and restarts them gracefully.
|
||||||
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ServerUpdate")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -229,9 +228,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
|||||||
// - freeze (freezes all incoming S3 API calls)
|
// - freeze (freezes all incoming S3 API calls)
|
||||||
// - unfreeze (unfreezes previously frozen S3 API calls)
|
// - unfreeze (unfreezes previously frozen S3 API calls)
|
||||||
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "Service")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
action := vars["action"]
|
action := vars["action"]
|
||||||
@@ -255,11 +252,11 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
var objectAPI ObjectLayer
|
var objectAPI ObjectLayer
|
||||||
switch serviceSig {
|
switch serviceSig {
|
||||||
case serviceRestart:
|
case serviceRestart:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction)
|
||||||
case serviceStop:
|
case serviceStop:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction)
|
||||||
case serviceFreeze, serviceUnFreeze:
|
case serviceFreeze, serviceUnFreeze:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceFreezeAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
|
||||||
}
|
}
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
@@ -297,15 +294,12 @@ type ServerProperties struct {
|
|||||||
SQSARN []string `json:"sqsARN"`
|
SQSARN []string `json:"sqsARN"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConnStats holds transferred bytes from/to the server
|
// serverConnStats holds transferred bytes from/to the server
|
||||||
type ServerConnStats struct {
|
type serverConnStats struct {
|
||||||
TotalInputBytes uint64 `json:"transferred"`
|
internodeInputBytes uint64
|
||||||
TotalOutputBytes uint64 `json:"received"`
|
internodeOutputBytes uint64
|
||||||
Throughput uint64 `json:"throughput,omitempty"`
|
s3InputBytes uint64
|
||||||
S3InputBytes uint64 `json:"transferredS3"`
|
s3OutputBytes uint64
|
||||||
S3OutputBytes uint64 `json:"receivedS3"`
|
|
||||||
AdminInputBytes uint64 `json:"transferredAdmin"`
|
|
||||||
AdminOutputBytes uint64 `json:"receivedAdmin"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerHTTPAPIStats holds total number of HTTP operations from/to the server,
|
// ServerHTTPAPIStats holds total number of HTTP operations from/to the server,
|
||||||
@@ -335,11 +329,9 @@ type ServerHTTPStats struct {
|
|||||||
// ----------
|
// ----------
|
||||||
// Get server information
|
// Get server information
|
||||||
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StorageInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.StorageInfoAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.StorageInfoAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -376,11 +368,9 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
// ----------
|
// ----------
|
||||||
// Get realtime server metrics
|
// Get realtime server metrics
|
||||||
func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "Metrics")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -487,11 +477,9 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
// ----------
|
// ----------
|
||||||
// Get server/cluster data usage info
|
// Get server/cluster data usage info
|
||||||
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DataUsageInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DataUsageInfoAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DataUsageInfoAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -572,11 +560,9 @@ type PeerLocks struct {
|
|||||||
|
|
||||||
// ForceUnlockHandler force unlocks requested resource
|
// ForceUnlockHandler force unlocks requested resource
|
||||||
func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ForceUnlock")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ForceUnlockAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ForceUnlockAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -609,11 +595,9 @@ func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
// TopLocksHandler Get list of locks in use
|
// TopLocksHandler Get list of locks in use
|
||||||
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "TopLocks")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.TopLocksAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.TopLocksAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -661,12 +645,10 @@ type StartProfilingResult struct {
|
|||||||
// ----------
|
// ----------
|
||||||
// Enable server profiling
|
// Enable server profiling
|
||||||
func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StartProfiling")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -748,12 +730,10 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R
|
|||||||
// ----------
|
// ----------
|
||||||
// Enable server profiling
|
// Enable server profiling
|
||||||
func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "Profile")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -845,12 +825,10 @@ func (f dummyFileInfo) Sys() interface{} { return f.sys }
|
|||||||
// ----------
|
// ----------
|
||||||
// Download profiling information of all nodes in a zip format - deprecated API
|
// Download profiling information of all nodes in a zip format - deprecated API
|
||||||
func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DownloadProfiling")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ProfilingAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -946,11 +924,9 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
|
|||||||
// sequence. However, if the force-start flag is provided, the server
|
// sequence. However, if the force-start flag is provided, the server
|
||||||
// aborts the running heal sequence and starts a new one.
|
// aborts the running heal sequence and starts a new one.
|
||||||
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "Heal")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1049,7 +1025,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 {
|
if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 {
|
||||||
clientToken := nh.clientToken
|
clientToken := nh.clientToken
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
clientToken = fmt.Sprintf("%s@%d", nh.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
clientToken = fmt.Sprintf("%s:%d", nh.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(madmin.HealStartSuccess{
|
b, err := json.Marshal(madmin.HealStartSuccess{
|
||||||
ClientToken: clientToken,
|
ClientToken: clientToken,
|
||||||
@@ -1132,11 +1108,9 @@ func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HealBackgroundStatus")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1153,13 +1127,118 @@ func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SitePerfHandler - measures network throughput between site replicated setups
|
||||||
|
func (a adminAPIHandlers) SitePerfHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !globalSiteReplicationSys.isEnabled() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsLock := objectAPI.NewNSLock(minioMetaBucket, "site-net-perf")
|
||||||
|
lkctx, err := nsLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(toAPIErrorCode(ctx, err)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer nsLock.Unlock(lkctx)
|
||||||
|
|
||||||
|
durationStr := r.Form.Get(peerRESTDuration)
|
||||||
|
duration, err := time.ParseDuration(durationStr)
|
||||||
|
if err != nil {
|
||||||
|
duration = globalNetPerfMinDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
if duration < globalNetPerfMinDuration {
|
||||||
|
// We need sample size of minimum 10 secs.
|
||||||
|
duration = globalNetPerfMinDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
duration = duration.Round(time.Second)
|
||||||
|
|
||||||
|
results, err := globalSiteReplicationSys.Netperf(ctx, duration)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(toAPIErrorCode(ctx, err)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
if err := enc.Encode(results); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientDevNullExtraTime - return extratime for last devnull
|
||||||
|
// [POST] /minio/admin/v3/speedtest/client/devnull/extratime
|
||||||
|
func (a adminAPIHandlers) ClientDevNullExtraTime(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.BandwidthMonitorAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
if err := enc.Encode(madmin.ClientPerfExtraTime{TimeSpent: atomic.LoadInt64(&globalLastClientPerfExtraTime)}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientDevNull - everything goes to io.Discard
|
||||||
|
// [POST] /minio/admin/v3/speedtest/client/devnull
|
||||||
|
func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
timeStart := time.Now()
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.BandwidthMonitorAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsLock := objectAPI.NewNSLock(minioMetaBucket, "client-perf")
|
||||||
|
lkctx, err := nsLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(toAPIErrorCode(ctx, err)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer nsLock.Unlock(lkctx)
|
||||||
|
timeEnd := time.Now()
|
||||||
|
|
||||||
|
atomic.SwapInt64(&globalLastClientPerfExtraTime, timeEnd.Sub(timeStart).Nanoseconds())
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, madmin.MaxClientPerfTimeout)
|
||||||
|
defer cancel()
|
||||||
|
totalRx := int64(0)
|
||||||
|
connectTime := time.Now()
|
||||||
|
for {
|
||||||
|
n, err := io.CopyN(io.Discard, r.Body, 128*humanize.KiByte)
|
||||||
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
|
// would mean the network is not stable. Logging here will help in debugging network issues.
|
||||||
|
if time.Since(connectTime) < (globalNetPerfMinDuration - time.Second) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalRx += n
|
||||||
|
if err != nil || ctx.Err() != nil || totalRx > 100*humanize.GiByte {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
// NetperfHandler - perform mesh style network throughput test
|
// NetperfHandler - perform mesh style network throughput test
|
||||||
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "NetperfHandler")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1175,6 +1254,7 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(toAPIErrorCode(ctx, err)), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(toAPIErrorCode(ctx, err)), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
defer nsLock.Unlock(lkctx)
|
defer nsLock.Unlock(lkctx)
|
||||||
|
|
||||||
durationStr := r.Form.Get(peerRESTDuration)
|
durationStr := r.Form.Get(peerRESTDuration)
|
||||||
@@ -1197,21 +1277,14 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpeedtestHandler - Deprecated. See ObjectSpeedTestHandler
|
|
||||||
func (a adminAPIHandlers) SpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
a.ObjectSpeedTestHandler(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObjectSpeedTestHandler - reports maximum speed of a cluster by performing PUT and
|
// ObjectSpeedTestHandler - reports maximum speed of a cluster by performing PUT and
|
||||||
// GET operations on the server, supports auto tuning by default by automatically
|
// GET operations on the server, supports auto tuning by default by automatically
|
||||||
// increasing concurrency and stopping when we have reached the limits on the
|
// increasing concurrency and stopping when we have reached the limits on the
|
||||||
// system.
|
// system.
|
||||||
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ObjectSpeedTestHandler")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1368,20 +1441,11 @@ func validateObjPerfOptions(ctx context.Context, storageInfo madmin.StorageInfo,
|
|||||||
return true, autotune, ""
|
return true, autotune, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetSpeedtestHandler - reports maximum network throughput
|
|
||||||
func (a adminAPIHandlers) NetSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "NetSpeedtestHandler")
|
|
||||||
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DriveSpeedtestHandler - reports throughput of drives available in the cluster
|
// DriveSpeedtestHandler - reports throughput of drives available in the cluster
|
||||||
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DriveSpeedtestHandler")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1499,10 +1563,10 @@ func extractTraceOptions(r *http.Request) (opts madmin.ServiceTraceOpts, err err
|
|||||||
// ----------
|
// ----------
|
||||||
// The handler sends http trace to the connected HTTP client.
|
// The handler sends http trace to the connected HTTP client.
|
||||||
func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HTTPTrace")
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.TraceAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.TraceAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1517,7 +1581,9 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
|
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
|
||||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||||
traceCh := make(chan madmin.TraceInfo, 4000)
|
|
||||||
|
// Keep 100k buffered channel, should be sufficient to ensure we do not lose any events.
|
||||||
|
traceCh := make(chan madmin.TraceInfo, 100000)
|
||||||
|
|
||||||
peers, _ := newPeerRestClients(globalEndpoints)
|
peers, _ := newPeerRestClients(globalEndpoints)
|
||||||
|
|
||||||
@@ -1541,7 +1607,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
peer.Trace(traceCh, ctx.Done(), traceOpts)
|
peer.Trace(traceCh, ctx.Done(), traceOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
keepAliveTicker := time.NewTicker(time.Second)
|
||||||
defer keepAliveTicker.Stop()
|
defer keepAliveTicker.Stop()
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
@@ -1571,11 +1637,9 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// The ConsoleLogHandler handler sends console logs to the connected HTTP client.
|
// The ConsoleLogHandler handler sends console logs to the connected HTTP client.
|
||||||
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ConsoleLog")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConsoleLogAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConsoleLogAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1654,10 +1718,9 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
// KMSCreateKeyHandler - POST /minio/admin/v3/kms/key/create?key-id=<master-key-id>
|
// KMSCreateKeyHandler - POST /minio/admin/v3/kms/key/create?key-id=<master-key-id>
|
||||||
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSCreateKey")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSCreateKeyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1676,10 +1739,9 @@ func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
// KMSKeyStatusHandler - GET /minio/admin/v3/kms/status
|
// KMSKeyStatusHandler - GET /minio/admin/v3/kms/status
|
||||||
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSStatus")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1714,11 +1776,9 @@ func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
// KMSKeyStatusHandler - GET /minio/admin/v3/kms/key/status?key-id=<master-key-id>
|
// KMSKeyStatusHandler - GET /minio/admin/v3/kms/key/status?key-id=<master-key-id>
|
||||||
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSKeyStatus")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSKeyStatusAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1811,6 +1871,7 @@ func getPoolsInfo(ctx context.Context, allDisks []madmin.Disk) (map[int]map[int]
|
|||||||
dataUsageInfo := cache.dui(dataUsageRoot, nil)
|
dataUsageInfo := cache.dui(dataUsageRoot, nil)
|
||||||
erasureSet.ObjectsCount = dataUsageInfo.ObjectsTotalCount
|
erasureSet.ObjectsCount = dataUsageInfo.ObjectsTotalCount
|
||||||
erasureSet.VersionsCount = dataUsageInfo.VersionsTotalCount
|
erasureSet.VersionsCount = dataUsageInfo.VersionsTotalCount
|
||||||
|
erasureSet.DeleteMarkersCount = dataUsageInfo.DeleteMarkersTotalCount
|
||||||
erasureSet.Usage = dataUsageInfo.ObjectsTotalSize
|
erasureSet.Usage = dataUsageInfo.ObjectsTotalSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1826,8 +1887,6 @@ func getPoolsInfo(ctx context.Context, allDisks []madmin.Disk) (map[int]map[int]
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request) madmin.InfoMessage {
|
func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request) madmin.InfoMessage {
|
||||||
kmsStat := fetchKMSStatus()
|
|
||||||
|
|
||||||
ldap := madmin.LDAP{}
|
ldap := madmin.LDAP{}
|
||||||
if globalIAMSys.LDAPConfig.Enabled() {
|
if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
ldapConn, err := globalIAMSys.LDAPConfig.LDAP.Connect()
|
ldapConn, err := globalIAMSys.LDAPConfig.LDAP.Connect()
|
||||||
@@ -1862,6 +1921,7 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
|||||||
buckets := madmin.Buckets{}
|
buckets := madmin.Buckets{}
|
||||||
objects := madmin.Objects{}
|
objects := madmin.Objects{}
|
||||||
versions := madmin.Versions{}
|
versions := madmin.Versions{}
|
||||||
|
deleteMarkers := madmin.DeleteMarkers{}
|
||||||
usage := madmin.Usage{}
|
usage := madmin.Usage{}
|
||||||
|
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -1874,10 +1934,12 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
|||||||
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
||||||
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
||||||
versions = madmin.Versions{Count: dataUsageInfo.VersionsTotalCount}
|
versions = madmin.Versions{Count: dataUsageInfo.VersionsTotalCount}
|
||||||
|
deleteMarkers = madmin.DeleteMarkers{Count: dataUsageInfo.DeleteMarkersTotalCount}
|
||||||
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
||||||
} else {
|
} else {
|
||||||
buckets = madmin.Buckets{Error: err.Error()}
|
buckets = madmin.Buckets{Error: err.Error()}
|
||||||
objects = madmin.Objects{Error: err.Error()}
|
objects = madmin.Objects{Error: err.Error()}
|
||||||
|
deleteMarkers = madmin.DeleteMarkers{Error: err.Error()}
|
||||||
usage = madmin.Usage{Error: err.Error()}
|
usage = madmin.Usage{Error: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1907,7 +1969,8 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
|||||||
|
|
||||||
domain := globalDomainNames
|
domain := globalDomainNames
|
||||||
services := madmin.Services{
|
services := madmin.Services{
|
||||||
KMS: kmsStat,
|
KMS: fetchKMSStatus(),
|
||||||
|
KMSStatus: fetchKMSStatusV2(ctx),
|
||||||
LDAP: ldap,
|
LDAP: ldap,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
Audit: audit,
|
Audit: audit,
|
||||||
@@ -1923,6 +1986,7 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
|||||||
Buckets: buckets,
|
Buckets: buckets,
|
||||||
Objects: objects,
|
Objects: objects,
|
||||||
Versions: versions,
|
Versions: versions,
|
||||||
|
DeleteMarkers: deleteMarkers,
|
||||||
Usage: usage,
|
Usage: usage,
|
||||||
Services: services,
|
Services: services,
|
||||||
Backend: backend,
|
Backend: backend,
|
||||||
@@ -2103,6 +2167,27 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collect all realtime metrics except disk
|
||||||
|
// disk metrics are already included under drive info of each server
|
||||||
|
getRealtimeMetrics := func() *madmin.RealtimeMetrics {
|
||||||
|
var m madmin.RealtimeMetrics
|
||||||
|
var types madmin.MetricType = madmin.MetricsAll &^ madmin.MetricsDisk
|
||||||
|
mLocal := collectLocalMetrics(types, collectMetricsOpts{})
|
||||||
|
m.Merge(&mLocal)
|
||||||
|
cctx, cancel := context.WithTimeout(healthCtx, time.Second/2)
|
||||||
|
mRemote := collectRemoteMetrics(cctx, types, collectMetricsOpts{})
|
||||||
|
cancel()
|
||||||
|
m.Merge(&mRemote)
|
||||||
|
for idx, host := range m.Hosts {
|
||||||
|
m.Hosts[idx] = anonAddr(host)
|
||||||
|
}
|
||||||
|
for host, metrics := range m.ByHost {
|
||||||
|
m.ByHost[anonAddr(host)] = metrics
|
||||||
|
delete(m.ByHost, host)
|
||||||
|
}
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
anonymizeCmdLine := func(cmdLine string) string {
|
anonymizeCmdLine := func(cmdLine string) string {
|
||||||
if !globalIsDistErasure {
|
if !globalIsDistErasure {
|
||||||
// FS mode - single server - hard code to `server1`
|
// FS mode - single server - hard code to `server1`
|
||||||
@@ -2280,6 +2365,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
|||||||
TLS: &tls,
|
TLS: &tls,
|
||||||
IsKubernetes: &isK8s,
|
IsKubernetes: &isK8s,
|
||||||
IsDocker: &isDocker,
|
IsDocker: &isDocker,
|
||||||
|
Metrics: getRealtimeMetrics(),
|
||||||
}
|
}
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
@@ -2290,10 +2376,9 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
|||||||
// ----------
|
// ----------
|
||||||
// Get server health info
|
// Get server health info
|
||||||
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HealthInfo")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2397,12 +2482,10 @@ func getTLSInfo() madmin.TLSInfo {
|
|||||||
// ----------
|
// ----------
|
||||||
// Get server information
|
// Get server information
|
||||||
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ServerInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.ServerInfoAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2505,6 +2588,28 @@ func fetchKMSStatus() madmin.KMS {
|
|||||||
return kmsStat
|
return kmsStat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchKMSStatusV2 fetches KMS-related status information for all instances
|
||||||
|
func fetchKMSStatusV2(ctx context.Context) []madmin.KMS {
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
return []madmin.KMS{}
|
||||||
|
}
|
||||||
|
|
||||||
|
results := GlobalKMS.Verify(ctx)
|
||||||
|
|
||||||
|
stats := []madmin.KMS{}
|
||||||
|
for _, result := range results {
|
||||||
|
stats = append(stats, madmin.KMS{
|
||||||
|
Status: result.Status,
|
||||||
|
Endpoint: result.Endpoint,
|
||||||
|
Encrypt: result.Encrypt,
|
||||||
|
Decrypt: result.Decrypt,
|
||||||
|
Version: result.Version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
// fetchLoggerDetails return log info
|
// fetchLoggerDetails return log info
|
||||||
func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
||||||
var loggerInfo []madmin.Logger
|
var loggerInfo []madmin.Logger
|
||||||
@@ -2524,12 +2629,12 @@ func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
|||||||
return loggerInfo, auditloggerInfo
|
return loggerInfo, auditloggerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func embedFileInZip(zipWriter *zip.Writer, name string, data []byte) error {
|
func embedFileInZip(zipWriter *zip.Writer, name string, data []byte, fileMode os.FileMode) error {
|
||||||
// Send profiling data to zip as file
|
// Send profiling data to zip as file
|
||||||
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
||||||
name: name,
|
name: name,
|
||||||
size: int64(len(data)),
|
size: int64(len(data)),
|
||||||
mode: 0o600,
|
mode: fileMode,
|
||||||
modTime: UTCNow(),
|
modTime: UTCNow(),
|
||||||
isDir: false,
|
isDir: false,
|
||||||
sys: nil,
|
sys: nil,
|
||||||
@@ -2629,15 +2734,14 @@ type getRawDataer interface {
|
|||||||
// ----------
|
// ----------
|
||||||
// Download file from all nodes in a zip format
|
// Download file from all nodes in a zip format
|
||||||
func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "InspectData")
|
ctx := r.Context()
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.InspectDataAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, policy.InspectDataAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objLayer := newObjectLayerFn()
|
objLayer := newObjectLayerFn()
|
||||||
o, ok := objLayer.(getRawDataer)
|
o, ok := objLayer.(getRawDataer)
|
||||||
@@ -2761,7 +2865,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
defer inspectZipW.Close()
|
defer inspectZipW.Close()
|
||||||
|
|
||||||
if b := getClusterMetaInfo(ctx); len(b) > 0 {
|
if b := getClusterMetaInfo(ctx); len(b) > 0 {
|
||||||
logger.LogIf(ctx, embedFileInZip(inspectZipW, "cluster.info", b))
|
logger.LogIf(ctx, embedFileInZip(inspectZipW, "cluster.info", b, 0o600))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2825,7 +2929,41 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
sb.WriteString(pool.CmdLine)
|
sb.WriteString(pool.CmdLine)
|
||||||
}
|
}
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
logger.LogIf(ctx, embedFileInZip(inspectZipW, "inspect-input.txt", sb.Bytes()))
|
logger.LogIf(ctx, embedFileInZip(inspectZipW, "inspect-input.txt", sb.Bytes(), 0o600))
|
||||||
|
|
||||||
|
scheme := "https"
|
||||||
|
if !globalIsTLS {
|
||||||
|
scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
// save MinIO start script to inspect command
|
||||||
|
var scrb bytes.Buffer
|
||||||
|
fmt.Fprintf(&scrb, `#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
for file in $(ls -1); do
|
||||||
|
dest_file=$(echo "$file" | cut -d ":" -f1)
|
||||||
|
mv "$file" "$dest_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Read content of inspect-input.txt
|
||||||
|
MINIO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#%s:\/\/#\.\/#g")
|
||||||
|
|
||||||
|
# Start MinIO instance using the options
|
||||||
|
START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off minio server ${MINIO_OPTS} &"
|
||||||
|
echo
|
||||||
|
echo "Starting MinIO instance: ${START_CMD}"
|
||||||
|
echo
|
||||||
|
eval "$START_CMD"
|
||||||
|
MINIO_SRVR_PID="$!"
|
||||||
|
echo "MinIO Server PID: ${MINIO_SRVR_PID}"
|
||||||
|
echo
|
||||||
|
echo "Waiting for MinIO instance to get ready!"
|
||||||
|
sleep 10
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"`, scheme)
|
||||||
|
logger.LogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes(), 0o755))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSubnetAdminPublicKey() []byte {
|
func getSubnetAdminPublicKey() []byte {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
|
|||||||
// Initialize boot time
|
// Initialize boot time
|
||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
|
|
||||||
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
globalEndpoints = mustGetPoolEndpoints(0, erasureDirs...)
|
||||||
|
|
||||||
initAllSubsystems(ctx)
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ func initTestErasureObjLayer(ctx context.Context) (ObjectLayer, []string, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
endpoints := mustGetPoolEndpoints(erasureDirs...)
|
endpoints := mustGetPoolEndpoints(0, erasureDirs...)
|
||||||
globalPolicySys = NewPolicySys()
|
globalPolicySys = NewPolicySys()
|
||||||
objLayer, err := newErasureServerPools(ctx, endpoints)
|
objLayer, err := newErasureServerPools(ctx, endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
|||||||
} else {
|
} else {
|
||||||
clientToken := he.clientToken
|
clientToken := he.clientToken
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
clientToken = fmt.Sprintf("%s@%d", he.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
clientToken = fmt.Sprintf("%s:%d", he.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
hsp = madmin.HealStopSuccess{
|
hsp = madmin.HealStopSuccess{
|
||||||
@@ -327,7 +327,7 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence, objAPI ObjectLay
|
|||||||
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(madmin.HealStartSuccess{
|
b, err := json.Marshal(madmin.HealStartSuccess{
|
||||||
@@ -683,13 +683,6 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) logHeal(healType madmin.HealItemType) {
|
|
||||||
h.mutex.Lock()
|
|
||||||
h.scannedItemsMap[healType]++
|
|
||||||
h.lastHealActivity = UTCNow()
|
|
||||||
h.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||||
// Send heal request
|
// Send heal request
|
||||||
task := healTask{
|
task := healTask{
|
||||||
|
|||||||
+224
-115
@@ -19,6 +19,9 @@ 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"
|
||||||
@@ -31,8 +34,112 @@ const (
|
|||||||
adminPathPrefix = minioReservedBucketPath + "/admin"
|
adminPathPrefix = minioReservedBucketPath + "/admin"
|
||||||
adminAPIVersion = madmin.AdminAPIVersion
|
adminAPIVersion = madmin.AdminAPIVersion
|
||||||
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
||||||
|
adminAPISiteReplicationDevNull = "/site-replication/devnull"
|
||||||
|
adminAPISiteReplicationNetPerf = "/site-replication/netperf"
|
||||||
|
adminAPIClientDevNull = "/speedtest/client/devnull"
|
||||||
|
adminAPIClientDevExtraTime = "/speedtest/client/devnull/extratime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var gzipHandler = func() func(http.Handler) http.HandlerFunc {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
return gz
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set of handler options as bit flags
|
||||||
|
type hFlag uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// this flag disables gzip compression of responses
|
||||||
|
noGZFlag = 1 << iota
|
||||||
|
|
||||||
|
// this flag enables tracing body and headers instead of just headers
|
||||||
|
traceAllFlag
|
||||||
|
|
||||||
|
// pass this flag to skip checking if object layer is available
|
||||||
|
noObjLayerFlag
|
||||||
|
)
|
||||||
|
|
||||||
|
// Has checks if the the given flag is enabled in `h`.
|
||||||
|
func (h hFlag) Has(flag hFlag) bool {
|
||||||
|
// Use bitwise-AND and check if the result is non-zero.
|
||||||
|
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
|
||||||
|
// handlers:
|
||||||
|
//
|
||||||
|
// - updates request context with `logger.ReqInfo` and api name based on the
|
||||||
|
// name of the function handler passed (this handler must be a method of
|
||||||
|
// `adminAPIHandlers`).
|
||||||
|
//
|
||||||
|
// - sets up call to send AuditLog
|
||||||
|
//
|
||||||
|
// Note that, while this is a middleware function (i.e. it takes a handler
|
||||||
|
// function and returns one), due to flags being passed based on required
|
||||||
|
// conditions, it is done per-"handler function registration" in the router.
|
||||||
|
//
|
||||||
|
// When no flags are passed, gzip compression, http tracing of headers and
|
||||||
|
// checking of object layer availability are all enabled. Use flags to modify
|
||||||
|
// this behavior.
|
||||||
|
func adminMiddleware(f http.HandlerFunc, flags ...hFlag) http.HandlerFunc {
|
||||||
|
// Collect all flags with bitwise-OR and assign operator
|
||||||
|
var handlerFlags hFlag
|
||||||
|
for _, flag := range flags {
|
||||||
|
handlerFlags |= flag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get name of the handler using reflection. NOTE: The passed in handler
|
||||||
|
// function must be a method of `adminAPIHandlers` for this extraction to
|
||||||
|
// work as expected.
|
||||||
|
handlerName := getHandlerName(f)
|
||||||
|
|
||||||
|
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Update request context with `logger.ReqInfo`.
|
||||||
|
r = r.WithContext(newContext(r, w, handlerName))
|
||||||
|
|
||||||
|
defer logger.AuditLog(r.Context(), w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Check if object layer is available, if not return error early.
|
||||||
|
if !handlerFlags.Has(noObjLayerFlag) {
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(r.Context(), w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply http tracing "middleware" based on presence of flag.
|
||||||
|
var f2 http.HandlerFunc
|
||||||
|
if handlerFlags.Has(traceAllFlag) {
|
||||||
|
f2 = httpTraceAll(f)
|
||||||
|
} else {
|
||||||
|
f2 = httpTraceHdrs(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the final handler
|
||||||
|
f2(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable compression of responses based on presence of flag.
|
||||||
|
if !handlerFlags.Has(noGZFlag) {
|
||||||
|
handler = gzipHandler(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
// adminAPIHandlers provides HTTP handlers for MinIO admin API.
|
// adminAPIHandlers provides HTTP handlers for MinIO admin API.
|
||||||
type adminAPIHandlers struct{}
|
type adminAPIHandlers struct{}
|
||||||
|
|
||||||
@@ -46,262 +153,264 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminAPIVersionPrefix,
|
adminAPIVersionPrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 _, adminVersion := range adminVersions {
|
for _, adminVersion := range adminVersions {
|
||||||
// Restart and stop MinIO service.
|
// Restart and stop MinIO service.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(gz(httpTraceAll(adminAPI.ServiceHandler))).Queries("action", "{action:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(adminMiddleware(adminAPI.ServiceHandler, traceAllFlag)).Queries("action", "{action:.*}")
|
||||||
// Update MinIO servers.
|
// Update MinIO servers.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(gz(httpTraceAll(adminAPI.ServerUpdateHandler))).Queries("updateURL", "{updateURL:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(adminMiddleware(adminAPI.ServerUpdateHandler, traceAllFlag)).Queries("updateURL", "{updateURL:.*}")
|
||||||
|
|
||||||
// Info operations
|
// Info operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(adminMiddleware(adminAPI.ServerInfoHandler, traceAllFlag, noObjLayerFlag))
|
||||||
adminRouter.Methods(http.MethodGet, http.MethodPost).Path(adminVersion + "/inspect-data").HandlerFunc(httpTraceAll(adminAPI.InspectDataHandler))
|
adminRouter.Methods(http.MethodGet, http.MethodPost).Path(adminVersion + "/inspect-data").HandlerFunc(adminMiddleware(adminAPI.InspectDataHandler, noGZFlag, traceAllFlag))
|
||||||
|
|
||||||
// StorageInfo operations
|
// StorageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(adminMiddleware(adminAPI.StorageInfoHandler, traceAllFlag))
|
||||||
// DataUsageInfo operations
|
// DataUsageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.DataUsageInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(adminMiddleware(adminAPI.DataUsageInfoHandler, traceAllFlag))
|
||||||
// Metrics operation
|
// Metrics operation
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/metrics").HandlerFunc(gz(httpTraceAll(adminAPI.MetricsHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/metrics").HandlerFunc(adminMiddleware(adminAPI.MetricsHandler, traceAllFlag))
|
||||||
|
|
||||||
if globalIsDistErasure || globalIsErasure {
|
if globalIsDistErasure || globalIsErasure {
|
||||||
// Heal operations
|
// Heal operations
|
||||||
|
|
||||||
// Heal processing endpoint.
|
// Heal processing endpoint.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(gz(httpTraceAll(adminAPI.BackgroundHealStatusHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(adminMiddleware(adminAPI.BackgroundHealStatusHandler, traceAllFlag))
|
||||||
|
|
||||||
// Pool operations
|
// Pool operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/pools/list").HandlerFunc(gz(httpTraceAll(adminAPI.ListPools)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/pools/list").HandlerFunc(adminMiddleware(adminAPI.ListPools, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/pools/status").HandlerFunc(gz(httpTraceAll(adminAPI.StatusPool))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/pools/status").HandlerFunc(adminMiddleware(adminAPI.StatusPool, traceAllFlag)).Queries("pool", "{pool:.*}")
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(gz(httpTraceAll(adminAPI.StartDecommission))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(adminMiddleware(adminAPI.StartDecommission, traceAllFlag)).Queries("pool", "{pool:.*}")
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(adminMiddleware(adminAPI.CancelDecommission, traceAllFlag)).Queries("pool", "{pool:.*}")
|
||||||
|
|
||||||
// Rebalance operations
|
// Rebalance operations
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/start").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStart)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/start").HandlerFunc(adminMiddleware(adminAPI.RebalanceStart, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/rebalance/status").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStatus)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/rebalance/status").HandlerFunc(adminMiddleware(adminAPI.RebalanceStatus, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/stop").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStop)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/stop").HandlerFunc(adminMiddleware(adminAPI.RebalanceStop, traceAllFlag))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profiling operations - deprecated API
|
// Profiling operations - deprecated API
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(gz(httpTraceAll(adminAPI.StartProfilingHandler))).
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(adminMiddleware(adminAPI.StartProfilingHandler, traceAllFlag, noObjLayerFlag)).
|
||||||
Queries("profilerType", "{profilerType:.*}")
|
Queries("profilerType", "{profilerType:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(gz(httpTraceAll(adminAPI.DownloadProfilingHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(adminMiddleware(adminAPI.DownloadProfilingHandler, traceAllFlag, noObjLayerFlag))
|
||||||
// Profiling operations
|
// Profiling operations
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/profile").HandlerFunc(gz(httpTraceAll(adminAPI.ProfileHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/profile").HandlerFunc(adminMiddleware(adminAPI.ProfileHandler, traceAllFlag, noObjLayerFlag))
|
||||||
|
|
||||||
// Config KV operations.
|
// Config KV operations.
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetConfigKVHandler))).Queries("key", "{key:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(adminMiddleware(adminAPI.GetConfigKVHandler)).Queries("key", "{key:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetConfigKVHandler)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(adminMiddleware(adminAPI.SetConfigKVHandler))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.DelConfigKVHandler)))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(adminMiddleware(adminAPI.DelConfigKVHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable config help in all modes.
|
// Enable config help in all modes.
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/help-config-kv").HandlerFunc(gz(httpTraceAll(adminAPI.HelpConfigKVHandler))).Queries("subSys", "{subSys:.*}", "key", "{key:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/help-config-kv").HandlerFunc(adminMiddleware(adminAPI.HelpConfigKVHandler, traceAllFlag)).Queries("subSys", "{subSys:.*}", "key", "{key:.*}")
|
||||||
|
|
||||||
// Config KV history operations.
|
// Config KV history operations.
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-config-history-kv").HandlerFunc(gz(httpTraceAll(adminAPI.ListConfigHistoryKVHandler))).Queries("count", "{count:[0-9]+}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-config-history-kv").HandlerFunc(adminMiddleware(adminAPI.ListConfigHistoryKVHandler, traceAllFlag)).Queries("count", "{count:[0-9]+}")
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.ClearConfigHistoryKVHandler))).Queries("restoreId", "{restoreId:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(adminMiddleware(adminAPI.ClearConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/restore-config-history-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.RestoreConfigHistoryKVHandler))).Queries("restoreId", "{restoreId:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/restore-config-history-kv").HandlerFunc(adminMiddleware(adminAPI.RestoreConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config import/export bulk operations
|
// Config import/export bulk operations
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
// Get config
|
// Get config
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/config").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetConfigHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/config").HandlerFunc(adminMiddleware(adminAPI.GetConfigHandler))
|
||||||
// Set config
|
// Set config
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/config").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetConfigHandler)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/config").HandlerFunc(adminMiddleware(adminAPI.SetConfigHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- IAM APIs --
|
// -- IAM APIs --
|
||||||
|
|
||||||
// Add policy IAM
|
// Add policy IAM
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(gz(httpTraceAll(adminAPI.AddCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(adminMiddleware(adminAPI.AddCannedPolicy, traceAllFlag)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
// Add user IAM
|
// Add user IAM
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(gz(httpTraceAll(adminAPI.AccountInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(adminMiddleware(adminAPI.AccountInfoHandler, traceAllFlag))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddUser))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(adminMiddleware(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetUserStatus))).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(adminMiddleware(adminAPI.SetUserStatus)).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
// Service accounts ops
|
// Service accounts ops
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddServiceAccount)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccount))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update-service-account").HandlerFunc(adminMiddleware(adminAPI.UpdateServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-service-account").HandlerFunc(adminMiddleware(adminAPI.InfoServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(adminMiddleware(adminAPI.ListServiceAccounts))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(adminMiddleware(adminAPI.DeleteServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// STS accounts ops
|
// STS accounts ops
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.TemporaryAccountInfo))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(adminMiddleware(adminAPI.TemporaryAccountInfo)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// Info policy IAM latest
|
// Info policy IAM latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(adminMiddleware(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
// List policies latest
|
// List policies latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(adminMiddleware(adminAPI.ListBucketPolicies)).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(adminMiddleware(adminAPI.ListCannedPolicies))
|
||||||
|
|
||||||
// Builtin IAM policy associations
|
// Builtin IAM policy associations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/builtin/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListPolicyMappingEntities)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/builtin/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListPolicyMappingEntities))
|
||||||
|
|
||||||
// Remove policy IAM
|
// Remove policy IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(adminMiddleware(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
// Set user or group policy
|
// Set user or group policy
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-or-group-policy").
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-or-group-policy").
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
|
HandlerFunc(adminMiddleware(adminAPI.SetPolicyForUserOrGroup)).
|
||||||
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
||||||
|
|
||||||
// Attach/Detach policies to/from user or group
|
// Attach/Detach policies to/from user or group
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/{operation}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachDetachPolicyBuiltin)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyBuiltin))
|
||||||
|
|
||||||
// Remove user IAM
|
// Remove user IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(adminMiddleware(adminAPI.RemoveUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// List users
|
// List users
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketUsers))).Queries("bucket", "{bucket:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(adminMiddleware(adminAPI.ListBucketUsers)).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListUsers)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(adminMiddleware(adminAPI.ListUsers))
|
||||||
|
|
||||||
// User info
|
// User info
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetUserInfo))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(adminMiddleware(adminAPI.GetUserInfo)).Queries("accessKey", "{accessKey:.*}")
|
||||||
// Add/Remove members from group
|
// Add/Remove members from group
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateGroupMembers)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(adminMiddleware(adminAPI.UpdateGroupMembers))
|
||||||
|
|
||||||
// Get Group
|
// Get Group
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/group").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetGroup))).Queries("group", "{group:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/group").HandlerFunc(adminMiddleware(adminAPI.GetGroup)).Queries("group", "{group:.*}")
|
||||||
|
|
||||||
// List Groups
|
// List Groups
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/groups").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListGroups)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/groups").HandlerFunc(adminMiddleware(adminAPI.ListGroups))
|
||||||
|
|
||||||
// Set Group Status
|
// Set Group Status
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetGroupStatus))).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(adminMiddleware(adminAPI.SetGroupStatus)).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
// Export IAM info to zipped file
|
// Export IAM info to zipped file
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-iam").HandlerFunc(httpTraceHdrs(adminAPI.ExportIAM))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-iam").HandlerFunc(adminMiddleware(adminAPI.ExportIAM, noGZFlag))
|
||||||
|
|
||||||
// Import IAM info
|
// Import IAM info
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(adminMiddleware(adminAPI.ImportIAM, noGZFlag))
|
||||||
|
|
||||||
// IDentity Provider configuration APIs
|
// IDentity Provider configuration APIs
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddIdentityProviderCfg)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.AddIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateIdentityProviderCfg)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.UpdateIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListIdentityProviderCfg)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}").HandlerFunc(adminMiddleware(adminAPI.ListIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.GetIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg)))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
||||||
|
|
||||||
// LDAP IAM operations
|
// LDAP IAM operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListLDAPPolicyMappingEntities)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachDetachPolicyLDAP)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
||||||
// -- END IAM APIs --
|
// -- END IAM APIs --
|
||||||
|
|
||||||
// GetBucketQuotaConfig
|
// GetBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.GetBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
// PutBucketQuotaConfig
|
// PutBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
|
||||||
// Bucket replication operations
|
// Bucket replication operations
|
||||||
// GetBucketTargetHandler
|
// GetBucketTargetHandler
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ListRemoteTargetsHandler))).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
adminMiddleware(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
||||||
// SetRemoteTargetHandler
|
// SetRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.SetRemoteTargetHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
// RemoveRemoteTargetHandler
|
// RemoveRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler))).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
adminMiddleware(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
// ReplicationDiff - MinIO extension API
|
// ReplicationDiff - MinIO extension API
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ReplicationDiffHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.ReplicationDiffHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
// ReplicationMRFHandler - MinIO extension API
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/replication/mrf").HandlerFunc(
|
||||||
|
adminMiddleware(adminAPI.ReplicationMRFHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
|
||||||
// Batch job operations
|
// Batch job operations
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/start-job").HandlerFunc(
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/start-job").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.StartBatchJob)))
|
adminMiddleware(adminAPI.StartBatchJob))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ListBatchJobs)))
|
adminMiddleware(adminAPI.ListBatchJobs))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.DescribeBatchJob)))
|
adminMiddleware(adminAPI.DescribeBatchJob))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/cancel-job").HandlerFunc(
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/cancel-job").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.CancelBatchJob)))
|
adminMiddleware(adminAPI.CancelBatchJob))
|
||||||
|
|
||||||
// Bucket migration operations
|
// Bucket migration operations
|
||||||
// ExportBucketMetaHandler
|
// ExportBucketMetaHandler
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ExportBucketMetadataHandler)))
|
adminMiddleware(adminAPI.ExportBucketMetadataHandler))
|
||||||
// ImportBucketMetaHandler
|
// ImportBucketMetaHandler
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-bucket-metadata").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-bucket-metadata").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ImportBucketMetadataHandler)))
|
adminMiddleware(adminAPI.ImportBucketMetadataHandler))
|
||||||
|
|
||||||
// Remote Tier management operations
|
// Remote Tier management operations
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddTierHandler)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(adminMiddleware(adminAPI.AddTierHandler))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.EditTierHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.EditTierHandler))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListTierHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(adminMiddleware(adminAPI.ListTierHandler))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveTierHandler)))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.RemoveTierHandler))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.VerifyTierHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.VerifyTierHandler))
|
||||||
// Tier stats
|
// Tier stats
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier-stats").HandlerFunc(gz(httpTraceHdrs(adminAPI.TierStatsHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier-stats").HandlerFunc(adminMiddleware(adminAPI.TierStatsHandler))
|
||||||
|
|
||||||
// Cluster Replication APIs
|
// Cluster Replication APIs
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationAdd)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationAdd))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationRemove)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationRemove))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationInfo)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationInfo))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationMetaInfo)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationMetaInfo))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationStatus)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/status").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationStatus))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPISiteReplicationDevNull).HandlerFunc(adminMiddleware(adminAPI.SiteReplicationDevNull, noObjLayerFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPISiteReplicationNetPerf).HandlerFunc(adminMiddleware(adminAPI.SiteReplicationNetPerf, noObjLayerFlag))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/join").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerJoin)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/join").HandlerFunc(adminMiddleware(adminAPI.SRPeerJoin))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/peer/bucket-ops").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerBucketOps))).Queries("bucket", "{bucket:.*}").Queries("operation", "{operation:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/peer/bucket-ops").HandlerFunc(adminMiddleware(adminAPI.SRPeerBucketOps)).Queries("bucket", "{bucket:.*}").Queries("operation", "{operation:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/iam-item").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateIAMItem)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/iam-item").HandlerFunc(adminMiddleware(adminAPI.SRPeerReplicateIAMItem))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/bucket-meta").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateBucketItem)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/bucket-meta").HandlerFunc(adminMiddleware(adminAPI.SRPeerReplicateBucketItem))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/peer/idp-settings").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerGetIDPSettings)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/peer/idp-settings").HandlerFunc(adminMiddleware(adminAPI.SRPeerGetIDPSettings))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationEdit))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(adminMiddleware(adminAPI.SRPeerEdit))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(adminMiddleware(adminAPI.SRPeerRemove))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationResyncOp))).Queries("operation", "{operation:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationResyncOp)).Queries("operation", "{operation:.*}")
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Top locks
|
// Top locks
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(gz(httpTraceHdrs(adminAPI.TopLocksHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(adminMiddleware(adminAPI.TopLocksHandler))
|
||||||
// Force unlocks paths
|
// Force unlocks paths
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/force-unlock").
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/force-unlock").
|
||||||
Queries("paths", "{paths:.*}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ForceUnlockHandler)))
|
Queries("paths", "{paths:.*}").HandlerFunc(adminMiddleware(adminAPI.ForceUnlockHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(httpTraceHdrs(adminAPI.SpeedTestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(adminMiddleware(adminAPI.ObjectSpeedTestHandler, noGZFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/object").HandlerFunc(httpTraceHdrs(adminAPI.ObjectSpeedTestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/object").HandlerFunc(adminMiddleware(adminAPI.ObjectSpeedTestHandler, noGZFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(httpTraceHdrs(adminAPI.DriveSpeedtestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(adminMiddleware(adminAPI.DriveSpeedtestHandler, noGZFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/net").HandlerFunc(httpTraceHdrs(adminAPI.NetperfHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/net").HandlerFunc(adminMiddleware(adminAPI.NetperfHandler, noGZFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/site").HandlerFunc(adminMiddleware(adminAPI.SitePerfHandler, noGZFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPIClientDevNull).HandlerFunc(adminMiddleware(adminAPI.ClientDevNull, noGZFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPIClientDevExtraTime).HandlerFunc(adminMiddleware(adminAPI.ClientDevNullExtraTime, noGZFlag))
|
||||||
|
|
||||||
// HTTP Trace
|
// HTTP Trace
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/trace").HandlerFunc(gz(http.HandlerFunc(adminAPI.TraceHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/trace").HandlerFunc(adminMiddleware(adminAPI.TraceHandler, noObjLayerFlag))
|
||||||
|
|
||||||
// Console Logs
|
// Console Logs
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/log").HandlerFunc(gz(httpTraceAll(adminAPI.ConsoleLogHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/log").HandlerFunc(adminMiddleware(adminAPI.ConsoleLogHandler, traceAllFlag))
|
||||||
|
|
||||||
// -- KMS APIs --
|
// -- KMS APIs --
|
||||||
//
|
//
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSStatusHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(adminMiddleware(adminAPI.KMSStatusHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(adminMiddleware(adminAPI.KMSCreateKeyHandler, traceAllFlag)).Queries("key-id", "{key-id:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(adminMiddleware(adminAPI.KMSKeyStatusHandler, traceAllFlag))
|
||||||
|
|
||||||
// Keep obdinfo for backward compatibility with mc
|
// Keep obdinfo for backward compatibility with mc
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
HandlerFunc(adminMiddleware(adminAPI.HealthInfoHandler))
|
||||||
// -- Health API --
|
// -- Health API --
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
HandlerFunc(adminMiddleware(adminAPI.HealthInfoHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If none of the routes match add default error handler routes
|
// If none of the routes match add default error handler routes
|
||||||
|
|||||||
+22
-4
@@ -47,7 +47,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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError structure
|
// APIError structure
|
||||||
@@ -138,6 +138,8 @@ const (
|
|||||||
ErrReplicationDenyEditError
|
ErrReplicationDenyEditError
|
||||||
ErrRemoteTargetDenyAddError
|
ErrRemoteTargetDenyAddError
|
||||||
ErrReplicationNoExistingObjects
|
ErrReplicationNoExistingObjects
|
||||||
|
ErrReplicationValidationError
|
||||||
|
ErrReplicationPermissionCheckError
|
||||||
ErrObjectRestoreAlreadyInProgress
|
ErrObjectRestoreAlreadyInProgress
|
||||||
ErrNoSuchKey
|
ErrNoSuchKey
|
||||||
ErrNoSuchUpload
|
ErrNoSuchUpload
|
||||||
@@ -188,6 +190,7 @@ const (
|
|||||||
ErrMaximumExpires
|
ErrMaximumExpires
|
||||||
ErrSlowDownRead
|
ErrSlowDownRead
|
||||||
ErrSlowDownWrite
|
ErrSlowDownWrite
|
||||||
|
ErrMaxVersionsExceeded
|
||||||
ErrInvalidPrefixMarker
|
ErrInvalidPrefixMarker
|
||||||
ErrBadRequest
|
ErrBadRequest
|
||||||
ErrKeyTooLongError
|
ErrKeyTooLongError
|
||||||
@@ -855,6 +858,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Resource requested is unwritable, please reduce your request rate",
|
Description: "Resource requested is unwritable, please reduce your request rate",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
|
ErrMaxVersionsExceeded: {
|
||||||
|
Code: "MaxVersionsExceeded",
|
||||||
|
Description: "You've exceeded the limit on the number of versions you can create on this object",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInvalidPrefixMarker: {
|
ErrInvalidPrefixMarker: {
|
||||||
Code: "InvalidPrefixMarker",
|
Code: "InvalidPrefixMarker",
|
||||||
Description: "Invalid marker prefix combination",
|
Description: "Invalid marker prefix combination",
|
||||||
@@ -1015,6 +1023,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Versioning must be 'Enabled' on the bucket to add a replication target",
|
Description: "Versioning must be 'Enabled' on the bucket to add a replication target",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrReplicationValidationError: {
|
||||||
|
Code: "InvalidRequest",
|
||||||
|
Description: "Replication validation failed on target",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrReplicationPermissionCheckError: {
|
||||||
|
Code: "ReplicationPermissionCheck",
|
||||||
|
Description: "X-Minio-Source-Replication-Check cannot be specified in request. Request cannot be completed",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrNoSuchObjectLockConfiguration: {
|
ErrNoSuchObjectLockConfiguration: {
|
||||||
Code: "NoSuchObjectLockConfiguration",
|
Code: "NoSuchObjectLockConfiguration",
|
||||||
Description: "The specified object does not have a ObjectLock configuration",
|
Description: "The specified object does not have a ObjectLock configuration",
|
||||||
@@ -1385,7 +1403,7 @@ var errorCodes = errorCodeMap{
|
|||||||
},
|
},
|
||||||
ErrAdminConfigIDPCfgNameAlreadyExists: {
|
ErrAdminConfigIDPCfgNameAlreadyExists: {
|
||||||
Code: "XMinioAdminConfigIDPCfgNameAlreadyExists",
|
Code: "XMinioAdminConfigIDPCfgNameAlreadyExists",
|
||||||
Description: "An IDP configuration with the given name aleady exists",
|
Description: "An IDP configuration with the given name already exists",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrAdminConfigIDPCfgNameDoesNotExist: {
|
ErrAdminConfigIDPCfgNameDoesNotExist: {
|
||||||
@@ -2098,6 +2116,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrSlowDownRead
|
apiErr = ErrSlowDownRead
|
||||||
case errErasureWriteQuorum:
|
case errErasureWriteQuorum:
|
||||||
apiErr = ErrSlowDownWrite
|
apiErr = ErrSlowDownWrite
|
||||||
|
case errMaxVersionsExceeded:
|
||||||
|
apiErr = ErrMaxVersionsExceeded
|
||||||
// SSE errors
|
// SSE errors
|
||||||
case errInvalidEncryptionParameters:
|
case errInvalidEncryptionParameters:
|
||||||
apiErr = ErrInvalidEncryptionParameters
|
apiErr = ErrInvalidEncryptionParameters
|
||||||
@@ -2216,8 +2236,6 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrSlowDownWrite
|
apiErr = ErrSlowDownWrite
|
||||||
case InsufficientReadQuorum:
|
case InsufficientReadQuorum:
|
||||||
apiErr = ErrSlowDownRead
|
apiErr = ErrSlowDownRead
|
||||||
case InvalidMarkerPrefixCombination:
|
|
||||||
apiErr = ErrNotImplemented
|
|
||||||
case InvalidUploadIDKeyCombination:
|
case InvalidUploadIDKeyCombination:
|
||||||
apiErr = ErrNotImplemented
|
apiErr = ErrNotImplemented
|
||||||
case MalformedUploadID:
|
case MalformedUploadID:
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ var toAPIErrorTests = []struct {
|
|||||||
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
||||||
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
|
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
|
||||||
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
|
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
|
||||||
{err: InvalidMarkerPrefixCombination{}, errCode: ErrNotImplemented},
|
|
||||||
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
||||||
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
|
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
|
||||||
{err: PartTooSmall{}, errCode: ErrEntityTooSmall},
|
{err: PartTooSmall{}, errCode: ErrEntityTooSmall},
|
||||||
|
|||||||
+10
-6
@@ -23,11 +23,11 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"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"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -140,9 +140,13 @@ 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, _ := url.ParseQuery(objInfo.UserTags)
|
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
if len(tags) > 0 {
|
if tags.Count() > 0 {
|
||||||
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(len(tags))}
|
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())}
|
||||||
|
if opts.Tagging {
|
||||||
|
// This is MinIO only extension to return back tags along with the count.
|
||||||
|
w.Header()[xhttp.AmzObjectTagging] = []string{objInfo.UserTags}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +157,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
// Do not need to send any internal metadata
|
// Do not need to send any internal metadata
|
||||||
// values to client.
|
// values to client.
|
||||||
continue
|
continue
|
||||||
@@ -166,7 +170,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
|
|
||||||
var isSet bool
|
var isSet bool
|
||||||
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
|
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
|
||||||
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(userMetadataPrefix)) {
|
if !stringsHasPrefixFold(k, userMetadataPrefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w.Header()[strings.ToLower(k)] = []string{v}
|
w.Header()[strings.ToLower(k)] = []string{v}
|
||||||
|
|||||||
+3
-3
@@ -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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
xxml "github.com/minio/xxml"
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -550,7 +550,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
// Do not need to send any internal metadata
|
// Do not need to send any internal metadata
|
||||||
// values to client.
|
// values to client.
|
||||||
continue
|
continue
|
||||||
@@ -693,7 +693,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
// Do not need to send any internal metadata
|
// Do not need to send any internal metadata
|
||||||
// values to client.
|
// values to client.
|
||||||
continue
|
continue
|
||||||
|
|||||||
+12
-10
@@ -27,7 +27,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/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -461,10 +461,16 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
|
|
||||||
// MinIO extension API for replication.
|
// MinIO extension API for replication.
|
||||||
//
|
//
|
||||||
// GetBucketReplicationMetrics
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
|
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsV2Handler))))).Queries("replication-metrics", "2")
|
||||||
|
// deprecated handler
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsHandler))))).Queries("replication-metrics", "")
|
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsHandler))))).Queries("replication-metrics", "")
|
||||||
|
|
||||||
|
// ValidateBucketReplicationCreds
|
||||||
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
|
collectAPIStats("checkbucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.ValidateBucketReplicationCredsHandler))))).Queries("replication-check", "")
|
||||||
|
|
||||||
// Register rejected bucket APIs
|
// Register rejected bucket APIs
|
||||||
for _, r := range rejectedBucketAPIs {
|
for _, r := range rejectedBucketAPIs {
|
||||||
router.Methods(r.methods...).
|
router.Methods(r.methods...).
|
||||||
@@ -520,14 +526,9 @@ func corsHandler(handler http.Handler) http.Handler {
|
|||||||
"x-amz*",
|
"x-amz*",
|
||||||
"*",
|
"*",
|
||||||
}
|
}
|
||||||
|
opts := cors.Options{
|
||||||
return cors.New(cors.Options{
|
|
||||||
AllowOriginFunc: func(origin string) bool {
|
AllowOriginFunc: func(origin string) bool {
|
||||||
allowedOrigins := globalAPIConfig.getCorsAllowOrigins()
|
for _, allowedOrigin := range globalAPIConfig.getCorsAllowOrigins() {
|
||||||
if len(allowedOrigins) == 0 {
|
|
||||||
allowedOrigins = []string{"*"}
|
|
||||||
}
|
|
||||||
for _, allowedOrigin := range allowedOrigins {
|
|
||||||
if wildcard.MatchSimple(allowedOrigin, origin) {
|
if wildcard.MatchSimple(allowedOrigin, origin) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -546,5 +547,6 @@ func corsHandler(handler http.Handler) http.Handler {
|
|||||||
AllowedHeaders: commonS3Headers,
|
AllowedHeaders: commonS3Headers,
|
||||||
ExposedHeaders: commonS3Headers,
|
ExposedHeaders: commonS3Headers,
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
}).Handler(handler)
|
}
|
||||||
|
return cors.New(opts).Handler(handler)
|
||||||
}
|
}
|
||||||
|
|||||||
+260
-257
File diff suppressed because one or more lines are too long
+25
-26
@@ -41,8 +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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify if request has JWT.
|
// Verify if request has JWT.
|
||||||
@@ -186,15 +185,15 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
||||||
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
||||||
// are automatically rejected.
|
// are automatically rejected.
|
||||||
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action policy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
||||||
cred, owner, s3Err := validateAdminSignature(ctx, r, region)
|
cred, owner, s3Err := validateAdminSignature(ctx, r, region)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, s3Err
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: policy.Action(action),
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -248,7 +247,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if a session policy is set. If so, decode it here.
|
// Check if a session policy is set. If so, decode it here.
|
||||||
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
|
sp, spok := claims.Lookup(policy.SessionPolicyName)
|
||||||
if spok {
|
if spok {
|
||||||
// Looks like subpolicy is set and is a string, if set then its
|
// Looks like subpolicy is set and is a string, if set then its
|
||||||
// base64 encoded, decode it. Decoding fails reject such
|
// base64 encoded, decode it. Decoding fails reject such
|
||||||
@@ -413,7 +412,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
|
|
||||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||||
// Anonymous checks are not meant for ListAllBuckets action
|
// Anonymous checks are not meant for ListAllBuckets action
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
@@ -429,7 +428,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
@@ -446,10 +445,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
if action == policy.DeleteObjectAction && versionID != "" {
|
if action == policy.DeleteObjectAction && versionID != "" {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(policy.DeleteObjectVersionAction),
|
Action: policy.Action(policy.DeleteObjectVersionAction),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -460,10 +459,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: action,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -477,10 +476,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -568,7 +567,7 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
|
|
||||||
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
||||||
// The verification happens implicit during reading.
|
// The verification happens implicit during reading.
|
||||||
reader, err := hash.NewReader(r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
|
reader, err := hash.NewReader(ctx, r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -595,8 +594,8 @@ func isSupportedS3AuthType(aType authType) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// setAuthHandler to validate authorization header for the incoming request.
|
// setAuthMiddleware to validate authorization header for the incoming request.
|
||||||
func setAuthHandler(h http.Handler) http.Handler {
|
func setAuthMiddleware(h http.Handler) http.Handler {
|
||||||
// handler for validating incoming authorization headers.
|
// handler for validating incoming authorization headers.
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
|
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
|
||||||
@@ -696,10 +695,10 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
||||||
}
|
}
|
||||||
if retMode == objectlock.RetGovernance && byPassSet {
|
if retMode == objectlock.RetGovernance && byPassSet {
|
||||||
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
|
byPassSet = globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.BypassGovernanceRetentionAction,
|
Action: policy.BypassGovernanceRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
@@ -707,10 +706,10 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectRetentionAction,
|
Action: policy.PutObjectRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
@@ -728,7 +727,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
||||||
// call verifies bucket policies and IAM policies, supports multi user
|
// call verifies bucket policies and IAM policies, supports multi user
|
||||||
// checks etc.
|
// checks etc.
|
||||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.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
|
||||||
@@ -751,17 +750,17 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
// Do not check for PutObjectRetentionAction permission,
|
// Do not check for PutObjectRetentionAction permission,
|
||||||
// if mode and retain until date are not set.
|
// if mode and retain until date are not set.
|
||||||
// Can happen when bucket has default lock config set
|
// Can happen when bucket has default lock config set
|
||||||
if action == iampolicy.PutObjectRetentionAction &&
|
if action == policy.PutObjectRetentionAction &&
|
||||||
r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
|
r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
|
||||||
r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
|
r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
|
||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.AccessKey == "" {
|
if cred.AccessKey == "" {
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.Action(action),
|
Action: action,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
@@ -772,7 +771,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nullReader struct{}
|
type nullReader struct{}
|
||||||
@@ -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, iampolicy.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,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/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// healTask represents what to heal along with options
|
// healTask represents what to heal along with options
|
||||||
@@ -75,9 +75,9 @@ func waitForLowIO(maxIO int, maxWait time.Duration, currentIO func() int) {
|
|||||||
if tmpMaxWait > 0 {
|
if tmpMaxWait > 0 {
|
||||||
if tmpMaxWait < waitTick {
|
if tmpMaxWait < waitTick {
|
||||||
time.Sleep(tmpMaxWait)
|
time.Sleep(tmpMaxWait)
|
||||||
} else {
|
return
|
||||||
time.Sleep(waitTick)
|
|
||||||
}
|
}
|
||||||
|
time.Sleep(waitTick)
|
||||||
tmpMaxWait -= waitTick
|
tmpMaxWait -= waitTick
|
||||||
}
|
}
|
||||||
if tmpMaxWait <= 0 {
|
if tmpMaxWait <= 0 {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -347,7 +347,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||||
|
|
||||||
if env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
||||||
go monitorLocalDisksAndHeal(ctx, z)
|
go monitorLocalDisksAndHeal(ctx, z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,7 +485,12 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove .healing.bin from all disks with similar heal-id
|
// Remove .healing.bin from all disks with similar heal-id
|
||||||
for _, disk := range z.serverPools[poolIdx].sets[setIdx].getDisks() {
|
disks, err := z.GetDisks(poolIdx, setIdx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, disk := range disks {
|
||||||
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) {
|
||||||
|
|||||||
+155
-300
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -43,230 +42,18 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/auth"
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"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/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/v2/console"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/workers"
|
||||||
"github.com/minio/pkg/workers"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// replicate:
|
|
||||||
// # source of the objects to be replicated
|
|
||||||
// source:
|
|
||||||
// type: "minio"
|
|
||||||
// bucket: "testbucket"
|
|
||||||
// prefix: "spark/"
|
|
||||||
//
|
|
||||||
// # optional flags based filtering criteria
|
|
||||||
// # for source objects
|
|
||||||
// flags:
|
|
||||||
// filter:
|
|
||||||
// newerThan: "7d"
|
|
||||||
// olderThan: "7d"
|
|
||||||
// createdAfter: "date"
|
|
||||||
// createdBefore: "date"
|
|
||||||
// tags:
|
|
||||||
// - key: "name"
|
|
||||||
// value: "value*"
|
|
||||||
// metadata:
|
|
||||||
// - key: "content-type"
|
|
||||||
// value: "image/*"
|
|
||||||
// notify:
|
|
||||||
// endpoint: "https://splunk-hec.dev.com"
|
|
||||||
// token: "Splunk ..." # e.g. "Bearer token"
|
|
||||||
//
|
|
||||||
// # target where the objects must be replicated
|
|
||||||
// target:
|
|
||||||
// type: "minio"
|
|
||||||
// bucket: "testbucket1"
|
|
||||||
// endpoint: "https://play.min.io"
|
|
||||||
// path: "on"
|
|
||||||
// credentials:
|
|
||||||
// accessKey: "minioadmin"
|
|
||||||
// secretKey: "minioadmin"
|
|
||||||
// sessionToken: ""
|
|
||||||
|
|
||||||
// BatchJobReplicateKV is a datatype that holds key and values for filtering of objects
|
|
||||||
// used by metadata filter as well as tags based filtering.
|
|
||||||
type BatchJobReplicateKV struct {
|
|
||||||
Key string `yaml:"key" json:"key"`
|
|
||||||
Value string `yaml:"value" json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate returns an error if key is empty
|
|
||||||
func (kv BatchJobReplicateKV) Validate() error {
|
|
||||||
if kv.Key == "" {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty indicates if kv is not set
|
|
||||||
func (kv BatchJobReplicateKV) Empty() bool {
|
|
||||||
return kv.Key == "" && kv.Value == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match matches input kv with kv, value will be wildcard matched depending on the user input
|
|
||||||
func (kv BatchJobReplicateKV) Match(ikv BatchJobReplicateKV) bool {
|
|
||||||
if kv.Empty() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.EqualFold(kv.Key, ikv.Key) {
|
|
||||||
return wildcard.Match(kv.Value, ikv.Value)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchReplicateRetry datatype represents total retry attempts and delay between each retries.
|
|
||||||
type BatchReplicateRetry struct {
|
|
||||||
Attempts int `yaml:"attempts" json:"attempts"` // number of retry attempts
|
|
||||||
Delay time.Duration `yaml:"delay" json:"delay"` // delay between each retries
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates input replicate retries.
|
|
||||||
func (r BatchReplicateRetry) Validate() error {
|
|
||||||
if r.Attempts < 0 {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Delay < 0 {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchReplicateFilter holds all the filters currently supported for batch replication
|
|
||||||
type BatchReplicateFilter struct {
|
|
||||||
NewerThan time.Duration `yaml:"newerThan,omitempty" json:"newerThan"`
|
|
||||||
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
|
||||||
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
|
||||||
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
|
||||||
Tags []BatchJobReplicateKV `yaml:"tags,omitempty" json:"tags"`
|
|
||||||
Metadata []BatchJobReplicateKV `yaml:"metadata,omitempty" json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchReplicateNotification success or failure notification endpoint for each job attempts
|
|
||||||
type BatchReplicateNotification struct {
|
|
||||||
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
|
||||||
Token string `yaml:"token" json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchJobReplicateFlags various configurations for replication job definition currently includes
|
|
||||||
// - filter
|
|
||||||
// - notify
|
|
||||||
// - retry
|
|
||||||
type BatchJobReplicateFlags struct {
|
|
||||||
Filter BatchReplicateFilter `yaml:"filter" json:"filter"`
|
|
||||||
Notify BatchReplicateNotification `yaml:"notify" json:"notify"`
|
|
||||||
Retry BatchReplicateRetry `yaml:"retry" json:"retry"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchJobReplicateResourceType defines the type of batch jobs
|
|
||||||
type BatchJobReplicateResourceType string
|
|
||||||
|
|
||||||
// Validate validates if the replicate resource type is recognized and supported
|
|
||||||
func (t BatchJobReplicateResourceType) Validate() error {
|
|
||||||
switch t {
|
|
||||||
case BatchJobReplicateResourceMinIO:
|
|
||||||
case BatchJobReplicateResourceS3:
|
|
||||||
default:
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t BatchJobReplicateResourceType) isMinio() bool {
|
|
||||||
return t == BatchJobReplicateResourceMinIO
|
|
||||||
}
|
|
||||||
|
|
||||||
// Different types of batch jobs..
|
|
||||||
const (
|
|
||||||
BatchJobReplicateResourceMinIO BatchJobReplicateResourceType = "minio"
|
|
||||||
BatchJobReplicateResourceS3 BatchJobReplicateResourceType = "s3"
|
|
||||||
|
|
||||||
// add future targets
|
|
||||||
)
|
|
||||||
|
|
||||||
// BatchJobReplicateCredentials access credentials for batch replication it may
|
|
||||||
// be either for target or source.
|
|
||||||
type BatchJobReplicateCredentials struct {
|
|
||||||
AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty" yaml:"accessKey"`
|
|
||||||
SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty" yaml:"secretKey"`
|
|
||||||
SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty" yaml:"sessionToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty indicates if credentials are not set
|
|
||||||
func (c BatchJobReplicateCredentials) Empty() bool {
|
|
||||||
return c.AccessKey == "" && c.SecretKey == "" && c.SessionToken == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates if credentials are valid
|
|
||||||
func (c BatchJobReplicateCredentials) Validate() error {
|
|
||||||
if !auth.IsAccessKeyValid(c.AccessKey) || !auth.IsSecretKeyValid(c.SecretKey) {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchJobReplicateTarget describes target element of the replication job that receives
|
|
||||||
// the filtered data from source
|
|
||||||
type BatchJobReplicateTarget struct {
|
|
||||||
Type BatchJobReplicateResourceType `yaml:"type" json:"type"`
|
|
||||||
Bucket string `yaml:"bucket" json:"bucket"`
|
|
||||||
Prefix string `yaml:"prefix" json:"prefix"`
|
|
||||||
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
|
||||||
Path string `yaml:"path" json:"path"`
|
|
||||||
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidPath returns true if path is valid
|
|
||||||
func (t BatchJobReplicateTarget) ValidPath() bool {
|
|
||||||
return t.Path == "on" || t.Path == "off" || t.Path == "auto" || t.Path == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchJobReplicateSource describes source element of the replication job that is
|
|
||||||
// the source of the data for the target
|
|
||||||
type BatchJobReplicateSource struct {
|
|
||||||
Type BatchJobReplicateResourceType `yaml:"type" json:"type"`
|
|
||||||
Bucket string `yaml:"bucket" json:"bucket"`
|
|
||||||
Prefix string `yaml:"prefix" json:"prefix"`
|
|
||||||
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
|
||||||
Path string `yaml:"path" json:"path"`
|
|
||||||
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidPath returns true if path is valid
|
|
||||||
func (s BatchJobReplicateSource) ValidPath() bool {
|
|
||||||
switch s.Path {
|
|
||||||
case "on", "off", "auto", "":
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchJobReplicateV1 v1 of batch job replication
|
|
||||||
type BatchJobReplicateV1 struct {
|
|
||||||
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
|
|
||||||
Flags BatchJobReplicateFlags `yaml:"flags" json:"flags"`
|
|
||||||
Target BatchJobReplicateTarget `yaml:"target" json:"target"`
|
|
||||||
Source BatchJobReplicateSource `yaml:"source" json:"source"`
|
|
||||||
|
|
||||||
clnt *miniogo.Core `msg:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteToLocal returns true if source is remote and target is local
|
|
||||||
func (r BatchJobReplicateV1) RemoteToLocal() bool {
|
|
||||||
return !r.Source.Creds.Empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchJobRequest this is an internal data structure not for external consumption.
|
// BatchJobRequest this is an internal data structure not for external consumption.
|
||||||
type BatchJobRequest struct {
|
type BatchJobRequest struct {
|
||||||
ID string `yaml:"-" json:"name"`
|
ID string `yaml:"-" json:"name"`
|
||||||
@@ -278,23 +65,28 @@ type BatchJobRequest struct {
|
|||||||
ctx context.Context `msg:"-"`
|
ctx context.Context `msg:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify notifies notification endpoint if configured regarding job failure or success.
|
func notifyEndpoint(ctx context.Context, ri *batchJobInfo, endpoint, token string) error {
|
||||||
func (r BatchJobReplicateV1) Notify(ctx context.Context, body io.Reader) error {
|
if endpoint == "" {
|
||||||
if r.Flags.Notify.Endpoint == "" {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf, err := json.Marshal(ri)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.Flags.Notify.Endpoint, body)
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(buf))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Flags.Notify.Token != "" {
|
if token != "" {
|
||||||
req.Header.Set("Authorization", r.Flags.Notify.Token)
|
req.Header.Set("Authorization", token)
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
@@ -310,18 +102,21 @@ func (r BatchJobReplicateV1) Notify(ctx context.Context, body io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify notifies notification endpoint if configured regarding job failure or success.
|
||||||
|
func (r BatchJobReplicateV1) Notify(ctx context.Context, ri *batchJobInfo) error {
|
||||||
|
return notifyEndpoint(ctx, ri, r.Flags.Notify.Endpoint, r.Flags.Notify.Token)
|
||||||
|
}
|
||||||
|
|
||||||
// ReplicateFromSource - this is not implemented yet where source is 'remote' and target is local.
|
// ReplicateFromSource - this is not implemented yet where source is 'remote' and target is local.
|
||||||
func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api ObjectLayer, core *minio.Core, srcObjInfo ObjectInfo, retry bool) error {
|
func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api ObjectLayer, core *miniogo.Core, srcObjInfo ObjectInfo, retry bool) error {
|
||||||
srcBucket := r.Source.Bucket
|
srcBucket := r.Source.Bucket
|
||||||
tgtBucket := r.Target.Bucket
|
tgtBucket := r.Target.Bucket
|
||||||
srcObject := srcObjInfo.Name
|
srcObject := srcObjInfo.Name
|
||||||
tgtObject := srcObjInfo.Name
|
tgtObject := srcObjInfo.Name
|
||||||
if r.Target.Prefix != "" {
|
if r.Target.Prefix != "" {
|
||||||
tgtObject = path.Join(r.Target.Prefix, srcObjInfo.Name)
|
tgtObject = pathJoin(r.Target.Prefix, srcObjInfo.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
versioned := globalBucketVersioningSys.PrefixEnabled(tgtBucket, tgtObject)
|
|
||||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(tgtBucket, tgtObject)
|
|
||||||
versionID := srcObjInfo.VersionID
|
versionID := srcObjInfo.VersionID
|
||||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||||
versionID = ""
|
versionID = ""
|
||||||
@@ -329,8 +124,10 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
if srcObjInfo.DeleteMarker {
|
if srcObjInfo.DeleteMarker {
|
||||||
_, err := api.DeleteObject(ctx, tgtBucket, tgtObject, ObjectOptions{
|
_, err := api.DeleteObject(ctx, tgtBucket, tgtObject, ObjectOptions{
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
VersionSuspended: versionSuspended,
|
// Since we are preserving a delete marker, we have to make sure this is always true.
|
||||||
Versioned: versioned,
|
// regardless of the current configuration of the bucket we must preserve all versions
|
||||||
|
// on the pool being batch replicated from source.
|
||||||
|
Versioned: true,
|
||||||
MTime: srcObjInfo.ModTime,
|
MTime: srcObjInfo.ModTime,
|
||||||
DeleteMarker: srcObjInfo.DeleteMarker,
|
DeleteMarker: srcObjInfo.DeleteMarker,
|
||||||
ReplicationRequest: true,
|
ReplicationRequest: true,
|
||||||
@@ -340,8 +137,6 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
|
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
VersionID: srcObjInfo.VersionID,
|
VersionID: srcObjInfo.VersionID,
|
||||||
Versioned: versioned,
|
|
||||||
VersionSuspended: versionSuspended,
|
|
||||||
MTime: srcObjInfo.ModTime,
|
MTime: srcObjInfo.ModTime,
|
||||||
PreserveETag: srcObjInfo.ETag,
|
PreserveETag: srcObjInfo.ETag,
|
||||||
UserDefined: srcObjInfo.UserDefined,
|
UserDefined: srcObjInfo.UserDefined,
|
||||||
@@ -360,7 +155,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
}
|
}
|
||||||
return r.copyWithMultipartfromSource(ctx, api, core, srcObjInfo, opts, partsCount)
|
return r.copyWithMultipartfromSource(ctx, api, core, srcObjInfo, opts, partsCount)
|
||||||
}
|
}
|
||||||
gopts := minio.GetObjectOptions{
|
gopts := miniogo.GetObjectOptions{
|
||||||
VersionID: srcObjInfo.VersionID,
|
VersionID: srcObjInfo.VersionID,
|
||||||
}
|
}
|
||||||
if err := gopts.SetMatchETag(srcObjInfo.ETag); err != nil {
|
if err := gopts.SetMatchETag(srcObjInfo.ETag); err != nil {
|
||||||
@@ -372,7 +167,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
|
|
||||||
hr, err := hash.NewReader(rd, objInfo.Size, "", "", objInfo.Size)
|
hr, err := hash.NewReader(ctx, rd, objInfo.Size, "", "", objInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -381,13 +176,13 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, api ObjectLayer, c *minio.Core, srcObjInfo ObjectInfo, opts ObjectOptions, partsCount int) (err error) {
|
func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, api ObjectLayer, c *miniogo.Core, srcObjInfo ObjectInfo, opts ObjectOptions, partsCount int) (err error) {
|
||||||
srcBucket := r.Source.Bucket
|
srcBucket := r.Source.Bucket
|
||||||
tgtBucket := r.Target.Bucket
|
tgtBucket := r.Target.Bucket
|
||||||
srcObject := srcObjInfo.Name
|
srcObject := srcObjInfo.Name
|
||||||
tgtObject := srcObjInfo.Name
|
tgtObject := srcObjInfo.Name
|
||||||
if r.Target.Prefix != "" {
|
if r.Target.Prefix != "" {
|
||||||
tgtObject = path.Join(r.Target.Prefix, srcObjInfo.Name)
|
tgtObject = pathJoin(r.Target.Prefix, srcObjInfo.Name)
|
||||||
}
|
}
|
||||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||||
opts.VersionID = ""
|
opts.VersionID = ""
|
||||||
@@ -422,7 +217,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
|||||||
)
|
)
|
||||||
|
|
||||||
for i := 0; i < partsCount; i++ {
|
for i := 0; i < partsCount; i++ {
|
||||||
gopts := minio.GetObjectOptions{
|
gopts := miniogo.GetObjectOptions{
|
||||||
VersionID: srcObjInfo.VersionID,
|
VersionID: srcObjInfo.VersionID,
|
||||||
PartNumber: i + 1,
|
PartNumber: i + 1,
|
||||||
}
|
}
|
||||||
@@ -435,7 +230,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
|||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
|
|
||||||
hr, err = hash.NewReader(io.LimitReader(rd, objInfo.Size), objInfo.Size, "", "", objInfo.Size)
|
hr, err = hash.NewReader(ctx, io.LimitReader(rd, objInfo.Size), objInfo.Size, "", "", objInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -475,6 +270,10 @@ 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
|
||||||
|
isMetadata := len(r.Flags.Filter.Metadata) != 0
|
||||||
|
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
|
||||||
|
|
||||||
skip := func(oi ObjectInfo) (ok bool) {
|
skip := func(oi ObjectInfo) (ok bool) {
|
||||||
if r.Flags.Filter.OlderThan > 0 && time.Since(oi.ModTime) < r.Flags.Filter.OlderThan {
|
if r.Flags.Filter.OlderThan > 0 && time.Since(oi.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
|
||||||
@@ -495,7 +294,8 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
// skip all objects that are created after the specified time.
|
// skip all objects that are created after the specified time.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(r.Flags.Filter.Tags) > 0 {
|
|
||||||
|
if isTags {
|
||||||
// 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
|
||||||
@@ -508,7 +308,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
}
|
}
|
||||||
for _, kv := range r.Flags.Filter.Tags {
|
for _, kv := range r.Flags.Filter.Tags {
|
||||||
for t, v := range tagMap {
|
for t, v := range tagMap {
|
||||||
if kv.Match(BatchJobReplicateKV{Key: t, Value: v}) {
|
if kv.Match(BatchJobKV{Key: t, Value: v}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,23 +318,19 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Flags.Filter.Metadata) > 0 {
|
|
||||||
for _, kv := range r.Flags.Filter.Metadata {
|
for _, kv := range r.Flags.Filter.Metadata {
|
||||||
for k, v := range oi.UserDefined {
|
for k, v := range oi.UserDefined {
|
||||||
if !strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") && !isStandardHeader(k) {
|
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// We only need to match x-amz-meta or standardHeaders
|
// We only need to match x-amz-meta or standardHeaders
|
||||||
if kv.Match(BatchJobReplicateKV{Key: k, Value: v}) {
|
if kv.Match(BatchJobKV{Key: k, Value: v}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// None of the provided metadata filters match skip the object.
|
// None of the provided filters match
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,7 +352,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
||||||
core := &minio.Core{Client: c}
|
core := &miniogo.Core{Client: c}
|
||||||
|
|
||||||
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -577,7 +373,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
|
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
|
||||||
minioSrc := r.Source.Type == BatchJobReplicateResourceMinIO
|
minioSrc := r.Source.Type == BatchJobReplicateResourceMinIO
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
objInfoCh := c.ListObjects(ctx, r.Source.Bucket, minio.ListObjectsOptions{
|
objInfoCh := c.ListObjects(ctx, r.Source.Bucket, miniogo.ListObjectsOptions{
|
||||||
Prefix: r.Source.Prefix,
|
Prefix: r.Source.Prefix,
|
||||||
WithVersions: minioSrc,
|
WithVersions: minioSrc,
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
@@ -589,17 +385,32 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
for obj := range objInfoCh {
|
for obj := range objInfoCh {
|
||||||
oi := toObjectInfo(r.Source.Bucket, obj.Key, obj)
|
oi := toObjectInfo(r.Source.Bucket, obj.Key, obj)
|
||||||
if !minioSrc {
|
if !minioSrc {
|
||||||
oi2, err := c.StatObject(ctx, r.Source.Bucket, obj.Key, minio.StatObjectOptions{})
|
// Check if metadata filter was requested and it is expected to have
|
||||||
|
// all user metadata or just storageClass. If its only storageClass
|
||||||
|
// List() already returns relevant information for filter to be applied.
|
||||||
|
if isMetadata && !isStorageClassOnly {
|
||||||
|
oi2, err := c.StatObject(ctx, r.Source.Bucket, obj.Key, miniogo.StatObjectOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
oi = toObjectInfo(r.Source.Bucket, obj.Key, oi2)
|
oi = toObjectInfo(r.Source.Bucket, obj.Key, oi2)
|
||||||
} 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)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if isTags {
|
||||||
|
tags, err := c.GetObjectTagging(ctx, r.Source.Bucket, obj.Key, minio.GetObjectTaggingOptions{})
|
||||||
|
if err == nil {
|
||||||
|
oi.UserTags = tags.String()
|
||||||
|
} else {
|
||||||
|
if !isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) &&
|
||||||
|
!isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
cancel()
|
}
|
||||||
return err
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if skip(oi) {
|
if skip(oi) {
|
||||||
@@ -646,8 +457,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
|
||||||
buf, _ := json.Marshal(ri)
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
if err := r.Notify(ctx, bytes.NewReader(buf)); err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,7 +481,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toObjectInfo converts minio.ObjectInfo to ObjectInfo
|
// toObjectInfo converts minio.ObjectInfo to ObjectInfo
|
||||||
func toObjectInfo(bucket, object string, objInfo minio.ObjectInfo) ObjectInfo {
|
func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo {
|
||||||
tags, _ := tags.MapToObjectTags(objInfo.UserTags)
|
tags, _ := tags.MapToObjectTags(objInfo.UserTags)
|
||||||
oi := ObjectInfo{
|
oi := ObjectInfo{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
@@ -688,10 +498,12 @@ func toObjectInfo(bucket, object string, objInfo minio.ObjectInfo) ObjectInfo {
|
|||||||
ReplicationStatusInternal: objInfo.ReplicationStatus,
|
ReplicationStatusInternal: objInfo.ReplicationStatus,
|
||||||
UserTags: tags.String(),
|
UserTags: tags.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
oi.UserDefined = make(map[string]string, len(objInfo.Metadata))
|
oi.UserDefined = make(map[string]string, len(objInfo.Metadata))
|
||||||
for k, v := range objInfo.Metadata {
|
for k, v := range objInfo.Metadata {
|
||||||
oi.UserDefined[k] = v[0]
|
oi.UserDefined[k] = v[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
ce, ok := oi.UserDefined[xhttp.ContentEncoding]
|
ce, ok := oi.UserDefined[xhttp.ContentEncoding]
|
||||||
if !ok {
|
if !ok {
|
||||||
ce, ok = oi.UserDefined[strings.ToLower(xhttp.ContentEncoding)]
|
ce, ok = oi.UserDefined[strings.ToLower(xhttp.ContentEncoding)]
|
||||||
@@ -699,6 +511,12 @@ func toObjectInfo(bucket, object string, objInfo minio.ObjectInfo) ObjectInfo {
|
|||||||
if ok {
|
if ok {
|
||||||
oi.ContentEncoding = ce
|
oi.ContentEncoding = ce
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, ok = oi.UserDefined[xhttp.AmzStorageClass]
|
||||||
|
if !ok {
|
||||||
|
oi.UserDefined[xhttp.AmzStorageClass] = objInfo.StorageClass
|
||||||
|
}
|
||||||
|
|
||||||
return oi
|
return oi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,7 +846,7 @@ func (r *BatchJobReplicateV1) 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
|
||||||
@@ -1063,7 +881,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
|
|
||||||
for _, kv := range r.Flags.Filter.Tags {
|
for _, kv := range r.Flags.Filter.Tags {
|
||||||
for t, v := range tagMap {
|
for t, v := range tagMap {
|
||||||
if kv.Match(BatchJobReplicateKV{Key: t, Value: v}) {
|
if kv.Match(BatchJobKV{Key: t, Value: v}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1076,11 +894,11 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if len(r.Flags.Filter.Metadata) > 0 {
|
if len(r.Flags.Filter.Metadata) > 0 {
|
||||||
for _, kv := range r.Flags.Filter.Metadata {
|
for _, kv := range r.Flags.Filter.Metadata {
|
||||||
for k, v := range info.Metadata {
|
for k, v := range info.Metadata {
|
||||||
if !strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") && !isStandardHeader(k) {
|
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// We only need to match x-amz-meta or standardHeaders
|
// We only need to match x-amz-meta or standardHeaders
|
||||||
if kv.Match(BatchJobReplicateKV{Key: k, Value: v}) {
|
if kv.Match(BatchJobKV{Key: k, Value: v}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1138,7 +956,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan ObjectInfo, 100)
|
||||||
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{
|
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{
|
||||||
WalkMarker: lastObject,
|
WalkMarker: lastObject,
|
||||||
WalkFilter: skip,
|
WalkFilter: 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.
|
||||||
@@ -1194,8 +1012,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
|
||||||
buf, _ := json.Marshal(ri)
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
if err := r.Notify(ctx, bytes.NewReader(buf)); err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1407,7 +1224,6 @@ func (j BatchJobRequest) delete(ctx context.Context, api ObjectLayer) {
|
|||||||
case j.KeyRotate != nil:
|
case j.KeyRotate != nil:
|
||||||
deleteConfig(ctx, api, pathJoin(j.Location, batchKeyRotationName))
|
deleteConfig(ctx, api, pathJoin(j.Location, batchKeyRotationName))
|
||||||
}
|
}
|
||||||
globalBatchJobsMetrics.delete(j.ID)
|
|
||||||
deleteConfig(ctx, api, j.Location)
|
deleteConfig(ctx, api, j.Location)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1464,11 +1280,9 @@ func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (p
|
|||||||
// ListBatchJobs - lists all currently active batch jobs, optionally takes {jobType}
|
// ListBatchJobs - lists all currently active batch jobs, optionally takes {jobType}
|
||||||
// input to list only active batch jobs of 'jobType'
|
// input to list only active batch jobs of 'jobType'
|
||||||
func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListBatchJobs")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListBatchJobsAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListBatchJobsAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1516,23 +1330,21 @@ var errNoSuchJob = errors.New("no such job")
|
|||||||
|
|
||||||
// DescribeBatchJob returns the currently active batch job definition
|
// DescribeBatchJob returns the currently active batch job definition
|
||||||
func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DescribeBatchJob")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DescribeBatchJobAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DescribeBatchJobAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id := r.Form.Get("jobId")
|
jobID := r.Form.Get("jobId")
|
||||||
if id == "" {
|
if jobID == "" {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &BatchJobRequest{}
|
req := &BatchJobRequest{}
|
||||||
if err := req.load(ctx, objectAPI, pathJoin(batchJobPrefix, id)); 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)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -1553,16 +1365,14 @@ func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
// StarBatchJob queue a new job for execution
|
// StarBatchJob queue a new job for execution
|
||||||
func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StartBatchJob")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, creds := validateAdminReq(ctx, w, r, policy.StartBatchJobAction)
|
||||||
|
|
||||||
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.StartBatchJobAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := io.ReadAll(r.Body)
|
buf, err := io.ReadAll(ioutil.HardLimitReader(r.Body, humanize.MiByte*4))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1574,12 +1384,12 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
job := &BatchJobRequest{}
|
job := &BatchJobRequest{}
|
||||||
if err = yaml.Unmarshal(buf, job); err != nil {
|
if err = yaml.UnmarshalStrict(buf, job); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job.ID = shortuuid.New()
|
job.ID = fmt.Sprintf("%s:%d", shortuuid.New(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
job.User = user
|
job.User = user
|
||||||
job.Started = time.Now()
|
job.Started = time.Now()
|
||||||
|
|
||||||
@@ -1609,27 +1419,33 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
// CancelBatchJob cancels a job in progress
|
// CancelBatchJob cancels a job in progress
|
||||||
func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "CancelBatchJob")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.CancelBatchJobAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CancelBatchJobAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jobID := r.Form.Get("id")
|
jobID := r.Form.Get("id")
|
||||||
if jobID == "" {
|
if jobID == "" {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, success := proxyRequestByToken(ctx, w, r, jobID); success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := globalBatchJobPool.canceler(jobID, true); err != nil {
|
if err := globalBatchJobPool.canceler(jobID, true); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrInvalidRequest, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrInvalidRequest, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
j := BatchJobRequest{
|
j := BatchJobRequest{
|
||||||
ID: jobID,
|
ID: jobID,
|
||||||
Location: pathJoin(batchJobPrefix, jobID),
|
Location: pathJoin(batchJobPrefix, jobID),
|
||||||
}
|
}
|
||||||
|
|
||||||
j.delete(ctx, objectAPI)
|
j.delete(ctx, objectAPI)
|
||||||
|
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
@@ -1684,6 +1500,11 @@ func (j *BatchJobPool) resume() {
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
_, nodeIdx := parseRequestToken(req.ID)
|
||||||
|
if nodeIdx > -1 && GetProxyEndpointLocalIndex(globalProxyEndpoints) != nodeIdx {
|
||||||
|
// This job doesn't belong on this node.
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err := j.queueJob(req); err != nil {
|
if err := j.queueJob(req); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
@@ -1804,10 +1625,6 @@ type batchJobMetrics struct {
|
|||||||
metrics map[string]*batchJobInfo
|
metrics map[string]*batchJobInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalBatchJobsMetrics = batchJobMetrics{
|
|
||||||
metrics: make(map[string]*batchJobInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
//msgp:ignore batchJobMetric
|
//msgp:ignore batchJobMetric
|
||||||
//go:generate stringer -type=batchJobMetric -trimprefix=batchJobMetric $GOFILE
|
//go:generate stringer -type=batchJobMetric -trimprefix=batchJobMetric $GOFILE
|
||||||
type batchJobMetric uint8
|
type batchJobMetric uint8
|
||||||
@@ -1847,9 +1664,17 @@ func (m *batchJobMetrics) report(jobID string) (metrics *madmin.BatchJobMetrics)
|
|||||||
metrics = &madmin.BatchJobMetrics{CollectedAt: time.Now(), Jobs: make(map[string]madmin.JobMetric)}
|
metrics = &madmin.BatchJobMetrics{CollectedAt: time.Now(), Jobs: make(map[string]madmin.JobMetric)}
|
||||||
m.RLock()
|
m.RLock()
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
|
|
||||||
|
match := true
|
||||||
for id, job := range m.metrics {
|
for id, job := range m.metrics {
|
||||||
match := jobID != "" && id == jobID
|
if jobID != "" {
|
||||||
metrics.Jobs[id] = madmin.JobMetric{
|
match = id == jobID
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m := madmin.JobMetric{
|
||||||
JobID: job.JobID,
|
JobID: job.JobID,
|
||||||
JobType: job.JobType,
|
JobType: job.JobType,
|
||||||
StartTime: job.StartTime,
|
StartTime: job.StartTime,
|
||||||
@@ -1857,28 +1682,58 @@ func (m *batchJobMetrics) report(jobID string) (metrics *madmin.BatchJobMetrics)
|
|||||||
RetryAttempts: job.RetryAttempts,
|
RetryAttempts: job.RetryAttempts,
|
||||||
Complete: job.Complete,
|
Complete: job.Complete,
|
||||||
Failed: job.Failed,
|
Failed: job.Failed,
|
||||||
Replicate: &madmin.ReplicateInfo{
|
}
|
||||||
|
|
||||||
|
switch job.JobType {
|
||||||
|
case string(madmin.BatchJobReplicate):
|
||||||
|
m.Replicate = &madmin.ReplicateInfo{
|
||||||
Bucket: job.Bucket,
|
Bucket: job.Bucket,
|
||||||
Object: job.Object,
|
Object: job.Object,
|
||||||
Objects: job.Objects,
|
Objects: job.Objects,
|
||||||
ObjectsFailed: job.ObjectsFailed,
|
ObjectsFailed: job.ObjectsFailed,
|
||||||
BytesTransferred: job.BytesTransferred,
|
BytesTransferred: job.BytesTransferred,
|
||||||
BytesFailed: job.BytesFailed,
|
BytesFailed: job.BytesFailed,
|
||||||
},
|
}
|
||||||
KeyRotate: &madmin.KeyRotationInfo{
|
case string(madmin.BatchJobKeyRotate):
|
||||||
|
m.KeyRotate = &madmin.KeyRotationInfo{
|
||||||
Bucket: job.Bucket,
|
Bucket: job.Bucket,
|
||||||
Object: job.Object,
|
Object: job.Object,
|
||||||
Objects: job.Objects,
|
Objects: job.Objects,
|
||||||
ObjectsFailed: job.ObjectsFailed,
|
ObjectsFailed: job.ObjectsFailed,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if match {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metrics.Jobs[id] = m
|
||||||
}
|
}
|
||||||
return metrics
|
return metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keep job metrics for some time after the job is completed
|
||||||
|
// in-case some one wants to look at the older results.
|
||||||
|
func (m *batchJobMetrics) purgeJobMetrics() {
|
||||||
|
t := time.NewTicker(6 * time.Hour)
|
||||||
|
defer t.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-GlobalContext.Done():
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
var toDeleteJobMetrics []string
|
||||||
|
m.RLock()
|
||||||
|
for id, metrics := range m.metrics {
|
||||||
|
if time.Since(metrics.LastUpdate) > 24*time.Hour && (metrics.Complete || metrics.Failed) {
|
||||||
|
toDeleteJobMetrics = append(toDeleteJobMetrics, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.RUnlock()
|
||||||
|
for _, jobID := range toDeleteJobMetrics {
|
||||||
|
m.delete(jobID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *batchJobMetrics) delete(jobID string) {
|
func (m *batchJobMetrics) delete(jobID string) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE
|
||||||
|
|
||||||
|
// BatchJobKV is a key-value data type which supports wildcard matching
|
||||||
|
type BatchJobKV struct {
|
||||||
|
Key string `yaml:"key" json:"key"`
|
||||||
|
Value string `yaml:"value" json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns an error if key is empty
|
||||||
|
func (kv BatchJobKV) Validate() error {
|
||||||
|
if kv.Key == "" {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty indicates if kv is not set
|
||||||
|
func (kv BatchJobKV) Empty() bool {
|
||||||
|
return kv.Key == "" && kv.Value == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches input kv with kv, value will be wildcard matched depending on the user input
|
||||||
|
func (kv BatchJobKV) Match(ikv BatchJobKV) bool {
|
||||||
|
if kv.Empty() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.EqualFold(kv.Key, ikv.Key) {
|
||||||
|
return wildcard.Match(kv.Value, ikv.Value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobNotification stores notification endpoint and token information.
|
||||||
|
// Used by batch jobs to notify of their status.
|
||||||
|
type BatchJobNotification struct {
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Token string `yaml:"token" json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobRetry stores retry configuration used in the event of failures.
|
||||||
|
type BatchJobRetry struct {
|
||||||
|
Attempts int `yaml:"attempts" json:"attempts"` // number of retry attempts
|
||||||
|
Delay time.Duration `yaml:"delay" json:"delay"` // delay between each retries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates input replicate retries.
|
||||||
|
func (r BatchJobRetry) Validate() error {
|
||||||
|
if r.Attempts < 0 {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Delay < 0 {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobKV) 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 "Key":
|
||||||
|
z.Key, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Value":
|
||||||
|
z.Value, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobKV) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "Key"
|
||||||
|
err = en.Append(0x82, 0xa3, 0x4b, 0x65, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Key)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Value"
|
||||||
|
err = en.Append(0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Value)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobKV) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "Key"
|
||||||
|
o = append(o, 0x82, 0xa3, 0x4b, 0x65, 0x79)
|
||||||
|
o = msgp.AppendString(o, z.Key)
|
||||||
|
// string "Value"
|
||||||
|
o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
||||||
|
o = msgp.AppendString(o, z.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobKV) 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 "Key":
|
||||||
|
z.Key, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Value":
|
||||||
|
z.Value, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Value")
|
||||||
|
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 BatchJobKV) Msgsize() (s int) {
|
||||||
|
s = 1 + 4 + msgp.StringPrefixSize + len(z.Key) + 6 + msgp.StringPrefixSize + len(z.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobNotification) 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 "Endpoint":
|
||||||
|
z.Endpoint, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Token":
|
||||||
|
z.Token, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobNotification) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "Endpoint"
|
||||||
|
err = en.Append(0x82, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Token"
|
||||||
|
err = en.Append(0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Token)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobNotification) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "Endpoint"
|
||||||
|
o = append(o, 0x82, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.Endpoint)
|
||||||
|
// string "Token"
|
||||||
|
o = append(o, 0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||||
|
o = msgp.AppendString(o, z.Token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobNotification) 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 "Endpoint":
|
||||||
|
z.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Token":
|
||||||
|
z.Token, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Token")
|
||||||
|
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 BatchJobNotification) Msgsize() (s int) {
|
||||||
|
s = 1 + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 6 + msgp.StringPrefixSize + len(z.Token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobRetry) 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 "Attempts":
|
||||||
|
z.Attempts, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Delay":
|
||||||
|
z.Delay, err = dc.ReadDuration()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Delay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobRetry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "Attempts"
|
||||||
|
err = en.Append(0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.Attempts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Delay"
|
||||||
|
err = en.Append(0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteDuration(z.Delay)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Delay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobRetry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "Attempts"
|
||||||
|
o = append(o, 0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
||||||
|
o = msgp.AppendInt(o, z.Attempts)
|
||||||
|
// string "Delay"
|
||||||
|
o = append(o, 0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
||||||
|
o = msgp.AppendDuration(o, z.Delay)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobRetry) 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 "Attempts":
|
||||||
|
z.Attempts, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Delay":
|
||||||
|
z.Delay, bts, err = msgp.ReadDurationBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Delay")
|
||||||
|
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 BatchJobRetry) Msgsize() (s int) {
|
||||||
|
s = 1 + 9 + msgp.IntSize + 6 + msgp.DurationSize
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobKV(t *testing.T) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
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 TestEncodeDecodeBatchJobKV(t *testing.T) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobKV Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobKV{}
|
||||||
|
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 BenchmarkEncodeBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
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 BenchmarkDecodeBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
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 TestMarshalUnmarshalBatchJobNotification(t *testing.T) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
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 TestEncodeDecodeBatchJobNotification(t *testing.T) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobNotification Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobNotification{}
|
||||||
|
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 BenchmarkEncodeBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
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 BenchmarkDecodeBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
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 TestMarshalUnmarshalBatchJobRetry(t *testing.T) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
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 TestEncodeDecodeBatchJobRetry(t *testing.T) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobRetry Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobRetry{}
|
||||||
|
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 BenchmarkEncodeBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
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 BenchmarkDecodeBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE
|
||||||
|
|
||||||
|
// replicate:
|
||||||
|
// # source of the objects to be replicated
|
||||||
|
// source:
|
||||||
|
// type: "minio"
|
||||||
|
// bucket: "testbucket"
|
||||||
|
// prefix: "spark/"
|
||||||
|
//
|
||||||
|
// # optional flags based filtering criteria
|
||||||
|
// # for source objects
|
||||||
|
// flags:
|
||||||
|
// filter:
|
||||||
|
// newerThan: "7d"
|
||||||
|
// olderThan: "7d"
|
||||||
|
// createdAfter: "date"
|
||||||
|
// createdBefore: "date"
|
||||||
|
// tags:
|
||||||
|
// - key: "name"
|
||||||
|
// value: "value*"
|
||||||
|
// metadata:
|
||||||
|
// - key: "content-type"
|
||||||
|
// value: "image/*"
|
||||||
|
// notify:
|
||||||
|
// endpoint: "https://splunk-hec.dev.com"
|
||||||
|
// token: "Splunk ..." # e.g. "Bearer token"
|
||||||
|
//
|
||||||
|
// # target where the objects must be replicated
|
||||||
|
// target:
|
||||||
|
// type: "minio"
|
||||||
|
// bucket: "testbucket1"
|
||||||
|
// endpoint: "https://play.min.io"
|
||||||
|
// path: "on"
|
||||||
|
// credentials:
|
||||||
|
// accessKey: "minioadmin"
|
||||||
|
// secretKey: "minioadmin"
|
||||||
|
// sessionToken: ""
|
||||||
|
|
||||||
|
// BatchReplicateFilter holds all the filters currently supported for batch replication
|
||||||
|
type BatchReplicateFilter struct {
|
||||||
|
NewerThan time.Duration `yaml:"newerThan,omitempty" json:"newerThan"`
|
||||||
|
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
||||||
|
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
||||||
|
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
||||||
|
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
||||||
|
Metadata []BatchJobKV `yaml:"metadata,omitempty" json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateFlags various configurations for replication job definition currently includes
|
||||||
|
// - filter
|
||||||
|
// - notify
|
||||||
|
// - retry
|
||||||
|
type BatchJobReplicateFlags struct {
|
||||||
|
Filter BatchReplicateFilter `yaml:"filter" json:"filter"`
|
||||||
|
Notify BatchJobNotification `yaml:"notify" json:"notify"`
|
||||||
|
Retry BatchJobRetry `yaml:"retry" json:"retry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateResourceType defines the type of batch jobs
|
||||||
|
type BatchJobReplicateResourceType string
|
||||||
|
|
||||||
|
// Validate validates if the replicate resource type is recognized and supported
|
||||||
|
func (t BatchJobReplicateResourceType) Validate() error {
|
||||||
|
switch t {
|
||||||
|
case BatchJobReplicateResourceMinIO:
|
||||||
|
case BatchJobReplicateResourceS3:
|
||||||
|
default:
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t BatchJobReplicateResourceType) isMinio() bool {
|
||||||
|
return t == BatchJobReplicateResourceMinIO
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different types of batch jobs..
|
||||||
|
const (
|
||||||
|
BatchJobReplicateResourceMinIO BatchJobReplicateResourceType = "minio"
|
||||||
|
BatchJobReplicateResourceS3 BatchJobReplicateResourceType = "s3"
|
||||||
|
|
||||||
|
// add future targets
|
||||||
|
)
|
||||||
|
|
||||||
|
// BatchJobReplicateCredentials access credentials for batch replication it may
|
||||||
|
// be either for target or source.
|
||||||
|
type BatchJobReplicateCredentials struct {
|
||||||
|
AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty" yaml:"accessKey"`
|
||||||
|
SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty" yaml:"secretKey"`
|
||||||
|
SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty" yaml:"sessionToken"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty indicates if credentials are not set
|
||||||
|
func (c BatchJobReplicateCredentials) Empty() bool {
|
||||||
|
return c.AccessKey == "" && c.SecretKey == "" && c.SessionToken == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates if credentials are valid
|
||||||
|
func (c BatchJobReplicateCredentials) Validate() error {
|
||||||
|
if !auth.IsAccessKeyValid(c.AccessKey) || !auth.IsSecretKeyValid(c.SecretKey) {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateTarget describes target element of the replication job that receives
|
||||||
|
// the filtered data from source
|
||||||
|
type BatchJobReplicateTarget struct {
|
||||||
|
Type BatchJobReplicateResourceType `yaml:"type" json:"type"`
|
||||||
|
Bucket string `yaml:"bucket" json:"bucket"`
|
||||||
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Path string `yaml:"path" json:"path"`
|
||||||
|
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidPath returns true if path is valid
|
||||||
|
func (t BatchJobReplicateTarget) ValidPath() bool {
|
||||||
|
return t.Path == "on" || t.Path == "off" || t.Path == "auto" || t.Path == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateSource describes source element of the replication job that is
|
||||||
|
// the source of the data for the target
|
||||||
|
type BatchJobReplicateSource struct {
|
||||||
|
Type BatchJobReplicateResourceType `yaml:"type" json:"type"`
|
||||||
|
Bucket string `yaml:"bucket" json:"bucket"`
|
||||||
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Path string `yaml:"path" json:"path"`
|
||||||
|
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidPath returns true if path is valid
|
||||||
|
func (s BatchJobReplicateSource) ValidPath() bool {
|
||||||
|
switch s.Path {
|
||||||
|
case "on", "off", "auto", "":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateV1 v1 of batch job replication
|
||||||
|
type BatchJobReplicateV1 struct {
|
||||||
|
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
|
||||||
|
Flags BatchJobReplicateFlags `yaml:"flags" json:"flags"`
|
||||||
|
Target BatchJobReplicateTarget `yaml:"target" json:"target"`
|
||||||
|
Source BatchJobReplicateSource `yaml:"source" json:"source"`
|
||||||
|
|
||||||
|
clnt *miniogo.Core `msg:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteToLocal returns true if source is remote and target is local
|
||||||
|
func (r BatchJobReplicateV1) RemoteToLocal() bool {
|
||||||
|
return !r.Source.Creds.Empty()
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,688 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobReplicateCredentials(t *testing.T) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
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 TestEncodeDecodeBatchJobReplicateCredentials(t *testing.T) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateCredentials Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateCredentials{}
|
||||||
|
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 BenchmarkEncodeBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
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 BenchmarkDecodeBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
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 TestMarshalUnmarshalBatchJobReplicateFlags(t *testing.T) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
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 TestEncodeDecodeBatchJobReplicateFlags(t *testing.T) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateFlags Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateFlags{}
|
||||||
|
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 BenchmarkEncodeBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
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 BenchmarkDecodeBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
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 TestMarshalUnmarshalBatchJobReplicateSource(t *testing.T) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
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 TestEncodeDecodeBatchJobReplicateSource(t *testing.T) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateSource Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateSource{}
|
||||||
|
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 BenchmarkEncodeBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
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 BenchmarkDecodeBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
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 TestMarshalUnmarshalBatchJobReplicateTarget(t *testing.T) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
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 TestEncodeDecodeBatchJobReplicateTarget(t *testing.T) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateTarget Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateTarget{}
|
||||||
|
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 BenchmarkEncodeBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
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 BenchmarkDecodeBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
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 TestMarshalUnmarshalBatchJobReplicateV1(t *testing.T) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
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 BenchmarkMarshalMsgBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
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 BenchmarkUnmarshalBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
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 TestEncodeDecodeBatchJobReplicateV1(t *testing.T) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateV1 Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateV1{}
|
||||||
|
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 BenchmarkEncodeBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
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 BenchmarkDecodeBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
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 TestMarshalUnmarshalBatchReplicateFilter(t *testing.T) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
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 BenchmarkMarshalMsgBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
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 BenchmarkUnmarshalBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
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 TestEncodeDecodeBatchReplicateFilter(t *testing.T) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchReplicateFilter Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchReplicateFilter{}
|
||||||
|
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 BenchmarkEncodeBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
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 BenchmarkDecodeBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
-105
@@ -18,13 +18,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -38,9 +34,8 @@ import (
|
|||||||
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/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/v2/workers"
|
||||||
"github.com/minio/pkg/workers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// keyrotate:
|
// keyrotate:
|
||||||
@@ -76,56 +71,6 @@ import (
|
|||||||
|
|
||||||
//go:generate msgp -file $GOFILE -unexported
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
|
|
||||||
// BatchKeyRotateKV is a datatype that holds key and values for filtering of objects
|
|
||||||
// used by metadata filter as well as tags based filtering.
|
|
||||||
type BatchKeyRotateKV struct {
|
|
||||||
Key string `yaml:"key" json:"key"`
|
|
||||||
Value string `yaml:"value" json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate returns an error if key is empty
|
|
||||||
func (kv BatchKeyRotateKV) Validate() error {
|
|
||||||
if kv.Key == "" {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty indicates if kv is not set
|
|
||||||
func (kv BatchKeyRotateKV) Empty() bool {
|
|
||||||
return kv.Key == "" && kv.Value == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match matches input kv with kv, value will be wildcard matched depending on the user input
|
|
||||||
func (kv BatchKeyRotateKV) Match(ikv BatchKeyRotateKV) bool {
|
|
||||||
if kv.Empty() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.EqualFold(kv.Key, ikv.Key) {
|
|
||||||
return wildcard.Match(kv.Value, ikv.Value)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchKeyRotateRetry datatype represents total retry attempts and delay between each retries.
|
|
||||||
type BatchKeyRotateRetry struct {
|
|
||||||
Attempts int `yaml:"attempts" json:"attempts"` // number of retry attempts
|
|
||||||
Delay time.Duration `yaml:"delay" json:"delay"` // delay between each retries
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates input replicate retries.
|
|
||||||
func (r BatchKeyRotateRetry) Validate() error {
|
|
||||||
if r.Attempts < 0 {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Delay < 0 {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchKeyRotationType defines key rotation type
|
// BatchKeyRotationType defines key rotation type
|
||||||
type BatchKeyRotationType string
|
type BatchKeyRotationType string
|
||||||
|
|
||||||
@@ -182,8 +127,8 @@ type BatchKeyRotateFilter struct {
|
|||||||
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
||||||
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
||||||
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
||||||
Tags []BatchKeyRotateKV `yaml:"tags,omitempty" json:"tags"`
|
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
||||||
Metadata []BatchKeyRotateKV `yaml:"metadata,omitempty" json:"metadata"`
|
Metadata []BatchJobKV `yaml:"metadata,omitempty" json:"metadata"`
|
||||||
KMSKeyID string `yaml:"kmskeyid" json:"kmskey"`
|
KMSKeyID string `yaml:"kmskeyid" json:"kmskey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,8 +144,8 @@ type BatchKeyRotateNotification struct {
|
|||||||
// - retry
|
// - retry
|
||||||
type BatchJobKeyRotateFlags struct {
|
type BatchJobKeyRotateFlags struct {
|
||||||
Filter BatchKeyRotateFilter `yaml:"filter" json:"filter"`
|
Filter BatchKeyRotateFilter `yaml:"filter" json:"filter"`
|
||||||
Notify BatchKeyRotateNotification `yaml:"notify" json:"notify"`
|
Notify BatchJobNotification `yaml:"notify" json:"notify"`
|
||||||
Retry BatchKeyRotateRetry `yaml:"retry" json:"retry"`
|
Retry BatchJobRetry `yaml:"retry" json:"retry"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchJobKeyRotateV1 v1 of batch key rotation job
|
// BatchJobKeyRotateV1 v1 of batch key rotation job
|
||||||
@@ -214,35 +159,8 @@ type BatchJobKeyRotateV1 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notify notifies notification endpoint if configured regarding job failure or success.
|
// Notify notifies notification endpoint if configured regarding job failure or success.
|
||||||
func (r BatchJobKeyRotateV1) Notify(ctx context.Context, body io.Reader) error {
|
func (r BatchJobKeyRotateV1) Notify(ctx context.Context, ri *batchJobInfo) error {
|
||||||
if r.Flags.Notify.Endpoint == "" {
|
return notifyEndpoint(ctx, ri, r.Flags.Notify.Endpoint, r.Flags.Notify.Token)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.Flags.Notify.Endpoint, body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Flags.Notify.Token != "" {
|
|
||||||
req.Header.Set("Authorization", r.Flags.Notify.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
clnt := http.Client{Transport: getRemoteInstanceTransport}
|
|
||||||
resp, err := clnt.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
xhttp.DrainBody(resp.Body)
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return errors.New(resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyRotate rotates encryption key of an object
|
// KeyRotate rotates encryption key of an object
|
||||||
@@ -289,7 +207,7 @@ func (r *BatchJobKeyRotateV1) KeyRotate(ctx context.Context, api ObjectLayer, ob
|
|||||||
)
|
)
|
||||||
encMetadata := make(map[string]string)
|
encMetadata := make(map[string]string)
|
||||||
for k, v := range oi.UserDefined {
|
for k, v := range oi.UserDefined {
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
encMetadata[k] = v
|
encMetadata[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,7 +248,7 @@ const (
|
|||||||
batchKeyRotateVersion = batchKeyRotateVersionV1
|
batchKeyRotateVersion = batchKeyRotateVersionV1
|
||||||
batchKeyRotateAPIVersion = "v1"
|
batchKeyRotateAPIVersion = "v1"
|
||||||
batchKeyRotateJobDefaultRetries = 3
|
batchKeyRotateJobDefaultRetries = 3
|
||||||
batchKeyRotateJobDefaultRetryDelay = 250 * time.Millisecond
|
batchKeyRotateJobDefaultRetryDelay = 25 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start the batch key rottion job, resumes if there was a pending job via "job.ID"
|
// Start the batch key rottion job, resumes if there was a pending job via "job.ID"
|
||||||
@@ -351,6 +269,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if delay == 0 {
|
if delay == 0 {
|
||||||
delay = batchKeyRotateJobDefaultRetryDelay
|
delay = batchKeyRotateJobDefaultRetryDelay
|
||||||
}
|
}
|
||||||
|
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
skip := func(info FileInfo) (ok bool) {
|
skip := func(info FileInfo) (ok bool) {
|
||||||
@@ -388,7 +307,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
|
|
||||||
for _, kv := range r.Flags.Filter.Tags {
|
for _, kv := range r.Flags.Filter.Tags {
|
||||||
for t, v := range tagMap {
|
for t, v := range tagMap {
|
||||||
if kv.Match(BatchKeyRotateKV{Key: t, Value: v}) {
|
if kv.Match(BatchJobKV{Key: t, Value: v}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,11 +320,11 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if len(r.Flags.Filter.Metadata) > 0 {
|
if len(r.Flags.Filter.Metadata) > 0 {
|
||||||
for _, kv := range r.Flags.Filter.Metadata {
|
for _, kv := range r.Flags.Filter.Metadata {
|
||||||
for k, v := range info.Metadata {
|
for k, v := range info.Metadata {
|
||||||
if !strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") && !isStandardHeader(k) {
|
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// We only need to match x-amz-meta or standardHeaders
|
// We only need to match x-amz-meta or standardHeaders
|
||||||
if kv.Match(BatchKeyRotateKV{Key: k, Value: v}) {
|
if kv.Match(BatchJobKV{Key: k, Value: v}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,6 +394,9 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if success {
|
if success {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if delay > 0 {
|
||||||
|
time.Sleep(delay + time.Duration(rnd.Float64()*float64(delay)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -486,20 +408,11 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
|
||||||
buf, _ := json.Marshal(ri)
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
if err := r.Notify(ctx, bytes.NewReader(buf)); err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
if ri.Failed {
|
|
||||||
ri.ObjectsFailed = 0
|
|
||||||
ri.Bucket = ""
|
|
||||||
ri.Object = ""
|
|
||||||
ri.Objects = 0
|
|
||||||
time.Sleep(delay + time.Duration(rnd.Float64()*float64(delay)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+171
-618
@@ -192,75 +192,17 @@ func (z *BatchJobKeyRotateFlags) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Notify":
|
case "Notify":
|
||||||
var zb0002 uint32
|
err = z.Notify.DecodeMsg(dc)
|
||||||
zb0002, err = dc.ReadMapHeader()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Notify")
|
err = msgp.WrapError(err, "Notify")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0002 > 0 {
|
|
||||||
zb0002--
|
|
||||||
field, err = dc.ReadMapKeyPtr()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Endpoint":
|
|
||||||
z.Notify.Endpoint, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify", "Endpoint")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Token":
|
|
||||||
z.Notify.Token, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify", "Token")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = dc.Skip()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "Retry":
|
case "Retry":
|
||||||
var zb0003 uint32
|
err = z.Retry.DecodeMsg(dc)
|
||||||
zb0003, err = dc.ReadMapHeader()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Retry")
|
err = msgp.WrapError(err, "Retry")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0003 > 0 {
|
|
||||||
zb0003--
|
|
||||||
field, err = dc.ReadMapKeyPtr()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Attempts":
|
|
||||||
z.Retry.Attempts, err = dc.ReadInt()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry", "Attempts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Delay":
|
|
||||||
z.Retry.Delay, err = dc.ReadDuration()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry", "Delay")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = dc.Skip()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,25 +232,9 @@ func (z *BatchJobKeyRotateFlags) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// map header, size 2
|
err = z.Notify.EncodeMsg(en)
|
||||||
// write "Endpoint"
|
|
||||||
err = en.Append(0x82, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "Notify")
|
||||||
}
|
|
||||||
err = en.WriteString(z.Notify.Endpoint)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify", "Endpoint")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Token"
|
|
||||||
err = en.Append(0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Notify.Token)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify", "Token")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Retry"
|
// write "Retry"
|
||||||
@@ -316,25 +242,9 @@ func (z *BatchJobKeyRotateFlags) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// map header, size 2
|
err = z.Retry.EncodeMsg(en)
|
||||||
// write "Attempts"
|
|
||||||
err = en.Append(0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "Retry")
|
||||||
}
|
|
||||||
err = en.WriteInt(z.Retry.Attempts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry", "Attempts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Delay"
|
|
||||||
err = en.Append(0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteDuration(z.Retry.Delay)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry", "Delay")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -353,22 +263,18 @@ func (z *BatchJobKeyRotateFlags) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
// string "Notify"
|
// string "Notify"
|
||||||
o = append(o, 0xa6, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79)
|
o = append(o, 0xa6, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79)
|
||||||
// map header, size 2
|
o, err = z.Notify.MarshalMsg(o)
|
||||||
// string "Endpoint"
|
if err != nil {
|
||||||
o = append(o, 0x82, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
err = msgp.WrapError(err, "Notify")
|
||||||
o = msgp.AppendString(o, z.Notify.Endpoint)
|
return
|
||||||
// string "Token"
|
}
|
||||||
o = append(o, 0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
|
||||||
o = msgp.AppendString(o, z.Notify.Token)
|
|
||||||
// string "Retry"
|
// string "Retry"
|
||||||
o = append(o, 0xa5, 0x52, 0x65, 0x74, 0x72, 0x79)
|
o = append(o, 0xa5, 0x52, 0x65, 0x74, 0x72, 0x79)
|
||||||
// map header, size 2
|
o, err = z.Retry.MarshalMsg(o)
|
||||||
// string "Attempts"
|
if err != nil {
|
||||||
o = append(o, 0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
err = msgp.WrapError(err, "Retry")
|
||||||
o = msgp.AppendInt(o, z.Retry.Attempts)
|
return
|
||||||
// string "Delay"
|
}
|
||||||
o = append(o, 0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
|
||||||
o = msgp.AppendDuration(o, z.Retry.Delay)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,75 +303,17 @@ func (z *BatchJobKeyRotateFlags) UnmarshalMsg(bts []byte) (o []byte, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Notify":
|
case "Notify":
|
||||||
var zb0002 uint32
|
bts, err = z.Notify.UnmarshalMsg(bts)
|
||||||
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Notify")
|
err = msgp.WrapError(err, "Notify")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0002 > 0 {
|
|
||||||
zb0002--
|
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Endpoint":
|
|
||||||
z.Notify.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify", "Endpoint")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Token":
|
|
||||||
z.Notify.Token, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify", "Token")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
bts, err = msgp.Skip(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Notify")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "Retry":
|
case "Retry":
|
||||||
var zb0003 uint32
|
bts, err = z.Retry.UnmarshalMsg(bts)
|
||||||
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Retry")
|
err = msgp.WrapError(err, "Retry")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0003 > 0 {
|
|
||||||
zb0003--
|
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Attempts":
|
|
||||||
z.Retry.Attempts, bts, err = msgp.ReadIntBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry", "Attempts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Delay":
|
|
||||||
z.Retry.Delay, bts, err = msgp.ReadDurationBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry", "Delay")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
bts, err = msgp.Skip(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Retry")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -480,7 +328,7 @@ func (z *BatchJobKeyRotateFlags) 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 *BatchJobKeyRotateFlags) Msgsize() (s int) {
|
func (z *BatchJobKeyRotateFlags) Msgsize() (s int) {
|
||||||
s = 1 + 7 + z.Filter.Msgsize() + 7 + 1 + 9 + msgp.StringPrefixSize + len(z.Notify.Endpoint) + 6 + msgp.StringPrefixSize + len(z.Notify.Token) + 6 + 1 + 9 + msgp.IntSize + 6 + msgp.DurationSize
|
s = 1 + 7 + z.Filter.Msgsize() + 7 + z.Notify.Msgsize() + 6 + z.Retry.Msgsize()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,11 +357,46 @@ func (z *BatchJobKeyRotateV1) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Flags":
|
case "Flags":
|
||||||
err = z.Flags.DecodeMsg(dc)
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadMapHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Flags")
|
err = msgp.WrapError(err, "Flags")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for zb0002 > 0 {
|
||||||
|
zb0002--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Filter":
|
||||||
|
err = z.Flags.Filter.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Filter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Notify":
|
||||||
|
err = z.Flags.Notify.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Notify")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Retry":
|
||||||
|
err = z.Flags.Retry.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Retry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
case "Bucket":
|
case "Bucket":
|
||||||
z.Bucket, err = dc.ReadString()
|
z.Bucket, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -567,9 +450,35 @@ func (z *BatchJobKeyRotateV1) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = z.Flags.EncodeMsg(en)
|
// map header, size 3
|
||||||
|
// write "Filter"
|
||||||
|
err = en.Append(0x83, 0xa6, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Flags")
|
return
|
||||||
|
}
|
||||||
|
err = z.Flags.Filter.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Filter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Notify"
|
||||||
|
err = en.Append(0xa6, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = z.Flags.Notify.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Notify")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Retry"
|
||||||
|
err = en.Append(0xa5, 0x52, 0x65, 0x74, 0x72, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = z.Flags.Retry.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Retry")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Bucket"
|
// write "Bucket"
|
||||||
@@ -624,9 +533,26 @@ func (z *BatchJobKeyRotateV1) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
o = msgp.AppendString(o, z.APIVersion)
|
o = msgp.AppendString(o, z.APIVersion)
|
||||||
// string "Flags"
|
// string "Flags"
|
||||||
o = append(o, 0xa5, 0x46, 0x6c, 0x61, 0x67, 0x73)
|
o = append(o, 0xa5, 0x46, 0x6c, 0x61, 0x67, 0x73)
|
||||||
o, err = z.Flags.MarshalMsg(o)
|
// map header, size 3
|
||||||
|
// string "Filter"
|
||||||
|
o = append(o, 0x83, 0xa6, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72)
|
||||||
|
o, err = z.Flags.Filter.MarshalMsg(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Flags")
|
err = msgp.WrapError(err, "Flags", "Filter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "Notify"
|
||||||
|
o = append(o, 0xa6, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79)
|
||||||
|
o, err = z.Flags.Notify.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Notify")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "Retry"
|
||||||
|
o = append(o, 0xa5, 0x52, 0x65, 0x74, 0x72, 0x79)
|
||||||
|
o, err = z.Flags.Retry.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Retry")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// string "Bucket"
|
// string "Bucket"
|
||||||
@@ -673,11 +599,46 @@ func (z *BatchJobKeyRotateV1) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Flags":
|
case "Flags":
|
||||||
bts, err = z.Flags.UnmarshalMsg(bts)
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Flags")
|
err = msgp.WrapError(err, "Flags")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for zb0002 > 0 {
|
||||||
|
zb0002--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Filter":
|
||||||
|
bts, err = z.Flags.Filter.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Filter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Notify":
|
||||||
|
bts, err = z.Flags.Notify.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Notify")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Retry":
|
||||||
|
bts, err = z.Flags.Retry.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags", "Retry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Flags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
case "Bucket":
|
case "Bucket":
|
||||||
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -716,7 +677,7 @@ func (z *BatchJobKeyRotateV1) 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 *BatchJobKeyRotateV1) Msgsize() (s int) {
|
func (z *BatchJobKeyRotateV1) Msgsize() (s int) {
|
||||||
s = 1 + 11 + msgp.StringPrefixSize + len(z.APIVersion) + 6 + z.Flags.Msgsize() + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 11 + z.Encryption.Msgsize()
|
s = 1 + 11 + msgp.StringPrefixSize + len(z.APIVersion) + 6 + 1 + 7 + z.Flags.Filter.Msgsize() + 7 + z.Flags.Notify.Msgsize() + 6 + z.Flags.Retry.Msgsize() + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 11 + z.Encryption.Msgsize()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,91 +733,33 @@ func (z *BatchKeyRotateFilter) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
if cap(z.Tags) >= int(zb0002) {
|
if cap(z.Tags) >= int(zb0002) {
|
||||||
z.Tags = (z.Tags)[:zb0002]
|
z.Tags = (z.Tags)[:zb0002]
|
||||||
} else {
|
} else {
|
||||||
z.Tags = make([]BatchKeyRotateKV, zb0002)
|
z.Tags = make([]BatchJobKV, zb0002)
|
||||||
}
|
}
|
||||||
for za0001 := range z.Tags {
|
for za0001 := range z.Tags {
|
||||||
var zb0003 uint32
|
err = z.Tags[za0001].DecodeMsg(dc)
|
||||||
zb0003, err = dc.ReadMapHeader()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Tags", za0001)
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0003 > 0 {
|
|
||||||
zb0003--
|
|
||||||
field, err = dc.ReadMapKeyPtr()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Key":
|
|
||||||
z.Tags[za0001].Key, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Value":
|
|
||||||
z.Tags[za0001].Value, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001, "Value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = dc.Skip()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "Metadata":
|
case "Metadata":
|
||||||
var zb0004 uint32
|
var zb0003 uint32
|
||||||
zb0004, err = dc.ReadArrayHeader()
|
zb0003, err = dc.ReadArrayHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Metadata")
|
err = msgp.WrapError(err, "Metadata")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cap(z.Metadata) >= int(zb0004) {
|
if cap(z.Metadata) >= int(zb0003) {
|
||||||
z.Metadata = (z.Metadata)[:zb0004]
|
z.Metadata = (z.Metadata)[:zb0003]
|
||||||
} else {
|
} else {
|
||||||
z.Metadata = make([]BatchKeyRotateKV, zb0004)
|
z.Metadata = make([]BatchJobKV, zb0003)
|
||||||
}
|
}
|
||||||
for za0002 := range z.Metadata {
|
for za0002 := range z.Metadata {
|
||||||
var zb0005 uint32
|
err = z.Metadata[za0002].DecodeMsg(dc)
|
||||||
zb0005, err = dc.ReadMapHeader()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Metadata", za0002)
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0005 > 0 {
|
|
||||||
zb0005--
|
|
||||||
field, err = dc.ReadMapKeyPtr()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Key":
|
|
||||||
z.Metadata[za0002].Key, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Value":
|
|
||||||
z.Metadata[za0002].Value, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002, "Value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = dc.Skip()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "KMSKeyID":
|
case "KMSKeyID":
|
||||||
z.KMSKeyID, err = dc.ReadString()
|
z.KMSKeyID, err = dc.ReadString()
|
||||||
@@ -929,25 +832,9 @@ func (z *BatchKeyRotateFilter) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for za0001 := range z.Tags {
|
for za0001 := range z.Tags {
|
||||||
// map header, size 2
|
err = z.Tags[za0001].EncodeMsg(en)
|
||||||
// write "Key"
|
|
||||||
err = en.Append(0x82, 0xa3, 0x4b, 0x65, 0x79)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
}
|
|
||||||
err = en.WriteString(z.Tags[za0001].Key)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Value"
|
|
||||||
err = en.Append(0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Tags[za0001].Value)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001, "Value")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -962,25 +849,9 @@ func (z *BatchKeyRotateFilter) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for za0002 := range z.Metadata {
|
for za0002 := range z.Metadata {
|
||||||
// map header, size 2
|
err = z.Metadata[za0002].EncodeMsg(en)
|
||||||
// write "Key"
|
|
||||||
err = en.Append(0x82, 0xa3, 0x4b, 0x65, 0x79)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
}
|
|
||||||
err = en.WriteString(z.Metadata[za0002].Key)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Value"
|
|
||||||
err = en.Append(0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Metadata[za0002].Value)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002, "Value")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1017,25 +888,21 @@ func (z *BatchKeyRotateFilter) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
o = append(o, 0xa4, 0x54, 0x61, 0x67, 0x73)
|
o = append(o, 0xa4, 0x54, 0x61, 0x67, 0x73)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(len(z.Tags)))
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Tags)))
|
||||||
for za0001 := range z.Tags {
|
for za0001 := range z.Tags {
|
||||||
// map header, size 2
|
o, err = z.Tags[za0001].MarshalMsg(o)
|
||||||
// string "Key"
|
if err != nil {
|
||||||
o = append(o, 0x82, 0xa3, 0x4b, 0x65, 0x79)
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
o = msgp.AppendString(o, z.Tags[za0001].Key)
|
return
|
||||||
// string "Value"
|
}
|
||||||
o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
|
||||||
o = msgp.AppendString(o, z.Tags[za0001].Value)
|
|
||||||
}
|
}
|
||||||
// string "Metadata"
|
// string "Metadata"
|
||||||
o = append(o, 0xa8, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61)
|
o = append(o, 0xa8, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata)))
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata)))
|
||||||
for za0002 := range z.Metadata {
|
for za0002 := range z.Metadata {
|
||||||
// map header, size 2
|
o, err = z.Metadata[za0002].MarshalMsg(o)
|
||||||
// string "Key"
|
if err != nil {
|
||||||
o = append(o, 0x82, 0xa3, 0x4b, 0x65, 0x79)
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
o = msgp.AppendString(o, z.Metadata[za0002].Key)
|
return
|
||||||
// string "Value"
|
}
|
||||||
o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
|
||||||
o = msgp.AppendString(o, z.Metadata[za0002].Value)
|
|
||||||
}
|
}
|
||||||
// string "KMSKeyID"
|
// string "KMSKeyID"
|
||||||
o = append(o, 0xa8, 0x4b, 0x4d, 0x53, 0x4b, 0x65, 0x79, 0x49, 0x44)
|
o = append(o, 0xa8, 0x4b, 0x4d, 0x53, 0x4b, 0x65, 0x79, 0x49, 0x44)
|
||||||
@@ -1095,91 +962,33 @@ func (z *BatchKeyRotateFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
if cap(z.Tags) >= int(zb0002) {
|
if cap(z.Tags) >= int(zb0002) {
|
||||||
z.Tags = (z.Tags)[:zb0002]
|
z.Tags = (z.Tags)[:zb0002]
|
||||||
} else {
|
} else {
|
||||||
z.Tags = make([]BatchKeyRotateKV, zb0002)
|
z.Tags = make([]BatchJobKV, zb0002)
|
||||||
}
|
}
|
||||||
for za0001 := range z.Tags {
|
for za0001 := range z.Tags {
|
||||||
var zb0003 uint32
|
bts, err = z.Tags[za0001].UnmarshalMsg(bts)
|
||||||
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Tags", za0001)
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0003 > 0 {
|
|
||||||
zb0003--
|
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Key":
|
|
||||||
z.Tags[za0001].Key, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Value":
|
|
||||||
z.Tags[za0001].Value, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001, "Value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
bts, err = msgp.Skip(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Tags", za0001)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "Metadata":
|
case "Metadata":
|
||||||
var zb0004 uint32
|
var zb0003 uint32
|
||||||
zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Metadata")
|
err = msgp.WrapError(err, "Metadata")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cap(z.Metadata) >= int(zb0004) {
|
if cap(z.Metadata) >= int(zb0003) {
|
||||||
z.Metadata = (z.Metadata)[:zb0004]
|
z.Metadata = (z.Metadata)[:zb0003]
|
||||||
} else {
|
} else {
|
||||||
z.Metadata = make([]BatchKeyRotateKV, zb0004)
|
z.Metadata = make([]BatchJobKV, zb0003)
|
||||||
}
|
}
|
||||||
for za0002 := range z.Metadata {
|
for za0002 := range z.Metadata {
|
||||||
var zb0005 uint32
|
bts, err = z.Metadata[za0002].UnmarshalMsg(bts)
|
||||||
zb0005, bts, err = msgp.ReadMapHeaderBytes(bts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Metadata", za0002)
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for zb0005 > 0 {
|
|
||||||
zb0005--
|
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msgp.UnsafeString(field) {
|
|
||||||
case "Key":
|
|
||||||
z.Metadata[za0002].Key, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Value":
|
|
||||||
z.Metadata[za0002].Value, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002, "Value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
bts, err = msgp.Skip(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Metadata", za0002)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "KMSKeyID":
|
case "KMSKeyID":
|
||||||
z.KMSKeyID, bts, err = msgp.ReadStringBytes(bts)
|
z.KMSKeyID, bts, err = msgp.ReadStringBytes(bts)
|
||||||
@@ -1203,144 +1012,16 @@ func (z *BatchKeyRotateFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
func (z *BatchKeyRotateFilter) Msgsize() (s int) {
|
func (z *BatchKeyRotateFilter) Msgsize() (s int) {
|
||||||
s = 1 + 10 + msgp.DurationSize + 10 + msgp.DurationSize + 13 + msgp.TimeSize + 14 + msgp.TimeSize + 5 + msgp.ArrayHeaderSize
|
s = 1 + 10 + msgp.DurationSize + 10 + msgp.DurationSize + 13 + msgp.TimeSize + 14 + msgp.TimeSize + 5 + msgp.ArrayHeaderSize
|
||||||
for za0001 := range z.Tags {
|
for za0001 := range z.Tags {
|
||||||
s += 1 + 4 + msgp.StringPrefixSize + len(z.Tags[za0001].Key) + 6 + msgp.StringPrefixSize + len(z.Tags[za0001].Value)
|
s += z.Tags[za0001].Msgsize()
|
||||||
}
|
}
|
||||||
s += 9 + msgp.ArrayHeaderSize
|
s += 9 + msgp.ArrayHeaderSize
|
||||||
for za0002 := range z.Metadata {
|
for za0002 := range z.Metadata {
|
||||||
s += 1 + 4 + msgp.StringPrefixSize + len(z.Metadata[za0002].Key) + 6 + msgp.StringPrefixSize + len(z.Metadata[za0002].Value)
|
s += z.Metadata[za0002].Msgsize()
|
||||||
}
|
}
|
||||||
s += 9 + msgp.StringPrefixSize + len(z.KMSKeyID)
|
s += 9 + msgp.StringPrefixSize + len(z.KMSKeyID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
|
||||||
func (z *BatchKeyRotateKV) 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 "Key":
|
|
||||||
z.Key, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Value":
|
|
||||||
z.Value, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = dc.Skip()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
|
||||||
func (z BatchKeyRotateKV) EncodeMsg(en *msgp.Writer) (err error) {
|
|
||||||
// map header, size 2
|
|
||||||
// write "Key"
|
|
||||||
err = en.Append(0x82, 0xa3, 0x4b, 0x65, 0x79)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Key)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Value"
|
|
||||||
err = en.Append(0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Value)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
|
||||||
func (z BatchKeyRotateKV) MarshalMsg(b []byte) (o []byte, err error) {
|
|
||||||
o = msgp.Require(b, z.Msgsize())
|
|
||||||
// map header, size 2
|
|
||||||
// string "Key"
|
|
||||||
o = append(o, 0x82, 0xa3, 0x4b, 0x65, 0x79)
|
|
||||||
o = msgp.AppendString(o, z.Key)
|
|
||||||
// string "Value"
|
|
||||||
o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
|
||||||
o = msgp.AppendString(o, z.Value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalMsg implements msgp.Unmarshaler
|
|
||||||
func (z *BatchKeyRotateKV) 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 "Key":
|
|
||||||
z.Key, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Value":
|
|
||||||
z.Value, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Value")
|
|
||||||
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 BatchKeyRotateKV) Msgsize() (s int) {
|
|
||||||
s = 1 + 4 + msgp.StringPrefixSize + len(z.Key) + 6 + msgp.StringPrefixSize + len(z.Value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *BatchKeyRotateNotification) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *BatchKeyRotateNotification) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
var field []byte
|
var field []byte
|
||||||
@@ -1469,134 +1150,6 @@ func (z BatchKeyRotateNotification) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
|
||||||
func (z *BatchKeyRotateRetry) 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 "Attempts":
|
|
||||||
z.Attempts, err = dc.ReadInt()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Attempts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Delay":
|
|
||||||
z.Delay, err = dc.ReadDuration()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Delay")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = dc.Skip()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
|
||||||
func (z BatchKeyRotateRetry) EncodeMsg(en *msgp.Writer) (err error) {
|
|
||||||
// map header, size 2
|
|
||||||
// write "Attempts"
|
|
||||||
err = en.Append(0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteInt(z.Attempts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Attempts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Delay"
|
|
||||||
err = en.Append(0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteDuration(z.Delay)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Delay")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
|
||||||
func (z BatchKeyRotateRetry) MarshalMsg(b []byte) (o []byte, err error) {
|
|
||||||
o = msgp.Require(b, z.Msgsize())
|
|
||||||
// map header, size 2
|
|
||||||
// string "Attempts"
|
|
||||||
o = append(o, 0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
|
||||||
o = msgp.AppendInt(o, z.Attempts)
|
|
||||||
// string "Delay"
|
|
||||||
o = append(o, 0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
|
||||||
o = msgp.AppendDuration(o, z.Delay)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalMsg implements msgp.Unmarshaler
|
|
||||||
func (z *BatchKeyRotateRetry) 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 "Attempts":
|
|
||||||
z.Attempts, bts, err = msgp.ReadIntBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Attempts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Delay":
|
|
||||||
z.Delay, bts, err = msgp.ReadDurationBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Delay")
|
|
||||||
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 BatchKeyRotateRetry) Msgsize() (s int) {
|
|
||||||
s = 1 + 9 + msgp.IntSize + 6 + msgp.DurationSize
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *BatchKeyRotationType) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *BatchKeyRotationType) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -461,119 +461,6 @@ func BenchmarkDecodeBatchKeyRotateFilter(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalBatchKeyRotateKV(t *testing.T) {
|
|
||||||
v := BatchKeyRotateKV{}
|
|
||||||
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 BenchmarkMarshalMsgBatchKeyRotateKV(b *testing.B) {
|
|
||||||
v := BatchKeyRotateKV{}
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
v.MarshalMsg(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAppendMsgBatchKeyRotateKV(b *testing.B) {
|
|
||||||
v := BatchKeyRotateKV{}
|
|
||||||
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 BenchmarkUnmarshalBatchKeyRotateKV(b *testing.B) {
|
|
||||||
v := BatchKeyRotateKV{}
|
|
||||||
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 TestEncodeDecodeBatchKeyRotateKV(t *testing.T) {
|
|
||||||
v := BatchKeyRotateKV{}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
msgp.Encode(&buf, &v)
|
|
||||||
|
|
||||||
m := v.Msgsize()
|
|
||||||
if buf.Len() > m {
|
|
||||||
t.Log("WARNING: TestEncodeDecodeBatchKeyRotateKV Msgsize() is inaccurate")
|
|
||||||
}
|
|
||||||
|
|
||||||
vn := BatchKeyRotateKV{}
|
|
||||||
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 BenchmarkEncodeBatchKeyRotateKV(b *testing.B) {
|
|
||||||
v := BatchKeyRotateKV{}
|
|
||||||
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 BenchmarkDecodeBatchKeyRotateKV(b *testing.B) {
|
|
||||||
v := BatchKeyRotateKV{}
|
|
||||||
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 TestMarshalUnmarshalBatchKeyRotateNotification(t *testing.T) {
|
func TestMarshalUnmarshalBatchKeyRotateNotification(t *testing.T) {
|
||||||
v := BatchKeyRotateNotification{}
|
v := BatchKeyRotateNotification{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
@@ -686,116 +573,3 @@ func BenchmarkDecodeBatchKeyRotateNotification(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalBatchKeyRotateRetry(t *testing.T) {
|
|
||||||
v := BatchKeyRotateRetry{}
|
|
||||||
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 BenchmarkMarshalMsgBatchKeyRotateRetry(b *testing.B) {
|
|
||||||
v := BatchKeyRotateRetry{}
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
v.MarshalMsg(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAppendMsgBatchKeyRotateRetry(b *testing.B) {
|
|
||||||
v := BatchKeyRotateRetry{}
|
|
||||||
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 BenchmarkUnmarshalBatchKeyRotateRetry(b *testing.B) {
|
|
||||||
v := BatchKeyRotateRetry{}
|
|
||||||
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 TestEncodeDecodeBatchKeyRotateRetry(t *testing.T) {
|
|
||||||
v := BatchKeyRotateRetry{}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
msgp.Encode(&buf, &v)
|
|
||||||
|
|
||||||
m := v.Msgsize()
|
|
||||||
if buf.Len() > m {
|
|
||||||
t.Log("WARNING: TestEncodeDecodeBatchKeyRotateRetry Msgsize() is inaccurate")
|
|
||||||
}
|
|
||||||
|
|
||||||
vn := BatchKeyRotateRetry{}
|
|
||||||
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 BenchmarkEncodeBatchKeyRotateRetry(b *testing.B) {
|
|
||||||
v := BatchKeyRotateRetry{}
|
|
||||||
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 BenchmarkDecodeBatchKeyRotateRetry(b *testing.B) {
|
|
||||||
v := BatchKeyRotateRetry{}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
msgp.Encode(&buf, &v)
|
|
||||||
b.SetBytes(int64(buf.Len()))
|
|
||||||
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
|
||||||
dc := msgp.NewReader(rd)
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
err := v.DecodeMsg(dc)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+11
-4
@@ -94,16 +94,23 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
|
|||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
h := algo.New()
|
h := algo.New()
|
||||||
|
|
||||||
bw := &streamingBitrotWriter{iow: w, closeWithErr: w.CloseWithError, h: h, shardSize: shardSize, canClose: &sync.WaitGroup{}}
|
bw := &streamingBitrotWriter{
|
||||||
|
iow: ioutil.NewDeadlineWriter(w, diskMaxTimeout),
|
||||||
|
closeWithErr: w.CloseWithError,
|
||||||
|
h: h,
|
||||||
|
shardSize: shardSize,
|
||||||
|
canClose: &sync.WaitGroup{},
|
||||||
|
}
|
||||||
bw.canClose.Add(1)
|
bw.canClose.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer bw.canClose.Done()
|
||||||
|
|
||||||
totalFileSize := int64(-1) // For compressed objects length will be unknown (represented by length=-1)
|
totalFileSize := int64(-1) // For compressed objects length will be unknown (represented by length=-1)
|
||||||
if length != -1 {
|
if length != -1 {
|
||||||
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))
|
r.CloseWithError(disk.CreateFile(context.TODO(), volume, filePath, totalFileSize, r))
|
||||||
bw.canClose.Done()
|
|
||||||
}()
|
}()
|
||||||
return bw
|
return bw
|
||||||
}
|
}
|
||||||
@@ -165,9 +172,9 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
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 err != nil {
|
||||||
if !IsErr(err, ignoredErrs...) {
|
if !IsErr(err, ignoredErrs...) {
|
||||||
logger.LogIf(GlobalContext,
|
logger.LogOnceIf(GlobalContext,
|
||||||
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
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))
|
b.disk, b.volume, b.filePath, err), "bitrot-read-file-stream-"+b.volume+"-"+b.filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+13
-60
@@ -19,98 +19,51 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/pubsub"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bootstrapMsgsLimit = 4 << 10
|
const bootstrapTraceLimit = 4 << 10
|
||||||
|
|
||||||
type bootstrapInfo struct {
|
|
||||||
msg string
|
|
||||||
ts time.Time
|
|
||||||
source string
|
|
||||||
}
|
|
||||||
type bootstrapTracer struct {
|
type bootstrapTracer struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
idx int
|
info []madmin.TraceInfo
|
||||||
info [bootstrapMsgsLimit]bootstrapInfo
|
|
||||||
lastUpdate time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalBootstrapTracer = &bootstrapTracer{}
|
var globalBootstrapTracer = &bootstrapTracer{}
|
||||||
|
|
||||||
func (bs *bootstrapTracer) DropEvents() {
|
func (bs *bootstrapTracer) Record(info madmin.TraceInfo) {
|
||||||
bs.mu.Lock()
|
bs.mu.Lock()
|
||||||
defer bs.mu.Unlock()
|
defer bs.mu.Unlock()
|
||||||
|
|
||||||
if time.Now().UTC().Sub(bs.lastUpdate) > 24*time.Hour {
|
if len(bs.info) > bootstrapTraceLimit {
|
||||||
bs.info = [4096]bootstrapInfo{}
|
return
|
||||||
bs.idx = 0
|
|
||||||
}
|
}
|
||||||
}
|
bs.info = append(bs.info, info)
|
||||||
|
|
||||||
func (bs *bootstrapTracer) Empty() bool {
|
|
||||||
var empty bool
|
|
||||||
bs.mu.RLock()
|
|
||||||
empty = bs.info[0].msg == ""
|
|
||||||
bs.mu.RUnlock()
|
|
||||||
|
|
||||||
return empty
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs *bootstrapTracer) Record(msg string, skip int) {
|
|
||||||
source := getSource(skip + 1)
|
|
||||||
bs.mu.Lock()
|
|
||||||
now := time.Now().UTC()
|
|
||||||
bs.info[bs.idx] = bootstrapInfo{
|
|
||||||
msg: msg,
|
|
||||||
ts: now,
|
|
||||||
source: source,
|
|
||||||
}
|
|
||||||
bs.lastUpdate = now
|
|
||||||
bs.idx = (bs.idx + 1) % bootstrapMsgsLimit
|
|
||||||
bs.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *bootstrapTracer) Events() []madmin.TraceInfo {
|
func (bs *bootstrapTracer) Events() []madmin.TraceInfo {
|
||||||
traceInfo := make([]madmin.TraceInfo, 0, bootstrapMsgsLimit)
|
traceInfo := make([]madmin.TraceInfo, 0, bootstrapTraceLimit)
|
||||||
|
|
||||||
// Add all messages in order
|
|
||||||
addAll := func(info []bootstrapInfo) {
|
|
||||||
for _, msg := range info {
|
|
||||||
if msg.ts.IsZero() {
|
|
||||||
continue // skip empty events
|
|
||||||
}
|
|
||||||
traceInfo = append(traceInfo, madmin.TraceInfo{
|
|
||||||
TraceType: madmin.TraceBootstrap,
|
|
||||||
Time: msg.ts,
|
|
||||||
NodeName: globalLocalNodeName,
|
|
||||||
FuncName: "BOOTSTRAP",
|
|
||||||
Message: fmt.Sprintf("%s %s", msg.source, msg.msg),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bs.mu.RLock()
|
bs.mu.RLock()
|
||||||
addAll(bs.info[bs.idx:])
|
for _, i := range bs.info {
|
||||||
addAll(bs.info[:bs.idx])
|
traceInfo = append(traceInfo, i)
|
||||||
|
}
|
||||||
bs.mu.RUnlock()
|
bs.mu.RUnlock()
|
||||||
|
|
||||||
return traceInfo
|
return traceInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *bootstrapTracer) Publish(ctx context.Context, trace *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]) {
|
func (bs *bootstrapTracer) Publish(ctx context.Context, trace *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]) {
|
||||||
if bs.Empty() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, bsEvent := range bs.Events() {
|
for _, bsEvent := range bs.Events() {
|
||||||
|
if bsEvent.Message != "" {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
default:
|
default:
|
||||||
trace.Publish(bsEvent)
|
trace.Publish(bsEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of MinIO Object Storage stack
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBootstrap(t *testing.T) {
|
|
||||||
// Bootstrap events exceed bootstrap messages limit
|
|
||||||
bsTracer := &bootstrapTracer{}
|
|
||||||
for i := 0; i < bootstrapMsgsLimit+10; i++ {
|
|
||||||
bsTracer.Record(fmt.Sprintf("msg-%d", i), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
traceInfos := bsTracer.Events()
|
|
||||||
if len(traceInfos) != bootstrapMsgsLimit {
|
|
||||||
t.Fatalf("Expected length of events %d but got %d", bootstrapMsgsLimit, len(traceInfos))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate the case where bootstrap events were updated a day ago
|
|
||||||
bsTracer.lastUpdate = time.Now().UTC().Add(-25 * time.Hour)
|
|
||||||
bsTracer.DropEvents()
|
|
||||||
if !bsTracer.Empty() {
|
|
||||||
t.Fatalf("Expected all bootstrap events to have been dropped, but found %d events", len(bsTracer.Events()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fewer than 4K bootstrap events
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
bsTracer.Record(fmt.Sprintf("msg-%d", i), 1)
|
|
||||||
}
|
|
||||||
events := bsTracer.Events()
|
|
||||||
if len(events) != 10 {
|
|
||||||
t.Fatalf("Expected length of events %d but got %d", 10, len(events))
|
|
||||||
}
|
|
||||||
for i, traceInfo := range bsTracer.Events() {
|
|
||||||
msg := fmt.Sprintf("msg-%d", i)
|
|
||||||
if !strings.HasSuffix(traceInfo.Message, msg) {
|
|
||||||
t.Fatalf("Expected %s but got %s", msg, traceInfo.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/rest"
|
"github.com/minio/minio/internal/rest"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -153,14 +153,18 @@ func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
// registerBootstrapRESTHandlers - register bootstrap rest router.
|
// registerBootstrapRESTHandlers - register bootstrap rest router.
|
||||||
func registerBootstrapRESTHandlers(router *mux.Router) {
|
func registerBootstrapRESTHandlers(router *mux.Router) {
|
||||||
|
h := func(f http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return collectInternodeStats(httpTraceHdrs(f))
|
||||||
|
}
|
||||||
|
|
||||||
server := &bootstrapRESTServer{}
|
server := &bootstrapRESTServer{}
|
||||||
subrouter := router.PathPrefix(bootstrapRESTPrefix).Subrouter()
|
subrouter := router.PathPrefix(bootstrapRESTPrefix).Subrouter()
|
||||||
|
|
||||||
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth).HandlerFunc(
|
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth).HandlerFunc(
|
||||||
httpTraceHdrs(server.HealthHandler))
|
h(server.HealthHandler))
|
||||||
|
|
||||||
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify).HandlerFunc(
|
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify).HandlerFunc(
|
||||||
httpTraceHdrs(server.VerifyHandler))
|
h(server.VerifyHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// client to talk to bootstrap NEndpoints.
|
// client to talk to bootstrap NEndpoints.
|
||||||
@@ -217,7 +221,7 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
|||||||
for onlineServers < len(clnts)/2 {
|
for onlineServers < len(clnts)/2 {
|
||||||
for _, clnt := range clnts {
|
for _, clnt := range clnts {
|
||||||
if err := clnt.Verify(ctx, srcCfg); err != nil {
|
if err := clnt.Verify(ctx, srcCfg); err != nil {
|
||||||
bootstrapTrace(fmt.Sprintf("clnt.Verify: %v, endpoint: %v", err, clnt.endpoint))
|
bootstrapTraceMsg(fmt.Sprintf("clnt.Verify: %v, endpoint: %v", err, clnt.endpoint))
|
||||||
if !isNetworkError(err) {
|
if !isNetworkError(err) {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err), clnt.String())
|
logger.LogOnceIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err), clnt.String())
|
||||||
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err))
|
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+36
-23
@@ -20,7 +20,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -58,9 +60,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -365,10 +366,10 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
// Use the following trick to filter in place
|
// Use the following trick to filter in place
|
||||||
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
||||||
for _, bucketInfo := range bucketsInfo {
|
for _, bucketInfo := range bucketsInfo {
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucketInfo.Name,
|
BucketName: bucketInfo.Name,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -377,10 +378,10 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
}) {
|
}) {
|
||||||
bucketsInfo[n] = bucketInfo
|
bucketsInfo[n] = bucketInfo
|
||||||
n++
|
n++
|
||||||
} else if globalIAMSys.IsAllowed(iampolicy.Args{
|
} else if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.GetBucketLocationAction,
|
Action: policy.GetBucketLocationAction,
|
||||||
BucketName: bucketInfo.Name,
|
BucketName: bucketInfo.Name,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
@@ -578,8 +579,8 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if object.VersionID != "" && hasLockEnabled {
|
if object.VersionID != "" && hasLockEnabled {
|
||||||
if apiErrCode := enforceRetentionBypassForDelete(ctx, r, bucket, object, goi, gerr); apiErrCode != ErrNone {
|
if err := enforceRetentionBypassForDelete(ctx, r, bucket, object, goi, gerr); err != nil {
|
||||||
apiErr := errorCodes.ToAPIErr(apiErrCode)
|
apiErr := toAPIError(ctx, err)
|
||||||
deleteResults[index].errInfo = DeleteError{
|
deleteResults[index].errInfo = DeleteError{
|
||||||
Code: apiErr.Code,
|
Code: apiErr.Code,
|
||||||
Message: apiErr.Description,
|
Message: apiErr.Description,
|
||||||
@@ -768,8 +769,8 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
if objectLockEnabled {
|
if objectLockEnabled {
|
||||||
// Creating a bucket with locking requires the user having more permissions
|
// Creating a bucket with locking requires the user having more permissions
|
||||||
for _, action := range []iampolicy.Action{iampolicy.PutBucketObjectLockConfigurationAction, iampolicy.PutBucketVersioningAction} {
|
for _, action := range []policy.Action{policy.PutBucketObjectLockConfigurationAction, policy.PutBucketVersioningAction} {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
@@ -1101,10 +1102,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
if len(fanOutEntries) > 0 {
|
if len(fanOutEntries) > 0 {
|
||||||
// Once signature is validated, check if the user has
|
// Once signature is validated, check if the user has
|
||||||
// explicit permissions for the user.
|
// explicit permissions for the user.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectFanOutAction,
|
Action: policy.PutObjectFanOutAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -1117,10 +1118,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
} else {
|
} else {
|
||||||
// Once signature is validated, check if the user has
|
// Once signature is validated, check if the user has
|
||||||
// explicit permissions for the user.
|
// explicit permissions for the user.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectAction,
|
Action: policy.PutObjectAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
@@ -1138,7 +1139,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashReader, err := hash.NewReader(reader, fileSize, "", "", fileSize)
|
hashReader, err := hash.NewReader(ctx, reader, fileSize, "", "", fileSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -1253,7 +1254,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// do not try to verify encrypted content/
|
// do not try to verify encrypted content/
|
||||||
hashReader, err = hash.NewReader(reader, -1, "", "", -1)
|
hashReader, err = hash.NewReader(ctx, reader, -1, "", "", -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1288,17 +1289,25 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
// instead of "copying" from source, we need the stream to be seekable
|
// instead of "copying" from source, we need the stream to be seekable
|
||||||
// to ensure that we can make fan-out calls concurrently.
|
// to ensure that we can make fan-out calls concurrently.
|
||||||
buf := bytebufferpool.Get()
|
buf := bytebufferpool.Get()
|
||||||
defer bytebufferpool.Put(buf)
|
defer func() {
|
||||||
|
buf.Reset()
|
||||||
|
bytebufferpool.Put(buf)
|
||||||
|
}()
|
||||||
|
|
||||||
|
md5w := md5.New()
|
||||||
|
|
||||||
// Maximum allowed fan-out object size.
|
// Maximum allowed fan-out object size.
|
||||||
const maxFanOutSize = 16 << 20
|
const maxFanOutSize = 16 << 20
|
||||||
|
|
||||||
n, err := io.Copy(buf, ioutil.HardLimitReader(pReader, maxFanOutSize))
|
n, err := io.Copy(io.MultiWriter(buf, md5w), ioutil.HardLimitReader(pReader, maxFanOutSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the correct hex md5sum for the fan-out stream.
|
||||||
|
fanOutOpts.MD5Hex = hex.EncodeToString(md5w.Sum(nil))
|
||||||
|
|
||||||
concurrentSize := 100
|
concurrentSize := 100
|
||||||
if runtime.GOMAXPROCS(0) < concurrentSize {
|
if runtime.GOMAXPROCS(0) < concurrentSize {
|
||||||
concurrentSize = runtime.GOMAXPROCS(0)
|
concurrentSize = runtime.GOMAXPROCS(0)
|
||||||
@@ -1491,7 +1500,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if anonymous (non-owner) has access to list objects.
|
// Check if anonymous (non-owner) has access to list objects.
|
||||||
readable := globalPolicySys.IsAllowed(policy.Args{
|
readable := globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
@@ -1499,7 +1508,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Check if anonymous (non-owner) has access to upload objects.
|
// Check if anonymous (non-owner) has access to upload objects.
|
||||||
writable := globalPolicySys.IsAllowed(policy.Args{
|
writable := globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
Action: policy.PutObjectAction,
|
Action: policy.PutObjectAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
@@ -1614,10 +1623,14 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteBucket := objectAPI.DeleteBucket
|
// Return an error if the bucket does not exist
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil && !forceDelete {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Attempt to delete bucket.
|
// Attempt to delete bucket.
|
||||||
if err := deleteBucket(ctx, bucket, DeleteBucketOptions{
|
if err := objectAPI.DeleteBucket(ctx, bucket, DeleteBucketOptions{
|
||||||
Force: forceDelete,
|
Force: forceDelete,
|
||||||
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
|
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
|||||||
maxUploads: "0",
|
maxUploads: "0",
|
||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
secretKey: credentials.SecretKey,
|
secretKey: credentials.SecretKey,
|
||||||
expectedRespStatus: http.StatusNotFound,
|
expectedRespStatus: http.StatusBadRequest,
|
||||||
shouldPass: false,
|
shouldPass: false,
|
||||||
},
|
},
|
||||||
// Test case - 2.
|
// Test case - 2.
|
||||||
|
|||||||
@@ -27,7 +27,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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/s3select"
|
"github.com/minio/minio/internal/s3select"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
"github.com/minio/pkg/workers"
|
"github.com/minio/pkg/v2/workers"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -280,8 +280,10 @@ func (t *transitionState) worker(objectAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
atomic.AddInt32(&t.activeTasks, 1)
|
atomic.AddInt32(&t.activeTasks, 1)
|
||||||
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) {
|
||||||
logger.LogIf(t.ctx, fmt.Errorf("Transition to %s failed for %s/%s version:%s with %w",
|
logger.LogIf(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))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ts := tierStats{
|
ts := tierStats{
|
||||||
TotalSize: uint64(task.objInfo.Size),
|
TotalSize: uint64(task.objInfo.Size),
|
||||||
@@ -403,13 +405,11 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
|
|||||||
TierName: oi.TransitionedObject.Tier,
|
TierName: oi.TransitionedObject.Tier,
|
||||||
}
|
}
|
||||||
if err := globalTierJournal.AddEntry(entry); err != nil {
|
if err := globalTierJournal.AddEntry(entry); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Delete metadata on source, now that data in remote tier has been
|
// Delete metadata on source, now that data in remote tier has been
|
||||||
// marked for deletion.
|
// 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 {
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,7 +691,7 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
|
|||||||
|
|
||||||
if rreq.Type == SelectRestoreRequest {
|
if rreq.Type == SelectRestoreRequest {
|
||||||
for _, v := range rreq.OutputLocation.S3.UserMetadata {
|
for _, v := range rreq.OutputLocation.S3.UserMetadata {
|
||||||
if !strings.HasPrefix(strings.ToLower(v.Name), "x-amz-meta") {
|
if !stringsHasPrefixFold(v.Name, "x-amz-meta") {
|
||||||
meta["x-amz-meta-"+v.Name] = v.Value
|
meta["x-amz-meta-"+v.Name] = v.Value
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
||||||
@@ -231,7 +231,7 @@ func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
|||||||
if token == "" {
|
if token == "" {
|
||||||
return token, -1
|
return token, -1
|
||||||
}
|
}
|
||||||
i := strings.Index(token, "@")
|
i := strings.Index(token, ":")
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return token, -1
|
return token, -1
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-10
@@ -21,7 +21,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -36,8 +35,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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/pkg/sync/errgroup"
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
||||||
@@ -152,14 +151,27 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
|
|||||||
return updatedAt, fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
return updatedAt, fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := meta.Save(ctx, objAPI); err != nil {
|
err = sys.save(ctx, meta)
|
||||||
return updatedAt, err
|
return updatedAt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *BucketMetadataSys) save(ctx context.Context, meta BucketMetadata) error {
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.Set(bucket, meta)
|
if isMinioMetaBucketName(meta.Name) {
|
||||||
globalNotificationSys.LoadBucketMetadata(bgContext(ctx), bucket) // Do not use caller context here
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
return updatedAt, nil
|
if err := meta.Save(ctx, objAPI); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Set(meta.Name, meta)
|
||||||
|
globalNotificationSys.LoadBucketMetadata(bgContext(ctx), meta.Name) // Do not use caller context here
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete delete the bucket metadata for the specified bucket.
|
// Delete delete the bucket metadata for the specified bucket.
|
||||||
@@ -298,7 +310,7 @@ func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
|
|||||||
|
|
||||||
// GetPolicyConfig returns configured bucket policy
|
// GetPolicyConfig returns configured bucket policy
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) {
|
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.BucketPolicy, time.Time, error) {
|
||||||
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
@@ -489,6 +501,8 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
||||||
const bucketMetadataRefresh = 15 * time.Minute
|
const bucketMetadataRefresh = 15 * time.Minute
|
||||||
|
|
||||||
|
sleeper := newDynamicSleeper(2, 150*time.Millisecond, false)
|
||||||
|
|
||||||
t := time.NewTimer(bucketMetadataRefresh)
|
t := time.NewTimer(bucketMetadataRefresh)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
for {
|
for {
|
||||||
@@ -512,13 +526,16 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
|||||||
sys.RemoveStaleBuckets(diskBuckets)
|
sys.RemoveStaleBuckets(diskBuckets)
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
|
wait := sleeper.Timer(ctx)
|
||||||
|
|
||||||
err := sys.loadBucketMetadata(ctx, bucket)
|
err := sys.loadBucketMetadata(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
|
wait() // wait to proceed to next entry.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Check if there is a spare procs, wait 100ms instead
|
|
||||||
waitForLowIO(runtime.GOMAXPROCS(0), 100*time.Millisecond, currentHTTPIO)
|
wait() // wait to proceed to next entry.
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Reset(bucketMetadataRefresh)
|
t.Reset(bucketMetadataRefresh)
|
||||||
|
|||||||
+13
-14
@@ -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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ type BucketMetadata struct {
|
|||||||
LifecycleConfigUpdatedAt time.Time
|
LifecycleConfigUpdatedAt time.Time
|
||||||
|
|
||||||
// Unexported fields. Must be updated atomically.
|
// Unexported fields. Must be updated atomically.
|
||||||
policyConfig *policy.Policy
|
policyConfig *policy.BucketPolicy
|
||||||
notificationConfig *event.Config
|
notificationConfig *event.Config
|
||||||
lifecycleConfig *lifecycle.Lifecycle
|
lifecycleConfig *lifecycle.Lifecycle
|
||||||
objectLockConfig *objectlock.Config
|
objectLockConfig *objectlock.Config
|
||||||
@@ -143,39 +143,38 @@ func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
|
|||||||
|
|
||||||
// Load - loads the metadata of bucket by name from ObjectLayer api.
|
// Load - loads the metadata of bucket by name from ObjectLayer api.
|
||||||
// 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 (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) 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"))
|
logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
|
||||||
return errInvalidArgument
|
return BucketMetadata{}, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
b := newBucketMetadata(name)
|
||||||
configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile)
|
configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile)
|
||||||
data, err := readConfig(ctx, api, configFile)
|
data, err := readConfig(ctx, api, configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return b, err
|
||||||
}
|
}
|
||||||
if len(data) <= 4 {
|
if len(data) <= 4 {
|
||||||
return fmt.Errorf("loadBucketMetadata: no data")
|
return b, fmt.Errorf("loadBucketMetadata: no data")
|
||||||
}
|
}
|
||||||
// Read header
|
// Read header
|
||||||
switch binary.LittleEndian.Uint16(data[0:2]) {
|
switch binary.LittleEndian.Uint16(data[0:2]) {
|
||||||
case bucketMetadataFormat:
|
case bucketMetadataFormat:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("loadBucketMetadata: unknown format: %d", binary.LittleEndian.Uint16(data[0:2]))
|
return b, fmt.Errorf("loadBucketMetadata: unknown format: %d", binary.LittleEndian.Uint16(data[0:2]))
|
||||||
}
|
}
|
||||||
switch binary.LittleEndian.Uint16(data[2:4]) {
|
switch binary.LittleEndian.Uint16(data[2:4]) {
|
||||||
case bucketMetadataVersion:
|
case bucketMetadataVersion:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("loadBucketMetadata: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
|
return b, fmt.Errorf("loadBucketMetadata: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
|
||||||
}
|
}
|
||||||
// OK, parse data.
|
|
||||||
_, err = b.UnmarshalMsg(data[4:])
|
_, err = b.UnmarshalMsg(data[4:])
|
||||||
b.Name = name // in-case parsing failed for some reason, make sure bucket name is not empty.
|
return b, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) {
|
func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) {
|
||||||
b := newBucketMetadata(bucket)
|
b, err := readBucketMetadata(ctx, objectAPI, bucket)
|
||||||
err := b.Load(ctx, objectAPI, b.Name)
|
b.Name = bucket // in-case parsing failed for some reason, make sure bucket name is not empty.
|
||||||
if err != nil && !errors.Is(err, errConfigNotFound) {
|
if err != nil && !errors.Is(err, errConfigNotFound) {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
@@ -218,7 +217,7 @@ func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket strin
|
|||||||
// The first error encountered is returned.
|
// The first error encountered is returned.
|
||||||
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
|
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
|
||||||
if len(b.PolicyConfigJSON) != 0 {
|
if len(b.PolicyConfigJSON) != 0 {
|
||||||
b.policyConfig, err = policy.ParseConfig(bytes.NewReader(b.PolicyConfigJSON), b.Name)
|
b.policyConfig, err = policy.ParseBucketPolicyConfig(bytes.NewReader(b.PolicyConfigJSON), b.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+14
-20
@@ -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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketObjectLockSys - map of bucket and retention configuration.
|
// BucketObjectLockSys - map of bucket and retention configuration.
|
||||||
@@ -82,30 +82,24 @@ func enforceRetentionForDeletion(ctx context.Context, objInfo ObjectInfo) (locke
|
|||||||
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
||||||
// governance bypass headers are set and user has governance bypass permissions.
|
// governance bypass headers are set and user has governance bypass permissions.
|
||||||
// Objects in "Compliance" mode can be overwritten only if retention date is past.
|
// Objects in "Compliance" mode can be overwritten only if retention date is past.
|
||||||
func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket string, object ObjectToDelete, oi ObjectInfo, gerr error) APIErrorCode {
|
func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket string, object ObjectToDelete, oi ObjectInfo, gerr error) error {
|
||||||
opts, err := getOpts(ctx, r, bucket, object.ObjectName)
|
|
||||||
if err != nil {
|
|
||||||
return toAPIErrorCode(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.VersionID = object.VersionID
|
|
||||||
if gerr != nil { // error from GetObjectInfo
|
if gerr != nil { // error from GetObjectInfo
|
||||||
if _, ok := gerr.(MethodNotAllowed); ok {
|
if _, ok := gerr.(MethodNotAllowed); ok {
|
||||||
// This happens usually for a delete marker
|
// This happens usually for a delete marker
|
||||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||||
// Delete marker should be present and valid.
|
// Delete marker should be present and valid.
|
||||||
return ErrNone
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isErrObjectNotFound(gerr) || isErrVersionNotFound(gerr) {
|
if isErrObjectNotFound(gerr) || isErrVersionNotFound(gerr) {
|
||||||
return ErrNone
|
return nil
|
||||||
}
|
}
|
||||||
return toAPIErrorCode(ctx, gerr)
|
return gerr
|
||||||
}
|
}
|
||||||
|
|
||||||
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
||||||
if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn {
|
if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn {
|
||||||
return ErrObjectLocked
|
return ObjectLocked{}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
||||||
@@ -121,13 +115,13 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
|||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return ErrObjectLocked
|
return ObjectLocked{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ret.RetainUntilDate.Before(t) {
|
if !ret.RetainUntilDate.Before(t) {
|
||||||
return ErrObjectLocked
|
return ObjectLocked{}
|
||||||
}
|
}
|
||||||
return ErrNone
|
return nil
|
||||||
case objectlock.RetGovernance:
|
case objectlock.RetGovernance:
|
||||||
// In governance mode, users can't overwrite or delete an object
|
// In governance mode, users can't overwrite or delete an object
|
||||||
// version or alter its lock settings unless they have special
|
// version or alter its lock settings unless they have special
|
||||||
@@ -147,22 +141,22 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
|||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return ErrObjectLocked
|
return ObjectLocked{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ret.RetainUntilDate.Before(t) {
|
if !ret.RetainUntilDate.Before(t) {
|
||||||
return ErrObjectLocked
|
return ObjectLocked{}
|
||||||
}
|
}
|
||||||
return ErrNone
|
return nil
|
||||||
}
|
}
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
|
||||||
// If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention, the operation will succeed.
|
// If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention, the operation will succeed.
|
||||||
if checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName) != ErrNone {
|
if checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName) != ErrNone {
|
||||||
return ErrAccessDenied
|
return errAuthentication
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrNone
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforceRetentionBypassForPut enforces whether an existing object under governance can be overwritten
|
// enforceRetentionBypassForPut enforces whether an existing object under governance can be overwritten
|
||||||
|
|||||||
@@ -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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -84,7 +84,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
bucketPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, APIError{
|
writeErrorResponse(ctx, w, APIError{
|
||||||
Code: "MalformedPolicy",
|
Code: "MalformedPolicy",
|
||||||
|
|||||||
@@ -29,31 +29,31 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/pkg/bucket/policy/condition"
|
"github.com/minio/pkg/v2/policy/condition"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.Policy {
|
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
|
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, "")),
|
policy.NewResourceSet(policy.NewResource(bucketName)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.Policy {
|
func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
@@ -61,34 +61,34 @@ func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.Policy {
|
|||||||
policy.GetBucketLocationAction,
|
policy.GetBucketLocationAction,
|
||||||
policy.ListBucketMultipartUploadsAction,
|
policy.ListBucketMultipartUploadsAction,
|
||||||
),
|
),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, "")),
|
policy.NewResourceSet(policy.NewResource(bucketName)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAnonReadOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
|
func getAnonReadOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
policy.NewActionSet(policy.GetObjectAction),
|
policy.NewActionSet(policy.GetObjectAction),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, prefix)),
|
policy.NewResourceSet(policy.NewResource(bucketName+"/"+prefix)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
|
func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolicy {
|
||||||
return &policy.Policy{
|
return &policy.BucketPolicy{
|
||||||
Version: policy.DefaultVersion,
|
Version: policy.DefaultVersion,
|
||||||
Statements: []policy.Statement{
|
Statements: []policy.BPStatement{
|
||||||
policy.NewStatement(
|
policy.NewBPStatement(
|
||||||
"",
|
"",
|
||||||
policy.Allow,
|
policy.Allow,
|
||||||
policy.NewPrincipal("*"),
|
policy.NewPrincipal("*"),
|
||||||
@@ -98,7 +98,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
|
|||||||
policy.ListMultipartUploadPartsAction,
|
policy.ListMultipartUploadPartsAction,
|
||||||
policy.PutObjectAction,
|
policy.PutObjectAction,
|
||||||
),
|
),
|
||||||
policy.NewResourceSet(policy.NewResource(bucketName, prefix)),
|
policy.NewResourceSet(policy.NewResource(bucketName+"/"+prefix)),
|
||||||
condition.NewFunctions(),
|
condition.NewFunctions(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -289,7 +289,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
policyLen: len(fmt.Sprintf(bucketPolicyTemplate, bucketName, bucketName)),
|
policyLen: len(fmt.Sprintf(bucketPolicyTemplate, bucketName, bucketName)),
|
||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
secretKey: credentials.SecretKey,
|
secretKey: credentials.SecretKey,
|
||||||
expectedRespStatus: http.StatusNotFound,
|
expectedRespStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
// Test case - 10.
|
// Test case - 10.
|
||||||
// Existent bucket with policy with Version field empty.
|
// Existent bucket with policy with Version field empty.
|
||||||
@@ -463,7 +463,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
secretKey: credentials.SecretKey,
|
secretKey: credentials.SecretKey,
|
||||||
expectedBucketPolicy: "",
|
expectedBucketPolicy: "",
|
||||||
expectedRespStatus: http.StatusNotFound,
|
expectedRespStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Iterating over the cases, fetching the policy and validating the response.
|
// Iterating over the cases, fetching the policy and validating the response.
|
||||||
@@ -493,13 +493,13 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
|
|
||||||
if recV4.Code != testCase.expectedRespStatus {
|
if recV4.Code != testCase.expectedRespStatus {
|
||||||
// Verify whether the bucket policy fetched is same as the one inserted.
|
// Verify whether the bucket policy fetched is same as the one inserted.
|
||||||
var expectedPolicy *policy.Policy
|
var expectedPolicy *policy.BucketPolicy
|
||||||
expectedPolicy, err = policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
expectedPolicy, err = policy.ParseBucketPolicyConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
var gotPolicy *policy.Policy
|
var gotPolicy *policy.BucketPolicy
|
||||||
gotPolicy, err = policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
gotPolicy, err = policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
@@ -530,11 +530,11 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
}
|
}
|
||||||
if recV2.Code == http.StatusOK {
|
if recV2.Code == http.StatusOK {
|
||||||
// Verify whether the bucket policy fetched is same as the one inserted.
|
// Verify whether the bucket policy fetched is same as the one inserted.
|
||||||
expectedPolicy, err := policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
expectedPolicy, err := policy.ParseBucketPolicyConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
gotPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
gotPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error. %v", err)
|
t.Fatalf("unexpected error. %v", err)
|
||||||
}
|
}
|
||||||
@@ -686,7 +686,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
|||||||
bucketName: ".invalid-bucket-name",
|
bucketName: ".invalid-bucket-name",
|
||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
secretKey: credentials.SecretKey,
|
secretKey: credentials.SecretKey,
|
||||||
expectedRespStatus: http.StatusNotFound,
|
expectedRespStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Iterating over the cases and deleting the bucket policy and then asserting response.
|
// Iterating over the cases and deleting the bucket policy and then asserting response.
|
||||||
|
|||||||
@@ -32,20 +32,20 @@ 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/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolicySys - policy subsystem.
|
// PolicySys - policy subsystem.
|
||||||
type PolicySys struct{}
|
type PolicySys struct{}
|
||||||
|
|
||||||
// Get returns stored bucket policy
|
// Get returns stored bucket policy
|
||||||
func (sys *PolicySys) Get(bucket string) (*policy.Policy, error) {
|
func (sys *PolicySys) Get(bucket string) (*policy.BucketPolicy, error) {
|
||||||
policy, _, err := globalBucketMetadataSys.GetPolicyConfig(bucket)
|
policy, _, err := globalBucketMetadataSys.GetPolicyConfig(bucket)
|
||||||
return policy, err
|
return policy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||||
func (sys *PolicySys) IsAllowed(args policy.Args) bool {
|
func (sys *PolicySys) IsAllowed(args policy.BucketPolicyArgs) bool {
|
||||||
p, err := sys.Get(args.BucketName)
|
p, err := sys.Get(args.BucketName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return p.IsAllowed(args)
|
return p.IsAllowed(args)
|
||||||
@@ -236,7 +236,7 @@ func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PolicyToBucketAccessPolicy converts a MinIO policy into a minio-go policy data structure.
|
// PolicyToBucketAccessPolicy converts a MinIO policy into a minio-go policy data structure.
|
||||||
func PolicyToBucketAccessPolicy(bucketPolicy *policy.Policy) (*miniogopolicy.BucketAccessPolicy, error) {
|
func PolicyToBucketAccessPolicy(bucketPolicy *policy.BucketPolicy) (*miniogopolicy.BucketAccessPolicy, error) {
|
||||||
// Return empty BucketAccessPolicy for empty bucket policy.
|
// Return empty BucketAccessPolicy for empty bucket policy.
|
||||||
if bucketPolicy == nil {
|
if bucketPolicy == nil {
|
||||||
return &miniogopolicy.BucketAccessPolicy{Version: policy.DefaultVersion}, nil
|
return &miniogopolicy.BucketAccessPolicy{Version: policy.DefaultVersion}, nil
|
||||||
@@ -258,15 +258,15 @@ func PolicyToBucketAccessPolicy(bucketPolicy *policy.Policy) (*miniogopolicy.Buc
|
|||||||
return &policyInfo, nil
|
return &policyInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketAccessPolicyToPolicy - converts minio-go/policy.BucketAccessPolicy to policy.Policy.
|
// BucketAccessPolicyToPolicy - converts minio-go/policy.BucketAccessPolicy to policy.BucketPolicy.
|
||||||
func BucketAccessPolicyToPolicy(policyInfo *miniogopolicy.BucketAccessPolicy) (*policy.Policy, error) {
|
func BucketAccessPolicyToPolicy(policyInfo *miniogopolicy.BucketAccessPolicy) (*policy.BucketPolicy, error) {
|
||||||
data, err := json.Marshal(policyInfo)
|
data, err := json.Marshal(policyInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This should not happen because policyInfo is valid to convert to JSON data.
|
// This should not happen because policyInfo is valid to convert to JSON data.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var bucketPolicy policy.Policy
|
var bucketPolicy policy.BucketPolicy
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
if err = json.Unmarshal(data, &bucketPolicy); err != nil {
|
if err = json.Unmarshal(data, &bucketPolicy); err != nil {
|
||||||
// This should not happen because data is valid to JSON data.
|
// This should not happen because data is valid to JSON data.
|
||||||
|
|||||||
+28
-19
@@ -29,14 +29,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BucketQuotaSys - map of bucket and quota configuration.
|
// BucketQuotaSys - map of bucket and quota configuration.
|
||||||
type BucketQuotaSys struct {
|
type BucketQuotaSys struct{}
|
||||||
bucketStorageCache timedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get - Get quota configuration.
|
// Get - Get quota configuration.
|
||||||
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
|
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
|
||||||
qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
cfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
||||||
return qCfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
// NewBucketQuotaSys returns initialized BucketQuotaSys
|
||||||
@@ -44,16 +42,18 @@ func NewBucketQuotaSys() *BucketQuotaSys {
|
|||||||
return &BucketQuotaSys{}
|
return &BucketQuotaSys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bucketStorageCache timedValue
|
||||||
|
|
||||||
// Init initialize bucket quota.
|
// Init initialize bucket quota.
|
||||||
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
||||||
sys.bucketStorageCache.Once.Do(func() {
|
bucketStorageCache.Once.Do(func() {
|
||||||
// Set this to 10 secs since its enough, as scanner
|
// Set this to 10 secs since its enough, as scanner
|
||||||
// does not update the bucket usage values frequently.
|
// does not update the bucket usage values frequently.
|
||||||
sys.bucketStorageCache.TTL = 10 * time.Second
|
bucketStorageCache.TTL = 10 * time.Second
|
||||||
// Rely on older value if usage loading fails from disk.
|
// Rely on older value if usage loading fails from disk.
|
||||||
sys.bucketStorageCache.Relax = true
|
bucketStorageCache.Relax = true
|
||||||
sys.bucketStorageCache.Update = func() (interface{}, error) {
|
bucketStorageCache.Update = func() (interface{}, error) {
|
||||||
ctx, done := context.WithTimeout(context.Background(), 1*time.Second)
|
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
return loadDataUsageFromBackend(ctx, objAPI)
|
return loadDataUsageFromBackend(ctx, objAPI)
|
||||||
@@ -63,16 +63,17 @@ func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
|||||||
|
|
||||||
// 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(bucket string) (BucketUsageInfo, error) {
|
||||||
v, err := sys.bucketStorageCache.Get()
|
v, err := bucketStorageCache.Get()
|
||||||
if err != nil && v != nil {
|
timedout := OperationTimedOut{}
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err))
|
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
|
||||||
|
if v != nil {
|
||||||
|
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to retrieve usage information for bucket: %s, relying on older value cached in-memory: err(%v)", bucket, err), "bucket-usage-cache-"+bucket)
|
||||||
|
} else {
|
||||||
|
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
|
||||||
}
|
}
|
||||||
if v == nil {
|
|
||||||
logger.LogIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var bui BucketUsageInfo
|
var bui BucketUsageInfo
|
||||||
|
|
||||||
dui, ok := v.(DataUsageInfo)
|
dui, ok := v.(DataUsageInfo)
|
||||||
if ok {
|
if ok {
|
||||||
bui = dui.BucketsUsage[bucket]
|
bui = dui.BucketsUsage[bucket]
|
||||||
@@ -107,8 +108,16 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if q != nil && q.Type == madmin.HardQuota && q.Quota > 0 {
|
var quotaSize uint64
|
||||||
if uint64(size) >= q.Quota { // check if file size already exceeds the quota
|
if q != nil && q.Type == madmin.HardQuota {
|
||||||
|
if q.Size > 0 {
|
||||||
|
quotaSize = q.Size
|
||||||
|
} else if q.Quota > 0 {
|
||||||
|
quotaSize = q.Quota
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if quotaSize > 0 {
|
||||||
|
if uint64(size) >= quotaSize { // check if file size already exceeds the quota
|
||||||
return BucketQuotaExceeded{Bucket: bucket}
|
return BucketQuotaExceeded{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +126,7 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bui.Size > 0 && ((bui.Size + uint64(size)) >= q.Quota) {
|
if bui.Size > 0 && ((bui.Size + uint64(size)) >= quotaSize) {
|
||||||
return BucketQuotaExceeded{Bucket: bucket}
|
return BucketQuotaExceeded{Bucket: bucket}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,18 +18,23 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
||||||
@@ -188,7 +193,7 @@ func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.Respons
|
|||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketReplicationMetricsHandler - GET Bucket replication metrics.
|
// GetBucketReplicationMetricsHandler - GET Bucket replication metrics. // Deprecated Aug 2023
|
||||||
// ----------
|
// ----------
|
||||||
// Gets the replication metrics for a bucket.
|
// Gets the replication metrics for a bucket.
|
||||||
func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -222,29 +227,81 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseW
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var usageInfo BucketUsageInfo
|
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
||||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
|
||||||
if err == nil && !dataUsageInfo.LastUpdate.IsZero() {
|
enc := json.NewEncoder(w)
|
||||||
usageInfo = dataUsageInfo.BucketsUsage[bucket]
|
stats := globalReplicationStats.getLatestReplicationStats(bucket)
|
||||||
|
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
||||||
|
bwMap := bwRpt.BucketStats
|
||||||
|
for arn, st := range stats.ReplicationStats.Stats {
|
||||||
|
for opts, bw := range bwMap {
|
||||||
|
if opts.ReplicationARN != "" && opts.ReplicationARN == arn {
|
||||||
|
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
||||||
|
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
||||||
|
stats.ReplicationStats.Stats[arn] = st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := enc.Encode(stats.ReplicationStats); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBucketReplicationMetricsV2Handler - GET Bucket replication metrics.
|
||||||
|
// ----------
|
||||||
|
// Gets the replication metrics for a bucket.
|
||||||
|
func (api objectAPIHandlers) GetBucketReplicationMetricsV2Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetBucketReplicationMetricsV2")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if user has permissions to perform this operation
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
stats := globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)
|
stats := globalReplicationStats.getLatestReplicationStats(bucket)
|
||||||
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
||||||
bwMap := bwRpt.BucketStats[bucket]
|
bwMap := bwRpt.BucketStats
|
||||||
for arn, st := range stats.Stats {
|
for arn, st := range stats.ReplicationStats.Stats {
|
||||||
if bwMap != nil {
|
for opts, bw := range bwMap {
|
||||||
if bw, ok := bwMap[arn]; ok {
|
if opts.ReplicationARN != "" && opts.ReplicationARN == arn {
|
||||||
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
||||||
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
||||||
stats.Stats[arn] = st
|
stats.ReplicationStats.Stats[arn] = st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = enc.Encode(stats); err != nil {
|
stats.Uptime = UTCNow().Unix() - globalBootTime.Unix()
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
|
if err := enc.Encode(stats); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,27 +476,17 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var tgtStats map[string]TargetReplicationResyncStatus
|
brs, err := loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
||||||
globalReplicationPool.resyncer.RLock()
|
|
||||||
brs, ok := globalReplicationPool.resyncer.statusMap[bucket]
|
|
||||||
if ok {
|
|
||||||
tgtStats = brs.cloneTgtStats()
|
|
||||||
}
|
|
||||||
globalReplicationPool.resyncer.RUnlock()
|
|
||||||
if !ok {
|
|
||||||
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Err: fmt.Errorf("No replication resync status available for %s", arn),
|
Err: fmt.Errorf("replication resync status not available for %s (%s)", arn, err.Error()),
|
||||||
}), r.URL)
|
}), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgtStats = brs.cloneTgtStats()
|
|
||||||
}
|
|
||||||
|
|
||||||
var rinfo ResyncTargetsInfo
|
var rinfo ResyncTargetsInfo
|
||||||
for tarn, st := range tgtStats {
|
for tarn, st := range brs.TargetsMap {
|
||||||
if arn != "" && tarn != arn {
|
if arn != "" && tarn != arn {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -466,3 +513,148 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
|
|||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseJSON(w, data)
|
writeSuccessResponseJSON(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateBucketReplicationCredsHandler - validate replication credentials for a bucket.
|
||||||
|
// ----------
|
||||||
|
func (api objectAPIHandlers) ValidateBucketReplicationCredsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ValidateBucketReplicationCreds")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replicationConfig, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationConfigurationNotFoundError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lockEnabled := false
|
||||||
|
lcfg, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, BucketObjectLockConfigNotFound{Bucket: bucket}) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lcfg != nil {
|
||||||
|
lockEnabled = lcfg.Enabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, true)
|
||||||
|
if apiErr != noError {
|
||||||
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the bucket replication config
|
||||||
|
if err = replicationConfig.Validate(bucket, sameTarget); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := bytes.Repeat([]byte("a"), 8)
|
||||||
|
for _, rule := range replicationConfig.Rules {
|
||||||
|
if rule.Status == replication.Disabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clnt := globalBucketTargetSys.GetRemoteTargetClient(rule.Destination.Bucket)
|
||||||
|
if clnt == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetNotFoundError, fmt.Errorf("replication config with rule ID %s has a stale target", rule.ID)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lockEnabled {
|
||||||
|
lock, _, _, _, err := clnt.GetObjectLockConfig(ctx, clnt.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lock != objectlock.Enabled {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationDestinationMissingLock, fmt.Errorf("target bucket %s is not object lock enabled", clnt.Bucket)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vcfg, err := clnt.GetBucketVersioning(ctx, clnt.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !vcfg.Enabled() {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetNotVersionedError, fmt.Errorf("target bucket %s is not versioned", clnt.Bucket)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sameTarget && bucket == clnt.Bucket {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bytes.NewReader(buf)
|
||||||
|
// fake a PutObject and RemoveObject call to validate permissions
|
||||||
|
c := &minio.Core{Client: clnt.Client}
|
||||||
|
putOpts := minio.PutObjectOptions{
|
||||||
|
Internal: minio.AdvancedPutOptions{
|
||||||
|
SourceVersionID: mustGetUUID(),
|
||||||
|
ReplicationStatus: minio.ReplicationStatusReplica,
|
||||||
|
SourceMTime: time.Now(),
|
||||||
|
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||||
|
ReplicationValidityCheck: true, // set this to validate the replication config
|
||||||
|
},
|
||||||
|
}
|
||||||
|
obj := path.Join(minioReservedBucket, globalLocalNodeNameHex, "deleteme")
|
||||||
|
ui, err := c.PutObject(ctx, clnt.Bucket, obj, reader, int64(len(buf)), "", "", putOpts)
|
||||||
|
if err != nil && !isReplicationPermissionCheck(ErrorRespToObjectError(err, bucket, obj)) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, fmt.Errorf("s3:ReplicateObject permissions missing for replication user: %w", err)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.RemoveObject(ctx, clnt.Bucket, obj, minio.RemoveObjectOptions{
|
||||||
|
VersionID: ui.VersionID,
|
||||||
|
Internal: minio.AdvancedRemoveOptions{
|
||||||
|
ReplicationDeleteMarker: true,
|
||||||
|
ReplicationMTime: time.Now(),
|
||||||
|
ReplicationStatus: minio.ReplicationStatusReplica,
|
||||||
|
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||||
|
ReplicationValidityCheck: true, // set this to validate the replication config
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil && !isReplicationPermissionCheck(ErrorRespToObjectError(err, bucket, obj)) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, fmt.Errorf("s3:ReplicateDelete permissions missing for replication user: %w", err)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fake a versioned delete - to ensure deny policies are not in place
|
||||||
|
err = c.RemoveObject(ctx, clnt.Bucket, obj, minio.RemoveObjectOptions{
|
||||||
|
VersionID: ui.VersionID,
|
||||||
|
Internal: minio.AdvancedRemoveOptions{
|
||||||
|
ReplicationDeleteMarker: false,
|
||||||
|
ReplicationMTime: time.Now(),
|
||||||
|
ReplicationStatus: minio.ReplicationStatusReplica,
|
||||||
|
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||||
|
ReplicationValidityCheck: true, // set this to validate the replication config
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil && !isReplicationPermissionCheck(ErrorRespToObjectError(err, bucket, obj)) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationValidationError, fmt.Errorf("s3:ReplicateDelete/s3:DeleteObject permissions missing for replication user: %w", err)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rcrowley/go-metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE
|
||||||
|
|
||||||
|
const (
|
||||||
|
// beta is the weight used to calculate exponential moving average
|
||||||
|
beta = 0.1 // Number of averages considered = 1/(1-beta)
|
||||||
|
)
|
||||||
|
|
||||||
|
// rateMeasurement captures the transfer details for one bucket/target
|
||||||
|
//msgp:ignore rateMeasurement
|
||||||
|
|
||||||
|
type rateMeasurement struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
bytesSinceLastWindow uint64 // Total bytes since last window was processed
|
||||||
|
startTime time.Time // Start time for window
|
||||||
|
expMovingAvg float64 // Previously calculated exponential moving average
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRateMeasurement creates a new instance of the measurement with the initial start time.
|
||||||
|
func newRateMeasurement(initTime time.Time) *rateMeasurement {
|
||||||
|
return &rateMeasurement{
|
||||||
|
startTime: initTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// incrementBytes add bytes reported for a bucket/target.
|
||||||
|
func (m *rateMeasurement) incrementBytes(bytes uint64) {
|
||||||
|
atomic.AddUint64(&m.bytesSinceLastWindow, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateExponentialMovingAverage processes the measurements captured so far.
|
||||||
|
func (m *rateMeasurement) updateExponentialMovingAverage(endTime time.Time) {
|
||||||
|
// Calculate aggregate avg bandwidth and exp window avg
|
||||||
|
m.lock.Lock()
|
||||||
|
defer func() {
|
||||||
|
m.startTime = endTime
|
||||||
|
m.lock.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if m.startTime.IsZero() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if endTime.Before(m.startTime) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := endTime.Sub(m.startTime)
|
||||||
|
|
||||||
|
bytesSinceLastWindow := atomic.SwapUint64(&m.bytesSinceLastWindow, 0)
|
||||||
|
|
||||||
|
if m.expMovingAvg == 0 {
|
||||||
|
// Should address initial calculation and should be fine for resuming from 0
|
||||||
|
m.expMovingAvg = float64(bytesSinceLastWindow) / duration.Seconds()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
increment := float64(bytesSinceLastWindow) / duration.Seconds()
|
||||||
|
m.expMovingAvg = exponentialMovingAverage(beta, m.expMovingAvg, increment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exponentialMovingAverage calculates the exponential moving average
|
||||||
|
func exponentialMovingAverage(beta, previousAvg, incrementAvg float64) float64 {
|
||||||
|
return (1-beta)*incrementAvg + beta*previousAvg
|
||||||
|
}
|
||||||
|
|
||||||
|
// getExpMovingAvgBytesPerSecond returns the exponential moving average for the bucket/target in bytes
|
||||||
|
func (m *rateMeasurement) getExpMovingAvgBytesPerSecond() float64 {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
return m.expMovingAvg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveWorkerStat is stat for active replication workers
|
||||||
|
type ActiveWorkerStat struct {
|
||||||
|
Curr int `json:"curr"`
|
||||||
|
Avg float32 `json:"avg"`
|
||||||
|
Max int `json:"max"`
|
||||||
|
hist metrics.Histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActiveWorkerStat(r metrics.Registry) *ActiveWorkerStat {
|
||||||
|
h := metrics.NewHistogram(metrics.NewUniformSample(100))
|
||||||
|
r.Register("replication.active_workers", h)
|
||||||
|
return &ActiveWorkerStat{
|
||||||
|
hist: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update curr and max workers;
|
||||||
|
func (a *ActiveWorkerStat) update() {
|
||||||
|
if a == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.Curr = globalReplicationPool.ActiveWorkers()
|
||||||
|
a.hist.Update(int64(a.Curr))
|
||||||
|
a.Avg = float32(a.hist.Mean())
|
||||||
|
a.Max = int(a.hist.Max())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActiveWorkerStat) get() ActiveWorkerStat {
|
||||||
|
w := ActiveWorkerStat{
|
||||||
|
Curr: a.Curr,
|
||||||
|
Avg: a.Avg,
|
||||||
|
Max: a.Max,
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// QStat holds queue stats for replication
|
||||||
|
type QStat struct {
|
||||||
|
Count float64 `json:"count"`
|
||||||
|
Bytes float64 `json:"bytes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QStat) add(o QStat) QStat {
|
||||||
|
return QStat{Bytes: q.Bytes + o.Bytes, Count: q.Count + o.Count}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InQueueMetric holds queue stats for replication
|
||||||
|
type InQueueMetric struct {
|
||||||
|
Curr QStat `json:"curr" msg:"cq"`
|
||||||
|
Avg QStat `json:"avg" msg:"aq"`
|
||||||
|
Max QStat `json:"max" msg:"pq"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qm InQueueMetric) merge(o InQueueMetric) InQueueMetric {
|
||||||
|
return InQueueMetric{
|
||||||
|
Curr: qm.Curr.add(o.Curr),
|
||||||
|
Avg: qm.Avg.add(o.Avg),
|
||||||
|
Max: qm.Max.add(o.Max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type queueCache struct {
|
||||||
|
srQueueStats InQueueStats
|
||||||
|
bucketStats map[string]InQueueStats
|
||||||
|
sync.RWMutex // mutex for queue stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func newQueueCache(r metrics.Registry) queueCache {
|
||||||
|
return queueCache{
|
||||||
|
bucketStats: make(map[string]InQueueStats),
|
||||||
|
srQueueStats: newInQueueStats(r, "site"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queueCache) update() {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
q.srQueueStats.update()
|
||||||
|
for _, s := range q.bucketStats {
|
||||||
|
s.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queueCache) getBucketStats(bucket string) InQueueMetric {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
v, ok := q.bucketStats[bucket]
|
||||||
|
if !ok {
|
||||||
|
return InQueueMetric{}
|
||||||
|
}
|
||||||
|
return InQueueMetric{
|
||||||
|
Curr: QStat{Bytes: float64(v.nowBytes), Count: float64(v.nowCount)},
|
||||||
|
Max: QStat{Bytes: float64(v.histBytes.Max()), Count: float64(v.histCounts.Max())},
|
||||||
|
Avg: QStat{Bytes: v.histBytes.Mean(), Count: v.histCounts.Mean()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queueCache) getSiteStats() InQueueMetric {
|
||||||
|
q.RLock()
|
||||||
|
defer q.RUnlock()
|
||||||
|
v := q.srQueueStats
|
||||||
|
return InQueueMetric{
|
||||||
|
Curr: QStat{Bytes: float64(v.nowBytes), Count: float64(v.nowCount)},
|
||||||
|
Max: QStat{Bytes: float64(v.histBytes.Max()), Count: float64(v.histCounts.Max())},
|
||||||
|
Avg: QStat{Bytes: v.histBytes.Mean(), Count: v.histCounts.Mean()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InQueueStats holds queue stats for replication
|
||||||
|
type InQueueStats struct {
|
||||||
|
nowBytes int64 `json:"-"`
|
||||||
|
nowCount int64 `json:"-"`
|
||||||
|
histCounts metrics.Histogram
|
||||||
|
histBytes metrics.Histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInQueueStats(r metrics.Registry, lbl string) InQueueStats {
|
||||||
|
histCounts := metrics.NewHistogram(metrics.NewUniformSample(100))
|
||||||
|
histBytes := metrics.NewHistogram(metrics.NewUniformSample(100))
|
||||||
|
|
||||||
|
r.Register("replication.queue.counts."+lbl, histCounts)
|
||||||
|
r.Register("replication.queue.bytes."+lbl, histBytes)
|
||||||
|
|
||||||
|
return InQueueStats{
|
||||||
|
histCounts: histCounts,
|
||||||
|
histBytes: histBytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *InQueueStats) update() {
|
||||||
|
q.histBytes.Update(atomic.LoadInt64(&q.nowBytes))
|
||||||
|
q.histCounts.Update(atomic.LoadInt64(&q.nowCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
// XferStats has transfer stats for replication
|
||||||
|
type XferStats struct {
|
||||||
|
Curr float64 `json:"currRate" msg:"cr"`
|
||||||
|
Avg float64 `json:"avgRate" msg:"av"`
|
||||||
|
Peak float64 `json:"peakRate" msg:"p"`
|
||||||
|
N int64 `json:"n" msg:"n"`
|
||||||
|
measure *rateMeasurement `json:"-"`
|
||||||
|
sma *SMA `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a copy of XferStats
|
||||||
|
func (rx *XferStats) Clone() *XferStats {
|
||||||
|
curr := rx.curr()
|
||||||
|
peak := rx.Peak
|
||||||
|
if curr > peak {
|
||||||
|
peak = curr
|
||||||
|
}
|
||||||
|
return &XferStats{
|
||||||
|
Curr: curr,
|
||||||
|
Avg: rx.Avg,
|
||||||
|
Peak: peak,
|
||||||
|
N: rx.N,
|
||||||
|
measure: rx.measure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newXferStats() *XferStats {
|
||||||
|
return &XferStats{
|
||||||
|
measure: newRateMeasurement(time.Now()),
|
||||||
|
sma: newSMA(50),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rx *XferStats) String() string {
|
||||||
|
return fmt.Sprintf("curr=%f avg=%f, peak=%f", rx.curr(), rx.Avg, rx.Peak)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rx *XferStats) curr() float64 {
|
||||||
|
if rx.measure == nil {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return rx.measure.getExpMovingAvgBytesPerSecond()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rx *XferStats) merge(o XferStats) XferStats {
|
||||||
|
curr := calcAvg(rx.curr(), o.curr(), rx.N, o.N)
|
||||||
|
peak := rx.Peak
|
||||||
|
if o.Peak > peak {
|
||||||
|
peak = o.Peak
|
||||||
|
}
|
||||||
|
if curr > peak {
|
||||||
|
peak = curr
|
||||||
|
}
|
||||||
|
return XferStats{
|
||||||
|
Avg: calcAvg(rx.Avg, o.Avg, rx.N, o.N),
|
||||||
|
Peak: peak,
|
||||||
|
Curr: curr,
|
||||||
|
measure: rx.measure,
|
||||||
|
N: rx.N + o.N,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcAvg(x, y float64, n1, n2 int64) float64 {
|
||||||
|
if n1+n2 == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
avg := (x*float64(n1) + y*float64(n2)) / float64(n1+n2)
|
||||||
|
return avg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new transfer
|
||||||
|
func (rx *XferStats) addSize(sz int64, t time.Duration) {
|
||||||
|
if rx.measure == nil {
|
||||||
|
rx.measure = newRateMeasurement(time.Now())
|
||||||
|
}
|
||||||
|
rx.measure.incrementBytes(uint64(sz))
|
||||||
|
rx.Curr = rx.measure.getExpMovingAvgBytesPerSecond()
|
||||||
|
rx.sma.addSample(rx.Curr)
|
||||||
|
rx.Avg = rx.sma.simpleMovingAvg()
|
||||||
|
if rx.Curr > rx.Peak {
|
||||||
|
rx.Peak = rx.Curr
|
||||||
|
}
|
||||||
|
rx.N++
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplicationMRFStats holds stats of MRF backlog saved to disk in the last 5 minutes
|
||||||
|
// and number of entries that failed replication after 3 retries
|
||||||
|
type ReplicationMRFStats struct {
|
||||||
|
LastFailedCount uint64 `json:"failedCount_last5min"`
|
||||||
|
// Count of unreplicated entries that were dropped after MRF retry limit reached since cluster start.
|
||||||
|
TotalDroppedCount uint64 `json:"droppedCount_since_uptime"`
|
||||||
|
// Bytes of unreplicated entries that were dropped after MRF retry limit reached since cluster start.
|
||||||
|
TotalDroppedBytes uint64 `json:"droppedBytes_since_uptime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMA struct for calculating simple moving average
|
||||||
|
type SMA struct {
|
||||||
|
buf []float64
|
||||||
|
window int // len of buf
|
||||||
|
idx int // current index in buf
|
||||||
|
CAvg float64 // cumulative average
|
||||||
|
prevSMA float64
|
||||||
|
filledBuf bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSMA(len int) *SMA {
|
||||||
|
if len <= 0 {
|
||||||
|
len = defaultWindowSize
|
||||||
|
}
|
||||||
|
return &SMA{
|
||||||
|
buf: make([]float64, len),
|
||||||
|
window: len,
|
||||||
|
idx: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMA) addSample(next float64) {
|
||||||
|
prev := s.buf[s.idx]
|
||||||
|
s.buf[s.idx] = next
|
||||||
|
|
||||||
|
if s.filledBuf {
|
||||||
|
s.prevSMA += (next - prev) / float64(s.window)
|
||||||
|
s.CAvg += (next - s.CAvg) / float64(s.window)
|
||||||
|
} else {
|
||||||
|
s.CAvg = s.simpleMovingAvg()
|
||||||
|
s.prevSMA = s.CAvg
|
||||||
|
}
|
||||||
|
if s.idx == s.window-1 {
|
||||||
|
s.filledBuf = true
|
||||||
|
}
|
||||||
|
s.idx = (s.idx + 1) % s.window
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMA) simpleMovingAvg() float64 {
|
||||||
|
if s.filledBuf {
|
||||||
|
return s.prevSMA
|
||||||
|
}
|
||||||
|
var tot float64
|
||||||
|
for _, r := range s.buf {
|
||||||
|
tot += r
|
||||||
|
}
|
||||||
|
return tot / float64(s.idx+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultWindowSize = 10
|
||||||
|
)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,801 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalActiveWorkerStat(t *testing.T) {
|
||||||
|
v := ActiveWorkerStat{}
|
||||||
|
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 BenchmarkMarshalMsgActiveWorkerStat(b *testing.B) {
|
||||||
|
v := ActiveWorkerStat{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgActiveWorkerStat(b *testing.B) {
|
||||||
|
v := ActiveWorkerStat{}
|
||||||
|
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 BenchmarkUnmarshalActiveWorkerStat(b *testing.B) {
|
||||||
|
v := ActiveWorkerStat{}
|
||||||
|
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 TestEncodeDecodeActiveWorkerStat(t *testing.T) {
|
||||||
|
v := ActiveWorkerStat{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeActiveWorkerStat Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := ActiveWorkerStat{}
|
||||||
|
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 BenchmarkEncodeActiveWorkerStat(b *testing.B) {
|
||||||
|
v := ActiveWorkerStat{}
|
||||||
|
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 BenchmarkDecodeActiveWorkerStat(b *testing.B) {
|
||||||
|
v := ActiveWorkerStat{}
|
||||||
|
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 TestMarshalUnmarshalInQueueMetric(t *testing.T) {
|
||||||
|
v := InQueueMetric{}
|
||||||
|
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 BenchmarkMarshalMsgInQueueMetric(b *testing.B) {
|
||||||
|
v := InQueueMetric{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgInQueueMetric(b *testing.B) {
|
||||||
|
v := InQueueMetric{}
|
||||||
|
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 BenchmarkUnmarshalInQueueMetric(b *testing.B) {
|
||||||
|
v := InQueueMetric{}
|
||||||
|
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 TestEncodeDecodeInQueueMetric(t *testing.T) {
|
||||||
|
v := InQueueMetric{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeInQueueMetric Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := InQueueMetric{}
|
||||||
|
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 BenchmarkEncodeInQueueMetric(b *testing.B) {
|
||||||
|
v := InQueueMetric{}
|
||||||
|
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 BenchmarkDecodeInQueueMetric(b *testing.B) {
|
||||||
|
v := InQueueMetric{}
|
||||||
|
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 TestMarshalUnmarshalInQueueStats(t *testing.T) {
|
||||||
|
v := InQueueStats{}
|
||||||
|
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 BenchmarkMarshalMsgInQueueStats(b *testing.B) {
|
||||||
|
v := InQueueStats{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgInQueueStats(b *testing.B) {
|
||||||
|
v := InQueueStats{}
|
||||||
|
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 BenchmarkUnmarshalInQueueStats(b *testing.B) {
|
||||||
|
v := InQueueStats{}
|
||||||
|
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 TestEncodeDecodeInQueueStats(t *testing.T) {
|
||||||
|
v := InQueueStats{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeInQueueStats Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := InQueueStats{}
|
||||||
|
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 BenchmarkEncodeInQueueStats(b *testing.B) {
|
||||||
|
v := InQueueStats{}
|
||||||
|
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 BenchmarkDecodeInQueueStats(b *testing.B) {
|
||||||
|
v := InQueueStats{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalQStat(t *testing.T) {
|
||||||
|
v := QStat{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
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 BenchmarkMarshalMsgQStat(b *testing.B) {
|
||||||
|
v := QStat{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgQStat(b *testing.B) {
|
||||||
|
v := QStat{}
|
||||||
|
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 BenchmarkUnmarshalQStat(b *testing.B) {
|
||||||
|
v := QStat{}
|
||||||
|
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 TestEncodeDecodeQStat(t *testing.T) {
|
||||||
|
v := QStat{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeQStat Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := QStat{}
|
||||||
|
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 BenchmarkEncodeQStat(b *testing.B) {
|
||||||
|
v := QStat{}
|
||||||
|
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 BenchmarkDecodeQStat(b *testing.B) {
|
||||||
|
v := QStat{}
|
||||||
|
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 TestMarshalUnmarshalReplicationMRFStats(t *testing.T) {
|
||||||
|
v := ReplicationMRFStats{}
|
||||||
|
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 BenchmarkMarshalMsgReplicationMRFStats(b *testing.B) {
|
||||||
|
v := ReplicationMRFStats{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgReplicationMRFStats(b *testing.B) {
|
||||||
|
v := ReplicationMRFStats{}
|
||||||
|
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 BenchmarkUnmarshalReplicationMRFStats(b *testing.B) {
|
||||||
|
v := ReplicationMRFStats{}
|
||||||
|
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 TestEncodeDecodeReplicationMRFStats(t *testing.T) {
|
||||||
|
v := ReplicationMRFStats{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeReplicationMRFStats Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := ReplicationMRFStats{}
|
||||||
|
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 BenchmarkEncodeReplicationMRFStats(b *testing.B) {
|
||||||
|
v := ReplicationMRFStats{}
|
||||||
|
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 BenchmarkDecodeReplicationMRFStats(b *testing.B) {
|
||||||
|
v := ReplicationMRFStats{}
|
||||||
|
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 TestMarshalUnmarshalSMA(t *testing.T) {
|
||||||
|
v := SMA{}
|
||||||
|
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 BenchmarkMarshalMsgSMA(b *testing.B) {
|
||||||
|
v := SMA{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgSMA(b *testing.B) {
|
||||||
|
v := SMA{}
|
||||||
|
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 BenchmarkUnmarshalSMA(b *testing.B) {
|
||||||
|
v := SMA{}
|
||||||
|
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 TestEncodeDecodeSMA(t *testing.T) {
|
||||||
|
v := SMA{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeSMA Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := SMA{}
|
||||||
|
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 BenchmarkEncodeSMA(b *testing.B) {
|
||||||
|
v := SMA{}
|
||||||
|
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 BenchmarkDecodeSMA(b *testing.B) {
|
||||||
|
v := SMA{}
|
||||||
|
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 TestMarshalUnmarshalXferStats(t *testing.T) {
|
||||||
|
v := XferStats{}
|
||||||
|
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 BenchmarkMarshalMsgXferStats(b *testing.B) {
|
||||||
|
v := XferStats{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgXferStats(b *testing.B) {
|
||||||
|
v := XferStats{}
|
||||||
|
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 BenchmarkUnmarshalXferStats(b *testing.B) {
|
||||||
|
v := XferStats{}
|
||||||
|
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 TestEncodeDecodeXferStats(t *testing.T) {
|
||||||
|
v := XferStats{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeXferStats Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := XferStats{}
|
||||||
|
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 BenchmarkEncodeXferStats(b *testing.B) {
|
||||||
|
v := XferStats{}
|
||||||
|
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 BenchmarkDecodeXferStats(b *testing.B) {
|
||||||
|
v := XferStats{}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+313
-217
@@ -19,12 +19,12 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"math"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
|
"github.com/rcrowley/go-metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *BucketReplicationStats) hasReplicationUsage() bool {
|
func (b *BucketReplicationStats) hasReplicationUsage() bool {
|
||||||
@@ -38,12 +38,93 @@ func (b *BucketReplicationStats) hasReplicationUsage() bool {
|
|||||||
|
|
||||||
// ReplicationStats holds the global in-memory replication stats
|
// ReplicationStats holds the global in-memory replication stats
|
||||||
type ReplicationStats struct {
|
type ReplicationStats struct {
|
||||||
|
// map of site deployment ID to site replication status
|
||||||
|
// for site replication - maintain stats at global level
|
||||||
|
srStats *SRStats
|
||||||
|
// active worker stats
|
||||||
|
workers *ActiveWorkerStat
|
||||||
|
// queue stats cache
|
||||||
|
qCache queueCache
|
||||||
|
// mrf backlog stats
|
||||||
|
mrfStats ReplicationMRFStats
|
||||||
|
// for bucket replication, continue to use existing cache
|
||||||
Cache map[string]*BucketReplicationStats
|
Cache map[string]*BucketReplicationStats
|
||||||
UsageCache map[string]*BucketReplicationStats
|
|
||||||
mostRecentStats BucketStatsMap
|
mostRecentStats BucketStatsMap
|
||||||
|
registry metrics.Registry
|
||||||
sync.RWMutex // mutex for Cache
|
sync.RWMutex // mutex for Cache
|
||||||
ulock sync.RWMutex // mutex for UsageCache
|
|
||||||
mostRecentStatsMu sync.Mutex // mutex for mostRecentStats
|
mostRecentStatsMu sync.Mutex // mutex for mostRecentStats
|
||||||
|
|
||||||
|
wlock sync.RWMutex // mutex for active workers
|
||||||
|
|
||||||
|
movingAvgTicker *time.Ticker // Ticker for calculating moving averages
|
||||||
|
wTimer *time.Ticker // ticker for calculating active workers
|
||||||
|
qTimer *time.Ticker // ticker for calculating queue stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) trackEWMA() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-r.movingAvgTicker.C:
|
||||||
|
r.updateMovingAvg()
|
||||||
|
case <-GlobalContext.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) updateMovingAvg() {
|
||||||
|
r.RLock()
|
||||||
|
for _, s := range r.Cache {
|
||||||
|
for _, st := range s.Stats {
|
||||||
|
st.XferRateLrg.measure.updateExponentialMovingAverage(time.Now())
|
||||||
|
st.XferRateSml.measure.updateExponentialMovingAverage(time.Now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveWorkers returns worker stats
|
||||||
|
func (r *ReplicationStats) ActiveWorkers() ActiveWorkerStat {
|
||||||
|
r.wlock.RLock()
|
||||||
|
defer r.wlock.RUnlock()
|
||||||
|
w := r.workers.get()
|
||||||
|
return ActiveWorkerStat{
|
||||||
|
Curr: w.Curr,
|
||||||
|
Max: w.Max,
|
||||||
|
Avg: w.Avg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) collectWorkerMetrics(ctx context.Context) {
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-r.wTimer.C:
|
||||||
|
r.wlock.Lock()
|
||||||
|
r.workers.update()
|
||||||
|
r.wlock.Unlock()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) collectQueueMetrics(ctx context.Context) {
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-r.qTimer.C:
|
||||||
|
r.qCache.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes in-memory replication statistics for a bucket.
|
// Delete deletes in-memory replication statistics for a bucket.
|
||||||
@@ -55,10 +136,6 @@ func (r *ReplicationStats) Delete(bucket string) {
|
|||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
delete(r.Cache, bucket)
|
delete(r.Cache, bucket)
|
||||||
|
|
||||||
r.ulock.Lock()
|
|
||||||
defer r.ulock.Unlock()
|
|
||||||
delete(r.UsageCache, bucket)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateReplicaStat updates in-memory replica statistics with new values.
|
// UpdateReplicaStat updates in-memory replica statistics with new values.
|
||||||
@@ -71,83 +148,130 @@ func (r *ReplicationStats) UpdateReplicaStat(bucket string, n int64) {
|
|||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
bs, ok := r.Cache[bucket]
|
bs, ok := r.Cache[bucket]
|
||||||
if !ok {
|
if !ok {
|
||||||
bs = &BucketReplicationStats{Stats: make(map[string]*BucketReplicationStat)}
|
bs = newBucketReplicationStats()
|
||||||
}
|
}
|
||||||
bs.ReplicaSize += n
|
bs.ReplicaSize += n
|
||||||
|
bs.ReplicaCount++
|
||||||
r.Cache[bucket] = bs
|
r.Cache[bucket] = bs
|
||||||
|
r.srUpdateReplicaStat(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates in-memory replication statistics with new values.
|
func (r *ReplicationStats) srUpdateReplicaStat(sz int64) {
|
||||||
func (r *ReplicationStats) Update(bucket string, arn string, n int64, duration time.Duration, status, prevStatus replication.StatusType, opType replication.Type) {
|
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.Lock()
|
atomic.AddInt64(&r.srStats.ReplicaSize, sz)
|
||||||
defer r.Unlock()
|
atomic.AddInt64(&r.srStats.ReplicaCount, 1)
|
||||||
|
}
|
||||||
|
|
||||||
bs, ok := r.Cache[bucket]
|
func (r *ReplicationStats) srUpdate(sr replStat) {
|
||||||
if !ok {
|
dID, err := globalSiteReplicationSys.getDeplIDForEndpoint(sr.endpoint())
|
||||||
bs = &BucketReplicationStats{Stats: make(map[string]*BucketReplicationStat)}
|
if err == nil {
|
||||||
r.Cache[bucket] = bs
|
r.srStats.update(sr, dID)
|
||||||
}
|
|
||||||
b, ok := bs.Stats[arn]
|
|
||||||
if !ok {
|
|
||||||
b = &BucketReplicationStat{}
|
|
||||||
bs.Stats[arn] = b
|
|
||||||
}
|
|
||||||
switch status {
|
|
||||||
case replication.Pending:
|
|
||||||
if opType.IsDataReplication() && prevStatus != status {
|
|
||||||
b.PendingSize += n
|
|
||||||
b.PendingCount++
|
|
||||||
}
|
|
||||||
case replication.Completed:
|
|
||||||
switch prevStatus { // adjust counters based on previous state
|
|
||||||
case replication.Pending:
|
|
||||||
b.PendingCount--
|
|
||||||
case replication.Failed:
|
|
||||||
b.FailedCount--
|
|
||||||
}
|
|
||||||
if opType.IsDataReplication() {
|
|
||||||
b.ReplicatedSize += n
|
|
||||||
switch prevStatus {
|
|
||||||
case replication.Pending:
|
|
||||||
b.PendingSize -= n
|
|
||||||
case replication.Failed:
|
|
||||||
b.FailedSize -= n
|
|
||||||
}
|
|
||||||
if duration > 0 {
|
|
||||||
b.Latency.update(n, duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case replication.Failed:
|
|
||||||
if opType.IsDataReplication() {
|
|
||||||
if prevStatus == replication.Pending {
|
|
||||||
b.FailedSize += n
|
|
||||||
b.FailedCount++
|
|
||||||
b.PendingSize -= n
|
|
||||||
b.PendingCount--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case replication.Replica:
|
|
||||||
if opType == replication.ObjectReplicationType {
|
|
||||||
b.ReplicaSize += n
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInitialUsage get replication metrics available at the time of cluster initialization
|
// Update updates in-memory replication statistics with new values.
|
||||||
func (r *ReplicationStats) GetInitialUsage(bucket string) BucketReplicationStats {
|
func (r *ReplicationStats) Update(bucket string, ri replicatedTargetInfo, status, prevStatus replication.StatusType) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return BucketReplicationStats{}
|
return
|
||||||
}
|
}
|
||||||
r.ulock.RLock()
|
var rs replStat
|
||||||
defer r.ulock.RUnlock()
|
switch status {
|
||||||
st, ok := r.UsageCache[bucket]
|
case replication.Pending:
|
||||||
|
if ri.OpType.IsDataReplication() && prevStatus != status {
|
||||||
|
rs.set(ri.Arn, ri.Size, 0, status, ri.OpType, ri.endpoint, ri.secure, ri.Err)
|
||||||
|
}
|
||||||
|
case replication.Completed:
|
||||||
|
if ri.OpType.IsDataReplication() {
|
||||||
|
rs.set(ri.Arn, ri.Size, ri.Duration, status, ri.OpType, ri.endpoint, ri.secure, ri.Err)
|
||||||
|
}
|
||||||
|
case replication.Failed:
|
||||||
|
if ri.OpType.IsDataReplication() && prevStatus == replication.Pending {
|
||||||
|
rs.set(ri.Arn, ri.Size, ri.Duration, status, ri.OpType, ri.endpoint, ri.secure, ri.Err)
|
||||||
|
}
|
||||||
|
case replication.Replica:
|
||||||
|
if ri.OpType == replication.ObjectReplicationType {
|
||||||
|
rs.set(ri.Arn, ri.Size, 0, status, ri.OpType, "", false, ri.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update site-replication in-memory stats
|
||||||
|
if rs.Completed || rs.Failed {
|
||||||
|
r.srUpdate(rs)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
// update bucket replication in-memory stats
|
||||||
|
bs, ok := r.Cache[bucket]
|
||||||
if !ok {
|
if !ok {
|
||||||
return BucketReplicationStats{}
|
bs = newBucketReplicationStats()
|
||||||
|
r.Cache[bucket] = bs
|
||||||
|
}
|
||||||
|
b, ok := bs.Stats[ri.Arn]
|
||||||
|
if !ok {
|
||||||
|
b = &BucketReplicationStat{
|
||||||
|
XferRateLrg: newXferStats(),
|
||||||
|
XferRateSml: newXferStats(),
|
||||||
|
}
|
||||||
|
bs.Stats[ri.Arn] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case rs.Completed:
|
||||||
|
b.ReplicatedSize += rs.TransferSize
|
||||||
|
b.ReplicatedCount++
|
||||||
|
if rs.TransferDuration > 0 {
|
||||||
|
b.Latency.update(rs.TransferSize, rs.TransferDuration)
|
||||||
|
b.updateXferRate(rs.TransferSize, rs.TransferDuration)
|
||||||
|
}
|
||||||
|
case rs.Failed:
|
||||||
|
b.FailStats.addsize(rs.TransferSize, rs.Err)
|
||||||
|
case rs.Pending:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type replStat struct {
|
||||||
|
Arn string
|
||||||
|
Completed bool
|
||||||
|
Pending bool
|
||||||
|
Failed bool
|
||||||
|
opType replication.Type
|
||||||
|
// transfer size
|
||||||
|
TransferSize int64
|
||||||
|
// transfer duration
|
||||||
|
TransferDuration time.Duration
|
||||||
|
Endpoint string
|
||||||
|
Secure bool
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *replStat) endpoint() string {
|
||||||
|
scheme := "http"
|
||||||
|
if rs.Secure {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
return scheme + "://" + rs.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *replStat) set(arn string, n int64, duration time.Duration, status replication.StatusType, opType replication.Type, endpoint string, secure bool, err error) {
|
||||||
|
rs.Endpoint = endpoint
|
||||||
|
rs.Secure = secure
|
||||||
|
rs.TransferSize = n
|
||||||
|
rs.Arn = arn
|
||||||
|
rs.TransferDuration = duration
|
||||||
|
rs.opType = opType
|
||||||
|
switch status {
|
||||||
|
case replication.Completed:
|
||||||
|
rs.Completed = true
|
||||||
|
case replication.Pending:
|
||||||
|
rs.Pending = true
|
||||||
|
case replication.Failed:
|
||||||
|
rs.Failed = true
|
||||||
|
rs.Err = err
|
||||||
}
|
}
|
||||||
return st.Clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll returns replication metrics for all buckets at once.
|
// GetAll returns replication metrics for all buckets at once.
|
||||||
@@ -157,16 +281,36 @@ func (r *ReplicationStats) GetAll() map[string]BucketReplicationStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.RLock()
|
r.RLock()
|
||||||
defer r.RUnlock()
|
|
||||||
|
|
||||||
bucketReplicationStats := make(map[string]BucketReplicationStats, len(r.Cache))
|
bucketReplicationStats := make(map[string]BucketReplicationStats, len(r.Cache))
|
||||||
for k, v := range r.Cache {
|
for k, v := range r.Cache {
|
||||||
bucketReplicationStats[k] = v.Clone()
|
bucketReplicationStats[k] = v.Clone()
|
||||||
}
|
}
|
||||||
|
r.RUnlock()
|
||||||
|
for k, v := range bucketReplicationStats {
|
||||||
|
v.QStat = r.qCache.getBucketStats(k)
|
||||||
|
bucketReplicationStats[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
return bucketReplicationStats
|
return bucketReplicationStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) getSRMetricsForNode() SRMetricsSummary {
|
||||||
|
if r == nil {
|
||||||
|
return SRMetricsSummary{}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := SRMetricsSummary{
|
||||||
|
Uptime: UTCNow().Unix() - globalBootTime.Unix(),
|
||||||
|
Queued: r.qCache.getSiteStats(),
|
||||||
|
ActiveWorkers: r.ActiveWorkers(),
|
||||||
|
Metrics: r.srStats.get(),
|
||||||
|
ReplicaSize: atomic.LoadInt64(&r.srStats.ReplicaSize),
|
||||||
|
ReplicaCount: atomic.LoadInt64(&r.srStats.ReplicaCount),
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// Get replication metrics for a bucket from this node since this node came up.
|
// Get replication metrics for a bucket from this node since this node came up.
|
||||||
func (r *ReplicationStats) Get(bucket string) BucketReplicationStats {
|
func (r *ReplicationStats) Get(bucket string) BucketReplicationStats {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
@@ -178,99 +322,35 @@ func (r *ReplicationStats) Get(bucket string) BucketReplicationStats {
|
|||||||
|
|
||||||
st, ok := r.Cache[bucket]
|
st, ok := r.Cache[bucket]
|
||||||
if !ok {
|
if !ok {
|
||||||
return BucketReplicationStats{}
|
return BucketReplicationStats{Stats: make(map[string]*BucketReplicationStat)}
|
||||||
}
|
}
|
||||||
return st.Clone()
|
return st.Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReplicationStats initialize in-memory replication statistics
|
// NewReplicationStats initialize in-memory replication statistics
|
||||||
func NewReplicationStats(ctx context.Context, objectAPI ObjectLayer) *ReplicationStats {
|
func NewReplicationStats(ctx context.Context, objectAPI ObjectLayer) *ReplicationStats {
|
||||||
return &ReplicationStats{
|
r := metrics.NewRegistry()
|
||||||
|
rs := ReplicationStats{
|
||||||
Cache: make(map[string]*BucketReplicationStats),
|
Cache: make(map[string]*BucketReplicationStats),
|
||||||
UsageCache: make(map[string]*BucketReplicationStats),
|
qCache: newQueueCache(r),
|
||||||
|
srStats: newSRStats(),
|
||||||
|
movingAvgTicker: time.NewTicker(2 * time.Second),
|
||||||
|
wTimer: time.NewTicker(2 * time.Second),
|
||||||
|
qTimer: time.NewTicker(2 * time.Second),
|
||||||
|
|
||||||
|
workers: newActiveWorkerStat(r),
|
||||||
|
registry: r,
|
||||||
}
|
}
|
||||||
|
go rs.collectWorkerMetrics(ctx)
|
||||||
|
go rs.collectQueueMetrics(ctx)
|
||||||
|
return &rs
|
||||||
}
|
}
|
||||||
|
|
||||||
// load replication metrics at cluster start from latest replication stats saved in .minio.sys/buckets/replication/node-name.stats
|
func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo) (bucketsReplicationStats map[string]BucketStats) {
|
||||||
// fallback to replication stats in data usage to be backward compatible
|
|
||||||
func (r *ReplicationStats) loadInitialReplicationMetrics(ctx context.Context) {
|
|
||||||
m := make(map[string]*BucketReplicationStats)
|
|
||||||
if stats, err := globalReplicationPool.loadStatsFromDisk(); err == nil {
|
|
||||||
for b, st := range stats {
|
|
||||||
c := st.Clone()
|
|
||||||
m[b] = &c
|
|
||||||
}
|
|
||||||
r.ulock.Lock()
|
|
||||||
r.UsageCache = m
|
|
||||||
r.ulock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rTimer := time.NewTimer(time.Second * 5)
|
|
||||||
defer rTimer.Stop()
|
|
||||||
var (
|
|
||||||
dui DataUsageInfo
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
outer:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-rTimer.C:
|
|
||||||
dui, err = loadDataUsageFromBackend(GlobalContext, newObjectLayerFn())
|
|
||||||
// If LastUpdate is set, data usage is available.
|
|
||||||
if err == nil {
|
|
||||||
break outer
|
|
||||||
}
|
|
||||||
rTimer.Reset(time.Second * 5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for bucket, usage := range dui.BucketsUsage {
|
|
||||||
b := &BucketReplicationStats{
|
|
||||||
Stats: make(map[string]*BucketReplicationStat, len(usage.ReplicationInfo)),
|
|
||||||
}
|
|
||||||
for arn, uinfo := range usage.ReplicationInfo {
|
|
||||||
b.Stats[arn] = &BucketReplicationStat{
|
|
||||||
FailedSize: int64(uinfo.ReplicationFailedSize),
|
|
||||||
ReplicatedSize: int64(uinfo.ReplicatedSize),
|
|
||||||
ReplicaSize: int64(uinfo.ReplicaSize),
|
|
||||||
FailedCount: int64(uinfo.ReplicationFailedCount),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.ReplicaSize += int64(usage.ReplicaSize)
|
|
||||||
if b.hasReplicationUsage() {
|
|
||||||
m[bucket] = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.ulock.Lock()
|
|
||||||
r.UsageCache = m
|
|
||||||
r.ulock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeStats will serialize the current stats.
|
|
||||||
// Will return (nil, nil) if no data.
|
|
||||||
func (r *ReplicationStats) serializeStats() ([]byte, error) {
|
|
||||||
if r == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
r.mostRecentStatsMu.Lock()
|
|
||||||
defer r.mostRecentStatsMu.Unlock()
|
|
||||||
if len(r.mostRecentStats.Stats) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
data := make([]byte, 4, 4+r.mostRecentStats.Msgsize())
|
|
||||||
// Add the replication stats meta header.
|
|
||||||
binary.LittleEndian.PutUint16(data[0:2], replStatsMetaFormat)
|
|
||||||
binary.LittleEndian.PutUint16(data[2:4], replStatsVersion)
|
|
||||||
// Add data
|
|
||||||
return r.mostRecentStats.MarshalMsg(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo) (bucketsReplicationStats map[string]BucketReplicationStats) {
|
|
||||||
peerBucketStatsList := globalNotificationSys.GetClusterAllBucketStats(GlobalContext)
|
peerBucketStatsList := globalNotificationSys.GetClusterAllBucketStats(GlobalContext)
|
||||||
bucketsReplicationStats = make(map[string]BucketReplicationStats, len(bucketsUsage))
|
bucketsReplicationStats = make(map[string]BucketStats, len(bucketsUsage))
|
||||||
|
|
||||||
for bucket, u := range bucketsUsage {
|
for bucket := range bucketsUsage {
|
||||||
bucketStats := make([]BucketStats, len(peerBucketStatsList))
|
bucketStats := make([]BucketStats, len(peerBucketStatsList))
|
||||||
for i, peerBucketStats := range peerBucketStatsList {
|
for i, peerBucketStats := range peerBucketStatsList {
|
||||||
bucketStat, ok := peerBucketStats.Stats[bucket]
|
bucketStat, ok := peerBucketStats.Stats[bucket]
|
||||||
@@ -279,110 +359,126 @@ func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo)
|
|||||||
}
|
}
|
||||||
bucketStats[i] = bucketStat
|
bucketStats[i] = bucketStat
|
||||||
}
|
}
|
||||||
bucketsReplicationStats[bucket] = r.calculateBucketReplicationStats(bucket, u, bucketStats)
|
bucketsReplicationStats[bucket] = r.calculateBucketReplicationStats(bucket, bucketStats)
|
||||||
}
|
}
|
||||||
return bucketsReplicationStats
|
return bucketsReplicationStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u BucketUsageInfo, bucketStats []BucketStats) (s BucketReplicationStats) {
|
func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, bucketStats []BucketStats) (bs BucketStats) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
s = BucketReplicationStats{
|
bs = BucketStats{
|
||||||
|
ReplicationStats: BucketReplicationStats{
|
||||||
Stats: make(map[string]*BucketReplicationStat),
|
Stats: make(map[string]*BucketReplicationStat),
|
||||||
|
},
|
||||||
|
QueueStats: ReplicationQueueStats{},
|
||||||
}
|
}
|
||||||
return s
|
return bs
|
||||||
}
|
}
|
||||||
|
var s BucketReplicationStats
|
||||||
// accumulate cluster bucket stats
|
// accumulate cluster bucket stats
|
||||||
stats := make(map[string]*BucketReplicationStat)
|
stats := make(map[string]*BucketReplicationStat)
|
||||||
var totReplicaSize int64
|
var (
|
||||||
|
totReplicaSize, totReplicatedSize int64
|
||||||
|
totReplicaCount, totReplicatedCount int64
|
||||||
|
totFailed RTimedMetrics
|
||||||
|
tq InQueueMetric
|
||||||
|
)
|
||||||
for _, bucketStat := range bucketStats {
|
for _, bucketStat := range bucketStats {
|
||||||
totReplicaSize += bucketStat.ReplicationStats.ReplicaSize
|
totReplicaSize += bucketStat.ReplicationStats.ReplicaSize
|
||||||
|
totReplicaCount += bucketStat.ReplicationStats.ReplicaCount
|
||||||
|
for _, q := range bucketStat.QueueStats.Nodes {
|
||||||
|
tq = tq.merge(q.QStats)
|
||||||
|
}
|
||||||
|
|
||||||
for arn, stat := range bucketStat.ReplicationStats.Stats {
|
for arn, stat := range bucketStat.ReplicationStats.Stats {
|
||||||
oldst := stats[arn]
|
oldst := stats[arn]
|
||||||
if oldst == nil {
|
if oldst == nil {
|
||||||
oldst = &BucketReplicationStat{}
|
oldst = &BucketReplicationStat{
|
||||||
|
XferRateLrg: newXferStats(),
|
||||||
|
XferRateSml: newXferStats(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
fstats := stat.FailStats.merge(oldst.FailStats)
|
||||||
|
lrg := oldst.XferRateLrg.merge(*stat.XferRateLrg)
|
||||||
|
sml := oldst.XferRateSml.merge(*stat.XferRateSml)
|
||||||
stats[arn] = &BucketReplicationStat{
|
stats[arn] = &BucketReplicationStat{
|
||||||
FailedCount: stat.FailedCount + oldst.FailedCount,
|
Failed: fstats.toMetric(),
|
||||||
FailedSize: stat.FailedSize + oldst.FailedSize,
|
FailStats: fstats,
|
||||||
ReplicatedSize: stat.ReplicatedSize + oldst.ReplicatedSize,
|
ReplicatedSize: stat.ReplicatedSize + oldst.ReplicatedSize,
|
||||||
|
ReplicatedCount: stat.ReplicatedCount + oldst.ReplicatedCount,
|
||||||
Latency: stat.Latency.merge(oldst.Latency),
|
Latency: stat.Latency.merge(oldst.Latency),
|
||||||
PendingCount: stat.PendingCount + oldst.PendingCount,
|
XferRateLrg: &lrg,
|
||||||
PendingSize: stat.PendingSize + oldst.PendingSize,
|
XferRateSml: &sml,
|
||||||
}
|
}
|
||||||
|
totReplicatedSize += stat.ReplicatedSize
|
||||||
|
totReplicatedCount += stat.ReplicatedCount
|
||||||
|
totFailed = totFailed.merge(stat.FailStats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add initial usage stat to cluster stats
|
|
||||||
usageStat := globalReplicationStats.GetInitialUsage(bucket)
|
|
||||||
|
|
||||||
totReplicaSize += usageStat.ReplicaSize
|
|
||||||
for arn, stat := range usageStat.Stats {
|
|
||||||
st, ok := stats[arn]
|
|
||||||
if !ok {
|
|
||||||
st = &BucketReplicationStat{}
|
|
||||||
stats[arn] = st
|
|
||||||
}
|
|
||||||
st.ReplicatedSize += stat.ReplicatedSize
|
|
||||||
st.FailedSize += stat.FailedSize
|
|
||||||
st.FailedCount += stat.FailedCount
|
|
||||||
st.PendingSize += stat.PendingSize
|
|
||||||
st.PendingCount += stat.PendingCount
|
|
||||||
}
|
|
||||||
|
|
||||||
s = BucketReplicationStats{
|
s = BucketReplicationStats{
|
||||||
Stats: make(map[string]*BucketReplicationStat, len(stats)),
|
Stats: stats,
|
||||||
|
QStat: tq,
|
||||||
|
ReplicaSize: totReplicaSize,
|
||||||
|
ReplicaCount: totReplicaCount,
|
||||||
|
ReplicatedSize: totReplicatedSize,
|
||||||
|
ReplicatedCount: totReplicatedCount,
|
||||||
|
Failed: totFailed.toMetric(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var latestTotReplicatedSize int64
|
var qs ReplicationQueueStats
|
||||||
for _, st := range u.ReplicationInfo {
|
for _, bs := range bucketStats {
|
||||||
latestTotReplicatedSize += int64(st.ReplicatedSize)
|
qs.Nodes = append(qs.Nodes, bs.QueueStats.Nodes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize computed real time stats with latest usage stat
|
qs.Uptime = UTCNow().Unix() - globalBootTime.Unix()
|
||||||
for arn, tgtstat := range stats {
|
bs = BucketStats{
|
||||||
st := BucketReplicationStat{}
|
ReplicationStats: s,
|
||||||
bu, ok := u.ReplicationInfo[arn]
|
QueueStats: qs,
|
||||||
if !ok {
|
|
||||||
bu = BucketTargetUsageInfo{}
|
|
||||||
}
|
}
|
||||||
// use in memory replication stats if it is ahead of usage info.
|
|
||||||
st.ReplicatedSize = int64(bu.ReplicatedSize)
|
|
||||||
if tgtstat.ReplicatedSize >= int64(bu.ReplicatedSize) {
|
|
||||||
st.ReplicatedSize = tgtstat.ReplicatedSize
|
|
||||||
}
|
|
||||||
s.ReplicatedSize += st.ReplicatedSize
|
|
||||||
// Reset FailedSize and FailedCount to 0 for negative overflows which can
|
|
||||||
// happen since data usage picture can lag behind actual usage state at the time of cluster start
|
|
||||||
st.FailedSize = int64(math.Max(float64(tgtstat.FailedSize), 0))
|
|
||||||
st.FailedCount = int64(math.Max(float64(tgtstat.FailedCount), 0))
|
|
||||||
st.PendingSize = int64(math.Max(float64(tgtstat.PendingSize), 0))
|
|
||||||
st.PendingCount = int64(math.Max(float64(tgtstat.PendingCount), 0))
|
|
||||||
st.Latency = tgtstat.Latency
|
|
||||||
|
|
||||||
s.Stats[arn] = &st
|
|
||||||
s.FailedSize += st.FailedSize
|
|
||||||
s.FailedCount += st.FailedCount
|
|
||||||
s.PendingCount += st.PendingCount
|
|
||||||
s.PendingSize += st.PendingSize
|
|
||||||
}
|
|
||||||
// normalize overall stats
|
|
||||||
s.ReplicaSize = int64(math.Max(float64(totReplicaSize), float64(u.ReplicaSize)))
|
|
||||||
s.ReplicatedSize = int64(math.Max(float64(s.ReplicatedSize), float64(latestTotReplicatedSize)))
|
|
||||||
r.mostRecentStatsMu.Lock()
|
r.mostRecentStatsMu.Lock()
|
||||||
if len(r.mostRecentStats.Stats) == 0 {
|
if len(r.mostRecentStats.Stats) == 0 {
|
||||||
r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()}
|
r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()}
|
||||||
}
|
}
|
||||||
if len(s.Stats) > 0 {
|
if len(bs.ReplicationStats.Stats) > 0 {
|
||||||
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s}
|
r.mostRecentStats.Stats[bucket] = bs
|
||||||
}
|
}
|
||||||
r.mostRecentStats.Timestamp = UTCNow()
|
r.mostRecentStats.Timestamp = UTCNow()
|
||||||
r.mostRecentStatsMu.Unlock()
|
r.mostRecentStatsMu.Unlock()
|
||||||
return s
|
return bs
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the most current of in-memory replication stats and data usage info from crawler.
|
// get the most current of in-memory replication stats and data usage info from crawler.
|
||||||
func (r *ReplicationStats) getLatestReplicationStats(bucket string, u BucketUsageInfo) (s BucketReplicationStats) {
|
func (r *ReplicationStats) getLatestReplicationStats(bucket string) (s BucketStats) {
|
||||||
bucketStats := globalNotificationSys.GetClusterBucketStats(GlobalContext, bucket)
|
bucketStats := globalNotificationSys.GetClusterBucketStats(GlobalContext, bucket)
|
||||||
return r.calculateBucketReplicationStats(bucket, u, bucketStats)
|
return r.calculateBucketReplicationStats(bucket, bucketStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) incQ(bucket string, sz int64, isDeleleRepl bool, opType replication.Type) {
|
||||||
|
r.qCache.Lock()
|
||||||
|
defer r.qCache.Unlock()
|
||||||
|
v, ok := r.qCache.bucketStats[bucket]
|
||||||
|
if !ok {
|
||||||
|
v = newInQueueStats(r.registry, bucket)
|
||||||
|
}
|
||||||
|
atomic.AddInt64(&v.nowBytes, sz)
|
||||||
|
atomic.AddInt64(&v.nowCount, 1)
|
||||||
|
r.qCache.bucketStats[bucket] = v
|
||||||
|
atomic.AddInt64(&r.qCache.srQueueStats.nowBytes, sz)
|
||||||
|
atomic.AddInt64(&r.qCache.srQueueStats.nowCount, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReplicationStats) decQ(bucket string, sz int64, isDelMarker bool, opType replication.Type) {
|
||||||
|
r.qCache.Lock()
|
||||||
|
defer r.qCache.Unlock()
|
||||||
|
v, ok := r.qCache.bucketStats[bucket]
|
||||||
|
if !ok {
|
||||||
|
v = newInQueueStats(r.registry, bucket)
|
||||||
|
}
|
||||||
|
atomic.AddInt64(&v.nowBytes, -1*sz)
|
||||||
|
atomic.AddInt64(&v.nowCount, -1)
|
||||||
|
r.qCache.bucketStats[bucket] = v
|
||||||
|
|
||||||
|
atomic.AddInt64(&r.qCache.srQueueStats.nowBytes, -1*sz)
|
||||||
|
atomic.AddInt64(&r.qCache.srQueueStats.nowCount, -1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -30,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,6 +49,9 @@ type replicatedTargetInfo struct {
|
|||||||
VersionPurgeStatus VersionPurgeStatusType
|
VersionPurgeStatus VersionPurgeStatusType
|
||||||
ResyncTimestamp string
|
ResyncTimestamp string
|
||||||
ReplicationResynced bool // true only if resync attempted for this target
|
ReplicationResynced bool // true only if resync attempted for this target
|
||||||
|
endpoint string
|
||||||
|
secure bool
|
||||||
|
Err error // replication error if any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true for a target if arn is empty
|
// Empty returns true for a target if arn is empty
|
||||||
@@ -162,7 +167,21 @@ func (ri replicatedInfos) Action() replicationAction {
|
|||||||
var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`)
|
var replStatusRegex = regexp.MustCompile(`([^=].*?)=([^,].*?);`)
|
||||||
|
|
||||||
// TargetReplicationStatus - returns replication status of a target
|
// TargetReplicationStatus - returns replication status of a target
|
||||||
func (o *ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
|
func (ri ReplicateObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
|
||||||
|
repStatMatches := replStatusRegex.FindAllStringSubmatch(ri.ReplicationStatusInternal, -1)
|
||||||
|
for _, repStatMatch := range repStatMatches {
|
||||||
|
if len(repStatMatch) != 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if repStatMatch[1] == arn {
|
||||||
|
return replication.StatusType(repStatMatch[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetReplicationStatus - returns replication status of a target
|
||||||
|
func (o ObjectInfo) TargetReplicationStatus(arn string) (status replication.StatusType) {
|
||||||
repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1)
|
repStatMatches := replStatusRegex.FindAllStringSubmatch(o.ReplicationStatusInternal, -1)
|
||||||
for _, repStatMatch := range repStatMatches {
|
for _, repStatMatch := range repStatMatches {
|
||||||
if len(repStatMatch) != 3 {
|
if len(repStatMatch) != 3 {
|
||||||
@@ -201,7 +220,7 @@ type ReplicateDecision struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplicateAny returns true if atleast one target qualifies for replication
|
// ReplicateAny returns true if atleast one target qualifies for replication
|
||||||
func (d *ReplicateDecision) ReplicateAny() bool {
|
func (d ReplicateDecision) ReplicateAny() bool {
|
||||||
for _, t := range d.targetsMap {
|
for _, t := range d.targetsMap {
|
||||||
if t.Replicate {
|
if t.Replicate {
|
||||||
return true
|
return true
|
||||||
@@ -211,7 +230,7 @@ func (d *ReplicateDecision) ReplicateAny() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous returns true if atleast one target qualifies for synchronous replication
|
// Synchronous returns true if atleast one target qualifies for synchronous replication
|
||||||
func (d *ReplicateDecision) Synchronous() bool {
|
func (d ReplicateDecision) Synchronous() bool {
|
||||||
for _, t := range d.targetsMap {
|
for _, t := range d.targetsMap {
|
||||||
if t.Synchronous {
|
if t.Synchronous {
|
||||||
return true
|
return true
|
||||||
@@ -220,7 +239,7 @@ func (d *ReplicateDecision) Synchronous() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ReplicateDecision) String() string {
|
func (d ReplicateDecision) String() string {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
for key, value := range d.targetsMap {
|
for key, value := range d.targetsMap {
|
||||||
fmt.Fprintf(b, "%s=%s,", key, value.String())
|
fmt.Fprintf(b, "%s=%s,", key, value.String())
|
||||||
@@ -237,7 +256,7 @@ func (d *ReplicateDecision) Set(t replicateTargetDecision) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING`
|
// PendingStatus returns a stringified representation of internal replication status with all targets marked as `PENDING`
|
||||||
func (d *ReplicateDecision) PendingStatus() string {
|
func (d ReplicateDecision) PendingStatus() string {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
for _, k := range d.targetsMap {
|
for _, k := range d.targetsMap {
|
||||||
if k.Replicate {
|
if k.Replicate {
|
||||||
@@ -253,11 +272,11 @@ type ResyncDecision struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if no targets with resync decision present
|
// Empty returns true if no targets with resync decision present
|
||||||
func (r *ResyncDecision) Empty() bool {
|
func (r ResyncDecision) Empty() bool {
|
||||||
return r.targets == nil
|
return r.targets == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ResyncDecision) mustResync() bool {
|
func (r ResyncDecision) mustResync() bool {
|
||||||
for _, v := range r.targets {
|
for _, v := range r.targets {
|
||||||
if v.Replicate {
|
if v.Replicate {
|
||||||
return true
|
return true
|
||||||
@@ -266,15 +285,12 @@ func (r *ResyncDecision) mustResync() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ResyncDecision) mustResyncTarget(tgtArn string) bool {
|
func (r ResyncDecision) mustResyncTarget(tgtArn string) bool {
|
||||||
if r.targets == nil {
|
if r.targets == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
v, ok := r.targets[tgtArn]
|
v, ok := r.targets[tgtArn]
|
||||||
if ok && v.Replicate {
|
return ok && v.Replicate
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResyncTargetDecision is struct that represents resync decision for this target
|
// ResyncTargetDecision is struct that represents resync decision for this target
|
||||||
@@ -288,36 +304,27 @@ var errInvalidReplicateDecisionFormat = fmt.Errorf("ReplicateDecision has invali
|
|||||||
|
|
||||||
// parse k-v pairs of target ARN to stringified ReplicateTargetDecision delimited by ',' into a
|
// parse k-v pairs of target ARN to stringified ReplicateTargetDecision delimited by ',' into a
|
||||||
// ReplicateDecision struct
|
// ReplicateDecision struct
|
||||||
func parseReplicateDecision(s string) (r ReplicateDecision, err error) {
|
func parseReplicateDecision(ctx context.Context, bucket, s string) (r ReplicateDecision, err error) {
|
||||||
r = ReplicateDecision{
|
r = ReplicateDecision{
|
||||||
targetsMap: make(map[string]replicateTargetDecision),
|
targetsMap: make(map[string]replicateTargetDecision),
|
||||||
}
|
}
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pairs := strings.Split(s, ",")
|
for _, p := range strings.Split(s, ",") {
|
||||||
for _, p := range pairs {
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
slc := strings.Split(p, "=")
|
slc := strings.Split(p, "=")
|
||||||
if len(slc) != 2 {
|
if len(slc) != 2 {
|
||||||
return r, errInvalidReplicateDecisionFormat
|
return r, errInvalidReplicateDecisionFormat
|
||||||
}
|
}
|
||||||
tgtStr := strings.TrimPrefix(slc[1], "\"")
|
tgtStr := strings.TrimSuffix(strings.TrimPrefix(slc[1], `"`), `"`)
|
||||||
tgtStr = strings.TrimSuffix(tgtStr, "\"")
|
|
||||||
tgt := strings.Split(tgtStr, ";")
|
tgt := strings.Split(tgtStr, ";")
|
||||||
if len(tgt) != 4 {
|
if len(tgt) != 4 {
|
||||||
return r, errInvalidReplicateDecisionFormat
|
return r, errInvalidReplicateDecisionFormat
|
||||||
}
|
}
|
||||||
var replicate, sync bool
|
r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: tgt[0] == "true", Synchronous: tgt[1] == "true", Arn: tgt[2], ID: tgt[3]}
|
||||||
var err error
|
|
||||||
replicate, err = strconv.ParseBool(tgt[0])
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
sync, err = strconv.ParseBool(tgt[1])
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
r.targetsMap[slc[0]] = replicateTargetDecision{Replicate: replicate, Synchronous: sync, Arn: tgt[2], ID: tgt[3]}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -340,8 +347,6 @@ type ReplicationState struct {
|
|||||||
// Equal returns true if replication state is identical for version purge statuses and (replica)tion statuses.
|
// Equal returns true if replication state is identical for version purge statuses and (replica)tion statuses.
|
||||||
func (rs *ReplicationState) Equal(o ReplicationState) bool {
|
func (rs *ReplicationState) Equal(o ReplicationState) bool {
|
||||||
return rs.ReplicaStatus == o.ReplicaStatus &&
|
return rs.ReplicaStatus == o.ReplicaStatus &&
|
||||||
rs.ReplicaTimeStamp.Equal(o.ReplicaTimeStamp) &&
|
|
||||||
rs.ReplicationTimeStamp.Equal(o.ReplicationTimeStamp) &&
|
|
||||||
rs.ReplicationStatusInternal == o.ReplicationStatusInternal &&
|
rs.ReplicationStatusInternal == o.ReplicationStatusInternal &&
|
||||||
rs.VersionPurgeStatusInternal == o.VersionPurgeStatusInternal
|
rs.VersionPurgeStatusInternal == o.VersionPurgeStatusInternal
|
||||||
}
|
}
|
||||||
@@ -357,7 +362,10 @@ func (rs *ReplicationState) CompositeReplicationStatus() (st replication.StatusT
|
|||||||
replStatus := getCompositeReplicationStatus(rs.Targets)
|
replStatus := getCompositeReplicationStatus(rs.Targets)
|
||||||
// return REPLICA status if replica received timestamp is later than replication timestamp
|
// return REPLICA status if replica received timestamp is later than replication timestamp
|
||||||
// provided object replication completed for all targets.
|
// provided object replication completed for all targets.
|
||||||
if !rs.ReplicaTimeStamp.Equal(timeSentinel) && replStatus == replication.Completed && rs.ReplicaTimeStamp.After(rs.ReplicationTimeStamp) {
|
if rs.ReplicaTimeStamp.Equal(timeSentinel) || rs.ReplicaTimeStamp.IsZero() {
|
||||||
|
return replStatus
|
||||||
|
}
|
||||||
|
if replStatus == replication.Completed && rs.ReplicaTimeStamp.After(rs.ReplicationTimeStamp) {
|
||||||
return rs.ReplicaStatus
|
return rs.ReplicaStatus
|
||||||
}
|
}
|
||||||
return replStatus
|
return replStatus
|
||||||
@@ -483,8 +491,8 @@ func getCompositeVersionPurgeStatus(m map[string]VersionPurgeStatusType) Version
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo
|
// getHealReplicateObjectInfo returns info needed by heal replication in ReplicateObjectInfo
|
||||||
func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo {
|
func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) ReplicateObjectInfo {
|
||||||
oi := objInfo.Clone()
|
userDefined := cloneMSS(oi.UserDefined)
|
||||||
if rcfg.Config != nil && rcfg.Config.RoleArn != "" {
|
if rcfg.Config != nil && rcfg.Config.RoleArn != "" {
|
||||||
// For backward compatibility of objects pending/failed replication.
|
// For backward compatibility of objects pending/failed replication.
|
||||||
// Save replication related statuses in the new internal representation for
|
// Save replication related statuses in the new internal representation for
|
||||||
@@ -495,50 +503,69 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
|
|||||||
if !oi.VersionPurgeStatus.Empty() {
|
if !oi.VersionPurgeStatus.Empty() {
|
||||||
oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus)
|
oi.VersionPurgeStatusInternal = fmt.Sprintf("%s=%s;", rcfg.Config.RoleArn, oi.VersionPurgeStatus)
|
||||||
}
|
}
|
||||||
for k, v := range oi.UserDefined {
|
for k, v := range userDefined {
|
||||||
if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) {
|
if strings.EqualFold(k, ReservedMetadataPrefixLower+ReplicationReset) {
|
||||||
delete(oi.UserDefined, k)
|
delete(userDefined, k)
|
||||||
oi.UserDefined[targetResetHeader(rcfg.Config.RoleArn)] = v
|
userDefined[targetResetHeader(rcfg.Config.RoleArn)] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var dsc ReplicateDecision
|
|
||||||
var tgtStatuses map[string]replication.StatusType
|
|
||||||
var purgeStatuses map[string]VersionPurgeStatusType
|
|
||||||
|
|
||||||
|
var dsc ReplicateDecision
|
||||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||||
dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
|
dsc = checkReplicateDelete(GlobalContext, oi.Bucket, ObjectToDelete{
|
||||||
ObjectV: ObjectV{
|
ObjectV: ObjectV{
|
||||||
ObjectName: oi.Name,
|
ObjectName: oi.Name,
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
},
|
},
|
||||||
}, oi, ObjectOptions{VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name)}, nil)
|
}, oi, ObjectOptions{
|
||||||
|
Versioned: globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name),
|
||||||
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
||||||
|
}, nil)
|
||||||
} else {
|
} else {
|
||||||
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(ObjectInfo{
|
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(userDefined, oi.UserTags, "", replication.HealReplicationType, ObjectOptions{}))
|
||||||
UserDefined: oi.UserDefined,
|
|
||||||
}, replication.HealReplicationType, ObjectOptions{}))
|
|
||||||
}
|
}
|
||||||
tgtStatuses = replicationStatusesMap(oi.ReplicationStatusInternal)
|
|
||||||
purgeStatuses = versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
|
tgtStatuses := replicationStatusesMap(oi.ReplicationStatusInternal)
|
||||||
existingObjResync := rcfg.Resync(GlobalContext, oi, &dsc, tgtStatuses)
|
purgeStatuses := versionPurgeStatusesMap(oi.VersionPurgeStatusInternal)
|
||||||
tm, _ := time.Parse(time.RFC3339Nano, oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
|
existingObjResync := rcfg.Resync(GlobalContext, oi, dsc, tgtStatuses)
|
||||||
|
tm, _ := time.Parse(time.RFC3339Nano, userDefined[ReservedMetadataPrefixLower+ReplicationTimestamp])
|
||||||
|
rstate := oi.ReplicationState()
|
||||||
|
rstate.ReplicateDecisionStr = dsc.String()
|
||||||
|
asz, _ := oi.GetActualSize()
|
||||||
|
|
||||||
return ReplicateObjectInfo{
|
return ReplicateObjectInfo{
|
||||||
ObjectInfo: oi,
|
Name: oi.Name,
|
||||||
|
Size: oi.Size,
|
||||||
|
ActualSize: asz,
|
||||||
|
Bucket: oi.Bucket,
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
ETag: oi.ETag,
|
||||||
|
ModTime: oi.ModTime,
|
||||||
|
ReplicationStatus: oi.ReplicationStatus,
|
||||||
|
ReplicationStatusInternal: oi.ReplicationStatusInternal,
|
||||||
|
DeleteMarker: oi.DeleteMarker,
|
||||||
|
VersionPurgeStatusInternal: oi.VersionPurgeStatusInternal,
|
||||||
|
VersionPurgeStatus: oi.VersionPurgeStatus,
|
||||||
|
|
||||||
|
ReplicationState: rstate,
|
||||||
OpType: replication.HealReplicationType,
|
OpType: replication.HealReplicationType,
|
||||||
Dsc: dsc,
|
Dsc: dsc,
|
||||||
ExistingObjResync: existingObjResync,
|
ExistingObjResync: existingObjResync,
|
||||||
TargetStatuses: tgtStatuses,
|
TargetStatuses: tgtStatuses,
|
||||||
TargetPurgeStatuses: purgeStatuses,
|
TargetPurgeStatuses: purgeStatuses,
|
||||||
ReplicationTimestamp: tm,
|
ReplicationTimestamp: tm,
|
||||||
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
|
UserTags: oi.UserTags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// vID here represents the versionID client specified in request - need to distinguish between delete marker and delete marker deletion
|
// ReplicationState - returns replication state using other internal replication metadata in ObjectInfo
|
||||||
func (o *ObjectInfo) getReplicationState(dsc string, vID string, heal bool) ReplicationState {
|
func (o ObjectInfo) ReplicationState() ReplicationState {
|
||||||
rs := ReplicationState{
|
rs := ReplicationState{
|
||||||
ReplicationStatusInternal: o.ReplicationStatusInternal,
|
ReplicationStatusInternal: o.ReplicationStatusInternal,
|
||||||
VersionPurgeStatusInternal: o.VersionPurgeStatusInternal,
|
VersionPurgeStatusInternal: o.VersionPurgeStatusInternal,
|
||||||
ReplicateDecisionStr: dsc,
|
ReplicateDecisionStr: o.replicationDecision,
|
||||||
Targets: make(map[string]replication.StatusType),
|
Targets: make(map[string]replication.StatusType),
|
||||||
PurgeTargets: make(map[string]VersionPurgeStatusType),
|
PurgeTargets: make(map[string]VersionPurgeStatusType),
|
||||||
ResetStatusesMap: make(map[string]string),
|
ResetStatusesMap: make(map[string]string),
|
||||||
@@ -555,7 +582,7 @@ func (o *ObjectInfo) getReplicationState(dsc string, vID string, heal bool) Repl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplicationState returns replication state using other internal replication metadata in ObjectToDelete
|
// ReplicationState returns replication state using other internal replication metadata in ObjectToDelete
|
||||||
func (o *ObjectToDelete) ReplicationState() ReplicationState {
|
func (o ObjectToDelete) ReplicationState() ReplicationState {
|
||||||
r := ReplicationState{
|
r := ReplicationState{
|
||||||
ReplicationStatusInternal: o.DeleteMarkerReplicationStatus,
|
ReplicationStatusInternal: o.DeleteMarkerReplicationStatus,
|
||||||
VersionPurgeStatusInternal: o.VersionPurgeStatuses,
|
VersionPurgeStatusInternal: o.VersionPurgeStatuses,
|
||||||
@@ -711,6 +738,7 @@ type TargetReplicationResyncStatus struct {
|
|||||||
// Last bucket/object replicated.
|
// Last bucket/object replicated.
|
||||||
Bucket string `json:"-" msg:"bkt"`
|
Bucket string `json:"-" msg:"bkt"`
|
||||||
Object string `json:"-" msg:"obj"`
|
Object string `json:"-" msg:"obj"`
|
||||||
|
Error error `json:"-" msg:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketReplicationResyncStatus captures current replication resync status
|
// BucketReplicationResyncStatus captures current replication resync status
|
||||||
@@ -779,6 +807,8 @@ type MRFReplicateEntry struct {
|
|||||||
Bucket string `json:"bucket" msg:"b"`
|
Bucket string `json:"bucket" msg:"b"`
|
||||||
Object string `json:"object" msg:"o"`
|
Object string `json:"object" msg:"o"`
|
||||||
versionID string `json:"-"`
|
versionID string `json:"-"`
|
||||||
|
RetryCount int `json:"retryCount" msg:"rc"`
|
||||||
|
sz int64 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MRFReplicateEntries has the map of MRF entries to save to disk
|
// MRFReplicateEntries has the map of MRF entries to save to disk
|
||||||
@@ -793,16 +823,7 @@ func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry {
|
|||||||
Bucket: ri.Bucket,
|
Bucket: ri.Bucket,
|
||||||
Object: ri.Name,
|
Object: ri.Name,
|
||||||
versionID: ri.VersionID,
|
versionID: ri.VersionID,
|
||||||
|
sz: ri.Size,
|
||||||
|
RetryCount: int(ri.RetryCount),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getReplicationStatsPath() string {
|
|
||||||
return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + "replication.stats"
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
replStatsMetaFormat = 1
|
|
||||||
replStatsVersionV1 = 1
|
|
||||||
replStatsVersion = replStatsVersionV1
|
|
||||||
replStatsSaveInterval = time.Minute * 5
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -327,6 +327,12 @@ func (z *MRFReplicateEntries) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "rc":
|
||||||
|
za0002.RetryCount, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "RetryCount")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -373,9 +379,9 @@ func (z *MRFReplicateEntries) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Entries")
|
err = msgp.WrapError(err, "Entries")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// map header, size 2
|
// map header, size 3
|
||||||
// write "b"
|
// write "b"
|
||||||
err = en.Append(0x82, 0xa1, 0x62)
|
err = en.Append(0x83, 0xa1, 0x62)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -394,6 +400,16 @@ func (z *MRFReplicateEntries) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "rc"
|
||||||
|
err = en.Append(0xa2, 0x72, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(za0002.RetryCount)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "RetryCount")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// write "v"
|
// write "v"
|
||||||
err = en.Append(0xa1, 0x76)
|
err = en.Append(0xa1, 0x76)
|
||||||
@@ -417,13 +433,16 @@ func (z *MRFReplicateEntries) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
o = msgp.AppendMapHeader(o, uint32(len(z.Entries)))
|
o = msgp.AppendMapHeader(o, uint32(len(z.Entries)))
|
||||||
for za0001, za0002 := range z.Entries {
|
for za0001, za0002 := range z.Entries {
|
||||||
o = msgp.AppendString(o, za0001)
|
o = msgp.AppendString(o, za0001)
|
||||||
// map header, size 2
|
// map header, size 3
|
||||||
// string "b"
|
// string "b"
|
||||||
o = append(o, 0x82, 0xa1, 0x62)
|
o = append(o, 0x83, 0xa1, 0x62)
|
||||||
o = msgp.AppendString(o, za0002.Bucket)
|
o = msgp.AppendString(o, za0002.Bucket)
|
||||||
// string "o"
|
// string "o"
|
||||||
o = append(o, 0xa1, 0x6f)
|
o = append(o, 0xa1, 0x6f)
|
||||||
o = msgp.AppendString(o, za0002.Object)
|
o = msgp.AppendString(o, za0002.Object)
|
||||||
|
// string "rc"
|
||||||
|
o = append(o, 0xa2, 0x72, 0x63)
|
||||||
|
o = msgp.AppendInt(o, za0002.RetryCount)
|
||||||
}
|
}
|
||||||
// string "v"
|
// string "v"
|
||||||
o = append(o, 0xa1, 0x76)
|
o = append(o, 0xa1, 0x76)
|
||||||
@@ -498,6 +517,12 @@ func (z *MRFReplicateEntries) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "rc":
|
||||||
|
za0002.RetryCount, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Entries", za0001, "RetryCount")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -532,7 +557,7 @@ func (z *MRFReplicateEntries) Msgsize() (s int) {
|
|||||||
if z.Entries != nil {
|
if z.Entries != nil {
|
||||||
for za0001, za0002 := range z.Entries {
|
for za0001, za0002 := range z.Entries {
|
||||||
_ = za0002
|
_ = za0002
|
||||||
s += msgp.StringPrefixSize + len(za0001) + 1 + 2 + msgp.StringPrefixSize + len(za0002.Bucket) + 2 + msgp.StringPrefixSize + len(za0002.Object)
|
s += msgp.StringPrefixSize + len(za0001) + 1 + 2 + msgp.StringPrefixSize + len(za0002.Bucket) + 2 + msgp.StringPrefixSize + len(za0002.Object) + 3 + msgp.IntSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s += 2 + msgp.IntSize
|
s += 2 + msgp.IntSize
|
||||||
@@ -569,6 +594,12 @@ func (z *MRFReplicateEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Object")
|
err = msgp.WrapError(err, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "rc":
|
||||||
|
z.RetryCount, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetryCount")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -582,9 +613,9 @@ func (z *MRFReplicateEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z MRFReplicateEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z MRFReplicateEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 2
|
// map header, size 3
|
||||||
// write "b"
|
// write "b"
|
||||||
err = en.Append(0x82, 0xa1, 0x62)
|
err = en.Append(0x83, 0xa1, 0x62)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -603,19 +634,32 @@ func (z MRFReplicateEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Object")
|
err = msgp.WrapError(err, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "rc"
|
||||||
|
err = en.Append(0xa2, 0x72, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.RetryCount)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetryCount")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z MRFReplicateEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z MRFReplicateEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 2
|
// map header, size 3
|
||||||
// string "b"
|
// string "b"
|
||||||
o = append(o, 0x82, 0xa1, 0x62)
|
o = append(o, 0x83, 0xa1, 0x62)
|
||||||
o = msgp.AppendString(o, z.Bucket)
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
// string "o"
|
// string "o"
|
||||||
o = append(o, 0xa1, 0x6f)
|
o = append(o, 0xa1, 0x6f)
|
||||||
o = msgp.AppendString(o, z.Object)
|
o = msgp.AppendString(o, z.Object)
|
||||||
|
// string "rc"
|
||||||
|
o = append(o, 0xa2, 0x72, 0x63)
|
||||||
|
o = msgp.AppendInt(o, z.RetryCount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,6 +693,12 @@ func (z *MRFReplicateEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Object")
|
err = msgp.WrapError(err, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "rc":
|
||||||
|
z.RetryCount, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetryCount")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -663,7 +713,7 @@ func (z *MRFReplicateEntry) 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 MRFReplicateEntry) Msgsize() (s int) {
|
func (z MRFReplicateEntry) Msgsize() (s int) {
|
||||||
s = 1 + 2 + msgp.StringPrefixSize + len(z.Bucket) + 2 + msgp.StringPrefixSize + len(z.Object)
|
s = 1 + 2 + msgp.StringPrefixSize + len(z.Bucket) + 2 + msgp.StringPrefixSize + len(z.Object) + 3 + msgp.IntSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
@@ -183,7 +184,7 @@ var parseReplicationDecisionTest = []struct {
|
|||||||
|
|
||||||
func TestParseReplicateDecision(t *testing.T) {
|
func TestParseReplicateDecision(t *testing.T) {
|
||||||
for i, test := range parseReplicationDecisionTest {
|
for i, test := range parseReplicationDecisionTest {
|
||||||
dsc, err := parseReplicateDecision(test.expDsc.String())
|
dsc, err := parseReplicateDecision(context.Background(), "bucket", test.expDsc.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if test.expErr != err {
|
if test.expErr != err {
|
||||||
t.Errorf("Test%d (%s): Expected parse error got %t , want %t", i+1, test.name, err, test.expErr)
|
t.Errorf("Test%d (%s): Expected parse error got %t , want %t", i+1, test.name, err, test.expErr)
|
||||||
|
|||||||
+540
-376
File diff suppressed because it is too large
Load Diff
@@ -88,7 +88,7 @@ var replicationConfigTests = []struct {
|
|||||||
func TestReplicationResync(t *testing.T) {
|
func TestReplicationResync(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for i, test := range replicationConfigTests {
|
for i, test := range replicationConfigTests {
|
||||||
if sync := test.rcfg.Resync(ctx, test.info, &test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
if sync := test.rcfg.Resync(ctx, test.info, test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
||||||
t.Errorf("Test%d (%s): Resync got %t , want %t", i+1, test.name, sync.mustResync(), test.expectedSync)
|
t.Errorf("Test%d (%s): Resync got %t , want %t", i+1, test.name, sync.mustResync(), test.expectedSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ var (
|
|||||||
|
|
||||||
func TestReplicationResyncwrapper(t *testing.T) {
|
func TestReplicationResyncwrapper(t *testing.T) {
|
||||||
for i, test := range replicationConfigTests2 {
|
for i, test := range replicationConfigTests2 {
|
||||||
if sync := test.rcfg.resync(test.info, &test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
if sync := test.rcfg.resync(test.info, test.dsc, test.tgtStatuses); sync.mustResync() != test.expectedSync {
|
||||||
t.Errorf("%s (%s): Replicationresync got %t , want %t", fmt.Sprintf("Test%d - %s", i+1, time.Now().Format(http.TimeFormat)), test.name, sync.mustResync(), test.expectedSync)
|
t.Errorf("%s (%s): Replicationresync got %t , want %t", fmt.Sprintf("Test%d - %s", i+1, time.Now().Format(http.TimeFormat)), test.name, sync.mustResync(), test.expectedSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+299
-21
@@ -19,7 +19,10 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE
|
//go:generate msgp -file $GOFILE
|
||||||
@@ -52,6 +55,94 @@ func (rl *ReplicationLatency) update(size int64, duration time.Duration) {
|
|||||||
rl.UploadHistogram.Add(size, duration)
|
rl.UploadHistogram.Add(size, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReplicationLastMinute has last minute replication counters
|
||||||
|
type ReplicationLastMinute struct {
|
||||||
|
LastMinute lastMinuteLatency
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl ReplicationLastMinute) merge(other ReplicationLastMinute) (nl ReplicationLastMinute) {
|
||||||
|
nl = ReplicationLastMinute{rl.LastMinute.merge(other.LastMinute)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *ReplicationLastMinute) addsize(n int64) {
|
||||||
|
t := time.Now().Unix()
|
||||||
|
rl.LastMinute.addAll(t-1, AccElem{Total: t - 1, Size: n, N: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *ReplicationLastMinute) String() string {
|
||||||
|
t := rl.LastMinute.getTotal()
|
||||||
|
return fmt.Sprintf("ReplicationLastMinute sz= %d, n=%d , dur=%d", t.Size, t.N, t.Total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *ReplicationLastMinute) getTotal() AccElem {
|
||||||
|
return rl.LastMinute.getTotal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplicationLastHour keeps track of replication counts over the last hour
|
||||||
|
type ReplicationLastHour struct {
|
||||||
|
Totals [60]AccElem
|
||||||
|
LastMin int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge data of two ReplicationLastHour structure
|
||||||
|
func (l ReplicationLastHour) merge(o ReplicationLastHour) (merged ReplicationLastHour) {
|
||||||
|
if l.LastMin > o.LastMin {
|
||||||
|
o.forwardTo(l.LastMin)
|
||||||
|
merged.LastMin = l.LastMin
|
||||||
|
} else {
|
||||||
|
l.forwardTo(o.LastMin)
|
||||||
|
merged.LastMin = o.LastMin
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range merged.Totals {
|
||||||
|
merged.Totals[i] = AccElem{
|
||||||
|
Total: l.Totals[i].Total + o.Totals[i].Total,
|
||||||
|
N: l.Totals[i].N + o.Totals[i].N,
|
||||||
|
Size: l.Totals[i].Size + o.Totals[i].Size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new duration data
|
||||||
|
func (l *ReplicationLastHour) addsize(sz int64) {
|
||||||
|
min := time.Now().Unix() / 60
|
||||||
|
l.forwardTo(min)
|
||||||
|
winIdx := min % 60
|
||||||
|
l.Totals[winIdx].merge(AccElem{Total: min, Size: sz, N: 1})
|
||||||
|
l.LastMin = min
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all recorded counts of last hour into one
|
||||||
|
func (l *ReplicationLastHour) getTotal() AccElem {
|
||||||
|
var res AccElem
|
||||||
|
min := time.Now().Unix() / 60
|
||||||
|
l.forwardTo(min)
|
||||||
|
for _, elem := range l.Totals[:] {
|
||||||
|
res.merge(elem)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// forwardTo time t, clearing any entries in between.
|
||||||
|
func (l *ReplicationLastHour) forwardTo(t int64) {
|
||||||
|
tMin := t / 60
|
||||||
|
if l.LastMin >= tMin {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t-l.LastMin >= 60 {
|
||||||
|
l.Totals = [60]AccElem{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for l.LastMin != t {
|
||||||
|
// Clear next element.
|
||||||
|
idx := (l.LastMin + 1) % 60
|
||||||
|
l.Totals[idx] = AccElem{}
|
||||||
|
l.LastMin++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BucketStatsMap captures bucket statistics for all buckets
|
// BucketStatsMap captures bucket statistics for all buckets
|
||||||
type BucketStatsMap struct {
|
type BucketStatsMap struct {
|
||||||
Stats map[string]BucketStats
|
Stats map[string]BucketStats
|
||||||
@@ -60,19 +151,32 @@ type BucketStatsMap struct {
|
|||||||
|
|
||||||
// BucketStats bucket statistics
|
// BucketStats bucket statistics
|
||||||
type BucketStats struct {
|
type BucketStats struct {
|
||||||
ReplicationStats BucketReplicationStats
|
Uptime int64 `json:"uptime"`
|
||||||
|
ReplicationStats BucketReplicationStats `json:"currStats"` // current replication stats since cluster startup
|
||||||
|
QueueStats ReplicationQueueStats `json:"queueStats"` // replication queue stats
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketReplicationStats represents inline replication statistics
|
// BucketReplicationStats represents inline replication statistics
|
||||||
// such as pending, failed and completed bytes in total for a bucket
|
// such as pending, failed and completed bytes in total for a bucket
|
||||||
type BucketReplicationStats struct {
|
type BucketReplicationStats struct {
|
||||||
Stats map[string]*BucketReplicationStat `json:",omitempty"`
|
Stats map[string]*BucketReplicationStat `json:",omitempty"`
|
||||||
// Pending size in bytes
|
|
||||||
PendingSize int64 `json:"pendingReplicationSize"`
|
|
||||||
// Completed size in bytes
|
// Completed size in bytes
|
||||||
ReplicatedSize int64 `json:"completedReplicationSize"`
|
ReplicatedSize int64 `json:"completedReplicationSize"`
|
||||||
// Total Replica size in bytes
|
// Total Replica size in bytes
|
||||||
ReplicaSize int64 `json:"replicaSize"`
|
ReplicaSize int64 `json:"replicaSize"`
|
||||||
|
// Total failed operations including metadata updates for various time frames
|
||||||
|
Failed madmin.TimedErrStats `json:"failed"`
|
||||||
|
|
||||||
|
// Total number of completed operations
|
||||||
|
ReplicatedCount int64 `json:"replicationCount"`
|
||||||
|
// Total number of replica received
|
||||||
|
ReplicaCount int64 `json:"replicaCount"`
|
||||||
|
|
||||||
|
// in Queue stats for bucket - from qCache
|
||||||
|
QStat InQueueMetric `json:"queued"`
|
||||||
|
// Deprecated fields
|
||||||
|
// Pending size in bytes
|
||||||
|
PendingSize int64 `json:"pendingReplicationSize"`
|
||||||
// Failed size in bytes
|
// Failed size in bytes
|
||||||
FailedSize int64 `json:"failedReplicationSize"`
|
FailedSize int64 `json:"failedReplicationSize"`
|
||||||
// Total number of pending operations including metadata updates
|
// Total number of pending operations including metadata updates
|
||||||
@@ -81,6 +185,12 @@ type BucketReplicationStats struct {
|
|||||||
FailedCount int64 `json:"failedReplicationCount"`
|
FailedCount int64 `json:"failedReplicationCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBucketReplicationStats() *BucketReplicationStats {
|
||||||
|
return &BucketReplicationStats{
|
||||||
|
Stats: make(map[string]*BucketReplicationStat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Empty returns true if there are no target stats
|
// Empty returns true if there are no target stats
|
||||||
func (brs *BucketReplicationStats) Empty() bool {
|
func (brs *BucketReplicationStats) Empty() bool {
|
||||||
return len(brs.Stats) == 0 && brs.ReplicaSize == 0
|
return len(brs.Stats) == 0 && brs.ReplicaSize == 0
|
||||||
@@ -96,7 +206,24 @@ func (brs BucketReplicationStats) Clone() (c BucketReplicationStats) {
|
|||||||
c.Stats = make(map[string]*BucketReplicationStat, len(brs.Stats))
|
c.Stats = make(map[string]*BucketReplicationStat, len(brs.Stats))
|
||||||
for arn, st := range brs.Stats {
|
for arn, st := range brs.Stats {
|
||||||
// make a copy of `*st`
|
// make a copy of `*st`
|
||||||
s := *st
|
s := BucketReplicationStat{
|
||||||
|
ReplicatedSize: st.ReplicatedSize,
|
||||||
|
ReplicaSize: st.ReplicaSize,
|
||||||
|
Latency: st.Latency,
|
||||||
|
BandWidthLimitInBytesPerSecond: st.BandWidthLimitInBytesPerSecond,
|
||||||
|
CurrentBandwidthInBytesPerSecond: st.CurrentBandwidthInBytesPerSecond,
|
||||||
|
XferRateLrg: st.XferRateLrg.Clone(),
|
||||||
|
XferRateSml: st.XferRateSml.Clone(),
|
||||||
|
ReplicatedCount: st.ReplicatedCount,
|
||||||
|
Failed: st.Failed,
|
||||||
|
FailStats: st.FailStats,
|
||||||
|
}
|
||||||
|
if s.Failed.ErrCounts == nil {
|
||||||
|
s.Failed.ErrCounts = make(map[string]int)
|
||||||
|
for k, v := range st.Failed.ErrCounts {
|
||||||
|
s.Failed.ErrCounts[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
c.Stats[arn] = &s
|
c.Stats[arn] = &s
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
@@ -107,38 +234,189 @@ func (brs BucketReplicationStats) Clone() (c BucketReplicationStats) {
|
|||||||
// remote target
|
// remote target
|
||||||
type BucketReplicationStat struct {
|
type BucketReplicationStat struct {
|
||||||
// Pending size in bytes
|
// Pending size in bytes
|
||||||
PendingSize int64 `json:"pendingReplicationSize"`
|
// PendingSize int64 `json:"pendingReplicationSize"`
|
||||||
// Completed size in bytes
|
// Completed size in bytes
|
||||||
ReplicatedSize int64 `json:"completedReplicationSize"`
|
ReplicatedSize int64 `json:"completedReplicationSize"`
|
||||||
// Total Replica size in bytes
|
// Total Replica size in bytes
|
||||||
ReplicaSize int64 `json:"replicaSize"`
|
ReplicaSize int64 `json:"replicaSize"`
|
||||||
// Failed size in bytes
|
// Collect stats for failures
|
||||||
FailedSize int64 `json:"failedReplicationSize"`
|
FailStats RTimedMetrics `json:"-"`
|
||||||
// Total number of pending operations including metadata updates
|
|
||||||
PendingCount int64 `json:"pendingReplicationCount"`
|
// Total number of failed operations including metadata updates in the last minute
|
||||||
// Total number of failed operations including metadata updates
|
Failed madmin.TimedErrStats `json:"failed"`
|
||||||
FailedCount int64 `json:"failedReplicationCount"`
|
// Total number of completed operations
|
||||||
|
ReplicatedCount int64 `json:"replicationCount"`
|
||||||
// Replication latency information
|
// Replication latency information
|
||||||
Latency ReplicationLatency `json:"replicationLatency"`
|
Latency ReplicationLatency `json:"replicationLatency"`
|
||||||
// bandwidth limit for target
|
// bandwidth limit for target
|
||||||
BandWidthLimitInBytesPerSecond int64 `json:"limitInBits"`
|
BandWidthLimitInBytesPerSecond int64 `json:"limitInBits"`
|
||||||
// current bandwidth reported
|
// current bandwidth reported
|
||||||
CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"`
|
CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"`
|
||||||
|
// transfer rate for large uploads
|
||||||
|
XferRateLrg *XferStats `json:"-" msg:"lt"`
|
||||||
|
// transfer rate for small uploads
|
||||||
|
XferRateSml *XferStats `json:"-" msg:"st"`
|
||||||
|
|
||||||
|
// Deprecated fields
|
||||||
|
// Pending size in bytes
|
||||||
|
PendingSize int64 `json:"pendingReplicationSize"`
|
||||||
|
// Failed size in bytes
|
||||||
|
FailedSize int64 `json:"failedReplicationSize"`
|
||||||
|
// Total number of pending operations including metadata updates
|
||||||
|
PendingCount int64 `json:"pendingReplicationCount"`
|
||||||
|
// Total number of failed operations including metadata updates
|
||||||
|
FailedCount int64 `json:"failedReplicationCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BucketReplicationStat) hasReplicationUsage() bool {
|
func (bs *BucketReplicationStat) hasReplicationUsage() bool {
|
||||||
return bs.FailedSize > 0 ||
|
return bs.FailStats.SinceUptime.Count > 0 ||
|
||||||
bs.ReplicatedSize > 0 ||
|
bs.ReplicatedSize > 0 ||
|
||||||
bs.ReplicaSize > 0 ||
|
bs.ReplicaSize > 0
|
||||||
bs.FailedCount > 0 ||
|
|
||||||
bs.PendingCount > 0 ||
|
|
||||||
bs.PendingSize > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (brs BucketReplicationStats) String() string {
|
func (bs *BucketReplicationStat) updateXferRate(sz int64, duration time.Duration) {
|
||||||
s := "ReplicatedSize=" + fmt.Sprintf("%d", brs.ReplicatedSize) + "+\n ReplicaSize=" + fmt.Sprintf("%d", brs.ReplicaSize)
|
if sz > minLargeObjSize {
|
||||||
for arn, st := range brs.Stats {
|
bs.XferRateLrg.addSize(sz, duration)
|
||||||
s += "\n arn: " + arn + " ReplicatedSize=" + fmt.Sprintf("%d", st.ReplicatedSize) + " +::ReplicaSize=" + fmt.Sprintf("%d", st.ReplicaSize)
|
} else {
|
||||||
|
bs.XferRateSml.addSize(sz, duration)
|
||||||
}
|
}
|
||||||
return s
|
}
|
||||||
|
|
||||||
|
// RMetricName - name of replication metric
|
||||||
|
type RMetricName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Large - objects larger than 128MiB
|
||||||
|
Large RMetricName = "Large"
|
||||||
|
// Small - objects smaller than 128MiB
|
||||||
|
Small RMetricName = "Small"
|
||||||
|
// Total - metric pertaining to totals
|
||||||
|
Total RMetricName = "Total"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReplQNodeStats holds queue stats for replication per node
|
||||||
|
type ReplQNodeStats struct {
|
||||||
|
NodeName string `json:"nodeName"`
|
||||||
|
Uptime int64 `json:"uptime"`
|
||||||
|
ActiveWorkers ActiveWorkerStat `json:"activeWorkers"`
|
||||||
|
XferStats map[RMetricName]XferStats `json:"transferSummary"`
|
||||||
|
TgtXferStats map[string]map[RMetricName]XferStats `json:"tgtTransferStats"`
|
||||||
|
QStats InQueueMetric `json:"queueStats"`
|
||||||
|
MRFStats ReplicationMRFStats `json:"mrfStats"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNodeQueueStats returns replication operational stats at the node level
|
||||||
|
func (r *ReplicationStats) getNodeQueueStats(bucket string) (qs ReplQNodeStats) {
|
||||||
|
qs.NodeName = globalLocalNodeName
|
||||||
|
qs.Uptime = UTCNow().Unix() - globalBootTime.Unix()
|
||||||
|
qs.ActiveWorkers = globalReplicationStats.ActiveWorkers()
|
||||||
|
qs.XferStats = make(map[RMetricName]XferStats)
|
||||||
|
qs.QStats = r.qCache.getBucketStats(bucket)
|
||||||
|
qs.TgtXferStats = make(map[string]map[RMetricName]XferStats)
|
||||||
|
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
|
||||||
|
brs, ok := r.Cache[bucket]
|
||||||
|
if !ok {
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
for arn := range brs.Stats {
|
||||||
|
qs.TgtXferStats[arn] = make(map[RMetricName]XferStats)
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
var totPeak float64
|
||||||
|
// calculate large, small transfers and total transfer rates per replication target at bucket level
|
||||||
|
for arn, v := range brs.Stats {
|
||||||
|
lcurrTgt := v.XferRateLrg.curr()
|
||||||
|
scurrTgt := v.XferRateSml.curr()
|
||||||
|
totPeak = math.Max(math.Max(v.XferRateLrg.Peak, v.XferRateSml.Peak), totPeak)
|
||||||
|
totPeak = math.Max(math.Max(lcurrTgt, scurrTgt), totPeak)
|
||||||
|
tcount := 0
|
||||||
|
if v.XferRateLrg.Peak > 0 {
|
||||||
|
tcount++
|
||||||
|
}
|
||||||
|
if v.XferRateSml.Peak > 0 {
|
||||||
|
tcount++
|
||||||
|
}
|
||||||
|
qs.TgtXferStats[arn][Large] = XferStats{
|
||||||
|
Avg: v.XferRateLrg.Avg,
|
||||||
|
Curr: lcurrTgt,
|
||||||
|
Peak: math.Max(v.XferRateLrg.Peak, lcurrTgt),
|
||||||
|
}
|
||||||
|
qs.TgtXferStats[arn][Small] = XferStats{
|
||||||
|
Avg: v.XferRateSml.Avg,
|
||||||
|
Curr: scurrTgt,
|
||||||
|
Peak: math.Max(v.XferRateSml.Peak, scurrTgt),
|
||||||
|
}
|
||||||
|
if tcount > 0 {
|
||||||
|
qs.TgtXferStats[arn][Total] = XferStats{
|
||||||
|
Avg: (v.XferRateLrg.Avg + v.XferRateSml.Avg) / float64(tcount),
|
||||||
|
Curr: (scurrTgt + lcurrTgt) / float64(tcount),
|
||||||
|
Peak: totPeak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// calculate large, small and total transfer rates for a minio node
|
||||||
|
var lavg, lcurr, lpeak, savg, scurr, speak, totpeak float64
|
||||||
|
for _, v := range qs.TgtXferStats {
|
||||||
|
tot := v[Total]
|
||||||
|
lavg += v[Large].Avg
|
||||||
|
lcurr += v[Large].Curr
|
||||||
|
savg += v[Small].Avg
|
||||||
|
scurr += v[Small].Curr
|
||||||
|
totpeak = math.Max(math.Max(tot.Peak, totpeak), tot.Curr)
|
||||||
|
lpeak = math.Max(math.Max(v[Large].Peak, lpeak), v[Large].Curr)
|
||||||
|
speak = math.Max(math.Max(v[Small].Peak, speak), v[Small].Curr)
|
||||||
|
if lpeak > 0 || speak > 0 {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
lrg := XferStats{
|
||||||
|
Avg: lavg / float64(count),
|
||||||
|
Curr: lcurr / float64(count),
|
||||||
|
Peak: lpeak,
|
||||||
|
}
|
||||||
|
sml := XferStats{
|
||||||
|
Avg: savg / float64(count),
|
||||||
|
Curr: scurr / float64(count),
|
||||||
|
Peak: speak,
|
||||||
|
}
|
||||||
|
qs.XferStats[Large] = lrg
|
||||||
|
qs.XferStats[Small] = sml
|
||||||
|
qs.XferStats[Total] = XferStats{
|
||||||
|
Avg: (savg + lavg) / float64(count),
|
||||||
|
Curr: (lcurr + scurr) / float64(count),
|
||||||
|
Peak: totpeak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate queue totals for node and active workers in use for metrics
|
||||||
|
func (r *ReplicationStats) getNodeQueueStatsSummary() (qs ReplQNodeStats) {
|
||||||
|
qs.NodeName = globalLocalNodeName
|
||||||
|
qs.Uptime = UTCNow().Unix() - globalBootTime.Unix()
|
||||||
|
qs.ActiveWorkers = globalReplicationStats.ActiveWorkers()
|
||||||
|
qs.XferStats = make(map[RMetricName]XferStats)
|
||||||
|
qs.QStats = r.qCache.getSiteStats()
|
||||||
|
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
tx := newXferStats()
|
||||||
|
for _, brs := range r.Cache {
|
||||||
|
for _, v := range brs.Stats {
|
||||||
|
tx := tx.merge(*v.XferRateLrg)
|
||||||
|
tx = tx.merge(*v.XferRateSml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qs.XferStats[Total] = *tx
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplicationQueueStats holds overall queue stats for replication
|
||||||
|
type ReplicationQueueStats struct {
|
||||||
|
Nodes []ReplQNodeStats `json:"nodes"`
|
||||||
|
Uptime int64 `json:"uptime"`
|
||||||
}
|
}
|
||||||
|
|||||||
+1330
-166
File diff suppressed because it is too large
Load Diff
@@ -461,6 +461,345 @@ func BenchmarkDecodeBucketStatsMap(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalReplQNodeStats(t *testing.T) {
|
||||||
|
v := ReplQNodeStats{}
|
||||||
|
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 BenchmarkMarshalMsgReplQNodeStats(b *testing.B) {
|
||||||
|
v := ReplQNodeStats{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgReplQNodeStats(b *testing.B) {
|
||||||
|
v := ReplQNodeStats{}
|
||||||
|
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 BenchmarkUnmarshalReplQNodeStats(b *testing.B) {
|
||||||
|
v := ReplQNodeStats{}
|
||||||
|
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 TestEncodeDecodeReplQNodeStats(t *testing.T) {
|
||||||
|
v := ReplQNodeStats{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeReplQNodeStats Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := ReplQNodeStats{}
|
||||||
|
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 BenchmarkEncodeReplQNodeStats(b *testing.B) {
|
||||||
|
v := ReplQNodeStats{}
|
||||||
|
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 BenchmarkDecodeReplQNodeStats(b *testing.B) {
|
||||||
|
v := ReplQNodeStats{}
|
||||||
|
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 TestMarshalUnmarshalReplicationLastHour(t *testing.T) {
|
||||||
|
v := ReplicationLastHour{}
|
||||||
|
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 BenchmarkMarshalMsgReplicationLastHour(b *testing.B) {
|
||||||
|
v := ReplicationLastHour{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgReplicationLastHour(b *testing.B) {
|
||||||
|
v := ReplicationLastHour{}
|
||||||
|
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 BenchmarkUnmarshalReplicationLastHour(b *testing.B) {
|
||||||
|
v := ReplicationLastHour{}
|
||||||
|
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 TestEncodeDecodeReplicationLastHour(t *testing.T) {
|
||||||
|
v := ReplicationLastHour{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeReplicationLastHour Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := ReplicationLastHour{}
|
||||||
|
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 BenchmarkEncodeReplicationLastHour(b *testing.B) {
|
||||||
|
v := ReplicationLastHour{}
|
||||||
|
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 BenchmarkDecodeReplicationLastHour(b *testing.B) {
|
||||||
|
v := ReplicationLastHour{}
|
||||||
|
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 TestMarshalUnmarshalReplicationLastMinute(t *testing.T) {
|
||||||
|
v := ReplicationLastMinute{}
|
||||||
|
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 BenchmarkMarshalMsgReplicationLastMinute(b *testing.B) {
|
||||||
|
v := ReplicationLastMinute{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgReplicationLastMinute(b *testing.B) {
|
||||||
|
v := ReplicationLastMinute{}
|
||||||
|
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 BenchmarkUnmarshalReplicationLastMinute(b *testing.B) {
|
||||||
|
v := ReplicationLastMinute{}
|
||||||
|
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 TestEncodeDecodeReplicationLastMinute(t *testing.T) {
|
||||||
|
v := ReplicationLastMinute{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeReplicationLastMinute Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := ReplicationLastMinute{}
|
||||||
|
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 BenchmarkEncodeReplicationLastMinute(b *testing.B) {
|
||||||
|
v := ReplicationLastMinute{}
|
||||||
|
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 BenchmarkDecodeReplicationLastMinute(b *testing.B) {
|
||||||
|
v := ReplicationLastMinute{}
|
||||||
|
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 TestMarshalUnmarshalReplicationLatency(t *testing.T) {
|
func TestMarshalUnmarshalReplicationLatency(t *testing.T) {
|
||||||
v := ReplicationLatency{}
|
v := ReplicationLatency{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
@@ -573,3 +912,116 @@ func BenchmarkDecodeReplicationLatency(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalReplicationQueueStats(t *testing.T) {
|
||||||
|
v := ReplicationQueueStats{}
|
||||||
|
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 BenchmarkMarshalMsgReplicationQueueStats(b *testing.B) {
|
||||||
|
v := ReplicationQueueStats{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgReplicationQueueStats(b *testing.B) {
|
||||||
|
v := ReplicationQueueStats{}
|
||||||
|
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 BenchmarkUnmarshalReplicationQueueStats(b *testing.B) {
|
||||||
|
v := ReplicationQueueStats{}
|
||||||
|
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 TestEncodeDecodeReplicationQueueStats(t *testing.T) {
|
||||||
|
v := ReplicationQueueStats{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeReplicationQueueStats Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := ReplicationQueueStats{}
|
||||||
|
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 BenchmarkEncodeReplicationQueueStats(b *testing.B) {
|
||||||
|
v := ReplicationQueueStats{}
|
||||||
|
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 BenchmarkDecodeReplicationQueueStats(b *testing.B) {
|
||||||
|
v := ReplicationQueueStats{}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+117
-20
@@ -50,15 +50,37 @@ type BucketTargetSys struct {
|
|||||||
hcClient *madmin.AnonymousClient
|
hcClient *madmin.AnonymousClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type latencyStat struct {
|
||||||
|
lastmin lastMinuteLatency
|
||||||
|
curr time.Duration
|
||||||
|
avg time.Duration
|
||||||
|
peak time.Duration
|
||||||
|
N int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *latencyStat) update(d time.Duration) {
|
||||||
|
l.lastmin.add(d)
|
||||||
|
l.N++
|
||||||
|
if d > l.peak {
|
||||||
|
l.peak = d
|
||||||
|
}
|
||||||
|
l.curr = l.lastmin.getTotal().avg()
|
||||||
|
l.avg = time.Duration((int64(l.avg)*(l.N-1) + int64(l.curr)) / l.N)
|
||||||
|
}
|
||||||
|
|
||||||
// epHealth struct represents health of a replication target endpoint.
|
// epHealth struct represents health of a replication target endpoint.
|
||||||
type epHealth struct {
|
type epHealth struct {
|
||||||
Endpoint string
|
Endpoint string
|
||||||
Scheme string
|
Scheme string
|
||||||
Online bool
|
Online bool
|
||||||
|
lastOnline time.Time
|
||||||
|
lastHCAt time.Time
|
||||||
|
offlineDuration time.Duration
|
||||||
|
latency latencyStat
|
||||||
}
|
}
|
||||||
|
|
||||||
// isOffline returns current liveness result of remote target. Add endpoint to
|
// isOffline returns current liveness result of remote target. Add endpoint to
|
||||||
// healthcheck map if missing and default to online status
|
// healthCheck map if missing and default to online status
|
||||||
func (sys *BucketTargetSys) isOffline(ep *url.URL) bool {
|
func (sys *BucketTargetSys) isOffline(ep *url.URL) bool {
|
||||||
sys.hMutex.RLock()
|
sys.hMutex.RLock()
|
||||||
defer sys.hMutex.RUnlock()
|
defer sys.hMutex.RUnlock()
|
||||||
@@ -69,6 +91,16 @@ func (sys *BucketTargetSys) isOffline(ep *url.URL) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// markOffline sets endpoint to offline if network i/o timeout seen.
|
||||||
|
func (sys *BucketTargetSys) markOffline(ep *url.URL) {
|
||||||
|
sys.hMutex.Lock()
|
||||||
|
defer sys.hMutex.Unlock()
|
||||||
|
if h, ok := sys.hc[ep.Host]; ok {
|
||||||
|
h.Online = false
|
||||||
|
sys.hc[ep.Host] = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (sys *BucketTargetSys) initHC(ep *url.URL) {
|
func (sys *BucketTargetSys) initHC(ep *url.URL) {
|
||||||
sys.hMutex.Lock()
|
sys.hMutex.Lock()
|
||||||
sys.hc[ep.Host] = epHealth{
|
sys.hc[ep.Host] = epHealth{
|
||||||
@@ -107,11 +139,40 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
|||||||
if len(eps) > 0 {
|
if len(eps) > 0 {
|
||||||
cctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
cctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||||
m := make(map[string]epHealth, len(eps))
|
m := make(map[string]epHealth, len(eps))
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) {
|
for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) {
|
||||||
|
var lastOnline time.Time
|
||||||
|
var offline time.Duration
|
||||||
|
// var deploymentID string
|
||||||
|
sys.hMutex.RLock()
|
||||||
|
prev, ok := sys.hc[result.Endpoint.Host]
|
||||||
|
sys.hMutex.RUnlock()
|
||||||
|
if ok {
|
||||||
|
if prev.Online != result.Online || !result.Online {
|
||||||
|
if !prev.lastHCAt.IsZero() {
|
||||||
|
offline = time.Since(prev.lastHCAt) + prev.offlineDuration
|
||||||
|
} else {
|
||||||
|
offline = prev.offlineDuration
|
||||||
|
}
|
||||||
|
} else if result.Online {
|
||||||
|
offline = prev.offlineDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastOnline = prev.lastOnline
|
||||||
|
if result.Online {
|
||||||
|
lastOnline = time.Now()
|
||||||
|
}
|
||||||
|
l := prev.latency
|
||||||
|
l.update(time.Since(start))
|
||||||
m[result.Endpoint.Host] = epHealth{
|
m[result.Endpoint.Host] = epHealth{
|
||||||
Endpoint: result.Endpoint.Host,
|
Endpoint: result.Endpoint.Host,
|
||||||
Scheme: result.Endpoint.Scheme,
|
Scheme: result.Endpoint.Scheme,
|
||||||
Online: result.Online,
|
Online: result.Online,
|
||||||
|
lastOnline: lastOnline,
|
||||||
|
offlineDuration: offline,
|
||||||
|
lastHCAt: time.Now(),
|
||||||
|
latency: l,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
@@ -126,37 +187,66 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// periodically rebuild the healthcheck map from list of targets to clear
|
// periodically rebuild the healthCheck map from list of targets to clear
|
||||||
// out stale endpoints
|
// out stale endpoints
|
||||||
func (sys *BucketTargetSys) reloadHealthCheckers(ctx context.Context) {
|
func (sys *BucketTargetSys) reloadHealthCheckers(ctx context.Context) {
|
||||||
m := make(map[string]epHealth)
|
m := make(map[string]epHealth)
|
||||||
tgts := sys.ListTargets(ctx, "", "")
|
tgts := sys.ListTargets(ctx, "", "")
|
||||||
|
sys.hMutex.Lock()
|
||||||
for _, t := range tgts {
|
for _, t := range tgts {
|
||||||
if _, ok := m[t.Endpoint]; !ok {
|
if _, ok := m[t.Endpoint]; !ok {
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
if t.Secure {
|
if t.Secure {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
m[t.Endpoint] = epHealth{
|
epHealth := epHealth{
|
||||||
Online: true,
|
Online: true,
|
||||||
Endpoint: t.Endpoint,
|
Endpoint: t.Endpoint,
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
}
|
}
|
||||||
|
if prev, ok := sys.hc[t.Endpoint]; ok {
|
||||||
|
epHealth.lastOnline = prev.lastOnline
|
||||||
|
epHealth.offlineDuration = prev.offlineDuration
|
||||||
|
epHealth.lastHCAt = prev.lastHCAt
|
||||||
|
epHealth.latency = prev.latency
|
||||||
|
}
|
||||||
|
m[t.Endpoint] = epHealth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sys.hMutex.Lock()
|
|
||||||
// swap out the map
|
// swap out the map
|
||||||
sys.hc = m
|
sys.hc = m
|
||||||
sys.hMutex.Unlock()
|
sys.hMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sys *BucketTargetSys) healthStats() map[string]epHealth {
|
||||||
|
sys.hMutex.RLock()
|
||||||
|
defer sys.hMutex.RUnlock()
|
||||||
|
m := make(map[string]epHealth, len(sys.hc))
|
||||||
|
for k, v := range sys.hc {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// ListTargets lists bucket targets across tenant or for individual bucket, and returns
|
// ListTargets lists bucket targets across tenant or for individual bucket, and returns
|
||||||
// results filtered by arnType
|
// results filtered by arnType
|
||||||
func (sys *BucketTargetSys) ListTargets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget) {
|
func (sys *BucketTargetSys) ListTargets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget) {
|
||||||
|
h := sys.healthStats()
|
||||||
|
|
||||||
if bucket != "" {
|
if bucket != "" {
|
||||||
if ts, err := sys.ListBucketTargets(ctx, bucket); err == nil {
|
if ts, err := sys.ListBucketTargets(ctx, bucket); err == nil {
|
||||||
for _, t := range ts.Targets {
|
for _, t := range ts.Targets {
|
||||||
if string(t.Type) == arnType || arnType == "" {
|
if string(t.Type) == arnType || arnType == "" {
|
||||||
|
if hs, ok := h[t.URL().Host]; ok {
|
||||||
|
t.TotalDowntime = hs.offlineDuration
|
||||||
|
t.Online = hs.Online
|
||||||
|
t.LastOnline = hs.lastOnline
|
||||||
|
t.Latency = madmin.LatencyStat{
|
||||||
|
Curr: hs.latency.curr,
|
||||||
|
Avg: hs.latency.avg,
|
||||||
|
Max: hs.latency.peak,
|
||||||
|
}
|
||||||
|
}
|
||||||
targets = append(targets, t.Clone())
|
targets = append(targets, t.Clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,6 +258,16 @@ func (sys *BucketTargetSys) ListTargets(ctx context.Context, bucket, arnType str
|
|||||||
for _, tgts := range sys.targetsMap {
|
for _, tgts := range sys.targetsMap {
|
||||||
for _, t := range tgts {
|
for _, t := range tgts {
|
||||||
if string(t.Type) == arnType || arnType == "" {
|
if string(t.Type) == arnType || arnType == "" {
|
||||||
|
if hs, ok := h[t.URL().Host]; ok {
|
||||||
|
t.TotalDowntime = hs.offlineDuration
|
||||||
|
t.Online = hs.Online
|
||||||
|
t.LastOnline = hs.lastOnline
|
||||||
|
t.Latency = madmin.LatencyStat{
|
||||||
|
Curr: hs.latency.curr,
|
||||||
|
Avg: hs.latency.avg,
|
||||||
|
Max: hs.latency.peak,
|
||||||
|
}
|
||||||
|
}
|
||||||
targets = append(targets, t.Clone())
|
targets = append(targets, t.Clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +433,7 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRemoteTargetClient returns minio-go client for replication target instance
|
// GetRemoteTargetClient returns minio-go client for replication target instance
|
||||||
func (sys *BucketTargetSys) GetRemoteTargetClient(ctx context.Context, arn string) *TargetClient {
|
func (sys *BucketTargetSys) GetRemoteTargetClient(arn string) *TargetClient {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
defer sys.RUnlock()
|
defer sys.RUnlock()
|
||||||
return sys.arnRemotesMap[arn]
|
return sys.arnRemotesMap[arn]
|
||||||
@@ -362,7 +462,7 @@ func NewBucketTargetSys(ctx context.Context) *BucketTargetSys {
|
|||||||
hc: make(map[string]epHealth),
|
hc: make(map[string]epHealth),
|
||||||
hcClient: newHCClient(),
|
hcClient: newHCClient(),
|
||||||
}
|
}
|
||||||
// reload healthcheck endpoints map periodically to remove stale endpoints from the map.
|
// reload healthCheck endpoints map periodically to remove stale endpoints from the map.
|
||||||
go func() {
|
go func() {
|
||||||
rTimer := time.NewTimer(defaultHealthCheckReloadDuration)
|
rTimer := time.NewTimer(defaultHealthCheckReloadDuration)
|
||||||
defer rTimer.Stop()
|
defer rTimer.Stop()
|
||||||
@@ -389,22 +489,14 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
|||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
|
|
||||||
// Remove existingtarget and arn association
|
// Remove existingtarget and arn association
|
||||||
if tgts, ok := sys.targetsMap[bucket]; ok {
|
if stgts, ok := sys.targetsMap[bucket]; ok {
|
||||||
for _, t := range tgts {
|
for _, t := range stgts {
|
||||||
delete(sys.arnRemotesMap, t.Arn)
|
delete(sys.arnRemotesMap, t.Arn)
|
||||||
}
|
}
|
||||||
delete(sys.targetsMap, bucket)
|
delete(sys.targetsMap, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need for more if not adding anything
|
if tgts != nil {
|
||||||
if tgts == nil || tgts.Empty() {
|
|
||||||
globalBucketMonitor.DeleteBucket(bucket)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tgts.Targets) > 0 {
|
|
||||||
sys.targetsMap[bucket] = tgts.Targets
|
|
||||||
}
|
|
||||||
for _, tgt := range tgts.Targets {
|
for _, tgt := range tgts.Targets {
|
||||||
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -413,7 +505,11 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
|||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||||
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !tgts.Empty() {
|
||||||
sys.targetsMap[bucket] = tgts.Targets
|
sys.targetsMap[bucket] = tgts.Targets
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create minio-go clients for buckets having remote targets
|
// create minio-go clients for buckets having remote targets
|
||||||
@@ -424,9 +520,6 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
|
|||||||
}
|
}
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
if len(cfg.Targets) > 0 {
|
|
||||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
|
||||||
}
|
|
||||||
for _, tgt := range cfg.Targets {
|
for _, tgt := range cfg.Targets {
|
||||||
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -479,6 +572,8 @@ func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTar
|
|||||||
if target == nil {
|
if target == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
tgts := sys.targetsMap[bucket]
|
tgts := sys.targetsMap[bucket]
|
||||||
for _, tgt := range tgts {
|
for _, tgt := range tgts {
|
||||||
if tgt.Type == target.Type &&
|
if tgt.Type == target.Type &&
|
||||||
@@ -496,6 +591,8 @@ func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTar
|
|||||||
|
|
||||||
// getRemoteARNForPeer returns the remote target for a peer site in site replication
|
// getRemoteARNForPeer returns the remote target for a peer site in site replication
|
||||||
func (sys *BucketTargetSys) getRemoteARNForPeer(bucket string, peer madmin.PeerInfo) string {
|
func (sys *BucketTargetSys) getRemoteARNForPeer(bucket string, peer madmin.PeerInfo) string {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
tgts := sys.targetsMap[bucket]
|
tgts := sys.targetsMap[bucket]
|
||||||
for _, target := range tgts {
|
for _, target := range tgts {
|
||||||
ep, _ := url.Parse(peer.Endpoint)
|
ep, _ := url.Parse(peer.Endpoint)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user