mirror of
https://github.com/pgsty/minio.git
synced 2026-08-10 08:13:28 +03:00
Compare commits
344 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 216069d0da | |||
| eefa047974 | |||
| d8dad5c9ea | |||
| bf8a68879c | |||
| f3248a4b37 | |||
| d315d012a4 | |||
| bd9bf3693f | |||
| 15daa2e74a | |||
| 74759b05a5 | |||
| 82ce78a17c | |||
| 9af6c6ceef | |||
| 021372cc4c | |||
| b94ab07c2f | |||
| 7605d07bb2 | |||
| ccc5801112 | |||
| 7c72b25ef0 | |||
| 02c2ec3027 | |||
| 65c31fab12 | |||
| b6b68be052 | |||
| 15911c85f6 | |||
| 5a1612fe32 | |||
| bbb7ae156c | |||
| f9b8d1c699 | |||
| 1443b5927a | |||
| 35ef35b5c1 | |||
| 6806537eb3 | |||
| 64de61d15d | |||
| 22b7c8cd8a | |||
| c4d0c49a5f | |||
| 142a5b0dcd | |||
| 25db1e4eca | |||
| 47a48b6832 | |||
| e98309eb75 | |||
| 87051872a7 | |||
| a2aed12dcd | |||
| d8e6e76e89 | |||
| 8c33fdf5f4 | |||
| ad4e511026 | |||
| 4a562d6732 | |||
| a9082e4f79 | |||
| 6278679ffd | |||
| 0474791cf8 | |||
| 69f819e199 | |||
| f32efd5429 | |||
| 22c247a988 | |||
| 35d71682f6 | |||
| 3d6b88a60e | |||
| 26a0803388 | |||
| ae95384dd8 | |||
| 0f0dcf0c5e | |||
| 6f2406b0b6 | |||
| bb24346e04 | |||
| be45ffd8a4 | |||
| f986b0c493 | |||
| c9e87f0548 | |||
| 43468f4d47 | |||
| 91987d6f7a | |||
| 6b7c98bd0f | |||
| b829e80ecb | |||
| 6e38d0f3ab | |||
| 38342b1df5 | |||
| dbd4c2425e | |||
| 49ce85ee3d | |||
| eba378e4a1 | |||
| 442c50ff00 | |||
| d1448adbda | |||
| 5a21b1f353 | |||
| 123a2fb3a8 | |||
| 2f9e2147f5 | |||
| 75c6fc4f02 | |||
| f9e07d6143 | |||
| 1436858347 | |||
| 8030e12ba5 | |||
| a485b923bf | |||
| 0649aca219 | |||
| b210ea79bc | |||
| 68f80b5fe7 | |||
| e95825a42e | |||
| 931712dc46 | |||
| 54e544e03e | |||
| f86b9abf32 | |||
| 9ef7eda33a | |||
| c9e26401fa | |||
| e53f49e9a9 | |||
| 14f6ac9222 | |||
| b8474295af | |||
| 817e85a3e0 | |||
| fb5ce3b87a | |||
| 6fe028b7c5 | |||
| 1cd7f1e38d | |||
| 043fd8b536 | |||
| 669acbb032 | |||
| 086d8f036e | |||
| 394690dcfb | |||
| 398bca92ff | |||
| fb328b1a64 | |||
| 563f667e30 | |||
| c839b64f6a | |||
| 6425fec366 | |||
| d5059840ef | |||
| 65cba212e8 | |||
| 7a69c9c75a | |||
| 4a425cbac1 | |||
| 615169c4ec | |||
| 5cd9dcb844 | |||
| 54c5c88fe6 | |||
| 443250d135 | |||
| 9b5829c16e | |||
| d749aaab69 | |||
| 62df731006 | |||
| d0a0eb9738 | |||
| 66156b8230 | |||
| 5677f73794 | |||
| ef54200db7 | |||
| 7875efbf61 | |||
| 3e128c116e | |||
| 55a3310446 | |||
| fc03be7891 | |||
| b1b00a5055 | |||
| 2920b0fc6d | |||
| a30a55f3b1 | |||
| ecfb18b26a | |||
| 41fa8fa2d2 | |||
| e94e6adf91 | |||
| 7d433f16c4 | |||
| b06d7bf834 | |||
| b784e458cb | |||
| 9d96b18df0 | |||
| ad2ab6eb3e | |||
| f037c9b286 | |||
| 85912985b6 | |||
| 876f51a708 | |||
| f7d29b4a53 | |||
| 06557fe8be | |||
| 2131046427 | |||
| aaf1abc993 | |||
| 413549bcf5 | |||
| 9a799065b3 | |||
| fd2959fa3a | |||
| 07927e032a | |||
| 15bec32bb4 | |||
| e2b7a08c10 | |||
| ef2fc0f99e | |||
| d063596430 | |||
| bd2dc6c670 | |||
| 684399433b | |||
| b62791617c | |||
| e07c2ab868 | |||
| 203755793c | |||
| 883c98e26f | |||
| f5a20a5d06 | |||
| ef7177ebbd | |||
| 3637aad36e | |||
| 77db9686fb | |||
| c326e5a34e | |||
| c23c982593 | |||
| a3d666356c | |||
| 3cdbc2f414 | |||
| b92cdea578 | |||
| 5e629a99af | |||
| 99c4ffa34f | |||
| a7f266c907 | |||
| 57acacd5a7 | |||
| 42fb3cd95e | |||
| 855ed642c3 | |||
| 629503ff73 | |||
| e3a070e3de | |||
| 5b364bca1f | |||
| c5c1426262 | |||
| be18d435a2 | |||
| 7eea6cdb12 | |||
| 824c55b3a4 | |||
| 76913a9fd5 | |||
| 2f44dac14f | |||
| 5569acd95c | |||
| 1d0211d395 | |||
| 06cd0a636e | |||
| 7f7b489a3d | |||
| bb6f4d7633 | |||
| 6e24dff26a | |||
| e372e4e592 | |||
| 9571b0825e | |||
| 90e2cc3d4c | |||
| 0c0820caef | |||
| 9112ca4e29 | |||
| 8203cb9990 | |||
| d5bce978a8 | |||
| ec84bad882 | |||
| b53376a3a4 | |||
| 0ec722bc54 | |||
| 4640b13c66 | |||
| 1704abaf6b | |||
| ab34f0065c | |||
| e8c0a50862 | |||
| b963f69f34 | |||
| 02d8f3cdc8 | |||
| 7ae69accc0 | |||
| 701b89f377 |
@@ -1,5 +0,0 @@
|
||||
# Config file for markdownlint-cli
|
||||
MD033:
|
||||
allowed_elements:
|
||||
- details
|
||||
- summary
|
||||
@@ -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 .
|
||||
@@ -82,9 +82,9 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Test LDAP/OpenID/Etcd combo
|
||||
env:
|
||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||
ETCD_SERVER: ${{ matrix.etcd }}
|
||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||
run: |
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||
@@ -92,20 +92,20 @@ jobs:
|
||||
- name: Test with multiple OpenID providers
|
||||
if: matrix.openid == 'http://127.0.0.1:5556/dex'
|
||||
env:
|
||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||
ETCD_SERVER: ${{ matrix.etcd }}
|
||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||
OPENID_TEST_SERVER_2: "http://127.0.0.1:5557/dex"
|
||||
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||
_MINIO_OPENID_TEST_SERVER_2: "http://127.0.0.1:5557/dex"
|
||||
run: |
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||
make test-iam
|
||||
- name: Test with Access Management Plugin enabled
|
||||
env:
|
||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||
ETCD_SERVER: ${{ matrix.etcd }}
|
||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||
POLICY_PLUGIN_ENDPOINT: "http://127.0.0.1:8080"
|
||||
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||
_MINIO_POLICY_PLUGIN_TEST_ENDPOINT: "http://127.0.0.1:8080"
|
||||
run: |
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Markdown Linter
|
||||
|
||||
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:
|
||||
lint:
|
||||
name: Lint all docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Lint all docs
|
||||
run: |
|
||||
npm install -g markdownlint-cli
|
||||
markdownlint --fix '**/*.md' \
|
||||
--config /home/runner/work/minio/minio/.github/markdown-lint-cfg.yaml \
|
||||
--disable MD013 MD040 MD051
|
||||
@@ -3,7 +3,7 @@ name: Mint Tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- master
|
||||
|
||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||
# updated.
|
||||
@@ -37,8 +37,7 @@ jobs:
|
||||
|
||||
- name: build-minio
|
||||
run: |
|
||||
make install
|
||||
docker build . -t "minio/minio:${{ steps.vars.outputs.sha_short }}"
|
||||
TAG="minio/minio:${{ steps.vars.outputs.sha_short }}" make docker
|
||||
|
||||
- name: compress and encrypt
|
||||
run: |
|
||||
@@ -51,7 +50,16 @@ jobs:
|
||||
- name: standalone erasure
|
||||
run: |
|
||||
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ 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 rmi -f 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
|
||||
|
||||
@@ -11,8 +11,9 @@ x-minio-common: &minio-common
|
||||
MINIO_CI_CD: "on"
|
||||
MINIO_ROOT_USER: "minio"
|
||||
MINIO_ROOT_PASSWORD: "minio123"
|
||||
MINIO_COMPRESS: "true"
|
||||
MINIO_COMPRESS_MIMETYPES: "*"
|
||||
MINIO_COMPRESSION_ENABLE: "on"
|
||||
MINIO_COMPRESSION_MIME_TYPES: "*"
|
||||
MINIO_COMPRESSION_ALLOW_ENCRYPTION: "on"
|
||||
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
|
||||
@@ -11,8 +11,6 @@ x-minio-common: &minio-common
|
||||
MINIO_CI_CD: "on"
|
||||
MINIO_ROOT_USER: "minio"
|
||||
MINIO_ROOT_PASSWORD: "minio123"
|
||||
MINIO_COMPRESS: "true"
|
||||
MINIO_COMPRESS_MIMETYPES: "*"
|
||||
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Root lockdown tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||
# updated.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.20.x]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
- name: Start root lockdown tests
|
||||
run: |
|
||||
make test-root-disable
|
||||
@@ -8,8 +8,9 @@ export SECRET_KEY="$3"
|
||||
export JOB_NAME="$4"
|
||||
export MINT_MODE="full"
|
||||
|
||||
docker system prune -f
|
||||
docker volume prune -f
|
||||
docker system prune -f || true
|
||||
docker volume prune -f || true
|
||||
docker volume rm $(docker volume ls -f dangling=true) || true
|
||||
|
||||
## change working directory
|
||||
cd .github/workflows/mint
|
||||
@@ -17,35 +18,29 @@ cd .github/workflows/mint
|
||||
docker-compose -f minio-${MODE}.yaml up -d
|
||||
sleep 5m
|
||||
|
||||
docker run --rm --net=host \
|
||||
--name="mint-${MODE}-${JOB_NAME}" \
|
||||
-e SERVER_ENDPOINT="127.0.0.1:9000" \
|
||||
-e ACCESS_KEY="${ACCESS_KEY}" \
|
||||
-e SECRET_KEY="${SECRET_KEY}" \
|
||||
-e ENABLE_HTTPS=0 \
|
||||
-e MINT_MODE="${MINT_MODE}" \
|
||||
docker.io/minio/mint:edge \
|
||||
aws-sdk-go \
|
||||
aws-sdk-java \
|
||||
aws-sdk-php \
|
||||
aws-sdk-ruby \
|
||||
awscli \
|
||||
healthcheck \
|
||||
mc \
|
||||
minio-go \
|
||||
minio-java \
|
||||
minio-js \
|
||||
minio-py \
|
||||
s3cmd \
|
||||
s3select \
|
||||
versioning
|
||||
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}" \
|
||||
-e SERVER_ENDPOINT="nginx:9000" \
|
||||
-e ACCESS_KEY="${ACCESS_KEY}" \
|
||||
-e SECRET_KEY="${SECRET_KEY}" \
|
||||
-e ENABLE_HTTPS=0 \
|
||||
-e MINT_MODE="${MINT_MODE}" \
|
||||
docker.io/minio/mint:edge
|
||||
|
||||
docker-compose -f minio-${MODE}.yaml down || true
|
||||
sleep 10s
|
||||
|
||||
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,22 @@
|
||||
name: Shell formatting checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: runner / shfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: luizm/action-sh-checker@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SHFMT_OPTS: "-s"
|
||||
with:
|
||||
sh_checker_shellcheck_disable: true # disable for now
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.19.12
|
||||
check-latest: true
|
||||
- name: Get official govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
@@ -38,4 +38,6 @@ 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
|
||||
.bin/
|
||||
*.gz
|
||||
+2
-3
@@ -1,4 +1,4 @@
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
|
||||
|
||||
ARG RELEASE
|
||||
|
||||
@@ -27,9 +27,8 @@ COPY LICENSE /licenses/LICENSE
|
||||
RUN \
|
||||
microdnf clean all && \
|
||||
microdnf update --nodocs && \
|
||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
||||
microdnf install minisign --nodocs && \
|
||||
microdnf install curl ca-certificates shadow-utils util-linux gzip lsof tar net-tools iproute iputils jq minisign --nodocs && \
|
||||
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
||||
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.${RELEASE} -o /opt/bin/minio && \
|
||||
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
|
||||
|
||||
+3
-4
@@ -1,4 +1,4 @@
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -29,17 +29,16 @@ COPY LICENSE /licenses/LICENSE
|
||||
RUN \
|
||||
microdnf clean all && \
|
||||
microdnf update --nodocs && \
|
||||
microdnf install curl ca-certificates shadow-utils util-linux gzip lsof tar net-tools --nodocs && \
|
||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
||||
microdnf install minisign --nodocs && \
|
||||
microdnf install curl ca-certificates shadow-utils util-linux gzip lsof tar net-tools iproute iputils jq minisign --nodocs && \
|
||||
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /opt/bin/minio && \
|
||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.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/client/mc/release/linux-${TARGETARCH}/mc -o /opt/bin/mc && \
|
||||
gzip /opt/bin/mc && \
|
||||
microdnf clean all && \
|
||||
chmod +x /opt/bin/minio && \
|
||||
chmod +x /opt/bin/mc && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/verify-minio.sh && \
|
||||
/usr/bin/verify-minio.sh && \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -29,9 +29,8 @@ COPY LICENSE /licenses/LICENSE
|
||||
RUN \
|
||||
microdnf clean all && \
|
||||
microdnf update --nodocs && \
|
||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
||||
microdnf install minisign --nodocs && \
|
||||
microdnf install curl ca-certificates shadow-utils util-linux gzip lsof tar net-tools iproute iputils jq minisign --nodocs && \
|
||||
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips -o /opt/bin/minio && \
|
||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.sha256sum -o /opt/bin/minio.sha256sum && \
|
||||
|
||||
@@ -49,6 +49,10 @@ test: verifiers build ## builds minio, runs linters, tests
|
||||
@echo "Running unit tests"
|
||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
||||
|
||||
test-root-disable: install
|
||||
@echo "Running minio root lockdown tests"
|
||||
@env bash $(PWD)/buildscripts/disable-root.sh
|
||||
|
||||
test-decom: install
|
||||
@echo "Running minio decom tests"
|
||||
@env bash $(PWD)/docs/distributed/decom.sh
|
||||
@@ -70,6 +74,9 @@ test-iam: build ## verify IAM (external IDP, etcd backends)
|
||||
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
||||
|
||||
test-sio-error:
|
||||
@(env bash $(PWD)/docs/bucket/replication/sio-error.sh)
|
||||
|
||||
test-replication-2site:
|
||||
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
||||
|
||||
@@ -79,7 +86,7 @@ test-replication-3site:
|
||||
test-delete-replication:
|
||||
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||
|
||||
test-replication: install test-replication-2site test-replication-3site test-delete-replication ## verify multi site replication
|
||||
test-replication: install test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
|
||||
@echo "Running tests for replicating three sites"
|
||||
|
||||
test-site-replication-ldap: install ## verify automatic site replication
|
||||
|
||||
@@ -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.
|
||||
|
||||
> 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
|
||||
|
||||
@@ -241,7 +241,7 @@ mc admin update <minio alias, e.g., myminio>
|
||||
### 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.
|
||||
- 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.
|
||||
- `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.**
|
||||
|
||||
+85
-84
@@ -3,19 +3,19 @@
|
||||
|
||||
_init() {
|
||||
|
||||
shopt -s extglob
|
||||
shopt -s extglob
|
||||
|
||||
## Minimum required versions for build dependencies
|
||||
GIT_VERSION="1.0"
|
||||
GO_VERSION="1.16"
|
||||
OSX_VERSION="10.8"
|
||||
KNAME=$(uname -s)
|
||||
ARCH=$(uname -m)
|
||||
case "${KNAME}" in
|
||||
SunOS )
|
||||
ARCH=$(isainfo -k)
|
||||
;;
|
||||
esac
|
||||
## Minimum required versions for build dependencies
|
||||
GIT_VERSION="1.0"
|
||||
GO_VERSION="1.16"
|
||||
OSX_VERSION="10.8"
|
||||
KNAME=$(uname -s)
|
||||
ARCH=$(uname -m)
|
||||
case "${KNAME}" in
|
||||
SunOS)
|
||||
ARCH=$(isainfo -k)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
## FIXME:
|
||||
@@ -28,24 +28,23 @@ _init() {
|
||||
## }
|
||||
##
|
||||
readlink() {
|
||||
TARGET_FILE=$1
|
||||
TARGET_FILE=$1
|
||||
|
||||
cd `dirname $TARGET_FILE`
|
||||
TARGET_FILE=`basename $TARGET_FILE`
|
||||
cd $(dirname $TARGET_FILE)
|
||||
TARGET_FILE=$(basename $TARGET_FILE)
|
||||
|
||||
# Iterate down a (possible) chain of symlinks
|
||||
while [ -L "$TARGET_FILE" ]
|
||||
do
|
||||
TARGET_FILE=$(env readlink $TARGET_FILE)
|
||||
cd `dirname $TARGET_FILE`
|
||||
TARGET_FILE=`basename $TARGET_FILE`
|
||||
done
|
||||
# Iterate down a (possible) chain of symlinks
|
||||
while [ -L "$TARGET_FILE" ]; do
|
||||
TARGET_FILE=$(env readlink $TARGET_FILE)
|
||||
cd $(dirname $TARGET_FILE)
|
||||
TARGET_FILE=$(basename $TARGET_FILE)
|
||||
done
|
||||
|
||||
# Compute the canonicalized name by finding the physical path
|
||||
# for the directory we're in and appending the target file.
|
||||
PHYS_DIR=`pwd -P`
|
||||
RESULT=$PHYS_DIR/$TARGET_FILE
|
||||
echo $RESULT
|
||||
# Compute the canonicalized name by finding the physical path
|
||||
# for the directory we're in and appending the target file.
|
||||
PHYS_DIR=$(pwd -P)
|
||||
RESULT=$PHYS_DIR/$TARGET_FILE
|
||||
echo $RESULT
|
||||
}
|
||||
|
||||
## FIXME:
|
||||
@@ -59,84 +58,86 @@ readlink() {
|
||||
## }
|
||||
##
|
||||
check_minimum_version() {
|
||||
IFS='.' read -r -a varray1 <<< "$1"
|
||||
IFS='.' read -r -a varray2 <<< "$2"
|
||||
IFS='.' read -r -a varray1 <<<"$1"
|
||||
IFS='.' read -r -a varray2 <<<"$2"
|
||||
|
||||
for i in "${!varray1[@]}"; do
|
||||
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
||||
return 0
|
||||
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
for i in "${!varray1[@]}"; do
|
||||
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
||||
return 0
|
||||
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_is_supported_arch() {
|
||||
case "${ARCH}" in
|
||||
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x | loong64 | loongarch64 )
|
||||
return
|
||||
;;
|
||||
*)
|
||||
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x, loong64, loongarch64]"
|
||||
exit 1
|
||||
esac
|
||||
case "${ARCH}" in
|
||||
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x | loong64 | loongarch64)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x, loong64, loongarch64]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_is_supported_os() {
|
||||
case "${KNAME}" in
|
||||
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly | SunOS )
|
||||
return
|
||||
;;
|
||||
Darwin )
|
||||
osx_host_version=$(env sw_vers -productVersion)
|
||||
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
||||
echo "OSX version '${osx_host_version}' is not supported. Minimum supported version: ${OSX_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
return
|
||||
;;
|
||||
*)
|
||||
echo "OS '${KNAME}' is not supported. Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
||||
exit 1
|
||||
esac
|
||||
case "${KNAME}" in
|
||||
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly | SunOS)
|
||||
return
|
||||
;;
|
||||
Darwin)
|
||||
osx_host_version=$(env sw_vers -productVersion)
|
||||
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
||||
echo "OSX version '${osx_host_version}' is not supported. Minimum supported version: ${OSX_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
return
|
||||
;;
|
||||
*)
|
||||
echo "OS '${KNAME}' is not supported. Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_check_golang_env() {
|
||||
if ! which go >/dev/null 2>&1; then
|
||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
|
||||
exit 1
|
||||
fi
|
||||
if ! which go >/dev/null 2>&1; then
|
||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
||||
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
||||
echo "Go runtime version '${installed_go_version}' is unsupported. Minimum supported version: ${GO_VERSION} to compile."
|
||||
exit 1
|
||||
fi
|
||||
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
||||
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
||||
echo "Go runtime version '${installed_go_version}' is unsupported. Minimum supported version: ${GO_VERSION} to compile."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_check_deps() {
|
||||
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
||||
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
||||
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
||||
echo "Git version '${installed_git_version}' is not supported. Minimum supported version: ${GIT_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
||||
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
||||
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
||||
echo "Git version '${installed_git_version}' is not supported. Minimum supported version: ${GIT_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
## Check for supported arch
|
||||
assert_is_supported_arch
|
||||
## Check for supported arch
|
||||
assert_is_supported_arch
|
||||
|
||||
## Check for supported os
|
||||
assert_is_supported_os
|
||||
## Check for supported os
|
||||
assert_is_supported_os
|
||||
|
||||
## Check for Go environment
|
||||
assert_check_golang_env
|
||||
## Check for Go environment
|
||||
assert_check_golang_env
|
||||
|
||||
## Check for dependencies
|
||||
assert_check_deps
|
||||
## Check for dependencies
|
||||
assert_check_deps
|
||||
}
|
||||
|
||||
_init && main "$@"
|
||||
|
||||
@@ -5,33 +5,33 @@ set -e
|
||||
[ -n "$BASH_XTRACEFD" ] && set -x
|
||||
|
||||
function _init() {
|
||||
## All binaries are static make sure to disable CGO.
|
||||
export CGO_ENABLED=0
|
||||
## All binaries are static make sure to disable CGO.
|
||||
export CGO_ENABLED=0
|
||||
|
||||
## List of architectures and OS to test coss compilation.
|
||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
||||
## List of architectures and OS to test coss compilation.
|
||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips"
|
||||
}
|
||||
|
||||
function _build() {
|
||||
local osarch=$1
|
||||
IFS=/ read -r -a arr <<<"$osarch"
|
||||
os="${arr[0]}"
|
||||
arch="${arr[1]}"
|
||||
package=$(go list -f '{{.ImportPath}}')
|
||||
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
||||
local osarch=$1
|
||||
IFS=/ read -r -a arr <<<"$osarch"
|
||||
os="${arr[0]}"
|
||||
arch="${arr[1]}"
|
||||
package=$(go list -f '{{.ImportPath}}')
|
||||
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
||||
|
||||
# go build -trimpath to build the binary.
|
||||
export GOOS=$os
|
||||
export GOARCH=$arch
|
||||
export GO111MODULE=on
|
||||
go build -trimpath -tags kqueue -o /dev/null
|
||||
# go build -trimpath to build the binary.
|
||||
export GOOS=$os
|
||||
export GOARCH=$arch
|
||||
export GO111MODULE=on
|
||||
go build -trimpath -tags kqueue -o /dev/null
|
||||
}
|
||||
|
||||
function main() {
|
||||
echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
|
||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||
_build "${each_osarch}"
|
||||
done
|
||||
echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
|
||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||
_build "${each_osarch}"
|
||||
done
|
||||
}
|
||||
|
||||
_init && main "$@"
|
||||
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
|
||||
export MINIO_CI_CD=1
|
||||
killall -9 minio
|
||||
|
||||
rm -rf ${HOME}/tmp/dist
|
||||
|
||||
scheme="http"
|
||||
nr_servers=4
|
||||
|
||||
addr="localhost"
|
||||
args=""
|
||||
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||
args="$args $scheme://$addr:$((9100 + i))/${HOME}/tmp/dist/path1/$i"
|
||||
done
|
||||
|
||||
echo $args
|
||||
|
||||
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||
(minio server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
|
||||
done
|
||||
|
||||
sleep 10s
|
||||
|
||||
if [ ! -f ./mc ]; then
|
||||
wget --quiet -O ./mc https://dl.minio.io/client/mc/release/linux-amd64/./mc &&
|
||||
chmod +x mc
|
||||
fi
|
||||
|
||||
set +e
|
||||
|
||||
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
|
||||
|
||||
./mc ls minioadm/
|
||||
|
||||
./mc admin config set minioadm/ api root_access=off
|
||||
|
||||
sleep 3s # let things settle a little
|
||||
|
||||
./mc ls minioadm/
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "listing succeeded, 'minioadmin' was not disabled"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
killall -9 minio
|
||||
|
||||
export MINIO_API_ROOT_ACCESS=on
|
||||
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||
(minio server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
|
||||
done
|
||||
|
||||
set +e
|
||||
|
||||
./mc ls minioadm/
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "listing failed, 'minioadmin' should be enabled"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
killall -9 minio
|
||||
|
||||
rm -rf /tmp/multisitea/
|
||||
rm -rf /tmp/multisiteb/
|
||||
|
||||
echo "Setup site-replication and then disable root credentials"
|
||||
|
||||
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||
|
||||
minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
|
||||
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||
|
||||
sleep 20s
|
||||
|
||||
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
|
||||
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
|
||||
|
||||
./mc admin replicate add sitea siteb
|
||||
|
||||
./mc admin user add sitea foobar foo12345
|
||||
|
||||
./mc admin policy attach sitea/ consoleAdmin --user=foobar
|
||||
|
||||
./mc admin user info siteb foobar
|
||||
|
||||
killall -9 minio
|
||||
|
||||
echo "turning off root access, however site replication must continue"
|
||||
export MINIO_API_ROOT_ACCESS=off
|
||||
|
||||
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||
|
||||
minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
|
||||
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||
|
||||
sleep 20s
|
||||
|
||||
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
|
||||
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
|
||||
|
||||
./mc admin user add sitea foobar-admin foo12345
|
||||
|
||||
sleep 2s
|
||||
|
||||
./mc admin user info siteb foobar-admin
|
||||
@@ -6,88 +6,87 @@ set -x
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function start_minio_4drive() {
|
||||
start_port=$1
|
||||
start_port=$1
|
||||
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
mkdir ${WORK_DIR}
|
||||
C_PWD=${PWD}
|
||||
if [ ! -x "$PWD/mc" ]; then
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
mkdir ${WORK_DIR}
|
||||
C_PWD=${PWD}
|
||||
if [ ! -x "$PWD/mc" ]; then
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 5
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
fi
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 5
|
||||
"${PWD}/mc" mb --with-versioning minio/bucket
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
for i in $(seq 1 4); do
|
||||
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||
|
||||
"${PWD}/mc" mb --with-versioning minio/bucket
|
||||
sudo chown -R root. "${WORK_DIR}/disk${i}"
|
||||
|
||||
for i in $(seq 1 4); do
|
||||
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||
|
||||
sudo chown -R root. "${WORK_DIR}/disk${i}"
|
||||
sudo chown -R ${USER}. "${WORK_DIR}/disk${i}"
|
||||
done
|
||||
|
||||
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||
for vid in $("${PWD}/mc" ls --json --versions minio/bucket/testobj | jq -r .versionId); do
|
||||
"${PWD}/mc" cat --vid "${vid}" minio/bucket/testobj | md5sum
|
||||
done
|
||||
|
||||
sudo chown -R ${USER}. "${WORK_DIR}/disk${i}"
|
||||
done
|
||||
|
||||
for vid in $("${PWD}/mc" ls --json --versions minio/bucket/testobj | jq -r .versionId); do
|
||||
"${PWD}/mc" cat --vid "${vid}" minio/bucket/testobj | md5sum
|
||||
done
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
}
|
||||
|
||||
function main() {
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
|
||||
start_minio_4drive ${start_port}
|
||||
start_minio_4drive ${start_port}
|
||||
}
|
||||
|
||||
function purge()
|
||||
{
|
||||
rm -rf "$1"
|
||||
function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
( main "$@" )
|
||||
(main "$@")
|
||||
rv=$?
|
||||
purge "$WORK_DIR"
|
||||
exit "$rv"
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -4,89 +4,89 @@ trap 'cleanup $LINENO' ERR
|
||||
|
||||
# shellcheck disable=SC2120
|
||||
cleanup() {
|
||||
MINIO_VERSION=dev docker-compose \
|
||||
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||
rm -s -f
|
||||
docker volume prune -f
|
||||
MINIO_VERSION=dev docker-compose \
|
||||
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||
rm -s -f
|
||||
docker volume prune -f
|
||||
}
|
||||
|
||||
verify_checksum_after_heal() {
|
||||
local sum1
|
||||
sum1=$(curl -s "$2" | sha256sum);
|
||||
mc admin heal --json -r "$1" >/dev/null; # test after healing
|
||||
local sum1_heal
|
||||
sum1_heal=$(curl -s "$2" | sha256sum);
|
||||
local sum1
|
||||
sum1=$(curl -s "$2" | sha256sum)
|
||||
mc admin heal --json -r "$1" >/dev/null # test after healing
|
||||
local sum1_heal
|
||||
sum1_heal=$(curl -s "$2" | sha256sum)
|
||||
|
||||
if [ "${sum1_heal}" != "${sum1}" ]; then
|
||||
echo "mismatch expected ${sum1_heal}, got ${sum1}"
|
||||
exit 1;
|
||||
fi
|
||||
if [ "${sum1_heal}" != "${sum1}" ]; then
|
||||
echo "mismatch expected ${sum1_heal}, got ${sum1}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_checksum_mc() {
|
||||
local expected
|
||||
expected=$(mc cat "$1" | sha256sum)
|
||||
local got
|
||||
got=$(mc cat "$2" | sha256sum)
|
||||
local expected
|
||||
expected=$(mc cat "$1" | sha256sum)
|
||||
local got
|
||||
got=$(mc cat "$2" | sha256sum)
|
||||
|
||||
if [ "${expected}" != "${got}" ]; then
|
||||
echo "mismatch - expected ${expected}, got ${got}"
|
||||
exit 1;
|
||||
fi
|
||||
echo "matches - ${expected}, got ${got}"
|
||||
if [ "${expected}" != "${got}" ]; then
|
||||
echo "mismatch - expected ${expected}, got ${got}"
|
||||
exit 1
|
||||
fi
|
||||
echo "matches - ${expected}, got ${got}"
|
||||
}
|
||||
|
||||
add_alias() {
|
||||
for i in $(seq 1 4); do
|
||||
echo "... attempting to add alias $i"
|
||||
until (mc alias set minio http://127.0.0.1:9000 minioadmin minioadmin); do
|
||||
echo "...waiting... for 5secs" && sleep 5
|
||||
done
|
||||
done
|
||||
for i in $(seq 1 4); do
|
||||
echo "... attempting to add alias $i"
|
||||
until (mc alias set minio http://127.0.0.1:9000 minioadmin minioadmin); do
|
||||
echo "...waiting... for 5secs" && sleep 5
|
||||
done
|
||||
done
|
||||
|
||||
echo "Sleeping for nginx"
|
||||
sleep 20
|
||||
echo "Sleeping for nginx"
|
||||
sleep 20
|
||||
}
|
||||
|
||||
__init__() {
|
||||
sudo apt install curl -y
|
||||
export GOPATH=/tmp/gopath
|
||||
export PATH=${PATH}:${GOPATH}/bin
|
||||
sudo apt install curl -y
|
||||
export GOPATH=/tmp/gopath
|
||||
export PATH=${PATH}:${GOPATH}/bin
|
||||
|
||||
go install github.com/minio/mc@latest
|
||||
go install github.com/minio/mc@latest
|
||||
|
||||
TAG=minio/minio:dev make docker
|
||||
TAG=minio/minio:dev make docker
|
||||
|
||||
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
||||
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||
up -d --build
|
||||
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
||||
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||
up -d --build
|
||||
|
||||
add_alias
|
||||
add_alias
|
||||
|
||||
mc mb minio/minio-test/
|
||||
mc cp ./minio minio/minio-test/to-read/
|
||||
mc cp /etc/hosts minio/minio-test/to-read/hosts
|
||||
mc anonymous set download minio/minio-test
|
||||
mc mb minio/minio-test/
|
||||
mc cp ./minio minio/minio-test/to-read/
|
||||
mc cp /etc/hosts minio/minio-test/to-read/hosts
|
||||
mc anonymous set download minio/minio-test
|
||||
|
||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||
|
||||
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
|
||||
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
|
||||
|
||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
|
||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
|
||||
}
|
||||
|
||||
main() {
|
||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
|
||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
|
||||
|
||||
add_alias
|
||||
add_alias
|
||||
|
||||
verify_checksum_after_heal minio/minio-test http://127.0.0.1:9000/minio-test/to-read/hosts
|
||||
verify_checksum_after_heal minio/minio-test http://127.0.0.1:9000/minio-test/to-read/hosts
|
||||
|
||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||
|
||||
verify_checksum_mc /etc/hosts minio/minio-test/to-read/hosts
|
||||
verify_checksum_mc /etc/hosts minio/minio-test/to-read/hosts
|
||||
|
||||
cleanup
|
||||
cleanup
|
||||
}
|
||||
|
||||
( __init__ "$@" && main "$@" )
|
||||
(__init__ "$@" && main "$@")
|
||||
|
||||
@@ -6,5 +6,5 @@ export GORACE="history_size=7"
|
||||
export MINIO_API_REQUESTS_MAX=10000
|
||||
|
||||
for d in $(go list ./...); do
|
||||
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
||||
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
||||
done
|
||||
|
||||
@@ -6,67 +6,66 @@ set -x
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function start_minio_5drive() {
|
||||
start_port=$1
|
||||
start_port=$1
|
||||
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
"${WORK_DIR}/mc" cp --quiet -r "buildscripts/cicd-corpus/" "${WORK_DIR}/cicd-corpus/"
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 5
|
||||
|
||||
"${WORK_DIR}/mc" cp --quiet -r "buildscripts/cicd-corpus/" "${WORK_DIR}/cicd-corpus/"
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 5
|
||||
"${WORK_DIR}/mc" stat minio/bucket/testobj
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${WORK_DIR}/mc" stat minio/bucket/testobj
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
}
|
||||
|
||||
function main() {
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
|
||||
start_minio_5drive ${start_port}
|
||||
start_minio_5drive ${start_port}
|
||||
}
|
||||
|
||||
function purge()
|
||||
{
|
||||
rm -rf "$1"
|
||||
function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
( main "$@" )
|
||||
(main "$@")
|
||||
rv=$?
|
||||
purge "$WORK_DIR"
|
||||
exit "$rv"
|
||||
|
||||
+109
-104
@@ -6,146 +6,151 @@ set -x
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO_OLD=( "$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
MINIO_OLD=("$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function download_old_release() {
|
||||
if [ ! -f minio.RELEASE.2020-10-28T08-16-50Z ]; then
|
||||
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-10-28T08-16-50Z
|
||||
chmod a+x minio.RELEASE.2020-10-28T08-16-50Z
|
||||
fi
|
||||
if [ ! -f minio.RELEASE.2020-10-28T08-16-50Z ]; then
|
||||
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-10-28T08-16-50Z
|
||||
chmod a+x minio.RELEASE.2020-10-28T08-16-50Z
|
||||
fi
|
||||
}
|
||||
|
||||
function verify_rewrite() {
|
||||
start_port=$1
|
||||
start_port=$1
|
||||
|
||||
export MINIO_ACCESS_KEY=minio
|
||||
export MINIO_SECRET_KEY=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_ACCESS_KEY=minio
|
||||
export MINIO_SECRET_KEY=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 10
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 10
|
||||
"${WORK_DIR}/mc" mb minio/healing-rewrite-bucket --quiet --with-lock
|
||||
"${WORK_DIR}/mc" cp \
|
||||
buildscripts/verify-build.sh \
|
||||
minio/healing-rewrite-bucket/ \
|
||||
--disable-multipart --quiet
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
"${WORK_DIR}/mc" cp \
|
||||
buildscripts/verify-build.sh \
|
||||
minio/healing-rewrite-bucket/ \
|
||||
--disable-multipart --quiet
|
||||
|
||||
"${WORK_DIR}/mc" mb minio/healing-rewrite-bucket --quiet --with-lock
|
||||
"${WORK_DIR}/mc" cp \
|
||||
buildscripts/verify-build.sh \
|
||||
minio/healing-rewrite-bucket/ \
|
||||
--disable-multipart --quiet
|
||||
"${WORK_DIR}/mc" cp \
|
||||
buildscripts/verify-build.sh \
|
||||
minio/healing-rewrite-bucket/ \
|
||||
--disable-multipart --quiet
|
||||
|
||||
"${WORK_DIR}/mc" cp \
|
||||
buildscripts/verify-build.sh \
|
||||
minio/healing-rewrite-bucket/ \
|
||||
--disable-multipart --quiet
|
||||
kill ${pid}
|
||||
sleep 3
|
||||
|
||||
"${WORK_DIR}/mc" cp \
|
||||
buildscripts/verify-build.sh \
|
||||
minio/healing-rewrite-bucket/ \
|
||||
--disable-multipart --quiet
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 10
|
||||
|
||||
kill ${pid}
|
||||
sleep 3
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 10
|
||||
go build ./docs/debugging/s3-check-md5/
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-versions \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(
|
||||
cd inspects
|
||||
"${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**
|
||||
)
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
go build ./docs/debugging/s3-check-md5/
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-versions \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
||||
sleep 1
|
||||
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-versions \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(
|
||||
cd inspects
|
||||
"${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**
|
||||
)
|
||||
|
||||
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
||||
sleep 1
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-versions \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kill ${pid}
|
||||
kill ${pid}
|
||||
}
|
||||
|
||||
function main() {
|
||||
download_old_release
|
||||
download_old_release
|
||||
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
|
||||
verify_rewrite ${start_port}
|
||||
verify_rewrite ${start_port}
|
||||
}
|
||||
|
||||
function purge()
|
||||
{
|
||||
rm -rf "$1"
|
||||
function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
( main "$@" )
|
||||
(main "$@")
|
||||
rv=$?
|
||||
purge "$WORK_DIR"
|
||||
exit "$rv"
|
||||
|
||||
+128
-123
@@ -6,167 +6,172 @@ set -o pipefail
|
||||
set -x
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO_OLD=( "$PWD/minio.RELEASE.2021-11-24T23-19-33Z" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
MINIO_OLD=("$PWD/minio.RELEASE.2021-11-24T23-19-33Z" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
|
||||
function download_old_release() {
|
||||
if [ ! -f minio.RELEASE.2021-11-24T23-19-33Z ]; then
|
||||
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2021-11-24T23-19-33Z
|
||||
chmod a+x minio.RELEASE.2021-11-24T23-19-33Z
|
||||
fi
|
||||
if [ ! -f minio.RELEASE.2021-11-24T23-19-33Z ]; then
|
||||
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2021-11-24T23-19-33Z
|
||||
chmod a+x minio.RELEASE.2021-11-24T23-19-33Z
|
||||
fi
|
||||
}
|
||||
|
||||
function start_minio_16drive() {
|
||||
start_port=$1
|
||||
start_port=$1
|
||||
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export _MINIO_SHARD_DISKTIME_DELTA="5s" # do not change this as its needed for tests
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export _MINIO_SHARD_DISKTIME_DELTA="5s" # do not change this as its needed for tests
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 30
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 30
|
||||
shred --iterations=1 --size=5241856 - 1>"${WORK_DIR}/unaligned" 2>/dev/null
|
||||
"${WORK_DIR}/mc" mb minio/healing-shard-bucket --quiet
|
||||
"${WORK_DIR}/mc" cp \
|
||||
"${WORK_DIR}/unaligned" \
|
||||
minio/healing-shard-bucket/unaligned \
|
||||
--disable-multipart --quiet
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
## "unaligned" object name gets consistently distributed
|
||||
## to disks in following distribution order
|
||||
##
|
||||
## NOTE: if you change the name make sure to change the
|
||||
## distribution order present here
|
||||
##
|
||||
## [15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||||
|
||||
shred --iterations=1 --size=5241856 - 1>"${WORK_DIR}/unaligned" 2>/dev/null
|
||||
"${WORK_DIR}/mc" mb minio/healing-shard-bucket --quiet
|
||||
"${WORK_DIR}/mc" cp \
|
||||
"${WORK_DIR}/unaligned" \
|
||||
minio/healing-shard-bucket/unaligned \
|
||||
--disable-multipart --quiet
|
||||
## make sure to remove the "last" data shard
|
||||
rm -rf "${WORK_DIR}/xl14/healing-shard-bucket/unaligned"
|
||||
sleep 10
|
||||
## Heal the shard
|
||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||
## then remove any other data shard let's pick first disk
|
||||
## - 1st data shard.
|
||||
rm -rf "${WORK_DIR}/xl3/healing-shard-bucket/unaligned"
|
||||
sleep 10
|
||||
|
||||
## "unaligned" object name gets consistently distributed
|
||||
## to disks in following distribution order
|
||||
##
|
||||
## NOTE: if you change the name make sure to change the
|
||||
## distribution order present here
|
||||
##
|
||||
## [15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||||
go build ./docs/debugging/s3-check-md5/
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep CORRUPTED; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## make sure to remove the "last" data shard
|
||||
rm -rf "${WORK_DIR}/xl14/healing-shard-bucket/unaligned"
|
||||
sleep 10
|
||||
## Heal the shard
|
||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||
## then remove any other data shard let's pick first disk
|
||||
## - 1st data shard.
|
||||
rm -rf "${WORK_DIR}/xl3/healing-shard-bucket/unaligned"
|
||||
sleep 10
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
go build ./docs/debugging/s3-check-md5/
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep CORRUPTED; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 30
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 30
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(
|
||||
cd inspects
|
||||
"${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**
|
||||
)
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(cd inspects; "${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**)
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(
|
||||
cd inspects
|
||||
"${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**
|
||||
)
|
||||
|
||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
if ! ./s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(cd inspects; "${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**)
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
}
|
||||
|
||||
function main() {
|
||||
download_old_release
|
||||
download_old_release
|
||||
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
|
||||
start_minio_16drive ${start_port}
|
||||
start_minio_16drive ${start_port}
|
||||
}
|
||||
|
||||
function purge()
|
||||
{
|
||||
rm -rf "$1"
|
||||
function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
( main "$@" )
|
||||
(main "$@")
|
||||
rv=$?
|
||||
purge "$WORK_DIR"
|
||||
exit "$rv"
|
||||
|
||||
+186
-201
@@ -6,8 +6,8 @@ set -E
|
||||
set -o pipefail
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
@@ -25,285 +25,270 @@ export ENABLE_ADMIN=1
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" )
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR")
|
||||
|
||||
FILE_1_MB="$MINT_DATA_DIR/datafile-1-MB"
|
||||
FILE_65_MB="$MINT_DATA_DIR/datafile-65-MB"
|
||||
|
||||
FUNCTIONAL_TESTS="$WORK_DIR/functional-tests.sh"
|
||||
|
||||
function start_minio_fs()
|
||||
{
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
||||
sleep 10
|
||||
function start_minio_fs() {
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
||||
sleep 10
|
||||
}
|
||||
|
||||
function start_minio_erasure()
|
||||
{
|
||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
||||
sleep 15
|
||||
function start_minio_erasure() {
|
||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
||||
sleep 15
|
||||
}
|
||||
|
||||
function start_minio_erasure_sets()
|
||||
{
|
||||
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||
"${MINIO[@]}" server > "$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||
sleep 15
|
||||
function start_minio_erasure_sets() {
|
||||
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||
sleep 15
|
||||
}
|
||||
|
||||
function start_minio_pool_erasure_sets()
|
||||
{
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||
"${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||
"${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||
function start_minio_pool_erasure_sets() {
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||
|
||||
sleep 40
|
||||
sleep 40
|
||||
}
|
||||
|
||||
function start_minio_pool_erasure_sets_ipv6()
|
||||
{
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets-ipv6{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets-ipv6{5...8}"
|
||||
"${MINIO[@]}" server --address="[::1]:9000" > "$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||
"${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||
function start_minio_pool_erasure_sets_ipv6() {
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets-ipv6{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets-ipv6{5...8}"
|
||||
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||
|
||||
sleep 40
|
||||
sleep 40
|
||||
}
|
||||
|
||||
function start_minio_dist_erasure()
|
||||
{
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
||||
for i in $(seq 0 3); do
|
||||
"${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||
done
|
||||
function start_minio_dist_erasure() {
|
||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
||||
for i in $(seq 0 3); do
|
||||
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||
done
|
||||
|
||||
sleep 40
|
||||
sleep 40
|
||||
}
|
||||
|
||||
function run_test_fs()
|
||||
{
|
||||
start_minio_fs
|
||||
function run_test_fs() {
|
||||
start_minio_fs
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/fs-minio.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/fs-minio.log"
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/fs-minio.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/fs-minio.log"
|
||||
|
||||
return "$rv"
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_erasure_sets()
|
||||
{
|
||||
start_minio_erasure_sets
|
||||
function run_test_erasure_sets() {
|
||||
start_minio_erasure_sets
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/erasure-minio-sets.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/erasure-minio-sets.log"
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/erasure-minio-sets.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/erasure-minio-sets.log"
|
||||
|
||||
return "$rv"
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_pool_erasure_sets()
|
||||
{
|
||||
start_minio_pool_erasure_sets
|
||||
function run_test_pool_erasure_sets() {
|
||||
start_minio_pool_erasure_sets
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
for i in $(seq 0 1); do
|
||||
echo "server$i log:"
|
||||
cat "$WORK_DIR/pool-minio-900$i.log"
|
||||
done
|
||||
fi
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
for i in $(seq 0 1); do
|
||||
echo "server$i log:"
|
||||
cat "$WORK_DIR/pool-minio-900$i.log"
|
||||
done
|
||||
fi
|
||||
|
||||
for i in $(seq 0 1); do
|
||||
rm -f "$WORK_DIR/pool-minio-900$i.log"
|
||||
done
|
||||
for i in $(seq 0 1); do
|
||||
rm -f "$WORK_DIR/pool-minio-900$i.log"
|
||||
done
|
||||
|
||||
return "$rv"
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_pool_erasure_sets_ipv6()
|
||||
{
|
||||
start_minio_pool_erasure_sets_ipv6
|
||||
function run_test_pool_erasure_sets_ipv6() {
|
||||
start_minio_pool_erasure_sets_ipv6
|
||||
|
||||
export SERVER_ENDPOINT="[::1]:9000"
|
||||
export SERVER_ENDPOINT="[::1]:9000"
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
for i in $(seq 0 1); do
|
||||
echo "server$i log:"
|
||||
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||
done
|
||||
fi
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
for i in $(seq 0 1); do
|
||||
echo "server$i log:"
|
||||
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||
done
|
||||
fi
|
||||
|
||||
for i in $(seq 0 1); do
|
||||
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||
done
|
||||
for i in $(seq 0 1); do
|
||||
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||
done
|
||||
|
||||
return "$rv"
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_erasure()
|
||||
{
|
||||
start_minio_erasure
|
||||
function run_test_erasure() {
|
||||
start_minio_erasure
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/erasure-minio.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/erasure-minio.log"
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
cat "$WORK_DIR/erasure-minio.log"
|
||||
fi
|
||||
rm -f "$WORK_DIR/erasure-minio.log"
|
||||
|
||||
return "$rv"
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function run_test_dist_erasure()
|
||||
{
|
||||
start_minio_dist_erasure
|
||||
function run_test_dist_erasure() {
|
||||
start_minio_dist_erasure
|
||||
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||
rv=$?
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
echo "server1 log:"
|
||||
cat "$WORK_DIR/dist-minio-9000.log"
|
||||
echo "server2 log:"
|
||||
cat "$WORK_DIR/dist-minio-9001.log"
|
||||
echo "server3 log:"
|
||||
cat "$WORK_DIR/dist-minio-9002.log"
|
||||
echo "server4 log:"
|
||||
cat "$WORK_DIR/dist-minio-9003.log"
|
||||
fi
|
||||
if [ "$rv" -ne 0 ]; then
|
||||
echo "server1 log:"
|
||||
cat "$WORK_DIR/dist-minio-9000.log"
|
||||
echo "server2 log:"
|
||||
cat "$WORK_DIR/dist-minio-9001.log"
|
||||
echo "server3 log:"
|
||||
cat "$WORK_DIR/dist-minio-9002.log"
|
||||
echo "server4 log:"
|
||||
cat "$WORK_DIR/dist-minio-9003.log"
|
||||
fi
|
||||
|
||||
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
||||
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
||||
|
||||
return "$rv"
|
||||
return "$rv"
|
||||
}
|
||||
|
||||
function purge()
|
||||
{
|
||||
rm -rf "$1"
|
||||
function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
function __init__()
|
||||
{
|
||||
echo "Initializing environment"
|
||||
mkdir -p "$WORK_DIR"
|
||||
mkdir -p "$MINIO_CONFIG_DIR"
|
||||
mkdir -p "$MINT_DATA_DIR"
|
||||
function __init__() {
|
||||
echo "Initializing environment"
|
||||
mkdir -p "$WORK_DIR"
|
||||
mkdir -p "$MINIO_CONFIG_DIR"
|
||||
mkdir -p "$MINT_DATA_DIR"
|
||||
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
|
||||
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
|
||||
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
|
||||
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
|
||||
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
|
||||
|
||||
## version is purposefully set to '3' for minio to migrate configuration file
|
||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
||||
## version is purposefully set to '3' for minio to migrate configuration file
|
||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
||||
|
||||
if ! wget -q -O "$FUNCTIONAL_TESTS" https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh; then
|
||||
echo "failed to download https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh"
|
||||
exit 1
|
||||
fi
|
||||
if ! wget -q -O "$FUNCTIONAL_TESTS" https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh; then
|
||||
echo "failed to download https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed -i 's|-sS|-sSg|g' "$FUNCTIONAL_TESTS"
|
||||
chmod a+x "$FUNCTIONAL_TESTS"
|
||||
sed -i 's|-sS|-sSg|g' "$FUNCTIONAL_TESTS"
|
||||
chmod a+x "$FUNCTIONAL_TESTS"
|
||||
}
|
||||
|
||||
function main()
|
||||
{
|
||||
echo "Testing in FS setup"
|
||||
if ! run_test_fs; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
function main() {
|
||||
echo "Testing in FS setup"
|
||||
if ! run_test_fs; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Erasure setup"
|
||||
if ! run_test_erasure; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo "Testing in Erasure setup"
|
||||
if ! run_test_erasure; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Distributed Erasure setup"
|
||||
if ! run_test_dist_erasure; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo "Testing in Distributed Erasure setup"
|
||||
if ! run_test_dist_erasure; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Erasure setup as sets"
|
||||
if ! run_test_erasure_sets; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo "Testing in Erasure setup as sets"
|
||||
if ! run_test_erasure_sets; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Distributed Eraure expanded setup"
|
||||
if ! run_test_pool_erasure_sets; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo "Testing in Distributed Eraure expanded setup"
|
||||
if ! run_test_pool_erasure_sets; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
||||
if ! run_test_pool_erasure_sets_ipv6; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
||||
if ! run_test_pool_erasure_sets_ipv6; then
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
purge "$WORK_DIR"
|
||||
purge "$WORK_DIR"
|
||||
}
|
||||
|
||||
( __init__ "$@" && main "$@" )
|
||||
(__init__ "$@" && main "$@")
|
||||
rv=$?
|
||||
purge "$WORK_DIR"
|
||||
exit "$rv"
|
||||
|
||||
@@ -4,96 +4,93 @@ set -E
|
||||
set -o pipefail
|
||||
set -x
|
||||
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
|
||||
function start_minio() {
|
||||
start_port=$1
|
||||
start_port=$1
|
||||
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
unset MINIO_CI_CD
|
||||
unset CI
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
unset MINIO_CI_CD
|
||||
unset CI
|
||||
|
||||
args=()
|
||||
for i in $(seq 1 4); do
|
||||
args+=("http://localhost:$[${start_port}+$i]${WORK_DIR}/mnt/disk$i/ ")
|
||||
done
|
||||
args=()
|
||||
for i in $(seq 1 4); do
|
||||
args+=("http://localhost:$((start_port + i))${WORK_DIR}/mnt/disk$i/ ")
|
||||
done
|
||||
|
||||
for i in $(seq 1 4); do
|
||||
"${MINIO[@]}" --address ":$[$start_port+$i]" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
|
||||
done
|
||||
for i in $(seq 1 4); do
|
||||
"${MINIO[@]}" --address ":$((start_port + i))" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
|
||||
done
|
||||
|
||||
# Wait until all nodes return 403
|
||||
for i in $(seq 1 4); do
|
||||
while [ "$(curl -m 1 -s -o /dev/null -w "%{http_code}" http://localhost:$[$start_port+$i])" -ne "403" ]; do
|
||||
echo -n ".";
|
||||
sleep 1;
|
||||
done
|
||||
done
|
||||
# Wait until all nodes return 403
|
||||
for i in $(seq 1 4); do
|
||||
while [ "$(curl -m 1 -s -o /dev/null -w "%{http_code}" http://localhost:$((start_port + i)))" -ne "403" ]; do
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
done
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Prepare fake disks with losetup
|
||||
function prepare_block_devices() {
|
||||
set -e
|
||||
mkdir -p ${WORK_DIR}/disks/ ${WORK_DIR}/mnt/
|
||||
sudo modprobe loop
|
||||
for i in 1 2 3 4; do
|
||||
dd if=/dev/zero of=${WORK_DIR}/disks/img.${i} bs=1M count=2000
|
||||
device=$(sudo losetup --find --show ${WORK_DIR}/disks/img.${i})
|
||||
sudo mkfs.ext4 -F ${device}
|
||||
mkdir -p ${WORK_DIR}/mnt/disk${i}/
|
||||
sudo mount ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||
sudo chown "$(id -u):$(id -g)" ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||
done
|
||||
set +e
|
||||
set -e
|
||||
mkdir -p ${WORK_DIR}/disks/ ${WORK_DIR}/mnt/
|
||||
sudo modprobe loop
|
||||
for i in 1 2 3 4; do
|
||||
dd if=/dev/zero of=${WORK_DIR}/disks/img.${i} bs=1M count=2000
|
||||
device=$(sudo losetup --find --show ${WORK_DIR}/disks/img.${i})
|
||||
sudo mkfs.ext4 -F ${device}
|
||||
mkdir -p ${WORK_DIR}/mnt/disk${i}/
|
||||
sudo mount ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||
sudo chown "$(id -u):$(id -g)" ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||
done
|
||||
set +e
|
||||
}
|
||||
|
||||
# Start a distributed MinIO setup, unmount one disk and check if it is formatted
|
||||
function main() {
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
start_minio ${start_port}
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
start_minio ${start_port}
|
||||
|
||||
# Unmount the disk, after the unmount the device id
|
||||
# /tmp/xxx/mnt/disk4 will be the same as '/' and it
|
||||
# will be detected as root disk
|
||||
while [ "$u" != "0" ]; do
|
||||
sudo umount ${WORK_DIR}/mnt/disk4/
|
||||
u=$?
|
||||
sleep 1
|
||||
done
|
||||
# Unmount the disk, after the unmount the device id
|
||||
# /tmp/xxx/mnt/disk4 will be the same as '/' and it
|
||||
# will be detected as root disk
|
||||
while [ "$u" != "0" ]; do
|
||||
sudo umount ${WORK_DIR}/mnt/disk4/
|
||||
u=$?
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Wait until MinIO self heal kicks in
|
||||
sleep 60
|
||||
# Wait until MinIO self heal kicks in
|
||||
sleep 60
|
||||
|
||||
if [ -f ${WORK_DIR}/mnt/disk4/.minio.sys/format.json ]; then
|
||||
echo "A root disk is formatted unexpectedely"
|
||||
cat "${WORK_DIR}/server4.log"
|
||||
exit -1
|
||||
fi
|
||||
if [ -f ${WORK_DIR}/mnt/disk4/.minio.sys/format.json ]; then
|
||||
echo "A root disk is formatted unexpectedely"
|
||||
cat "${WORK_DIR}/server4.log"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
pkill minio
|
||||
sudo umount ${WORK_DIR}/mnt/disk{1..3}/
|
||||
sudo rm /dev/minio-loopdisk*
|
||||
rm -rf "$WORK_DIR"
|
||||
pkill minio
|
||||
sudo umount ${WORK_DIR}/mnt/disk{1..3}/
|
||||
sudo rm /dev/minio-loopdisk*
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
|
||||
( prepare_block_devices )
|
||||
( main "$@" )
|
||||
(prepare_block_devices)
|
||||
(main "$@")
|
||||
rv=$?
|
||||
|
||||
cleanup
|
||||
exit "$rv"
|
||||
|
||||
|
||||
+98
-102
@@ -5,139 +5,135 @@ set -E
|
||||
set -o pipefail
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
|
||||
function start_minio_3_node() {
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
start_port=$2
|
||||
args=""
|
||||
for i in $(seq 1 3); do
|
||||
args="$args http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/"
|
||||
done
|
||||
|
||||
"${MINIO[@]}" --address ":$[$start_port+1]" $args > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||
pid1=$!
|
||||
disown ${pid1}
|
||||
|
||||
"${MINIO[@]}" --address ":$[$start_port+2]" $args > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
||||
pid2=$!
|
||||
disown $pid2
|
||||
|
||||
"${MINIO[@]}" --address ":$[$start_port+3]" $args > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
||||
pid3=$!
|
||||
disown $pid3
|
||||
|
||||
sleep "$1"
|
||||
|
||||
if ! ps -p $pid1 1>&2 > /dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! ps -p $pid2 1>&2 > /dev/null; then
|
||||
echo "server2 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server2.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! ps -p $pid3 1>&2 > /dev/null; then
|
||||
echo "server3 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server3.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! pkill minio; then
|
||||
start_port=$2
|
||||
args=""
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
|
||||
done
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 1;
|
||||
if pgrep minio; then
|
||||
# forcibly killing, to proceed further properly.
|
||||
if ! pkill -9 minio; then
|
||||
echo "no minio process running anymore, proceed."
|
||||
"${MINIO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||
pid1=$!
|
||||
disown ${pid1}
|
||||
|
||||
"${MINIO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
||||
pid2=$!
|
||||
disown $pid2
|
||||
|
||||
"${MINIO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
||||
pid3=$!
|
||||
disown $pid3
|
||||
|
||||
sleep "$1"
|
||||
|
||||
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if ! ps -p $pid2 1>&2 >/dev/null; then
|
||||
echo "server2 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server2.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! ps -p $pid3 1>&2 >/dev/null; then
|
||||
echo "server3 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server3.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! pkill minio; then
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
if pgrep minio; then
|
||||
# forcibly killing, to proceed further properly.
|
||||
if ! pkill -9 minio; then
|
||||
echo "no minio process running anymore, proceed."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function check_online() {
|
||||
if grep -q 'Unable to initialize sub-systems' ${WORK_DIR}/dist-minio-*.log; then
|
||||
echo "1"
|
||||
fi
|
||||
if grep -q 'Unable to initialize sub-systems' ${WORK_DIR}/dist-minio-*.log; then
|
||||
echo "1"
|
||||
fi
|
||||
}
|
||||
|
||||
function purge()
|
||||
{
|
||||
rm -rf "$1"
|
||||
function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
function __init__()
|
||||
{
|
||||
echo "Initializing environment"
|
||||
mkdir -p "$WORK_DIR"
|
||||
mkdir -p "$MINIO_CONFIG_DIR"
|
||||
function __init__() {
|
||||
echo "Initializing environment"
|
||||
mkdir -p "$WORK_DIR"
|
||||
mkdir -p "$MINIO_CONFIG_DIR"
|
||||
|
||||
## version is purposefully set to '3' for minio to migrate configuration file
|
||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
||||
## version is purposefully set to '3' for minio to migrate configuration file
|
||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
||||
}
|
||||
|
||||
function perform_test() {
|
||||
start_minio_3_node 120 $2
|
||||
start_minio_3_node 120 $2
|
||||
|
||||
echo "Testing Distributed Erasure setup healing of drives"
|
||||
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
||||
echo "Testing Distributed Erasure setup healing of drives"
|
||||
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
||||
|
||||
rm -rf ${WORK_DIR}/${1}/*/
|
||||
rm -rf ${WORK_DIR}/${1}/*/
|
||||
|
||||
start_minio_3_node 120 $2
|
||||
start_minio_3_node 120 $2
|
||||
|
||||
rv=$(check_online)
|
||||
if [ "$rv" == "1" ]; then
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
pkill -9 minio
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
rv=$(check_online)
|
||||
if [ "$rv" == "1" ]; then
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
pkill -9 minio
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function main()
|
||||
{
|
||||
# use same ports for all tests
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
function main() {
|
||||
# use same ports for all tests
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
|
||||
perform_test "2" ${start_port}
|
||||
perform_test "1" ${start_port}
|
||||
perform_test "3" ${start_port}
|
||||
perform_test "2" ${start_port}
|
||||
perform_test "1" ${start_port}
|
||||
perform_test "3" ${start_port}
|
||||
}
|
||||
|
||||
( __init__ "$@" && main "$@" )
|
||||
(__init__ "$@" && main "$@")
|
||||
rv=$?
|
||||
purge "$WORK_DIR"
|
||||
exit "$rv"
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
||||
acl := &accessControlPolicy{}
|
||||
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
||||
if terr, ok := err.(*xml.SyntaxError); ok && terr.Msg == io.EOF.Error() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader),
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML),
|
||||
r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/kes-go"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||
@@ -56,9 +56,7 @@ const (
|
||||
// specified in the quota configuration will be applied by default
|
||||
// to enforce total quota for the specified bucket.
|
||||
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "PutBucketQuotaConfig")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -110,9 +108,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
||||
|
||||
// GetBucketQuotaConfigHandler - gets bucket quota configuration
|
||||
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetBucketQuotaConfig")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -145,9 +141,8 @@ func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
||||
|
||||
// SetRemoteTargetHandler - sets a remote target for bucket
|
||||
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)
|
||||
bucket := pathClean(vars["bucket"])
|
||||
update := r.Form.Get("update") == "true"
|
||||
@@ -210,6 +205,11 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
if globalSiteReplicationSys.isEnabled() && !update {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetDenyAddError, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if update {
|
||||
// overlay the updates on existing target
|
||||
tgt := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, target.Arn)
|
||||
@@ -220,14 +220,14 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
||||
for _, op := range ops {
|
||||
switch op {
|
||||
case madmin.CredentialsUpdateType:
|
||||
if globalSiteReplicationSys.isEnabled() {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetDenyEditError, err), r.URL)
|
||||
return
|
||||
if !globalSiteReplicationSys.isEnabled() {
|
||||
// credentials update is possible only in bucket replication. User will never
|
||||
// know the site replicator creds.
|
||||
tgt.Credentials = target.Credentials
|
||||
tgt.TargetBucket = target.TargetBucket
|
||||
tgt.Secure = target.Secure
|
||||
tgt.Endpoint = target.Endpoint
|
||||
}
|
||||
tgt.Credentials = target.Credentials
|
||||
tgt.TargetBucket = target.TargetBucket
|
||||
tgt.Secure = target.Secure
|
||||
tgt.Endpoint = target.Endpoint
|
||||
case madmin.SyncUpdateType:
|
||||
tgt.ReplicationSync = target.ReplicationSync
|
||||
case madmin.ProxyUpdateType:
|
||||
@@ -284,9 +284,8 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
||||
// ListRemoteTargetsHandler - lists remote target(s) for a bucket or gets a target
|
||||
// for a particular ARN type
|
||||
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)
|
||||
bucket := pathClean(vars["bucket"])
|
||||
arnType := vars["type"]
|
||||
@@ -319,9 +318,8 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
||||
|
||||
// RemoveRemoteTargetHandler - removes a remote target for bucket with specified ARN
|
||||
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)
|
||||
bucket := pathClean(vars["bucket"])
|
||||
arn := vars["arn"]
|
||||
@@ -363,8 +361,7 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
||||
|
||||
// ExportBucketMetadataHandler - exports all bucket metadata as a zipped file
|
||||
func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ExportBucketMetadata")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
bucket := pathClean(r.Form.Get("bucket"))
|
||||
// Get current object layer instance.
|
||||
@@ -455,7 +452,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
return
|
||||
}
|
||||
case bucketLifecycleConfig:
|
||||
config, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||
config, _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||
if err != nil {
|
||||
if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) {
|
||||
continue
|
||||
@@ -647,9 +644,7 @@ 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.
|
||||
// 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) {
|
||||
ctx := newContext(r, w, "ImportBucketMetadata")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
|
||||
@@ -700,7 +695,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
}
|
||||
if _, ok := bucketMap[bucket]; !ok {
|
||||
opts := MakeBucketOptions{
|
||||
LockEnabled: config.ObjectLockEnabled == "Enabled",
|
||||
LockEnabled: config.Enabled(),
|
||||
}
|
||||
err = objectAPI.MakeBucket(ctx, bucket, opts)
|
||||
if err != nil {
|
||||
@@ -1064,8 +1059,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
||||
// to the connected HTTP client.
|
||||
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ReplicationDiff")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
@@ -1124,3 +1118,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, iampolicy.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/kes-go"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/config"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
@@ -168,10 +168,16 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
||||
}
|
||||
case errors.Is(err, errPolicyInUse):
|
||||
apiErr = APIError{
|
||||
Code: "XMinioAdminPolicyInUse",
|
||||
Code: "XMinioIAMPolicyInUse",
|
||||
Description: "The policy cannot be removed, as it is in use",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
case errors.Is(err, errSessionPolicyTooLarge):
|
||||
apiErr = APIError{
|
||||
Code: "XMinioIAMServiceAccountSessionPolicyTooLarge",
|
||||
Description: err.Error(),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
case errors.Is(err, kes.ErrKeyExists):
|
||||
apiErr = APIError{
|
||||
Code: "XMinioKMSKeyExists",
|
||||
@@ -220,12 +226,10 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
||||
// toAdminAPIErrCode - converts errErasureWriteQuorum error to admin API
|
||||
// specific error.
|
||||
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
||||
switch err {
|
||||
case errErasureWriteQuorum:
|
||||
if errors.Is(err, errErasureWriteQuorum) {
|
||||
return ErrAdminConfigNoQuorum
|
||||
default:
|
||||
return toAPIErrorCode(ctx, err)
|
||||
}
|
||||
return toAPIErrorCode(ctx, err)
|
||||
}
|
||||
|
||||
// wraps export error for more context
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/cache"
|
||||
"github.com/minio/minio/internal/config/etcd"
|
||||
@@ -43,9 +43,7 @@ import (
|
||||
|
||||
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
||||
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DeleteConfigKV")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -83,7 +81,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateConfig(cfg, subSys); err != nil {
|
||||
if err = validateConfig(ctx, cfg, subSys); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -149,9 +147,7 @@ type setConfigResult struct {
|
||||
|
||||
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
||||
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SetConfigKV")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -211,7 +207,12 @@ func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (re
|
||||
return
|
||||
}
|
||||
|
||||
if verr := validateConfig(result.Cfg, result.SubSys); verr != nil {
|
||||
tgts, err := config.ParseConfigTargetID(bytes.NewReader(kvBytes))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx = context.WithValue(ctx, config.ContextKeyForTargetFromConfig, tgts)
|
||||
if verr := validateConfig(ctx, result.Cfg, result.SubSys); verr != nil {
|
||||
err = badConfigErr{Err: verr}
|
||||
return
|
||||
}
|
||||
@@ -236,10 +237,10 @@ func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (re
|
||||
// 1. `subsys:target` -> request for config of a single subsystem and target pair.
|
||||
// 2. `subsys:` -> request for config of a single subsystem and the default target.
|
||||
// 3. `subsys` -> request for config of all targets for the given subsystem.
|
||||
//
|
||||
// This is a reporting API and config secrets are redacted in the response.
|
||||
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetConfigKV")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -263,7 +264,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
}
|
||||
|
||||
subSysConfigs, err := cfg.GetSubsysInfo(subSys, target)
|
||||
subSysConfigs, err := cfg.GetSubsysInfo(subSys, target, true)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -285,9 +286,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ClearConfigHistoryKV")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -320,9 +319,7 @@ func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *
|
||||
|
||||
// RestoreConfigHistoryKVHandler - restores a config with KV settings for the given KV id.
|
||||
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "RestoreConfigHistoryKV")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -353,7 +350,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateConfig(cfg, ""); err != nil {
|
||||
if err = validateConfig(ctx, cfg, ""); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -368,9 +365,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
||||
|
||||
// ListConfigHistoryKVHandler - lists all the KV ids.
|
||||
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListConfigHistoryKV")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -408,9 +403,7 @@ func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *h
|
||||
|
||||
// HelpConfigKVHandler - GET /minio/admin/v3/help-config-kv?subSys={subSys}&key={key}
|
||||
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "HelpConfigKV")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -435,9 +428,7 @@ func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
// SetConfigHandler - PUT /minio/admin/v3/config
|
||||
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SetConfig")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -464,7 +455,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateConfig(cfg, ""); err != nil {
|
||||
if err = validateConfig(ctx, cfg, ""); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -485,11 +476,11 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
// GetConfigHandler - GET /minio/admin/v3/config
|
||||
// Get config.json of this minio setup.
|
||||
//
|
||||
// This endpoint is mainly for exporting and backing up the configuration.
|
||||
// Secrets are not redacted.
|
||||
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetConfig")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -502,7 +493,7 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
||||
hkvs := config.HelpSubSysMap[""]
|
||||
for _, hkv := range hkvs {
|
||||
// We ignore the error below, as we cannot get one.
|
||||
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "")
|
||||
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "", false)
|
||||
|
||||
for _, item := range cfgSubsysItems {
|
||||
off := item.Config.Get(config.Enable) == config.EnableOff
|
||||
@@ -541,7 +532,7 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
||||
// setLoggerWebhookSubnetProxy - Sets the logger webhook's subnet proxy value to
|
||||
// one being set for subnet proxy
|
||||
func setLoggerWebhookSubnetProxy(subSys string, cfg config.Config) bool {
|
||||
if subSys == config.SubnetSubSys {
|
||||
if subSys == config.SubnetSubSys || subSys == config.LoggerWebhookSubSys {
|
||||
subnetWebhookCfg := cfg[config.LoggerWebhookSubSys][subnet.LoggerWebhookName]
|
||||
loggerWebhookSubnetProxy := subnetWebhookCfg.Get(logger.Proxy)
|
||||
subnetProxy := cfg[config.SubnetSubSys][config.Default].Get(logger.Proxy)
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/config"
|
||||
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"github.com/minio/pkg/ldap"
|
||||
)
|
||||
|
||||
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)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
@@ -125,7 +125,7 @@ func (a adminAPIHandlers) addOrUpdateIDPHandler(ctx context.Context, w http.Resp
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateConfig(cfg, subSys); err != nil {
|
||||
if err = validateConfig(ctx, cfg, subSys); err != nil {
|
||||
|
||||
var validationErr ldap.Validation
|
||||
if errors.As(err, &validationErr) {
|
||||
@@ -198,30 +198,27 @@ func handleCreateUpdateValidation(s config.Config, subSys, cfgTarget string, isU
|
||||
//
|
||||
// PUT <admin-prefix>/idp-cfg/openid/_ -> create (default) named config `_`
|
||||
func (a adminAPIHandlers) AddIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AddIdentityProviderCfg")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
a.addOrUpdateIDPHandler(ctx, w, r, false)
|
||||
addOrUpdateIDPHandler(ctx, w, r, false)
|
||||
}
|
||||
|
||||
// UpdateIdentityProviderCfg: updates an existing IDP config for openid/ldap.
|
||||
//
|
||||
// PATCH <admin-prefix>/idp-cfg/openid/dex1 -> update named config `dex1`
|
||||
// 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) {
|
||||
ctx := newContext(r, w, "UpdateIdentityProviderCfg")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
a.addOrUpdateIDPHandler(ctx, w, r, true)
|
||||
addOrUpdateIDPHandler(ctx, w, r, true)
|
||||
}
|
||||
|
||||
// ListIdentityProviderCfg:
|
||||
//
|
||||
// GET <admin-prefix>/idp-cfg/openid -> lists openid provider configs.
|
||||
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListIdentityProviderCfg")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -274,8 +271,7 @@ func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http
|
||||
//
|
||||
// GET <admin-prefix>/idp-cfg/openid/dex_test
|
||||
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetIdentityProviderCfg")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -334,9 +330,7 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
||||
//
|
||||
// DELETE <admin-prefix>/idp-cfg/openid/dex_test
|
||||
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DeleteIdentityProviderCfg")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -422,7 +416,17 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err = validateConfig(cfg, subSys); err != nil {
|
||||
if err = validateConfig(ctx, cfg, subSys); err != nil {
|
||||
|
||||
var validationErr ldap.Validation
|
||||
if errors.As(err, &validationErr) {
|
||||
// If we got an LDAP validation error, we need to send appropriate
|
||||
// error message back to client (likely mc).
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigLDAPValidation),
|
||||
validationErr.FormatError(), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
@@ -45,9 +45,7 @@ import (
|
||||
//
|
||||
// When all query parameters are omitted, returns mappings for all policies.
|
||||
func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListLDAPPolicyMappingEntities")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Check authorization.
|
||||
|
||||
@@ -94,9 +92,7 @@ func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r
|
||||
//
|
||||
// POST <admin-prefix>/idp/ldap/policy/{operation}
|
||||
func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AttachDetachPolicyLDAP")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Check authorization.
|
||||
|
||||
@@ -150,7 +146,7 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
// Call IAM subsystem
|
||||
updatedAt, addedOrRemoved, err := globalIAMSys.PolicyDBUpdateLDAP(ctx, isAttach, par)
|
||||
updatedAt, addedOrRemoved, _, err := globalIAMSys.PolicyDBUpdateLDAP(ctx, isAttach, par)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
@@ -30,14 +30,12 @@ import (
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "StartDecommission")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -95,7 +93,7 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
||||
poolIndices = append(poolIndices, idx)
|
||||
}
|
||||
|
||||
if len(poolIndices) > 0 && globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
||||
if len(poolIndices) > 0 && !globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
||||
ep := globalEndpoints[poolIndices[0]].Endpoints[0]
|
||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||
if proxyEp.Endpoint.Host == ep.Host {
|
||||
@@ -113,9 +111,7 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "CancelDecommission")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -161,9 +157,7 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "StatusPool")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -204,9 +198,7 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListPools")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -239,8 +231,7 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "RebalanceStart")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -311,8 +302,7 @@ func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "RebalanceStatus")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -352,8 +342,7 @@ func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "RebalanceStop")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
||||
if objectAPI == nil {
|
||||
|
||||
@@ -20,25 +20,26 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/mux"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
||||
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationAdd")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
||||
if objectAPI == nil {
|
||||
@@ -72,9 +73,7 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
|
||||
// used internally to tell current cluster to enable SR with
|
||||
// the provided peer clusters and service account.
|
||||
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SRPeerJoin")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
||||
if objectAPI == nil {
|
||||
@@ -96,9 +95,7 @@ func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// SRPeerBucketOps - PUT /minio/admin/v3/site-replication/bucket-ops?bucket=x&operation=y
|
||||
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SRPeerBucketOps")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
||||
if objectAPI == nil {
|
||||
@@ -145,9 +142,7 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// SRPeerReplicateIAMItem - PUT /minio/admin/v3/site-replication/iam-item
|
||||
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SRPeerReplicateIAMItem")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
||||
if objectAPI == nil {
|
||||
@@ -199,9 +194,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
||||
|
||||
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/bucket-meta
|
||||
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SRPeerReplicateBucketItem")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
||||
if objectAPI == nil {
|
||||
@@ -265,9 +258,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
||||
|
||||
// SiteReplicationInfo - GET /minio/admin/v3/site-replication/info
|
||||
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
||||
if objectAPI == nil {
|
||||
@@ -287,9 +278,7 @@ func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationGetIDPSettings")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
||||
if objectAPI == nil {
|
||||
@@ -326,9 +315,7 @@ func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptio
|
||||
|
||||
// SiteReplicationStatus - GET /minio/admin/v3/site-replication/status
|
||||
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationStatus")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
||||
if objectAPI == nil {
|
||||
@@ -357,9 +344,7 @@ func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.R
|
||||
|
||||
// SiteReplicationMetaInfo - GET /minio/admin/v3/site-replication/metainfo
|
||||
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationMetaInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
||||
if objectAPI == nil {
|
||||
@@ -381,8 +366,7 @@ func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http
|
||||
|
||||
// SiteReplicationEdit - PUT /minio/admin/v3/site-replication/edit
|
||||
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationEdit")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
||||
if objectAPI == nil {
|
||||
@@ -413,8 +397,7 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
|
||||
//
|
||||
// used internally to tell current cluster to update endpoint for peer
|
||||
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SRPeerEdit")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
||||
if objectAPI == nil {
|
||||
@@ -448,9 +431,7 @@ func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
|
||||
|
||||
// SiteReplicationRemove - PUT /minio/admin/v3/site-replication/remove
|
||||
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationRemove")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
||||
if objectAPI == nil {
|
||||
@@ -481,8 +462,7 @@ func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.R
|
||||
//
|
||||
// used internally to tell current cluster to update endpoint for peer
|
||||
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SRPeerRemove")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
||||
if objectAPI == nil {
|
||||
@@ -504,9 +484,7 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// SiteReplicationResyncOp - PUT /minio/admin/v3/site-replication/resync/op
|
||||
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SiteReplicationResyncOp")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationResyncAction)
|
||||
if objectAPI == nil {
|
||||
@@ -543,3 +521,45 @@ func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/pkg/sync/errgroup"
|
||||
)
|
||||
|
||||
+163
-314
@@ -26,11 +26,10 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/config/dns"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -40,11 +39,9 @@ import (
|
||||
|
||||
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
||||
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, _ := validateAdminReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
@@ -62,6 +59,13 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// When the user is root credential you are not allowed to
|
||||
// remove the root user. Also you cannot delete yourself.
|
||||
if accessKey == globalActiveCred.AccessKey || accessKey == cred.AccessKey {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalIAMSys.DeleteUser(ctx, accessKey, true); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -79,9 +83,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ListBucketUsers - GET /minio/admin/v3/list-users?bucket={bucket}
|
||||
func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListBucketUsers")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -115,9 +117,7 @@ func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// ListUsers - GET /minio/admin/v3/list-users
|
||||
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListUsers")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -161,9 +161,7 @@ func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// GetUserInfo - GET /minio/admin/v3/user-info
|
||||
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetUserInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
name := vars["accessKey"]
|
||||
@@ -218,9 +216,7 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// UpdateGroupMembers - PUT /minio/admin/v3/update-group-members
|
||||
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "UpdateGroupMembers")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -239,6 +235,26 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Reject if the group add and remove are temporary credentials, or root credential.
|
||||
for _, member := range updReq.Members {
|
||||
ok, _, err := globalIAMSys.IsTempUser(member)
|
||||
if err != nil && err != errNoSuchUser {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
// When the user is root credential you are not allowed to
|
||||
// add policies for root user.
|
||||
if member == globalActiveCred.AccessKey {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var updatedAt time.Time
|
||||
if updReq.IsRemove {
|
||||
updatedAt, err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members)
|
||||
@@ -270,9 +286,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// GetGroup - /minio/admin/v3/group?group=mygroup1
|
||||
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetGroup")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetGroupAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -299,9 +313,7 @@ func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ListGroups - GET /minio/admin/v3/groups
|
||||
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListGroups")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -325,9 +337,7 @@ func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// SetGroupStatus - PUT /minio/admin/v3/set-group-status?group=mygroup1&status=enabled
|
||||
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SetGroupStatus")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -370,11 +380,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]
|
||||
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, _ := validateAdminReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
||||
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
@@ -383,9 +391,9 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
||||
accessKey := vars["accessKey"]
|
||||
status := vars["status"]
|
||||
|
||||
// This API is not allowed to lookup master access key user status
|
||||
if accessKey == globalActiveCred.AccessKey {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
// you cannot enable or disable yourself.
|
||||
if accessKey == creds.AccessKey {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -410,9 +418,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// AddUser - PUT /minio/admin/v3/add-user?accessKey=<access_key>
|
||||
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AddUser")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
accessKey := vars["accessKey"]
|
||||
@@ -517,9 +523,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// TemporaryAccountInfo - GET /minio/admin/v3/temporary-account-info
|
||||
func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "TemporaryAccountInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -602,9 +606,7 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AddServiceAccount")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -638,6 +640,13 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
if err := createReq.Validate(); err != nil {
|
||||
// Since this validation would happen client side as well, we only send
|
||||
// a generic error message here.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
targetUser string
|
||||
targetGroups []string
|
||||
@@ -650,12 +659,17 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
targetUser = cred.AccessKey
|
||||
}
|
||||
|
||||
description := createReq.Description
|
||||
if description == "" {
|
||||
description = createReq.Comment
|
||||
}
|
||||
opts := newServiceAccountOpts{
|
||||
accessKey: createReq.AccessKey,
|
||||
secretKey: createReq.SecretKey,
|
||||
comment: createReq.Comment,
|
||||
expiration: createReq.Expiration,
|
||||
claims: make(map[string]interface{}),
|
||||
accessKey: createReq.AccessKey,
|
||||
secretKey: createReq.SecretKey,
|
||||
name: createReq.Name,
|
||||
description: description,
|
||||
expiration: createReq.Expiration,
|
||||
claims: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Find the user for the request sender (as it may be sent via a service
|
||||
@@ -808,7 +822,8 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
AccessKey: newCred.AccessKey,
|
||||
SecretKey: newCred.SecretKey,
|
||||
Groups: newCred.Groups,
|
||||
Comment: newCred.Comment,
|
||||
Name: newCred.Name,
|
||||
Description: newCred.Description,
|
||||
Claims: opts.claims,
|
||||
SessionPolicy: createReq.Policy,
|
||||
Status: auth.AccountOn,
|
||||
@@ -822,9 +837,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// UpdateServiceAccount - POST /minio/admin/v3/update-service-account
|
||||
func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "UpdateServiceAccount")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -883,6 +896,13 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
if err := updateReq.Validate(); err != nil {
|
||||
// Since this validation would happen client side as well, we only send
|
||||
// a generic error message here.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var sp *iampolicy.Policy
|
||||
if len(updateReq.NewPolicy) > 0 {
|
||||
sp, err = iampolicy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
||||
@@ -894,7 +914,8 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
opts := updateServiceAccountOpts{
|
||||
secretKey: updateReq.NewSecretKey,
|
||||
status: updateReq.NewStatus,
|
||||
comment: updateReq.NewComment,
|
||||
name: updateReq.NewName,
|
||||
description: updateReq.NewDescription,
|
||||
expiration: updateReq.NewExpiration,
|
||||
sessionPolicy: sp,
|
||||
}
|
||||
@@ -913,7 +934,8 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
AccessKey: accessKey,
|
||||
SecretKey: opts.secretKey,
|
||||
Status: opts.status,
|
||||
Comment: opts.comment,
|
||||
Name: opts.name,
|
||||
Description: opts.description,
|
||||
SessionPolicy: updateReq.NewPolicy,
|
||||
Expiration: updateReq.NewExpiration,
|
||||
},
|
||||
@@ -927,9 +949,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// InfoServiceAccount - GET /minio/admin/v3/info-service-account
|
||||
func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "InfoServiceAccount")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -1001,7 +1021,8 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
infoResp := madmin.InfoServiceAccountResp{
|
||||
ParentUser: svcAccount.ParentUser,
|
||||
Comment: svcAccount.Comment,
|
||||
Name: svcAccount.Name,
|
||||
Description: svcAccount.Description,
|
||||
AccountStatus: svcAccount.Status,
|
||||
ImpliedPolicy: policy == nil,
|
||||
Policy: string(policyJSON),
|
||||
@@ -1025,9 +1046,7 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// ListServiceAccounts - GET /minio/admin/v3/list-service-accounts
|
||||
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListServiceAccounts")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -1073,14 +1092,18 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
var serviceAccountsNames []string
|
||||
var serviceAccountList []madmin.ServiceAccountInfo
|
||||
|
||||
for _, svc := range serviceAccounts {
|
||||
serviceAccountsNames = append(serviceAccountsNames, svc.AccessKey)
|
||||
expiryTime := svc.Expiration
|
||||
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
||||
AccessKey: svc.AccessKey,
|
||||
Expiration: &expiryTime,
|
||||
})
|
||||
}
|
||||
|
||||
listResp := madmin.ListServiceAccountsResp{
|
||||
Accounts: serviceAccountsNames,
|
||||
Accounts: serviceAccountList,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(listResp)
|
||||
@@ -1100,9 +1123,7 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
||||
|
||||
// DeleteServiceAccount - DELETE /minio/admin/v3/delete-service-account
|
||||
func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DeleteServiceAccount")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -1173,9 +1194,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// AccountInfoHandler returns usage
|
||||
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AccountInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -1396,9 +1415,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
||||
// timestamps along with the policy JSON. Both versions are supported for now,
|
||||
// for smooth transition to new API.
|
||||
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "InfoCannedPolicy")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1444,9 +1461,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
// ListBucketPolicies - GET /minio/admin/v3/list-canned-policies?bucket={bucket}
|
||||
func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListBucketPolicies")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1477,9 +1492,7 @@ func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// ListCannedPolicies - GET /minio/admin/v3/list-canned-policies
|
||||
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListCannedPolicies")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1509,9 +1522,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// RemoveCannedPolicy - DELETE /minio/admin/v3/remove-canned-policy?name=<policy_name>
|
||||
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "RemoveCannedPolicy")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1537,9 +1548,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// AddCannedPolicy - PUT /minio/admin/v3/add-canned-policy?name=<policy_name>
|
||||
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AddCannedPolicy")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1603,9 +1612,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// SetPolicyForUserOrGroup - PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group]
|
||||
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1627,6 +1634,12 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
// When the user is root credential you are not allowed to
|
||||
// add policies for root user.
|
||||
if entityName == globalActiveCred.AccessKey {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that user or group exists.
|
||||
@@ -1671,9 +1684,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
||||
|
||||
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx
|
||||
func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListPolicyMappingEntities")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Check authorization.
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r,
|
||||
@@ -1711,24 +1722,38 @@ func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *ht
|
||||
writeSuccessResponseJSON(w, econfigData)
|
||||
}
|
||||
|
||||
// AttachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/attach
|
||||
func (a adminAPIHandlers) AttachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "AttachPolicyBuiltin")
|
||||
// AttachDetachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/policy/{operation}
|
||||
func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction,
|
||||
iampolicy.AttachPolicyAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cred, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||
// More than maxConfigSize bytes were available
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||
return
|
||||
}
|
||||
password := cred.SecretKey
|
||||
|
||||
// Ensure body content type is opaque to ensure that request body has not
|
||||
// been interpreted as form data.
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType != "application/octet-stream" {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
operation := mux.Vars(r)["operation"]
|
||||
if operation != "attach" && operation != "detach" {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
isAttach := operation == "attach"
|
||||
|
||||
password := cred.SecretKey
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
@@ -1746,224 +1771,42 @@ func (a adminAPIHandlers) AttachPolicyBuiltin(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
userOrGroup := par.User
|
||||
isGroup := false
|
||||
|
||||
if userOrGroup == "" {
|
||||
userOrGroup = par.Group
|
||||
isGroup = true
|
||||
}
|
||||
|
||||
if isGroup {
|
||||
_, err := globalIAMSys.GetGroupDescription(userOrGroup)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ok, _, err := globalIAMSys.IsTempUser(userOrGroup)
|
||||
if err != nil && err != errNoSuchUser {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that user exists.
|
||||
if globalIAMSys.GetUsersSysType() == MinIOUsersSysType {
|
||||
_, ok := globalIAMSys.GetUser(ctx, userOrGroup)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||
updatedAt, addedOrRemoved, _, err := globalIAMSys.PolicyDBUpdateBuiltin(ctx, isAttach, par)
|
||||
if err != nil {
|
||||
if err == errNoSuchUser || err == errNoSuchGroup {
|
||||
if globalIAMSys.LDAPConfig.Enabled() {
|
||||
// When LDAP is enabled, warn user that they are using the wrong
|
||||
// API. FIXME: error can be no such group as well - fix errNoSuchUserLDAPWarn
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUserLDAPWarn), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userType := regUser
|
||||
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
|
||||
userType = stsUser
|
||||
}
|
||||
|
||||
var existingPolicies []string
|
||||
if isGroup {
|
||||
existingPolicies, err = globalIAMSys.PolicyDBGet(userOrGroup, true)
|
||||
} else {
|
||||
existingPolicies, err = globalIAMSys.GetUserPolicies(userOrGroup)
|
||||
}
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
policyMap := make(map[string]bool)
|
||||
for _, p := range existingPolicies {
|
||||
policyMap[p] = true
|
||||
}
|
||||
|
||||
policiesToAttach := par.Policies
|
||||
|
||||
// Check if policy is already attached to user.
|
||||
for _, p := range policiesToAttach {
|
||||
if _, ok := policyMap[p]; ok {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyAlreadyAttached), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
existingPolicies = append(existingPolicies, policiesToAttach...)
|
||||
newPolicies := strings.Join(existingPolicies, ",")
|
||||
|
||||
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, userOrGroup, newPolicies, userType, isGroup)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: userOrGroup,
|
||||
UserType: int(userType),
|
||||
IsGroup: isGroup,
|
||||
Policy: strings.Join(policiesToAttach, ","),
|
||||
},
|
||||
respBody := madmin.PolicyAssociationResp{
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
|
||||
writeResponse(w, http.StatusCreated, nil, mimeNone)
|
||||
}
|
||||
|
||||
// DetachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/detach
|
||||
func (a adminAPIHandlers) DetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DetachPolicyBuiltin")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cred, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
password := cred.SecretKey
|
||||
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var par madmin.PolicyAssociationReq
|
||||
if err = json.Unmarshal(reqBytes, &par); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err = par.IsValid(); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
userOrGroup := par.User
|
||||
isGroup := false
|
||||
|
||||
if userOrGroup == "" {
|
||||
userOrGroup = par.Group
|
||||
isGroup = true
|
||||
}
|
||||
|
||||
if isGroup {
|
||||
_, err := globalIAMSys.GetGroupDescription(userOrGroup)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if isAttach {
|
||||
respBody.PoliciesAttached = addedOrRemoved
|
||||
} else {
|
||||
ok, _, err := globalIAMSys.IsTempUser(userOrGroup)
|
||||
if err != nil && err != errNoSuchUser {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that user exists.
|
||||
if globalIAMSys.GetUsersSysType() == MinIOUsersSysType {
|
||||
_, ok := globalIAMSys.GetUser(ctx, userOrGroup)
|
||||
if !ok {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
respBody.PoliciesDetached = addedOrRemoved
|
||||
}
|
||||
|
||||
userType := regUser
|
||||
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
|
||||
userType = stsUser
|
||||
}
|
||||
|
||||
var existingPolicies []string
|
||||
if isGroup {
|
||||
existingPolicies, err = globalIAMSys.PolicyDBGet(userOrGroup, true)
|
||||
} else {
|
||||
existingPolicies, err = globalIAMSys.GetUserPolicies(userOrGroup)
|
||||
}
|
||||
data, err := json.Marshal(respBody)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
policyMap := make(map[string]bool)
|
||||
for _, p := range existingPolicies {
|
||||
policyMap[p] = true
|
||||
}
|
||||
|
||||
policiesToDetach := par.Policies
|
||||
|
||||
// Check if policy is already attached to user.
|
||||
for _, p := range policiesToDetach {
|
||||
if _, ok := policyMap[p]; ok {
|
||||
delete(policyMap, p)
|
||||
} else {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyNotAttached), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newPoliciesSl := []string{}
|
||||
for p := range policyMap {
|
||||
newPoliciesSl = append(newPoliciesSl, p)
|
||||
}
|
||||
|
||||
newPolicies := strings.Join(newPoliciesSl, ",")
|
||||
|
||||
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, userOrGroup, newPolicies, userType, isGroup)
|
||||
encryptedData, err := madmin.EncryptData(password, data)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: userOrGroup,
|
||||
UserType: int(userType),
|
||||
IsGroup: isGroup,
|
||||
Policy: newPolicies,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
|
||||
// Return successful JSON response
|
||||
writeSuccessNoContent(w)
|
||||
writeSuccessResponseJSON(w, encryptedData)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -1980,8 +1823,7 @@ const (
|
||||
|
||||
// ExportIAMHandler - exports all iam info as a zipped file
|
||||
func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ExportIAM")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportIAMAction)
|
||||
@@ -2056,13 +1898,15 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
userAccounts := make(map[string]madmin.AddOrUpdateUserReq)
|
||||
for u, uid := range userIdentities {
|
||||
status := madmin.AccountDisabled
|
||||
if uid.Credentials.IsValid() {
|
||||
status = madmin.AccountEnabled
|
||||
}
|
||||
userAccounts[u] = madmin.AddOrUpdateUserReq{
|
||||
SecretKey: uid.Credentials.SecretKey,
|
||||
Status: status,
|
||||
Status: func() madmin.AccountStatus {
|
||||
// Export current credential status
|
||||
if uid.Credentials.Status == auth.AccountOff {
|
||||
return madmin.AccountDisabled
|
||||
}
|
||||
return madmin.AccountEnabled
|
||||
}(),
|
||||
}
|
||||
}
|
||||
userData, err := json.Marshal(userAccounts)
|
||||
@@ -2101,6 +1945,10 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
svcAccts := make(map[string]madmin.SRSvcAccCreate)
|
||||
for user, acc := range serviceAccounts {
|
||||
if user == siteReplicatorSvcAcc {
|
||||
// skip site-replication service account.
|
||||
continue
|
||||
}
|
||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.Credentials.AccessKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
@@ -2213,9 +2061,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ImportIAM - imports all IAM info into MinIO
|
||||
func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ImportIAM")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -2449,7 +2295,8 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
opts := updateServiceAccountOpts{
|
||||
secretKey: svcAcctReq.SecretKey,
|
||||
status: svcAcctReq.Status,
|
||||
comment: svcAcctReq.Comment,
|
||||
name: svcAcctReq.Name,
|
||||
description: svcAcctReq.Description,
|
||||
expiration: svcAcctReq.Expiration,
|
||||
sessionPolicy: sp,
|
||||
}
|
||||
@@ -2461,12 +2308,14 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
opts := newServiceAccountOpts{
|
||||
accessKey: user,
|
||||
secretKey: svcAcctReq.SecretKey,
|
||||
sessionPolicy: sp,
|
||||
claims: svcAcctReq.Claims,
|
||||
comment: svcAcctReq.Comment,
|
||||
expiration: svcAcctReq.Expiration,
|
||||
accessKey: user,
|
||||
secretKey: svcAcctReq.SecretKey,
|
||||
sessionPolicy: sp,
|
||||
claims: svcAcctReq.Claims,
|
||||
name: svcAcctReq.Name,
|
||||
description: svcAcctReq.Description,
|
||||
expiration: svcAcctReq.Expiration,
|
||||
allowSiteReplicatorAccount: false,
|
||||
}
|
||||
|
||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||
|
||||
@@ -27,20 +27,19 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"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/set"
|
||||
"github.com/minio/minio-go/v7/pkg/signer"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/pkg/env"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -122,7 +121,7 @@ var iamTestSuites = func() []*TestSuiteIAM {
|
||||
}()
|
||||
|
||||
const (
|
||||
EnvTestEtcdBackend = "ETCD_SERVER"
|
||||
EnvTestEtcdBackend = "_MINIO_ETCD_TEST_SERVER"
|
||||
)
|
||||
|
||||
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) {
|
||||
// If etcd backend is specified and etcd server is not present, the test
|
||||
// is skipped.
|
||||
etcdServer := os.Getenv(EnvTestEtcdBackend)
|
||||
etcdServer := env.Get(EnvTestEtcdBackend, "")
|
||||
if s.withEtcdBackend && etcdServer == "" {
|
||||
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
|
||||
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||
Creds: cr.NewStaticV4(accessKey, secretKey, ""),
|
||||
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||
Secure: s.secure,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -984,9 +983,9 @@ func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
pluginEndpoint := os.Getenv("POLICY_PLUGIN_ENDPOINT")
|
||||
pluginEndpoint := env.Get("_MINIO_POLICY_PLUGIN_ENDPOINT", "")
|
||||
if pluginEndpoint == "" {
|
||||
c.Skip("POLICY_PLUGIN_ENDPOINT not given - skipping.")
|
||||
c.Skip("_MINIO_POLICY_PLUGIN_ENDPOINT not given - skipping.")
|
||||
}
|
||||
|
||||
configCmds := []string{
|
||||
@@ -1072,7 +1071,7 @@ func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
|
||||
|
||||
// Create an madmin client with user creds
|
||||
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||
Creds: cr.NewStaticV4(accessKey, secretKey, ""),
|
||||
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||
Secure: s.secure,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1329,7 +1328,11 @@ func (c *check) assertSvcAccAppearsInListing(ctx context.Context, madmClient *ma
|
||||
if err != nil {
|
||||
c.Fatalf("unable to list svc accounts: %v", err)
|
||||
}
|
||||
if !set.CreateStringSet(listResp.Accounts...).Contains(svcAK) {
|
||||
var accessKeys []string
|
||||
for _, item := range listResp.Accounts {
|
||||
accessKeys = append(accessKeys, item.AccessKey)
|
||||
}
|
||||
if !set.CreateStringSet(accessKeys...).Contains(svcAK) {
|
||||
c.Fatalf("service account did not appear in listing!")
|
||||
}
|
||||
}
|
||||
|
||||
+177
-108
@@ -45,8 +45,8 @@ import (
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v2/estream"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/madmin-go/v3/estream"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
"github.com/minio/minio/internal/handlers"
|
||||
@@ -78,9 +78,7 @@ const (
|
||||
// ----------
|
||||
// updates all minio servers and restarts them gracefully.
|
||||
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ServerUpdate")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -229,9 +227,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
// - freeze (freezes all incoming S3 API calls)
|
||||
// - unfreeze (unfreezes previously frozen S3 API calls)
|
||||
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "Service")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
action := vars["action"]
|
||||
@@ -297,15 +293,12 @@ type ServerProperties struct {
|
||||
SQSARN []string `json:"sqsARN"`
|
||||
}
|
||||
|
||||
// ServerConnStats holds transferred bytes from/to the server
|
||||
type ServerConnStats struct {
|
||||
TotalInputBytes uint64 `json:"transferred"`
|
||||
TotalOutputBytes uint64 `json:"received"`
|
||||
Throughput uint64 `json:"throughput,omitempty"`
|
||||
S3InputBytes uint64 `json:"transferredS3"`
|
||||
S3OutputBytes uint64 `json:"receivedS3"`
|
||||
AdminInputBytes uint64 `json:"transferredAdmin"`
|
||||
AdminOutputBytes uint64 `json:"receivedAdmin"`
|
||||
// serverConnStats holds transferred bytes from/to the server
|
||||
type serverConnStats struct {
|
||||
internodeInputBytes uint64
|
||||
internodeOutputBytes uint64
|
||||
s3InputBytes uint64
|
||||
s3OutputBytes uint64
|
||||
}
|
||||
|
||||
// ServerHTTPAPIStats holds total number of HTTP operations from/to the server,
|
||||
@@ -335,9 +328,7 @@ type ServerHTTPStats struct {
|
||||
// ----------
|
||||
// Get server information
|
||||
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "StorageInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.StorageInfoAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -376,9 +367,7 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
||||
// ----------
|
||||
// Get realtime server metrics
|
||||
func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "Metrics")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -487,9 +476,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
||||
// ----------
|
||||
// Get server/cluster data usage info
|
||||
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DataUsageInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DataUsageInfoAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -572,9 +559,7 @@ type PeerLocks struct {
|
||||
|
||||
// ForceUnlockHandler force unlocks requested resource
|
||||
func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ForceUnlock")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ForceUnlockAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -609,9 +594,7 @@ func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// TopLocksHandler Get list of locks in use
|
||||
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "TopLocks")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.TopLocksAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -661,9 +644,7 @@ type StartProfilingResult struct {
|
||||
// ----------
|
||||
// Enable server profiling
|
||||
func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "StartProfiling")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Validate request signature.
|
||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||
@@ -748,9 +729,7 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R
|
||||
// ----------
|
||||
// Enable server profiling
|
||||
func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "Profile")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Validate request signature.
|
||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||
@@ -770,7 +749,7 @@ func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request)
|
||||
var err error
|
||||
duration, err = time.ParseDuration(dstr)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -845,9 +824,7 @@ func (f dummyFileInfo) Sys() interface{} { return f.sys }
|
||||
// ----------
|
||||
// Download profiling information of all nodes in a zip format - deprecated API
|
||||
func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DownloadProfiling")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Validate request signature.
|
||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||
@@ -946,9 +923,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
|
||||
// sequence. However, if the force-start flag is provided, the server
|
||||
// aborts the running heal sequence and starts a new one.
|
||||
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "Heal")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1132,9 +1107,7 @@ func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmi
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "HealBackgroundStatus")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1153,11 +1126,55 @@ 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, iampolicy.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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// NetperfHandler - perform mesh style network throughput test
|
||||
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "NetperfHandler")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1197,19 +1214,12 @@ 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
|
||||
// GET operations on the server, supports auto tuning by default by automatically
|
||||
// increasing concurrency and stopping when we have reached the limits on the
|
||||
// system.
|
||||
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ObjectSpeedTestHandler")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1368,18 +1378,9 @@ func validateObjPerfOptions(ctx context.Context, storageInfo madmin.StorageInfo,
|
||||
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
|
||||
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DriveSpeedtestHandler")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1499,7 +1500,7 @@ func extractTraceOptions(r *http.Request) (opts madmin.ServiceTraceOpts, err err
|
||||
// ----------
|
||||
// The handler sends http trace to the connected HTTP client.
|
||||
func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "HTTPTrace")
|
||||
ctx := r.Context()
|
||||
|
||||
// Validate request signature.
|
||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.TraceAdminAction, "")
|
||||
@@ -1525,7 +1526,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return shouldTrace(entry, traceOpts)
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrSlowDown), r.URL)
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1571,9 +1572,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// The ConsoleLogHandler handler sends console logs to the connected HTTP client.
|
||||
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ConsoleLog")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConsoleLogAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1605,7 +1604,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
err = globalConsoleSys.Subscribe(logCh, ctx.Done(), node, limitLines, logKind, nil)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrSlowDown), r.URL)
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1654,8 +1653,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// KMSCreateKeyHandler - POST /minio/admin/v3/kms/key/create?key-id=<master-key-id>
|
||||
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSCreateKey")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1676,8 +1674,7 @@ func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
// KMSKeyStatusHandler - GET /minio/admin/v3/kms/status
|
||||
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSStatus")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1714,9 +1711,7 @@ func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
// KMSKeyStatusHandler - GET /minio/admin/v3/kms/key/status?key-id=<master-key-id>
|
||||
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "KMSKeyStatus")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1811,6 +1806,7 @@ func getPoolsInfo(ctx context.Context, allDisks []madmin.Disk) (map[int]map[int]
|
||||
dataUsageInfo := cache.dui(dataUsageRoot, nil)
|
||||
erasureSet.ObjectsCount = dataUsageInfo.ObjectsTotalCount
|
||||
erasureSet.VersionsCount = dataUsageInfo.VersionsTotalCount
|
||||
erasureSet.DeleteMarkersCount = dataUsageInfo.DeleteMarkersTotalCount
|
||||
erasureSet.Usage = dataUsageInfo.ObjectsTotalSize
|
||||
}
|
||||
}
|
||||
@@ -1826,8 +1822,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 {
|
||||
kmsStat := fetchKMSStatus()
|
||||
|
||||
ldap := madmin.LDAP{}
|
||||
if globalIAMSys.LDAPConfig.Enabled() {
|
||||
ldapConn, err := globalIAMSys.LDAPConfig.LDAP.Connect()
|
||||
@@ -1855,13 +1849,14 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
||||
assignPoolNumbers(servers)
|
||||
|
||||
var poolsInfo map[int]map[int]madmin.ErasureSetInfo
|
||||
var backend interface{}
|
||||
var backend madmin.ErasureBackend
|
||||
|
||||
mode := madmin.ItemInitializing
|
||||
|
||||
buckets := madmin.Buckets{}
|
||||
objects := madmin.Objects{}
|
||||
versions := madmin.Versions{}
|
||||
deleteMarkers := madmin.DeleteMarkers{}
|
||||
usage := madmin.Usage{}
|
||||
|
||||
objectAPI := newObjectLayerFn()
|
||||
@@ -1874,10 +1869,12 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
||||
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
||||
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
||||
versions = madmin.Versions{Count: dataUsageInfo.VersionsTotalCount}
|
||||
deleteMarkers = madmin.DeleteMarkers{Count: dataUsageInfo.DeleteMarkersTotalCount}
|
||||
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
||||
} else {
|
||||
buckets = madmin.Buckets{Error: err.Error()}
|
||||
objects = madmin.Objects{Error: err.Error()}
|
||||
deleteMarkers = madmin.DeleteMarkers{Error: err.Error()}
|
||||
usage = madmin.Usage{Error: err.Error()}
|
||||
}
|
||||
|
||||
@@ -1907,7 +1904,8 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
||||
|
||||
domain := globalDomainNames
|
||||
services := madmin.Services{
|
||||
KMS: kmsStat,
|
||||
KMS: fetchKMSStatus(),
|
||||
KMSStatus: fetchKMSStatusV2(ctx),
|
||||
LDAP: ldap,
|
||||
Logger: log,
|
||||
Audit: audit,
|
||||
@@ -1915,19 +1913,20 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
||||
}
|
||||
|
||||
return madmin.InfoMessage{
|
||||
Mode: string(mode),
|
||||
Domain: domain,
|
||||
Region: globalSite.Region,
|
||||
SQSARN: globalEventNotifier.GetARNList(false),
|
||||
DeploymentID: globalDeploymentID,
|
||||
Buckets: buckets,
|
||||
Objects: objects,
|
||||
Versions: versions,
|
||||
Usage: usage,
|
||||
Services: services,
|
||||
Backend: backend,
|
||||
Servers: servers,
|
||||
Pools: poolsInfo,
|
||||
Mode: string(mode),
|
||||
Domain: domain,
|
||||
Region: globalSite.Region,
|
||||
SQSARN: globalEventNotifier.GetARNList(false),
|
||||
DeploymentID: globalDeploymentID,
|
||||
Buckets: buckets,
|
||||
Objects: objects,
|
||||
Versions: versions,
|
||||
DeleteMarkers: deleteMarkers,
|
||||
Usage: usage,
|
||||
Services: services,
|
||||
Backend: backend,
|
||||
Servers: servers,
|
||||
Pools: poolsInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2103,6 +2102,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 {
|
||||
if !globalIsDistErasure {
|
||||
// FS mode - single server - hard code to `server1`
|
||||
@@ -2280,6 +2300,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
||||
TLS: &tls,
|
||||
IsKubernetes: &isK8s,
|
||||
IsDocker: &isDocker,
|
||||
Metrics: getRealtimeMetrics(),
|
||||
}
|
||||
partialWrite(healthInfo)
|
||||
}
|
||||
@@ -2290,8 +2311,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
||||
// ----------
|
||||
// Get server health info
|
||||
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "HealthInfo")
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
||||
if objectAPI == nil {
|
||||
@@ -2397,9 +2417,7 @@ func getTLSInfo() madmin.TLSInfo {
|
||||
// ----------
|
||||
// Get server information
|
||||
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ServerInfo")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
// Validate request signature.
|
||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
|
||||
@@ -2505,19 +2523,41 @@ func fetchKMSStatus() madmin.KMS {
|
||||
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
|
||||
func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
||||
var loggerInfo []madmin.Logger
|
||||
var auditloggerInfo []madmin.Audit
|
||||
for _, tgt := range logger.SystemTargets() {
|
||||
if tgt.Endpoint() != "" {
|
||||
loggerInfo = append(loggerInfo, madmin.Logger{tgt.String(): logger.TargetStatus(tgt)})
|
||||
loggerInfo = append(loggerInfo, madmin.Logger{tgt.String(): logger.TargetStatus(GlobalContext, tgt)})
|
||||
}
|
||||
}
|
||||
|
||||
for _, tgt := range logger.AuditTargets() {
|
||||
if tgt.Endpoint() != "" {
|
||||
auditloggerInfo = append(auditloggerInfo, madmin.Audit{tgt.String(): logger.TargetStatus(tgt)})
|
||||
auditloggerInfo = append(auditloggerInfo, madmin.Audit{tgt.String(): logger.TargetStatus(GlobalContext, tgt)})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2629,7 +2669,7 @@ type getRawDataer interface {
|
||||
// ----------
|
||||
// Download file from all nodes in a zip format
|
||||
func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "InspectData")
|
||||
ctx := r.Context()
|
||||
|
||||
// Validate request signature.
|
||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.InspectDataAction, "")
|
||||
@@ -2637,7 +2677,6 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||
return
|
||||
}
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objLayer := newObjectLayerFn()
|
||||
o, ok := objLayer.(getRawDataer)
|
||||
@@ -2826,6 +2865,36 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
logger.LogIf(ctx, embedFileInZip(inspectZipW, "inspect-input.txt", sb.Bytes()))
|
||||
|
||||
// save MinIO start script to inspect command
|
||||
var scrb bytes.Buffer
|
||||
scrb.WriteString(`#!/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#https:\/\/#\.\/#g")
|
||||
|
||||
# Start MinIO instance using the options
|
||||
START_CMD="CI=on MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 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 "$@"`,
|
||||
)
|
||||
logger.LogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes()))
|
||||
}
|
||||
|
||||
func getSubnetAdminPublicKey() []byte {
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
@@ -73,7 +73,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
|
||||
// Initialize boot time
|
||||
globalBootTime = UTCNow()
|
||||
|
||||
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
||||
globalEndpoints = mustGetPoolEndpoints(0, erasureDirs...)
|
||||
|
||||
initAllSubsystems(ctx)
|
||||
|
||||
@@ -108,7 +108,7 @@ func initTestErasureObjLayer(ctx context.Context) (ObjectLayer, []string, error)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
endpoints := mustGetPoolEndpoints(erasureDirs...)
|
||||
endpoints := mustGetPoolEndpoints(0, erasureDirs...)
|
||||
globalPolicySys = NewPolicySys()
|
||||
objLayer, err := newErasureServerPools(ctx, endpoints)
|
||||
if err != nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
@@ -784,6 +784,11 @@ func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
|
||||
}
|
||||
|
||||
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
||||
if h.clientToken == bgHealingUUID {
|
||||
// For background heal do nothing.
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := h.healDiskMeta(objAPI); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+225
-123
@@ -19,20 +19,125 @@ package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/klauspost/compress/gzip"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
adminPathPrefix = minioReservedBucketPath + "/admin"
|
||||
adminAPIVersion = madmin.AdminAPIVersion
|
||||
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
||||
adminPathPrefix = minioReservedBucketPath + "/admin"
|
||||
adminAPIVersion = madmin.AdminAPIVersion
|
||||
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
||||
adminAPISiteReplicationDevNull = "/site-replication/devnull"
|
||||
adminAPISiteReplicationNetPerf = "/site-replication/netperf"
|
||||
)
|
||||
|
||||
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.
|
||||
type adminAPIHandlers struct{}
|
||||
|
||||
@@ -46,265 +151,262 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
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 {
|
||||
// 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.
|
||||
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
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler)))
|
||||
adminRouter.Methods(http.MethodGet, http.MethodPost).Path(adminVersion + "/inspect-data").HandlerFunc(httpTraceAll(adminAPI.InspectDataHandler))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(adminMiddleware(adminAPI.ServerInfoHandler, traceAllFlag, noObjLayerFlag))
|
||||
adminRouter.Methods(http.MethodGet, http.MethodPost).Path(adminVersion + "/inspect-data").HandlerFunc(adminMiddleware(adminAPI.InspectDataHandler, noGZFlag, traceAllFlag))
|
||||
|
||||
// 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
|
||||
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
|
||||
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 {
|
||||
// Heal operations
|
||||
|
||||
// Heal processing endpoint.
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(gz(httpTraceAll(adminAPI.BackgroundHealStatusHandler)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(adminMiddleware(adminAPI.BackgroundHealStatusHandler, traceAllFlag))
|
||||
|
||||
// Pool operations
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/pools/list").HandlerFunc(gz(httpTraceAll(adminAPI.ListPools)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/pools/status").HandlerFunc(gz(httpTraceAll(adminAPI.StatusPool))).Queries("pool", "{pool:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/pools/list").HandlerFunc(adminMiddleware(adminAPI.ListPools, traceAllFlag))
|
||||
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/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).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(adminMiddleware(adminAPI.CancelDecommission, traceAllFlag)).Queries("pool", "{pool:.*}")
|
||||
|
||||
// Rebalance operations
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/start").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStart)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/rebalance/status").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStatus)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/stop").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStop)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/start").HandlerFunc(adminMiddleware(adminAPI.RebalanceStart, traceAllFlag))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/rebalance/status").HandlerFunc(adminMiddleware(adminAPI.RebalanceStatus, traceAllFlag))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/stop").HandlerFunc(adminMiddleware(adminAPI.RebalanceStop, traceAllFlag))
|
||||
}
|
||||
|
||||
// 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:.*}")
|
||||
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
|
||||
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.
|
||||
if enableConfigOps {
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetConfigKVHandler))).Queries("key", "{key:.*}")
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetConfigKVHandler)))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.DelConfigKVHandler)))
|
||||
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(adminMiddleware(adminAPI.SetConfigKVHandler))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(adminMiddleware(adminAPI.DelConfigKVHandler))
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(gz(httpTraceHdrs(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.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(adminMiddleware(adminAPI.ClearConfigHistoryKVHandler)).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
|
||||
if enableConfigOps {
|
||||
// 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
|
||||
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 --
|
||||
|
||||
// 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
|
||||
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
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddServiceAccount)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update-service-account").HandlerFunc(gz(httpTraceHdrs(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 + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccount))
|
||||
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(adminMiddleware(adminAPI.InfoServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(adminMiddleware(adminAPI.ListServiceAccounts))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(adminMiddleware(adminAPI.DeleteServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||
|
||||
// STS accounts ops
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.TemporaryAccountInfo))).Queries("accessKey", "{accessKey:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(adminMiddleware(adminAPI.TemporaryAccountInfo)).Queries("accessKey", "{accessKey:.*}")
|
||||
|
||||
// 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
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(adminMiddleware(adminAPI.ListBucketPolicies)).Queries("bucket", "{bucket:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(adminMiddleware(adminAPI.ListCannedPolicies))
|
||||
|
||||
// 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
|
||||
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
|
||||
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}")
|
||||
|
||||
// Attach policies to user or group
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/attach").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachPolicyBuiltin)))
|
||||
|
||||
// Detach policies from user or group
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/detach").HandlerFunc(gz(httpTraceHdrs(adminAPI.DetachPolicyBuiltin)))
|
||||
// Attach/Detach policies to/from user or group
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyBuiltin))
|
||||
|
||||
// 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
|
||||
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(gz(httpTraceHdrs(adminAPI.ListUsers)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(adminMiddleware(adminAPI.ListBucketUsers)).Queries("bucket", "{bucket:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(adminMiddleware(adminAPI.ListUsers))
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddIdentityProviderCfg)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(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}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg)))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg)))
|
||||
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(adminMiddleware(adminAPI.UpdateIdentityProviderCfg))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}").HandlerFunc(adminMiddleware(adminAPI.ListIdentityProviderCfg))
|
||||
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(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
||||
|
||||
// LDAP IAM operations
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListLDAPPolicyMappingEntities)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachDetachPolicyLDAP)))
|
||||
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(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
||||
// -- END IAM APIs --
|
||||
|
||||
// GetBucketQuotaConfig
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
||||
adminMiddleware(adminAPI.GetBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||
// PutBucketQuotaConfig
|
||||
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
|
||||
// GetBucketTargetHandler
|
||||
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
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.SetRemoteTargetHandler))).Queries("bucket", "{bucket:.*}")
|
||||
adminMiddleware(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||
// RemoveRemoteTargetHandler
|
||||
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
|
||||
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
|
||||
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(
|
||||
gz(httpTraceHdrs(adminAPI.ListBatchJobs)))
|
||||
adminMiddleware(adminAPI.ListBatchJobs))
|
||||
|
||||
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(
|
||||
gz(httpTraceHdrs(adminAPI.CancelBatchJob)))
|
||||
adminMiddleware(adminAPI.CancelBatchJob))
|
||||
|
||||
// Bucket migration operations
|
||||
// ExportBucketMetaHandler
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.ExportBucketMetadataHandler)))
|
||||
adminMiddleware(adminAPI.ExportBucketMetadataHandler))
|
||||
// ImportBucketMetaHandler
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-bucket-metadata").HandlerFunc(
|
||||
gz(httpTraceHdrs(adminAPI.ImportBucketMetadataHandler)))
|
||||
adminMiddleware(adminAPI.ImportBucketMetadataHandler))
|
||||
|
||||
// Remote Tier management operations
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddTierHandler)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.EditTierHandler)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListTierHandler)))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveTierHandler)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.VerifyTierHandler)))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(adminMiddleware(adminAPI.AddTierHandler))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.EditTierHandler))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(adminMiddleware(adminAPI.ListTierHandler))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.RemoveTierHandler))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.VerifyTierHandler))
|
||||
// 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
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationAdd)))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationRemove)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationInfo)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationMetaInfo)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationStatus)))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationAdd))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationRemove))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationInfo))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationMetaInfo))
|
||||
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/bucket-ops").HandlerFunc(gz(httpTraceHdrs(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/bucket-meta").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateBucketItem)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/peer/idp-settings").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerGetIDPSettings)))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(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/peer/join").HandlerFunc(adminMiddleware(adminAPI.SRPeerJoin))
|
||||
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(adminMiddleware(adminAPI.SRPeerReplicateIAMItem))
|
||||
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(adminMiddleware(adminAPI.SRPeerGetIDPSettings))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationEdit))
|
||||
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(adminMiddleware(adminAPI.SRPeerRemove))
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationResyncOp)).Queries("operation", "{operation:.*}")
|
||||
|
||||
if globalIsDistErasure {
|
||||
// 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
|
||||
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/object").HandlerFunc(httpTraceHdrs(adminAPI.ObjectSpeedTestHandler))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(httpTraceHdrs(adminAPI.DriveSpeedtestHandler))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/net").HandlerFunc(httpTraceHdrs(adminAPI.NetperfHandler))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(adminMiddleware(adminAPI.ObjectSpeedTestHandler, noGZFlag))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/object").HandlerFunc(adminMiddleware(adminAPI.ObjectSpeedTestHandler, noGZFlag))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(adminMiddleware(adminAPI.DriveSpeedtestHandler, noGZFlag))
|
||||
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))
|
||||
|
||||
// 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
|
||||
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 --
|
||||
//
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSStatusHandler)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler)))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(adminMiddleware(adminAPI.KMSStatusHandler, traceAllFlag))
|
||||
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(adminMiddleware(adminAPI.KMSKeyStatusHandler, traceAllFlag))
|
||||
|
||||
// Keep obdinfo for backward compatibility with mc
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
||||
HandlerFunc(adminMiddleware(adminAPI.HealthInfoHandler))
|
||||
// -- Health API --
|
||||
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
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
|
||||
+106
-36
@@ -28,9 +28,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/minio/minio/internal/ioutil"
|
||||
"google.golang.org/api/googleapi"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
@@ -86,6 +87,7 @@ const (
|
||||
ErrInternalError
|
||||
ErrInvalidAccessKeyID
|
||||
ErrAccessKeyDisabled
|
||||
ErrInvalidArgument
|
||||
ErrInvalidBucketName
|
||||
ErrInvalidDigest
|
||||
ErrInvalidRange
|
||||
@@ -134,8 +136,10 @@ const (
|
||||
ErrReplicationNeedsVersioningError
|
||||
ErrReplicationBucketNeedsVersioningError
|
||||
ErrReplicationDenyEditError
|
||||
ErrRemoteTargetDenyEditError
|
||||
ErrRemoteTargetDenyAddError
|
||||
ErrReplicationNoExistingObjects
|
||||
ErrReplicationValidationError
|
||||
ErrReplicationPermissionCheckError
|
||||
ErrObjectRestoreAlreadyInProgress
|
||||
ErrNoSuchKey
|
||||
ErrNoSuchUpload
|
||||
@@ -148,6 +152,7 @@ const (
|
||||
ErrMethodNotAllowed
|
||||
ErrInvalidPart
|
||||
ErrInvalidPartOrder
|
||||
ErrMissingPart
|
||||
ErrAuthorizationHeaderMalformed
|
||||
ErrMalformedPOSTRequest
|
||||
ErrPOSTFileRequired
|
||||
@@ -181,8 +186,11 @@ const (
|
||||
ErrBucketAlreadyExists
|
||||
ErrMetadataTooLarge
|
||||
ErrUnsupportedMetadata
|
||||
ErrUnsupportedHostHeader
|
||||
ErrMaximumExpires
|
||||
ErrSlowDown
|
||||
ErrSlowDownRead
|
||||
ErrSlowDownWrite
|
||||
ErrMaxVersionsExceeded
|
||||
ErrInvalidPrefixMarker
|
||||
ErrBadRequest
|
||||
ErrKeyTooLongError
|
||||
@@ -199,6 +207,7 @@ const (
|
||||
ErrInvalidTagDirective
|
||||
ErrPolicyAlreadyAttached
|
||||
ErrPolicyNotAttached
|
||||
ErrExcessData
|
||||
// Add new error codes here.
|
||||
|
||||
// SSE-S3/SSE-KMS related API errors
|
||||
@@ -251,10 +260,11 @@ const (
|
||||
ErrInvalidObjectName
|
||||
ErrInvalidObjectNamePrefixSlash
|
||||
ErrInvalidResourceName
|
||||
ErrInvalidLifecycleQueryParameter
|
||||
ErrServerNotInitialized
|
||||
ErrOperationTimedOut
|
||||
ErrRequestTimedout
|
||||
ErrClientDisconnected
|
||||
ErrOperationMaxedOut
|
||||
ErrTooManyRequests
|
||||
ErrInvalidRequest
|
||||
ErrTransitionStorageClassNotFoundError
|
||||
// MinIO storage class error codes
|
||||
@@ -266,6 +276,7 @@ const (
|
||||
|
||||
ErrMalformedJSON
|
||||
ErrAdminNoSuchUser
|
||||
ErrAdminNoSuchUserLDAPWarn
|
||||
ErrAdminNoSuchGroup
|
||||
ErrAdminGroupNotEmpty
|
||||
ErrAdminGroupDisabled
|
||||
@@ -527,6 +538,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Your proposed upload exceeds the maximum allowed object size.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrExcessData: {
|
||||
Code: "ExcessData",
|
||||
Description: "More data provided than indicated content length",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPolicyTooLarge: {
|
||||
Code: "PolicyTooLarge",
|
||||
Description: "Policy exceeds the maximum allowed document size.",
|
||||
@@ -552,6 +568,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Your account is disabled; please contact your administrator.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrInvalidArgument: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid argument",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidBucketName: {
|
||||
Code: "InvalidBucketName",
|
||||
Description: "The specified bucket is not valid.",
|
||||
@@ -677,6 +698,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingPart: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "You must specify at least one part",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidPartOrder: {
|
||||
Code: "InvalidPartOrder",
|
||||
Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
||||
@@ -822,11 +848,21 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Request is not valid yet",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrSlowDown: {
|
||||
Code: "SlowDown",
|
||||
ErrSlowDownRead: {
|
||||
Code: "SlowDownRead",
|
||||
Description: "Resource requested is unreadable, please reduce your request rate",
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
ErrSlowDownWrite: {
|
||||
Code: "SlowDownWrite",
|
||||
Description: "Resource requested is unwritable, please reduce your request rate",
|
||||
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: {
|
||||
Code: "InvalidPrefixMarker",
|
||||
Description: "Invalid marker prefix combination",
|
||||
@@ -927,9 +963,9 @@ var errorCodes = errorCodeMap{
|
||||
Description: "No matching ExistingsObjects rule enabled",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrRemoteTargetDenyEditError: {
|
||||
Code: "XMinioAdminRemoteTargetDenyEdit",
|
||||
Description: "Cannot alter remote target endpoint since this server is in a cluster replication setup. use `mc admin replicate update`",
|
||||
ErrRemoteTargetDenyAddError: {
|
||||
Code: "XMinioAdminRemoteTargetDenyAdd",
|
||||
Description: "Cannot add remote target endpoint since this server is in a cluster replication setup",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrReplicationDenyEditError: {
|
||||
@@ -987,6 +1023,16 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Versioning must be 'Enabled' on the bucket to add a replication target",
|
||||
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: {
|
||||
Code: "NoSuchObjectLockConfiguration",
|
||||
Description: "The specified object does not have a ObjectLock configuration",
|
||||
@@ -1100,8 +1146,13 @@ var errorCodes = errorCodeMap{
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidEncryptionMethod: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "The encryption method specified is not supported",
|
||||
Code: "InvalidArgument",
|
||||
Description: "Server Side Encryption with AWS KMS managed key requires HTTP header x-amz-server-side-encryption : aws:kms",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrIncompatibleEncryptionMethod: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Server Side Encryption with Customer provided key is incompatible with the encryption method specified",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidEncryptionKeyID: {
|
||||
@@ -1164,11 +1215,6 @@ var errorCodes = errorCodeMap{
|
||||
Description: "The provided encryption parameters did not match the ones used originally.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrIncompatibleEncryptionMethod: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Server side encryption specified with both SSE-C and SSE-S3 headers",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrKMSNotConfigured: {
|
||||
Code: "NotImplemented",
|
||||
Description: "Server side encryption specified but KMS is not configured",
|
||||
@@ -1248,11 +1294,21 @@ var errorCodes = errorCodeMap{
|
||||
Description: "The JSON you provided was not well-formed or did not validate against our published format.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidLifecycleQueryParameter: {
|
||||
Code: "XMinioInvalidLifecycleParameter",
|
||||
Description: "The boolean value provided for withUpdatedAt query parameter was invalid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrAdminNoSuchUser: {
|
||||
Code: "XMinioAdminNoSuchUser",
|
||||
Description: "The specified user does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrAdminNoSuchUserLDAPWarn: {
|
||||
Code: "XMinioAdminNoSuchUser",
|
||||
Description: "The specified user does not exist. If you meant a user in LDAP, use `mc idp ldap`",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrAdminNoSuchGroup: {
|
||||
Code: "XMinioAdminNoSuchGroup",
|
||||
Description: "The specified group does not exist.",
|
||||
@@ -1347,7 +1403,7 @@ var errorCodes = errorCodeMap{
|
||||
},
|
||||
ErrAdminConfigIDPCfgNameAlreadyExists: {
|
||||
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,
|
||||
},
|
||||
ErrAdminConfigIDPCfgNameDoesNotExist: {
|
||||
@@ -1385,7 +1441,7 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Cannot respond to plain-text request from TLS-encrypted server",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrOperationTimedOut: {
|
||||
ErrRequestTimedout: {
|
||||
Code: "RequestTimeout",
|
||||
Description: "A timeout occurred while trying to lock a resource, please reduce your request rate",
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
@@ -1395,9 +1451,9 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Client disconnected before response was ready",
|
||||
HTTPStatusCode: 499, // No official code, use nginx value.
|
||||
},
|
||||
ErrOperationMaxedOut: {
|
||||
Code: "SlowDown",
|
||||
Description: "A timeout exceeded while waiting to proceed with the request, please reduce your request rate",
|
||||
ErrTooManyRequests: {
|
||||
Code: "TooManyRequests",
|
||||
Description: "Deadline exceeded while waiting in incoming queue, please reduce your request rate",
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
ErrUnsupportedMetadata: {
|
||||
@@ -1405,6 +1461,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Your metadata headers are not supported.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrUnsupportedHostHeader: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Your Host header is malformed.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrObjectTampered: {
|
||||
Code: "XMinioObjectTampered",
|
||||
Description: errObjectTampered.Error(),
|
||||
@@ -2008,11 +2069,14 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
}
|
||||
|
||||
// Only return ErrClientDisconnected if the provided context is actually canceled.
|
||||
// This way downstream context.Canceled will still report ErrOperationTimedOut
|
||||
// This way downstream context.Canceled will still report ErrRequestTimedout
|
||||
if contextCanceled(ctx) && errors.Is(ctx.Err(), context.Canceled) {
|
||||
return ErrClientDisconnected
|
||||
}
|
||||
|
||||
// Unwrap the error first
|
||||
err = unwrapAll(err)
|
||||
|
||||
switch err {
|
||||
case errInvalidArgument:
|
||||
apiErr = ErrAdminInvalidArgument
|
||||
@@ -2020,6 +2084,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrAdminNoSuchPolicy
|
||||
case errNoSuchUser:
|
||||
apiErr = ErrAdminNoSuchUser
|
||||
case errNoSuchUserLDAPWarn:
|
||||
apiErr = ErrAdminNoSuchUserLDAPWarn
|
||||
case errNoSuchServiceAccount:
|
||||
apiErr = ErrAdminServiceAccountNotFound
|
||||
case errNoSuchGroup:
|
||||
@@ -2047,9 +2113,11 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
case errInvalidStorageClass:
|
||||
apiErr = ErrInvalidStorageClass
|
||||
case errErasureReadQuorum:
|
||||
apiErr = ErrSlowDown
|
||||
apiErr = ErrSlowDownRead
|
||||
case errErasureWriteQuorum:
|
||||
apiErr = ErrSlowDown
|
||||
apiErr = ErrSlowDownWrite
|
||||
case errMaxVersionsExceeded:
|
||||
apiErr = ErrMaxVersionsExceeded
|
||||
// SSE errors
|
||||
case errInvalidEncryptionParameters:
|
||||
apiErr = ErrInvalidEncryptionParameters
|
||||
@@ -2083,10 +2151,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrKMSKeyNotFoundException
|
||||
case errKMSDefaultKeyAlreadyConfigured:
|
||||
apiErr = ErrKMSDefaultKeyAlreadyConfigured
|
||||
case context.Canceled, context.DeadlineExceeded:
|
||||
apiErr = ErrOperationTimedOut
|
||||
case errDiskNotFound:
|
||||
apiErr = ErrSlowDown
|
||||
case context.Canceled:
|
||||
apiErr = ErrClientDisconnected
|
||||
case context.DeadlineExceeded:
|
||||
apiErr = ErrRequestTimedout
|
||||
case objectlock.ErrInvalidRetentionDate:
|
||||
apiErr = ErrInvalidRetentionDate
|
||||
case objectlock.ErrPastObjectLockRetainDate:
|
||||
@@ -2099,6 +2167,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrMalformedXML
|
||||
case errInvalidMaxParts:
|
||||
apiErr = ErrInvalidMaxParts
|
||||
case ioutil.ErrOverread:
|
||||
apiErr = ErrExcessData
|
||||
}
|
||||
|
||||
// Compression errors
|
||||
@@ -2163,9 +2233,9 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
case InvalidPart:
|
||||
apiErr = ErrInvalidPart
|
||||
case InsufficientWriteQuorum:
|
||||
apiErr = ErrSlowDown
|
||||
apiErr = ErrSlowDownWrite
|
||||
case InsufficientReadQuorum:
|
||||
apiErr = ErrSlowDown
|
||||
apiErr = ErrSlowDownRead
|
||||
case InvalidMarkerPrefixCombination:
|
||||
apiErr = ErrNotImplemented
|
||||
case InvalidUploadIDKeyCombination:
|
||||
@@ -2180,10 +2250,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrContentSHA256Mismatch
|
||||
case hash.ChecksumMismatch:
|
||||
apiErr = ErrContentChecksumMismatch
|
||||
case ObjectTooLarge:
|
||||
apiErr = ErrEntityTooLarge
|
||||
case ObjectTooSmall:
|
||||
case hash.SizeTooSmall:
|
||||
apiErr = ErrEntityTooSmall
|
||||
case hash.SizeTooLarge:
|
||||
apiErr = ErrEntityTooLarge
|
||||
case NotImplemented:
|
||||
apiErr = ErrNotImplemented
|
||||
case PartTooBig:
|
||||
@@ -2259,7 +2329,7 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
case *event.ErrUnsupportedConfiguration:
|
||||
apiErr = ErrUnsupportedNotification
|
||||
case OperationTimedOut:
|
||||
apiErr = ErrOperationTimedOut
|
||||
apiErr = ErrRequestTimedout
|
||||
case BackendDown:
|
||||
apiErr = ErrBackendDown
|
||||
case ObjectNameTooLong:
|
||||
@@ -2354,7 +2424,7 @@ func toAPIError(ctx context.Context, err error) APIError {
|
||||
}
|
||||
case lifecycle.Error:
|
||||
apiErr = APIError{
|
||||
Code: "InvalidRequest",
|
||||
Code: "InvalidArgument",
|
||||
Description: e.Error(),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
@@ -42,8 +40,8 @@ var toAPIErrorTests = []struct {
|
||||
{err: ObjectNameInvalid{}, errCode: ErrInvalidObjectName},
|
||||
{err: InvalidUploadID{}, errCode: ErrNoSuchUpload},
|
||||
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
||||
{err: InsufficientReadQuorum{}, errCode: ErrSlowDown},
|
||||
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDown},
|
||||
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
|
||||
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
|
||||
{err: InvalidMarkerPrefixCombination{}, errCode: ErrNotImplemented},
|
||||
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
||||
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
|
||||
@@ -67,11 +65,6 @@ var toAPIErrorTests = []struct {
|
||||
}
|
||||
|
||||
func TestAPIErrCode(t *testing.T) {
|
||||
disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
defer os.RemoveAll(disk)
|
||||
|
||||
initFSObjects(disk, t)
|
||||
|
||||
ctx := context.Background()
|
||||
for i, testCase := range toAPIErrorTests {
|
||||
errCode := toAPIErrorCode(ctx, testCase.err)
|
||||
|
||||
+11
-7
@@ -23,11 +23,11 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"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
|
||||
if len(objInfo.UserTags) > 0 {
|
||||
tags, _ := url.ParseQuery(objInfo.UserTags)
|
||||
if len(tags) > 0 {
|
||||
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(len(tags))}
|
||||
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||
if tags.Count() > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||
// Do not need to send any internal metadata
|
||||
// values to client.
|
||||
continue
|
||||
@@ -166,7 +170,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
||||
|
||||
var isSet bool
|
||||
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
|
||||
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(userMetadataPrefix)) {
|
||||
if !stringsHasPrefixFold(k, userMetadataPrefix) {
|
||||
continue
|
||||
}
|
||||
w.Header()[strings.ToLower(k)] = []string{v}
|
||||
@@ -203,7 +207,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
||||
}
|
||||
|
||||
// Set the relevant version ID as part of the response header.
|
||||
if objInfo.VersionID != "" {
|
||||
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
|
||||
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string,
|
||||
maxkeys = maxObjectList
|
||||
}
|
||||
|
||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
||||
marker = trimLeadingSlash(values.Get("marker"))
|
||||
prefix = values.Get("prefix")
|
||||
marker = values.Get("marker")
|
||||
delimiter = values.Get("delimiter")
|
||||
encodingType = values.Get("encoding-type")
|
||||
return
|
||||
@@ -57,8 +57,8 @@ func getListBucketObjectVersionsArgs(values url.Values) (prefix, marker, delimit
|
||||
maxkeys = maxObjectList
|
||||
}
|
||||
|
||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
||||
marker = trimLeadingSlash(values.Get("key-marker"))
|
||||
prefix = values.Get("prefix")
|
||||
marker = values.Get("key-marker")
|
||||
delimiter = values.Get("delimiter")
|
||||
encodingType = values.Get("encoding-type")
|
||||
versionIDMarker = values.Get("version-id-marker")
|
||||
@@ -87,8 +87,8 @@ func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimit
|
||||
maxkeys = maxObjectList
|
||||
}
|
||||
|
||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
||||
startAfter = trimLeadingSlash(values.Get("start-after"))
|
||||
prefix = values.Get("prefix")
|
||||
startAfter = values.Get("start-after")
|
||||
delimiter = values.Get("delimiter")
|
||||
fetchOwner = values.Get("fetch-owner") == "true"
|
||||
encodingType = values.Get("encoding-type")
|
||||
@@ -118,8 +118,8 @@ func getBucketMultipartResources(values url.Values) (prefix, keyMarker, uploadID
|
||||
maxUploads = maxUploadsList
|
||||
}
|
||||
|
||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
||||
keyMarker = trimLeadingSlash(values.Get("key-marker"))
|
||||
prefix = values.Get("prefix")
|
||||
keyMarker = values.Get("key-marker")
|
||||
uploadIDMarker = values.Get("upload-id-marker")
|
||||
delimiter = values.Get("delimiter")
|
||||
encodingType = values.Get("encoding-type")
|
||||
|
||||
+35
-12
@@ -85,7 +85,7 @@ type ListVersionsResponse struct {
|
||||
VersionIDMarker string `xml:"VersionIdMarker"`
|
||||
|
||||
MaxKeys int
|
||||
Delimiter string
|
||||
Delimiter string `xml:"Delimiter,omitempty"`
|
||||
// A flag that indicates whether or not ListObjects returned all of the results
|
||||
// that satisfied the search criteria.
|
||||
IsTruncated bool
|
||||
@@ -115,7 +115,7 @@ type ListObjectsResponse struct {
|
||||
NextMarker string `xml:"NextMarker,omitempty"`
|
||||
|
||||
MaxKeys int
|
||||
Delimiter string
|
||||
Delimiter string `xml:"Delimiter,omitempty"`
|
||||
// A flag that indicates whether or not ListObjects returned all of the results
|
||||
// that satisfied the search criteria.
|
||||
IsTruncated bool
|
||||
@@ -146,7 +146,7 @@ type ListObjectsV2Response struct {
|
||||
|
||||
KeyCount int
|
||||
MaxKeys int
|
||||
Delimiter string
|
||||
Delimiter string `xml:"Delimiter,omitempty"`
|
||||
// A flag that indicates whether or not ListObjects returned all of the results
|
||||
// that satisfied the search criteria.
|
||||
IsTruncated bool
|
||||
@@ -205,7 +205,7 @@ type ListMultipartUploadsResponse struct {
|
||||
UploadIDMarker string `xml:"UploadIdMarker"`
|
||||
NextKeyMarker string
|
||||
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
|
||||
Delimiter string
|
||||
Delimiter string `xml:"Delimiter,omitempty"`
|
||||
Prefix string
|
||||
EncodingType string `xml:"EncodingType,omitempty"`
|
||||
MaxUploads int
|
||||
@@ -345,6 +345,13 @@ func (s *Metadata) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||
return e.EncodeToken(start.End())
|
||||
}
|
||||
|
||||
// ObjectInternalInfo contains some internal information about a given
|
||||
// object, it will printed in listing calls with enabled metadata.
|
||||
type ObjectInternalInfo struct {
|
||||
K int // Data blocks
|
||||
M int // Parity blocks
|
||||
}
|
||||
|
||||
// Object container for object metadata
|
||||
type Object struct {
|
||||
Key string
|
||||
@@ -353,7 +360,7 @@ type Object struct {
|
||||
Size int64
|
||||
|
||||
// Owner of the object.
|
||||
Owner Owner
|
||||
Owner *Owner `xml:"Owner,omitempty"`
|
||||
|
||||
// The class of storage used to store the object.
|
||||
StorageClass string
|
||||
@@ -361,6 +368,8 @@ type Object struct {
|
||||
// UserMetadata user-defined metadata
|
||||
UserMetadata *Metadata `xml:"UserMetadata,omitempty"`
|
||||
UserTags string `xml:"UserTags,omitempty"`
|
||||
|
||||
Internal *ObjectInternalInfo `xml:"Internal,omitempty"`
|
||||
}
|
||||
|
||||
// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
|
||||
@@ -497,7 +506,7 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||
|
||||
owner := Owner{
|
||||
owner := &Owner{
|
||||
ID: globalMinioDefaultOwnerID,
|
||||
DisplayName: "minio",
|
||||
}
|
||||
@@ -541,7 +550,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||
}
|
||||
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
|
||||
// values to client.
|
||||
continue
|
||||
@@ -552,6 +561,10 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
||||
}
|
||||
content.UserMetadata.Set(k, v)
|
||||
}
|
||||
content.Internal = &ObjectInternalInfo{
|
||||
K: object.DataBlocks,
|
||||
M: object.ParityBlocks,
|
||||
}
|
||||
}
|
||||
content.Owner = owner
|
||||
content.VersionID = object.VersionID
|
||||
@@ -589,7 +602,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
||||
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
||||
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
||||
contents := make([]Object, 0, len(resp.Objects))
|
||||
owner := Owner{
|
||||
owner := &Owner{
|
||||
ID: globalMinioDefaultOwnerID,
|
||||
DisplayName: "minio",
|
||||
}
|
||||
@@ -638,10 +651,14 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
||||
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
||||
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
|
||||
contents := make([]Object, 0, len(objects))
|
||||
owner := Owner{
|
||||
ID: globalMinioDefaultOwnerID,
|
||||
DisplayName: "minio",
|
||||
var owner *Owner
|
||||
if fetchOwner {
|
||||
owner = &Owner{
|
||||
ID: globalMinioDefaultOwnerID,
|
||||
DisplayName: "minio",
|
||||
}
|
||||
}
|
||||
|
||||
data := ListObjectsV2Response{}
|
||||
|
||||
for _, object := range objects {
|
||||
@@ -676,7 +693,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||
}
|
||||
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
|
||||
// values to client.
|
||||
continue
|
||||
@@ -687,6 +704,10 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
||||
}
|
||||
content.UserMetadata.Set(k, v)
|
||||
}
|
||||
content.Internal = &ObjectInternalInfo{
|
||||
K: object.DataBlocks,
|
||||
M: object.ParityBlocks,
|
||||
}
|
||||
}
|
||||
}
|
||||
contents = append(contents, content)
|
||||
@@ -924,6 +945,8 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
||||
}
|
||||
|
||||
func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
|
||||
w.Header().Set(xMinIOErrCodeHeader, err.Code)
|
||||
w.Header().Set(xMinIOErrDescHeader, "\""+err.Description+"\"")
|
||||
writeResponse(w, err.HTTPStatusCode, nil, mimeNone)
|
||||
}
|
||||
|
||||
|
||||
+9
-10
@@ -245,10 +245,10 @@ func registerAPIRouter(router *mux.Router) {
|
||||
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
||||
HandlerFunc(collectAPIStats("copyobjectpart", maxClients(gz(httpTraceAll(api.CopyObjectPartHandler))))).
|
||||
Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
||||
// PutObjectPart
|
||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||
collectAPIStats("putobjectpart", maxClients(gz(httpTraceHdrs(api.PutObjectPartHandler))))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||
collectAPIStats("putobjectpart", maxClients(gz(httpTraceHdrs(api.PutObjectPartHandler))))).Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
||||
// ListObjectParts
|
||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||
collectAPIStats("listobjectparts", maxClients(gz(httpTraceAll(api.ListObjectPartsHandler))))).Queries("uploadId", "{uploadId:.*}")
|
||||
@@ -464,6 +464,9 @@ func registerAPIRouter(router *mux.Router) {
|
||||
// GetBucketReplicationMetrics
|
||||
router.Methods(http.MethodGet).HandlerFunc(
|
||||
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
|
||||
for _, r := range rejectedBucketAPIs {
|
||||
@@ -520,14 +523,9 @@ func corsHandler(handler http.Handler) http.Handler {
|
||||
"x-amz*",
|
||||
"*",
|
||||
}
|
||||
|
||||
return cors.New(cors.Options{
|
||||
opts := cors.Options{
|
||||
AllowOriginFunc: func(origin string) bool {
|
||||
allowedOrigins := globalAPIConfig.getCorsAllowOrigins()
|
||||
if len(allowedOrigins) == 0 {
|
||||
allowedOrigins = []string{"*"}
|
||||
}
|
||||
for _, allowedOrigin := range allowedOrigins {
|
||||
for _, allowedOrigin := range globalAPIConfig.getCorsAllowOrigins() {
|
||||
if wildcard.MatchSimple(allowedOrigin, origin) {
|
||||
return true
|
||||
}
|
||||
@@ -546,5 +544,6 @@ func corsHandler(handler http.Handler) http.Handler {
|
||||
AllowedHeaders: commonS3Headers,
|
||||
ExposedHeaders: commonS3Headers,
|
||||
AllowCredentials: true,
|
||||
}).Handler(handler)
|
||||
}
|
||||
return cors.New(opts).Handler(handler)
|
||||
}
|
||||
|
||||
+311
-301
File diff suppressed because one or more lines are too long
+41
-14
@@ -88,6 +88,18 @@ func isRequestSignStreamingV4(r *http.Request) bool {
|
||||
r.Method == http.MethodPut
|
||||
}
|
||||
|
||||
// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
|
||||
func isRequestSignStreamingTrailerV4(r *http.Request) bool {
|
||||
return r.Header.Get(xhttp.AmzContentSha256) == streamingContentSHA256Trailer &&
|
||||
r.Method == http.MethodPut
|
||||
}
|
||||
|
||||
// Verify if the request has AWS Streaming Signature Version '4', with unsigned content and trailer.
|
||||
func isRequestUnsignedTrailerV4(r *http.Request) bool {
|
||||
return r.Header.Get(xhttp.AmzContentSha256) == unsignedPayloadTrailer &&
|
||||
r.Method == http.MethodPut && strings.Contains(r.Header.Get(xhttp.ContentEncoding), streamingContentEncoding)
|
||||
}
|
||||
|
||||
// Authorization type.
|
||||
//
|
||||
//go:generate stringer -type=authType -trimprefix=authType $GOFILE
|
||||
@@ -105,10 +117,12 @@ const (
|
||||
authTypeSignedV2
|
||||
authTypeJWT
|
||||
authTypeSTS
|
||||
authTypeStreamingSignedTrailer
|
||||
authTypeStreamingUnsignedTrailer
|
||||
)
|
||||
|
||||
// Get request authentication type.
|
||||
func getRequestAuthType(r *http.Request) authType {
|
||||
func getRequestAuthType(r *http.Request) (at authType) {
|
||||
if r.URL != nil {
|
||||
var err error
|
||||
r.Form, err = url.ParseQuery(r.URL.RawQuery)
|
||||
@@ -123,6 +137,10 @@ func getRequestAuthType(r *http.Request) authType {
|
||||
return authTypePresignedV2
|
||||
} else if isRequestSignStreamingV4(r) {
|
||||
return authTypeStreamingSigned
|
||||
} else if isRequestSignStreamingTrailerV4(r) {
|
||||
return authTypeStreamingSignedTrailer
|
||||
} else if isRequestUnsignedTrailerV4(r) {
|
||||
return authTypeStreamingUnsignedTrailer
|
||||
} else if isRequestSignatureV4(r) {
|
||||
return authTypeSigned
|
||||
} else if isRequestPresignedSignatureV4(r) {
|
||||
@@ -560,13 +578,15 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
||||
|
||||
// List of all support S3 auth types.
|
||||
var supportedS3AuthTypes = map[authType]struct{}{
|
||||
authTypeAnonymous: {},
|
||||
authTypePresigned: {},
|
||||
authTypePresignedV2: {},
|
||||
authTypeSigned: {},
|
||||
authTypeSignedV2: {},
|
||||
authTypePostPolicy: {},
|
||||
authTypeStreamingSigned: {},
|
||||
authTypeAnonymous: {},
|
||||
authTypePresigned: {},
|
||||
authTypePresignedV2: {},
|
||||
authTypeSigned: {},
|
||||
authTypeSignedV2: {},
|
||||
authTypePostPolicy: {},
|
||||
authTypeStreamingSigned: {},
|
||||
authTypeStreamingSignedTrailer: {},
|
||||
authTypeStreamingUnsignedTrailer: {},
|
||||
}
|
||||
|
||||
// Validate if the authType is valid and supported.
|
||||
@@ -575,14 +595,15 @@ func isSupportedS3AuthType(aType authType) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// setAuthHandler to validate authorization header for the incoming request.
|
||||
func setAuthHandler(h http.Handler) http.Handler {
|
||||
// setAuthMiddleware to validate authorization header for the incoming request.
|
||||
func setAuthMiddleware(h http.Handler) http.Handler {
|
||||
// handler for validating incoming authorization headers.
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
|
||||
|
||||
aType := getRequestAuthType(r)
|
||||
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
|
||||
switch aType {
|
||||
case authTypeSigned, authTypeSignedV2, authTypeStreamingSigned, authTypeStreamingSignedTrailer:
|
||||
// Verify if date headers are set, if not reject the request
|
||||
amzDate, errCode := parseAmzDateHeader(r)
|
||||
if errCode != ErrNone {
|
||||
@@ -613,10 +634,16 @@ func setAuthHandler(h http.Handler) http.Handler {
|
||||
atomic.AddUint64(&globalHTTPStats.rejectedRequestsTime, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
if isSupportedS3AuthType(aType) || aType == authTypeJWT || aType == authTypeSTS {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
case authTypeJWT, authTypeSTS:
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
default:
|
||||
if isSupportedS3AuthType(aType) {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ok {
|
||||
@@ -710,7 +737,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
||||
return ErrSignatureVersionNotSupported
|
||||
case authTypeSignedV2, authTypePresignedV2:
|
||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||
case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
|
||||
case authTypeStreamingSigned, authTypePresigned, authTypeSigned, authTypeStreamingSignedTrailer, authTypeStreamingUnsignedTrailer:
|
||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||
}
|
||||
if s3Err != ErrNone {
|
||||
|
||||
@@ -375,8 +375,6 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
|
||||
initConfigSubsystem(ctx, objLayer)
|
||||
|
||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||
|
||||
creds, err := auth.CreateCredentials("myuser", "mypassword")
|
||||
if err != nil {
|
||||
t.Fatalf("unable create credential, %s", err)
|
||||
@@ -384,6 +382,8 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
|
||||
globalActiveCred = creds
|
||||
|
||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||
|
||||
// List of test cases for validating http request authentication.
|
||||
testCases := []struct {
|
||||
req *http.Request
|
||||
@@ -464,9 +464,8 @@ func TestValidateAdminSignature(t *testing.T) {
|
||||
}
|
||||
|
||||
initAllSubsystems(ctx)
|
||||
initConfigSubsystem(ctx, objLayer)
|
||||
|
||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||
initConfigSubsystem(ctx, objLayer)
|
||||
|
||||
creds, err := auth.CreateCredentials("admin", "mypassword")
|
||||
if err != nil {
|
||||
@@ -474,6 +473,8 @@ func TestValidateAdminSignature(t *testing.T) {
|
||||
}
|
||||
globalActiveCred = creds
|
||||
|
||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||
|
||||
testCases := []struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
|
||||
@@ -18,11 +18,13 @@ func _() {
|
||||
_ = x[authTypeSignedV2-7]
|
||||
_ = x[authTypeJWT-8]
|
||||
_ = x[authTypeSTS-9]
|
||||
_ = x[authTypeStreamingSignedTrailer-10]
|
||||
_ = x[authTypeStreamingUnsignedTrailer-11]
|
||||
}
|
||||
|
||||
const _authType_name = "UnknownAnonymousPresignedPresignedV2PostPolicyStreamingSignedSignedSignedV2JWTSTS"
|
||||
const _authType_name = "UnknownAnonymousPresignedPresignedV2PostPolicyStreamingSignedSignedSignedV2JWTSTSStreamingSignedTrailerStreamingUnsignedTrailer"
|
||||
|
||||
var _authType_index = [...]uint8{0, 7, 16, 25, 36, 46, 61, 67, 75, 78, 81}
|
||||
var _authType_index = [...]uint8{0, 7, 16, 25, 36, 46, 61, 67, 75, 78, 81, 103, 127}
|
||||
|
||||
func (i authType) String() string {
|
||||
if i < 0 || i >= authType(len(_authType_index)-1) {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/env"
|
||||
)
|
||||
|
||||
@@ -30,9 +30,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/env"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -298,6 +300,7 @@ func (h *healingTracker) printTo(writer io.Writer) {
|
||||
b, err := json.MarshalIndent(h, "", " ")
|
||||
if err != nil {
|
||||
writer.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
writer.Write(b)
|
||||
}
|
||||
@@ -344,7 +347,9 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
||||
|
||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||
|
||||
go monitorLocalDisksAndHeal(ctx, z)
|
||||
if env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
||||
go monitorLocalDisksAndHeal(ctx, z)
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||
@@ -480,7 +485,12 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errFileNotFound) {
|
||||
|
||||
+146
-43
@@ -37,8 +37,7 @@ import (
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
@@ -86,6 +85,7 @@ import (
|
||||
// type: "minio"
|
||||
// bucket: "testbucket1"
|
||||
// endpoint: "https://play.min.io"
|
||||
// path: "on"
|
||||
// credentials:
|
||||
// accessKey: "minioadmin"
|
||||
// secretKey: "minioadmin"
|
||||
@@ -174,15 +174,22 @@ type BatchJobReplicateResourceType string
|
||||
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
|
||||
)
|
||||
|
||||
@@ -214,9 +221,15 @@ type BatchJobReplicateTarget struct {
|
||||
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 {
|
||||
@@ -224,9 +237,20 @@ type BatchJobReplicateSource struct {
|
||||
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"`
|
||||
@@ -286,7 +310,7 @@ func (r BatchJobReplicateV1) Notify(ctx context.Context, body io.Reader) error {
|
||||
}
|
||||
|
||||
// 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
|
||||
tgtBucket := r.Target.Bucket
|
||||
srcObject := srcObjInfo.Name
|
||||
@@ -297,10 +321,13 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
||||
|
||||
versioned := globalBucketVersioningSys.PrefixEnabled(tgtBucket, tgtObject)
|
||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(tgtBucket, tgtObject)
|
||||
|
||||
versionID := srcObjInfo.VersionID
|
||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||
versionID = ""
|
||||
}
|
||||
if srcObjInfo.DeleteMarker {
|
||||
_, err := api.DeleteObject(ctx, tgtBucket, tgtObject, ObjectOptions{
|
||||
VersionID: srcObjInfo.VersionID,
|
||||
VersionID: versionID,
|
||||
VersionSuspended: versionSuspended,
|
||||
Versioned: versioned,
|
||||
MTime: srcObjInfo.ModTime,
|
||||
@@ -318,6 +345,9 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
||||
PreserveETag: srcObjInfo.ETag,
|
||||
UserDefined: srcObjInfo.UserDefined,
|
||||
}
|
||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||
opts.VersionID = ""
|
||||
}
|
||||
if crypto.S3.IsEncrypted(srcObjInfo.UserDefined) {
|
||||
opts.ServerSideEncryption = encrypt.NewSSE()
|
||||
}
|
||||
@@ -329,7 +359,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
||||
}
|
||||
return r.copyWithMultipartfromSource(ctx, api, core, srcObjInfo, opts, partsCount)
|
||||
}
|
||||
gopts := minio.GetObjectOptions{
|
||||
gopts := miniogo.GetObjectOptions{
|
||||
VersionID: srcObjInfo.VersionID,
|
||||
}
|
||||
if err := gopts.SetMatchETag(srcObjInfo.ETag); err != nil {
|
||||
@@ -350,7 +380,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
||||
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
|
||||
tgtBucket := r.Target.Bucket
|
||||
srcObject := srcObjInfo.Name
|
||||
@@ -358,7 +388,9 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
||||
if r.Target.Prefix != "" {
|
||||
tgtObject = path.Join(r.Target.Prefix, srcObjInfo.Name)
|
||||
}
|
||||
|
||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||
opts.VersionID = ""
|
||||
}
|
||||
var uploadedParts []CompletePart
|
||||
res, err := api.NewMultipartUpload(context.Background(), tgtBucket, tgtObject, opts)
|
||||
if err != nil {
|
||||
@@ -389,7 +421,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
||||
)
|
||||
|
||||
for i := 0; i < partsCount; i++ {
|
||||
gopts := minio.GetObjectOptions{
|
||||
gopts := miniogo.GetObjectOptions{
|
||||
VersionID: srcObjInfo.VersionID,
|
||||
PartNumber: i + 1,
|
||||
}
|
||||
@@ -402,7 +434,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
||||
}
|
||||
defer rd.Close()
|
||||
|
||||
hr, err = hash.NewReader(rd, objInfo.Size, "", "", objInfo.Size)
|
||||
hr, err = hash.NewReader(io.LimitReader(rd, objInfo.Size), objInfo.Size, "", "", objInfo.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -488,7 +520,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
if len(r.Flags.Filter.Metadata) > 0 {
|
||||
for _, kv := range r.Flags.Filter.Metadata {
|
||||
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
|
||||
}
|
||||
// We only need to match x-amz-meta or standardHeaders
|
||||
@@ -513,16 +545,17 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
cred := r.Source.Creds
|
||||
|
||||
c, err := miniogo.New(u.Host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport,
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport,
|
||||
BucketLookup: lookupStyle(r.Source.Path),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)))
|
||||
if err != nil {
|
||||
@@ -539,19 +572,47 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
retry := false
|
||||
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||
attempts := attempts
|
||||
|
||||
// one of source/target is s3, skip delete marker and all versions under the same object name.
|
||||
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
|
||||
minioSrc := r.Source.Type == BatchJobReplicateResourceMinIO
|
||||
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,
|
||||
WithVersions: true,
|
||||
WithVersions: minioSrc,
|
||||
Recursive: true,
|
||||
WithMetadata: true,
|
||||
})
|
||||
prevObj := ""
|
||||
skipReplicate := false
|
||||
|
||||
for obj := range objInfoCh {
|
||||
oi := toObjectInfo(r.Source.Bucket, obj.Key, obj)
|
||||
if !minioSrc {
|
||||
oi2, err := c.StatObject(ctx, r.Source.Bucket, obj.Key, miniogo.StatObjectOptions{})
|
||||
if err == nil {
|
||||
oi = toObjectInfo(r.Source.Bucket, obj.Key, oi2)
|
||||
} else {
|
||||
if isErrMethodNotAllowed(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) ||
|
||||
isErrObjectNotFound(ErrorRespToObjectError(err, r.Source.Bucket, obj.Key)) {
|
||||
continue
|
||||
}
|
||||
logger.LogIf(ctx, err)
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if skip(oi) {
|
||||
continue
|
||||
}
|
||||
if obj.Key != prevObj {
|
||||
prevObj = obj.Key
|
||||
// skip replication of delete marker and all versions under the same object name if one of source or target is s3.
|
||||
skipReplicate = obj.IsDeleteMarker && s3Type
|
||||
}
|
||||
if skipReplicate {
|
||||
continue
|
||||
}
|
||||
|
||||
wk.Take()
|
||||
go func() {
|
||||
defer wk.Give()
|
||||
@@ -609,7 +670,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
}
|
||||
|
||||
// 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)
|
||||
oi := ObjectInfo{
|
||||
Bucket: bucket,
|
||||
@@ -646,9 +707,10 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
|
||||
tgtBucket := r.Target.Bucket
|
||||
tgtPrefix := r.Target.Prefix
|
||||
srcObject := srcObjInfo.Name
|
||||
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
|
||||
|
||||
if srcObjInfo.DeleteMarker || !srcObjInfo.VersionPurgeStatus.Empty() {
|
||||
if retry {
|
||||
if retry && !s3Type {
|
||||
if _, err := c.StatObject(ctx, tgtBucket, pathJoin(tgtPrefix, srcObject), miniogo.StatObjectOptions{
|
||||
VersionID: srcObjInfo.VersionID,
|
||||
Internal: miniogo.AdvancedGetOptions{
|
||||
@@ -664,7 +726,10 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
|
||||
if srcObjInfo.VersionPurgeStatus.Empty() {
|
||||
dmVersionID = srcObjInfo.VersionID
|
||||
}
|
||||
|
||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||
dmVersionID = ""
|
||||
versionID = ""
|
||||
}
|
||||
return c.RemoveObject(ctx, tgtBucket, pathJoin(tgtPrefix, srcObject), miniogo.RemoveObjectOptions{
|
||||
VersionID: versionID,
|
||||
Internal: miniogo.AdvancedRemoveOptions{
|
||||
@@ -676,7 +741,7 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
|
||||
})
|
||||
}
|
||||
|
||||
if retry { // when we are retrying avoid copying if necessary.
|
||||
if retry && !s3Type { // when we are retrying avoid copying if necessary.
|
||||
gopts := miniogo.GetObjectOptions{}
|
||||
if err := gopts.SetMatchETag(srcObjInfo.ETag); err != nil {
|
||||
return err
|
||||
@@ -710,7 +775,9 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 {
|
||||
putOpts.Internal = miniogo.AdvancedPutOptions{}
|
||||
}
|
||||
if objInfo.isMultipart() {
|
||||
if err := replicateObjectWithMultipart(ctx, c, tgtBucket, pathJoin(tgtPrefix, objInfo.Name), rd, objInfo, putOpts); err != nil {
|
||||
return err
|
||||
@@ -1008,7 +1075,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
if len(r.Flags.Filter.Metadata) > 0 {
|
||||
for _, kv := range r.Flags.Filter.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
|
||||
}
|
||||
// We only need to match x-amz-meta or standardHeaders
|
||||
@@ -1021,6 +1088,10 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
// None of the provided metadata filters match skip the object.
|
||||
return false
|
||||
}
|
||||
// if one of source or target is non MinIO, just replicate the top most version like `mc mirror`
|
||||
if (r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3) && !info.IsLatest {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1033,9 +1104,10 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
cred := r.Target.Creds
|
||||
|
||||
c, err := miniogo.NewCore(u.Host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport,
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport,
|
||||
BucketLookup: lookupStyle(r.Target.Path),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1059,6 +1131,8 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
attempts := attempts
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
// one of source/target is s3, skip delete marker and all versions under the same object name.
|
||||
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
|
||||
|
||||
results := make(chan ObjectInfo, 100)
|
||||
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{
|
||||
@@ -1070,8 +1144,18 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
return err
|
||||
}
|
||||
|
||||
prevObj := ""
|
||||
|
||||
skipReplicate := false
|
||||
for result := range results {
|
||||
result := result
|
||||
if result.Name != prevObj {
|
||||
prevObj = result.Name
|
||||
skipReplicate = result.DeleteMarker && s3Type
|
||||
}
|
||||
if skipReplicate {
|
||||
continue
|
||||
}
|
||||
wk.Take()
|
||||
go func() {
|
||||
defer wk.Give()
|
||||
@@ -1195,6 +1279,13 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
if r.Source.Endpoint != "" && !r.Source.Type.isMinio() && !r.Source.ValidPath() {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
if r.Target.Endpoint != "" && !r.Target.Type.isMinio() && !r.Target.ValidPath() {
|
||||
return errInvalidArgument
|
||||
}
|
||||
if r.Target.Bucket == "" {
|
||||
return errInvalidArgument
|
||||
}
|
||||
@@ -1232,11 +1323,14 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
||||
remoteEp := r.Target.Endpoint
|
||||
remoteBkt := r.Target.Bucket
|
||||
cred := r.Target.Creds
|
||||
pathStyle := r.Target.Path
|
||||
|
||||
if r.Source.Endpoint != "" {
|
||||
remoteEp = r.Source.Endpoint
|
||||
cred = r.Source.Creds
|
||||
remoteBkt = r.Source.Bucket
|
||||
pathStyle = r.Source.Path
|
||||
|
||||
}
|
||||
|
||||
u, err := url.Parse(remoteEp)
|
||||
@@ -1245,9 +1339,10 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
||||
}
|
||||
|
||||
c, err := miniogo.NewCore(u.Host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport,
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteInstanceTransport,
|
||||
BucketLookup: lookupStyle(pathStyle),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1265,8 +1360,10 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
||||
}
|
||||
return err
|
||||
}
|
||||
// if both source and target are minio instances
|
||||
minioType := r.Target.Type == BatchJobReplicateResourceMinIO && r.Source.Type == BatchJobReplicateResourceMinIO
|
||||
// If source has versioning enabled, target must have versioning enabled
|
||||
if (info.Versioning && !vcfg.Enabled() && !isRemoteToLocal) || (!info.Versioning && vcfg.Enabled() && isRemoteToLocal) {
|
||||
if minioType && ((info.Versioning && !vcfg.Enabled() && !isRemoteToLocal) || (!info.Versioning && vcfg.Enabled() && isRemoteToLocal)) {
|
||||
return batchReplicationJobError{
|
||||
Code: "InvalidBucketState",
|
||||
Description: fmt.Sprintf("The source '%s' has versioning enabled, target '%s' must have versioning enabled",
|
||||
@@ -1366,9 +1463,7 @@ func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (p
|
||||
// ListBatchJobs - lists all currently active batch jobs, optionally takes {jobType}
|
||||
// input to list only active batch jobs of 'jobType'
|
||||
func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListBatchJobs")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListBatchJobsAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1418,9 +1513,7 @@ var errNoSuchJob = errors.New("no such job")
|
||||
|
||||
// DescribeBatchJob returns the currently active batch job definition
|
||||
func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "DescribeBatchJob")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DescribeBatchJobAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1455,9 +1548,7 @@ func (a adminAPIHandlers) DescribeBatchJob(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
// StarBatchJob queue a new job for execution
|
||||
func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "StartBatchJob")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, creds := validateAdminReq(ctx, w, r, iampolicy.StartBatchJobAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1511,9 +1602,7 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// CancelBatchJob cancels a job in progress
|
||||
func (a adminAPIHandlers) CancelBatchJob(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "CancelBatchJob")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CancelBatchJobAction)
|
||||
if objectAPI == nil {
|
||||
@@ -1811,3 +1900,17 @@ func (m *batchJobMetrics) trace(d batchJobMetric, job string, attempts int, info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lookupStyle(s string) miniogo.BucketLookupType {
|
||||
var lookup miniogo.BucketLookupType
|
||||
switch s {
|
||||
case "on":
|
||||
lookup = miniogo.BucketLookupPath
|
||||
case "off":
|
||||
lookup = miniogo.BucketLookupDNS
|
||||
default:
|
||||
lookup = miniogo.BucketLookupAuto
|
||||
|
||||
}
|
||||
return lookup
|
||||
}
|
||||
|
||||
+60
-10
@@ -702,6 +702,12 @@ func (z *BatchJobReplicateSource) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "Path":
|
||||
z.Path, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Path")
|
||||
return
|
||||
}
|
||||
case "Creds":
|
||||
var zb0003 uint32
|
||||
zb0003, err = dc.ReadMapHeader()
|
||||
@@ -756,9 +762,9 @@ func (z *BatchJobReplicateSource) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *BatchJobReplicateSource) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 5
|
||||
// map header, size 6
|
||||
// write "Type"
|
||||
err = en.Append(0x85, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
err = en.Append(0x86, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -797,6 +803,16 @@ func (z *BatchJobReplicateSource) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
// write "Path"
|
||||
err = en.Append(0xa4, 0x50, 0x61, 0x74, 0x68)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Path)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Path")
|
||||
return
|
||||
}
|
||||
// write "Creds"
|
||||
err = en.Append(0xa5, 0x43, 0x72, 0x65, 0x64, 0x73)
|
||||
if err != nil {
|
||||
@@ -839,9 +855,9 @@ func (z *BatchJobReplicateSource) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *BatchJobReplicateSource) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 5
|
||||
// map header, size 6
|
||||
// string "Type"
|
||||
o = append(o, 0x85, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
o = append(o, 0x86, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
o = msgp.AppendString(o, string(z.Type))
|
||||
// string "Bucket"
|
||||
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
@@ -852,6 +868,9 @@ func (z *BatchJobReplicateSource) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "Endpoint"
|
||||
o = append(o, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
o = msgp.AppendString(o, z.Endpoint)
|
||||
// string "Path"
|
||||
o = append(o, 0xa4, 0x50, 0x61, 0x74, 0x68)
|
||||
o = msgp.AppendString(o, z.Path)
|
||||
// string "Creds"
|
||||
o = append(o, 0xa5, 0x43, 0x72, 0x65, 0x64, 0x73)
|
||||
// map header, size 3
|
||||
@@ -913,6 +932,12 @@ func (z *BatchJobReplicateSource) UnmarshalMsg(bts []byte) (o []byte, err error)
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "Path":
|
||||
z.Path, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Path")
|
||||
return
|
||||
}
|
||||
case "Creds":
|
||||
var zb0003 uint32
|
||||
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
@@ -968,7 +993,7 @@ func (z *BatchJobReplicateSource) UnmarshalMsg(bts []byte) (o []byte, err error)
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *BatchJobReplicateSource) Msgsize() (s int) {
|
||||
s = 1 + 5 + msgp.StringPrefixSize + len(string(z.Type)) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 6 + 1 + 10 + msgp.StringPrefixSize + len(z.Creds.AccessKey) + 10 + msgp.StringPrefixSize + len(z.Creds.SecretKey) + 13 + msgp.StringPrefixSize + len(z.Creds.SessionToken)
|
||||
s = 1 + 5 + msgp.StringPrefixSize + len(string(z.Type)) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 5 + msgp.StringPrefixSize + len(z.Path) + 6 + 1 + 10 + msgp.StringPrefixSize + len(z.Creds.AccessKey) + 10 + msgp.StringPrefixSize + len(z.Creds.SecretKey) + 13 + msgp.StringPrefixSize + len(z.Creds.SessionToken)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1018,6 +1043,12 @@ func (z *BatchJobReplicateTarget) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "Path":
|
||||
z.Path, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Path")
|
||||
return
|
||||
}
|
||||
case "Creds":
|
||||
var zb0003 uint32
|
||||
zb0003, err = dc.ReadMapHeader()
|
||||
@@ -1072,9 +1103,9 @@ func (z *BatchJobReplicateTarget) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *BatchJobReplicateTarget) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 5
|
||||
// map header, size 6
|
||||
// write "Type"
|
||||
err = en.Append(0x85, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
err = en.Append(0x86, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -1113,6 +1144,16 @@ func (z *BatchJobReplicateTarget) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
// write "Path"
|
||||
err = en.Append(0xa4, 0x50, 0x61, 0x74, 0x68)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Path)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Path")
|
||||
return
|
||||
}
|
||||
// write "Creds"
|
||||
err = en.Append(0xa5, 0x43, 0x72, 0x65, 0x64, 0x73)
|
||||
if err != nil {
|
||||
@@ -1155,9 +1196,9 @@ func (z *BatchJobReplicateTarget) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *BatchJobReplicateTarget) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 5
|
||||
// map header, size 6
|
||||
// string "Type"
|
||||
o = append(o, 0x85, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
o = append(o, 0x86, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
o = msgp.AppendString(o, string(z.Type))
|
||||
// string "Bucket"
|
||||
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
@@ -1168,6 +1209,9 @@ func (z *BatchJobReplicateTarget) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "Endpoint"
|
||||
o = append(o, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
o = msgp.AppendString(o, z.Endpoint)
|
||||
// string "Path"
|
||||
o = append(o, 0xa4, 0x50, 0x61, 0x74, 0x68)
|
||||
o = msgp.AppendString(o, z.Path)
|
||||
// string "Creds"
|
||||
o = append(o, 0xa5, 0x43, 0x72, 0x65, 0x64, 0x73)
|
||||
// map header, size 3
|
||||
@@ -1229,6 +1273,12 @@ func (z *BatchJobReplicateTarget) UnmarshalMsg(bts []byte) (o []byte, err error)
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "Path":
|
||||
z.Path, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Path")
|
||||
return
|
||||
}
|
||||
case "Creds":
|
||||
var zb0003 uint32
|
||||
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
@@ -1284,7 +1334,7 @@ func (z *BatchJobReplicateTarget) UnmarshalMsg(bts []byte) (o []byte, err error)
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *BatchJobReplicateTarget) Msgsize() (s int) {
|
||||
s = 1 + 5 + msgp.StringPrefixSize + len(string(z.Type)) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 6 + 1 + 10 + msgp.StringPrefixSize + len(z.Creds.AccessKey) + 10 + msgp.StringPrefixSize + len(z.Creds.SecretKey) + 13 + msgp.StringPrefixSize + len(z.Creds.SessionToken)
|
||||
s = 1 + 5 + msgp.StringPrefixSize + len(string(z.Type)) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 5 + msgp.StringPrefixSize + len(z.Path) + 6 + 1 + 10 + msgp.StringPrefixSize + len(z.Creds.AccessKey) + 10 + msgp.StringPrefixSize + len(z.Creds.SecretKey) + 13 + msgp.StringPrefixSize + len(z.Creds.SessionToken)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -289,7 +289,7 @@ func (r *BatchJobKeyRotateV1) KeyRotate(ctx context.Context, api ObjectLayer, ob
|
||||
)
|
||||
encMetadata := make(map[string]string)
|
||||
for k, v := range oi.UserDefined {
|
||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||
encMetadata[k] = v
|
||||
}
|
||||
}
|
||||
@@ -401,7 +401,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
if len(r.Flags.Filter.Metadata) > 0 {
|
||||
for _, kv := range r.Flags.Filter.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
|
||||
}
|
||||
// We only need to match x-amz-meta or standardHeaders
|
||||
|
||||
+11
-4
@@ -94,16 +94,23 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
|
||||
r, w := io.Pipe()
|
||||
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)
|
||||
go func() {
|
||||
defer bw.canClose.Done()
|
||||
|
||||
totalFileSize := int64(-1) // For compressed objects length will be unknown (represented by length=-1)
|
||||
if length != -1 {
|
||||
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
||||
totalFileSize = bitrotSumsTotalSize + length
|
||||
}
|
||||
r.CloseWithError(disk.CreateFile(context.TODO(), volume, filePath, totalFileSize, r))
|
||||
bw.canClose.Done()
|
||||
}()
|
||||
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)
|
||||
if err != nil {
|
||||
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",
|
||||
b.disk, b.volume, b.filePath, err))
|
||||
b.disk, b.volume, b.filePath, err), "bitrot-read-file-stream-"+b.volume+"-"+b.filePath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/pubsub"
|
||||
)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
@@ -152,14 +153,18 @@ func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// registerBootstrapRESTHandlers - register bootstrap rest router.
|
||||
func registerBootstrapRESTHandlers(router *mux.Router) {
|
||||
h := func(f http.HandlerFunc) http.HandlerFunc {
|
||||
return collectInternodeStats(httpTraceHdrs(f))
|
||||
}
|
||||
|
||||
server := &bootstrapRESTServer{}
|
||||
subrouter := router.PathPrefix(bootstrapRESTPrefix).Subrouter()
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth).HandlerFunc(
|
||||
httpTraceHdrs(server.HealthHandler))
|
||||
h(server.HealthHandler))
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify).HandlerFunc(
|
||||
httpTraceHdrs(server.VerifyHandler))
|
||||
h(server.VerifyHandler))
|
||||
}
|
||||
|
||||
// client to talk to bootstrap NEndpoints.
|
||||
@@ -216,6 +221,7 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
||||
for onlineServers < len(clnts)/2 {
|
||||
for _, clnt := range clnts {
|
||||
if err := clnt.Verify(ctx, srcCfg); err != nil {
|
||||
bootstrapTrace(fmt.Sprintf("clnt.Verify: %v, endpoint: %v", err, clnt.endpoint))
|
||||
if !isNetworkError(err) {
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err), clnt.String())
|
||||
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err))
|
||||
@@ -264,7 +270,11 @@ func newBootstrapRESTClients(endpointServerPools EndpointServerPools) []*bootstr
|
||||
|
||||
// Only proceed for remote endpoints.
|
||||
if !endpoint.IsLocal {
|
||||
clnts = append(clnts, newBootstrapRESTClient(endpoint))
|
||||
cl := newBootstrapRESTClient(endpoint)
|
||||
if serverDebugLog {
|
||||
cl.restClient.TraceOutput = os.Stdout
|
||||
}
|
||||
clnts = append(clnts, cl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/kes-go"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
|
||||
+383
-78
@@ -20,14 +20,20 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -35,8 +41,10 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/minio/mux"
|
||||
"github.com/valyala/bytebufferpool"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
@@ -49,6 +57,7 @@ import (
|
||||
"github.com/minio/minio/internal/handlers"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
@@ -60,6 +69,9 @@ const (
|
||||
objectLockConfig = "object-lock.xml"
|
||||
bucketTaggingConfig = "tagging.xml"
|
||||
bucketReplicationConfig = "replication.xml"
|
||||
|
||||
xMinIOErrCodeHeader = "x-minio-error-code"
|
||||
xMinIOErrDescHeader = "x-minio-error-desc"
|
||||
)
|
||||
|
||||
// Check if there are buckets on server without corresponding entry in etcd backend and
|
||||
@@ -429,6 +441,11 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
// The max. XML contains 100000 object names (each at most 1024 bytes long) + XML overhead
|
||||
const maxBodySize = 2 * 100000 * 1024
|
||||
|
||||
if r.ContentLength > maxBodySize {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal list of keys to be deleted.
|
||||
deleteObjectsReq := &DeleteObjectsRequest{}
|
||||
if err := xmlDecoder(r.Body, deleteObjectsReq, maxBodySize); err != nil {
|
||||
@@ -540,6 +557,11 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
oss[index].SetTransitionState(goi.TransitionedObject)
|
||||
}
|
||||
|
||||
// All deletes on directory objects needs to be for `nullVersionID`
|
||||
if isDirObject(object.ObjectName) && object.VersionID == "" {
|
||||
object.VersionID = nullVersionID
|
||||
}
|
||||
|
||||
if replicateDeletes {
|
||||
dsc = checkReplicateDelete(ctx, bucket, ObjectToDelete{
|
||||
ObjectV: ObjectV{
|
||||
@@ -558,8 +580,8 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
}
|
||||
}
|
||||
if object.VersionID != "" && hasLockEnabled {
|
||||
if apiErrCode := enforceRetentionBypassForDelete(ctx, r, bucket, object, goi, gerr); apiErrCode != ErrNone {
|
||||
apiErr := errorCodes.ToAPIErr(apiErrCode)
|
||||
if err := enforceRetentionBypassForDelete(ctx, r, bucket, object, goi, gerr); err != nil {
|
||||
apiErr := toAPIError(ctx, err)
|
||||
deleteResults[index].errInfo = DeleteError{
|
||||
Code: apiErr.Code,
|
||||
Message: apiErr.Description,
|
||||
@@ -634,6 +656,11 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
if deleteResult.errInfo.Code != "" {
|
||||
deleteErrors = append(deleteErrors, deleteResult.errInfo)
|
||||
} else {
|
||||
// All deletes on directory objects was with `nullVersionID`.
|
||||
// Remove it from response.
|
||||
if isDirObject(deleteResult.delInfo.ObjectName) && deleteResult.delInfo.VersionID == nullVersionID {
|
||||
deleteResult.delInfo.VersionID = ""
|
||||
}
|
||||
deletedObjects = append(deletedObjects, deleteResult.delInfo)
|
||||
}
|
||||
}
|
||||
@@ -649,6 +676,11 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
}
|
||||
|
||||
if replicateDeletes && (dobj.DeleteMarkerReplicationStatus() == replication.Pending || dobj.VersionPurgeStatus() == Pending) {
|
||||
// copy so we can re-add null ID.
|
||||
dobj := dobj
|
||||
if isDirObject(dobj.ObjectName) && dobj.VersionID == "" {
|
||||
dobj.VersionID = nullVersionID
|
||||
}
|
||||
dv := DeletedObjectReplicationInfo{
|
||||
DeletedObject: dobj,
|
||||
Bucket: bucket,
|
||||
@@ -882,14 +914,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
|
||||
bucket := mux.Vars(r)["bucket"]
|
||||
|
||||
// Require Content-Length to be set in the request
|
||||
size := r.ContentLength
|
||||
if size < 0 {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
resource, err := getResource(r.URL.Path, r.Host, globalDomainNames)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
@@ -904,43 +928,151 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
|
||||
// Here the parameter is the size of the form data that should
|
||||
// be loaded in memory, the remaining being put in temporary files.
|
||||
reader, err := r.MultipartReader()
|
||||
mp, err := r.MultipartReader()
|
||||
if err != nil {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Read multipart data and save in memory and in the disk if needed
|
||||
form, err := reader.ReadForm(maxFormMemory)
|
||||
if err != nil {
|
||||
const mapEntryOverhead = 200
|
||||
|
||||
var (
|
||||
reader io.Reader
|
||||
fileSize int64 = -1
|
||||
fileName string
|
||||
fanOutEntries = make([]minio.PutObjectFanOutEntry, 0, 100)
|
||||
)
|
||||
|
||||
maxParts := 1000
|
||||
// Canonicalize the form values into http.Header.
|
||||
formValues := make(http.Header)
|
||||
for {
|
||||
part, err := mp.NextRawPart()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
if maxParts <= 0 {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, multipart.ErrMessageTooLarge)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
maxParts--
|
||||
|
||||
name := part.FormName()
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName = part.FileName()
|
||||
|
||||
// Multiple values for the same key (one map entry, longer slice) are cheaper
|
||||
// than the same number of values for different keys (many map entries), but
|
||||
// using a consistent per-value cost for overhead is simpler.
|
||||
maxMemoryBytes := 2 * int64(10<<20)
|
||||
maxMemoryBytes -= int64(len(name))
|
||||
maxMemoryBytes -= mapEntryOverhead
|
||||
if maxMemoryBytes < 0 {
|
||||
// We can't actually take this path, since nextPart would already have
|
||||
// rejected the MIME headers for being too large. Check anyway.
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, multipart.ErrMessageTooLarge)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if fileName == "" {
|
||||
if http.CanonicalHeaderKey(name) == http.CanonicalHeaderKey("x-minio-fanout-list") {
|
||||
dec := json.NewDecoder(part)
|
||||
|
||||
// while the array contains values
|
||||
for dec.More() {
|
||||
var m minio.PutObjectFanOutEntry
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
part.Close()
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, multipart.ErrMessageTooLarge)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
fanOutEntries = append(fanOutEntries, m)
|
||||
}
|
||||
part.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// value, store as string in memory
|
||||
n, err := io.CopyN(&b, part, maxMemoryBytes+1)
|
||||
part.Close()
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
maxMemoryBytes -= n
|
||||
if maxMemoryBytes < 0 {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, multipart.ErrMessageTooLarge)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
if n > maxFormFieldSize {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, multipart.ErrMessageTooLarge)
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
formValues[http.CanonicalHeaderKey(name)] = append(formValues[http.CanonicalHeaderKey(name)], b.String())
|
||||
continue
|
||||
}
|
||||
|
||||
// In accordance with https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html
|
||||
// The file or text content.
|
||||
// The file or text content must be the last field in the form.
|
||||
// You cannot upload more than one file at a time.
|
||||
reader = part
|
||||
// we have found the File part of the request we are done processing multipart-form
|
||||
break
|
||||
}
|
||||
|
||||
if _, ok := formValues["Key"]; !ok {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, errors.New("The name of the uploaded key is missing"))
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove all tmp files created during multipart upload
|
||||
defer form.RemoveAll()
|
||||
|
||||
// Extract all form fields
|
||||
fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form)
|
||||
if err != nil {
|
||||
if fileName == "" {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, errors.New("The file or text content is missing"))
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if file is provided, error out otherwise.
|
||||
if fileBody == nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPOSTFileRequired), r.URL)
|
||||
checksum, err := hash.GetContentChecksum(formValues)
|
||||
if err != nil {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, fmt.Errorf("Invalid checksum: %w", err))
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
if checksum != nil && checksum.Type.Trailing() {
|
||||
// Not officially supported in POST requests.
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, errors.New("Trailing checksums not available for POST operations"))
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Close multipart file
|
||||
defer fileBody.Close()
|
||||
|
||||
formValues.Set("Bucket", bucket)
|
||||
if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") {
|
||||
@@ -968,20 +1100,38 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
// Once signature is validated, check if the user has
|
||||
// explicit permissions for the user.
|
||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: iampolicy.PutObjectAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
BucketName: bucket,
|
||||
ObjectName: object,
|
||||
IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
if len(fanOutEntries) > 0 {
|
||||
// Once signature is validated, check if the user has
|
||||
// explicit permissions for the user.
|
||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: iampolicy.PutObjectFanOutAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
BucketName: bucket,
|
||||
ObjectName: object,
|
||||
IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Once signature is validated, check if the user has
|
||||
// explicit permissions for the user.
|
||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: iampolicy.PutObjectAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
BucketName: bucket,
|
||||
ObjectName: object,
|
||||
IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy"))
|
||||
@@ -990,6 +1140,19 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(reader, fileSize, "", "", fileSize)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if checksum != nil && checksum.Valid() {
|
||||
if err = hashReader.AddChecksumNoTrailer(formValues, false); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Handle policy if it is set.
|
||||
if len(policyBytes) > 0 {
|
||||
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||
@@ -1010,15 +1173,8 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
// should not exceed the maximum single Put size (5 GiB)
|
||||
lengthRange := postPolicyForm.Conditions.ContentLengthRange
|
||||
if lengthRange.Valid {
|
||||
if fileSize < lengthRange.Min {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errDataTooSmall), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if fileSize > lengthRange.Max || isMaxObjectSize(fileSize) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errDataTooLarge), r.URL)
|
||||
return
|
||||
}
|
||||
hashReader.SetExpectedMin(lengthRange.Min)
|
||||
hashReader.SetExpectedMax(lengthRange.Max)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,19 +1186,13 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
rawReader := hashReader
|
||||
pReader := NewPutObjReader(rawReader)
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
|
||||
// Check if bucket encryption is enabled
|
||||
sseConfig, _ := globalBucketSSEConfigSys.Get(bucket)
|
||||
sseConfig.Apply(r.Header, sse.ApplyOptions{
|
||||
sseConfig.Apply(formValues, sse.ApplyOptions{
|
||||
AutoEncrypt: globalAutoEncryption,
|
||||
})
|
||||
|
||||
@@ -1053,12 +1203,24 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
fanOutOpts := fanOutOptions{Checksum: checksum}
|
||||
|
||||
if crypto.Requested(formValues) {
|
||||
if crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && crypto.S3.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, crypto.ErrIncompatibleEncryptionMethod), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && crypto.S3KMS.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, crypto.ErrIncompatibleEncryptionMethod), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||
return
|
||||
@@ -1085,23 +1247,152 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
}
|
||||
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
||||
|
||||
if len(fanOutEntries) == 0 {
|
||||
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
// do not try to verify encrypted content/
|
||||
hashReader, err = hash.NewReader(reader, -1, "", "", -1)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if checksum != nil && checksum.Valid() {
|
||||
if err = hashReader.AddChecksumNoTrailer(formValues, true); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fanOutOpts = fanOutOptions{
|
||||
Key: key,
|
||||
Kind: kind,
|
||||
KeyID: keyID,
|
||||
KmsCtx: kmsCtx,
|
||||
Checksum: checksum,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(fanOutEntries) > 0 {
|
||||
// Fan-out requires no copying, and must be carried from original source
|
||||
// https://en.wikipedia.org/wiki/Copy_protection so the incoming stream
|
||||
// is always going to be in-memory as we cannot re-read from what we
|
||||
// wrote to disk - since that amounts to "copying" from a "copy"
|
||||
// instead of "copying" from source, we need the stream to be seekable
|
||||
// to ensure that we can make fan-out calls concurrently.
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
md5w := md5.New()
|
||||
|
||||
// Maximum allowed fan-out object size.
|
||||
const maxFanOutSize = 16 << 20
|
||||
|
||||
n, err := io.Copy(io.MultiWriter(buf, md5w), ioutil.HardLimitReader(pReader, maxFanOutSize))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
info := ObjectInfo{Size: fileSize}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
// Set the correct hex md5sum for the fan-out stream.
|
||||
fanOutOpts.MD5Hex = hex.EncodeToString(md5w.Sum(nil))
|
||||
|
||||
concurrentSize := 100
|
||||
if runtime.GOMAXPROCS(0) < concurrentSize {
|
||||
concurrentSize = runtime.GOMAXPROCS(0)
|
||||
}
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
fanOutResp := make([]minio.PutObjectFanOutResponse, 0, len(fanOutEntries))
|
||||
eventArgsList := make([]eventArgs, 0, len(fanOutEntries))
|
||||
for {
|
||||
var objInfos []ObjectInfo
|
||||
var errs []error
|
||||
|
||||
var done bool
|
||||
if len(fanOutEntries) < concurrentSize {
|
||||
objInfos, errs = fanOutPutObject(ctx, bucket, objectAPI, fanOutEntries, buf.Bytes()[:n], fanOutOpts)
|
||||
done = true
|
||||
} else {
|
||||
objInfos, errs = fanOutPutObject(ctx, bucket, objectAPI, fanOutEntries[:concurrentSize], buf.Bytes()[:n], fanOutOpts)
|
||||
fanOutEntries = fanOutEntries[concurrentSize:]
|
||||
}
|
||||
|
||||
for i, objInfo := range objInfos {
|
||||
if errs[i] != nil {
|
||||
fanOutResp = append(fanOutResp, minio.PutObjectFanOutResponse{
|
||||
Key: objInfo.Name,
|
||||
Error: errs[i].Error(),
|
||||
})
|
||||
|
||||
eventArgsList = append(eventArgsList, eventArgs{
|
||||
EventName: event.ObjectCreatedPost,
|
||||
BucketName: objInfo.Bucket,
|
||||
Object: ObjectInfo{Name: objInfo.Name},
|
||||
ReqParams: extractReqParams(r),
|
||||
RespElements: extractRespElements(w),
|
||||
UserAgent: fmt.Sprintf("%s MinIO-Fan-Out (failed: %v)", r.UserAgent(), errs[i]),
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
fanOutResp = append(fanOutResp, minio.PutObjectFanOutResponse{
|
||||
Key: objInfo.Name,
|
||||
ETag: getDecryptedETag(formValues, objInfo, false),
|
||||
VersionID: objInfo.VersionID,
|
||||
LastModified: &objInfo.ModTime,
|
||||
})
|
||||
|
||||
eventArgsList = append(eventArgsList, eventArgs{
|
||||
EventName: event.ObjectCreatedPost,
|
||||
BucketName: objInfo.Bucket,
|
||||
Object: objInfo,
|
||||
ReqParams: extractReqParams(r),
|
||||
RespElements: extractRespElements(w),
|
||||
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
}
|
||||
|
||||
if done {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
for i, fanOutResp := range fanOutResp {
|
||||
if err = enc.Encode(&fanOutResp); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Notify object created events.
|
||||
sendEvent(eventArgsList[i])
|
||||
|
||||
if eventArgsList[i].Object.NumVersions > dataScannerExcessiveVersionsThreshold {
|
||||
// Send events for excessive versions.
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectManyVersions,
|
||||
BucketName: eventArgsList[i].Object.Bucket,
|
||||
Object: eventArgsList[i].Object,
|
||||
ReqParams: extractReqParams(r),
|
||||
RespElements: extractRespElements(w),
|
||||
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
||||
@@ -1116,7 +1407,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`}
|
||||
|
||||
// Set the relevant version ID as part of the response header.
|
||||
if objInfo.VersionID != "" {
|
||||
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
|
||||
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
||||
}
|
||||
|
||||
@@ -1134,6 +1425,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
UserAgent: r.UserAgent(),
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
|
||||
if objInfo.NumVersions > dataScannerExcessiveVersionsThreshold {
|
||||
defer sendEvent(eventArgs{
|
||||
EventName: event.ObjectManyVersions,
|
||||
@@ -1156,6 +1448,11 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
// Add checksum header.
|
||||
if checksum != nil && checksum.Valid() {
|
||||
hash.AddChecksumHeader(w, checksum.AsMap())
|
||||
}
|
||||
|
||||
// Decide what http response to send depending on success_action_status parameter
|
||||
switch successStatus {
|
||||
case "201":
|
||||
@@ -1324,10 +1621,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.
|
||||
if err := deleteBucket(ctx, bucket, DeleteBucketOptions{
|
||||
if err := objectAPI.DeleteBucket(ctx, bucket, DeleteBucketOptions{
|
||||
Force: forceDelete,
|
||||
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
|
||||
}); err != nil {
|
||||
@@ -1393,7 +1694,7 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
||||
|
||||
config, err := objectlock.ParseObjectLockConfig(r.Body)
|
||||
if err != nil {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
||||
apiErr := errorCodes.ToAPIErr(ErrInvalidArgument)
|
||||
apiErr.Description = err.Error()
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
@@ -1407,7 +1708,11 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
||||
|
||||
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
||||
if _, _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
if _, ok := err.(BucketObjectLockConfigNotFound); ok {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrObjectLockConfigurationNotAllowed), r.URL)
|
||||
} else {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1609,5 +1914,5 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
||||
}))
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
writeSuccessNoContent(w)
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
maxUploads: "0",
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
expectedRespStatus: http.StatusNotFound,
|
||||
expectedRespStatus: http.StatusBadRequest,
|
||||
shouldPass: false,
|
||||
},
|
||||
// Test case - 2.
|
||||
@@ -657,13 +657,14 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
) {
|
||||
var err error
|
||||
|
||||
contentBytes := []byte("hello")
|
||||
sha256sum := ""
|
||||
var objectNames []string
|
||||
for i := 0; i < 10; i++ {
|
||||
contentBytes := []byte("hello")
|
||||
objectName := "test-object-" + strconv.Itoa(i)
|
||||
if i == 0 {
|
||||
objectName += "/"
|
||||
contentBytes = []byte{}
|
||||
}
|
||||
// uploading the object.
|
||||
_, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
|
||||
@@ -676,6 +677,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
objectNames = append(objectNames, objectName)
|
||||
}
|
||||
|
||||
contentBytes := []byte("hello")
|
||||
for _, name := range []string{"private/object", "public/object"} {
|
||||
// Uploading the object with retention enabled
|
||||
_, err = obj.PutObject(GlobalContext, bucketName, name, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
|
||||
@@ -745,8 +747,13 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
|
||||
deletedObjects := make([]DeletedObject, len(requestList[0].Objects))
|
||||
for i := range requestList[0].Objects {
|
||||
var vid string
|
||||
if isDirObject(requestList[0].Objects[i].ObjectName) {
|
||||
vid = ""
|
||||
}
|
||||
deletedObjects[i] = DeletedObject{
|
||||
ObjectName: requestList[0].Objects[i].ObjectName,
|
||||
VersionID: vid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,9 +763,14 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
successRequest1 := encodeResponse(requestList[1])
|
||||
|
||||
deletedObjects = make([]DeletedObject, len(requestList[1].Objects))
|
||||
for i := range requestList[0].Objects {
|
||||
for i := range requestList[1].Objects {
|
||||
var vid string
|
||||
if isDirObject(requestList[0].Objects[i].ObjectName) {
|
||||
vid = ""
|
||||
}
|
||||
deletedObjects[i] = DeletedObject{
|
||||
ObjectName: requestList[1].Objects[i].ObjectName,
|
||||
VersionID: vid,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 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 "github.com/minio/minio/internal/bucket/lifecycle"
|
||||
|
||||
//go:generate stringer -type lcEventSrc -trimprefix lcEventSrc_ $GOFILE
|
||||
type lcEventSrc uint8
|
||||
|
||||
//revive:disable:var-naming Underscores is used here to indicate where common prefix ends and the enumeration name begins
|
||||
const (
|
||||
lcEventSrc_None lcEventSrc = iota
|
||||
lcEventSrc_Scanner
|
||||
lcEventSrc_Decom
|
||||
lcEventSrc_Rebal
|
||||
lcEventSrc_s3HeadObject
|
||||
lcEventSrc_s3GetObject
|
||||
lcEventSrc_s3ListObjects
|
||||
lcEventSrc_s3PutObject
|
||||
lcEventSrc_s3CopyObject
|
||||
lcEventSrc_s3CompleteMultipartUpload
|
||||
)
|
||||
|
||||
//revive:enable:var-naming
|
||||
type lcAuditEvent struct {
|
||||
lifecycle.Event
|
||||
source lcEventSrc
|
||||
}
|
||||
|
||||
func (lae lcAuditEvent) Tags() map[string]interface{} {
|
||||
event := lae.Event
|
||||
src := lae.source
|
||||
const (
|
||||
ilmSrc = "ilm-src"
|
||||
ilmAction = "ilm-action"
|
||||
ilmDue = "ilm-due"
|
||||
ilmRuleID = "ilm-rule-id"
|
||||
ilmTier = "ilm-tier"
|
||||
ilmNewerNoncurrentVersions = "ilm-newer-noncurrent-versions"
|
||||
ilmNoncurrentDays = "ilm-noncurrent-days"
|
||||
)
|
||||
tags := make(map[string]interface{}, 5)
|
||||
if src > lcEventSrc_None {
|
||||
tags[ilmSrc] = src.String()
|
||||
}
|
||||
tags[ilmAction] = event.Action.String()
|
||||
tags[ilmRuleID] = event.RuleID
|
||||
|
||||
if !event.Due.IsZero() {
|
||||
tags[ilmDue] = event.Due
|
||||
}
|
||||
|
||||
// rule with Transition/NoncurrentVersionTransition in effect
|
||||
if event.StorageClass != "" {
|
||||
tags[ilmTier] = event.StorageClass
|
||||
}
|
||||
|
||||
// rule with NewernoncurrentVersions in effect
|
||||
if event.NewerNoncurrentVersions > 0 {
|
||||
tags[ilmNewerNoncurrentVersions] = event.NewerNoncurrentVersions
|
||||
}
|
||||
if event.NoncurrentDays > 0 {
|
||||
tags[ilmNoncurrentDays] = event.NoncurrentDays
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func newLifecycleAuditEvent(src lcEventSrc, event lifecycle.Event) lcAuditEvent {
|
||||
return lcAuditEvent{
|
||||
Event: event,
|
||||
source: src,
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
@@ -115,6 +116,16 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
var withUpdatedAt bool
|
||||
if updatedAtStr := r.Form.Get("withUpdatedAt"); updatedAtStr != "" {
|
||||
var err error
|
||||
withUpdatedAt, err = strconv.ParseBool(updatedAtStr)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidLifecycleQueryParameter), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketLifecycleAction, bucket, ""); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
@@ -126,7 +137,7 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
config, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||
config, updatedAt, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
@@ -138,6 +149,9 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if withUpdatedAt {
|
||||
w.Header().Set(xhttp.MinIOLifecycleCfgUpdatedAt, updatedAt.Format(iso8601Format))
|
||||
}
|
||||
// Write lifecycle configuration to client.
|
||||
writeSuccessResponseXML(w, configData)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
lifecycleResponse: []byte(``),
|
||||
errorResponse: APIErrorResponse{
|
||||
Resource: SlashSeparator + bucketName + SlashSeparator,
|
||||
Code: "InvalidRequest",
|
||||
Code: "InvalidArgument",
|
||||
Message: "Filter must have exactly one of Prefix, Tag, or And specified",
|
||||
},
|
||||
|
||||
@@ -196,7 +196,7 @@ func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName strin
|
||||
lifecycleResponse: []byte(``),
|
||||
errorResponse: APIErrorResponse{
|
||||
Resource: SlashSeparator + bucketName + SlashSeparator,
|
||||
Code: "InvalidRequest",
|
||||
Code: "InvalidArgument",
|
||||
Message: "Date must be provided in ISO 8601 format",
|
||||
},
|
||||
|
||||
|
||||
+31
-44
@@ -32,7 +32,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/amztime"
|
||||
sse "github.com/minio/minio/internal/bucket/encryption"
|
||||
@@ -63,7 +63,8 @@ type LifecycleSys struct{}
|
||||
|
||||
// Get - gets lifecycle config associated to a given bucket name.
|
||||
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
|
||||
return globalBucketMetadataSys.GetLifecycleConfig(bucketName)
|
||||
lc, _, err = globalBucketMetadataSys.GetLifecycleConfig(bucketName)
|
||||
return lc, err
|
||||
}
|
||||
|
||||
// NewLifecycleSys - creates new lifecycle system.
|
||||
@@ -98,6 +99,7 @@ func (sys *LifecycleSys) trace(oi ObjectInfo) func(event string) {
|
||||
type expiryTask struct {
|
||||
objInfo ObjectInfo
|
||||
event lifecycle.Event
|
||||
src lcEventSrc
|
||||
}
|
||||
|
||||
type expiryState struct {
|
||||
@@ -120,22 +122,22 @@ func (es *expiryState) close() {
|
||||
}
|
||||
|
||||
// enqueueByDays enqueues object versions expired by days for expiry.
|
||||
func (es *expiryState) enqueueByDays(oi ObjectInfo, event lifecycle.Event) {
|
||||
func (es *expiryState) enqueueByDays(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) {
|
||||
select {
|
||||
case <-GlobalContext.Done():
|
||||
es.close()
|
||||
case es.byDaysCh <- expiryTask{objInfo: oi, event: event}:
|
||||
case es.byDaysCh <- expiryTask{objInfo: oi, event: event, src: src}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// enqueueByNewerNoncurrent enqueues object versions expired by
|
||||
// NewerNoncurrentVersions limit for expiry.
|
||||
func (es *expiryState) enqueueByNewerNoncurrent(bucket string, versions []ObjectToDelete) {
|
||||
func (es *expiryState) enqueueByNewerNoncurrent(bucket string, versions []ObjectToDelete, lcEvent lifecycle.Event) {
|
||||
select {
|
||||
case <-GlobalContext.Done():
|
||||
es.close()
|
||||
case es.byNewerNoncurrentCh <- newerNoncurrentTask{bucket: bucket, versions: versions}:
|
||||
case es.byNewerNoncurrentCh <- newerNoncurrentTask{bucket: bucket, versions: versions, event: lcEvent}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -153,7 +155,9 @@ func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) {
|
||||
globalExpiryState = newExpiryState()
|
||||
|
||||
workerSize, _ := strconv.Atoi(env.Get("_MINIO_ILM_EXPIRY_WORKERS", strconv.Itoa((runtime.GOMAXPROCS(0)+1)/2)))
|
||||
|
||||
if workerSize == 0 {
|
||||
workerSize = 4
|
||||
}
|
||||
ewk, err := workers.New(workerSize)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
@@ -170,9 +174,9 @@ func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) {
|
||||
go func(t expiryTask) {
|
||||
defer ewk.Give()
|
||||
if t.objInfo.TransitionedObject.Status != "" {
|
||||
applyExpiryOnTransitionedObject(ctx, objectAPI, t.objInfo, t.event)
|
||||
applyExpiryOnTransitionedObject(ctx, objectAPI, t.objInfo, t.event, t.src)
|
||||
} else {
|
||||
applyExpiryOnNonTransitionedObjects(ctx, objectAPI, t.objInfo, t.event)
|
||||
applyExpiryOnNonTransitionedObjects(ctx, objectAPI, t.objInfo, t.event, t.src)
|
||||
}
|
||||
}(t)
|
||||
}
|
||||
@@ -183,7 +187,7 @@ func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) {
|
||||
nwk.Take()
|
||||
go func(t newerNoncurrentTask) {
|
||||
defer nwk.Give()
|
||||
deleteObjectVersions(ctx, objectAPI, t.bucket, t.versions)
|
||||
deleteObjectVersions(ctx, objectAPI, t.bucket, t.versions, t.event)
|
||||
}(t)
|
||||
}
|
||||
nwk.Wait()
|
||||
@@ -195,10 +199,12 @@ func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) {
|
||||
type newerNoncurrentTask struct {
|
||||
bucket string
|
||||
versions []ObjectToDelete
|
||||
event lifecycle.Event
|
||||
}
|
||||
|
||||
type transitionTask struct {
|
||||
objInfo ObjectInfo
|
||||
src lcEventSrc
|
||||
event lifecycle.Event
|
||||
}
|
||||
|
||||
@@ -217,10 +223,10 @@ type transitionState struct {
|
||||
lastDayStats map[string]*lastDayTierStats
|
||||
}
|
||||
|
||||
func (t *transitionState) queueTransitionTask(oi ObjectInfo, event lifecycle.Event) {
|
||||
func (t *transitionState) queueTransitionTask(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
case t.transitionCh <- transitionTask{objInfo: oi, event: event}:
|
||||
case t.transitionCh <- transitionTask{objInfo: oi, event: event, src: src}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -273,7 +279,7 @@ func (t *transitionState) worker(objectAPI ObjectLayer) {
|
||||
return
|
||||
}
|
||||
atomic.AddInt32(&t.activeTasks, 1)
|
||||
if err := transitionObject(t.ctx, objectAPI, task.objInfo, task.event); err != nil {
|
||||
if err := transitionObject(t.ctx, objectAPI, task.objInfo, newLifecycleAuditEvent(task.src, task.event)); err != nil {
|
||||
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))
|
||||
} else {
|
||||
@@ -355,11 +361,11 @@ func validateTransitionTier(lc *lifecycle.Lifecycle) error {
|
||||
|
||||
// enqueueTransitionImmediate enqueues obj for transition if eligible.
|
||||
// This is to be called after a successful upload of an object (version).
|
||||
func enqueueTransitionImmediate(obj ObjectInfo) {
|
||||
func enqueueTransitionImmediate(obj ObjectInfo, src lcEventSrc) {
|
||||
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
|
||||
switch event := lc.Eval(obj.ToLifecycleOpts()); event.Action {
|
||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||
globalTransitionState.queueTransitionTask(obj, event)
|
||||
globalTransitionState.queueTransitionTask(obj, event, src)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,13 +375,13 @@ func enqueueTransitionImmediate(obj ObjectInfo) {
|
||||
//
|
||||
// 1. when a restored (via PostRestoreObject API) object expires.
|
||||
// 2. when a transitioned object expires (based on an ILM rule).
|
||||
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, lcEvent lifecycle.Event) error {
|
||||
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, lcEvent lifecycle.Event, src lcEventSrc) error {
|
||||
traceFn := globalLifecycleSys.trace(*oi)
|
||||
var opts ObjectOptions
|
||||
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name)
|
||||
opts.VersionID = lcOpts.VersionID
|
||||
opts.Expiration = ExpirationOptions{Expire: true}
|
||||
tags := auditLifecycleTags(lcEvent)
|
||||
tags := newLifecycleAuditEvent(src, lcEvent).Tags()
|
||||
if lcEvent.Action.DeleteRestored() {
|
||||
// delete locally restored copy of object or object version
|
||||
// from the source, while leaving metadata behind. The data on
|
||||
@@ -397,13 +403,11 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
|
||||
TierName: oi.TransitionedObject.Tier,
|
||||
}
|
||||
if err := globalTierJournal.AddEntry(entry); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
// Delete metadata on source, now that data in remote tier has been
|
||||
// marked for deletion.
|
||||
if _, err := objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -446,18 +450,18 @@ func genTransitionObjName(bucket string) (string, error) {
|
||||
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content
|
||||
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
||||
// to the transition tier without decrypting or re-encrypting.
|
||||
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lcEvent lifecycle.Event) error {
|
||||
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) error {
|
||||
opts := ObjectOptions{
|
||||
Transition: TransitionOptions{
|
||||
Status: lifecycle.TransitionPending,
|
||||
Tier: lcEvent.StorageClass,
|
||||
Tier: lae.StorageClass,
|
||||
ETag: oi.ETag,
|
||||
},
|
||||
LifecycleEvent: lcEvent,
|
||||
VersionID: oi.VersionID,
|
||||
Versioned: globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name),
|
||||
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
||||
MTime: oi.ModTime,
|
||||
LifecycleAuditEvent: lae,
|
||||
VersionID: oi.VersionID,
|
||||
Versioned: globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name),
|
||||
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
||||
MTime: oi.ModTime,
|
||||
}
|
||||
return objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
||||
}
|
||||
@@ -685,7 +689,7 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
|
||||
|
||||
if rreq.Type == SelectRestoreRequest {
|
||||
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
|
||||
continue
|
||||
}
|
||||
@@ -865,20 +869,3 @@ func (oi ObjectInfo) ToLifecycleOpts() lifecycle.ObjectOpts {
|
||||
TransitionStatus: oi.TransitionedObject.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func auditLifecycleTags(event lifecycle.Event) map[string]interface{} {
|
||||
const (
|
||||
ilmAction = "ilm-action"
|
||||
ilmDue = "ilm-due"
|
||||
ilmRuleID = "ilm-rule-id"
|
||||
ilmTier = "ilm-tier"
|
||||
)
|
||||
tags := make(map[string]interface{}, 4)
|
||||
tags[ilmAction] = event.Action.String()
|
||||
tags[ilmDue] = event.Due
|
||||
tags[ilmRuleID] = event.RuleID
|
||||
if event.StorageClass != "" {
|
||||
tags[ilmTier] = event.StorageClass
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
@@ -190,7 +190,6 @@ func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.Re
|
||||
}
|
||||
|
||||
// Validate the query params before beginning to serve the request.
|
||||
// fetch-owner is not validated since it is a boolean
|
||||
if s3Error := validateListObjectsArgs(prefix, token, delimiter, encodingType, maxKeys); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
|
||||
+16
-11
@@ -21,11 +21,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
bucketsse "github.com/minio/minio/internal/bucket/encryption"
|
||||
@@ -121,6 +120,7 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
|
||||
meta.NotificationConfigXML = configData
|
||||
case bucketLifecycleConfig:
|
||||
meta.LifecycleConfigXML = configData
|
||||
meta.LifecycleConfigUpdatedAt = updatedAt
|
||||
case bucketSSEConfig:
|
||||
meta.EncryptionConfigXML = configData
|
||||
meta.EncryptionConfigUpdatedAt = updatedAt
|
||||
@@ -246,18 +246,18 @@ func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Co
|
||||
|
||||
// GetLifecycleConfig returns configured lifecycle config
|
||||
// The returned object may not be modified.
|
||||
func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, error) {
|
||||
func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, time.Time, error) {
|
||||
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||
if err != nil {
|
||||
if errors.Is(err, errConfigNotFound) {
|
||||
return nil, BucketLifecycleNotFound{Bucket: bucket}
|
||||
return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
|
||||
}
|
||||
return nil, err
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
if meta.lifecycleConfig == nil {
|
||||
return nil, BucketLifecycleNotFound{Bucket: bucket}
|
||||
return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
|
||||
}
|
||||
return meta.lifecycleConfig, nil
|
||||
return meta.lifecycleConfig, meta.LifecycleConfigUpdatedAt, nil
|
||||
}
|
||||
|
||||
// GetNotificationConfig returns configured notification config
|
||||
@@ -419,8 +419,8 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
|
||||
|
||||
sys.objAPI = objAPI
|
||||
|
||||
// Load bucket metadata sys in background
|
||||
go sys.init(ctx, buckets)
|
||||
// Load bucket metadata sys.
|
||||
sys.init(ctx, buckets)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -488,6 +488,8 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
||||
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
||||
const bucketMetadataRefresh = 15 * time.Minute
|
||||
|
||||
sleeper := newDynamicSleeper(2, 150*time.Millisecond, false)
|
||||
|
||||
t := time.NewTimer(bucketMetadataRefresh)
|
||||
defer t.Stop()
|
||||
for {
|
||||
@@ -511,13 +513,16 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
||||
sys.RemoveStaleBuckets(diskBuckets)
|
||||
|
||||
for _, bucket := range buckets {
|
||||
wait := sleeper.Timer(ctx)
|
||||
|
||||
err := sys.loadBucketMetadata(ctx, bucket)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
wait() // wait to proceed to next entry.
|
||||
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)
|
||||
|
||||
+16
-1
@@ -29,7 +29,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
bucketsse "github.com/minio/minio/internal/bucket/encryption"
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
@@ -88,6 +88,7 @@ type BucketMetadata struct {
|
||||
QuotaConfigUpdatedAt time.Time
|
||||
ReplicationConfigUpdatedAt time.Time
|
||||
VersioningConfigUpdatedAt time.Time
|
||||
LifecycleConfigUpdatedAt time.Time
|
||||
|
||||
// Unexported fields. Must be updated atomically.
|
||||
policyConfig *policy.Policy
|
||||
@@ -119,6 +120,16 @@ func newBucketMetadata(name string) BucketMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
// Versioning returns true if versioning is enabled
|
||||
func (b BucketMetadata) Versioning() bool {
|
||||
return b.LockEnabled || (b.versioningConfig != nil && b.versioningConfig.Enabled()) || (b.objectLockConfig != nil && b.objectLockConfig.Enabled())
|
||||
}
|
||||
|
||||
// ObjectLocking returns true if object locking is enabled
|
||||
func (b BucketMetadata) ObjectLocking() bool {
|
||||
return b.LockEnabled || (b.objectLockConfig != nil && b.objectLockConfig.Enabled())
|
||||
}
|
||||
|
||||
// SetCreatedAt preserves the CreatedAt time for bucket across sites in site replication. It defaults to
|
||||
// creation time of bucket on this cluster in all other cases.
|
||||
func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
|
||||
@@ -417,6 +428,10 @@ func (b *BucketMetadata) defaultTimestamps() {
|
||||
if b.VersioningConfigUpdatedAt.IsZero() {
|
||||
b.VersioningConfigUpdatedAt = b.Created
|
||||
}
|
||||
|
||||
if b.LifecycleConfigUpdatedAt.IsZero() {
|
||||
b.LifecycleConfigUpdatedAt = b.Created
|
||||
}
|
||||
}
|
||||
|
||||
// Save config to supplied ObjectLayer api.
|
||||
|
||||
@@ -150,6 +150,12 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
|
||||
return
|
||||
}
|
||||
case "LifecycleConfigUpdatedAt":
|
||||
z.LifecycleConfigUpdatedAt, err = dc.ReadTime()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "LifecycleConfigUpdatedAt")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -163,9 +169,9 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 21
|
||||
// map header, size 22
|
||||
// write "Name"
|
||||
err = en.Append(0xde, 0x0, 0x15, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||
err = en.Append(0xde, 0x0, 0x16, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -374,15 +380,25 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
|
||||
return
|
||||
}
|
||||
// write "LifecycleConfigUpdatedAt"
|
||||
err = en.Append(0xb8, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteTime(z.LifecycleConfigUpdatedAt)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "LifecycleConfigUpdatedAt")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 21
|
||||
// map header, size 22
|
||||
// string "Name"
|
||||
o = append(o, 0xde, 0x0, 0x15, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||
o = append(o, 0xde, 0x0, 0x16, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||
o = msgp.AppendString(o, z.Name)
|
||||
// string "Created"
|
||||
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64)
|
||||
@@ -444,6 +460,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "VersioningConfigUpdatedAt"
|
||||
o = append(o, 0xb9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||
o = msgp.AppendTime(o, z.VersioningConfigUpdatedAt)
|
||||
// string "LifecycleConfigUpdatedAt"
|
||||
o = append(o, 0xb8, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
|
||||
o = msgp.AppendTime(o, z.LifecycleConfigUpdatedAt)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -591,6 +610,12 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
|
||||
return
|
||||
}
|
||||
case "LifecycleConfigUpdatedAt":
|
||||
z.LifecycleConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "LifecycleConfigUpdatedAt")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -605,6 +630,6 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *BucketMetadata) Msgsize() (s int) {
|
||||
s = 3 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON) + 22 + msgp.TimeSize + 26 + msgp.TimeSize + 26 + msgp.TimeSize + 23 + msgp.TimeSize + 21 + msgp.TimeSize + 27 + msgp.TimeSize + 26 + msgp.TimeSize
|
||||
s = 3 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON) + 22 + msgp.TimeSize + 26 + msgp.TimeSize + 26 + msgp.TimeSize + 23 + msgp.TimeSize + 21 + msgp.TimeSize + 27 + msgp.TimeSize + 26 + msgp.TimeSize + 25 + msgp.TimeSize
|
||||
return
|
||||
}
|
||||
|
||||
+14
-20
@@ -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
|
||||
// governance bypass headers are set and user has governance bypass permissions.
|
||||
// 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 {
|
||||
opts, err := getOpts(ctx, r, bucket, object.ObjectName)
|
||||
if err != nil {
|
||||
return toAPIErrorCode(ctx, err)
|
||||
}
|
||||
|
||||
opts.VersionID = object.VersionID
|
||||
func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket string, object ObjectToDelete, oi ObjectInfo, gerr error) error {
|
||||
if gerr != nil { // error from GetObjectInfo
|
||||
if _, ok := gerr.(MethodNotAllowed); ok {
|
||||
// This happens usually for a delete marker
|
||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||
// Delete marker should be present and valid.
|
||||
return ErrNone
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if isErrObjectNotFound(gerr) || isErrVersionNotFound(gerr) {
|
||||
return ErrNone
|
||||
return nil
|
||||
}
|
||||
return toAPIErrorCode(ctx, gerr)
|
||||
return gerr
|
||||
}
|
||||
|
||||
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
||||
if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn {
|
||||
return ErrObjectLocked
|
||||
return ObjectLocked{}
|
||||
}
|
||||
|
||||
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
||||
@@ -121,13 +115,13 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
||||
t, err := objectlock.UTCNowNTP()
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return ErrObjectLocked
|
||||
return ObjectLocked{}
|
||||
}
|
||||
|
||||
if !ret.RetainUntilDate.Before(t) {
|
||||
return ErrObjectLocked
|
||||
return ObjectLocked{}
|
||||
}
|
||||
return ErrNone
|
||||
return nil
|
||||
case objectlock.RetGovernance:
|
||||
// In governance mode, users can't overwrite or delete an object
|
||||
// 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()
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return ErrObjectLocked
|
||||
return ObjectLocked{}
|
||||
}
|
||||
|
||||
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
|
||||
// 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 {
|
||||
return ErrAccessDenied
|
||||
return errAuthentication
|
||||
}
|
||||
}
|
||||
}
|
||||
return ErrNone
|
||||
return nil
|
||||
}
|
||||
|
||||
// enforceRetentionBypassForPut enforces whether an existing object under governance can be overwritten
|
||||
@@ -312,7 +306,7 @@ func checkPutObjectLockAllowed(ctx context.Context, rq *http.Request, bucket, ob
|
||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||
}
|
||||
rMode, rDate, err := objectlock.ParseObjectLockRetentionHeaders(rq.Header)
|
||||
if err != nil {
|
||||
if err != nil && !(replica && rMode == "" && rDate.IsZero()) {
|
||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||
}
|
||||
if retentionPermErr != ErrNone {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
|
||||
@@ -289,7 +289,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
policyLen: len(fmt.Sprintf(bucketPolicyTemplate, bucketName, bucketName)),
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
expectedRespStatus: http.StatusNotFound,
|
||||
expectedRespStatus: http.StatusBadRequest,
|
||||
},
|
||||
// Test case - 10.
|
||||
// Existent bucket with policy with Version field empty.
|
||||
@@ -463,7 +463,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
expectedBucketPolicy: "",
|
||||
expectedRespStatus: http.StatusNotFound,
|
||||
expectedRespStatus: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
// Iterating over the cases, fetching the policy and validating the response.
|
||||
@@ -686,7 +686,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
|
||||
bucketName: ".invalid-bucket-name",
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
expectedRespStatus: http.StatusNotFound,
|
||||
expectedRespStatus: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
// Iterating over the cases and deleting the bucket policy and then asserting response.
|
||||
|
||||
+20
-8
@@ -24,7 +24,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
@@ -47,9 +47,13 @@ func NewBucketQuotaSys() *BucketQuotaSys {
|
||||
// Init initialize bucket quota.
|
||||
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
||||
sys.bucketStorageCache.Once.Do(func() {
|
||||
sys.bucketStorageCache.TTL = 1 * time.Second
|
||||
// Set this to 10 secs since its enough, as scanner
|
||||
// does not update the bucket usage values frequently.
|
||||
sys.bucketStorageCache.TTL = 10 * time.Second
|
||||
// Rely on older value if usage loading fails from disk.
|
||||
sys.bucketStorageCache.Relax = true
|
||||
sys.bucketStorageCache.Update = func() (interface{}, error) {
|
||||
ctx, done := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
ctx, done := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer done()
|
||||
|
||||
return loadDataUsageFromBackend(ctx, objAPI)
|
||||
@@ -60,16 +64,20 @@ func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
||||
// GetBucketUsageInfo return bucket usage info for a given bucket
|
||||
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
|
||||
v, err := sys.bucketStorageCache.Get()
|
||||
if err != nil {
|
||||
return BucketUsageInfo{}, err
|
||||
if err != nil && 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)
|
||||
}
|
||||
if v == nil {
|
||||
logger.LogOnceIf(GlobalContext, errors.New("unable to retrieve usage information for bucket: %s, no reliable usage value available - quota will not be enforced"), "bucket-usage-empty-"+bucket)
|
||||
}
|
||||
|
||||
var bui BucketUsageInfo
|
||||
|
||||
dui, ok := v.(DataUsageInfo)
|
||||
if !ok {
|
||||
return BucketUsageInfo{}, fmt.Errorf("internal error: Unexpected DUI data type: %T", v)
|
||||
if ok {
|
||||
bui = dui.BucketsUsage[bucket]
|
||||
}
|
||||
|
||||
bui := dui.BucketsUsage[bucket]
|
||||
return bui, nil
|
||||
}
|
||||
|
||||
@@ -100,6 +108,10 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
|
||||
}
|
||||
|
||||
if q != nil && q.Type == madmin.HardQuota && q.Quota > 0 {
|
||||
if uint64(size) >= q.Quota { // check if file size already exceeds the quota
|
||||
return BucketQuotaExceeded{Bucket: bucket}
|
||||
}
|
||||
|
||||
bui, err := sys.GetBucketUsageInfo(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,13 +18,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -466,3 +471,148 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
|
||||
// Write success response.
|
||||
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(ctx, 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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
//
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
)
|
||||
@@ -512,7 +512,10 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
|
||||
ObjectName: oi.Name,
|
||||
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 {
|
||||
dsc = mustReplicate(GlobalContext, oi.Bucket, oi.Name, getMustReplicateOptions(ObjectInfo{
|
||||
UserDefined: oi.UserDefined,
|
||||
@@ -533,12 +536,18 @@ func getHealReplicateObjectInfo(objInfo ObjectInfo, rcfg replicationConfig) Repl
|
||||
}
|
||||
}
|
||||
|
||||
func (ri *ReplicateObjectInfo) getReplicationState() ReplicationState {
|
||||
rs := ri.ObjectInfo.getReplicationState()
|
||||
rs.ReplicateDecisionStr = ri.Dsc.String()
|
||||
return rs
|
||||
}
|
||||
|
||||
// vID here represents the versionID client specified in request - need to distinguish between delete marker and delete marker deletion
|
||||
func (o *ObjectInfo) getReplicationState(dsc string, vID string, heal bool) ReplicationState {
|
||||
func (o *ObjectInfo) getReplicationState() ReplicationState {
|
||||
rs := ReplicationState{
|
||||
ReplicationStatusInternal: o.ReplicationStatusInternal,
|
||||
VersionPurgeStatusInternal: o.VersionPurgeStatusInternal,
|
||||
ReplicateDecisionStr: dsc,
|
||||
ReplicateDecisionStr: o.replicationDecision,
|
||||
Targets: make(map[string]replication.StatusType),
|
||||
PurgeTargets: make(map[string]VersionPurgeStatusType),
|
||||
ResetStatusesMap: make(map[string]string),
|
||||
@@ -711,6 +720,7 @@ type TargetReplicationResyncStatus struct {
|
||||
// Last bucket/object replicated.
|
||||
Bucket string `json:"-" msg:"bkt"`
|
||||
Object string `json:"-" msg:"obj"`
|
||||
Error error `json:"-" msg:"-"`
|
||||
}
|
||||
|
||||
// BucketReplicationResyncStatus captures current replication resync status
|
||||
@@ -776,9 +786,10 @@ const (
|
||||
|
||||
// MRFReplicateEntry mrf entry to save to disk
|
||||
type MRFReplicateEntry struct {
|
||||
Bucket string `json:"bucket" msg:"b"`
|
||||
Object string `json:"object" msg:"o"`
|
||||
versionID string `json:"-"`
|
||||
Bucket string `json:"bucket" msg:"b"`
|
||||
Object string `json:"object" msg:"o"`
|
||||
versionID string `json:"-"`
|
||||
RetryCount int `json:"retryCount" msg:"rc"`
|
||||
}
|
||||
|
||||
// MRFReplicateEntries has the map of MRF entries to save to disk
|
||||
@@ -790,9 +801,10 @@ type MRFReplicateEntries struct {
|
||||
// ToMRFEntry returns the relevant info needed by MRF
|
||||
func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry {
|
||||
return MRFReplicateEntry{
|
||||
Bucket: ri.Bucket,
|
||||
Object: ri.Name,
|
||||
versionID: ri.VersionID,
|
||||
Bucket: ri.Bucket,
|
||||
Object: ri.Name,
|
||||
versionID: ri.VersionID,
|
||||
RetryCount: int(ri.RetryCount),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -327,6 +327,12 @@ func (z *MRFReplicateEntries) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||
return
|
||||
}
|
||||
case "rc":
|
||||
za0002.RetryCount, err = dc.ReadInt()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Entries", za0001, "RetryCount")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -373,9 +379,9 @@ func (z *MRFReplicateEntries) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Entries")
|
||||
return
|
||||
}
|
||||
// map header, size 2
|
||||
// map header, size 3
|
||||
// write "b"
|
||||
err = en.Append(0x82, 0xa1, 0x62)
|
||||
err = en.Append(0x83, 0xa1, 0x62)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -394,6 +400,16 @@ func (z *MRFReplicateEntries) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Entries", za0001, "Object")
|
||||
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"
|
||||
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)))
|
||||
for za0001, za0002 := range z.Entries {
|
||||
o = msgp.AppendString(o, za0001)
|
||||
// map header, size 2
|
||||
// map header, size 3
|
||||
// string "b"
|
||||
o = append(o, 0x82, 0xa1, 0x62)
|
||||
o = append(o, 0x83, 0xa1, 0x62)
|
||||
o = msgp.AppendString(o, za0002.Bucket)
|
||||
// string "o"
|
||||
o = append(o, 0xa1, 0x6f)
|
||||
o = msgp.AppendString(o, za0002.Object)
|
||||
// string "rc"
|
||||
o = append(o, 0xa2, 0x72, 0x63)
|
||||
o = msgp.AppendInt(o, za0002.RetryCount)
|
||||
}
|
||||
// string "v"
|
||||
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")
|
||||
return
|
||||
}
|
||||
case "rc":
|
||||
za0002.RetryCount, bts, err = msgp.ReadIntBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Entries", za0001, "RetryCount")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -532,7 +557,7 @@ func (z *MRFReplicateEntries) Msgsize() (s int) {
|
||||
if z.Entries != nil {
|
||||
for za0001, za0002 := range z.Entries {
|
||||
_ = 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
|
||||
@@ -569,6 +594,12 @@ func (z *MRFReplicateEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Object")
|
||||
return
|
||||
}
|
||||
case "rc":
|
||||
z.RetryCount, err = dc.ReadInt()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "RetryCount")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -582,9 +613,9 @@ func (z *MRFReplicateEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z MRFReplicateEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 2
|
||||
// map header, size 3
|
||||
// write "b"
|
||||
err = en.Append(0x82, 0xa1, 0x62)
|
||||
err = en.Append(0x83, 0xa1, 0x62)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -603,19 +634,32 @@ func (z MRFReplicateEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Object")
|
||||
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
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z MRFReplicateEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 2
|
||||
// map header, size 3
|
||||
// string "b"
|
||||
o = append(o, 0x82, 0xa1, 0x62)
|
||||
o = append(o, 0x83, 0xa1, 0x62)
|
||||
o = msgp.AppendString(o, z.Bucket)
|
||||
// string "o"
|
||||
o = append(o, 0xa1, 0x6f)
|
||||
o = msgp.AppendString(o, z.Object)
|
||||
// string "rc"
|
||||
o = append(o, 0xa2, 0x72, 0x63)
|
||||
o = msgp.AppendInt(o, z.RetryCount)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -649,6 +693,12 @@ func (z *MRFReplicateEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Object")
|
||||
return
|
||||
}
|
||||
case "rc":
|
||||
z.RetryCount, bts, err = msgp.ReadIntBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "RetryCount")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+402
-162
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
@@ -58,7 +58,7 @@ type epHealth struct {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
sys.hMutex.RLock()
|
||||
defer sys.hMutex.RUnlock()
|
||||
@@ -126,7 +126,7 @@ 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
|
||||
func (sys *BucketTargetSys) reloadHealthCheckers(ctx context.Context) {
|
||||
m := make(map[string]epHealth)
|
||||
@@ -215,7 +215,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
||||
if err != nil {
|
||||
switch minio.ToErrorResponse(err).Code {
|
||||
case "NoSuchBucket":
|
||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket, Err: err}
|
||||
case "AccessDenied":
|
||||
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err}
|
||||
}
|
||||
@@ -362,7 +362,7 @@ func NewBucketTargetSys(ctx context.Context) *BucketTargetSys {
|
||||
hc: make(map[string]epHealth),
|
||||
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() {
|
||||
rTimer := time.NewTimer(defaultHealthCheckReloadDuration)
|
||||
defer rTimer.Stop()
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/versioning"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
|
||||
+34
-33
@@ -52,7 +52,7 @@ import (
|
||||
"github.com/minio/console/restapi"
|
||||
"github.com/minio/console/restapi/operations"
|
||||
"github.com/minio/kes-go"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
@@ -66,7 +66,6 @@ import (
|
||||
"github.com/minio/pkg/ellipses"
|
||||
"github.com/minio/pkg/env"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
"github.com/rs/dnscache"
|
||||
)
|
||||
|
||||
// serverDebugLog will enable debug printing
|
||||
@@ -96,11 +95,6 @@ func init() {
|
||||
|
||||
initGlobalContext()
|
||||
|
||||
options := dnscache.ResolverRefreshOptions{
|
||||
ClearUnused: true,
|
||||
PersistOnFailure: false,
|
||||
}
|
||||
|
||||
t, _ := minioVersionToReleaseTime(Version)
|
||||
if !t.IsZero() {
|
||||
globalVersionUnix = uint64(t.Unix())
|
||||
@@ -108,34 +102,11 @@ func init() {
|
||||
|
||||
globalIsCICD = env.Get("MINIO_CI_CD", "") != "" || env.Get("CI", "") != ""
|
||||
|
||||
containers := IsKubernetes() || IsDocker() || IsBOSH() || IsDCOS() || IsPCFTile()
|
||||
|
||||
// Call to refresh will refresh names in cache. If you pass true, it will also
|
||||
// remove cached names not looked up since the last call to Refresh. It is a good idea
|
||||
// to call this method on a regular interval.
|
||||
go func() {
|
||||
var t *time.Ticker
|
||||
if containers {
|
||||
// k8s DNS TTL is 30s (Attempt a refresh only after)
|
||||
t = time.NewTicker(30 * time.Second)
|
||||
} else {
|
||||
t = time.NewTicker(10 * time.Minute)
|
||||
}
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
globalDNSCache.RefreshWithOptions(options)
|
||||
case <-GlobalContext.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
console.SetColor("Debug", fcolor.New())
|
||||
|
||||
gob.Register(StorageErr(""))
|
||||
gob.Register(madmin.TimeInfo{})
|
||||
gob.Register(madmin.XFSErrorConfigs{})
|
||||
gob.Register(map[string]interface{}{})
|
||||
|
||||
defaultAWSCredProvider = []credentials.Provider{
|
||||
@@ -176,7 +147,7 @@ func minioConfigToConsoleFeatures() {
|
||||
}
|
||||
}
|
||||
// pass the console subpath configuration
|
||||
if value := env.Get(config.EnvMinIOBrowserRedirectURL, ""); value != "" {
|
||||
if value := env.Get(config.EnvBrowserRedirectURL, ""); value != "" {
|
||||
subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator))
|
||||
if subPath != SlashSeparator {
|
||||
os.Setenv("CONSOLE_SUBPATH", subPath)
|
||||
@@ -197,6 +168,11 @@ func minioConfigToConsoleFeatures() {
|
||||
if globalIAMSys.LDAPConfig.Enabled() {
|
||||
os.Setenv("CONSOLE_LDAP_ENABLED", config.EnableOn)
|
||||
}
|
||||
// Handle animation in welcome page
|
||||
if value := env.Get(config.EnvBrowserLoginAnimation, "on"); value != "" {
|
||||
os.Setenv("CONSOLE_ANIMATED_LOGIN", value)
|
||||
}
|
||||
|
||||
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
||||
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
|
||||
|
||||
@@ -451,6 +427,28 @@ func handleCommonCmdArgs(ctx *cli.Context) {
|
||||
globalCertsCADir = &ConfigDir{path: filepath.Join(globalCertsDir.Get(), certsCADir)}
|
||||
|
||||
logger.FatalIf(mkdirAllIgnorePerm(globalCertsCADir.Get()), "Unable to create certs CA directory at %s", globalCertsCADir.Get())
|
||||
|
||||
// Check if we have configured a custom DNS cache TTL.
|
||||
dnsTTL := ctx.Duration("dns-cache-ttl")
|
||||
if dnsTTL <= 0 {
|
||||
dnsTTL = 10 * time.Minute
|
||||
}
|
||||
|
||||
// Call to refresh will refresh names in cache.
|
||||
go func() {
|
||||
// Baremetal setups set DNS refresh window up to dnsTTL duration.
|
||||
t := time.NewTicker(dnsTTL)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
globalDNSCache.Refresh()
|
||||
|
||||
case <-GlobalContext.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type envKV struct {
|
||||
@@ -621,7 +619,7 @@ func handleCommonEnvVars() {
|
||||
logger.Fatal(config.ErrInvalidBrowserValue(err), "Invalid MINIO_BROWSER value in environment variable")
|
||||
}
|
||||
if globalBrowserEnabled {
|
||||
if redirectURL := env.Get(config.EnvMinIOBrowserRedirectURL, ""); redirectURL != "" {
|
||||
if redirectURL := env.Get(config.EnvBrowserRedirectURL, ""); redirectURL != "" {
|
||||
u, err := xnet.ParseHTTPURL(redirectURL)
|
||||
if err != nil {
|
||||
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value in environment variable")
|
||||
@@ -766,9 +764,12 @@ func handleCommonEnvVars() {
|
||||
logger.Info(color.RedBold(msg))
|
||||
}
|
||||
globalActiveCred = cred
|
||||
globalCredViaEnv = true
|
||||
} else {
|
||||
globalActiveCred = auth.DefaultCredentials
|
||||
}
|
||||
|
||||
globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog
|
||||
}
|
||||
|
||||
// Initialize KMS global variable after valiadating and loading the configuration.
|
||||
|
||||
+16
-13
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/api"
|
||||
"github.com/minio/minio/internal/config/cache"
|
||||
@@ -283,7 +283,7 @@ var (
|
||||
globalServerConfigMu sync.RWMutex
|
||||
)
|
||||
|
||||
func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) error {
|
||||
func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, objAPI ObjectLayer) error {
|
||||
switch subSys {
|
||||
case config.SiteSubSys:
|
||||
if _, err := config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default]); err != nil {
|
||||
@@ -386,14 +386,14 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
||||
}
|
||||
default:
|
||||
if config.LoggerSubSystems.Contains(subSys) {
|
||||
if err := logger.ValidateSubSysConfig(s, subSys); err != nil {
|
||||
if err := logger.ValidateSubSysConfig(ctx, s, subSys); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.NotifySubSystems.Contains(subSys) {
|
||||
if err := notify.TestSubSysNotificationTargets(GlobalContext, s, subSys, NewHTTPTransport()); err != nil {
|
||||
if err := notify.TestSubSysNotificationTargets(ctx, s, subSys, NewHTTPTransport()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -407,7 +407,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfig(s config.Config, subSys string) error {
|
||||
func validateConfig(ctx context.Context, s config.Config, subSys string) error {
|
||||
objAPI := newObjectLayerFn()
|
||||
|
||||
// We must have a global lock for this so nobody else modifies env while we do.
|
||||
@@ -419,12 +419,12 @@ func validateConfig(s config.Config, subSys string) error {
|
||||
// Enable env values to validate KMS.
|
||||
defer env.SetEnvOn()
|
||||
if subSys != "" {
|
||||
return validateSubSysConfig(s, subSys, objAPI)
|
||||
return validateSubSysConfig(ctx, s, subSys, objAPI)
|
||||
}
|
||||
|
||||
// No sub-system passed. Validate all of them.
|
||||
for _, ss := range config.SubSystems.ToSlice() {
|
||||
if err := validateSubSysConfig(s, ss, objAPI); err != nil {
|
||||
if err := validateSubSysConfig(ctx, s, ss, objAPI); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -575,7 +575,7 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
scannerCycle.Store(scannerCfg.Cycle)
|
||||
logger.LogIf(ctx, scannerSleeper.Update(scannerCfg.Delay, scannerCfg.MaxWait))
|
||||
case config.LoggerWebhookSubSys:
|
||||
loggerCfg, err := logger.LookupConfigForSubSys(s, config.LoggerWebhookSubSys)
|
||||
loggerCfg, err := logger.LookupConfigForSubSys(ctx, s, config.LoggerWebhookSubSys)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to load logger webhook config: %w", err))
|
||||
}
|
||||
@@ -588,11 +588,11 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
}
|
||||
loggerCfg.HTTP[n] = l
|
||||
}
|
||||
if errs := logger.UpdateSystemTargets(loggerCfg); len(errs) > 0 {
|
||||
if errs := logger.UpdateSystemTargets(ctx, loggerCfg); len(errs) > 0 {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to update logger webhook config: %v", errs))
|
||||
}
|
||||
case config.AuditWebhookSubSys:
|
||||
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditWebhookSubSys)
|
||||
loggerCfg, err := logger.LookupConfigForSubSys(ctx, s, config.AuditWebhookSubSys)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to load audit webhook config: %w", err))
|
||||
}
|
||||
@@ -606,21 +606,24 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
loggerCfg.AuditWebhook[n] = l
|
||||
}
|
||||
|
||||
if errs := logger.UpdateAuditWebhookTargets(loggerCfg); len(errs) > 0 {
|
||||
if errs := logger.UpdateAuditWebhookTargets(ctx, loggerCfg); len(errs) > 0 {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to update audit webhook targets: %v", errs))
|
||||
}
|
||||
case config.AuditKafkaSubSys:
|
||||
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditKafkaSubSys)
|
||||
loggerCfg, err := logger.LookupConfigForSubSys(ctx, s, config.AuditKafkaSubSys)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to load audit kafka config: %w", err))
|
||||
}
|
||||
for n, l := range loggerCfg.AuditKafka {
|
||||
if l.Enabled {
|
||||
if l.TLS.Enable {
|
||||
l.TLS.RootCAs = globalRootCAs
|
||||
}
|
||||
l.LogOnce = logger.LogOnceIf
|
||||
loggerCfg.AuditKafka[n] = l
|
||||
}
|
||||
}
|
||||
if errs := logger.UpdateAuditKafkaTargets(loggerCfg); len(errs) > 0 {
|
||||
if errs := logger.UpdateAuditKafkaTargets(ctx, loggerCfg); len(errs) > 0 {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to update audit kafka targets: %v", errs))
|
||||
}
|
||||
case config.StorageClassSubSys:
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
)
|
||||
|
||||
|
||||
@@ -2531,6 +2531,12 @@ func readConfigWithoutMigrate(ctx context.Context, objAPI ObjectLayer) (config.C
|
||||
}
|
||||
}
|
||||
|
||||
if !globalCredViaEnv && cfg.Credential.IsValid() {
|
||||
// Preserve older credential if we do not have
|
||||
// root credentials set via environment variable.
|
||||
globalActiveCred = cfg.Credential
|
||||
}
|
||||
|
||||
// Init compression config. For future migration, Compression config needs to be copied over from previous version.
|
||||
switch cfg.Version {
|
||||
case "29":
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import (
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/logger/target/console"
|
||||
"github.com/minio/minio/internal/logger/target/types"
|
||||
@@ -58,7 +58,7 @@ func NewConsoleLogger(ctx context.Context) *HTTPConsoleLoggerSys {
|
||||
}
|
||||
|
||||
// IsOnline always true in case of console logger
|
||||
func (sys *HTTPConsoleLoggerSys) IsOnline() bool {
|
||||
func (sys *HTTPConsoleLoggerSys) IsOnline(_ context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool {
|
||||
func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan log.Info, doneCh <-chan struct{}, node string, last int, logKind madmin.LogMask, filter func(entry log.Info) bool) error {
|
||||
// Enable console logging for remote client.
|
||||
if !sys.HasLogListeners() {
|
||||
logger.AddSystemTarget(sys)
|
||||
logger.AddSystemTarget(GlobalContext, sys)
|
||||
}
|
||||
|
||||
cnt := 0
|
||||
@@ -128,7 +128,7 @@ func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan log.Info, doneCh <-chan st
|
||||
}
|
||||
|
||||
// Init if HTTPConsoleLoggerSys is valid, always returns nil right now
|
||||
func (sys *HTTPConsoleLoggerSys) Init() error {
|
||||
func (sys *HTTPConsoleLoggerSys) Init(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func (sys *HTTPConsoleLoggerSys) Type() types.TargetType {
|
||||
|
||||
// Send log message 'e' to console and publish to console
|
||||
// log pubsub system
|
||||
func (sys *HTTPConsoleLoggerSys) Send(entry interface{}) error {
|
||||
func (sys *HTTPConsoleLoggerSys) Send(ctx context.Context, entry interface{}) error {
|
||||
var lg log.Info
|
||||
switch e := entry.(type) {
|
||||
case log.Entry:
|
||||
|
||||
@@ -30,7 +30,7 @@ const crossDomainXMLEntity = "/crossdomain.xml"
|
||||
// When clients request content hosted on a particular source domain and that content make requests
|
||||
// directed towards a domain other than its own, the remote domain needs to host a cross-domain
|
||||
// policy file that grants access to the source domain, allowing the client to continue the transaction.
|
||||
func setCrossDomainPolicy(h http.Handler) http.Handler {
|
||||
func setCrossDomainPolicyMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Look for 'crossdomain.xml' in the incoming request.
|
||||
if r.URL.Path == crossDomainXMLEntity {
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
func TestCrossXMLHandler(t *testing.T) {
|
||||
// Server initialization.
|
||||
router := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
||||
handler := setCrossDomainPolicy(router)
|
||||
handler := setCrossDomainPolicyMiddleware(router)
|
||||
srv := httptest.NewServer(handler)
|
||||
|
||||
resp, err := http.Get(srv.URL + crossDomainXMLEntity)
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -7,7 +24,7 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
)
|
||||
|
||||
|
||||
+52
-52
@@ -33,8 +33,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
"github.com/minio/minio/internal/bucket/object/lock"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
@@ -206,7 +205,7 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
go storeDataUsageInBackend(ctx, objAPI, results)
|
||||
err := objAPI.NSScanner(ctx, results, uint32(cycleInfo.current), scanMode)
|
||||
logger.LogIf(ctx, err)
|
||||
res := map[string]string{"cycle": fmt.Sprint(cycleInfo.current)}
|
||||
res := map[string]string{"cycle": strconv.FormatUint(cycleInfo.current, 10)}
|
||||
if err != nil {
|
||||
res["error"] = err.Error()
|
||||
}
|
||||
@@ -302,7 +301,7 @@ type folderScanner struct {
|
||||
// The returned cache will always be valid, but may not be updated from the existing.
|
||||
// Before each operation sleepDuration is called which can be used to temporarily halt the scanner.
|
||||
// If the supplied context is canceled the function will return at the first chance.
|
||||
func scanDataFolder(ctx context.Context, poolIdx, setIdx int, basePath string, cache dataUsageCache, getSize getSizeFn, scanMode madmin.HealScanMode) (dataUsageCache, error) {
|
||||
func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, cache dataUsageCache, getSize getSizeFn, scanMode madmin.HealScanMode) (dataUsageCache, error) {
|
||||
switch cache.Info.Name {
|
||||
case "", dataUsageRoot:
|
||||
return cache, errors.New("internal error: root scan attempted")
|
||||
@@ -321,20 +320,8 @@ func scanDataFolder(ctx context.Context, poolIdx, setIdx int, basePath string, c
|
||||
scanMode: scanMode,
|
||||
updates: cache.Info.updates,
|
||||
updateCurrentPath: updatePath,
|
||||
}
|
||||
|
||||
// Add disks for set healing.
|
||||
if poolIdx >= 0 && setIdx >= 0 {
|
||||
objAPI, ok := newObjectLayerFn().(*erasureServerPools)
|
||||
if ok {
|
||||
if poolIdx < len(objAPI.serverPools) && setIdx < len(objAPI.serverPools[poolIdx].sets) {
|
||||
// Pass the disks belonging to the set.
|
||||
s.disks = objAPI.serverPools[poolIdx].sets[setIdx].getDisks()
|
||||
s.disksQuorum = len(s.disks) / 2
|
||||
} else {
|
||||
logger.LogIf(ctx, fmt.Errorf("Matching pool %s, set %s not found", humanize.Ordinal(poolIdx+1), humanize.Ordinal(setIdx+1)))
|
||||
}
|
||||
}
|
||||
disks: disks,
|
||||
disksQuorum: len(disks) / 2,
|
||||
}
|
||||
|
||||
// Enable healing in XL mode.
|
||||
@@ -373,7 +360,7 @@ func (f *folderScanner) sendUpdate() {
|
||||
}
|
||||
if flat := f.updateCache.sizeRecursive(f.newCache.Info.Name); flat != nil {
|
||||
select {
|
||||
case f.updates <- *flat:
|
||||
case f.updates <- flat.clone():
|
||||
default:
|
||||
}
|
||||
f.lastUpdate = time.Now()
|
||||
@@ -403,7 +390,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
abandonedChildren = f.oldCache.findChildrenCopy(thisHash)
|
||||
}
|
||||
|
||||
// If there are lifecycle rules for the prefix, remove the filter.
|
||||
// If there are lifecycle rules for the prefix.
|
||||
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
|
||||
var activeLifeCycle *lifecycle.Lifecycle
|
||||
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix) {
|
||||
@@ -412,7 +399,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
}
|
||||
activeLifeCycle = f.oldCache.Info.lifeCycle
|
||||
}
|
||||
// If there are replication rules for the prefix, remove the filter.
|
||||
// If there are replication rules for the prefix.
|
||||
var replicationCfg replicationConfig
|
||||
if !f.oldCache.Info.replication.Empty() && f.oldCache.Info.replication.Config.HasActiveRules(prefix, true) {
|
||||
replicationCfg = f.oldCache.Info.replication
|
||||
@@ -649,8 +636,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
break
|
||||
}
|
||||
|
||||
objAPI, ok := newObjectLayerFn().(*erasureServerPools)
|
||||
if !ok || len(f.disks) == 0 || f.disksQuorum == 0 {
|
||||
if len(f.disks) == 0 || f.disksQuorum == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -688,7 +674,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
// Bucket might be missing as well with abandoned children.
|
||||
// make sure it is created first otherwise healing won't proceed
|
||||
// for objects.
|
||||
_, _ = objAPI.HealBucket(ctx, bucket, madmin.HealOpts{})
|
||||
bgSeq.queueHealTask(healSource{
|
||||
bucket: bucket,
|
||||
}, madmin.HealItemBucket)
|
||||
}
|
||||
|
||||
resolver.bucket = bucket
|
||||
@@ -813,11 +801,11 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
f.newCache.deleteRecursive(thisHash)
|
||||
f.newCache.replaceHashed(thisHash, folder.parent, *flat)
|
||||
total := map[string]string{
|
||||
"objects": fmt.Sprint(flat.Objects),
|
||||
"size": fmt.Sprint(flat.Size),
|
||||
"objects": strconv.FormatUint(flat.Objects, 10),
|
||||
"size": strconv.FormatInt(flat.Size, 10),
|
||||
}
|
||||
if flat.Versions > 0 {
|
||||
total["versions"] = fmt.Sprint(flat.Versions)
|
||||
total["versions"] = strconv.FormatUint(flat.Versions, 10)
|
||||
}
|
||||
stop(total)
|
||||
}
|
||||
@@ -857,6 +845,7 @@ type scannerItem struct {
|
||||
type sizeSummary struct {
|
||||
totalSize int64
|
||||
versions uint64
|
||||
deleteMarkers uint64
|
||||
replicatedSize int64
|
||||
pendingSize int64
|
||||
failedSize int64
|
||||
@@ -941,13 +930,13 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), lcEvt.Action)
|
||||
}
|
||||
}
|
||||
defer globalScannerMetrics.timeILM(lcEvt.Action)
|
||||
defer globalScannerMetrics.timeILM(lcEvt.Action)()
|
||||
|
||||
switch lcEvt.Action {
|
||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||
return applyLifecycleAction(lcEvt, oi), 0
|
||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction, lifecycle.DeleteAllVersionsAction:
|
||||
return applyLifecycleAction(lcEvt, lcEventSrc_Scanner, oi), 0
|
||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||
return applyLifecycleAction(lcEvt, oi), size
|
||||
return applyLifecycleAction(lcEvt, lcEventSrc_Scanner, oi), size
|
||||
default:
|
||||
// No action.
|
||||
return false, size
|
||||
@@ -1011,7 +1000,8 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
|
||||
return objectInfos, nil
|
||||
}
|
||||
|
||||
_, days, lim := i.lifeCycle.NoncurrentVersionsExpirationLimit(lifecycle.ObjectOpts{Name: i.objectPath()})
|
||||
event := i.lifeCycle.NoncurrentVersionsExpirationLimit(lifecycle.ObjectOpts{Name: i.objectPath()})
|
||||
lim := event.NewerNoncurrentVersions
|
||||
if lim == 0 || len(fivs) <= lim+1 { // fewer than lim _noncurrent_ versions
|
||||
for _, fi := range fivs {
|
||||
objectInfos = append(objectInfos, fi.ToObjectInfo(i.bucket, i.objectPath(), versioned))
|
||||
@@ -1040,10 +1030,10 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
|
||||
}
|
||||
|
||||
// NoncurrentDays not passed yet.
|
||||
if time.Now().UTC().Before(lifecycle.ExpectedExpiryTime(obj.SuccessorModTime, days)) {
|
||||
if time.Now().UTC().Before(lifecycle.ExpectedExpiryTime(obj.SuccessorModTime, event.NoncurrentDays)) {
|
||||
// add this version back to remaining versions for
|
||||
// subsequent lifecycle policy applications
|
||||
fivs = append(fivs, fi)
|
||||
objectInfos = append(objectInfos, obj)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1055,7 +1045,7 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
|
||||
})
|
||||
}
|
||||
|
||||
globalExpiryState.enqueueByNewerNoncurrent(i.bucket, toDel)
|
||||
globalExpiryState.enqueueByNewerNoncurrent(i.bucket, toDel, event)
|
||||
return objectInfos, nil
|
||||
}
|
||||
|
||||
@@ -1132,6 +1122,12 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
|
||||
return event
|
||||
}
|
||||
|
||||
if obj.IsLatest && event.Action == lifecycle.DeleteAllVersionsAction {
|
||||
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
||||
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||
}
|
||||
}
|
||||
|
||||
switch event.Action {
|
||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction:
|
||||
// Defensive code, should never happen
|
||||
@@ -1153,16 +1149,16 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
|
||||
return event
|
||||
}
|
||||
|
||||
func applyTransitionRule(event lifecycle.Event, obj ObjectInfo) bool {
|
||||
func applyTransitionRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo) bool {
|
||||
if obj.DeleteMarker {
|
||||
return false
|
||||
}
|
||||
globalTransitionState.queueTransitionTask(obj, event)
|
||||
globalTransitionState.queueTransitionTask(obj, event, src)
|
||||
return true
|
||||
}
|
||||
|
||||
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event) bool {
|
||||
if err := expireTransitionedObject(ctx, objLayer, &obj, obj.ToLifecycleOpts(), lcEvent); err != nil {
|
||||
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) bool {
|
||||
if err := expireTransitionedObject(ctx, objLayer, &obj, obj.ToLifecycleOpts(), lcEvent, src); err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
return false
|
||||
}
|
||||
@@ -1173,7 +1169,7 @@ func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer,
|
||||
return true
|
||||
}
|
||||
|
||||
func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event) bool {
|
||||
func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) bool {
|
||||
traceFn := globalLifecycleSys.trace(obj)
|
||||
opts := ObjectOptions{
|
||||
Expiration: ExpirationOptions{Expire: true},
|
||||
@@ -1186,6 +1182,9 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
||||
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(obj.Bucket, obj.Name)
|
||||
opts.VersionSuspended = globalBucketVersioningSys.PrefixSuspended(obj.Bucket, obj.Name)
|
||||
}
|
||||
if lcEvent.Action.DeleteAll() {
|
||||
opts.DeletePrefix = true
|
||||
}
|
||||
|
||||
obj, err := objLayer.DeleteObject(ctx, obj.Bucket, obj.Name, opts)
|
||||
if err != nil {
|
||||
@@ -1193,11 +1192,11 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
||||
return false
|
||||
}
|
||||
// Assume it is still there.
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, "non-transition-expiry")
|
||||
return false
|
||||
}
|
||||
|
||||
tags := auditLifecycleTags(lcEvent)
|
||||
tags := newLifecycleAuditEvent(src, lcEvent).Tags()
|
||||
// Send audit for the lifecycle delete operation
|
||||
auditLogLifecycle(ctx, obj, ILMExpiry, tags, traceFn)
|
||||
|
||||
@@ -1219,19 +1218,20 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
||||
}
|
||||
|
||||
// Apply object, object version, restored object or restored object version action on the given object
|
||||
func applyExpiryRule(event lifecycle.Event, obj ObjectInfo) bool {
|
||||
globalExpiryState.enqueueByDays(obj, event)
|
||||
func applyExpiryRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo) bool {
|
||||
globalExpiryState.enqueueByDays(obj, event, src)
|
||||
return true
|
||||
}
|
||||
|
||||
// Perform actions (removal or transitioning of objects), return true the action is successfully performed
|
||||
func applyLifecycleAction(event lifecycle.Event, obj ObjectInfo) (success bool) {
|
||||
func applyLifecycleAction(event lifecycle.Event, src lcEventSrc, obj ObjectInfo) (success bool) {
|
||||
switch action := event.Action; action {
|
||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction,
|
||||
lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||
success = applyExpiryRule(event, obj)
|
||||
lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction,
|
||||
lifecycle.DeleteAllVersionsAction:
|
||||
success = applyExpiryRule(event, src, obj)
|
||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||
success = applyTransitionRule(event, obj)
|
||||
success = applyTransitionRule(event, src, obj)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1249,7 +1249,7 @@ func (i *scannerItem) healReplication(ctx context.Context, o ObjectLayer, oi Obj
|
||||
if i.replication.Config == nil {
|
||||
return
|
||||
}
|
||||
roi := queueReplicationHeal(ctx, oi.Bucket, oi, i.replication)
|
||||
roi := queueReplicationHeal(ctx, oi.Bucket, oi, i.replication, 0)
|
||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||
return
|
||||
}
|
||||
@@ -1345,11 +1345,11 @@ func (d *dynamicSleeper) Timer(ctx context.Context) func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
if d.isScanner {
|
||||
globalScannerMetrics.incTime(scannerMetricYield, wantSleep)
|
||||
}
|
||||
<-timer.C
|
||||
}
|
||||
if d.isScanner {
|
||||
globalScannerMetrics.incTime(scannerMetricYield, wantSleep)
|
||||
}
|
||||
return
|
||||
case <-timer.C:
|
||||
if d.isScanner {
|
||||
|
||||
+49
-14
@@ -33,7 +33,7 @@ import (
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -57,6 +57,7 @@ type dataUsageEntry struct {
|
||||
Size int64 `msg:"sz"`
|
||||
Objects uint64 `msg:"os"`
|
||||
Versions uint64 `msg:"vs"` // Versions that are not delete markers.
|
||||
DeleteMarkers uint64 `msg:"dms"`
|
||||
ObjSizes sizeHistogram `msg:"szs"`
|
||||
ObjVersions versionsHistogram `msg:"vh"`
|
||||
ReplicationStats *replicationAllStats `msg:"rs,omitempty"`
|
||||
@@ -88,6 +89,20 @@ func (ats *allTierStats) merge(other *allTierStats) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ats *allTierStats) clone() *allTierStats {
|
||||
if ats == nil {
|
||||
return nil
|
||||
}
|
||||
dst := *ats
|
||||
if dst.Tiers != nil {
|
||||
dst.Tiers = make(map[string]tierStats, len(dst.Tiers))
|
||||
for tier, st := range dst.Tiers {
|
||||
dst.Tiers[tier] = st
|
||||
}
|
||||
}
|
||||
return &dst
|
||||
}
|
||||
|
||||
func (ats *allTierStats) adminStats(stats map[string]madmin.TierStats) map[string]madmin.TierStats {
|
||||
if ats == nil {
|
||||
return stats
|
||||
@@ -168,6 +183,25 @@ type replicationAllStatsV1 struct {
|
||||
ReplicaSize uint64 `msg:"ReplicaSize,omitempty"`
|
||||
}
|
||||
|
||||
// clone creates a deep-copy clone.
|
||||
func (r *replicationAllStats) clone() *replicationAllStats {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shallow copy
|
||||
dst := *r
|
||||
|
||||
// Copy individual targets.
|
||||
if dst.Targets != nil {
|
||||
dst.Targets = make(map[string]replicationStats, len(dst.Targets))
|
||||
for k, v := range r.Targets {
|
||||
dst.Targets[k] = v
|
||||
}
|
||||
}
|
||||
return &dst
|
||||
}
|
||||
|
||||
//msgp:encode ignore dataUsageEntryV2 dataUsageEntryV3 dataUsageEntryV4 dataUsageEntryV5 dataUsageEntryV6
|
||||
//msgp:marshal ignore dataUsageEntryV2 dataUsageEntryV3 dataUsageEntryV4 dataUsageEntryV5 dataUsageEntryV6
|
||||
|
||||
@@ -295,6 +329,7 @@ type dataUsageCacheInfo struct {
|
||||
func (e *dataUsageEntry) addSizes(summary sizeSummary) {
|
||||
e.Size += summary.totalSize
|
||||
e.Versions += summary.versions
|
||||
e.DeleteMarkers += summary.deleteMarkers
|
||||
e.ObjSizes.add(summary.totalSize)
|
||||
e.ObjVersions.add(summary.versions)
|
||||
|
||||
@@ -333,6 +368,7 @@ func (e *dataUsageEntry) addSizes(summary sizeSummary) {
|
||||
func (e *dataUsageEntry) merge(other dataUsageEntry) {
|
||||
e.Objects += other.Objects
|
||||
e.Versions += other.Versions
|
||||
e.DeleteMarkers += other.DeleteMarkers
|
||||
e.Size += other.Size
|
||||
if other.ReplicationStats != nil {
|
||||
if e.ReplicationStats == nil {
|
||||
@@ -413,14 +449,11 @@ func (e dataUsageEntry) clone() dataUsageEntry {
|
||||
e.Children = ch
|
||||
}
|
||||
if e.ReplicationStats != nil {
|
||||
// Copy to new struct
|
||||
r := *e.ReplicationStats
|
||||
e.ReplicationStats = &r
|
||||
// Clone ReplicationStats
|
||||
e.ReplicationStats = e.ReplicationStats.clone()
|
||||
}
|
||||
if e.AllTierStats != nil {
|
||||
ats := newAllTierStats()
|
||||
ats.merge(e.AllTierStats)
|
||||
e.AllTierStats = ats
|
||||
e.AllTierStats = e.AllTierStats.clone()
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -501,13 +534,14 @@ func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo {
|
||||
}
|
||||
flat := d.flatten(*e)
|
||||
dui := DataUsageInfo{
|
||||
LastUpdate: d.Info.LastUpdate,
|
||||
ObjectsTotalCount: flat.Objects,
|
||||
VersionsTotalCount: flat.Versions,
|
||||
ObjectsTotalSize: uint64(flat.Size),
|
||||
BucketsCount: uint64(len(e.Children)),
|
||||
BucketsUsage: d.bucketsUsageInfo(buckets),
|
||||
TierStats: d.tiersUsageInfo(buckets),
|
||||
LastUpdate: d.Info.LastUpdate,
|
||||
ObjectsTotalCount: flat.Objects,
|
||||
VersionsTotalCount: flat.Versions,
|
||||
DeleteMarkersTotalCount: flat.DeleteMarkers,
|
||||
ObjectsTotalSize: uint64(flat.Size),
|
||||
BucketsCount: uint64(len(e.Children)),
|
||||
BucketsUsage: d.bucketsUsageInfo(buckets),
|
||||
TierStats: d.tiersUsageInfo(buckets),
|
||||
}
|
||||
return dui
|
||||
}
|
||||
@@ -775,6 +809,7 @@ func (d *dataUsageCache) bucketsUsageInfo(buckets []BucketInfo) map[string]Bucke
|
||||
Size: uint64(flat.Size),
|
||||
VersionsCount: flat.Versions,
|
||||
ObjectsCount: flat.Objects,
|
||||
DeleteMarkersCount: flat.DeleteMarkers,
|
||||
ObjectSizesHistogram: flat.ObjSizes.toMap(),
|
||||
ObjectVersionsHistogram: flat.ObjVersions.toMap(),
|
||||
}
|
||||
|
||||
+38
-13
@@ -1522,6 +1522,12 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Versions")
|
||||
return
|
||||
}
|
||||
case "dms":
|
||||
z.DeleteMarkers, err = dc.ReadUint64()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "DeleteMarkers")
|
||||
return
|
||||
}
|
||||
case "szs":
|
||||
var zb0002 uint32
|
||||
zb0002, err = dc.ReadArrayHeader()
|
||||
@@ -1614,16 +1620,16 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
zb0001Len := uint32(9)
|
||||
var zb0001Mask uint16 /* 9 bits */
|
||||
zb0001Len := uint32(10)
|
||||
var zb0001Mask uint16 /* 10 bits */
|
||||
_ = zb0001Mask
|
||||
if z.ReplicationStats == nil {
|
||||
zb0001Len--
|
||||
zb0001Mask |= 0x40
|
||||
zb0001Mask |= 0x80
|
||||
}
|
||||
if z.AllTierStats == nil {
|
||||
zb0001Len--
|
||||
zb0001Mask |= 0x80
|
||||
zb0001Mask |= 0x100
|
||||
}
|
||||
// variable map header, size zb0001Len
|
||||
err = en.Append(0x80 | uint8(zb0001Len))
|
||||
@@ -1673,6 +1679,16 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Versions")
|
||||
return
|
||||
}
|
||||
// write "dms"
|
||||
err = en.Append(0xa3, 0x64, 0x6d, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteUint64(z.DeleteMarkers)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "DeleteMarkers")
|
||||
return
|
||||
}
|
||||
// write "szs"
|
||||
err = en.Append(0xa3, 0x73, 0x7a, 0x73)
|
||||
if err != nil {
|
||||
@@ -1707,7 +1723,7 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x40) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x80) == 0 { // if not empty
|
||||
// write "rs"
|
||||
err = en.Append(0xa2, 0x72, 0x73)
|
||||
if err != nil {
|
||||
@@ -1726,7 +1742,7 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x80) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x100) == 0 { // if not empty
|
||||
// write "ats"
|
||||
err = en.Append(0xa3, 0x61, 0x74, 0x73)
|
||||
if err != nil {
|
||||
@@ -1762,16 +1778,16 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
zb0001Len := uint32(9)
|
||||
var zb0001Mask uint16 /* 9 bits */
|
||||
zb0001Len := uint32(10)
|
||||
var zb0001Mask uint16 /* 10 bits */
|
||||
_ = zb0001Mask
|
||||
if z.ReplicationStats == nil {
|
||||
zb0001Len--
|
||||
zb0001Mask |= 0x40
|
||||
zb0001Mask |= 0x80
|
||||
}
|
||||
if z.AllTierStats == nil {
|
||||
zb0001Len--
|
||||
zb0001Mask |= 0x80
|
||||
zb0001Mask |= 0x100
|
||||
}
|
||||
// variable map header, size zb0001Len
|
||||
o = append(o, 0x80|uint8(zb0001Len))
|
||||
@@ -1794,6 +1810,9 @@ func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "vs"
|
||||
o = append(o, 0xa2, 0x76, 0x73)
|
||||
o = msgp.AppendUint64(o, z.Versions)
|
||||
// string "dms"
|
||||
o = append(o, 0xa3, 0x64, 0x6d, 0x73)
|
||||
o = msgp.AppendUint64(o, z.DeleteMarkers)
|
||||
// string "szs"
|
||||
o = append(o, 0xa3, 0x73, 0x7a, 0x73)
|
||||
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
|
||||
@@ -1806,7 +1825,7 @@ func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
for za0002 := range z.ObjVersions {
|
||||
o = msgp.AppendUint64(o, z.ObjVersions[za0002])
|
||||
}
|
||||
if (zb0001Mask & 0x40) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x80) == 0 { // if not empty
|
||||
// string "rs"
|
||||
o = append(o, 0xa2, 0x72, 0x73)
|
||||
if z.ReplicationStats == nil {
|
||||
@@ -1819,7 +1838,7 @@ func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x80) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x100) == 0 { // if not empty
|
||||
// string "ats"
|
||||
o = append(o, 0xa3, 0x61, 0x74, 0x73)
|
||||
if z.AllTierStats == nil {
|
||||
@@ -1880,6 +1899,12 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Versions")
|
||||
return
|
||||
}
|
||||
case "dms":
|
||||
z.DeleteMarkers, bts, err = msgp.ReadUint64Bytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "DeleteMarkers")
|
||||
return
|
||||
}
|
||||
case "szs":
|
||||
var zb0002 uint32
|
||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||
@@ -1970,7 +1995,7 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *dataUsageEntry) Msgsize() (s int) {
|
||||
s = 1 + 3 + z.Children.Msgsize() + 3 + msgp.Int64Size + 3 + msgp.Uint64Size + 3 + msgp.Uint64Size + 4 + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + 3 + msgp.ArrayHeaderSize + (dataUsageVersionLen * (msgp.Uint64Size)) + 3
|
||||
s = 1 + 3 + z.Children.Msgsize() + 3 + msgp.Int64Size + 3 + msgp.Uint64Size + 3 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + 3 + msgp.ArrayHeaderSize + (dataUsageVersionLen * (msgp.Uint64Size)) + 3
|
||||
if z.ReplicationStats == nil {
|
||||
s += msgp.NilSize
|
||||
} else {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user