mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 10:43:15 +03:00
Compare commits
307 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca258c04cb | |||
| 30bd5e2669 | |||
| 38637897ba | |||
| c727c8b684 | |||
| 993d96feef | |||
| b2b26d9c95 | |||
| cba3dd276b | |||
| a47fc75c26 | |||
| 42cfdf246f | |||
| e5c8794b8b | |||
| ac90a873eb | |||
| 5ce68ad7fd | |||
| 099e88516d | |||
| 82a6ad2c10 | |||
| c1a78224cf | |||
| 39f9350697 | |||
| e31081d79d | |||
| f02d282754 | |||
| a89e0bab7d | |||
| 3a90af0bcd | |||
| 53ceb0791f | |||
| 2cd98a0d21 | |||
| a0b10c05e5 | |||
| 04135fa6cd | |||
| 42dc6329e6 | |||
| 9b8ba97f9f | |||
| 7705605b5a | |||
| 414bcb0c73 | |||
| f4710948c4 | |||
| 3f4488c589 | |||
| 9434fff215 | |||
| 695962fae8 | |||
| 8f13c8c3bf | |||
| c1cae51fb5 | |||
| 31d16f6cc2 | |||
| a50ea92c64 | |||
| 5b2ced0119 | |||
| 8a0ba093dd | |||
| fbd8dfe60f | |||
| 60aff22931 | |||
| 5fc7da345d | |||
| fd2c38fbef | |||
| ba245c6c46 | |||
| 496027b589 | |||
| 8bd4f6568b | |||
| 9d7660b409 | |||
| da55499db0 | |||
| 22f8e39b58 | |||
| eba23bbac4 | |||
| 4550535cbb | |||
| 8432fd5ac2 | |||
| 7c948adf88 | |||
| 56b7045c20 | |||
| b1a109a611 | |||
| 7a311a3b66 | |||
| d55b6b9909 | |||
| f4389fb322 | |||
| 331208bec1 | |||
| 7680e5f81d | |||
| 6acf038a84 | |||
| bdf4e386cf | |||
| ad8a34858f | |||
| 162eced7d2 | |||
| bec1f7c26a | |||
| 8771617199 | |||
| 54bc995f0a | |||
| 6c89a81af4 | |||
| 8fa2898ff1 | |||
| 10ca0a6936 | |||
| b3314e97a6 | |||
| 3b9a948045 | |||
| 3781a0f9ad | |||
| e79b289325 | |||
| 6d4c1156d6 | |||
| 3f72c7fcc7 | |||
| d521c84d55 | |||
| 946b070744 | |||
| 4a21dce2b5 | |||
| 5fe7f9fa93 | |||
| 65f34cd823 | |||
| 196e7e072b | |||
| 6f97663174 | |||
| aed7a1818a | |||
| b50d90183e | |||
| 6b06da76cb | |||
| 6ca6788bb7 | |||
| 2e23e61a45 | |||
| 9cdf490bc5 | |||
| cfed671ea3 | |||
| 53ce92b9ca | |||
| 7350a29fec | |||
| 5cc2c62c66 | |||
| 4bc5ed6c76 | |||
| e99a597899 | |||
| 73dde66dbe | |||
| e30c0e7ca3 | |||
| 8fc200c0cc | |||
| 708296ae1b | |||
| fbb5e75e01 | |||
| f327b21557 | |||
| 45b7253f39 | |||
| 05bb655efc | |||
| 8fdfcfb562 | |||
| e7c144eeac | |||
| e98172d72d | |||
| f2d063e7b9 | |||
| a50f26b7f5 | |||
| 69294cf98a | |||
| c397fb6c7a | |||
| 961b0b524e | |||
| 860fc200b0 | |||
| 109a9e3f35 | |||
| 5f971fea6e | |||
| 0d7abe3b9f | |||
| 94fbcd8ebe | |||
| 879d5dd236 | |||
| 34187e047d | |||
| 0ee722f8c3 | |||
| b7d11141e1 | |||
| e9babf3dac | |||
| 0bb81f2e9c | |||
| bea0b050cd | |||
| ce62980d4e | |||
| dc88865908 | |||
| 9fbd931058 | |||
| b0264bdb90 | |||
| 95d6f43cc8 | |||
| 9cb94eb4a9 | |||
| bd0819330d | |||
| 8d9e83fd99 | |||
| be02333529 | |||
| 506f121576 | |||
| ca488cce87 | |||
| 11dc723324 | |||
| dd6ea18901 | |||
| 3369eeb920 | |||
| 9032f49f25 | |||
| fbc6f3f6e8 | |||
| fba883839d | |||
| a93214ea63 | |||
| e6b0fc465b | |||
| 70fbcfee4a | |||
| 0b074d0fae | |||
| d67e4d5b17 | |||
| 891c60d83d | |||
| fe3e49c4eb | |||
| 58306a9d34 | |||
| 41091d9472 | |||
| a4cfb5e1ed | |||
| 51aa59a737 | |||
| 8bedb419a9 | |||
| f56a182b71 | |||
| 317b40ef90 | |||
| e938ece492 | |||
| 02331a612c | |||
| 8317557f70 | |||
| 1bb7a2a295 | |||
| 215ca58d6a | |||
| 12f570a307 | |||
| e4b619ce1a | |||
| 0a286153bb | |||
| 22d59e757d | |||
| 0daa2dbf59 | |||
| 96c2304ae8 | |||
| 343dd2f491 | |||
| 38f35463b7 | |||
| 5573986e8e | |||
| f3367a1b20 | |||
| a3c2f7b0e8 | |||
| 8fbec30998 | |||
| a7466eeb0e | |||
| 8b1e819bf3 | |||
| fe63664164 | |||
| 4598827dcb | |||
| 9afdb05bf4 | |||
| 9569a85cee | |||
| 54721b7c7b | |||
| 91d8bddbd1 | |||
| 80adc87a14 | |||
| 117ad1b65b | |||
| 2229509362 | |||
| 6ef8e87492 | |||
| 0a25083fdb | |||
| 15137d0327 | |||
| 8c9974bc0f | |||
| 079b6c2b50 | |||
| 0924b34a17 | |||
| 754f7a8a39 | |||
| 64bafe1dfe | |||
| c3e456e7e6 | |||
| 57aaeafd2f | |||
| 3c2e1a87e2 | |||
| da95a2d13f | |||
| cc5e05fdeb | |||
| a79c390cca | |||
| 8a56af439c | |||
| f6e581ce54 | |||
| 8953f88780 | |||
| 4b4a98d5e5 | |||
| 7472818d94 | |||
| ad44fe8d3e | |||
| 55e713db0a | |||
| 33322e6638 | |||
| a1792ca0d1 | |||
| ac8c43fe9c | |||
| 4d40ee00e9 | |||
| 06f59ad631 | |||
| 877e0cac03 | |||
| ef67c39910 | |||
| 508710f4d1 | |||
| 3aa3d9cf14 | |||
| c2fedb4c3f | |||
| 03dc65e12d | |||
| b8d62a8068 | |||
| dbc2368a7b | |||
| 54aed421b8 | |||
| d5e8dac1cf | |||
| 96ec8fcba1 | |||
| 0663eb69ed | |||
| 0594d37230 | |||
| 3cc30bcc18 | |||
| 99c1a642a4 | |||
| c60f54e5be | |||
| 483389f2e2 | |||
| c0f2f84285 | |||
| 069d118329 | |||
| a7b1834772 | |||
| 6415dec37a | |||
| 74253e1ddc | |||
| 01b3fb91e5 | |||
| 2dc917e87f | |||
| 0a284a1a10 | |||
| 5c8339e1e8 | |||
| fd37418da2 | |||
| bbfea29c2b | |||
| aa703dc903 | |||
| 8cd80fec8c | |||
| 780882efcf | |||
| c5636143c6 | |||
| ba6218b354 | |||
| 8e32de3ba9 | |||
| e37508fb8f | |||
| b46a717425 | |||
| 7926df0b80 | |||
| 557df666fd | |||
| f91b257f50 | |||
| 28a2d1eb3d | |||
| a0ae1489e5 | |||
| edfb310a59 | |||
| a2312028b9 | |||
| 78f1f69d57 | |||
| e1e33077e8 | |||
| b3e7de010d | |||
| f5b04865f4 | |||
| bf1c6edb76 | |||
| 2ac7fee017 | |||
| 128256e3ab | |||
| a66a7f3e97 | |||
| 20b79f8945 | |||
| 9a877734b2 | |||
| 409c391850 | |||
| 763ff085a6 | |||
| 5b9656374c | |||
| b32014549c | |||
| 9476d212bc | |||
| 000928d34e | |||
| 6829ae5b13 | |||
| f09756443d | |||
| 5512016885 | |||
| 21ecb941fe | |||
| 77e94087cf | |||
| 9ab1f25a47 | |||
| aaab7aefbe | |||
| 5b8599e52d | |||
| 74e0c9ab9b | |||
| dcce83b288 | |||
| f731e7ea36 | |||
| ec30bb89a4 | |||
| 7cd08594f6 | |||
| 2b4531f069 | |||
| 11544a62aa | |||
| 18550387d5 | |||
| efb03e19e6 | |||
| c27d0583d4 | |||
| 0de2b9a1b2 | |||
| 9dc29d7687 | |||
| 72871dbb9a | |||
| 4bda4e4e2b | |||
| 1971c54a50 | |||
| bb77b89da0 | |||
| b336e9a79f | |||
| a2ab21e91c | |||
| 603437e70f | |||
| db3a9a5990 | |||
| 24c7e73b4e | |||
| c053e57068 | |||
| 6d20ec3bea | |||
| c50627ee3e | |||
| b3cd893f93 | |||
| 22d2dbc4e6 | |||
| 2b5d9428b1 | |||
| d6446cb096 | |||
| c34bdc33fb | |||
| dd8547e51c | |||
| aec023f537 | |||
| e101eeeda9 | |||
| f29522269d |
@@ -1,6 +1,6 @@
|
|||||||
## Community Contribution License
|
## Community Contribution License
|
||||||
All community contributions in this pull request are licensed to the project maintainers
|
All community contributions in this pull request are licensed to the project maintainers
|
||||||
under the terms of the [Apache 2 license] (https://www.apache.org/licenses/LICENSE-2.0).
|
under the terms of the [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
By creating this pull request I represent that I have the right to license the
|
By creating this pull request I represent that I have the right to license the
|
||||||
contributions to the project maintainers under the Apache 2 license.
|
contributions to the project maintainers under the Apache 2 license.
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# @format
|
||||||
|
|
||||||
|
name: Issue Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-to-project:
|
||||||
|
name: Add issue to project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/add-to-project@v0.5.0
|
||||||
|
with:
|
||||||
|
project-url: https://github.com/orgs/miniohq/projects/2
|
||||||
|
github-token: ${{ secrets.BOT_PAT }}
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -37,7 +38,11 @@ jobs:
|
|||||||
|
|
||||||
- name: build-minio
|
- name: build-minio
|
||||||
run: |
|
run: |
|
||||||
TAG="minio/minio:${{ steps.vars.outputs.sha_short }}" make docker
|
TAG="quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}" make docker
|
||||||
|
|
||||||
|
- name: multipart uploads test
|
||||||
|
run: |
|
||||||
|
${GITHUB_WORKSPACE}/.github/workflows/multipart/migrate.sh "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
- name: compress and encrypt
|
- name: compress and encrypt
|
||||||
run: |
|
run: |
|
||||||
@@ -59,7 +64,14 @@ jobs:
|
|||||||
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml down || true
|
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
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml rm || true
|
||||||
done
|
done
|
||||||
docker rmi -f minio/minio:${{ steps.vars.outputs.sha_short }}
|
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site1.yaml rm -s -f || true
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site2.yaml rm -s -f || true
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume} || true
|
||||||
|
done
|
||||||
|
|
||||||
|
docker rmi -f quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}
|
||||||
docker system prune -f || true
|
docker system prune -f || true
|
||||||
docker volume prune -f || true
|
docker volume prune -f || true
|
||||||
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
# Settings and configurations that are common for all containers
|
# Settings and configurations that are common for all containers
|
||||||
x-minio-common: &minio-common
|
x-minio-common: &minio-common
|
||||||
image: minio/minio:${JOB_NAME}
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
command: server --console-address ":9001" http://minio{1...4}/cdata{1...2}
|
command: server --console-address ":9001" http://minio{1...4}/cdata{1...2}
|
||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
@@ -16,10 +16,10 @@ x-minio-common: &minio-common
|
|||||||
MINIO_COMPRESSION_ALLOW_ENCRYPTION: "on"
|
MINIO_COMPRESSION_ALLOW_ENCRYPTION: "on"
|
||||||
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
interval: 30s
|
interval: 5s
|
||||||
timeout: 20s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 5
|
||||||
|
|
||||||
# starts 4 docker containers running minio server instances.
|
# starts 4 docker containers running minio server instances.
|
||||||
# using nginx reverse proxy, load balancing, you can access
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
# Settings and configurations that are common for all containers
|
# Settings and configurations that are common for all containers
|
||||||
x-minio-common: &minio-common
|
x-minio-common: &minio-common
|
||||||
image: minio/minio:${JOB_NAME}
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
command: server --console-address ":9001" edata{1...4}
|
command: server --console-address ":9001" edata{1...4}
|
||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
@@ -13,10 +13,10 @@ x-minio-common: &minio-common
|
|||||||
MINIO_ROOT_PASSWORD: "minio123"
|
MINIO_ROOT_PASSWORD: "minio123"
|
||||||
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
interval: 30s
|
interval: 5s
|
||||||
timeout: 20s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 5
|
||||||
|
|
||||||
# starts 4 docker containers running minio server instances.
|
# starts 4 docker containers running minio server instances.
|
||||||
# using nginx reverse proxy, load balancing, you can access
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
# Settings and configurations that are common for all containers
|
# Settings and configurations that are common for all containers
|
||||||
x-minio-common: &minio-common
|
x-minio-common: &minio-common
|
||||||
image: minio/minio:${JOB_NAME}
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
command: server --console-address ":9001" http://minio{1...4}/pdata{1...2} http://minio{5...8}/pdata{1...2}
|
command: server --console-address ":9001" http://minio{1...4}/pdata{1...2} http://minio{5...8}/pdata{1...2}
|
||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
@@ -13,10 +13,10 @@ x-minio-common: &minio-common
|
|||||||
MINIO_ROOT_PASSWORD: "minio123"
|
MINIO_ROOT_PASSWORD: "minio123"
|
||||||
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
interval: 30s
|
interval: 5s
|
||||||
timeout: 20s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 5
|
||||||
|
|
||||||
# starts 4 docker containers running minio server instances.
|
# starts 4 docker containers running minio server instances.
|
||||||
# using nginx reverse proxy, load balancing, you can access
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${RELEASE}
|
||||||
|
command: server http://site1-minio{1...4}/data{1...2}
|
||||||
|
environment:
|
||||||
|
- MINIO_PROMETHEUS_AUTH_TYPE=public
|
||||||
|
- CI=true
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
site1-minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio1
|
||||||
|
volumes:
|
||||||
|
- site1-data1-1:/data1
|
||||||
|
- site1-data1-2:/data2
|
||||||
|
|
||||||
|
site1-minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio2
|
||||||
|
volumes:
|
||||||
|
- site1-data2-1:/data1
|
||||||
|
- site1-data2-2:/data2
|
||||||
|
|
||||||
|
site1-minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio3
|
||||||
|
volumes:
|
||||||
|
- site1-data3-1:/data1
|
||||||
|
- site1-data3-2:/data2
|
||||||
|
|
||||||
|
site1-minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio4
|
||||||
|
volumes:
|
||||||
|
- site1-data4-1:/data1
|
||||||
|
- site1-data4-2:/data2
|
||||||
|
|
||||||
|
site1-nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: site1-nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-site1.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- site1-minio1
|
||||||
|
- site1-minio2
|
||||||
|
- site1-minio3
|
||||||
|
- site1-minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
site1-data1-1:
|
||||||
|
site1-data1-2:
|
||||||
|
site1-data2-1:
|
||||||
|
site1-data2-2:
|
||||||
|
site1-data3-1:
|
||||||
|
site1-data3-2:
|
||||||
|
site1-data4-1:
|
||||||
|
site1-data4-2:
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${RELEASE}
|
||||||
|
command: server http://site2-minio{1...4}/data{1...2}
|
||||||
|
environment:
|
||||||
|
- MINIO_PROMETHEUS_AUTH_TYPE=public
|
||||||
|
- CI=true
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
site2-minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio1
|
||||||
|
volumes:
|
||||||
|
- site2-data1-1:/data1
|
||||||
|
- site2-data1-2:/data2
|
||||||
|
|
||||||
|
site2-minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio2
|
||||||
|
volumes:
|
||||||
|
- site2-data2-1:/data1
|
||||||
|
- site2-data2-2:/data2
|
||||||
|
|
||||||
|
site2-minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio3
|
||||||
|
volumes:
|
||||||
|
- site2-data3-1:/data1
|
||||||
|
- site2-data3-2:/data2
|
||||||
|
|
||||||
|
site2-minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio4
|
||||||
|
volumes:
|
||||||
|
- site2-data4-1:/data1
|
||||||
|
- site2-data4-2:/data2
|
||||||
|
|
||||||
|
site2-nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: site2-nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-site2.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9002:9002"
|
||||||
|
depends_on:
|
||||||
|
- site2-minio1
|
||||||
|
- site2-minio2
|
||||||
|
- site2-minio3
|
||||||
|
- site2-minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
site2-data1-1:
|
||||||
|
site2-data1-2:
|
||||||
|
site2-data2-1:
|
||||||
|
site2-data2-2:
|
||||||
|
site2-data3-1:
|
||||||
|
site2-data3-2:
|
||||||
|
site2-data4-1:
|
||||||
|
site2-data4-2:
|
||||||
Executable
+115
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd .github/workflows/multipart/
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
docker-compose -f docker-compose-site1.yaml rm -s -f || true
|
||||||
|
docker-compose -f docker-compose-site2.yaml rm -s -f || true
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume} || true
|
||||||
|
done
|
||||||
|
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
wget --quiet -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x mc
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
cd /tmp
|
||||||
|
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||||
|
)
|
||||||
|
|
||||||
|
export RELEASE=RELEASE.2023-08-29T23-07-35Z
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
|
docker-compose -f docker-compose-site2.yaml up -d
|
||||||
|
|
||||||
|
sleep 30s
|
||||||
|
|
||||||
|
./mc alias set site1 http://site1-nginx:9001 minioadmin minioadmin --api s3v4
|
||||||
|
./mc alias set site2 http://site2-nginx:9002 minioadmin minioadmin --api s3v4
|
||||||
|
|
||||||
|
./mc ready site1/
|
||||||
|
./mc ready site2/
|
||||||
|
|
||||||
|
./mc admin replicate add site1 site2
|
||||||
|
./mc mb site1/testbucket/
|
||||||
|
./mc cp -r --quiet /usr/bin site1/testbucket/
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
s3-check-md5 -h
|
||||||
|
|
||||||
|
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site1 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
./mc cp -r --quiet /usr/bin site1/testbucket/
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
|
## we do not need to fail here, since we are going to test
|
||||||
|
## upgrading to master, healing and being able to recover
|
||||||
|
## the last version.
|
||||||
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site1 uploads ${failed_count_site1}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads ${failed_count_site2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export RELEASE=${1}
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
|
docker-compose -f docker-compose-site2.yaml up -d
|
||||||
|
|
||||||
|
./mc ready site1/
|
||||||
|
./mc ready site2/
|
||||||
|
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
# mc admin heal -r --remove when used against a LB endpoint
|
||||||
|
# behaves flaky, let this run 10 times before giving up
|
||||||
|
./mc admin heal -r --remove --json site1/ 2>&1 >/dev/null
|
||||||
|
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site1 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd ../../../
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server site1-minio1:9000;
|
||||||
|
server site1-minio2:9000;
|
||||||
|
server site1-minio3:9000;
|
||||||
|
server site1-minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server site2-minio1:9000;
|
||||||
|
server site2-minio2:9000;
|
||||||
|
server site2-minio3:9000;
|
||||||
|
server site2-minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9002;
|
||||||
|
listen [::]:9002;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -35,6 +36,12 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-decom
|
make test-decom
|
||||||
|
|
||||||
|
- name: Test Config File
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
make test-configfile
|
||||||
|
|
||||||
- name: Test Replication
|
- name: Test Replication
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
@@ -47,3 +54,8 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-site-replication-minio
|
make test-site-replication-minio
|
||||||
|
|
||||||
|
- name: Test Versioning
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
make test-versioning
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ docker volume rm $(docker volume ls -f dangling=true) || true
|
|||||||
cd .github/workflows/mint
|
cd .github/workflows/mint
|
||||||
|
|
||||||
docker-compose -f minio-${MODE}.yaml up -d
|
docker-compose -f minio-${MODE}.yaml up -d
|
||||||
sleep 5m
|
sleep 30s
|
||||||
|
|
||||||
docker system prune -f || true
|
docker system prune -f || true
|
||||||
docker volume prune -f || true
|
docker volume prune -f || true
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -20,7 +21,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.1
|
go-version: 1.21.5
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Get official govulncheck
|
- name: Get official govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|||||||
@@ -42,3 +42,5 @@ docs/debugging/healing-bin/healing-bin
|
|||||||
docs/debugging/inspect/inspect
|
docs/debugging/inspect/inspect
|
||||||
docs/debugging/pprofgoparser/pprofgoparser
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
docs/debugging/reorder-disks/reorder-disks
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
docs/debugging/populate-hard-links/populate-hardlinks
|
||||||
|
docs/debugging/xattr/xattr
|
||||||
+3
-1
@@ -13,7 +13,6 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- goimports
|
- goimports
|
||||||
- gomodguard
|
- gomodguard
|
||||||
@@ -30,5 +29,8 @@ linters:
|
|||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
exclude:
|
exclude:
|
||||||
|
- "empty-block:"
|
||||||
|
- "unused-parameter:"
|
||||||
|
- "dot-imports:"
|
||||||
- should have a package comment
|
- should have a package comment
|
||||||
- error strings should not be capitalized or end with punctuation or a newline
|
- error strings should not be capitalized or end with punctuation or a newline
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
FROM minio/minio:latest
|
FROM minio/minio:latest
|
||||||
|
|
||||||
ENV PATH=/opt/bin:$PATH
|
COPY ./minio /usr/bin/minio
|
||||||
|
|
||||||
COPY ./minio /opt/bin/minio
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|||||||
+35
-23
@@ -1,4 +1,31 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
|
FROM golang:1.21-alpine as build
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
# Install curl and minisign
|
||||||
|
RUN apk add -U --no-cache ca-certificates && \
|
||||||
|
apk add -U --no-cache curl && \
|
||||||
|
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||||
|
|
||||||
|
# Download minio binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
|
||||||
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
# Download mc binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
|
||||||
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||||
|
chmod +x /go/bin/mc
|
||||||
|
|
||||||
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
@@ -17,33 +44,18 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
MINIO_CONFIG_ENV_FILE=config.env \
|
MINIO_CONFIG_ENV_FILE=config.env \
|
||||||
PATH=/opt/bin:$PATH
|
MC_CONFIG_DIR=/tmp/.mc
|
||||||
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
RUN \
|
|
||||||
microdnf clean all && \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
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 && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /opt/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh && \
|
|
||||||
microdnf clean all
|
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
CMD ["minio"]
|
CMD ["minio"]
|
||||||
|
|||||||
+33
-25
@@ -1,6 +1,31 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
|
FROM golang:1.21-alpine as build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
# Install curl and minisign
|
||||||
|
RUN apk add -U --no-cache ca-certificates && \
|
||||||
|
apk add -U --no-cache curl && \
|
||||||
|
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||||
|
|
||||||
|
# Download minio binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
|
||||||
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
# Download mc binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
|
||||||
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||||
|
chmod +x /go/bin/mc
|
||||||
|
|
||||||
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
@@ -19,35 +44,18 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
MINIO_CONFIG_ENV_FILE=config.env \
|
MINIO_CONFIG_ENV_FILE=config.env \
|
||||||
PATH=/opt/bin:$PATH
|
MC_CONFIG_DIR=/tmp/.mc
|
||||||
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
RUN \
|
|
||||||
microdnf clean all && \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
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 && \
|
|
||||||
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 && \
|
|
||||||
microdnf clean all
|
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
CMD ["minio"]
|
CMD ["minio"]
|
||||||
|
|||||||
+26
-24
@@ -1,6 +1,25 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
|
FROM golang:1.21-alpine as build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
# Install curl and minisign
|
||||||
|
RUN apk add -U --no-cache ca-certificates && \
|
||||||
|
apk add -U --no-cache curl && \
|
||||||
|
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||||
|
|
||||||
|
# Download minio binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips -o /go/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /go/bin/minio.minisig && \
|
||||||
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
@@ -18,34 +37,17 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
MINIO_CONFIG_ENV_FILE=config.env \
|
MINIO_CONFIG_ENV_FILE=config.env
|
||||||
PATH=/opt/bin:$PATH
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
RUN \
|
|
||||||
microdnf clean all && \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
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 && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /opt/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /opt/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh && \
|
|
||||||
microdnf clean all
|
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
CMD ["minio"]
|
CMD ["minio"]
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
FROM golang:1.21-alpine as build
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
# Install curl and minisign
|
||||||
|
RUN apk add -U --no-cache ca-certificates && \
|
||||||
|
apk add -U --no-cache curl && \
|
||||||
|
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||||
|
|
||||||
|
# Download minio binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
|
||||||
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
# Download mc binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
|
||||||
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||||
|
chmod +x /go/bin/mc
|
||||||
|
|
||||||
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi8/ubi-micro:latest
|
||||||
|
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
LABEL name="MinIO" \
|
||||||
|
vendor="MinIO Inc <dev@min.io>" \
|
||||||
|
maintainer="MinIO Inc <dev@min.io>" \
|
||||||
|
version="${RELEASE}" \
|
||||||
|
release="${RELEASE}" \
|
||||||
|
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
||||||
|
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
||||||
|
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
|
MINIO_CONFIG_ENV_FILE=config.env \
|
||||||
|
MC_CONFIG_DIR=/tmp/.mc
|
||||||
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
|
||||||
|
COPY CREDITS /licenses/CREDITS
|
||||||
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
CMD ["minio"]
|
||||||
@@ -6,9 +6,8 @@ GOARCH := $(shell go env GOARCH)
|
|||||||
GOOS := $(shell go env GOOS)
|
GOOS := $(shell go env GOOS)
|
||||||
|
|
||||||
VERSION ?= $(shell git describe --tags)
|
VERSION ?= $(shell git describe --tags)
|
||||||
TAG ?= "minio/minio:$(VERSION)"
|
TAG ?= "quay.io/minio/minio:$(VERSION)"
|
||||||
|
|
||||||
GOLANGCI_VERSION = v1.51.2
|
|
||||||
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
||||||
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
|
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
|
||||||
|
|
||||||
@@ -23,8 +22,8 @@ help: ## print this help
|
|||||||
|
|
||||||
getdeps: ## fetch necessary dependencies
|
getdeps: ## fetch necessary dependencies
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR) $(GOLANGCI_VERSION)
|
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR)
|
||||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.7
|
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@6ac204f0b4d48d17ab4fa442134c7fba13127a4e
|
||||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||||
|
|
||||||
crosscompile: ## cross compile minio
|
crosscompile: ## cross compile minio
|
||||||
@@ -60,6 +59,13 @@ test-decom: install-race
|
|||||||
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||||
|
|
||||||
|
test-versioning: install-race
|
||||||
|
@echo "Running minio versioning tests"
|
||||||
|
@env bash $(PWD)/docs/bucket/versioning/versioning-tests.sh
|
||||||
|
|
||||||
|
test-configfile: install-race
|
||||||
|
@env bash $(PWD)/docs/distributed/distributed-from-config-file.sh
|
||||||
|
|
||||||
test-upgrade: install-race
|
test-upgrade: install-race
|
||||||
@echo "Running minio upgrade tests"
|
@echo "Running minio upgrade tests"
|
||||||
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
||||||
@@ -141,14 +147,23 @@ hotfix-vars:
|
|||||||
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
|
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
|
||||||
$(eval TAG := "minio/minio:$(VERSION)")
|
$(eval TAG := "minio/minio:$(VERSION)")
|
||||||
|
|
||||||
hotfix: hotfix-vars install ## builds minio binary with hotfix tags
|
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
|
||||||
@mv -f ./minio ./minio.$(VERSION)
|
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.0/pkger_2.2.0_linux_amd64.deb
|
||||||
@minisign -qQSm ./minio.$(VERSION) -s "${CRED_DIR}/minisign.key" < "${CRED_DIR}/minisign-passphrase"
|
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.0/linux-systemd/distributed/minio.service
|
||||||
@sha256sum < ./minio.$(VERSION) | sed 's, -,minio.$(VERSION),g' > minio.$(VERSION).sha256sum
|
@sudo apt install ./pkger_2.2.0_linux_amd64.deb --yes
|
||||||
|
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
|
||||||
|
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
|
||||||
|
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
|
||||||
|
@minisign -qQSm minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION) -s "${CRED_DIR}/minisign.key" < "${CRED_DIR}/minisign-passphrase"
|
||||||
|
@sha256sum < minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION) | sed 's, -,minio.$(VERSION),g' > minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION).sha256sum
|
||||||
|
@cp -af minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)* minio-release/$(GOOS)-$(GOARCH)/archive/
|
||||||
|
@pkger -r $(VERSION) --ignore
|
||||||
|
|
||||||
hotfix-push: hotfix
|
hotfix-push: hotfix
|
||||||
@scp -q -r minio.$(VERSION)* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-amd64/archive/
|
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-amd64/
|
||||||
@scp -q -r minio.$(VERSION)* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-amd64/archive/
|
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-amd64/archive
|
||||||
|
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-amd64/
|
||||||
|
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-amd64/archive
|
||||||
@echo "Published new hotfix binaries at https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.$(VERSION)"
|
@echo "Published new hotfix binaries at https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.$(VERSION)"
|
||||||
|
|
||||||
docker-hotfix-push: docker-hotfix
|
docker-hotfix-push: docker-hotfix
|
||||||
@@ -183,3 +198,6 @@ clean: ## cleanup all generated assets
|
|||||||
@rm -rvf build
|
@rm -rvf build
|
||||||
@rm -rvf release
|
@rm -rvf release
|
||||||
@rm -rvf .verify*
|
@rm -rvf .verify*
|
||||||
|
@rm -rvf minio-release
|
||||||
|
@rm -rvf minio.RELEASE*.hotfix.*
|
||||||
|
@rm -rvf pkger_*.deb
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function _init() {
|
|||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
## List of architectures and OS to test coss compilation.
|
## List of architectures and OS to test coss compilation.
|
||||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips"
|
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
||||||
}
|
}
|
||||||
|
|
||||||
function _build() {
|
function _build() {
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ function verify_rewrite() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
go build ./docs/debugging/s3-check-md5/
|
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||||
if ! ./s3-check-md5 \
|
if ! s3-check-md5 \
|
||||||
-debug \
|
-debug \
|
||||||
-versions \
|
-versions \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
@@ -113,7 +113,7 @@ function verify_rewrite() {
|
|||||||
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
if ! ./s3-check-md5 \
|
if ! s3-check-md5 \
|
||||||
-debug \
|
-debug \
|
||||||
-versions \
|
-versions \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ function start_minio_16drive() {
|
|||||||
rm -rf "${WORK_DIR}/xl3/healing-shard-bucket/unaligned"
|
rm -rf "${WORK_DIR}/xl3/healing-shard-bucket/unaligned"
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
go build ./docs/debugging/s3-check-md5/
|
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||||
if ! ./s3-check-md5 \
|
if ! s3-check-md5 \
|
||||||
-debug \
|
-debug \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
-secret-key minio123 \
|
-secret-key minio123 \
|
||||||
@@ -111,7 +111,7 @@ function start_minio_16drive() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! ./s3-check-md5 \
|
if ! s3-check-md5 \
|
||||||
-debug \
|
-debug \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
-secret-key minio123 \
|
-secret-key minio123 \
|
||||||
@@ -134,7 +134,7 @@ function start_minio_16drive() {
|
|||||||
|
|
||||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||||
|
|
||||||
if ! ./s3-check-md5 \
|
if ! s3-check-md5 \
|
||||||
-debug \
|
-debug \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
-secret-key minio123 \
|
-secret-key minio123 \
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ x-minio-common: &minio-common
|
|||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
- "9001"
|
- "9001"
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 20s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
# starts 4 docker containers running minio server instances.
|
# starts 4 docker containers running minio server instances.
|
||||||
# using nginx reverse proxy, load balancing, you can access
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
Quota: data,
|
Quota: data,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}
|
}
|
||||||
if quotaConfig.Size == 0 || quotaConfig.Quota == 0 {
|
if quotaConfig.Size == 0 && quotaConfig.Quota == 0 {
|
||||||
bucketMeta.Quota = nil
|
bucketMeta.Quota = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +392,8 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
// of bucket metadata
|
// of bucket metadata
|
||||||
zipWriter := zip.NewWriter(w)
|
zipWriter := zip.NewWriter(w)
|
||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
rawDataFn := func(r io.Reader, filename string, sz int) error {
|
|
||||||
|
rawDataFn := func(r io.Reader, filename string, sz int) {
|
||||||
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
||||||
name: filename,
|
name: filename,
|
||||||
size: int64(sz),
|
size: int64(sz),
|
||||||
@@ -401,20 +402,13 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
isDir: false,
|
isDir: false,
|
||||||
sys: nil,
|
sys: nil,
|
||||||
})
|
})
|
||||||
if zerr != nil {
|
if zerr == nil {
|
||||||
logger.LogIf(ctx, zerr)
|
header.Method = zip.Deflate
|
||||||
return nil
|
zwriter, zerr := zipWriter.CreateHeader(header)
|
||||||
|
if zerr == nil {
|
||||||
|
io.Copy(zwriter, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
header.Method = zip.Deflate
|
|
||||||
zwriter, zerr := zipWriter.CreateHeader(header)
|
|
||||||
if zerr != nil {
|
|
||||||
logger.LogIf(ctx, zerr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(zwriter, r); err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgFiles := []string{
|
cfgFiles := []string{
|
||||||
@@ -446,10 +440,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketLifecycleConfig:
|
case bucketLifecycleConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -465,10 +456,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketQuotaConfigFile:
|
case bucketQuotaConfigFile:
|
||||||
config, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket)
|
config, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -483,10 +471,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketSSEConfig:
|
case bucketSSEConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -501,10 +486,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketTaggingConfig:
|
case bucketTaggingConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetTaggingConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetTaggingConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -519,10 +501,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case objectLockConfig:
|
case objectLockConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -538,10 +517,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketVersioningConfig:
|
case bucketVersioningConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetVersioningConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetVersioningConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -557,10 +533,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketReplicationConfig:
|
case bucketReplicationConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -575,11 +548,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketTargetsFile:
|
case bucketTargetsFile:
|
||||||
config, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket)
|
config, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -595,10 +564,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/cache"
|
|
||||||
"github.com/minio/minio/internal/config/etcd"
|
"github.com/minio/minio/internal/config/etcd"
|
||||||
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
@@ -500,8 +499,6 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
switch hkv.Key {
|
switch hkv.Key {
|
||||||
case config.EtcdSubSys:
|
case config.EtcdSubSys:
|
||||||
off = !etcd.Enabled(item.Config)
|
off = !etcd.Enabled(item.Config)
|
||||||
case config.CacheSubSys:
|
|
||||||
off = !cache.Enabled(item.Config)
|
|
||||||
case config.StorageClassSubSys:
|
case config.StorageClassSubSys:
|
||||||
off = !storageclass.Enabled(item.Config)
|
off = !storageclass.Enabled(item.Config)
|
||||||
case config.PolicyPluginSubSys:
|
case config.PolicyPluginSubSys:
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
// IDP config is not dynamic. Sanity check.
|
// IDP config is not dynamic. Sanity check.
|
||||||
if dynamic {
|
if dynamic {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), "", r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
@@ -175,3 +179,267 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
writeSuccessResponseJSON(w, encryptedData)
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddServiceAccountLDAP adds a new service account for provided LDAP username or DN
|
||||||
|
//
|
||||||
|
// PUT /minio/admin/v3/idp/ldap/add-service-account
|
||||||
|
func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cred, opts, createReq, targetUser, APIError := commonAddServiceAccount(r)
|
||||||
|
if APIError.Code != "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, APIError, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail if ldap is not enabled
|
||||||
|
if !globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errors.New("LDAP not enabled")), r.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the user for the request sender (as it may be sent via a service
|
||||||
|
// account or STS account):
|
||||||
|
requestorUser := cred.AccessKey
|
||||||
|
requestorParentUser := cred.AccessKey
|
||||||
|
requestorGroups := cred.Groups
|
||||||
|
requestorIsDerivedCredential := false
|
||||||
|
if cred.IsServiceAccount() || cred.IsTemp() {
|
||||||
|
requestorParentUser = cred.ParentUser
|
||||||
|
requestorIsDerivedCredential = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are creating svc account for request sender.
|
||||||
|
isSvcAccForRequestor := false
|
||||||
|
if targetUser == requestorUser || targetUser == requestorParentUser {
|
||||||
|
isSvcAccForRequestor = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
targetGroups []string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// If we are creating svc account for request sender, ensure
|
||||||
|
// that targetUser is a real user (i.e. not derived
|
||||||
|
// credentials).
|
||||||
|
if isSvcAccForRequestor {
|
||||||
|
if requestorIsDerivedCredential {
|
||||||
|
if requestorParentUser == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
||||||
|
errors.New("service accounts cannot be generated for temporary credentials without parent")), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetUser = requestorParentUser
|
||||||
|
}
|
||||||
|
targetGroups = requestorGroups
|
||||||
|
|
||||||
|
// Deny if the target user is not LDAP
|
||||||
|
isLDAP, err := globalIAMSys.LDAPConfig.DoesUsernameExist(targetUser)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isLDAP == "" {
|
||||||
|
err := errors.New("Specified user does not exist on LDAP server")
|
||||||
|
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of LDAP/OIDC we need to set `opts.claims` to ensure
|
||||||
|
// it is associated with the LDAP/OIDC user properly.
|
||||||
|
for k, v := range cred.Claims {
|
||||||
|
if k == expClaim {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.claims[k] = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isDN := globalIAMSys.LDAPConfig.IsLDAPUserDN(targetUser)
|
||||||
|
|
||||||
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
|
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
|
if err != nil {
|
||||||
|
// if not found, check if DN
|
||||||
|
if strings.Contains(err.Error(), "not found") && isDN {
|
||||||
|
// warn user that DNs are not allowed
|
||||||
|
err = fmt.Errorf("Must use short username to add service account. %w", err)
|
||||||
|
}
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.claims[ldapUser] = targetUser // DN
|
||||||
|
}
|
||||||
|
|
||||||
|
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createResp := madmin.AddServiceAccountResp{
|
||||||
|
Credentials: madmin.Credentials{
|
||||||
|
AccessKey: newCred.AccessKey,
|
||||||
|
SecretKey: newCred.SecretKey,
|
||||||
|
Expiration: newCred.Expiration,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(createResp)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
|
|
||||||
|
// Call hook for cluster-replication if the service account is not for a
|
||||||
|
// root user.
|
||||||
|
if newCred.ParentUser != globalActiveCred.AccessKey {
|
||||||
|
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
|
Type: madmin.SRIAMItemSvcAcc,
|
||||||
|
SvcAccChange: &madmin.SRSvcAccChange{
|
||||||
|
Create: &madmin.SRSvcAccCreate{
|
||||||
|
Parent: newCred.ParentUser,
|
||||||
|
AccessKey: newCred.AccessKey,
|
||||||
|
SecretKey: newCred.SecretKey,
|
||||||
|
Groups: newCred.Groups,
|
||||||
|
Name: newCred.Name,
|
||||||
|
Description: newCred.Description,
|
||||||
|
Claims: opts.claims,
|
||||||
|
SessionPolicy: createReq.Policy,
|
||||||
|
Status: auth.AccountOn,
|
||||||
|
Expiration: createReq.Expiration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccessKeysLDAP - GET /minio/admin/v3/idp/ldap/list-access-keys
|
||||||
|
func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userDN := r.Form.Get("userDN")
|
||||||
|
|
||||||
|
// If listing is requested for a specific user (who is not the request
|
||||||
|
// sender), check that the user has permissions.
|
||||||
|
if userDN != "" && userDN != cred.ParentUser {
|
||||||
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
DenyOnly: true,
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userDN = cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
userDN = cred.ParentUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetAccount, err := globalIAMSys.LDAPConfig.DoesUsernameExist(userDN)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
} else if userDN == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listType := r.Form.Get("listType")
|
||||||
|
if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
|
||||||
|
// default to both
|
||||||
|
listType = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceAccounts []auth.Credentials
|
||||||
|
var stsKeys []auth.Credentials
|
||||||
|
|
||||||
|
if listType == "" || listType == "sts-only" {
|
||||||
|
stsKeys, err = globalIAMSys.ListSTSAccounts(ctx, targetAccount)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if listType == "" || listType == "svcacc-only" {
|
||||||
|
serviceAccounts, err = globalIAMSys.ListServiceAccounts(ctx, targetAccount)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceAccountList []madmin.ServiceAccountInfo
|
||||||
|
var stsKeyList []madmin.ServiceAccountInfo
|
||||||
|
|
||||||
|
for _, svc := range serviceAccounts {
|
||||||
|
expiryTime := svc.Expiration
|
||||||
|
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
||||||
|
AccessKey: svc.AccessKey,
|
||||||
|
Expiration: &expiryTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, sts := range stsKeys {
|
||||||
|
expiryTime := sts.Expiration
|
||||||
|
stsKeyList = append(stsKeyList, madmin.ServiceAccountInfo{
|
||||||
|
AccessKey: sts.AccessKey,
|
||||||
|
Expiration: &expiryTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
listResp := madmin.ListAccessKeysLDAPResp{
|
||||||
|
ServiceAccounts: serviceAccountList,
|
||||||
|
STSKeys: stsKeyList,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(listResp)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -66,16 +67,28 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
v := vars["pool"]
|
v := vars["pool"]
|
||||||
|
byID := vars["by-id"] == "true"
|
||||||
|
|
||||||
pools := strings.Split(v, ",")
|
pools := strings.Split(v, ",")
|
||||||
poolIndices := make([]int, 0, len(pools))
|
poolIndices := make([]int, 0, len(pools))
|
||||||
|
|
||||||
for _, pool := range pools {
|
for _, pool := range pools {
|
||||||
idx := globalEndpoints.GetPoolIdx(pool)
|
var idx int
|
||||||
if idx == -1 {
|
if byID {
|
||||||
// We didn't find any matching pools, invalid input
|
var err error
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
idx, err = strconv.Atoi(pool)
|
||||||
return
|
if err != nil {
|
||||||
|
// We didn't find any matching pools, invalid input
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = globalEndpoints.GetPoolIdx(pool)
|
||||||
|
if idx == -1 {
|
||||||
|
// We didn't find any matching pools, invalid input
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var pool *erasureSets
|
var pool *erasureSets
|
||||||
for pidx := range z.serverPools {
|
for pidx := range z.serverPools {
|
||||||
@@ -132,8 +145,17 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
v := vars["pool"]
|
v := vars["pool"]
|
||||||
|
byID := vars["by-id"] == "true"
|
||||||
|
idx := -1
|
||||||
|
|
||||||
|
if byID {
|
||||||
|
if i, err := strconv.Atoi(v); err == nil && i >= 0 && i < len(globalEndpoints) {
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = globalEndpoints.GetPoolIdx(v)
|
||||||
|
}
|
||||||
|
|
||||||
idx := globalEndpoints.GetPoolIdx(v)
|
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
// We didn't find any matching pools, invalid input
|
// We didn't find any matching pools, invalid input
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||||
@@ -178,8 +200,17 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
v := vars["pool"]
|
v := vars["pool"]
|
||||||
|
byID := vars["by-id"] == "true"
|
||||||
|
idx := -1
|
||||||
|
|
||||||
|
if byID {
|
||||||
|
if i, err := strconv.Atoi(v); err == nil && i >= 0 && i < len(globalEndpoints) {
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = globalEndpoints.GetPoolIdx(v)
|
||||||
|
}
|
||||||
|
|
||||||
idx := globalEndpoints.GetPoolIdx(v)
|
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
apiErr := toAdminAPIErr(ctx, errInvalidArgument)
|
apiErr := toAdminAPIErr(ctx, errInvalidArgument)
|
||||||
apiErr.Description = fmt.Sprintf("specified pool '%s' not found, please specify a valid pool", v)
|
apiErr.Description = fmt.Sprintf("specified pool '%s' not found, please specify a valid pool", v)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
@@ -51,7 +52,8 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := globalSiteReplicationSys.AddPeerClusters(ctx, sites)
|
opts := getSRAddOptions(r)
|
||||||
|
status, err := globalSiteReplicationSys.AddPeerClusters(ctx, sites, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -67,6 +69,11 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
|
|||||||
writeSuccessResponseJSON(w, body)
|
writeSuccessResponseJSON(w, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSRAddOptions(r *http.Request) (opts madmin.SRAddOptions) {
|
||||||
|
opts.ReplicateILMExpiry = r.Form.Get("replicateILMExpiry") == "true"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SRPeerJoin - PUT /minio/admin/v3/site-replication/join
|
// SRPeerJoin - PUT /minio/admin/v3/site-replication/join
|
||||||
//
|
//
|
||||||
// used internally to tell current cluster to enable SR with
|
// used internally to tell current cluster to enable SR with
|
||||||
@@ -191,7 +198,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/bucket-meta
|
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/peer/bucket-meta
|
||||||
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
@@ -252,6 +259,8 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
err = globalSiteReplicationSys.PeerBucketObjectLockConfigHandler(ctx, item.Bucket, item.ObjectLockConfig, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerBucketObjectLockConfigHandler(ctx, item.Bucket, item.ObjectLockConfig, item.UpdatedAt)
|
||||||
case madmin.SRBucketMetaTypeSSEConfig:
|
case madmin.SRBucketMetaTypeSSEConfig:
|
||||||
err = globalSiteReplicationSys.PeerBucketSSEConfigHandler(ctx, item.Bucket, item.SSEConfig, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerBucketSSEConfigHandler(ctx, item.Bucket, item.SSEConfig, item.UpdatedAt)
|
||||||
|
case madmin.SRBucketMetaLCConfig:
|
||||||
|
err = globalSiteReplicationSys.PeerBucketLCConfigHandler(ctx, item.Bucket, item.ExpiryLCConfig, item.UpdatedAt)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -333,6 +342,7 @@ func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.R
|
|||||||
opts.Users = true
|
opts.Users = true
|
||||||
opts.Policies = true
|
opts.Policies = true
|
||||||
opts.Groups = true
|
opts.Groups = true
|
||||||
|
opts.ILMExpiryRules = true
|
||||||
}
|
}
|
||||||
info, err := globalSiteReplicationSys.SiteReplicationStatus(ctx, objectAPI, opts)
|
info, err := globalSiteReplicationSys.SiteReplicationStatus(ctx, objectAPI, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -382,7 +392,9 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
status, err := globalSiteReplicationSys.EditPeerCluster(ctx, site)
|
|
||||||
|
opts := getSREditOptions(r)
|
||||||
|
status, err := globalSiteReplicationSys.EditPeerCluster(ctx, site, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -397,6 +409,12 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
|
|||||||
writeSuccessResponseJSON(w, body)
|
writeSuccessResponseJSON(w, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSREditOptions(r *http.Request) (opts madmin.SREditOptions) {
|
||||||
|
opts.DisableILMExpiryReplication = r.Form.Get("disableILMExpiryReplication") == "true"
|
||||||
|
opts.EnableILMExpiryReplication = r.Form.Get("enableILMExpiryReplication") == "true"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SRPeerEdit - PUT /minio/admin/v3/site-replication/peer/edit
|
// SRPeerEdit - PUT /minio/admin/v3/site-replication/peer/edit
|
||||||
//
|
//
|
||||||
// used internally to tell current cluster to update endpoint for peer
|
// used internally to tell current cluster to update endpoint for peer
|
||||||
@@ -421,12 +439,37 @@ func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SRStateEdit - PUT /minio/admin/v3/site-replication/state/edit
|
||||||
|
//
|
||||||
|
// used internally to tell current cluster to update site replication state
|
||||||
|
func (a adminAPIHandlers) SRStateEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var state madmin.SRStateEditReq
|
||||||
|
if err := parseJSONBody(ctx, r.Body, &state, ""); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := globalSiteReplicationSys.PeerStateEditReq(ctx, state); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
|
func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
|
||||||
q := r.Form
|
q := r.Form
|
||||||
opts.Buckets = q.Get("buckets") == "true"
|
opts.Buckets = q.Get("buckets") == "true"
|
||||||
opts.Policies = q.Get("policies") == "true"
|
opts.Policies = q.Get("policies") == "true"
|
||||||
opts.Groups = q.Get("groups") == "true"
|
opts.Groups = q.Get("groups") == "true"
|
||||||
opts.Users = q.Get("users") == "true"
|
opts.Users = q.Get("users") == "true"
|
||||||
|
opts.ILMExpiryRules = q.Get("ilm-expiry-rules") == "true"
|
||||||
|
opts.PeerState = q.Get("peer-state") == "true"
|
||||||
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
||||||
opts.EntityValue = q.Get("entityvalue")
|
opts.EntityValue = q.Get("entityvalue")
|
||||||
opts.ShowDeleted = q.Get("showDeleted") == "true"
|
opts.ShowDeleted = q.Get("showDeleted") == "true"
|
||||||
@@ -537,7 +580,7 @@ func (a adminAPIHandlers) SiteReplicationDevNull(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
connectTime := time.Now()
|
connectTime := time.Now()
|
||||||
for {
|
for {
|
||||||
n, err := io.CopyN(io.Discard, r.Body, 128*humanize.KiByte)
|
n, err := io.CopyN(xioutil.Discard, r.Body, 128*humanize.KiByte)
|
||||||
atomic.AddUint64(&globalSiteNetPerfRX.RX, uint64(n))
|
atomic.AddUint64(&globalSiteNetPerfRX.RX, uint64(n))
|
||||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
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)
|
// If there is a disconnection before globalNetPerfMinDuration (we give a margin of error of 1 sec)
|
||||||
|
|||||||
+119
-118
@@ -545,14 +545,16 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
args := policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.ListTemporaryAccountsAdminAction,
|
Action: policy.ListTemporaryAccountsAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
if !globalIAMSys.IsAllowed(args) {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -568,11 +570,16 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
if sessionPolicy != nil {
|
if sessionPolicy != nil {
|
||||||
stsAccountPolicy = *sessionPolicy
|
stsAccountPolicy = *sessionPolicy
|
||||||
} else {
|
} else {
|
||||||
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false)
|
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, stsAccount.Groups...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(policiesNames) == 0 {
|
||||||
|
policySet, _ := args.GetPolicies(iamPolicyClaimNameOpenID())
|
||||||
|
policiesNames = policySet.ToSlice()
|
||||||
|
}
|
||||||
|
|
||||||
stsAccountPolicy = globalIAMSys.GetCombinedPolicy(policiesNames...)
|
stsAccountPolicy = globalIAMSys.GetCombinedPolicy(policiesNames...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,72 +614,17 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||||
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx, cred, opts, createReq, targetUser, APIError := commonAddServiceAccount(r)
|
||||||
|
if APIError.Code != "" {
|
||||||
// Get current object layer instance.
|
writeErrorResponseJSON(ctx, w, APIError, r.URL)
|
||||||
objectAPI := newObjectLayerFn()
|
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cred, owner, 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, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var createReq madmin.AddServiceAccountReq
|
|
||||||
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// service account access key cannot have space characters beginning and end of the string.
|
|
||||||
if hasSpaceBE(createReq.AccessKey) {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
targetUser string
|
|
||||||
targetGroups []string
|
targetGroups []string
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
// If the request did not set a TargetUser, the service account is
|
|
||||||
// created for the request sender.
|
|
||||||
targetUser = createReq.TargetUser
|
|
||||||
if targetUser == "" {
|
|
||||||
targetUser = cred.AccessKey
|
|
||||||
}
|
|
||||||
|
|
||||||
description := createReq.Description
|
|
||||||
if description == "" {
|
|
||||||
description = createReq.Comment
|
|
||||||
}
|
|
||||||
opts := newServiceAccountOpts{
|
|
||||||
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
|
// Find the user for the request sender (as it may be sent via a service
|
||||||
// account or STS account):
|
// account or STS account):
|
||||||
requestorUser := cred.AccessKey
|
requestorUser := cred.AccessKey
|
||||||
@@ -706,23 +658,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// that targetUser is a real user (i.e. not derived
|
// that targetUser is a real user (i.e. not derived
|
||||||
// credentials).
|
// credentials).
|
||||||
if isSvcAccForRequestor {
|
if isSvcAccForRequestor {
|
||||||
// Check if adding service account is explicitly denied.
|
|
||||||
//
|
|
||||||
// This allows turning off service accounts for request sender,
|
|
||||||
// if there is no deny statement this call is implicitly enabled.
|
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
|
||||||
AccountName: requestorUser,
|
|
||||||
Groups: requestorGroups,
|
|
||||||
Action: policy.CreateServiceAccountAdminAction,
|
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
|
||||||
IsOwner: owner,
|
|
||||||
Claims: cred.Claims,
|
|
||||||
DenyOnly: true,
|
|
||||||
}) {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if requestorIsDerivedCredential {
|
if requestorIsDerivedCredential {
|
||||||
if requestorParentUser == "" {
|
if requestorParentUser == "" {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
||||||
@@ -741,32 +676,16 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
opts.claims[k] = v
|
opts.claims[k] = v
|
||||||
}
|
}
|
||||||
} else {
|
} else if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
// Need permission if we are creating a service account for a
|
|
||||||
// user <> to the request sender
|
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
|
||||||
AccountName: requestorUser,
|
|
||||||
Groups: requestorGroups,
|
|
||||||
Action: policy.CreateServiceAccountAdminAction,
|
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
|
||||||
IsOwner: owner,
|
|
||||||
Claims: cred.Claims,
|
|
||||||
}) {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||||
// query their groups:
|
// query their groups:
|
||||||
if globalIAMSys.LDAPConfig.Enabled() {
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
opts.claims[ldapUserN] = targetUser // simple username
|
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
if err != nil {
|
||||||
if err != nil {
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
opts.claims[ldapUser] = targetUser // username DN
|
|
||||||
}
|
}
|
||||||
|
opts.claims[ldapUser] = targetUser // username DN
|
||||||
|
|
||||||
// NOTE: if not using LDAP, then internal IDP or open ID is
|
// NOTE: if not using LDAP, then internal IDP or open ID is
|
||||||
// being used - in the former, group info is enforced when
|
// being used - in the former, group info is enforced when
|
||||||
@@ -774,16 +693,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// latter, a group notion is not supported.
|
// latter, a group notion is not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
var sp *policy.Policy
|
|
||||||
if len(createReq.Policy) > 0 {
|
|
||||||
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.sessionPolicy = sp
|
|
||||||
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -804,7 +713,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedData, err := madmin.EncryptData(password, data)
|
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -911,6 +820,9 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if sp.Version == "" && len(sp.Statements) == 0 {
|
||||||
|
sp = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
opts := updateServiceAccountOpts{
|
opts := updateServiceAccountOpts{
|
||||||
secretKey: updateReq.NewSecretKey,
|
secretKey: updateReq.NewSecretKey,
|
||||||
@@ -996,12 +908,15 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if session policy is nil or empty, then it is implied policy
|
||||||
|
impliedPolicy := sessionPolicy == nil || (sessionPolicy.Version == "" && len(sessionPolicy.Statements) == 0)
|
||||||
|
|
||||||
var svcAccountPolicy policy.Policy
|
var svcAccountPolicy policy.Policy
|
||||||
|
|
||||||
if sessionPolicy != nil {
|
if !impliedPolicy {
|
||||||
svcAccountPolicy = *sessionPolicy
|
svcAccountPolicy = *sessionPolicy
|
||||||
} else {
|
} else {
|
||||||
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false)
|
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, svcAccount.Groups...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1025,7 +940,7 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
|||||||
Name: svcAccount.Name,
|
Name: svcAccount.Name,
|
||||||
Description: svcAccount.Description,
|
Description: svcAccount.Description,
|
||||||
AccountStatus: svcAccount.Status,
|
AccountStatus: svcAccount.Status,
|
||||||
ImpliedPolicy: sessionPolicy == nil,
|
ImpliedPolicy: impliedPolicy,
|
||||||
Policy: string(policyJSON),
|
Policy: string(policyJSON),
|
||||||
Expiration: expiration,
|
Expiration: expiration,
|
||||||
}
|
}
|
||||||
@@ -1147,7 +1062,11 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
// We do not care if service account is readable or not at this point,
|
// We do not care if service account is readable or not at this point,
|
||||||
// since this is a delete call we shall allow it to be deleted if possible.
|
// since this is a delete call we shall allow it to be deleted if possible.
|
||||||
svcAccount, _, _ := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
||||||
|
if errors.Is(err, errNoSuchServiceAccount) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminServiceAccountNotFound), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
adminPrivilege := globalIAMSys.IsAllowed(policy.Args{
|
adminPrivilege := globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
@@ -1344,7 +1263,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
effectivePolicy = globalIAMSys.GetCombinedPolicy(policySetFromClaims.ToSlice()...)
|
effectivePolicy = globalIAMSys.GetCombinedPolicy(policySetFromClaims.ToSlice()...)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
policies, err := globalIAMSys.PolicyDBGet(accountName, false, cred.Groups...)
|
policies, err := globalIAMSys.PolicyDBGet(accountName, cred.Groups...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -2494,3 +2413,85 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrServerNotInitialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(s3Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var createReq madmin.AddServiceAccountReq
|
||||||
|
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// service account access key cannot have space characters beginning and end of the string.
|
||||||
|
if hasSpaceBE(createReq.AccessKey) {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createReq.Validate(); err != nil {
|
||||||
|
// Since this validation would happen client side as well, we only send
|
||||||
|
// a generic error message here.
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||||
|
}
|
||||||
|
// If the request did not set a TargetUser, the service account is
|
||||||
|
// created for the request sender.
|
||||||
|
targetUser := createReq.TargetUser
|
||||||
|
if targetUser == "" {
|
||||||
|
targetUser = cred.AccessKey
|
||||||
|
}
|
||||||
|
|
||||||
|
description := createReq.Description
|
||||||
|
if description == "" {
|
||||||
|
description = createReq.Comment
|
||||||
|
}
|
||||||
|
opts := newServiceAccountOpts{
|
||||||
|
accessKey: createReq.AccessKey,
|
||||||
|
secretKey: createReq.SecretKey,
|
||||||
|
name: createReq.Name,
|
||||||
|
description: description,
|
||||||
|
expiration: createReq.Expiration,
|
||||||
|
claims: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if action is allowed if creating access key for another user
|
||||||
|
// Check if action is explicitly denied if for self
|
||||||
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
DenyOnly: (targetUser == cred.AccessKey || targetUser == cred.ParentUser),
|
||||||
|
}) {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAccessDenied)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sp *policy.Policy
|
||||||
|
if len(createReq.Policy) > 0 {
|
||||||
|
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
||||||
|
if err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.sessionPolicy = sp
|
||||||
|
|
||||||
|
return ctx, cred, opts, createReq, targetUser, APIError{}
|
||||||
|
}
|
||||||
|
|||||||
+82
-21
@@ -52,6 +52,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/dsync"
|
"github.com/minio/minio/internal/dsync"
|
||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
@@ -59,11 +60,14 @@ import (
|
|||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/secure-io/sio-go"
|
"github.com/secure-io/sio-go"
|
||||||
|
"github.com/zeebo/xxh3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxEConfigJSONSize = 262272
|
maxEConfigJSONSize = 262272
|
||||||
kubernetesVersionEndpoint = "https://kubernetes.default.svc/version"
|
kubernetesVersionEndpoint = "https://kubernetes.default.svc/version"
|
||||||
|
anonymizeParam = "anonymize"
|
||||||
|
anonymizeStrict = "strict"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Only valid query params for mgmt admin APIs.
|
// Only valid query params for mgmt admin APIs.
|
||||||
@@ -336,7 +340,7 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
storageInfo := objectAPI.StorageInfo(ctx)
|
storageInfo := objectAPI.StorageInfo(ctx, true)
|
||||||
|
|
||||||
// Collect any disk healing.
|
// Collect any disk healing.
|
||||||
healing, _ := getAggregatedBackgroundHealState(ctx, nil)
|
healing, _ := getAggregatedBackgroundHealState(ctx, nil)
|
||||||
@@ -754,11 +758,8 @@ func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// read request body
|
|
||||||
io.CopyN(io.Discard, r.Body, 1)
|
|
||||||
|
|
||||||
globalProfilerMu.Lock()
|
globalProfilerMu.Lock()
|
||||||
|
|
||||||
if globalProfiler == nil {
|
if globalProfiler == nil {
|
||||||
globalProfiler = make(map[string]minioProfiler, 10)
|
globalProfiler = make(map[string]minioProfiler, 10)
|
||||||
}
|
}
|
||||||
@@ -1001,7 +1002,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Message: hr.errBody,
|
Message: hr.errBody,
|
||||||
Resource: r.URL.Path,
|
Resource: r.URL.Path,
|
||||||
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
HostID: globalDeploymentID,
|
HostID: globalDeploymentID(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if !started {
|
if !started {
|
||||||
@@ -1056,7 +1057,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
respCh := make(chan healResp)
|
respCh := make(chan healResp, 1)
|
||||||
switch {
|
switch {
|
||||||
case hip.forceStop:
|
case hip.forceStop:
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1218,7 +1219,7 @@ func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request)
|
|||||||
totalRx := int64(0)
|
totalRx := int64(0)
|
||||||
connectTime := time.Now()
|
connectTime := time.Now()
|
||||||
for {
|
for {
|
||||||
n, err := io.CopyN(io.Discard, r.Body, 128*humanize.KiByte)
|
n, err := io.CopyN(xioutil.Discard, r.Body, 128*humanize.KiByte)
|
||||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
// would mean the network is not stable. Logging here will help in debugging network issues.
|
// would mean the network is not stable. Logging here will help in debugging network issues.
|
||||||
if time.Since(connectTime) < (globalNetPerfMinDuration - time.Second) {
|
if time.Since(connectTime) < (globalNetPerfMinDuration - time.Second) {
|
||||||
@@ -1296,6 +1297,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
|||||||
customBucket := strings.TrimSpace(r.Form.Get(peerRESTBucket))
|
customBucket := strings.TrimSpace(r.Form.Get(peerRESTBucket))
|
||||||
autotune := r.Form.Get("autotune") == "true"
|
autotune := r.Form.Get("autotune") == "true"
|
||||||
noClear := r.Form.Get("noclear") == "true"
|
noClear := r.Form.Get("noclear") == "true"
|
||||||
|
enableSha256 := r.Form.Get("enableSha256") == "true"
|
||||||
|
|
||||||
size, err := strconv.Atoi(sizeStr)
|
size, err := strconv.Atoi(sizeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1312,7 +1314,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
|||||||
duration = time.Second * 10
|
duration = time.Second * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
storageInfo := objectAPI.StorageInfo(ctx)
|
storageInfo := objectAPI.StorageInfo(ctx, true)
|
||||||
|
|
||||||
sufficientCapacity, canAutotune, capacityErrMsg := validateObjPerfOptions(ctx, storageInfo, concurrent, size, autotune)
|
sufficientCapacity, canAutotune, capacityErrMsg := validateObjPerfOptions(ctx, storageInfo, concurrent, size, autotune)
|
||||||
if !sufficientCapacity {
|
if !sufficientCapacity {
|
||||||
@@ -1365,6 +1367,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
|||||||
autotune: autotune,
|
autotune: autotune,
|
||||||
storageClass: storageClass,
|
storageClass: storageClass,
|
||||||
bucketName: customBucket,
|
bucketName: customBucket,
|
||||||
|
enableSha256: enableSha256,
|
||||||
})
|
})
|
||||||
var prevResult madmin.SpeedTestResult
|
var prevResult madmin.SpeedTestResult
|
||||||
for {
|
for {
|
||||||
@@ -1400,13 +1403,21 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeObjectPerfBucket(ctx context.Context, objectAPI ObjectLayer, bucketName string) (bucketExists bool, err error) {
|
func makeObjectPerfBucket(ctx context.Context, objectAPI ObjectLayer, bucketName string) (bucketExists bool, err error) {
|
||||||
if err = objectAPI.MakeBucket(ctx, bucketName, MakeBucketOptions{}); err != nil {
|
if err = objectAPI.MakeBucket(ctx, bucketName, MakeBucketOptions{VersioningEnabled: globalSiteReplicationSys.isEnabled()}); err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
// Only BucketExists error can be ignored.
|
// Only BucketExists error can be ignored.
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
bucketExists = true
|
bucketExists = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalSiteReplicationSys.isEnabled() {
|
||||||
|
configData := []byte(`<VersioningConfiguration><Status>Enabled</Status><ExcludedPrefixes><Prefix>speedtest/*</Prefix></ExcludedPrefixes></VersioningConfiguration>`)
|
||||||
|
if _, err = globalBucketMetadataSys.Update(ctx, bucketName, bucketVersioningConfig, configData); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return bucketExists, nil
|
return bucketExists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1581,7 +1592,9 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
|
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
|
||||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||||
traceCh := make(chan madmin.TraceInfo, 4000)
|
|
||||||
|
// Keep 100k buffered channel, should be sufficient to ensure we do not lose any events.
|
||||||
|
traceCh := make(chan madmin.TraceInfo, 100000)
|
||||||
|
|
||||||
peers, _ := newPeerRestClients(globalEndpoints)
|
peers, _ := newPeerRestClients(globalEndpoints)
|
||||||
|
|
||||||
@@ -1605,7 +1618,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
peer.Trace(traceCh, ctx.Done(), traceOpts)
|
peer.Trace(traceCh, ctx.Done(), traceOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
keepAliveTicker := time.NewTicker(time.Second)
|
||||||
defer keepAliveTicker.Stop()
|
defer keepAliveTicker.Stop()
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
@@ -1900,7 +1913,7 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log, audit := fetchLoggerInfo()
|
log, audit := fetchLoggerInfo(ctx)
|
||||||
|
|
||||||
// Get the notification target info
|
// Get the notification target info
|
||||||
notifyTarget := fetchLambdaInfo()
|
notifyTarget := fetchLambdaInfo()
|
||||||
@@ -1980,7 +1993,7 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
Region: globalSite.Region,
|
Region: globalSite.Region,
|
||||||
SQSARN: globalEventNotifier.GetARNList(false),
|
SQSARN: globalEventNotifier.GetARNList(false),
|
||||||
DeploymentID: globalDeploymentID,
|
DeploymentID: globalDeploymentID(),
|
||||||
Buckets: buckets,
|
Buckets: buckets,
|
||||||
Objects: objects,
|
Objects: objects,
|
||||||
Versions: versions,
|
Versions: versions,
|
||||||
@@ -2025,8 +2038,14 @@ func getKubernetesInfo(dctx context.Context) madmin.KubernetesInfo {
|
|||||||
|
|
||||||
func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *url.Values, healthInfoCh chan madmin.HealthInfo, healthInfo madmin.HealthInfo) {
|
func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *url.Values, healthInfoCh chan madmin.HealthInfo, healthInfo madmin.HealthInfo) {
|
||||||
hostAnonymizer := createHostAnonymizer()
|
hostAnonymizer := createHostAnonymizer()
|
||||||
// anonAddr - Anonymizes hosts in given input string.
|
|
||||||
|
anonParam := query.Get(anonymizeParam)
|
||||||
|
// anonAddr - Anonymizes hosts in given input string
|
||||||
|
// (only if the anonymize param is set to srict).
|
||||||
anonAddr := func(addr string) string {
|
anonAddr := func(addr string) string {
|
||||||
|
if anonParam != anonymizeStrict {
|
||||||
|
return addr
|
||||||
|
}
|
||||||
newAddr, found := hostAnonymizer[addr]
|
newAddr, found := hostAnonymizer[addr]
|
||||||
if found {
|
if found {
|
||||||
return newAddr
|
return newAddr
|
||||||
@@ -2087,6 +2106,20 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAndWriteNetInfo := func() {
|
||||||
|
if query.Get(string(madmin.HealthDataTypeSysNet)) == "true" {
|
||||||
|
localNetInfo := madmin.GetNetInfo(globalLocalNodeName, globalInternodeInterface)
|
||||||
|
healthInfo.Sys.NetInfo = append(healthInfo.Sys.NetInfo, localNetInfo)
|
||||||
|
|
||||||
|
peerNetInfos := globalNotificationSys.GetNetInfo(healthCtx)
|
||||||
|
for _, n := range peerNetInfos {
|
||||||
|
anonymizeAddr(&n)
|
||||||
|
healthInfo.Sys.NetInfo = append(healthInfo.Sys.NetInfo, n)
|
||||||
|
}
|
||||||
|
partialWrite(healthInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getAndWriteOSInfo := func() {
|
getAndWriteOSInfo := func() {
|
||||||
if query.Get("sysosinfo") == "true" {
|
if query.Get("sysosinfo") == "true" {
|
||||||
localOSInfo := madmin.GetOSInfo(healthCtx, globalLocalNodeName)
|
localOSInfo := madmin.GetOSInfo(healthCtx, globalLocalNodeName)
|
||||||
@@ -2187,6 +2220,10 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
|||||||
}
|
}
|
||||||
|
|
||||||
anonymizeCmdLine := func(cmdLine string) string {
|
anonymizeCmdLine := func(cmdLine string) string {
|
||||||
|
if anonParam != anonymizeStrict {
|
||||||
|
return cmdLine
|
||||||
|
}
|
||||||
|
|
||||||
if !globalIsDistErasure {
|
if !globalIsDistErasure {
|
||||||
// FS mode - single server - hard code to `server1`
|
// FS mode - single server - hard code to `server1`
|
||||||
anonCmdLine := strings.ReplaceAll(cmdLine, globalLocalNodeName, "server1")
|
anonCmdLine := strings.ReplaceAll(cmdLine, globalLocalNodeName, "server1")
|
||||||
@@ -2308,6 +2345,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
|||||||
getAndWritePlatformInfo()
|
getAndWritePlatformInfo()
|
||||||
getAndWriteCPUs()
|
getAndWriteCPUs()
|
||||||
getAndWritePartitions()
|
getAndWritePartitions()
|
||||||
|
getAndWriteNetInfo()
|
||||||
getAndWriteOSInfo()
|
getAndWriteOSInfo()
|
||||||
getAndWriteMemInfo()
|
getAndWriteMemInfo()
|
||||||
getAndWriteProcInfo()
|
getAndWriteProcInfo()
|
||||||
@@ -2390,7 +2428,7 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
Version: madmin.HealthInfoVersion,
|
Version: madmin.HealthInfoVersion,
|
||||||
Minio: madmin.MinioHealthInfo{
|
Minio: madmin.MinioHealthInfo{
|
||||||
Info: madmin.MinioInfo{
|
Info: madmin.MinioInfo{
|
||||||
DeploymentID: globalDeploymentID,
|
DeploymentID: globalDeploymentID(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -2465,11 +2503,27 @@ func getTLSInfo() madmin.TLSInfo {
|
|||||||
|
|
||||||
if globalIsTLS {
|
if globalIsTLS {
|
||||||
for _, c := range globalPublicCerts {
|
for _, c := range globalPublicCerts {
|
||||||
|
check := xxh3.Hash(c.RawIssuer)
|
||||||
|
check ^= xxh3.Hash(c.RawSubjectPublicKeyInfo)
|
||||||
|
// We XOR, so order doesn't matter.
|
||||||
|
for _, v := range c.DNSNames {
|
||||||
|
check ^= xxh3.HashString(v)
|
||||||
|
}
|
||||||
|
for _, v := range c.EmailAddresses {
|
||||||
|
check ^= xxh3.HashString(v)
|
||||||
|
}
|
||||||
|
for _, v := range c.IPAddresses {
|
||||||
|
check ^= xxh3.HashString(v.String())
|
||||||
|
}
|
||||||
|
for _, v := range c.URIs {
|
||||||
|
check ^= xxh3.HashString(v.String())
|
||||||
|
}
|
||||||
tlsInfo.Certs = append(tlsInfo.Certs, madmin.TLSCert{
|
tlsInfo.Certs = append(tlsInfo.Certs, madmin.TLSCert{
|
||||||
PubKeyAlgo: c.PublicKeyAlgorithm.String(),
|
PubKeyAlgo: c.PublicKeyAlgorithm.String(),
|
||||||
SignatureAlgo: c.SignatureAlgorithm.String(),
|
SignatureAlgo: c.SignatureAlgorithm.String(),
|
||||||
NotBefore: c.NotBefore,
|
NotBefore: c.NotBefore,
|
||||||
NotAfter: c.NotAfter,
|
NotAfter: c.NotAfter,
|
||||||
|
Checksum: strconv.FormatUint(check, 16),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2520,7 +2574,7 @@ func assignPoolNumbers(servers []madmin.ServerProperties) {
|
|||||||
func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
||||||
lambdaMap := make(map[string][]madmin.TargetIDStatus)
|
lambdaMap := make(map[string][]madmin.TargetIDStatus)
|
||||||
|
|
||||||
for _, tgt := range globalNotifyTargetList.Targets() {
|
for _, tgt := range globalEventNotifier.Targets() {
|
||||||
targetIDStatus := make(map[string]madmin.Status)
|
targetIDStatus := make(map[string]madmin.Status)
|
||||||
active, _ := tgt.IsActive()
|
active, _ := tgt.IsActive()
|
||||||
targetID := tgt.ID()
|
targetID := tgt.ID()
|
||||||
@@ -2608,19 +2662,26 @@ func fetchKMSStatusV2(ctx context.Context) []madmin.KMS {
|
|||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func targetStatus(ctx context.Context, h logger.Target) madmin.Status {
|
||||||
|
if h.IsOnline(ctx) {
|
||||||
|
return madmin.Status{Status: string(madmin.ItemOnline)}
|
||||||
|
}
|
||||||
|
return madmin.Status{Status: string(madmin.ItemOffline)}
|
||||||
|
}
|
||||||
|
|
||||||
// fetchLoggerDetails return log info
|
// fetchLoggerDetails return log info
|
||||||
func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
func fetchLoggerInfo(ctx context.Context) ([]madmin.Logger, []madmin.Audit) {
|
||||||
var loggerInfo []madmin.Logger
|
var loggerInfo []madmin.Logger
|
||||||
var auditloggerInfo []madmin.Audit
|
var auditloggerInfo []madmin.Audit
|
||||||
for _, tgt := range logger.SystemTargets() {
|
for _, tgt := range logger.SystemTargets() {
|
||||||
if tgt.Endpoint() != "" {
|
if tgt.Endpoint() != "" {
|
||||||
loggerInfo = append(loggerInfo, madmin.Logger{tgt.String(): logger.TargetStatus(GlobalContext, tgt)})
|
loggerInfo = append(loggerInfo, madmin.Logger{tgt.String(): targetStatus(ctx, tgt)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tgt := range logger.AuditTargets() {
|
for _, tgt := range logger.AuditTargets() {
|
||||||
if tgt.Endpoint() != "" {
|
if tgt.Endpoint() != "" {
|
||||||
auditloggerInfo = append(auditloggerInfo, madmin.Audit{tgt.String(): logger.TargetStatus(GlobalContext, tgt)})
|
auditloggerInfo = append(auditloggerInfo, madmin.Audit{tgt.String(): targetStatus(ctx, tgt)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2674,7 +2735,7 @@ func getClusterMetaInfo(ctx context.Context) []byte {
|
|||||||
ci.Info.NoOfServers = len(globalEndpoints.Hostnames())
|
ci.Info.NoOfServers = len(globalEndpoints.Hostnames())
|
||||||
ci.Info.MinioVersion = Version
|
ci.Info.MinioVersion = Version
|
||||||
|
|
||||||
si := objectAPI.StorageInfo(ctx)
|
si := objectAPI.StorageInfo(ctx, true)
|
||||||
|
|
||||||
ci.Info.NoOfDrives = len(si.Disks)
|
ci.Info.NoOfDrives = len(si.Disks)
|
||||||
for _, disk := range si.Disks {
|
for _, disk := range si.Disks {
|
||||||
@@ -2688,7 +2749,7 @@ func getClusterMetaInfo(ctx context.Context) []byte {
|
|||||||
ci.Info.NoOfBuckets = dataUsageInfo.BucketsCount
|
ci.Info.NoOfBuckets = dataUsageInfo.BucketsCount
|
||||||
ci.Info.NoOfObjects = dataUsageInfo.ObjectsTotalCount
|
ci.Info.NoOfObjects = dataUsageInfo.ObjectsTotalCount
|
||||||
|
|
||||||
ci.DeploymentID = globalDeploymentID
|
ci.DeploymentID = globalDeploymentID()
|
||||||
ci.ClusterName = fmt.Sprintf("%d-servers-%d-disks-%s", ci.Info.NoOfServers, ci.Info.NoOfDrives, ci.Info.MinioVersion)
|
ci.ClusterName = fmt.Sprintf("%d-servers-%d-disks-%s", ci.Info.NoOfServers, ci.Info.NoOfDrives, ci.Info.MinioVersion)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -810,7 +810,7 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
|
|||||||
// NOTE: Healing on meta is run regardless
|
// NOTE: Healing on meta is run regardless
|
||||||
// of any bucket being selected, this is to ensure that
|
// of any bucket being selected, this is to ensure that
|
||||||
// meta are always upto date and correct.
|
// meta are always upto date and correct.
|
||||||
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string) error {
|
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string, scanMode madmin.HealScanMode) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
@@ -867,7 +867,7 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
|
|||||||
|
|
||||||
if !h.settings.Recursive {
|
if !h.settings.Recursive {
|
||||||
if h.object != "" {
|
if h.object != "" {
|
||||||
if err := h.healObject(bucket, h.object, ""); err != nil {
|
if err := h.healObject(bucket, h.object, "", h.settings.ScanMode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -882,7 +882,7 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
|
|||||||
}
|
}
|
||||||
|
|
||||||
// healObject - heal the given object and record result
|
// healObject - heal the given object and record result
|
||||||
func (h *healSequence) healObject(bucket, object, versionID string) error {
|
func (h *healSequence) healObject(bucket, object, versionID string, scanMode madmin.HealScanMode) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,6 +300,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.GetIdentityProviderCfg))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.GetIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
||||||
|
|
||||||
|
// LDAP specific service accounts ops
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp/ldap/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccountLDAP))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys").
|
||||||
|
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAP)).
|
||||||
|
Queries("userDN", "{userDN:.*}", "listType", "{listType:.*}")
|
||||||
|
|
||||||
// LDAP IAM operations
|
// LDAP IAM operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
||||||
@@ -376,6 +382,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(adminMiddleware(adminAPI.SRPeerEdit))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(adminMiddleware(adminAPI.SRPeerEdit))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(adminMiddleware(adminAPI.SRPeerRemove))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(adminMiddleware(adminAPI.SRPeerRemove))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationResyncOp)).Queries("operation", "{operation:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationResyncOp)).Queries("operation", "{operation:.*}")
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/state/edit").HandlerFunc(adminMiddleware(adminAPI.SRStateEdit))
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Top locks
|
// Top locks
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
|
|
||||||
objLayer := newObjectLayerFn()
|
objLayer := newObjectLayerFn()
|
||||||
if objLayer != nil {
|
if objLayer != nil {
|
||||||
storageInfo := objLayer.LocalStorageInfo(GlobalContext)
|
storageInfo := objLayer.LocalStorageInfo(GlobalContext, true)
|
||||||
props.State = string(madmin.ItemOnline)
|
props.State = string(madmin.ItemOnline)
|
||||||
props.Disks = storageInfo.Disks
|
props.Disks = storageInfo.Disks
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ const (
|
|||||||
ErrSiteReplicationBucketMetaError
|
ErrSiteReplicationBucketMetaError
|
||||||
ErrSiteReplicationIAMError
|
ErrSiteReplicationIAMError
|
||||||
ErrSiteReplicationConfigMissing
|
ErrSiteReplicationConfigMissing
|
||||||
|
ErrSiteReplicationIAMConfigMismatch
|
||||||
|
|
||||||
// Pool rebalance errors
|
// Pool rebalance errors
|
||||||
ErrAdminRebalanceAlreadyStarted
|
ErrAdminRebalanceAlreadyStarted
|
||||||
@@ -429,6 +430,9 @@ const (
|
|||||||
ErrLambdaARNInvalid
|
ErrLambdaARNInvalid
|
||||||
ErrLambdaARNNotFound
|
ErrLambdaARNNotFound
|
||||||
|
|
||||||
|
// New Codes for GetObjectAttributes and GetObjectVersionAttributes
|
||||||
|
ErrInvalidAttributeName
|
||||||
|
|
||||||
apiErrCodeEnd // This is used only for the testing code
|
apiErrCodeEnd // This is used only for the testing code
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1512,6 +1516,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Site not found in site replication configuration",
|
Description: "Site not found in site replication configuration",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrSiteReplicationIAMConfigMismatch: {
|
||||||
|
Code: "XMinioSiteReplicationIAMConfigMismatch",
|
||||||
|
Description: "IAM configuration mismatch between sites",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminRebalanceAlreadyStarted: {
|
ErrAdminRebalanceAlreadyStarted: {
|
||||||
Code: "XMinioAdminRebalanceAlreadyStarted",
|
Code: "XMinioAdminRebalanceAlreadyStarted",
|
||||||
Description: "Pool rebalance is already started",
|
Description: "Pool rebalance is already started",
|
||||||
@@ -2057,6 +2066,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The specified policy is not found.",
|
Description: "The specified policy is not found.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrInvalidAttributeName: {
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Invalid attribute name specified.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
// Add your error structure here.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2337,6 +2351,12 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
case dns.ErrBucketConflict:
|
case dns.ErrBucketConflict:
|
||||||
apiErr = ErrBucketAlreadyExists
|
apiErr = ErrBucketAlreadyExists
|
||||||
default:
|
default:
|
||||||
|
if _, ok := err.(tags.Error); ok {
|
||||||
|
// tag errors are not exported, so we check their custom interface to avoid logging.
|
||||||
|
// The correct type is inserted by toAPIError.
|
||||||
|
apiErr = ErrInternalError
|
||||||
|
break
|
||||||
|
}
|
||||||
var ie, iw int
|
var ie, iw int
|
||||||
// This work-around is to handle the issue golang/go#30648
|
// This work-around is to handle the issue golang/go#30648
|
||||||
//nolint:gocritic
|
//nolint:gocritic
|
||||||
|
|||||||
@@ -133,11 +133,6 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
w.Header().Set(xhttp.Expires, objInfo.Expires.UTC().Format(http.TimeFormat))
|
w.Header().Set(xhttp.Expires, objInfo.Expires.UTC().Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalCacheConfig.Enabled {
|
|
||||||
w.Header().Set(xhttp.XCache, objInfo.CacheStatus.String())
|
|
||||||
w.Header().Set(xhttp.XCacheLookup, objInfo.CacheLookupStatus.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set tag count if object has tags
|
// Set tag count if object has tags
|
||||||
if len(objInfo.UserTags) > 0 {
|
if len(objInfo.UserTags) > 0 {
|
||||||
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
|
|||||||
+47
-21
@@ -502,6 +502,47 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanReservedKeys(metadata map[string]string) map[string]string {
|
||||||
|
m := cloneMSS(metadata)
|
||||||
|
|
||||||
|
switch kind, _ := crypto.IsEncrypted(metadata); kind {
|
||||||
|
case crypto.S3:
|
||||||
|
m[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
||||||
|
case crypto.S3KMS:
|
||||||
|
m[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionKMS
|
||||||
|
m[xhttp.AmzServerSideEncryptionKmsID] = kmsKeyIDFromMetadata(metadata)
|
||||||
|
if kmsCtx, ok := metadata[crypto.MetaContext]; ok {
|
||||||
|
m[xhttp.AmzServerSideEncryptionKmsContext] = kmsCtx
|
||||||
|
}
|
||||||
|
case crypto.SSEC:
|
||||||
|
m[xhttp.AmzServerSideEncryptionCustomerAlgorithm] = xhttp.AmzEncryptionAES
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var toRemove []string
|
||||||
|
for k := range cleanMinioInternalMetadataKeys(m) {
|
||||||
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
|
// Do not need to send any internal metadata
|
||||||
|
// values to client.
|
||||||
|
toRemove = append(toRemove, k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
||||||
|
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
||||||
|
toRemove = append(toRemove, k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range toRemove {
|
||||||
|
delete(m, k)
|
||||||
|
delete(m, strings.ToLower(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
@@ -549,18 +590,11 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
case crypto.SSEC:
|
case crypto.SSEC:
|
||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
for k, v := range cleanReservedKeys(object.UserDefined) {
|
||||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
|
||||||
// Do not need to send any internal metadata
|
|
||||||
// values to client.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
|
||||||
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content.UserMetadata.Set(k, v)
|
content.UserMetadata.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
|
||||||
content.Internal = &ObjectInternalInfo{
|
content.Internal = &ObjectInternalInfo{
|
||||||
K: object.DataBlocks,
|
K: object.DataBlocks,
|
||||||
M: object.ParityBlocks,
|
M: object.ParityBlocks,
|
||||||
@@ -692,18 +726,10 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
case crypto.SSEC:
|
case crypto.SSEC:
|
||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
for k, v := range cleanReservedKeys(object.UserDefined) {
|
||||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
|
||||||
// Do not need to send any internal metadata
|
|
||||||
// values to client.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
|
||||||
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content.UserMetadata.Set(k, v)
|
content.UserMetadata.Set(k, v)
|
||||||
}
|
}
|
||||||
|
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
|
||||||
content.Internal = &ObjectInternalInfo{
|
content.Internal = &ObjectInternalInfo{
|
||||||
K: object.DataBlocks,
|
K: object.DataBlocks,
|
||||||
M: object.ParityBlocks,
|
M: object.ParityBlocks,
|
||||||
@@ -978,7 +1004,7 @@ func writeCustomErrorResponseJSON(ctx context.Context, w http.ResponseWriter, er
|
|||||||
BucketName: reqInfo.BucketName,
|
BucketName: reqInfo.BucketName,
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
HostID: globalDeploymentID,
|
HostID: globalDeploymentID(),
|
||||||
}
|
}
|
||||||
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
||||||
|
|||||||
+6
-17
@@ -23,7 +23,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/minio/console/restapi"
|
consoleapi "github.com/minio/console/api"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
@@ -43,13 +43,13 @@ func setHTTPServer(h *xhttp.Server) {
|
|||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConsoleServerFn() *restapi.Server {
|
func newConsoleServerFn() *consoleapi.Server {
|
||||||
globalObjLayerMutex.RLock()
|
globalObjLayerMutex.RLock()
|
||||||
defer globalObjLayerMutex.RUnlock()
|
defer globalObjLayerMutex.RUnlock()
|
||||||
return globalConsoleSrv
|
return globalConsoleSrv
|
||||||
}
|
}
|
||||||
|
|
||||||
func setConsoleSrv(srv *restapi.Server) {
|
func setConsoleSrv(srv *consoleapi.Server) {
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
globalConsoleSrv = srv
|
globalConsoleSrv = srv
|
||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
@@ -61,18 +61,6 @@ func newObjectLayerFn() ObjectLayer {
|
|||||||
return globalObjectAPI
|
return globalObjectAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCachedObjectLayerFn() CacheObjectLayer {
|
|
||||||
globalObjLayerMutex.RLock()
|
|
||||||
defer globalObjLayerMutex.RUnlock()
|
|
||||||
return globalCacheObjectAPI
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCacheObjectLayer(c CacheObjectLayer) {
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
globalCacheObjectAPI = c
|
|
||||||
globalObjLayerMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setObjectLayer(o ObjectLayer) {
|
func setObjectLayer(o ObjectLayer) {
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
globalObjectAPI = o
|
globalObjectAPI = o
|
||||||
@@ -82,7 +70,6 @@ func setObjectLayer(o ObjectLayer) {
|
|||||||
// objectAPIHandler implements and provides http handlers for S3 API.
|
// objectAPIHandler implements and provides http handlers for S3 API.
|
||||||
type objectAPIHandlers struct {
|
type objectAPIHandlers struct {
|
||||||
ObjectAPI func() ObjectLayer
|
ObjectAPI func() ObjectLayer
|
||||||
CacheAPI func() CacheObjectLayer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getHost tries its best to return the request host.
|
// getHost tries its best to return the request host.
|
||||||
@@ -189,7 +176,6 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// Initialize API.
|
// Initialize API.
|
||||||
api := objectAPIHandlers{
|
api := objectAPIHandlers{
|
||||||
ObjectAPI: newObjectLayerFn,
|
ObjectAPI: newObjectLayerFn,
|
||||||
CacheAPI: newCachedObjectLayerFn,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Router
|
// API Router
|
||||||
@@ -241,6 +227,9 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// HeadObject
|
// HeadObject
|
||||||
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
|
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
|
||||||
|
// GetObjectAttribytes
|
||||||
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
|
collectAPIStats("getobjectattributes", maxClients(gz(httpTraceHdrs(api.GetObjectAttributesHandler))))).Queries("attributes", "")
|
||||||
// CopyObjectPart
|
// CopyObjectPart
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
||||||
|
|||||||
+112
-110
File diff suppressed because one or more lines are too long
@@ -232,7 +232,7 @@ func (h *healingTracker) delete(ctx context.Context) error {
|
|||||||
pathJoin(bucketMetaPrefix, healingTrackerFilename),
|
pathJoin(bucketMetaPrefix, healingTrackerFilename),
|
||||||
DeleteOptions{
|
DeleteOptions{
|
||||||
Recursive: false,
|
Recursive: false,
|
||||||
Force: false,
|
Immediate: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -345,18 +345,17 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
||||||
|
|
||||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
|
||||||
|
|
||||||
if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
||||||
|
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||||
go monitorLocalDisksAndHeal(ctx, z)
|
go monitorLocalDisksAndHeal(ctx, z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
globalLocalDrives := globalLocalDrives
|
localDrives := globalLocalDrives
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
for _, disk := range globalLocalDrives {
|
for _, disk := range localDrives {
|
||||||
_, err := disk.GetDiskID()
|
_, err := disk.GetDiskID()
|
||||||
if errors.Is(err, errUnformattedDisk) {
|
if errors.Is(err, errUnformattedDisk) {
|
||||||
disksToHeal = append(disksToHeal, disk.Endpoint())
|
disksToHeal = append(disksToHeal, disk.Endpoint())
|
||||||
|
|||||||
@@ -0,0 +1,745 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/v2/env"
|
||||||
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
|
"github.com/minio/pkg/v2/workers"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// expire: # Expire objects that match a condition
|
||||||
|
// apiVersion: v1
|
||||||
|
// bucket: mybucket # Bucket where this batch job will expire matching objects from
|
||||||
|
// prefix: myprefix # (Optional) Prefix under which this job will expire objects matching the rules below.
|
||||||
|
// rules:
|
||||||
|
// - type: object # regular objects with zero ore more older versions
|
||||||
|
// name: NAME # match object names that satisfy the wildcard expression.
|
||||||
|
// olderThan: 70h # match objects older than this value
|
||||||
|
// createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
||||||
|
// tags:
|
||||||
|
// - key: name
|
||||||
|
// value: pick* # match objects with tag 'name', all values starting with 'pick'
|
||||||
|
// metadata:
|
||||||
|
// - key: content-type
|
||||||
|
// value: image/* # match objects with 'content-type', all values starting with 'image/'
|
||||||
|
// size:
|
||||||
|
// lessThan: "10MiB" # match objects with size less than this value (e.g. 10MiB)
|
||||||
|
// greaterThan: 1MiB # match objects with size greater than this value (e.g. 1MiB)
|
||||||
|
// purge:
|
||||||
|
// # retainVersions: 0 # (default) delete all versions of the object. This option is the fastest.
|
||||||
|
// # retainVersions: 5 # keep the latest 5 versions of the object.
|
||||||
|
//
|
||||||
|
// - type: deleted # objects with delete marker as their latest version
|
||||||
|
// name: NAME # match object names that satisfy the wildcard expression.
|
||||||
|
// olderThan: 10h # match objects older than this value (e.g. 7d10h31s)
|
||||||
|
// createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
||||||
|
// purge:
|
||||||
|
// # retainVersions: 0 # (default) delete all versions of the object. This option is the fastest.
|
||||||
|
// # retainVersions: 5 # keep the latest 5 versions of the object including delete markers.
|
||||||
|
//
|
||||||
|
// notify:
|
||||||
|
// endpoint: https://notify.endpoint # notification endpoint to receive job completion status
|
||||||
|
// token: Bearer xxxxx # optional authentication token for the notification endpoint
|
||||||
|
//
|
||||||
|
// retry:
|
||||||
|
// attempts: 10 # number of retries for the job before giving up
|
||||||
|
// delay: 500ms # least amount of delay between each retry
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE
|
||||||
|
|
||||||
|
// BatchJobExpirePurge type accepts non-negative versions to be retained
|
||||||
|
type BatchJobExpirePurge struct {
|
||||||
|
line, col int
|
||||||
|
RetainVersions int `yaml:"retainVersions" json:"retainVersions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.Unmarshaler = &BatchJobExpirePurge{}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobExpirePurge extends unmarshal to extract line, col
|
||||||
|
func (p *BatchJobExpirePurge) UnmarshalYAML(val *yaml.Node) error {
|
||||||
|
type purge BatchJobExpirePurge
|
||||||
|
var tmp purge
|
||||||
|
err := val.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = BatchJobExpirePurge(tmp)
|
||||||
|
p.line, p.col = val.Line, val.Column
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns nil if value is valid, ie > 0.
|
||||||
|
func (p BatchJobExpirePurge) Validate() error {
|
||||||
|
if p.RetainVersions < 0 {
|
||||||
|
return BatchJobYamlErr{
|
||||||
|
line: p.line,
|
||||||
|
col: p.col,
|
||||||
|
msg: "retainVersions must be >= 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobExpireFilter holds all the filters currently supported for batch replication
|
||||||
|
type BatchJobExpireFilter struct {
|
||||||
|
line, col int
|
||||||
|
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
||||||
|
CreatedBefore *time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
||||||
|
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
||||||
|
Metadata []BatchJobKV `yaml:"metadata,omitempty" json:"metadata"`
|
||||||
|
Size BatchJobSizeFilter `yaml:"size" json:"size"`
|
||||||
|
Type string `yaml:"type" json:"type"`
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
Purge BatchJobExpirePurge `yaml:"purge" json:"purge"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.Unmarshaler = &BatchJobExpireFilter{}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobExpireFilter extends unmarshal to extract line, col
|
||||||
|
// information
|
||||||
|
func (ef *BatchJobExpireFilter) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
type expFilter BatchJobExpireFilter
|
||||||
|
var tmp expFilter
|
||||||
|
err := value.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*ef = BatchJobExpireFilter(tmp)
|
||||||
|
ef.line, ef.col = value.Line, value.Column
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches returns true if obj matches the filter conditions specified in ef.
|
||||||
|
func (ef BatchJobExpireFilter) Matches(obj ObjectInfo, now time.Time) bool {
|
||||||
|
switch ef.Type {
|
||||||
|
case BatchJobExpireObject:
|
||||||
|
if obj.DeleteMarker {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case BatchJobExpireDeleted:
|
||||||
|
if !obj.DeleteMarker {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// we should never come here, Validate should have caught this.
|
||||||
|
logger.LogOnceIf(context.Background(), fmt.Errorf("invalid filter type: %s", ef.Type), ef.Type)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ef.Name) > 0 && !wildcard.Match(ef.Name, obj.Name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ef.OlderThan > 0 && now.Sub(obj.ModTime) <= ef.OlderThan {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ef.CreatedBefore != nil && !obj.ModTime.Before(*ef.CreatedBefore) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ef.Tags) > 0 && !obj.DeleteMarker {
|
||||||
|
// Only parse object tags if tags filter is specified.
|
||||||
|
var tagMap map[string]string
|
||||||
|
if len(obj.UserTags) != 0 {
|
||||||
|
t, err := tags.ParseObjectTags(obj.UserTags)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tagMap = t.ToMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range ef.Tags {
|
||||||
|
// Object (version) must match all tags specified in
|
||||||
|
// the filter
|
||||||
|
var match bool
|
||||||
|
for t, v := range tagMap {
|
||||||
|
if kv.Match(BatchJobKV{Key: t, Value: v}) {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(ef.Metadata) > 0 && !obj.DeleteMarker {
|
||||||
|
for _, kv := range ef.Metadata {
|
||||||
|
// Object (version) must match all x-amz-meta and
|
||||||
|
// standard metadata headers
|
||||||
|
// specified in the filter
|
||||||
|
var match bool
|
||||||
|
for k, v := range obj.UserDefined {
|
||||||
|
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We only need to match x-amz-meta or standardHeaders
|
||||||
|
if kv.Match(BatchJobKV{Key: k, Value: v}) {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ef.Size.InRange(obj.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BatchJobExpireObject - object type
|
||||||
|
BatchJobExpireObject string = "object"
|
||||||
|
// BatchJobExpireDeleted - delete marker type
|
||||||
|
BatchJobExpireDeleted string = "deleted"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate returns nil if ef has valid fields, validation error otherwise.
|
||||||
|
func (ef BatchJobExpireFilter) Validate() error {
|
||||||
|
switch ef.Type {
|
||||||
|
case BatchJobExpireObject:
|
||||||
|
case BatchJobExpireDeleted:
|
||||||
|
if len(ef.Tags) > 0 || len(ef.Metadata) > 0 {
|
||||||
|
return BatchJobYamlErr{
|
||||||
|
line: ef.line,
|
||||||
|
col: ef.col,
|
||||||
|
msg: "delete type filter can't have tags or metadata",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return BatchJobYamlErr{
|
||||||
|
line: ef.line,
|
||||||
|
col: ef.col,
|
||||||
|
msg: "invalid batch-expire type",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range ef.Tags {
|
||||||
|
if err := tag.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, meta := range ef.Metadata {
|
||||||
|
if err := meta.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ef.Purge.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ef.Size.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ef.CreatedBefore != nil && !ef.CreatedBefore.Before(time.Now()) {
|
||||||
|
return BatchJobYamlErr{
|
||||||
|
line: ef.line,
|
||||||
|
col: ef.col,
|
||||||
|
msg: "CreatedBefore is in the future",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobExpire represents configuration parameters for a batch expiration
|
||||||
|
// job typically supplied in yaml form
|
||||||
|
type BatchJobExpire struct {
|
||||||
|
line, col int
|
||||||
|
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
|
||||||
|
Bucket string `yaml:"bucket" json:"bucket"`
|
||||||
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
|
NotificationCfg BatchJobNotification `yaml:"notify" json:"notify"`
|
||||||
|
Retry BatchJobRetry `yaml:"retry" json:"retry"`
|
||||||
|
Rules []BatchJobExpireFilter `yaml:"rules" json:"rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.Unmarshaler = &BatchJobExpire{}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobExpire extends default unmarshal to extract line, col information.
|
||||||
|
func (r *BatchJobExpire) UnmarshalYAML(val *yaml.Node) error {
|
||||||
|
type expireJob BatchJobExpire
|
||||||
|
var tmp expireJob
|
||||||
|
err := val.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*r = BatchJobExpire(tmp)
|
||||||
|
r.line, r.col = val.Line, val.Column
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify notifies notification endpoint if configured regarding job failure or success.
|
||||||
|
func (r BatchJobExpire) Notify(ctx context.Context, body io.Reader) error {
|
||||||
|
if r.NotificationCfg.Endpoint == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.NotificationCfg.Endpoint, body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.NotificationCfg.Token != "" {
|
||||||
|
req.Header.Set("Authorization", r.NotificationCfg.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
clnt := http.Client{Transport: getRemoteInstanceTransport}
|
||||||
|
resp, err := clnt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xhttp.DrainBody(resp.Body)
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire expires object versions which have already matched supplied filter conditions
|
||||||
|
func (r *BatchJobExpire) Expire(ctx context.Context, api ObjectLayer, vc *versioning.Versioning, objsToDel []ObjectToDelete) []error {
|
||||||
|
opts := ObjectOptions{
|
||||||
|
PrefixEnabledFn: vc.PrefixEnabled,
|
||||||
|
VersionSuspended: vc.Suspended(),
|
||||||
|
}
|
||||||
|
_, errs := api.DeleteObjects(ctx, r.Bucket, objsToDel, opts)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
batchExpireName = "batch-expire.bin"
|
||||||
|
batchExpireFormat = 1
|
||||||
|
batchExpireVersionV1 = 1
|
||||||
|
batchExpireVersion = batchExpireVersionV1
|
||||||
|
batchExpireAPIVersion = "v1"
|
||||||
|
batchExpireJobDefaultRetries = 3
|
||||||
|
batchExpireJobDefaultRetryDelay = 250 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
type objInfoCache map[string]*ObjectInfo
|
||||||
|
|
||||||
|
func newObjInfoCache() objInfoCache {
|
||||||
|
return objInfoCache(make(map[string]*ObjectInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oiCache objInfoCache) Add(toDel ObjectToDelete, oi *ObjectInfo) {
|
||||||
|
oiCache[fmt.Sprintf("%s-%s", toDel.ObjectName, toDel.VersionID)] = oi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oiCache objInfoCache) Get(toDel ObjectToDelete) (*ObjectInfo, bool) {
|
||||||
|
oi, ok := oiCache[fmt.Sprintf("%s-%s", toDel.ObjectName, toDel.VersionID)]
|
||||||
|
return oi, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo, job BatchJobRequest, api ObjectLayer, wk *workers.Workers, expireCh <-chan []expireObjInfo) {
|
||||||
|
vc, _ := globalBucketVersioningSys.Get(r.Bucket)
|
||||||
|
retryAttempts := r.Retry.Attempts
|
||||||
|
delay := job.Expire.Retry.Delay
|
||||||
|
if delay == 0 {
|
||||||
|
delay = batchExpireJobDefaultRetryDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
var i int
|
||||||
|
for toExpire := range expireCh {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
if wait := globalBatchConfig.ExpirationWait(); wait > 0 {
|
||||||
|
time.Sleep(wait)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
wk.Take()
|
||||||
|
go func(toExpire []expireObjInfo) {
|
||||||
|
defer wk.Give()
|
||||||
|
|
||||||
|
toExpireAll := make([]ObjectInfo, 0, len(toExpire))
|
||||||
|
toDel := make([]ObjectToDelete, 0, len(toExpire))
|
||||||
|
oiCache := newObjInfoCache()
|
||||||
|
for _, exp := range toExpire {
|
||||||
|
if exp.ExpireAll {
|
||||||
|
toExpireAll = append(toExpireAll, exp.ObjectInfo)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Cache ObjectInfo value via pointers for
|
||||||
|
// subsequent use to track objects which
|
||||||
|
// couldn't be deleted.
|
||||||
|
od := ObjectToDelete{
|
||||||
|
ObjectV: ObjectV{
|
||||||
|
ObjectName: exp.Name,
|
||||||
|
VersionID: exp.VersionID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
toDel = append(toDel, od)
|
||||||
|
oiCache.Add(od, &exp.ObjectInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
var done bool
|
||||||
|
// DeleteObject(deletePrefix: true) to expire all versions of an object
|
||||||
|
for _, exp := range toExpireAll {
|
||||||
|
var success bool
|
||||||
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
done = true
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricExpire, ri.JobID, attempts)
|
||||||
|
_, err := api.DeleteObject(ctx, exp.Bucket, exp.Name, ObjectOptions{
|
||||||
|
DeletePrefix: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
stopFn(exp, err)
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", toExpire[i].Bucket, toExpire[i].Name, toExpire[i].VersionID, err, attempts))
|
||||||
|
} else {
|
||||||
|
stopFn(exp, err)
|
||||||
|
success = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ri.trackMultipleObjectVersions(r.Bucket, exp, success)
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMultiple objects
|
||||||
|
toDelCopy := make([]ObjectToDelete, len(toDel))
|
||||||
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricExpire, ri.JobID, attempts)
|
||||||
|
// Copying toDel to select from objects whose
|
||||||
|
// deletion failed
|
||||||
|
copy(toDelCopy, toDel)
|
||||||
|
var failed int
|
||||||
|
errs := r.Expire(ctx, api, vc, toDel)
|
||||||
|
// reslice toDel in preparation for next retry
|
||||||
|
// attempt
|
||||||
|
toDel = toDel[:0]
|
||||||
|
for i, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
stopFn(toDelCopy[i], err)
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", ri.Bucket, toDelCopy[i].ObjectName, toDelCopy[i].VersionID, err, attempts))
|
||||||
|
failed++
|
||||||
|
if attempts == retryAttempts { // all retry attempts failed, record failure
|
||||||
|
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
||||||
|
ri.trackCurrentBucketObject(r.Bucket, *oi, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toDel = append(toDel, toDelCopy[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopFn(toDelCopy[i], nil)
|
||||||
|
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
||||||
|
ri.trackCurrentBucketObject(r.Bucket, *oi, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalBatchJobsMetrics.save(ri.JobID, ri)
|
||||||
|
|
||||||
|
if failed == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a delay between retry attempts
|
||||||
|
if attempts < retryAttempts {
|
||||||
|
time.Sleep(delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(toExpire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type expireObjInfo struct {
|
||||||
|
ObjectInfo
|
||||||
|
ExpireAll bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the batch expiration job, resumes if there was a pending job via "job.ID"
|
||||||
|
func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
|
||||||
|
ri := &batchJobInfo{
|
||||||
|
JobID: job.ID,
|
||||||
|
JobType: string(job.Type()),
|
||||||
|
StartTime: job.Started,
|
||||||
|
}
|
||||||
|
if err := ri.load(ctx, api, job); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
lastObject := ri.Object
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_EXPIRATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wk, err := workers.New(workerSize)
|
||||||
|
if err != nil {
|
||||||
|
// invalid worker size.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
results := make(chan ObjectInfo, workerSize)
|
||||||
|
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
||||||
|
Marker: lastObject,
|
||||||
|
LatestOnly: false, // we need to visit all versions of the object to implement purge: retainVersions
|
||||||
|
VersionsSort: WalkVersionsSortDesc,
|
||||||
|
}); err != nil {
|
||||||
|
// Do not need to retry if we can't list objects on source.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Goroutine to periodically save batch-expire job's in-memory state
|
||||||
|
saverQuitCh := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
saveTicker := time.NewTicker(10 * time.Second)
|
||||||
|
defer saveTicker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-saveTicker.C:
|
||||||
|
// persist in-memory state to disk after every 10secs.
|
||||||
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
// persist in-memory state immediately before exiting due to context cancelation.
|
||||||
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-saverQuitCh:
|
||||||
|
// persist in-memory state immediately to disk.
|
||||||
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expireCh := make(chan []expireObjInfo, workerSize)
|
||||||
|
go batchObjsForDelete(ctx, r, ri, job, api, wk, expireCh)
|
||||||
|
|
||||||
|
var (
|
||||||
|
prevObj ObjectInfo
|
||||||
|
matchedFilter BatchJobExpireFilter
|
||||||
|
versionsCount int
|
||||||
|
toDel []expireObjInfo
|
||||||
|
)
|
||||||
|
for result := range results {
|
||||||
|
// Apply filter to find the matching rule to apply expiry
|
||||||
|
// actions accordingly.
|
||||||
|
// nolint:gocritic
|
||||||
|
if result.IsLatest {
|
||||||
|
// send down filtered entries to be deleted using
|
||||||
|
// DeleteObjects method
|
||||||
|
if len(toDel) > 10 { // batch up to 10 objects/versions to be expired simultaneously.
|
||||||
|
xfer := make([]expireObjInfo, len(toDel))
|
||||||
|
copy(xfer, toDel)
|
||||||
|
|
||||||
|
var done bool
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
done = true
|
||||||
|
case expireCh <- xfer:
|
||||||
|
toDel = toDel[:0] // resetting toDel
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var match BatchJobExpireFilter
|
||||||
|
var found bool
|
||||||
|
for _, rule := range r.Rules {
|
||||||
|
if rule.Matches(result, now) {
|
||||||
|
match = rule
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prevObj = result
|
||||||
|
matchedFilter = match
|
||||||
|
versionsCount = 1
|
||||||
|
// Include the latest version
|
||||||
|
if matchedFilter.Purge.RetainVersions == 0 {
|
||||||
|
toDel = append(toDel, expireObjInfo{
|
||||||
|
ObjectInfo: result,
|
||||||
|
ExpireAll: true,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if prevObj.Name == result.Name {
|
||||||
|
if matchedFilter.Purge.RetainVersions == 0 {
|
||||||
|
continue // including latest version in toDel suffices, skipping other versions
|
||||||
|
}
|
||||||
|
versionsCount++
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionsCount <= matchedFilter.Purge.RetainVersions {
|
||||||
|
continue // retain versions
|
||||||
|
}
|
||||||
|
toDel = append(toDel, expireObjInfo{
|
||||||
|
ObjectInfo: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Send any remaining objects downstream
|
||||||
|
if len(toDel) > 0 {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case expireCh <- toDel:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(expireCh)
|
||||||
|
|
||||||
|
wk.Wait() // waits for all expire goroutines to complete
|
||||||
|
|
||||||
|
ri.Complete = ri.ObjectsFailed == 0
|
||||||
|
ri.Failed = ri.ObjectsFailed > 0
|
||||||
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
|
||||||
|
// Close the saverQuitCh - this also triggers saving in-memory state
|
||||||
|
// immediately one last time before we exit this method.
|
||||||
|
close(saverQuitCh)
|
||||||
|
|
||||||
|
// Notify expire jobs final status to the configured endpoint
|
||||||
|
buf, _ := json.Marshal(ri)
|
||||||
|
if err := r.Notify(context.Background(), bytes.NewReader(buf)); err != nil {
|
||||||
|
logger.LogIf(context.Background(), fmt.Errorf("unable to notify %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//msgp:ignore batchExpireJobError
|
||||||
|
type batchExpireJobError struct {
|
||||||
|
Code string
|
||||||
|
Description string
|
||||||
|
HTTPStatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e batchExpireJobError) Error() string {
|
||||||
|
return e.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxBatchRules maximum number of rules a batch-expiry job supports
|
||||||
|
const maxBatchRules = 50
|
||||||
|
|
||||||
|
// Validate validates the job definition input
|
||||||
|
func (r *BatchJobExpire) Validate(ctx context.Context, job BatchJobRequest, o ObjectLayer) error {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.APIVersion != batchExpireAPIVersion {
|
||||||
|
return batchExpireJobError{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Unsupported batch expire API version",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Bucket == "" {
|
||||||
|
return batchExpireJobError{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Bucket argument missing",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := o.GetBucketInfo(ctx, r.Bucket, BucketOptions{}); err != nil {
|
||||||
|
if isErrBucketNotFound(err) {
|
||||||
|
return batchExpireJobError{
|
||||||
|
Code: "NoSuchSourceBucket",
|
||||||
|
Description: "The specified source bucket does not exist",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Rules) > maxBatchRules {
|
||||||
|
return batchExpireJobError{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Too many rules. Batch expire job can't have more than 100 rules",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range r.Rules {
|
||||||
|
if err := rule.Validate(); err != nil {
|
||||||
|
return batchExpireJobError{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: fmt.Sprintf("Invalid batch expire rule: %s", err),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Retry.Validate(); err != nil {
|
||||||
|
return batchExpireJobError{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: fmt.Sprintf("Invalid batch expire retry configuration: %s", err),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,856 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobExpire) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "APIVersion":
|
||||||
|
z.APIVersion, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "APIVersion")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Bucket":
|
||||||
|
z.Bucket, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Prefix":
|
||||||
|
z.Prefix, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Prefix")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "NotificationCfg":
|
||||||
|
err = z.NotificationCfg.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "NotificationCfg")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Retry":
|
||||||
|
err = z.Retry.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Retry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Rules":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Rules")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Rules) >= int(zb0002) {
|
||||||
|
z.Rules = (z.Rules)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Rules = make([]BatchJobExpireFilter, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Rules {
|
||||||
|
err = z.Rules[za0001].DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Rules", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *BatchJobExpire) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 6
|
||||||
|
// write "APIVersion"
|
||||||
|
err = en.Append(0x86, 0xaa, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.APIVersion)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "APIVersion")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Bucket"
|
||||||
|
err = en.Append(0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Prefix"
|
||||||
|
err = en.Append(0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Prefix)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Prefix")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "NotificationCfg"
|
||||||
|
err = en.Append(0xaf, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x66, 0x67)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = z.NotificationCfg.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "NotificationCfg")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Retry"
|
||||||
|
err = en.Append(0xa5, 0x52, 0x65, 0x74, 0x72, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = z.Retry.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Retry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Rules"
|
||||||
|
err = en.Append(0xa5, 0x52, 0x75, 0x6c, 0x65, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.Rules)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Rules")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.Rules {
|
||||||
|
err = z.Rules[za0001].EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Rules", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *BatchJobExpire) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 6
|
||||||
|
// string "APIVersion"
|
||||||
|
o = append(o, 0x86, 0xaa, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
||||||
|
o = msgp.AppendString(o, z.APIVersion)
|
||||||
|
// string "Bucket"
|
||||||
|
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
|
// string "Prefix"
|
||||||
|
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||||
|
o = msgp.AppendString(o, z.Prefix)
|
||||||
|
// string "NotificationCfg"
|
||||||
|
o = append(o, 0xaf, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x66, 0x67)
|
||||||
|
o, err = z.NotificationCfg.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "NotificationCfg")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "Retry"
|
||||||
|
o = append(o, 0xa5, 0x52, 0x65, 0x74, 0x72, 0x79)
|
||||||
|
o, err = z.Retry.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Retry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "Rules"
|
||||||
|
o = append(o, 0xa5, 0x52, 0x75, 0x6c, 0x65, 0x73)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Rules)))
|
||||||
|
for za0001 := range z.Rules {
|
||||||
|
o, err = z.Rules[za0001].MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Rules", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobExpire) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "APIVersion":
|
||||||
|
z.APIVersion, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "APIVersion")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Bucket":
|
||||||
|
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Prefix":
|
||||||
|
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Prefix")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "NotificationCfg":
|
||||||
|
bts, err = z.NotificationCfg.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "NotificationCfg")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Retry":
|
||||||
|
bts, err = z.Retry.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Retry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Rules":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Rules")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Rules) >= int(zb0002) {
|
||||||
|
z.Rules = (z.Rules)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Rules = make([]BatchJobExpireFilter, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Rules {
|
||||||
|
bts, err = z.Rules[za0001].UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Rules", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *BatchJobExpire) Msgsize() (s int) {
|
||||||
|
s = 1 + 11 + msgp.StringPrefixSize + len(z.APIVersion) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 16 + z.NotificationCfg.Msgsize() + 6 + z.Retry.Msgsize() + 6 + msgp.ArrayHeaderSize
|
||||||
|
for za0001 := range z.Rules {
|
||||||
|
s += z.Rules[za0001].Msgsize()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobExpireFilter) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "OlderThan":
|
||||||
|
z.OlderThan, err = dc.ReadDuration()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "CreatedBefore":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "CreatedBefore")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.CreatedBefore = nil
|
||||||
|
} else {
|
||||||
|
if z.CreatedBefore == nil {
|
||||||
|
z.CreatedBefore = new(time.Time)
|
||||||
|
}
|
||||||
|
*z.CreatedBefore, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "CreatedBefore")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Tags":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Tags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Tags) >= int(zb0002) {
|
||||||
|
z.Tags = (z.Tags)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Tags = make([]BatchJobKV, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Tags {
|
||||||
|
err = z.Tags[za0001].DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Metadata":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Metadata")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Metadata) >= int(zb0003) {
|
||||||
|
z.Metadata = (z.Metadata)[:zb0003]
|
||||||
|
} else {
|
||||||
|
z.Metadata = make([]BatchJobKV, zb0003)
|
||||||
|
}
|
||||||
|
for za0002 := range z.Metadata {
|
||||||
|
err = z.Metadata[za0002].DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Size":
|
||||||
|
err = z.Size.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Type":
|
||||||
|
z.Type, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Name":
|
||||||
|
z.Name, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Purge":
|
||||||
|
var zb0004 uint32
|
||||||
|
zb0004, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0004 > 0 {
|
||||||
|
zb0004--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "RetainVersions":
|
||||||
|
z.Purge.RetainVersions, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge", "RetainVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *BatchJobExpireFilter) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 8
|
||||||
|
// write "OlderThan"
|
||||||
|
err = en.Append(0x88, 0xa9, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteDuration(z.OlderThan)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "CreatedBefore"
|
||||||
|
err = en.Append(0xad, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.CreatedBefore == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = en.WriteTime(*z.CreatedBefore)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "CreatedBefore")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "Tags"
|
||||||
|
err = en.Append(0xa4, 0x54, 0x61, 0x67, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.Tags)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Tags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.Tags {
|
||||||
|
err = z.Tags[za0001].EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "Metadata"
|
||||||
|
err = en.Append(0xa8, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.Metadata)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Metadata")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0002 := range z.Metadata {
|
||||||
|
err = z.Metadata[za0002].EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "Size"
|
||||||
|
err = en.Append(0xa4, 0x53, 0x69, 0x7a, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = z.Size.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Type"
|
||||||
|
err = en.Append(0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Type)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Name"
|
||||||
|
err = en.Append(0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Name)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Purge"
|
||||||
|
err = en.Append(0xa5, 0x50, 0x75, 0x72, 0x67, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// map header, size 1
|
||||||
|
// write "RetainVersions"
|
||||||
|
err = en.Append(0x81, 0xae, 0x52, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.Purge.RetainVersions)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge", "RetainVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *BatchJobExpireFilter) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 8
|
||||||
|
// string "OlderThan"
|
||||||
|
o = append(o, 0x88, 0xa9, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
||||||
|
o = msgp.AppendDuration(o, z.OlderThan)
|
||||||
|
// string "CreatedBefore"
|
||||||
|
o = append(o, 0xad, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65)
|
||||||
|
if z.CreatedBefore == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o = msgp.AppendTime(o, *z.CreatedBefore)
|
||||||
|
}
|
||||||
|
// string "Tags"
|
||||||
|
o = append(o, 0xa4, 0x54, 0x61, 0x67, 0x73)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Tags)))
|
||||||
|
for za0001 := range z.Tags {
|
||||||
|
o, err = z.Tags[za0001].MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// string "Metadata"
|
||||||
|
o = append(o, 0xa8, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata)))
|
||||||
|
for za0002 := range z.Metadata {
|
||||||
|
o, err = z.Metadata[za0002].MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// string "Size"
|
||||||
|
o = append(o, 0xa4, 0x53, 0x69, 0x7a, 0x65)
|
||||||
|
o, err = z.Size.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "Type"
|
||||||
|
o = append(o, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||||
|
o = msgp.AppendString(o, z.Type)
|
||||||
|
// string "Name"
|
||||||
|
o = append(o, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||||
|
o = msgp.AppendString(o, z.Name)
|
||||||
|
// string "Purge"
|
||||||
|
o = append(o, 0xa5, 0x50, 0x75, 0x72, 0x67, 0x65)
|
||||||
|
// map header, size 1
|
||||||
|
// string "RetainVersions"
|
||||||
|
o = append(o, 0x81, 0xae, 0x52, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73)
|
||||||
|
o = msgp.AppendInt(o, z.Purge.RetainVersions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobExpireFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "OlderThan":
|
||||||
|
z.OlderThan, bts, err = msgp.ReadDurationBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "CreatedBefore":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.CreatedBefore = nil
|
||||||
|
} else {
|
||||||
|
if z.CreatedBefore == nil {
|
||||||
|
z.CreatedBefore = new(time.Time)
|
||||||
|
}
|
||||||
|
*z.CreatedBefore, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "CreatedBefore")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Tags":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Tags")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Tags) >= int(zb0002) {
|
||||||
|
z.Tags = (z.Tags)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Tags = make([]BatchJobKV, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Tags {
|
||||||
|
bts, err = z.Tags[za0001].UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Tags", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Metadata":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Metadata")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Metadata) >= int(zb0003) {
|
||||||
|
z.Metadata = (z.Metadata)[:zb0003]
|
||||||
|
} else {
|
||||||
|
z.Metadata = make([]BatchJobKV, zb0003)
|
||||||
|
}
|
||||||
|
for za0002 := range z.Metadata {
|
||||||
|
bts, err = z.Metadata[za0002].UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Metadata", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Size":
|
||||||
|
bts, err = z.Size.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Type":
|
||||||
|
z.Type, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Name":
|
||||||
|
z.Name, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Purge":
|
||||||
|
var zb0004 uint32
|
||||||
|
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0004 > 0 {
|
||||||
|
zb0004--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "RetainVersions":
|
||||||
|
z.Purge.RetainVersions, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge", "RetainVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Purge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *BatchJobExpireFilter) Msgsize() (s int) {
|
||||||
|
s = 1 + 10 + msgp.DurationSize + 14
|
||||||
|
if z.CreatedBefore == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += msgp.TimeSize
|
||||||
|
}
|
||||||
|
s += 5 + msgp.ArrayHeaderSize
|
||||||
|
for za0001 := range z.Tags {
|
||||||
|
s += z.Tags[za0001].Msgsize()
|
||||||
|
}
|
||||||
|
s += 9 + msgp.ArrayHeaderSize
|
||||||
|
for za0002 := range z.Metadata {
|
||||||
|
s += z.Metadata[za0002].Msgsize()
|
||||||
|
}
|
||||||
|
s += 5 + z.Size.Msgsize() + 5 + msgp.StringPrefixSize + len(z.Type) + 5 + msgp.StringPrefixSize + len(z.Name) + 6 + 1 + 15 + msgp.IntSize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobExpirePurge) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "RetainVersions":
|
||||||
|
z.RetainVersions, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetainVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobExpirePurge) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 1
|
||||||
|
// write "RetainVersions"
|
||||||
|
err = en.Append(0x81, 0xae, 0x52, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.RetainVersions)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetainVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobExpirePurge) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 1
|
||||||
|
// string "RetainVersions"
|
||||||
|
o = append(o, 0x81, 0xae, 0x52, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73)
|
||||||
|
o = msgp.AppendInt(o, z.RetainVersions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobExpirePurge) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "RetainVersions":
|
||||||
|
z.RetainVersions, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetainVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z BatchJobExpirePurge) Msgsize() (s int) {
|
||||||
|
s = 1 + 15 + msgp.IntSize
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobExpire(t *testing.T) {
|
||||||
|
v := BatchJobExpire{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobExpire(b *testing.B) {
|
||||||
|
v := BatchJobExpire{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobExpire(b *testing.B) {
|
||||||
|
v := BatchJobExpire{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobExpire(b *testing.B) {
|
||||||
|
v := BatchJobExpire{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobExpire(t *testing.T) {
|
||||||
|
v := BatchJobExpire{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobExpire Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobExpire{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobExpire(b *testing.B) {
|
||||||
|
v := BatchJobExpire{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobExpire(b *testing.B) {
|
||||||
|
v := BatchJobExpire{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobExpireFilter(t *testing.T) {
|
||||||
|
v := BatchJobExpireFilter{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobExpireFilter(b *testing.B) {
|
||||||
|
v := BatchJobExpireFilter{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobExpireFilter(b *testing.B) {
|
||||||
|
v := BatchJobExpireFilter{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobExpireFilter(b *testing.B) {
|
||||||
|
v := BatchJobExpireFilter{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobExpireFilter(t *testing.T) {
|
||||||
|
v := BatchJobExpireFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobExpireFilter Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobExpireFilter{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobExpireFilter(b *testing.B) {
|
||||||
|
v := BatchJobExpireFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobExpireFilter(b *testing.B) {
|
||||||
|
v := BatchJobExpireFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobExpirePurge(t *testing.T) {
|
||||||
|
v := BatchJobExpirePurge{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobExpirePurge(b *testing.B) {
|
||||||
|
v := BatchJobExpirePurge{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobExpirePurge(b *testing.B) {
|
||||||
|
v := BatchJobExpirePurge{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobExpirePurge(b *testing.B) {
|
||||||
|
v := BatchJobExpirePurge{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobExpirePurge(t *testing.T) {
|
||||||
|
v := BatchJobExpirePurge{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobExpirePurge Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobExpirePurge{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobExpirePurge(b *testing.B) {
|
||||||
|
v := BatchJobExpirePurge{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobExpirePurge(b *testing.B) {
|
||||||
|
v := BatchJobExpirePurge{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseBatchJobExpire(t *testing.T) {
|
||||||
|
expireYaml := `
|
||||||
|
expire: # Expire objects that match a condition
|
||||||
|
apiVersion: v1
|
||||||
|
bucket: mybucket # Bucket where this batch job will expire matching objects from
|
||||||
|
prefix: myprefix # (Optional) Prefix under which this job will expire objects matching the rules below.
|
||||||
|
rules:
|
||||||
|
- type: object # regular objects with zero ore more older versions
|
||||||
|
name: NAME # match object names that satisfy the wildcard expression.
|
||||||
|
olderThan: 70h # match objects older than this value
|
||||||
|
createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
||||||
|
tags:
|
||||||
|
- key: name
|
||||||
|
value: pick* # match objects with tag 'name', all values starting with 'pick'
|
||||||
|
metadata:
|
||||||
|
- key: content-type
|
||||||
|
value: image/* # match objects with 'content-type', all values starting with 'image/'
|
||||||
|
size:
|
||||||
|
lessThan: "10MiB" # match objects with size less than this value (e.g. 10MiB)
|
||||||
|
greaterThan: 1MiB # match objects with size greater than this value (e.g. 1MiB)
|
||||||
|
purge:
|
||||||
|
# retainVersions: 0 # (default) delete all versions of the object. This option is the fastest.
|
||||||
|
# retainVersions: 5 # keep the latest 5 versions of the object.
|
||||||
|
|
||||||
|
- type: deleted # objects with delete marker as their latest version
|
||||||
|
name: NAME # match object names that satisfy the wildcard expression.
|
||||||
|
olderThan: 10h # match objects older than this value (e.g. 7d10h31s)
|
||||||
|
createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
||||||
|
purge:
|
||||||
|
# retainVersions: 0 # (default) delete all versions of the object. This option is the fastest.
|
||||||
|
# retainVersions: 5 # keep the latest 5 versions of the object including delete markers.
|
||||||
|
|
||||||
|
notify:
|
||||||
|
endpoint: https://notify.endpoint # notification endpoint to receive job completion status
|
||||||
|
token: Bearer xxxxx # optional authentication token for the notification endpoint
|
||||||
|
|
||||||
|
retry:
|
||||||
|
attempts: 10 # number of retries for the job before giving up
|
||||||
|
delay: 500ms # least amount of delay between each retry
|
||||||
|
`
|
||||||
|
var job BatchJobRequest
|
||||||
|
err := yaml.UnmarshalStrict([]byte(expireYaml), &job)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to parse batch-job-expire yaml", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
+340
-80
@@ -42,6 +42,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/internal/config/batch"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
@@ -51,9 +52,11 @@ import (
|
|||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
"github.com/minio/pkg/v2/workers"
|
"github.com/minio/pkg/v2/workers"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var globalBatchConfig batch.Config
|
||||||
|
|
||||||
// BatchJobRequest this is an internal data structure not for external consumption.
|
// BatchJobRequest this is an internal data structure not for external consumption.
|
||||||
type BatchJobRequest struct {
|
type BatchJobRequest struct {
|
||||||
ID string `yaml:"-" json:"name"`
|
ID string `yaml:"-" json:"name"`
|
||||||
@@ -62,6 +65,7 @@ type BatchJobRequest struct {
|
|||||||
Location string `yaml:"-" json:"location"`
|
Location string `yaml:"-" json:"location"`
|
||||||
Replicate *BatchJobReplicateV1 `yaml:"replicate" json:"replicate"`
|
Replicate *BatchJobReplicateV1 `yaml:"replicate" json:"replicate"`
|
||||||
KeyRotate *BatchJobKeyRotateV1 `yaml:"keyrotate" json:"keyrotate"`
|
KeyRotate *BatchJobKeyRotateV1 `yaml:"keyrotate" json:"keyrotate"`
|
||||||
|
Expire *BatchJobExpire `yaml:"expire" json:"expire"`
|
||||||
ctx context.Context `msg:"-"`
|
ctx context.Context `msg:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +167,7 @@ func (r *BatchJobReplicateV1) ReplicateFromSource(ctx context.Context, api Objec
|
|||||||
}
|
}
|
||||||
rd, objInfo, _, err := core.GetObject(ctx, srcBucket, srcObject, gopts)
|
rd, objInfo, _, err := core.GetObject(ctx, srcBucket, srcObject, gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return ErrorRespToObjectError(err, srcBucket, srcObject, srcObjInfo.VersionID)
|
||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
|
|
||||||
@@ -226,7 +230,7 @@ func (r *BatchJobReplicateV1) copyWithMultipartfromSource(ctx context.Context, a
|
|||||||
}
|
}
|
||||||
rd, objInfo, _, err := c.GetObject(ctx, srcBucket, srcObject, gopts)
|
rd, objInfo, _, err := c.GetObject(ctx, srcBucket, srcObject, gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return ErrorRespToObjectError(err, srcBucket, srcObject, srcObjInfo.VersionID)
|
||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
|
|
||||||
@@ -262,6 +266,9 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
if err := ri.load(ctx, api, job); err != nil {
|
if err := ri.load(ctx, api, job); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if ri.Complete {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
|
||||||
delay := job.Replicate.Flags.Retry.Delay
|
delay := job.Replicate.Flags.Retry.Delay
|
||||||
@@ -428,23 +435,27 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
wk.Take()
|
wk.Take()
|
||||||
go func() {
|
go func() {
|
||||||
defer wk.Give()
|
defer wk.Give()
|
||||||
stopFn := globalBatchJobsMetrics.trace(batchReplicationMetricObject, job.ID, attempts, oi)
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricReplication, job.ID, attempts)
|
||||||
success := true
|
success := true
|
||||||
if err := r.ReplicateFromSource(ctx, api, core, oi, retry); err != nil {
|
if err := r.ReplicateFromSource(ctx, api, core, oi, retry); err != nil {
|
||||||
// object must be deleted concurrently, allow these failures but do not count them
|
// object must be deleted concurrently, allow these failures but do not count them
|
||||||
if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
|
if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stopFn(err)
|
stopFn(oi, err)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
success = false
|
success = false
|
||||||
} else {
|
} else {
|
||||||
stopFn(nil)
|
stopFn(oi, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Target.Bucket, oi, success)
|
ri.trackCurrentBucketObject(r.Target.Bucket, oi, success)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
|
||||||
|
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
||||||
|
time.Sleep(wait)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wk.Wait()
|
wk.Wait()
|
||||||
@@ -517,9 +528,68 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
|
|||||||
oi.UserDefined[xhttp.AmzStorageClass] = objInfo.StorageClass
|
oi.UserDefined[xhttp.AmzStorageClass] = objInfo.StorageClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for k, v := range objInfo.UserMetadata {
|
||||||
|
oi.UserDefined[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
return oi
|
return oi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLayer, remoteClnt *minio.Client, entries []ObjectInfo) error {
|
||||||
|
input := make(chan minio.SnowballObject, 1)
|
||||||
|
opts := minio.SnowballOptions{
|
||||||
|
Opts: minio.PutObjectOptions{},
|
||||||
|
InMemory: *r.Source.Snowball.InMemory,
|
||||||
|
Compress: *r.Source.Snowball.Compress,
|
||||||
|
SkipErrs: *r.Source.Snowball.SkipErrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(input)
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
gr, err := objAPI.GetObjectNInfo(ctx, r.Source.Bucket,
|
||||||
|
entry.Name, nil, nil, ObjectOptions{
|
||||||
|
VersionID: entry.VersionID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
snowballObj := minio.SnowballObject{
|
||||||
|
// Create path to store objects within the bucket.
|
||||||
|
Key: entry.Name,
|
||||||
|
Size: entry.Size,
|
||||||
|
ModTime: entry.ModTime,
|
||||||
|
VersionID: entry.VersionID,
|
||||||
|
Content: gr,
|
||||||
|
Headers: make(http.Header),
|
||||||
|
Close: func() {
|
||||||
|
gr.Close()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, err := batchReplicationOpts(ctx, "", gr.ObjInfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, vals := range opts.Header() {
|
||||||
|
for _, v := range vals {
|
||||||
|
snowballObj.Headers.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input <- snowballObj
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Collect and upload all entries.
|
||||||
|
return remoteClnt.PutObjectsSnowball(ctx, r.Target.Bucket, opts, input)
|
||||||
|
}
|
||||||
|
|
||||||
// ReplicateToTarget read from source and replicate to configured target
|
// ReplicateToTarget read from source and replicate to configured target
|
||||||
func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectLayer, c *miniogo.Core, srcObjInfo ObjectInfo, retry bool) error {
|
func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectLayer, c *miniogo.Core, srcObjInfo ObjectInfo, retry bool) error {
|
||||||
srcBucket := r.Source.Bucket
|
srcBucket := r.Source.Bucket
|
||||||
@@ -663,7 +733,12 @@ func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobR
|
|||||||
fileName = batchKeyRotationName
|
fileName = batchKeyRotationName
|
||||||
version = batchKeyRotateVersionV1
|
version = batchKeyRotateVersionV1
|
||||||
format = batchKeyRotationFormat
|
format = batchKeyRotationFormat
|
||||||
|
case job.Expire != nil:
|
||||||
|
fileName = batchExpireName
|
||||||
|
version = batchExpireVersionV1
|
||||||
|
format = batchExpireFormat
|
||||||
|
default:
|
||||||
|
return errors.New("no supported batch job request specified")
|
||||||
}
|
}
|
||||||
data, err := readConfig(ctx, api, pathJoin(job.Location, fileName))
|
data, err := readConfig(ctx, api, pathJoin(job.Location, fileName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -680,6 +755,11 @@ func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobR
|
|||||||
if job.KeyRotate.Flags.Retry.Attempts > 0 {
|
if job.KeyRotate.Flags.Retry.Attempts > 0 {
|
||||||
ri.RetryAttempts = job.KeyRotate.Flags.Retry.Attempts
|
ri.RetryAttempts = job.KeyRotate.Flags.Retry.Attempts
|
||||||
}
|
}
|
||||||
|
case job.Expire != nil:
|
||||||
|
ri.RetryAttempts = batchExpireJobDefaultRetries
|
||||||
|
if job.Expire.Retry.Attempts > 0 {
|
||||||
|
ri.RetryAttempts = job.Expire.Retry.Attempts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -789,6 +869,12 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
|
|||||||
jobTyp = string(job.Type())
|
jobTyp = string(job.Type())
|
||||||
fileName = batchKeyRotationName
|
fileName = batchKeyRotationName
|
||||||
ri.Version = batchKeyRotateVersionV1
|
ri.Version = batchKeyRotateVersionV1
|
||||||
|
case madmin.BatchJobExpire:
|
||||||
|
format = batchExpireFormat
|
||||||
|
version = batchExpireVersion
|
||||||
|
jobTyp = string(job.Type())
|
||||||
|
fileName = batchExpireName
|
||||||
|
ri.Version = batchExpireVersionV1
|
||||||
default:
|
default:
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -814,7 +900,18 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo, failed bool) {
|
// Note: to be used only with batch jobs that affect multiple versions through
|
||||||
|
// a single action. e.g batch-expire has an option to expire all versions of an
|
||||||
|
// object which matches the given filters.
|
||||||
|
func (ri *batchJobInfo) trackMultipleObjectVersions(bucket string, info ObjectInfo, success bool) {
|
||||||
|
if success {
|
||||||
|
ri.Objects += int64(info.NumVersions)
|
||||||
|
} else {
|
||||||
|
ri.ObjectsFailed += int64(info.NumVersions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo, success bool) {
|
||||||
if ri == nil {
|
if ri == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -824,7 +921,7 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
|
|||||||
|
|
||||||
ri.Bucket = bucket
|
ri.Bucket = bucket
|
||||||
ri.Object = info.Name
|
ri.Object = info.Name
|
||||||
ri.countItem(info.Size, info.DeleteMarker, failed)
|
ri.countItem(info.Size, info.DeleteMarker, success)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start start the batch replication job, resumes if there was a pending job via "job.ID"
|
// Start start the batch replication job, resumes if there was a pending job via "job.ID"
|
||||||
@@ -837,6 +934,9 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if err := ri.load(ctx, api, job); err != nil {
|
if err := ri.load(ctx, api, job); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if ri.Complete {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
lastObject := ri.Object
|
lastObject := ri.Object
|
||||||
|
|
||||||
@@ -907,12 +1007,9 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
// None of the provided metadata filters match skip the object.
|
// None of the provided metadata filters match skip the object.
|
||||||
return false
|
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
|
// if one of source or target is non MinIO, just replicate the top most version like `mc mirror`
|
||||||
|
return !((r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3) && !info.IsLatest)
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(r.Target.Endpoint)
|
u, err := url.Parse(r.Target.Endpoint)
|
||||||
@@ -931,8 +1028,73 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
||||||
|
|
||||||
|
var (
|
||||||
|
walkCh = make(chan ObjectInfo, 100)
|
||||||
|
slowCh = make(chan ObjectInfo, 100)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
||||||
|
go func() {
|
||||||
|
defer close(slowCh)
|
||||||
|
|
||||||
|
// Snowball currently needs the high level minio-go Client, not the Core one
|
||||||
|
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
||||||
|
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||||
|
Secure: u.Scheme == "https",
|
||||||
|
Transport: getRemoteInstanceTransport,
|
||||||
|
BucketLookup: lookupStyle(r.Target.Path),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already validated before arriving here
|
||||||
|
smallerThan, _ := humanize.ParseBytes(*r.Source.Snowball.SmallerThan)
|
||||||
|
|
||||||
|
var (
|
||||||
|
obj = ObjectInfo{}
|
||||||
|
batch = make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
||||||
|
valid = true
|
||||||
|
)
|
||||||
|
|
||||||
|
for valid {
|
||||||
|
obj, valid = <-walkCh
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
goto write
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.DeleteMarker || !obj.VersionPurgeStatus.Empty() || obj.Size >= int64(smallerThan) {
|
||||||
|
slowCh <- obj
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
batch = append(batch, obj)
|
||||||
|
|
||||||
|
if len(batch) < *r.Source.Snowball.Batch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
write:
|
||||||
|
if len(batch) > 0 {
|
||||||
|
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
for _, b := range batch {
|
||||||
|
slowCh <- b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch = batch[:0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
slowCh = walkCh
|
||||||
|
}
|
||||||
|
|
||||||
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -944,6 +1106,11 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
walkQuorum := env.Get("_MINIO_BATCH_REPLICATION_WALK_QUORUM", "strict")
|
||||||
|
if walkQuorum == "" {
|
||||||
|
walkQuorum = "strict"
|
||||||
|
}
|
||||||
|
|
||||||
retryAttempts := ri.RetryAttempts
|
retryAttempts := ri.RetryAttempts
|
||||||
retry := false
|
retry := false
|
||||||
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
@@ -953,10 +1120,10 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
// one of source/target is s3, skip delete marker and all versions under the same object name.
|
// 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
|
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, walkCh, WalkOptions{
|
||||||
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, ObjectOptions{
|
Marker: lastObject,
|
||||||
WalkMarker: lastObject,
|
Filter: selectObj,
|
||||||
WalkFilter: selectObj,
|
AskDisks: walkQuorum,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
// Do not need to retry if we can't list objects on source.
|
// Do not need to retry if we can't list objects on source.
|
||||||
@@ -966,7 +1133,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
prevObj := ""
|
prevObj := ""
|
||||||
|
|
||||||
skipReplicate := false
|
skipReplicate := false
|
||||||
for result := range results {
|
for result := range slowCh {
|
||||||
result := result
|
result := result
|
||||||
if result.Name != prevObj {
|
if result.Name != prevObj {
|
||||||
prevObj = result.Name
|
prevObj = result.Name
|
||||||
@@ -979,7 +1146,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
go func() {
|
go func() {
|
||||||
defer wk.Give()
|
defer wk.Give()
|
||||||
|
|
||||||
stopFn := globalBatchJobsMetrics.trace(batchReplicationMetricObject, job.ID, attempts, result)
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricReplication, job.ID, attempts)
|
||||||
success := true
|
success := true
|
||||||
if err := r.ReplicateToTarget(ctx, api, c, result, retry); err != nil {
|
if err := r.ReplicateToTarget(ctx, api, c, result, retry); err != nil {
|
||||||
if miniogo.ToErrorResponse(err).Code == "PreconditionFailed" {
|
if miniogo.ToErrorResponse(err).Code == "PreconditionFailed" {
|
||||||
@@ -990,16 +1157,20 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
|
if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stopFn(err)
|
stopFn(result, err)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
success = false
|
success = false
|
||||||
} else {
|
} else {
|
||||||
stopFn(nil)
|
stopFn(result, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
|
ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
|
||||||
|
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
||||||
|
time.Sleep(wait)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wk.Wait()
|
wk.Wait()
|
||||||
@@ -1080,6 +1251,9 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
if err := r.Source.Type.Validate(); err != nil {
|
if err := r.Source.Type.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := r.Source.Snowball.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if r.Source.Creds.Empty() && r.Target.Creds.Empty() {
|
if r.Source.Creds.Empty() && r.Target.Creds.Empty() {
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -1201,6 +1375,8 @@ func (j BatchJobRequest) Type() madmin.BatchJobType {
|
|||||||
return madmin.BatchJobReplicate
|
return madmin.BatchJobReplicate
|
||||||
case j.KeyRotate != nil:
|
case j.KeyRotate != nil:
|
||||||
return madmin.BatchJobKeyRotate
|
return madmin.BatchJobKeyRotate
|
||||||
|
case j.Expire != nil:
|
||||||
|
return madmin.BatchJobExpire
|
||||||
}
|
}
|
||||||
return madmin.BatchJobType("unknown")
|
return madmin.BatchJobType("unknown")
|
||||||
}
|
}
|
||||||
@@ -1213,6 +1389,8 @@ func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error {
|
|||||||
return j.Replicate.Validate(ctx, j, o)
|
return j.Replicate.Validate(ctx, j, o)
|
||||||
case j.KeyRotate != nil:
|
case j.KeyRotate != nil:
|
||||||
return j.KeyRotate.Validate(ctx, j, o)
|
return j.KeyRotate.Validate(ctx, j, o)
|
||||||
|
case j.Expire != nil:
|
||||||
|
return j.Expire.Validate(ctx, j, o)
|
||||||
}
|
}
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -1223,12 +1401,14 @@ func (j BatchJobRequest) delete(ctx context.Context, api ObjectLayer) {
|
|||||||
deleteConfig(ctx, api, pathJoin(j.Location, batchReplName))
|
deleteConfig(ctx, api, pathJoin(j.Location, batchReplName))
|
||||||
case j.KeyRotate != nil:
|
case j.KeyRotate != nil:
|
||||||
deleteConfig(ctx, api, pathJoin(j.Location, batchKeyRotationName))
|
deleteConfig(ctx, api, pathJoin(j.Location, batchKeyRotationName))
|
||||||
|
case j.Expire != nil:
|
||||||
|
deleteConfig(ctx, api, pathJoin(j.Location, batchExpireName))
|
||||||
}
|
}
|
||||||
deleteConfig(ctx, api, j.Location)
|
deleteConfig(ctx, api, j.Location)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error {
|
func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error {
|
||||||
if j.Replicate == nil && j.KeyRotate == nil {
|
if j.Replicate == nil && j.KeyRotate == nil && j.Expire == nil {
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1297,7 +1477,7 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
|||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := objectAPI.Walk(ctx, minioMetaBucket, batchJobPrefix, resultCh, ObjectOptions{}); err != nil {
|
if err := objectAPI.Walk(ctx, minioMetaBucket, batchJobPrefix, resultCh, WalkOptions{}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1384,7 +1564,35 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
job := &BatchJobRequest{}
|
job := &BatchJobRequest{}
|
||||||
if err = yaml.UnmarshalStrict(buf, job); err != nil {
|
if err = yaml.Unmarshal(buf, job); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill with default values
|
||||||
|
if job.Replicate != nil {
|
||||||
|
if job.Replicate.Source.Snowball.Disable == nil {
|
||||||
|
job.Replicate.Source.Snowball.Disable = ptr(false)
|
||||||
|
}
|
||||||
|
if job.Replicate.Source.Snowball.Batch == nil {
|
||||||
|
job.Replicate.Source.Snowball.Batch = ptr(100)
|
||||||
|
}
|
||||||
|
if job.Replicate.Source.Snowball.InMemory == nil {
|
||||||
|
job.Replicate.Source.Snowball.InMemory = ptr(true)
|
||||||
|
}
|
||||||
|
if job.Replicate.Source.Snowball.Compress == nil {
|
||||||
|
job.Replicate.Source.Snowball.Compress = ptr(false)
|
||||||
|
}
|
||||||
|
if job.Replicate.Source.Snowball.SmallerThan == nil {
|
||||||
|
job.Replicate.Source.Snowball.SmallerThan = ptr("5MiB")
|
||||||
|
}
|
||||||
|
if job.Replicate.Source.Snowball.SkipErrs == nil {
|
||||||
|
job.Replicate.Source.Snowball.SkipErrs = ptr(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the incoming job request
|
||||||
|
if err := job.Validate(ctx, objectAPI); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1486,7 +1694,7 @@ func (j *BatchJobPool) resume() {
|
|||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan ObjectInfo, 100)
|
||||||
ctx, cancel := context.WithCancel(j.ctx)
|
ctx, cancel := context.WithCancel(j.ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobPrefix, results, ObjectOptions{}); err != nil {
|
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobPrefix, results, WalkOptions{}); err != nil {
|
||||||
logger.LogIf(j.ctx, err)
|
logger.LogIf(j.ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1525,7 +1733,8 @@ func (j *BatchJobPool) AddWorker() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if job.Replicate != nil {
|
switch {
|
||||||
|
case job.Replicate != nil:
|
||||||
if job.Replicate.RemoteToLocal() {
|
if job.Replicate.RemoteToLocal() {
|
||||||
if err := job.Replicate.StartFromSource(job.ctx, j.objLayer, *job); err != nil {
|
if err := job.Replicate.StartFromSource(job.ctx, j.objLayer, *job); err != nil {
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
@@ -1545,14 +1754,20 @@ func (j *BatchJobPool) AddWorker() {
|
|||||||
// Bucket not found proceed to delete such a job.
|
// Bucket not found proceed to delete such a job.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
case job.KeyRotate != nil:
|
||||||
if job.KeyRotate != nil {
|
|
||||||
if err := job.KeyRotate.Start(job.ctx, j.objLayer, *job); err != nil {
|
if err := job.KeyRotate.Start(job.ctx, j.objLayer, *job); err != nil {
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(j.ctx, err)
|
logger.LogIf(j.ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case job.Expire != nil:
|
||||||
|
if err := job.Expire.Start(job.ctx, j.objLayer, *job); err != nil {
|
||||||
|
if !isErrBucketNotFound(err) {
|
||||||
|
logger.LogIf(j.ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
job.delete(j.ctx, j.objLayer)
|
job.delete(j.ctx, j.objLayer)
|
||||||
j.canceler(job.ID, false)
|
j.canceler(job.ID, false)
|
||||||
@@ -1630,24 +1845,26 @@ type batchJobMetrics struct {
|
|||||||
type batchJobMetric uint8
|
type batchJobMetric uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
batchReplicationMetricObject batchJobMetric = iota
|
batchJobMetricReplication batchJobMetric = iota
|
||||||
batchKeyRotationMetricObject
|
batchJobMetricKeyRotation
|
||||||
|
batchJobMetricExpire
|
||||||
)
|
)
|
||||||
|
|
||||||
func batchJobTrace(d batchJobMetric, job string, startTime time.Time, duration time.Duration, info ObjectInfo, attempts int, err error) madmin.TraceInfo {
|
func batchJobTrace(d batchJobMetric, job string, startTime time.Time, duration time.Duration, info objTraceInfoer, attempts int, err error) madmin.TraceInfo {
|
||||||
var errStr string
|
var errStr string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStr = err.Error()
|
errStr = err.Error()
|
||||||
}
|
}
|
||||||
jobKind := "batchReplication"
|
|
||||||
traceType := madmin.TraceBatchReplication
|
traceType := madmin.TraceBatchReplication
|
||||||
if d == batchKeyRotationMetricObject {
|
switch d {
|
||||||
jobKind = "batchKeyRotation"
|
case batchJobMetricKeyRotation:
|
||||||
traceType = madmin.TraceBatchKeyRotation
|
traceType = madmin.TraceBatchKeyRotation
|
||||||
|
case batchJobMetricExpire:
|
||||||
|
traceType = madmin.TraceBatchExpire
|
||||||
}
|
}
|
||||||
funcName := fmt.Sprintf("%s.%s (job-name=%s)", jobKind, d.String(), job)
|
funcName := fmt.Sprintf("%s() (job-name=%s)", d.String(), job)
|
||||||
if attempts > 0 {
|
if attempts > 0 {
|
||||||
funcName = fmt.Sprintf("%s.%s (job-name=%s,attempts=%s)", jobKind, d.String(), job, humanize.Ordinal(attempts))
|
funcName = fmt.Sprintf("%s() (job-name=%s,attempts=%s)", d.String(), job, humanize.Ordinal(attempts))
|
||||||
}
|
}
|
||||||
return madmin.TraceInfo{
|
return madmin.TraceInfo{
|
||||||
TraceType: traceType,
|
TraceType: traceType,
|
||||||
@@ -1655,55 +1872,65 @@ func batchJobTrace(d batchJobMetric, job string, startTime time.Time, duration t
|
|||||||
NodeName: globalLocalNodeName,
|
NodeName: globalLocalNodeName,
|
||||||
FuncName: funcName,
|
FuncName: funcName,
|
||||||
Duration: duration,
|
Duration: duration,
|
||||||
Path: info.Name,
|
Path: fmt.Sprintf("%s (versionID=%s)", info.TraceObjName(), info.TraceVersionID()),
|
||||||
Error: errStr,
|
Error: errStr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ri *batchJobInfo) metric() madmin.JobMetric {
|
||||||
|
m := madmin.JobMetric{
|
||||||
|
JobID: ri.JobID,
|
||||||
|
JobType: ri.JobType,
|
||||||
|
StartTime: ri.StartTime,
|
||||||
|
LastUpdate: ri.LastUpdate,
|
||||||
|
RetryAttempts: ri.RetryAttempts,
|
||||||
|
Complete: ri.Complete,
|
||||||
|
Failed: ri.Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ri.JobType {
|
||||||
|
case string(madmin.BatchJobReplicate):
|
||||||
|
m.Replicate = &madmin.ReplicateInfo{
|
||||||
|
Bucket: ri.Bucket,
|
||||||
|
Object: ri.Object,
|
||||||
|
Objects: ri.Objects,
|
||||||
|
ObjectsFailed: ri.ObjectsFailed,
|
||||||
|
BytesTransferred: ri.BytesTransferred,
|
||||||
|
BytesFailed: ri.BytesFailed,
|
||||||
|
}
|
||||||
|
case string(madmin.BatchJobKeyRotate):
|
||||||
|
m.KeyRotate = &madmin.KeyRotationInfo{
|
||||||
|
Bucket: ri.Bucket,
|
||||||
|
Object: ri.Object,
|
||||||
|
Objects: ri.Objects,
|
||||||
|
ObjectsFailed: ri.ObjectsFailed,
|
||||||
|
}
|
||||||
|
case string(madmin.BatchJobExpire):
|
||||||
|
m.Expired = &madmin.ExpirationInfo{
|
||||||
|
Bucket: ri.Bucket,
|
||||||
|
Object: ri.Object,
|
||||||
|
Objects: ri.Objects,
|
||||||
|
ObjectsFailed: ri.ObjectsFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func (m *batchJobMetrics) report(jobID string) (metrics *madmin.BatchJobMetrics) {
|
func (m *batchJobMetrics) report(jobID string) (metrics *madmin.BatchJobMetrics) {
|
||||||
metrics = &madmin.BatchJobMetrics{CollectedAt: time.Now(), Jobs: make(map[string]madmin.JobMetric)}
|
metrics = &madmin.BatchJobMetrics{CollectedAt: time.Now(), Jobs: make(map[string]madmin.JobMetric)}
|
||||||
m.RLock()
|
m.RLock()
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
|
|
||||||
match := true
|
if jobID != "" {
|
||||||
|
if job, ok := m.metrics[jobID]; ok {
|
||||||
|
metrics.Jobs[jobID] = job.metric()
|
||||||
|
}
|
||||||
|
return metrics
|
||||||
|
}
|
||||||
|
|
||||||
for id, job := range m.metrics {
|
for id, job := range m.metrics {
|
||||||
if jobID != "" {
|
metrics.Jobs[id] = job.metric()
|
||||||
match = id == jobID
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
m := madmin.JobMetric{
|
|
||||||
JobID: job.JobID,
|
|
||||||
JobType: job.JobType,
|
|
||||||
StartTime: job.StartTime,
|
|
||||||
LastUpdate: job.LastUpdate,
|
|
||||||
RetryAttempts: job.RetryAttempts,
|
|
||||||
Complete: job.Complete,
|
|
||||||
Failed: job.Failed,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch job.JobType {
|
|
||||||
case string(madmin.BatchJobReplicate):
|
|
||||||
m.Replicate = &madmin.ReplicateInfo{
|
|
||||||
Bucket: job.Bucket,
|
|
||||||
Object: job.Object,
|
|
||||||
Objects: job.Objects,
|
|
||||||
ObjectsFailed: job.ObjectsFailed,
|
|
||||||
BytesTransferred: job.BytesTransferred,
|
|
||||||
BytesFailed: job.BytesFailed,
|
|
||||||
}
|
|
||||||
case string(madmin.BatchJobKeyRotate):
|
|
||||||
m.KeyRotate = &madmin.KeyRotationInfo{
|
|
||||||
Bucket: job.Bucket,
|
|
||||||
Object: job.Object,
|
|
||||||
Objects: job.Objects,
|
|
||||||
ObjectsFailed: job.ObjectsFailed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics.Jobs[id] = m
|
|
||||||
}
|
}
|
||||||
return metrics
|
return metrics
|
||||||
}
|
}
|
||||||
@@ -1748,19 +1975,52 @@ func (m *batchJobMetrics) save(jobID string, ri *batchJobInfo) {
|
|||||||
m.metrics[jobID] = ri.clone()
|
m.metrics[jobID] = ri.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *batchJobMetrics) trace(d batchJobMetric, job string, attempts int, info ObjectInfo) func(err error) {
|
type objTraceInfoer interface {
|
||||||
|
TraceObjName() string
|
||||||
|
TraceVersionID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceObjName returns name of object being traced
|
||||||
|
func (td ObjectToDelete) TraceObjName() string {
|
||||||
|
return td.ObjectName
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceVersionID returns version-id of object being traced
|
||||||
|
func (td ObjectToDelete) TraceVersionID() string {
|
||||||
|
return td.VersionID
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceObjName returns name of object being traced
|
||||||
|
func (oi ObjectInfo) TraceObjName() string {
|
||||||
|
return oi.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceVersionID returns version-id of object being traced
|
||||||
|
func (oi ObjectInfo) TraceVersionID() string {
|
||||||
|
return oi.VersionID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *batchJobMetrics) trace(d batchJobMetric, job string, attempts int) func(info objTraceInfoer, err error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
return func(err error) {
|
return func(info objTraceInfoer, err error) {
|
||||||
duration := time.Since(startTime)
|
duration := time.Since(startTime)
|
||||||
|
if globalTrace.NumSubscribers(madmin.TraceBatch) > 0 {
|
||||||
|
globalTrace.Publish(batchJobTrace(d, job, startTime, duration, info, attempts, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
switch d {
|
switch d {
|
||||||
case batchReplicationMetricObject:
|
case batchJobMetricReplication:
|
||||||
if globalTrace.NumSubscribers(madmin.TraceBatchReplication) > 0 {
|
if globalTrace.NumSubscribers(madmin.TraceBatchReplication) > 0 {
|
||||||
globalTrace.Publish(batchJobTrace(d, job, startTime, duration, info, attempts, err))
|
globalTrace.Publish(batchJobTrace(d, job, startTime, duration, info, attempts, err))
|
||||||
}
|
}
|
||||||
case batchKeyRotationMetricObject:
|
case batchJobMetricKeyRotation:
|
||||||
if globalTrace.NumSubscribers(madmin.TraceBatchKeyRotation) > 0 {
|
if globalTrace.NumSubscribers(madmin.TraceBatchKeyRotation) > 0 {
|
||||||
globalTrace.Publish(batchJobTrace(d, job, startTime, duration, info, attempts, err))
|
globalTrace.Publish(batchJobTrace(d, job, startTime, duration, info, attempts, err))
|
||||||
}
|
}
|
||||||
|
case batchJobMetricExpire:
|
||||||
|
if globalTrace.NumSubscribers(madmin.TraceBatchExpire) > 0 {
|
||||||
|
globalTrace.Publish(batchJobTrace(d, job, startTime, duration, info, attempts, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,24 @@ func (z *BatchJobRequest) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "Expire":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Expire")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Expire = nil
|
||||||
|
} else {
|
||||||
|
if z.Expire == nil {
|
||||||
|
z.Expire = new(BatchJobExpire)
|
||||||
|
}
|
||||||
|
err = z.Expire.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Expire")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -97,9 +115,9 @@ func (z *BatchJobRequest) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 6
|
// map header, size 7
|
||||||
// write "ID"
|
// write "ID"
|
||||||
err = en.Append(0x86, 0xa2, 0x49, 0x44)
|
err = en.Append(0x87, 0xa2, 0x49, 0x44)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -172,15 +190,32 @@ func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// write "Expire"
|
||||||
|
err = en.Append(0xa6, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Expire == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = z.Expire.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Expire")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *BatchJobRequest) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *BatchJobRequest) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 6
|
// map header, size 7
|
||||||
// string "ID"
|
// string "ID"
|
||||||
o = append(o, 0x86, 0xa2, 0x49, 0x44)
|
o = append(o, 0x87, 0xa2, 0x49, 0x44)
|
||||||
o = msgp.AppendString(o, z.ID)
|
o = msgp.AppendString(o, z.ID)
|
||||||
// string "User"
|
// string "User"
|
||||||
o = append(o, 0xa4, 0x55, 0x73, 0x65, 0x72)
|
o = append(o, 0xa4, 0x55, 0x73, 0x65, 0x72)
|
||||||
@@ -213,6 +248,17 @@ func (z *BatchJobRequest) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// string "Expire"
|
||||||
|
o = append(o, 0xa6, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65)
|
||||||
|
if z.Expire == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o, err = z.Expire.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Expire")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +338,23 @@ func (z *BatchJobRequest) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "Expire":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Expire = nil
|
||||||
|
} else {
|
||||||
|
if z.Expire == nil {
|
||||||
|
z.Expire = new(BatchJobExpire)
|
||||||
|
}
|
||||||
|
bts, err = z.Expire.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Expire")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -318,6 +381,12 @@ func (z *BatchJobRequest) Msgsize() (s int) {
|
|||||||
} else {
|
} else {
|
||||||
s += z.KeyRotate.Msgsize()
|
s += z.KeyRotate.Msgsize()
|
||||||
}
|
}
|
||||||
|
s += 7
|
||||||
|
if z.Expire == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += z.Expire.Msgsize()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,24 +18,66 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/pkg/v2/wildcard"
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE
|
//go:generate msgp -file $GOFILE
|
||||||
|
//msgp:ignore BatchJobYamlErr
|
||||||
|
|
||||||
|
// BatchJobYamlErr can be used to return yaml validation errors with line,
|
||||||
|
// column information guiding user to fix syntax errors
|
||||||
|
type BatchJobYamlErr struct {
|
||||||
|
line, col int
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
// message returns the error message excluding line, col information.
|
||||||
|
// Intended to be used in unit tests.
|
||||||
|
func (b BatchJobYamlErr) message() string {
|
||||||
|
return b.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements Error interface
|
||||||
|
func (b BatchJobYamlErr) Error() string {
|
||||||
|
return fmt.Sprintf("%s\n Hint: error near line: %d, col: %d", b.msg, b.line, b.col)
|
||||||
|
}
|
||||||
|
|
||||||
// BatchJobKV is a key-value data type which supports wildcard matching
|
// BatchJobKV is a key-value data type which supports wildcard matching
|
||||||
type BatchJobKV struct {
|
type BatchJobKV struct {
|
||||||
Key string `yaml:"key" json:"key"`
|
line, col int
|
||||||
Value string `yaml:"value" json:"value"`
|
Key string `yaml:"key" json:"key"`
|
||||||
|
Value string `yaml:"value" json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.Unmarshaler = &BatchJobKV{}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobKV extends default unmarshal to extract line, col information.
|
||||||
|
func (kv *BatchJobKV) UnmarshalYAML(val *yaml.Node) error {
|
||||||
|
type jobKV BatchJobKV
|
||||||
|
var tmp jobKV
|
||||||
|
err := val.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*kv = BatchJobKV(tmp)
|
||||||
|
kv.line, kv.col = val.Line, val.Column
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate returns an error if key is empty
|
// Validate returns an error if key is empty
|
||||||
func (kv BatchJobKV) Validate() error {
|
func (kv BatchJobKV) Validate() error {
|
||||||
if kv.Key == "" {
|
if kv.Key == "" {
|
||||||
return errInvalidArgument
|
return BatchJobYamlErr{
|
||||||
|
line: kv.line,
|
||||||
|
col: kv.col,
|
||||||
|
msg: "key can't be empty",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -59,25 +101,187 @@ func (kv BatchJobKV) Match(ikv BatchJobKV) bool {
|
|||||||
// BatchJobNotification stores notification endpoint and token information.
|
// BatchJobNotification stores notification endpoint and token information.
|
||||||
// Used by batch jobs to notify of their status.
|
// Used by batch jobs to notify of their status.
|
||||||
type BatchJobNotification struct {
|
type BatchJobNotification struct {
|
||||||
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
line, col int
|
||||||
Token string `yaml:"token" json:"token"`
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Token string `yaml:"token" json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.Unmarshaler = &BatchJobNotification{}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobNotification extends unmarshal to extract line, column information
|
||||||
|
func (b *BatchJobNotification) UnmarshalYAML(val *yaml.Node) error {
|
||||||
|
type notification BatchJobNotification
|
||||||
|
var tmp notification
|
||||||
|
err := val.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*b = BatchJobNotification(tmp)
|
||||||
|
b.line, b.col = val.Line, val.Column
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchJobRetry stores retry configuration used in the event of failures.
|
// BatchJobRetry stores retry configuration used in the event of failures.
|
||||||
type BatchJobRetry struct {
|
type BatchJobRetry struct {
|
||||||
Attempts int `yaml:"attempts" json:"attempts"` // number of retry attempts
|
line, col int
|
||||||
Delay time.Duration `yaml:"delay" json:"delay"` // delay between each retries
|
Attempts int `yaml:"attempts" json:"attempts"` // number of retry attempts
|
||||||
|
Delay time.Duration `yaml:"delay" json:"delay"` // delay between each retries
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.Unmarshaler = &BatchJobRetry{}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobRetry extends unmarshal to extract line, column information
|
||||||
|
func (r *BatchJobRetry) UnmarshalYAML(val *yaml.Node) error {
|
||||||
|
type retry BatchJobRetry
|
||||||
|
var tmp retry
|
||||||
|
err := val.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*r = BatchJobRetry(tmp)
|
||||||
|
r.line, r.col = val.Line, val.Column
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates input replicate retries.
|
// Validate validates input replicate retries.
|
||||||
func (r BatchJobRetry) Validate() error {
|
func (r BatchJobRetry) Validate() error {
|
||||||
if r.Attempts < 0 {
|
if r.Attempts < 0 {
|
||||||
return errInvalidArgument
|
return BatchJobYamlErr{
|
||||||
|
line: r.line,
|
||||||
|
col: r.col,
|
||||||
|
msg: "Invalid arguments specified",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Delay < 0 {
|
if r.Delay < 0 {
|
||||||
return errInvalidArgument
|
return BatchJobYamlErr{
|
||||||
|
line: r.line,
|
||||||
|
col: r.col,
|
||||||
|
msg: "Invalid arguments specified",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// # snowball based archive transfer is by default enabled when source
|
||||||
|
// # is local and target is remote which is also minio.
|
||||||
|
// snowball:
|
||||||
|
// disable: false # optionally turn-off snowball archive transfer
|
||||||
|
// batch: 100 # upto this many objects per archive
|
||||||
|
// inmemory: true # indicates if the archive must be staged locally or in-memory
|
||||||
|
// compress: true # S2/Snappy compressed archive
|
||||||
|
// smallerThan: 5MiB # create archive for all objects smaller than 5MiB
|
||||||
|
// skipErrs: false # skips any source side read() errors
|
||||||
|
|
||||||
|
// BatchJobSnowball describes the snowball feature when replicating objects from a local source to a remote target
|
||||||
|
type BatchJobSnowball struct {
|
||||||
|
line, col int
|
||||||
|
Disable *bool `yaml:"disable" json:"disable"`
|
||||||
|
Batch *int `yaml:"batch" json:"batch"`
|
||||||
|
InMemory *bool `yaml:"inmemory" json:"inmemory"`
|
||||||
|
Compress *bool `yaml:"compress" json:"compress"`
|
||||||
|
SmallerThan *string `yaml:"smallerThan" json:"smallerThan"`
|
||||||
|
SkipErrs *bool `yaml:"skipErrs" json:"skipErrs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.Unmarshaler = &BatchJobSnowball{}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobSnowball extends unmarshal to extract line, column information
|
||||||
|
func (b *BatchJobSnowball) UnmarshalYAML(val *yaml.Node) error {
|
||||||
|
type snowball BatchJobSnowball
|
||||||
|
var tmp snowball
|
||||||
|
err := val.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*b = BatchJobSnowball(tmp)
|
||||||
|
b.line, b.col = val.Line, val.Column
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the snowball parameters in the job description
|
||||||
|
func (b BatchJobSnowball) Validate() error {
|
||||||
|
if *b.Batch <= 0 {
|
||||||
|
return BatchJobYamlErr{
|
||||||
|
line: b.line,
|
||||||
|
col: b.col,
|
||||||
|
msg: "batch number should be non positive zero",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := humanize.ParseBytes(*b.SmallerThan)
|
||||||
|
return BatchJobYamlErr{
|
||||||
|
line: b.line,
|
||||||
|
col: b.col,
|
||||||
|
msg: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobSizeFilter supports size based filters - LesserThan and GreaterThan
|
||||||
|
type BatchJobSizeFilter struct {
|
||||||
|
line, col int
|
||||||
|
UpperBound BatchJobSize `yaml:"lessThan" json:"lessThan"`
|
||||||
|
LowerBound BatchJobSize `yaml:"greaterThan" json:"greaterThan"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML - BatchJobSizeFilter extends unmarshal to extract line, column information
|
||||||
|
func (sf *BatchJobSizeFilter) UnmarshalYAML(val *yaml.Node) error {
|
||||||
|
type sizeFilter BatchJobSizeFilter
|
||||||
|
var tmp sizeFilter
|
||||||
|
err := val.Decode(&tmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*sf = BatchJobSizeFilter(tmp)
|
||||||
|
sf.line, sf.col = val.Line, val.Column
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRange returns true in the following cases and false otherwise,
|
||||||
|
// - sf.LowerBound < sz, when sf.LowerBound alone is specified
|
||||||
|
// - sz < sf.UpperBound, when sf.UpperBound alone is specified
|
||||||
|
// - sf.LowerBound < sz < sf.UpperBound when both are specified,
|
||||||
|
func (sf BatchJobSizeFilter) InRange(sz int64) bool {
|
||||||
|
if sf.UpperBound > 0 && sz > int64(sf.UpperBound) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if sf.LowerBound > 0 && sz < int64(sf.LowerBound) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if sf is a valid batch-job size filter
|
||||||
|
func (sf BatchJobSizeFilter) Validate() error {
|
||||||
|
if sf.LowerBound > 0 && sf.UpperBound > 0 && sf.LowerBound >= sf.UpperBound {
|
||||||
|
return BatchJobYamlErr{
|
||||||
|
line: sf.line,
|
||||||
|
col: sf.col,
|
||||||
|
msg: "invalid batch-job size filter",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobSize supports humanized byte values in yaml files type BatchJobSize uint64
|
||||||
|
type BatchJobSize int64
|
||||||
|
|
||||||
|
// UnmarshalYAML to parse humanized byte values
|
||||||
|
func (s *BatchJobSize) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var batchExpireSz string
|
||||||
|
err := unmarshal(&batchExpireSz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sz, err := humanize.ParseBytes(batchExpireSz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*s = BatchJobSize(sz)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -389,3 +389,666 @@ func (z BatchJobRetry) Msgsize() (s int) {
|
|||||||
s = 1 + 9 + msgp.IntSize + 6 + msgp.DurationSize
|
s = 1 + 9 + msgp.IntSize + 6 + msgp.DurationSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobSize) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
{
|
||||||
|
var zb0001 int64
|
||||||
|
zb0001, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(*z) = BatchJobSize(zb0001)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobSize) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
err = en.WriteInt64(int64(z))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobSize) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
o = msgp.AppendInt64(o, int64(z))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobSize) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
{
|
||||||
|
var zb0001 int64
|
||||||
|
zb0001, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(*z) = BatchJobSize(zb0001)
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z BatchJobSize) Msgsize() (s int) {
|
||||||
|
s = msgp.Int64Size
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobSizeFilter) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "UpperBound":
|
||||||
|
{
|
||||||
|
var zb0002 int64
|
||||||
|
zb0002, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "UpperBound")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.UpperBound = BatchJobSize(zb0002)
|
||||||
|
}
|
||||||
|
case "LowerBound":
|
||||||
|
{
|
||||||
|
var zb0003 int64
|
||||||
|
zb0003, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LowerBound")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.LowerBound = BatchJobSize(zb0003)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobSizeFilter) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "UpperBound"
|
||||||
|
err = en.Append(0x82, 0xaa, 0x55, 0x70, 0x70, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(int64(z.UpperBound))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "UpperBound")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "LowerBound"
|
||||||
|
err = en.Append(0xaa, 0x4c, 0x6f, 0x77, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(int64(z.LowerBound))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LowerBound")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobSizeFilter) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "UpperBound"
|
||||||
|
o = append(o, 0x82, 0xaa, 0x55, 0x70, 0x70, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64)
|
||||||
|
o = msgp.AppendInt64(o, int64(z.UpperBound))
|
||||||
|
// string "LowerBound"
|
||||||
|
o = append(o, 0xaa, 0x4c, 0x6f, 0x77, 0x65, 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64)
|
||||||
|
o = msgp.AppendInt64(o, int64(z.LowerBound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobSizeFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "UpperBound":
|
||||||
|
{
|
||||||
|
var zb0002 int64
|
||||||
|
zb0002, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "UpperBound")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.UpperBound = BatchJobSize(zb0002)
|
||||||
|
}
|
||||||
|
case "LowerBound":
|
||||||
|
{
|
||||||
|
var zb0003 int64
|
||||||
|
zb0003, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LowerBound")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.LowerBound = BatchJobSize(zb0003)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z BatchJobSizeFilter) Msgsize() (s int) {
|
||||||
|
s = 1 + 11 + msgp.Int64Size + 11 + msgp.Int64Size
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobSnowball) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Disable":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Disable = nil
|
||||||
|
} else {
|
||||||
|
if z.Disable == nil {
|
||||||
|
z.Disable = new(bool)
|
||||||
|
}
|
||||||
|
*z.Disable, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Batch":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Batch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Batch = nil
|
||||||
|
} else {
|
||||||
|
if z.Batch == nil {
|
||||||
|
z.Batch = new(int)
|
||||||
|
}
|
||||||
|
*z.Batch, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Batch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "InMemory":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "InMemory")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.InMemory = nil
|
||||||
|
} else {
|
||||||
|
if z.InMemory == nil {
|
||||||
|
z.InMemory = new(bool)
|
||||||
|
}
|
||||||
|
*z.InMemory, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "InMemory")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Compress":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Compress")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Compress = nil
|
||||||
|
} else {
|
||||||
|
if z.Compress == nil {
|
||||||
|
z.Compress = new(bool)
|
||||||
|
}
|
||||||
|
*z.Compress, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Compress")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "SmallerThan":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SmallerThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.SmallerThan = nil
|
||||||
|
} else {
|
||||||
|
if z.SmallerThan == nil {
|
||||||
|
z.SmallerThan = new(string)
|
||||||
|
}
|
||||||
|
*z.SmallerThan, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SmallerThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "SkipErrs":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SkipErrs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.SkipErrs = nil
|
||||||
|
} else {
|
||||||
|
if z.SkipErrs == nil {
|
||||||
|
z.SkipErrs = new(bool)
|
||||||
|
}
|
||||||
|
*z.SkipErrs, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SkipErrs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *BatchJobSnowball) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 6
|
||||||
|
// write "Disable"
|
||||||
|
err = en.Append(0x86, 0xa7, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Disable == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = en.WriteBool(*z.Disable)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "Batch"
|
||||||
|
err = en.Append(0xa5, 0x42, 0x61, 0x74, 0x63, 0x68)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Batch == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = en.WriteInt(*z.Batch)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Batch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "InMemory"
|
||||||
|
err = en.Append(0xa8, 0x49, 0x6e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.InMemory == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = en.WriteBool(*z.InMemory)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "InMemory")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "Compress"
|
||||||
|
err = en.Append(0xa8, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Compress == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = en.WriteBool(*z.Compress)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Compress")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "SmallerThan"
|
||||||
|
err = en.Append(0xab, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.SmallerThan == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = en.WriteString(*z.SmallerThan)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SmallerThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "SkipErrs"
|
||||||
|
err = en.Append(0xa8, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x72, 0x72, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.SkipErrs == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = en.WriteBool(*z.SkipErrs)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SkipErrs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *BatchJobSnowball) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 6
|
||||||
|
// string "Disable"
|
||||||
|
o = append(o, 0x86, 0xa7, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65)
|
||||||
|
if z.Disable == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o = msgp.AppendBool(o, *z.Disable)
|
||||||
|
}
|
||||||
|
// string "Batch"
|
||||||
|
o = append(o, 0xa5, 0x42, 0x61, 0x74, 0x63, 0x68)
|
||||||
|
if z.Batch == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o = msgp.AppendInt(o, *z.Batch)
|
||||||
|
}
|
||||||
|
// string "InMemory"
|
||||||
|
o = append(o, 0xa8, 0x49, 0x6e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79)
|
||||||
|
if z.InMemory == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o = msgp.AppendBool(o, *z.InMemory)
|
||||||
|
}
|
||||||
|
// string "Compress"
|
||||||
|
o = append(o, 0xa8, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73)
|
||||||
|
if z.Compress == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o = msgp.AppendBool(o, *z.Compress)
|
||||||
|
}
|
||||||
|
// string "SmallerThan"
|
||||||
|
o = append(o, 0xab, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
||||||
|
if z.SmallerThan == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o = msgp.AppendString(o, *z.SmallerThan)
|
||||||
|
}
|
||||||
|
// string "SkipErrs"
|
||||||
|
o = append(o, 0xa8, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x72, 0x72, 0x73)
|
||||||
|
if z.SkipErrs == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o = msgp.AppendBool(o, *z.SkipErrs)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobSnowball) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Disable":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Disable = nil
|
||||||
|
} else {
|
||||||
|
if z.Disable == nil {
|
||||||
|
z.Disable = new(bool)
|
||||||
|
}
|
||||||
|
*z.Disable, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Batch":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Batch = nil
|
||||||
|
} else {
|
||||||
|
if z.Batch == nil {
|
||||||
|
z.Batch = new(int)
|
||||||
|
}
|
||||||
|
*z.Batch, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Batch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "InMemory":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.InMemory = nil
|
||||||
|
} else {
|
||||||
|
if z.InMemory == nil {
|
||||||
|
z.InMemory = new(bool)
|
||||||
|
}
|
||||||
|
*z.InMemory, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "InMemory")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Compress":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Compress = nil
|
||||||
|
} else {
|
||||||
|
if z.Compress == nil {
|
||||||
|
z.Compress = new(bool)
|
||||||
|
}
|
||||||
|
*z.Compress, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Compress")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "SmallerThan":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.SmallerThan = nil
|
||||||
|
} else {
|
||||||
|
if z.SmallerThan == nil {
|
||||||
|
z.SmallerThan = new(string)
|
||||||
|
}
|
||||||
|
*z.SmallerThan, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SmallerThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "SkipErrs":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.SkipErrs = nil
|
||||||
|
} else {
|
||||||
|
if z.SkipErrs == nil {
|
||||||
|
z.SkipErrs = new(bool)
|
||||||
|
}
|
||||||
|
*z.SkipErrs, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SkipErrs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *BatchJobSnowball) Msgsize() (s int) {
|
||||||
|
s = 1 + 8
|
||||||
|
if z.Disable == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += msgp.BoolSize
|
||||||
|
}
|
||||||
|
s += 6
|
||||||
|
if z.Batch == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += msgp.IntSize
|
||||||
|
}
|
||||||
|
s += 9
|
||||||
|
if z.InMemory == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += msgp.BoolSize
|
||||||
|
}
|
||||||
|
s += 9
|
||||||
|
if z.Compress == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += msgp.BoolSize
|
||||||
|
}
|
||||||
|
s += 12
|
||||||
|
if z.SmallerThan == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += msgp.StringPrefixSize + len(*z.SmallerThan)
|
||||||
|
}
|
||||||
|
s += 9
|
||||||
|
if z.SkipErrs == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += msgp.BoolSize
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -347,3 +347,229 @@ func BenchmarkDecodeBatchJobRetry(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobSizeFilter(t *testing.T) {
|
||||||
|
v := BatchJobSizeFilter{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobSizeFilter(b *testing.B) {
|
||||||
|
v := BatchJobSizeFilter{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobSizeFilter(b *testing.B) {
|
||||||
|
v := BatchJobSizeFilter{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobSizeFilter(b *testing.B) {
|
||||||
|
v := BatchJobSizeFilter{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobSizeFilter(t *testing.T) {
|
||||||
|
v := BatchJobSizeFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobSizeFilter Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobSizeFilter{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobSizeFilter(b *testing.B) {
|
||||||
|
v := BatchJobSizeFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobSizeFilter(b *testing.B) {
|
||||||
|
v := BatchJobSizeFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobSnowball(t *testing.T) {
|
||||||
|
v := BatchJobSnowball{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobSnowball(b *testing.B) {
|
||||||
|
v := BatchJobSnowball{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobSnowball(b *testing.B) {
|
||||||
|
v := BatchJobSnowball{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobSnowball(b *testing.B) {
|
||||||
|
v := BatchJobSnowball{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobSnowball(t *testing.T) {
|
||||||
|
v := BatchJobSnowball{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobSnowball Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobSnowball{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobSnowball(b *testing.B) {
|
||||||
|
v := BatchJobSnowball{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobSnowball(b *testing.B) {
|
||||||
|
v := BatchJobSnowball{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBatchJobSizeInRange(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
objSize int64
|
||||||
|
sizeFilter BatchJobSizeFilter
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// 1Mib < 2Mib < 10MiB -> in range
|
||||||
|
objSize: 2 << 20,
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 10 << 20,
|
||||||
|
LowerBound: 1 << 20,
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 2KiB < 1 MiB -> out of range from left
|
||||||
|
objSize: 2 << 10,
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 10 << 20,
|
||||||
|
LowerBound: 1 << 20,
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 11MiB > 10 MiB -> out of range from right
|
||||||
|
objSize: 11 << 20,
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 10 << 20,
|
||||||
|
LowerBound: 1 << 20,
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 2MiB < 10MiB -> in range
|
||||||
|
objSize: 2 << 20,
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 10 << 20,
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 2MiB > 1MiB -> in range
|
||||||
|
objSize: 2 << 20,
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
LowerBound: 1 << 20,
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("test-%d", i+1), func(t *testing.T) {
|
||||||
|
if got := test.sizeFilter.InRange(test.objSize); got != test.want {
|
||||||
|
t.Fatalf("Expected %v but got %v", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchJobSizeValidate(t *testing.T) {
|
||||||
|
errInvalidBatchJobSizeFilter := BatchJobYamlErr{
|
||||||
|
msg: "invalid batch-job size filter",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
sizeFilter BatchJobSizeFilter
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Unspecified size filter is a valid filter
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 0,
|
||||||
|
LowerBound: 0,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 0,
|
||||||
|
LowerBound: 1 << 20,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 10 << 20,
|
||||||
|
LowerBound: 0,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// LowerBound > UpperBound -> empty range
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 1 << 20,
|
||||||
|
LowerBound: 10 << 20,
|
||||||
|
},
|
||||||
|
err: errInvalidBatchJobSizeFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// LowerBound == UpperBound -> empty range
|
||||||
|
sizeFilter: BatchJobSizeFilter{
|
||||||
|
UpperBound: 1 << 20,
|
||||||
|
LowerBound: 1 << 20,
|
||||||
|
},
|
||||||
|
err: errInvalidBatchJobSizeFilter,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("test-%d", i+1), func(t *testing.T) {
|
||||||
|
err := test.sizeFilter.Validate()
|
||||||
|
if err != nil {
|
||||||
|
gotErr := err.(BatchJobYamlErr)
|
||||||
|
testErr := test.err.(BatchJobYamlErr)
|
||||||
|
if gotErr.message() != testErr.message() {
|
||||||
|
t.Fatalf("Expected %v but got %v", test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil && test.err != nil {
|
||||||
|
t.Fatalf("Expected %v but got nil", test.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,6 +155,7 @@ type BatchJobReplicateSource struct {
|
|||||||
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
Path string `yaml:"path" json:"path"`
|
Path string `yaml:"path" json:"path"`
|
||||||
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
||||||
|
Snowball BatchJobSnowball `yaml:"snowball" json:"snowball"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidPath returns true if path is valid
|
// ValidPath returns true if path is valid
|
||||||
|
|||||||
@@ -469,6 +469,12 @@ func (z *BatchJobReplicateSource) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "Snowball":
|
||||||
|
err = z.Snowball.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Snowball")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -482,9 +488,9 @@ func (z *BatchJobReplicateSource) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *BatchJobReplicateSource) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *BatchJobReplicateSource) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 6
|
// map header, size 7
|
||||||
// write "Type"
|
// write "Type"
|
||||||
err = en.Append(0x86, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
err = en.Append(0x87, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -569,15 +575,25 @@ func (z *BatchJobReplicateSource) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Creds", "SessionToken")
|
err = msgp.WrapError(err, "Creds", "SessionToken")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "Snowball"
|
||||||
|
err = en.Append(0xa8, 0x53, 0x6e, 0x6f, 0x77, 0x62, 0x61, 0x6c, 0x6c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = z.Snowball.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Snowball")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *BatchJobReplicateSource) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *BatchJobReplicateSource) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 6
|
// map header, size 7
|
||||||
// string "Type"
|
// string "Type"
|
||||||
o = append(o, 0x86, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
o = append(o, 0x87, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||||
o = msgp.AppendString(o, string(z.Type))
|
o = msgp.AppendString(o, string(z.Type))
|
||||||
// string "Bucket"
|
// string "Bucket"
|
||||||
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||||
@@ -603,6 +619,13 @@ func (z *BatchJobReplicateSource) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "SessionToken"
|
// string "SessionToken"
|
||||||
o = append(o, 0xac, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
o = append(o, 0xac, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||||
o = msgp.AppendString(o, z.Creds.SessionToken)
|
o = msgp.AppendString(o, z.Creds.SessionToken)
|
||||||
|
// string "Snowball"
|
||||||
|
o = append(o, 0xa8, 0x53, 0x6e, 0x6f, 0x77, 0x62, 0x61, 0x6c, 0x6c)
|
||||||
|
o, err = z.Snowball.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Snowball")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,6 +722,12 @@ func (z *BatchJobReplicateSource) UnmarshalMsg(bts []byte) (o []byte, err error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "Snowball":
|
||||||
|
bts, err = z.Snowball.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Snowball")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -713,7 +742,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
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *BatchJobReplicateSource) Msgsize() (s int) {
|
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) + 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)
|
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) + 9 + z.Snowball.Msgsize()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-7
@@ -154,7 +154,6 @@ type BatchJobKeyRotateV1 struct {
|
|||||||
Flags BatchJobKeyRotateFlags `yaml:"flags" json:"flags"`
|
Flags BatchJobKeyRotateFlags `yaml:"flags" json:"flags"`
|
||||||
Bucket string `yaml:"bucket" json:"bucket"`
|
Bucket string `yaml:"bucket" json:"bucket"`
|
||||||
Prefix string `yaml:"prefix" json:"prefix"`
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
|
||||||
Encryption BatchJobKeyRotateEncryption `yaml:"encryption" json:"encryption"`
|
Encryption BatchJobKeyRotateEncryption `yaml:"encryption" json:"encryption"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +260,9 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if err := ri.load(ctx, api, job); err != nil {
|
if err := ri.load(ctx, api, job); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if ri.Complete {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
lastObject := ri.Object
|
lastObject := ri.Object
|
||||||
@@ -356,9 +358,9 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan ObjectInfo, 100)
|
||||||
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, ObjectOptions{
|
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
||||||
WalkMarker: lastObject,
|
Marker: lastObject,
|
||||||
WalkFilter: skip,
|
Filter: skip,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
// Do not need to retry if we can't list objects on source.
|
// Do not need to retry if we can't list objects on source.
|
||||||
@@ -377,14 +379,14 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
defer wk.Give()
|
defer wk.Give()
|
||||||
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
attempts := attempts
|
attempts := attempts
|
||||||
stopFn := globalBatchJobsMetrics.trace(batchKeyRotationMetricObject, job.ID, attempts, result)
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricKeyRotation, job.ID, attempts)
|
||||||
success := true
|
success := true
|
||||||
if err := r.KeyRotate(ctx, api, result); err != nil {
|
if err := r.KeyRotate(ctx, api, result); err != nil {
|
||||||
stopFn(err)
|
stopFn(result, err)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
success = false
|
success = false
|
||||||
} else {
|
} else {
|
||||||
stopFn(nil)
|
stopFn(result, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Bucket, result, success)
|
ri.trackCurrentBucketObject(r.Bucket, result, success)
|
||||||
ri.RetryAttempts = attempts
|
ri.RetryAttempts = attempts
|
||||||
@@ -398,6 +400,10 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
time.Sleep(delay + time.Duration(rnd.Float64()*float64(delay)))
|
time.Sleep(delay + time.Duration(rnd.Float64()*float64(delay)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if wait := globalBatchConfig.KeyRotationWait(); wait > 0 {
|
||||||
|
time.Sleep(wait)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wk.Wait()
|
wk.Wait()
|
||||||
|
|||||||
+5
-30
@@ -409,12 +409,6 @@ func (z *BatchJobKeyRotateV1) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Endpoint":
|
|
||||||
z.Endpoint, err = dc.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Endpoint")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Encryption":
|
case "Encryption":
|
||||||
err = z.Encryption.DecodeMsg(dc)
|
err = z.Encryption.DecodeMsg(dc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -434,9 +428,9 @@ func (z *BatchJobKeyRotateV1) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *BatchJobKeyRotateV1) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *BatchJobKeyRotateV1) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 6
|
// map header, size 5
|
||||||
// write "APIVersion"
|
// write "APIVersion"
|
||||||
err = en.Append(0x86, 0xaa, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
err = en.Append(0x85, 0xaa, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -501,16 +495,6 @@ func (z *BatchJobKeyRotateV1) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Endpoint"
|
|
||||||
err = en.Append(0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Endpoint)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Endpoint")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "Encryption"
|
// write "Encryption"
|
||||||
err = en.Append(0xaa, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e)
|
err = en.Append(0xaa, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -527,9 +511,9 @@ func (z *BatchJobKeyRotateV1) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *BatchJobKeyRotateV1) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *BatchJobKeyRotateV1) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 6
|
// map header, size 5
|
||||||
// string "APIVersion"
|
// string "APIVersion"
|
||||||
o = append(o, 0x86, 0xaa, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
o = append(o, 0x85, 0xaa, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
||||||
o = msgp.AppendString(o, z.APIVersion)
|
o = msgp.AppendString(o, z.APIVersion)
|
||||||
// string "Flags"
|
// string "Flags"
|
||||||
o = append(o, 0xa5, 0x46, 0x6c, 0x61, 0x67, 0x73)
|
o = append(o, 0xa5, 0x46, 0x6c, 0x61, 0x67, 0x73)
|
||||||
@@ -561,9 +545,6 @@ func (z *BatchJobKeyRotateV1) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "Prefix"
|
// string "Prefix"
|
||||||
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||||
o = msgp.AppendString(o, z.Prefix)
|
o = msgp.AppendString(o, z.Prefix)
|
||||||
// string "Endpoint"
|
|
||||||
o = append(o, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
|
||||||
o = msgp.AppendString(o, z.Endpoint)
|
|
||||||
// string "Encryption"
|
// string "Encryption"
|
||||||
o = append(o, 0xaa, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e)
|
o = append(o, 0xaa, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e)
|
||||||
o, err = z.Encryption.MarshalMsg(o)
|
o, err = z.Encryption.MarshalMsg(o)
|
||||||
@@ -651,12 +632,6 @@ func (z *BatchJobKeyRotateV1) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Endpoint":
|
|
||||||
z.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Endpoint")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "Encryption":
|
case "Encryption":
|
||||||
bts, err = z.Encryption.UnmarshalMsg(bts)
|
bts, err = z.Encryption.UnmarshalMsg(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -677,7 +652,7 @@ func (z *BatchJobKeyRotateV1) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *BatchJobKeyRotateV1) Msgsize() (s int) {
|
func (z *BatchJobKeyRotateV1) Msgsize() (s int) {
|
||||||
s = 1 + 11 + msgp.StringPrefixSize + len(z.APIVersion) + 6 + 1 + 7 + z.Flags.Filter.Msgsize() + 7 + z.Flags.Notify.Msgsize() + 6 + z.Flags.Retry.Msgsize() + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 11 + z.Encryption.Msgsize()
|
s = 1 + 11 + msgp.StringPrefixSize + len(z.APIVersion) + 6 + 1 + 7 + z.Flags.Filter.Msgsize() + 7 + z.Flags.Notify.Msgsize() + 6 + z.Flags.Retry.Msgsize() + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 11 + z.Encryption.Msgsize()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ func _() {
|
|||||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
// Re-run the stringer command to generate them again.
|
// Re-run the stringer command to generate them again.
|
||||||
var x [1]struct{}
|
var x [1]struct{}
|
||||||
_ = x[batchReplicationMetricObject-0]
|
_ = x[batchJobMetricReplication-0]
|
||||||
_ = x[batchKeyRotationMetricObject-1]
|
_ = x[batchJobMetricKeyRotation-1]
|
||||||
|
_ = x[batchJobMetricExpire-2]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _batchJobMetric_name = "batchReplicationMetricObjectbatchKeyRotationMetricObject"
|
const _batchJobMetric_name = "ReplicationKeyRotationExpire"
|
||||||
|
|
||||||
var _batchJobMetric_index = [...]uint8{0, 28, 56}
|
var _batchJobMetric_index = [...]uint8{0, 11, 22, 28}
|
||||||
|
|
||||||
func (i batchJobMetric) String() string {
|
func (i batchJobMetric) String() string {
|
||||||
if i >= batchJobMetric(len(_batchJobMetric_index)-1) {
|
if i >= batchJobMetric(len(_batchJobMetric_index)-1) {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
|
|||||||
h := algo.New()
|
h := algo.New()
|
||||||
|
|
||||||
bw := &streamingBitrotWriter{
|
bw := &streamingBitrotWriter{
|
||||||
iow: ioutil.NewDeadlineWriter(w, diskMaxTimeout),
|
iow: ioutil.NewDeadlineWriter(w, globalDriveConfig.GetMaxTimeout()),
|
||||||
closeWithErr: w.CloseWithError,
|
closeWithErr: w.CloseWithError,
|
||||||
h: h,
|
h: h,
|
||||||
shardSize: shardSize,
|
shardSize: shardSize,
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -82,23 +84,40 @@ func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
|
|||||||
ep.Platform, s2.MinioEndpoints[i].Platform)
|
ep.Platform, s2.MinioEndpoints[i].Platform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
|
if reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
|
||||||
var missing []string
|
return nil
|
||||||
var mismatching []string
|
|
||||||
for k, v := range s1.MinioEnv {
|
|
||||||
ev, ok := s2.MinioEnv[k]
|
|
||||||
if !ok {
|
|
||||||
missing = append(missing, k)
|
|
||||||
} else if v != ev {
|
|
||||||
mismatching = append(mismatching, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(mismatching) > 0 {
|
|
||||||
return fmt.Errorf(`Expected same MINIO_ environment variables and values across all servers: Missing environment values: %s / Mismatch environment values: %s`, missing, mismatching)
|
|
||||||
}
|
|
||||||
return fmt.Errorf(`Expected same MINIO_ environment variables and values across all servers: Missing environment values: %s`, missing)
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// Report differences in environment variables.
|
||||||
|
var missing []string
|
||||||
|
var mismatching []string
|
||||||
|
for k, v := range s1.MinioEnv {
|
||||||
|
ev, ok := s2.MinioEnv[k]
|
||||||
|
if !ok {
|
||||||
|
missing = append(missing, k)
|
||||||
|
} else if v != ev {
|
||||||
|
mismatching = append(mismatching, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var extra []string
|
||||||
|
for k := range s2.MinioEnv {
|
||||||
|
_, ok := s1.MinioEnv[k]
|
||||||
|
if !ok {
|
||||||
|
extra = append(extra, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg := "Expected same MINIO_ environment variables and values across all servers: "
|
||||||
|
if len(missing) > 0 {
|
||||||
|
msg += fmt.Sprintf(`Missing environment values: %v. `, missing)
|
||||||
|
}
|
||||||
|
if len(mismatching) > 0 {
|
||||||
|
msg += fmt.Sprintf(`Mismatching environment values: %v. `, mismatching)
|
||||||
|
}
|
||||||
|
if len(extra) > 0 {
|
||||||
|
msg += fmt.Sprintf(`Extra environment values: %v. `, extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(strings.TrimSpace(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
var skipEnvs = map[string]struct{}{
|
var skipEnvs = map[string]struct{}{
|
||||||
|
|||||||
+51
-16
@@ -51,6 +51,7 @@ import (
|
|||||||
sse "github.com/minio/minio/internal/bucket/encryption"
|
sse "github.com/minio/minio/internal/bucket/encryption"
|
||||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
|
"github.com/minio/minio/internal/config/cache"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
@@ -474,9 +475,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteObjectsFn := objectAPI.DeleteObjects
|
deleteObjectsFn := objectAPI.DeleteObjects
|
||||||
if api.CacheAPI() != nil {
|
|
||||||
deleteObjectsFn = api.CacheAPI().DeleteObjects
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return Malformed XML as S3 spec if the number of objects is empty
|
// Return Malformed XML as S3 spec if the number of objects is empty
|
||||||
if len(deleteObjectsReq.Objects) == 0 || len(deleteObjectsReq.Objects) > maxDeleteList {
|
if len(deleteObjectsReq.Objects) == 0 || len(deleteObjectsReq.Objects) > maxDeleteList {
|
||||||
@@ -486,9 +484,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
|
|
||||||
objectsToDelete := map[ObjectToDelete]int{}
|
objectsToDelete := map[ObjectToDelete]int{}
|
||||||
getObjectInfoFn := objectAPI.GetObjectInfo
|
getObjectInfoFn := objectAPI.GetObjectInfo
|
||||||
if api.CacheAPI() != nil {
|
|
||||||
getObjectInfoFn = api.CacheAPI().GetObjectInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hasLockEnabled bool
|
hasLockEnabled bool
|
||||||
@@ -674,6 +669,8 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer globalCacheConfig.Delete(bucket, dobj.ObjectName)
|
||||||
|
|
||||||
if replicateDeletes && (dobj.DeleteMarkerReplicationStatus() == replication.Pending || dobj.VersionPurgeStatus() == Pending) {
|
if replicateDeletes && (dobj.DeleteMarkerReplicationStatus() == replication.Pending || dobj.VersionPurgeStatus() == Pending) {
|
||||||
// copy so we can re-add null ID.
|
// copy so we can re-add null ID.
|
||||||
dobj := dobj
|
dobj := dobj
|
||||||
@@ -716,7 +713,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
if os == nil { // skip objects that weren't deleted due to invalid versionID etc.
|
if os == nil { // skip objects that weren't deleted due to invalid versionID etc.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, os.Sweep())
|
os.Sweep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,7 +791,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
// check if client is attempting to create more buckets, complain about it.
|
// check if client is attempting to create more buckets, complain about it.
|
||||||
if currBuckets := globalBucketMetadataSys.Count(); currBuckets+1 > maxBuckets {
|
if currBuckets := globalBucketMetadataSys.Count(); currBuckets+1 > maxBuckets {
|
||||||
logger.LogIf(ctx, fmt.Errorf("An attempt to create %d buckets beyond recommended %d", currBuckets+1, maxBuckets))
|
logger.LogIf(ctx, fmt.Errorf("Please avoid creating more buckets %d beyond recommended %d", currBuckets+1, maxBuckets))
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := MakeBucketOptions{
|
opts := MakeBucketOptions{
|
||||||
@@ -989,7 +986,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if fileName == "" {
|
if name != "file" {
|
||||||
if http.CanonicalHeaderKey(name) == http.CanonicalHeaderKey("x-minio-fanout-list") {
|
if http.CanonicalHeaderKey(name) == http.CanonicalHeaderKey("x-minio-fanout-list") {
|
||||||
dec := json.NewDecoder(part)
|
dec := json.NewDecoder(part)
|
||||||
|
|
||||||
@@ -1045,11 +1042,14 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := formValues["Key"]; !ok {
|
if keyName, ok := formValues["Key"]; !ok {
|
||||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, errors.New("The name of the uploaded key is missing"))
|
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, errors.New("The name of the uploaded key is missing"))
|
||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
|
} else if fileName == "" && len(keyName) >= 1 {
|
||||||
|
// if we can't get fileName. We use keyName[0] to fileName
|
||||||
|
fileName = keyName[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
@@ -1141,7 +1141,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
|
|
||||||
hashReader, err := hash.NewReader(ctx, reader, fileSize, "", "", fileSize)
|
hashReader, err := hash.NewReader(ctx, reader, fileSize, "", "", fileSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1196,7 +1195,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
})
|
})
|
||||||
|
|
||||||
var opts ObjectOptions
|
var opts ObjectOptions
|
||||||
opts, err = putOpts(ctx, r, bucket, object, metadata)
|
opts, err = putOptsFromReq(ctx, r, bucket, object, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||||
return
|
return
|
||||||
@@ -1347,6 +1346,22 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asize, err := objInfo.GetActualSize()
|
||||||
|
if err != nil {
|
||||||
|
asize = objInfo.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCacheConfig.Set(&cache.ObjectInfo{
|
||||||
|
Key: objInfo.Name,
|
||||||
|
Bucket: objInfo.Bucket,
|
||||||
|
ETag: getDecryptedETag(formValues, objInfo, false),
|
||||||
|
ModTime: objInfo.ModTime,
|
||||||
|
Expires: objInfo.Expires.UTC().Format(http.TimeFormat),
|
||||||
|
CacheControl: objInfo.CacheControl,
|
||||||
|
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||||
|
Size: asize,
|
||||||
|
})
|
||||||
|
|
||||||
fanOutResp = append(fanOutResp, minio.PutObjectFanOutResponse{
|
fanOutResp = append(fanOutResp, minio.PutObjectFanOutResponse{
|
||||||
Key: objInfo.Name,
|
Key: objInfo.Name,
|
||||||
ETag: getDecryptedETag(formValues, objInfo, false),
|
ETag: getDecryptedETag(formValues, objInfo, false),
|
||||||
@@ -1403,10 +1418,12 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
etag := getDecryptedETag(formValues, objInfo, false)
|
||||||
|
|
||||||
// We must not use the http.Header().Set method here because some (broken)
|
// We must not use the http.Header().Set method here because some (broken)
|
||||||
// clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive).
|
// clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive).
|
||||||
// Therefore, we have to set the ETag directly as map entry.
|
// Therefore, we have to set the ETag directly as map entry.
|
||||||
w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`}
|
w.Header()[xhttp.ETag] = []string{`"` + etag + `"`}
|
||||||
|
|
||||||
// Set the relevant version ID as part of the response header.
|
// Set the relevant version ID as part of the response header.
|
||||||
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
|
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
|
||||||
@@ -1417,6 +1434,22 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
w.Header().Set(xhttp.Location, obj)
|
w.Header().Set(xhttp.Location, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asize, err := objInfo.GetActualSize()
|
||||||
|
if err != nil {
|
||||||
|
asize = objInfo.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
defer globalCacheConfig.Set(&cache.ObjectInfo{
|
||||||
|
Key: objInfo.Name,
|
||||||
|
Bucket: objInfo.Bucket,
|
||||||
|
ETag: etag,
|
||||||
|
ModTime: objInfo.ModTime,
|
||||||
|
Expires: objInfo.ExpiresStr(),
|
||||||
|
CacheControl: objInfo.CacheControl,
|
||||||
|
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||||
|
Size: asize,
|
||||||
|
})
|
||||||
|
|
||||||
// Notify object created event.
|
// Notify object created event.
|
||||||
defer sendEvent(eventArgs{
|
defer sendEvent(eventArgs{
|
||||||
EventName: event.ObjectCreatedPost,
|
EventName: event.ObjectCreatedPost,
|
||||||
@@ -1624,9 +1657,11 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return an error if the bucket does not exist
|
// Return an error if the bucket does not exist
|
||||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil && !forceDelete {
|
if !forceDelete {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
return
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to delete bucket.
|
// Attempt to delete bucket.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type lcEventSrc uint8
|
|||||||
//revive:disable:var-naming Underscores is used here to indicate where common prefix ends and the enumeration name begins
|
//revive:disable:var-naming Underscores is used here to indicate where common prefix ends and the enumeration name begins
|
||||||
const (
|
const (
|
||||||
lcEventSrc_None lcEventSrc = iota
|
lcEventSrc_None lcEventSrc = iota
|
||||||
|
lcEventSrc_Heal
|
||||||
lcEventSrc_Scanner
|
lcEventSrc_Scanner
|
||||||
lcEventSrc_Decom
|
lcEventSrc_Decom
|
||||||
lcEventSrc_Rebal
|
lcEventSrc_Rebal
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
@@ -86,6 +87,41 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a map of updated set of rules in request
|
||||||
|
updatedRules := make(map[string]lifecycle.Rule, len(bucketLifecycle.Rules))
|
||||||
|
for _, rule := range bucketLifecycle.Rules {
|
||||||
|
updatedRules[rule.ID] = rule
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get list of rules for the bucket from disk
|
||||||
|
meta, err := globalBucketMetadataSys.GetConfigFromDisk(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expiryRuleRemoved := false
|
||||||
|
if len(meta.LifecycleConfigXML) > 0 {
|
||||||
|
var lcCfg lifecycle.Lifecycle
|
||||||
|
if err := xml.Unmarshal(meta.LifecycleConfigXML, &lcCfg); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, rl := range lcCfg.Rules {
|
||||||
|
updRule, ok := updatedRules[rl.ID]
|
||||||
|
// original rule had expiry that is no longer in the new config,
|
||||||
|
// or rule is present but missing expiration flags
|
||||||
|
if (!rl.Expiration.IsNull() || !rl.NoncurrentVersionExpiration.IsNull()) &&
|
||||||
|
(!ok || (updRule.Expiration.IsNull() && updRule.NoncurrentVersionExpiration.IsNull())) {
|
||||||
|
expiryRuleRemoved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bucketLifecycle.HasExpiry() || expiryRuleRemoved {
|
||||||
|
currtime := time.Now()
|
||||||
|
bucketLifecycle.ExpiryUpdatedAt = &currtime
|
||||||
|
}
|
||||||
|
|
||||||
configData, err := xml.Marshal(bucketLifecycle)
|
configData, err := xml.Marshal(bucketLifecycle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -142,6 +178,8 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// explicitly set ExpiryUpdatedAt nil as its meant for internal consumption only
|
||||||
|
config.ExpiryUpdatedAt = nil
|
||||||
|
|
||||||
configData, err := xml.Marshal(config)
|
configData, err := xml.Marshal(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+35
-12
@@ -42,7 +42,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/s3select"
|
"github.com/minio/minio/internal/s3select"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
|
xnet "github.com/minio/pkg/v2/net"
|
||||||
"github.com/minio/pkg/v2/workers"
|
"github.com/minio/pkg/v2/workers"
|
||||||
|
"github.com/zeebo/xxh3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -217,17 +219,24 @@ type transitionState struct {
|
|||||||
numWorkers int
|
numWorkers int
|
||||||
killCh chan struct{}
|
killCh chan struct{}
|
||||||
|
|
||||||
activeTasks int32
|
activeTasks atomic.Int64
|
||||||
|
missedImmediateTasks atomic.Int64
|
||||||
|
|
||||||
lastDayMu sync.RWMutex
|
lastDayMu sync.RWMutex
|
||||||
lastDayStats map[string]*lastDayTierStats
|
lastDayStats map[string]*lastDayTierStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transitionState) queueTransitionTask(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) {
|
func (t *transitionState) queueTransitionTask(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) {
|
||||||
|
task := transitionTask{objInfo: oi, event: event, src: src}
|
||||||
select {
|
select {
|
||||||
case <-t.ctx.Done():
|
case <-t.ctx.Done():
|
||||||
case t.transitionCh <- transitionTask{objInfo: oi, event: event, src: src}:
|
case t.transitionCh <- task:
|
||||||
default:
|
default:
|
||||||
|
switch src {
|
||||||
|
case lcEventSrc_s3PutObject, lcEventSrc_s3CopyObject, lcEventSrc_s3CompleteMultipartUpload:
|
||||||
|
// Update missed immediate tasks only for incoming requests.
|
||||||
|
t.missedImmediateTasks.Add(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +246,7 @@ var globalTransitionState *transitionState
|
|||||||
// via its Init method.
|
// via its Init method.
|
||||||
func newTransitionState(ctx context.Context) *transitionState {
|
func newTransitionState(ctx context.Context) *transitionState {
|
||||||
return &transitionState{
|
return &transitionState{
|
||||||
transitionCh: make(chan transitionTask, 10000),
|
transitionCh: make(chan transitionTask, 100000),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
killCh: make(chan struct{}),
|
killCh: make(chan struct{}),
|
||||||
lastDayStats: make(map[string]*lastDayTierStats),
|
lastDayStats: make(map[string]*lastDayTierStats),
|
||||||
@@ -262,8 +271,14 @@ func (t *transitionState) PendingTasks() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ActiveTasks returns the number of active (ongoing) ILM transition tasks.
|
// ActiveTasks returns the number of active (ongoing) ILM transition tasks.
|
||||||
func (t *transitionState) ActiveTasks() int {
|
func (t *transitionState) ActiveTasks() int64 {
|
||||||
return int(atomic.LoadInt32(&t.activeTasks))
|
return t.activeTasks.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MissedImmediateTasks returns the number of tasks - deferred to scanner due
|
||||||
|
// to tasks channel being backlogged.
|
||||||
|
func (t *transitionState) MissedImmediateTasks() int64 {
|
||||||
|
return t.missedImmediateTasks.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// worker waits for transition tasks
|
// worker waits for transition tasks
|
||||||
@@ -278,11 +293,13 @@ func (t *transitionState) worker(objectAPI ObjectLayer) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&t.activeTasks, 1)
|
t.activeTasks.Add(1)
|
||||||
if err := transitionObject(t.ctx, objectAPI, task.objInfo, newLifecycleAuditEvent(task.src, task.event)); err != nil {
|
if err := transitionObject(t.ctx, objectAPI, task.objInfo, newLifecycleAuditEvent(task.src, task.event)); err != nil {
|
||||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !xnet.IsNetworkOrHostDown(err, false) {
|
||||||
logger.LogIf(t.ctx, fmt.Errorf("Transition to %s failed for %s/%s version:%s with %w",
|
if !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
task.event.StorageClass, task.objInfo.Bucket, task.objInfo.Name, task.objInfo.VersionID, err))
|
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 {
|
} else {
|
||||||
ts := tierStats{
|
ts := tierStats{
|
||||||
@@ -294,7 +311,7 @@ func (t *transitionState) worker(objectAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
t.addLastDayStats(task.event.StorageClass, ts)
|
t.addLastDayStats(task.event.StorageClass, ts)
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&t.activeTasks, -1)
|
t.activeTasks.Add(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,7 +461,8 @@ func genTransitionObjName(bucket string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
us := u.String()
|
us := u.String()
|
||||||
obj := fmt.Sprintf("%s/%s/%s/%s/%s", globalDeploymentID, bucket, us[0:2], us[2:4], us)
|
hash := xxh3.HashString(pathJoin(globalDeploymentID(), bucket))
|
||||||
|
obj := fmt.Sprintf("%s/%s/%s/%s", strconv.FormatUint(hash, 16), us[0:2], us[2:4], us)
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,9 +507,13 @@ func auditTierActions(ctx context.Context, tier string, bytes int64) func(err er
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
op.TimeToResponseNS = time.Since(startTime).Nanoseconds()
|
since := time.Since(startTime)
|
||||||
|
op.TimeToResponseNS = since.Nanoseconds()
|
||||||
|
globalTierMetrics.Observe(tier, since)
|
||||||
|
globalTierMetrics.logSuccess(tier)
|
||||||
} else {
|
} else {
|
||||||
op.Error = err.Error()
|
op.Error = err.Error()
|
||||||
|
globalTierMetrics.logFailure(tier)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.GetReqInfo(ctx).AppendTags("tierStats", op)
|
logger.GetReqInfo(ctx).AppendTags("tierStats", op)
|
||||||
@@ -862,6 +884,7 @@ func (oi ObjectInfo) ToLifecycleOpts() lifecycle.ObjectOpts {
|
|||||||
UserTags: oi.UserTags,
|
UserTags: oi.UserTags,
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
ModTime: oi.ModTime,
|
ModTime: oi.ModTime,
|
||||||
|
Size: oi.Size,
|
||||||
IsLatest: oi.IsLatest,
|
IsLatest: oi.IsLatest,
|
||||||
NumVersions: oi.NumVersions,
|
NumVersions: oi.NumVersions,
|
||||||
DeleteMarker: oi.DeleteMarker,
|
DeleteMarker: oi.DeleteMarker,
|
||||||
|
|||||||
+64
-24
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -177,6 +178,40 @@ func (sys *BucketMetadataSys) save(ctx context.Context, meta BucketMetadata) err
|
|||||||
// Delete delete the bucket metadata for the specified bucket.
|
// Delete delete the bucket metadata for the specified bucket.
|
||||||
// must be used by all callers instead of using Update() with nil configData.
|
// must be used by all callers instead of using Update() with nil configData.
|
||||||
func (sys *BucketMetadataSys) Delete(ctx context.Context, bucket string, configFile string) (updatedAt time.Time, err error) {
|
func (sys *BucketMetadataSys) Delete(ctx context.Context, bucket string, configFile string) (updatedAt time.Time, err error) {
|
||||||
|
if configFile == bucketLifecycleConfig {
|
||||||
|
// Get bucket config from current site
|
||||||
|
meta, e := globalBucketMetadataSys.GetConfigFromDisk(ctx, bucket)
|
||||||
|
if e != nil && !errors.Is(e, errConfigNotFound) {
|
||||||
|
return updatedAt, e
|
||||||
|
}
|
||||||
|
var expiryRuleRemoved bool
|
||||||
|
if len(meta.LifecycleConfigXML) > 0 {
|
||||||
|
var lcCfg lifecycle.Lifecycle
|
||||||
|
if err := xml.Unmarshal(meta.LifecycleConfigXML, &lcCfg); err != nil {
|
||||||
|
return updatedAt, err
|
||||||
|
}
|
||||||
|
// find a single expiry rule set the flag
|
||||||
|
for _, rl := range lcCfg.Rules {
|
||||||
|
if !rl.Expiration.IsNull() || !rl.NoncurrentVersionExpiration.IsNull() {
|
||||||
|
expiryRuleRemoved = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form empty ILM details with `ExpiryUpdatedAt` field and save
|
||||||
|
var cfgData []byte
|
||||||
|
if expiryRuleRemoved {
|
||||||
|
var lcCfg lifecycle.Lifecycle
|
||||||
|
currtime := time.Now()
|
||||||
|
lcCfg.ExpiryUpdatedAt = &currtime
|
||||||
|
cfgData, err = xml.Marshal(lcCfg)
|
||||||
|
if err != nil {
|
||||||
|
return updatedAt, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sys.updateAndParse(ctx, bucket, configFile, cfgData, false)
|
||||||
|
}
|
||||||
return sys.updateAndParse(ctx, bucket, configFile, nil, false)
|
return sys.updateAndParse(ctx, bucket, configFile, nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +302,10 @@ func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Life
|
|||||||
}
|
}
|
||||||
return nil, time.Time{}, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
if meta.lifecycleConfig == nil {
|
// there could be just `ExpiryUpdatedAt` field populated as part
|
||||||
|
// of last delete all. Treat this situation as not lifecycle configuration
|
||||||
|
// available
|
||||||
|
if meta.lifecycleConfig == nil || len(meta.lifecycleConfig.Rules) == 0 {
|
||||||
return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
return meta.lifecycleConfig, meta.LifecycleConfigUpdatedAt, nil
|
return meta.lifecycleConfig, meta.LifecycleConfigUpdatedAt, nil
|
||||||
@@ -437,21 +475,8 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *BucketMetadataSys) loadBucketMetadata(ctx context.Context, bucket BucketInfo) error {
|
|
||||||
meta, err := loadBucketMetadata(ctx, sys.objAPI, bucket.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sys.Lock()
|
|
||||||
sys.metadataMap[bucket.Name] = meta
|
|
||||||
sys.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// concurrently load bucket metadata to speed up loading bucket metadata.
|
// concurrently load bucket metadata to speed up loading bucket metadata.
|
||||||
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo) {
|
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, failedBuckets map[string]struct{}) {
|
||||||
g := errgroup.WithNErrs(len(buckets))
|
g := errgroup.WithNErrs(len(buckets))
|
||||||
bucketMetas := make([]BucketMetadata, len(buckets))
|
bucketMetas := make([]BucketMetadata, len(buckets))
|
||||||
for index := range buckets {
|
for index := range buckets {
|
||||||
@@ -491,6 +516,10 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
|
|
||||||
for i, meta := range bucketMetas {
|
for i, meta := range bucketMetas {
|
||||||
if errs[i] != nil {
|
if errs[i] != nil {
|
||||||
|
if failedBuckets == nil {
|
||||||
|
failedBuckets = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
failedBuckets[buckets[i].Name] = struct{}{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
globalEventNotifier.set(buckets[i], meta) // set notification targets
|
globalEventNotifier.set(buckets[i], meta) // set notification targets
|
||||||
@@ -498,7 +527,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, failedBuckets map[string]struct{}) {
|
||||||
const bucketMetadataRefresh = 15 * time.Minute
|
const bucketMetadataRefresh = 15 * time.Minute
|
||||||
|
|
||||||
sleeper := newDynamicSleeper(2, 150*time.Millisecond, false)
|
sleeper := newDynamicSleeper(2, 150*time.Millisecond, false)
|
||||||
@@ -513,7 +542,7 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
|||||||
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
|
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle if we have some buckets in-memory those are stale.
|
// Handle if we have some buckets in-memory those are stale.
|
||||||
@@ -525,38 +554,49 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
sys.RemoveStaleBuckets(diskBuckets)
|
sys.RemoveStaleBuckets(diskBuckets)
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for i := range buckets {
|
||||||
wait := sleeper.Timer(ctx)
|
wait := sleeper.Timer(ctx)
|
||||||
|
|
||||||
err := sys.loadBucketMetadata(ctx, bucket)
|
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[i].Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
wait() // wait to proceed to next entry.
|
wait() // wait to proceed to next entry.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
sys.metadataMap[buckets[i].Name] = meta
|
||||||
|
sys.Unlock()
|
||||||
|
|
||||||
|
// Initialize the failed buckets
|
||||||
|
if _, ok := failedBuckets[buckets[i].Name]; ok {
|
||||||
|
globalEventNotifier.set(buckets[i], meta)
|
||||||
|
globalBucketTargetSys.set(buckets[i], meta)
|
||||||
|
delete(failedBuckets, buckets[i].Name)
|
||||||
|
}
|
||||||
|
|
||||||
wait() // wait to proceed to next entry.
|
wait() // wait to proceed to next entry.
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Reset(bucketMetadataRefresh)
|
|
||||||
}
|
}
|
||||||
|
t.Reset(bucketMetadataRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
||||||
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
||||||
count := 100 // load 100 bucket metadata at a time.
|
count := 100 // load 100 bucket metadata at a time.
|
||||||
|
failedBuckets := make(map[string]struct{})
|
||||||
for {
|
for {
|
||||||
if len(buckets) < count {
|
if len(buckets) < count {
|
||||||
sys.concurrentLoad(ctx, buckets)
|
sys.concurrentLoad(ctx, buckets, failedBuckets)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
sys.concurrentLoad(ctx, buckets[:count])
|
sys.concurrentLoad(ctx, buckets[:count], failedBuckets)
|
||||||
buckets = buckets[count:]
|
buckets = buckets[count:]
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
go sys.refreshBucketsMetadataLoop(ctx)
|
go sys.refreshBucketsMetadataLoop(ctx, failedBuckets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[s
|
|||||||
"CurrentTime": {currTime.Format(time.RFC3339)},
|
"CurrentTime": {currTime.Format(time.RFC3339)},
|
||||||
"EpochTime": {strconv.FormatInt(currTime.Unix(), 10)},
|
"EpochTime": {strconv.FormatInt(currTime.Unix(), 10)},
|
||||||
"SecureTransport": {strconv.FormatBool(r.TLS != nil)},
|
"SecureTransport": {strconv.FormatBool(r.TLS != nil)},
|
||||||
"SourceIp": {handlers.GetSourceIP(r)},
|
"SourceIp": {handlers.GetSourceIPRaw(r)},
|
||||||
"UserAgent": {r.UserAgent()},
|
"UserAgent": {r.UserAgent()},
|
||||||
"Referer": {r.Referer()},
|
"Referer": {r.Referer()},
|
||||||
"principaltype": {principalType},
|
"principaltype": {principalType},
|
||||||
@@ -137,6 +137,10 @@ func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[s
|
|||||||
}
|
}
|
||||||
|
|
||||||
cloneHeader := r.Header.Clone()
|
cloneHeader := r.Header.Clone()
|
||||||
|
if v := cloneHeader.Get("x-amz-signature-age"); v != "" {
|
||||||
|
args["signatureAge"] = []string{v}
|
||||||
|
cloneHeader.Del("x-amz-signature-age")
|
||||||
|
}
|
||||||
|
|
||||||
if userTags := cloneHeader.Get(xhttp.AmzObjectTagging); userTags != "" {
|
if userTags := cloneHeader.Get(xhttp.AmzObjectTagging); userTags != "" {
|
||||||
tag, _ := tags.ParseObjectTags(userTags)
|
tag, _ := tags.ParseObjectTags(userTags)
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ func (api objectAPIHandlers) ValidateBucketReplicationCredsHandler(w http.Respon
|
|||||||
if rule.Status == replication.Disabled {
|
if rule.Status == replication.Disabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
clnt := globalBucketTargetSys.GetRemoteTargetClient(rule.Destination.Bucket)
|
clnt := globalBucketTargetSys.GetRemoteTargetClient(bucket, rule.Destination.Bucket)
|
||||||
if clnt == nil {
|
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)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetNotFoundError, fmt.Errorf("replication config with rule ID %s has a stale target", rule.ID)), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -337,13 +337,13 @@ type SMA struct {
|
|||||||
filledBuf bool
|
filledBuf bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSMA(len int) *SMA {
|
func newSMA(ln int) *SMA {
|
||||||
if len <= 0 {
|
if ln <= 0 {
|
||||||
len = defaultWindowSize
|
ln = defaultWindowSize
|
||||||
}
|
}
|
||||||
return &SMA{
|
return &SMA{
|
||||||
buf: make([]float64, len),
|
buf: make([]float64, ln),
|
||||||
window: len,
|
window: ln,
|
||||||
idx: 0,
|
idx: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+255
-68
@@ -23,7 +23,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -113,7 +112,7 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
|
|||||||
if arn.Type != madmin.ReplicationService {
|
if arn.Type != madmin.ReplicationService {
|
||||||
return sameTarget, toAPIError(ctx, BucketRemoteArnTypeInvalid{Bucket: bucket})
|
return sameTarget, toAPIError(ctx, BucketRemoteArnTypeInvalid{Bucket: bucket})
|
||||||
}
|
}
|
||||||
clnt := globalBucketTargetSys.GetRemoteTargetClient(arnStr)
|
clnt := globalBucketTargetSys.GetRemoteTargetClient(bucket, arnStr)
|
||||||
if clnt == nil {
|
if clnt == nil {
|
||||||
return sameTarget, toAPIError(ctx, BucketRemoteTargetNotFound{Bucket: bucket})
|
return sameTarget, toAPIError(ctx, BucketRemoteTargetNotFound{Bucket: bucket})
|
||||||
}
|
}
|
||||||
@@ -138,7 +137,7 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// validate replication ARN against target endpoint
|
// validate replication ARN against target endpoint
|
||||||
c := globalBucketTargetSys.GetRemoteTargetClient(arnStr)
|
c := globalBucketTargetSys.GetRemoteTargetClient(bucket, arnStr)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
if err := checkRemoteEndpoint(ctx, c.EndpointURL()); err != nil {
|
if err := checkRemoteEndpoint(ctx, c.EndpointURL()); err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
@@ -280,7 +279,7 @@ func mustReplicate(ctx context.Context, bucket, object string, mopts mustReplica
|
|||||||
}
|
}
|
||||||
tgtArns := cfg.FilterTargetArns(opts)
|
tgtArns := cfg.FilterTargetArns(opts)
|
||||||
for _, tgtArn := range tgtArns {
|
for _, tgtArn := range tgtArns {
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtArn)
|
||||||
// the target online status should not be used here while deciding
|
// the target online status should not be used here while deciding
|
||||||
// whether to replicate as the target could be temporarily down
|
// whether to replicate as the target could be temporarily down
|
||||||
opts.TargetArn = tgtArn
|
opts.TargetArn = tgtArn
|
||||||
@@ -373,15 +372,16 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
|||||||
if oi.DeleteMarker && (validReplStatus || replicate) {
|
if oi.DeleteMarker && (validReplStatus || replicate) {
|
||||||
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
||||||
continue
|
continue
|
||||||
} else {
|
}
|
||||||
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
||||||
// is issued - this still needs to be replicated back to the other target
|
// is issued - this still needs to be replicated back to the other target
|
||||||
|
if !oi.VersionPurgeStatus.Empty() {
|
||||||
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
|
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
|
||||||
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtArn)
|
||||||
// the target online status should not be used here while deciding
|
// the target online status should not be used here while deciding
|
||||||
// whether to replicate deletes as the target could be temporarily down
|
// whether to replicate deletes as the target could be temporarily down
|
||||||
tgtDsc := newReplicateTargetDecision(tgtArn, false, false)
|
tgtDsc := newReplicateTargetDecision(tgtArn, false, false)
|
||||||
@@ -493,7 +493,7 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
|||||||
if dobj.TargetArn != "" && dobj.TargetArn != tgtEntry.Arn {
|
if dobj.TargetArn != "" && dobj.TargetArn != tgtEntry.Arn {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(tgtEntry.Arn)
|
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtEntry.Arn)
|
||||||
if tgtClnt == nil {
|
if tgtClnt == nil {
|
||||||
// Skip stale targets if any and log them to be missing atleast once.
|
// Skip stale targets if any and log them to be missing atleast once.
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtEntry.Arn), tgtEntry.Arn)
|
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtEntry.Arn), tgtEntry.Arn)
|
||||||
@@ -637,19 +637,23 @@ func replicateDeleteToTarget(ctx context.Context, dobj DeletedObjectReplicationI
|
|||||||
IsReplicationReadyForDeleteMarker: true,
|
IsReplicationReadyForDeleteMarker: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
serr := ErrorRespToObjectError(err, dobj.Bucket, dobj.ObjectName, dobj.VersionID)
|
||||||
switch {
|
switch {
|
||||||
case isErrMethodNotAllowed(ErrorRespToObjectError(err, dobj.Bucket, dobj.ObjectName)):
|
case isErrMethodNotAllowed(serr):
|
||||||
// delete marker already replicated
|
// delete marker already replicated
|
||||||
if dobj.VersionID == "" && rinfo.VersionPurgeStatus.Empty() {
|
if dobj.VersionID == "" && rinfo.VersionPurgeStatus.Empty() {
|
||||||
rinfo.ReplicationStatus = replication.Completed
|
rinfo.ReplicationStatus = replication.Completed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case isErrObjectNotFound(ErrorRespToObjectError(err, dobj.Bucket, dobj.ObjectName)):
|
case isErrObjectNotFound(serr), isErrVersionNotFound(serr):
|
||||||
// version being purged is already not found on target.
|
// version being purged is already not found on target.
|
||||||
if !rinfo.VersionPurgeStatus.Empty() {
|
if !rinfo.VersionPurgeStatus.Empty() {
|
||||||
rinfo.VersionPurgeStatus = Complete
|
rinfo.VersionPurgeStatus = Complete
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case isErrReadQuorum(serr), isErrWriteQuorum(serr):
|
||||||
|
// destination has some quorum issues, perform removeObject() anyways
|
||||||
|
// to complete the operation.
|
||||||
default:
|
default:
|
||||||
if err != nil && minio.IsNetworkOrHostDown(err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if err != nil && minio.IsNetworkOrHostDown(err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||||
@@ -775,6 +779,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (put
|
|||||||
UserMetadata: meta,
|
UserMetadata: meta,
|
||||||
ContentType: objInfo.ContentType,
|
ContentType: objInfo.ContentType,
|
||||||
ContentEncoding: objInfo.ContentEncoding,
|
ContentEncoding: objInfo.ContentEncoding,
|
||||||
|
Expires: objInfo.Expires,
|
||||||
StorageClass: sc,
|
StorageClass: sc,
|
||||||
Internal: minio.AdvancedPutOptions{
|
Internal: minio.AdvancedPutOptions{
|
||||||
SourceVersionID: objInfo.VersionID,
|
SourceVersionID: objInfo.VersionID,
|
||||||
@@ -1021,7 +1026,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
for _, tgtArn := range tgtArns {
|
for _, tgtArn := range tgtArns {
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(tgtArn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtArn)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn)
|
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtArn), tgtArn)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
@@ -1380,7 +1385,6 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
rinfo.Duration = time.Since(startTime)
|
rinfo.Duration = time.Since(startTime)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rAction = replicateAll
|
|
||||||
oi, cerr := tgt.StatObject(ctx, tgt.Bucket, object, minio.StatObjectOptions{
|
oi, cerr := tgt.StatObject(ctx, tgt.Bucket, object, minio.StatObjectOptions{
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: objInfo.VersionID,
|
||||||
Internal: minio.AdvancedGetOptions{
|
Internal: minio.AdvancedGetOptions{
|
||||||
@@ -1417,16 +1421,19 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
// if target returns error other than NoSuchKey, defer replication attempt
|
// if target returns error other than NoSuchKey, defer replication attempt
|
||||||
if cerr != nil {
|
|
||||||
if minio.IsNetworkOrHostDown(cerr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if minio.IsNetworkOrHostDown(cerr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
errResp := minio.ToErrorResponse(cerr)
|
serr := ErrorRespToObjectError(cerr, bucket, object, objInfo.VersionID)
|
||||||
switch errResp.Code {
|
switch {
|
||||||
case "NoSuchKey", "NoSuchVersion", "SlowDownRead":
|
case isErrMethodNotAllowed(serr):
|
||||||
|
rAction = replicateAll
|
||||||
|
case isErrObjectNotFound(serr), isErrVersionNotFound(serr):
|
||||||
|
rAction = replicateAll
|
||||||
|
case isErrReadQuorum(serr), isErrWriteQuorum(serr):
|
||||||
rAction = replicateAll
|
rAction = replicateAll
|
||||||
default:
|
default:
|
||||||
rinfo.Err = cerr
|
rinfo.Err = cerr
|
||||||
@@ -1460,6 +1467,24 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if tagTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+TaggingTimestamp]; ok {
|
||||||
|
ondiskTimestamp, err := time.Parse(time.RFC3339, tagTmStr)
|
||||||
|
if err == nil {
|
||||||
|
dstOpts.Internal.TaggingTimestamp = ondiskTimestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if retTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp]; ok {
|
||||||
|
ondiskTimestamp, err := time.Parse(time.RFC3339, retTmStr)
|
||||||
|
if err == nil {
|
||||||
|
dstOpts.Internal.RetentionTimestamp = ondiskTimestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lholdTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp]; ok {
|
||||||
|
ondiskTimestamp, err := time.Parse(time.RFC3339, lholdTmStr)
|
||||||
|
if err == nil {
|
||||||
|
dstOpts.Internal.LegalholdTimestamp = ondiskTimestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, rinfo.Err = c.CopyObject(ctx, tgt.Bucket, object, tgt.Bucket, object, getCopyObjMetadata(objInfo, tgt.StorageClass), srcOpts, dstOpts); rinfo.Err != nil {
|
if _, rinfo.Err = c.CopyObject(ctx, tgt.Bucket, object, tgt.Bucket, object, getCopyObjMetadata(objInfo, tgt.StorageClass), srcOpts, dstOpts); rinfo.Err != nil {
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
rinfo.ReplicationStatus = replication.Failed
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
logger.LogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
||||||
@@ -1689,12 +1714,13 @@ type ReplicationPool struct {
|
|||||||
activeWorkers int32
|
activeWorkers int32
|
||||||
activeMRFWorkers int32
|
activeMRFWorkers int32
|
||||||
|
|
||||||
objLayer ObjectLayer
|
objLayer ObjectLayer
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
priority string
|
priority string
|
||||||
mu sync.RWMutex
|
maxWorkers int
|
||||||
mrfMU sync.Mutex
|
mu sync.RWMutex
|
||||||
resyncer *replicationResyncer
|
mrfMU sync.Mutex
|
||||||
|
resyncer *replicationResyncer
|
||||||
|
|
||||||
// workers:
|
// workers:
|
||||||
workers []chan ReplicationWorkerOperation
|
workers []chan ReplicationWorkerOperation
|
||||||
@@ -1740,9 +1766,13 @@ const (
|
|||||||
func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPoolOpts) *ReplicationPool {
|
func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPoolOpts) *ReplicationPool {
|
||||||
var workers, failedWorkers int
|
var workers, failedWorkers int
|
||||||
priority := "auto"
|
priority := "auto"
|
||||||
|
maxWorkers := WorkerMaxLimit
|
||||||
if opts.Priority != "" {
|
if opts.Priority != "" {
|
||||||
priority = opts.Priority
|
priority = opts.Priority
|
||||||
}
|
}
|
||||||
|
if opts.MaxWorkers > 0 {
|
||||||
|
maxWorkers = opts.MaxWorkers
|
||||||
|
}
|
||||||
switch priority {
|
switch priority {
|
||||||
case "fast":
|
case "fast":
|
||||||
workers = WorkerMaxLimit
|
workers = WorkerMaxLimit
|
||||||
@@ -1754,7 +1784,13 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
|||||||
workers = WorkerAutoDefault
|
workers = WorkerAutoDefault
|
||||||
failedWorkers = MRFWorkerAutoDefault
|
failedWorkers = MRFWorkerAutoDefault
|
||||||
}
|
}
|
||||||
|
if maxWorkers > 0 && workers > maxWorkers {
|
||||||
|
workers = maxWorkers
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxWorkers > 0 && failedWorkers > maxWorkers {
|
||||||
|
failedWorkers = maxWorkers
|
||||||
|
}
|
||||||
pool := &ReplicationPool{
|
pool := &ReplicationPool{
|
||||||
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
||||||
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
|
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
|
||||||
@@ -1766,6 +1802,7 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
objLayer: o,
|
objLayer: o,
|
||||||
priority: priority,
|
priority: priority,
|
||||||
|
maxWorkers: maxWorkers,
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.AddLargeWorkers()
|
pool.AddLargeWorkers()
|
||||||
@@ -1921,7 +1958,7 @@ func (p *ReplicationPool) ResizeWorkers(n, checkOld int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResizeWorkerPriority sets replication failed workers pool size
|
// ResizeWorkerPriority sets replication failed workers pool size
|
||||||
func (p *ReplicationPool) ResizeWorkerPriority(pri string) {
|
func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers int) {
|
||||||
var workers, mrfWorkers int
|
var workers, mrfWorkers int
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
switch pri {
|
switch pri {
|
||||||
@@ -1935,13 +1972,21 @@ func (p *ReplicationPool) ResizeWorkerPriority(pri string) {
|
|||||||
workers = WorkerAutoDefault
|
workers = WorkerAutoDefault
|
||||||
mrfWorkers = MRFWorkerAutoDefault
|
mrfWorkers = MRFWorkerAutoDefault
|
||||||
if len(p.workers) < WorkerAutoDefault {
|
if len(p.workers) < WorkerAutoDefault {
|
||||||
workers = int(math.Min(float64(len(p.workers)+1), WorkerAutoDefault))
|
workers = min(len(p.workers)+1, WorkerAutoDefault)
|
||||||
}
|
}
|
||||||
if p.mrfWorkerSize < MRFWorkerAutoDefault {
|
if p.mrfWorkerSize < MRFWorkerAutoDefault {
|
||||||
mrfWorkers = int(math.Min(float64(p.mrfWorkerSize+1), MRFWorkerAutoDefault))
|
mrfWorkers = min(p.mrfWorkerSize+1, MRFWorkerAutoDefault)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if maxWorkers > 0 && workers > maxWorkers {
|
||||||
|
workers = maxWorkers
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxWorkers > 0 && mrfWorkers > maxWorkers {
|
||||||
|
mrfWorkers = maxWorkers
|
||||||
|
}
|
||||||
p.priority = pri
|
p.priority = pri
|
||||||
|
p.maxWorkers = maxWorkers
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
p.ResizeWorkers(workers, 0)
|
p.ResizeWorkers(workers, 0)
|
||||||
p.ResizeFailedWorkers(mrfWorkers)
|
p.ResizeFailedWorkers(mrfWorkers)
|
||||||
@@ -2015,6 +2060,7 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
|||||||
globalReplicationPool.queueMRFSave(ri.ToMRFEntry())
|
globalReplicationPool.queueMRFSave(ri.ToMRFEntry())
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
prio := p.priority
|
prio := p.priority
|
||||||
|
maxWorkers := p.maxWorkers
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
switch prio {
|
switch prio {
|
||||||
case "fast":
|
case "fast":
|
||||||
@@ -2022,16 +2068,18 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
|||||||
case "slow":
|
case "slow":
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
||||||
default:
|
default:
|
||||||
if p.ActiveWorkers() < WorkerMaxLimit {
|
maxWorkers = min(maxWorkers, WorkerMaxLimit)
|
||||||
|
if p.ActiveWorkers() < maxWorkers {
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
workers := int(math.Min(float64(len(p.workers)+1), WorkerMaxLimit))
|
workers := min(len(p.workers)+1, maxWorkers)
|
||||||
existing := len(p.workers)
|
existing := len(p.workers)
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
p.ResizeWorkers(workers, existing)
|
p.ResizeWorkers(workers, existing)
|
||||||
}
|
}
|
||||||
if p.ActiveMRFWorkers() < MRFWorkerMaxLimit {
|
maxMRFWorkers := min(maxWorkers, MRFWorkerMaxLimit)
|
||||||
|
if p.ActiveMRFWorkers() < maxMRFWorkers {
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
workers := int(math.Min(float64(p.mrfWorkerSize+1), MRFWorkerMaxLimit))
|
workers := min(p.mrfWorkerSize+1, maxMRFWorkers)
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
p.ResizeFailedWorkers(workers)
|
p.ResizeFailedWorkers(workers)
|
||||||
}
|
}
|
||||||
@@ -2069,6 +2117,7 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
|||||||
globalReplicationPool.queueMRFSave(doi.ToMRFEntry())
|
globalReplicationPool.queueMRFSave(doi.ToMRFEntry())
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
prio := p.priority
|
prio := p.priority
|
||||||
|
maxWorkers := p.maxWorkers
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
switch prio {
|
switch prio {
|
||||||
case "fast":
|
case "fast":
|
||||||
@@ -2076,9 +2125,10 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
|||||||
case "slow":
|
case "slow":
|
||||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
||||||
default:
|
default:
|
||||||
if p.ActiveWorkers() < WorkerMaxLimit {
|
maxWorkers = min(maxWorkers, WorkerMaxLimit)
|
||||||
|
if p.ActiveWorkers() < maxWorkers {
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
workers := int(math.Min(float64(len(p.workers)+1), WorkerMaxLimit))
|
workers := min(len(p.workers)+1, maxWorkers)
|
||||||
existing := len(p.workers)
|
existing := len(p.workers)
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
p.ResizeWorkers(workers, existing)
|
p.ResizeWorkers(workers, existing)
|
||||||
@@ -2088,13 +2138,12 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
type replicationPoolOpts struct {
|
type replicationPoolOpts struct {
|
||||||
Priority string
|
Priority string
|
||||||
|
MaxWorkers int
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
globalReplicationPool = NewReplicationPool(ctx, objectAPI, replicationPoolOpts{
|
globalReplicationPool = NewReplicationPool(ctx, objectAPI, globalAPIConfig.getReplicationOpts())
|
||||||
Priority: globalAPIConfig.getReplicationPriority(),
|
|
||||||
})
|
|
||||||
globalReplicationStats = NewReplicationStats(ctx, objectAPI)
|
globalReplicationStats = NewReplicationStats(ctx, objectAPI)
|
||||||
go globalReplicationStats.trackEWMA()
|
go globalReplicationStats.trackEWMA()
|
||||||
}
|
}
|
||||||
@@ -2186,7 +2235,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
|
|||||||
return nil, oi, proxy
|
return nil, oi, proxy
|
||||||
}
|
}
|
||||||
for _, t := range proxyTargets.Targets {
|
for _, t := range proxyTargets.Targets {
|
||||||
tgt = globalBucketTargetSys.GetRemoteTargetClient(t.Arn)
|
tgt = globalBucketTargetSys.GetRemoteTargetClient(bucket, t.Arn)
|
||||||
if tgt == nil || globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
if tgt == nil || globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2206,7 +2255,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, rs *HTTPRa
|
|||||||
if rs != nil {
|
if rs != nil {
|
||||||
h, err := rs.ToHeader()
|
h, err := rs.ToHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Invalid range header for %s/%s(%s) - %w", bucket, object, opts.VersionID, err))
|
logger.LogIf(ctx, fmt.Errorf("invalid range header for %s/%s(%s) - %w", bucket, object, opts.VersionID, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gopts.Set(xhttp.Range, h)
|
gopts.Set(xhttp.Range, h)
|
||||||
@@ -2299,6 +2348,127 @@ func scheduleReplication(ctx context.Context, oi ObjectInfo, o ObjectLayer, dsc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// proxyTaggingToRepTarget proxies tagging requests to remote targets for
|
||||||
|
// active-active replicated setups
|
||||||
|
func proxyTaggingToRepTarget(ctx context.Context, bucket, object string, tags *tags.Tags, opts ObjectOptions, proxyTargets *madmin.BucketTargets) (proxy proxyResult) {
|
||||||
|
// this option is set when active-active replication is in place between site A -> B,
|
||||||
|
// and request hits site B that does not have the object yet.
|
||||||
|
if opts.ProxyRequest || (opts.ProxyHeaderSet && !opts.ProxyRequest) { // true only when site B sets MinIOSourceProxyRequest header
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errs := make([]error, len(proxyTargets.Targets))
|
||||||
|
for idx, t := range proxyTargets.Targets {
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, t.Arn)
|
||||||
|
if tgt == nil || globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if proxying explicitly disabled on remote target
|
||||||
|
if tgt.disableProxy {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idx := idx
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, tgt *TargetClient) {
|
||||||
|
defer wg.Done()
|
||||||
|
var err error
|
||||||
|
if tags != nil {
|
||||||
|
popts := minio.PutObjectTaggingOptions{
|
||||||
|
VersionID: opts.VersionID,
|
||||||
|
Internal: minio.AdvancedObjectTaggingOptions{
|
||||||
|
ReplicationProxyRequest: "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = tgt.PutObjectTagging(ctx, tgt.Bucket, object, tags, popts)
|
||||||
|
} else {
|
||||||
|
dopts := minio.RemoveObjectTaggingOptions{
|
||||||
|
VersionID: opts.VersionID,
|
||||||
|
Internal: minio.AdvancedObjectTaggingOptions{
|
||||||
|
ReplicationProxyRequest: "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = tgt.RemoveObjectTagging(ctx, tgt.Bucket, object, dopts)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errs[idx] = err
|
||||||
|
}
|
||||||
|
}(idx, tgt)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
var (
|
||||||
|
terr error
|
||||||
|
taggedCount int
|
||||||
|
)
|
||||||
|
for _, err := range errs {
|
||||||
|
if err == nil {
|
||||||
|
taggedCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
errCode := minio.ToErrorResponse(err).Code
|
||||||
|
if errCode != "NoSuchKey" && errCode != "NoSuchVersion" {
|
||||||
|
terr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// don't return error if at least one target was tagged successfully
|
||||||
|
if taggedCount == 0 && terr != nil {
|
||||||
|
proxy.Err = terr
|
||||||
|
}
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyGetTaggingToRepTarget proxies get tagging requests to remote targets for
|
||||||
|
// active-active replicated setups
|
||||||
|
func proxyGetTaggingToRepTarget(ctx context.Context, bucket, object string, opts ObjectOptions, proxyTargets *madmin.BucketTargets) (tgs *tags.Tags, proxy proxyResult) {
|
||||||
|
// this option is set when active-active replication is in place between site A -> B,
|
||||||
|
// and request hits site B that does not have the object yet.
|
||||||
|
if opts.ProxyRequest || (opts.ProxyHeaderSet && !opts.ProxyRequest) { // true only when site B sets MinIOSourceProxyRequest header
|
||||||
|
return nil, proxy
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errs := make([]error, len(proxyTargets.Targets))
|
||||||
|
tagSlc := make([]map[string]string, len(proxyTargets.Targets))
|
||||||
|
for idx, t := range proxyTargets.Targets {
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, t.Arn)
|
||||||
|
if tgt == nil || globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if proxying explicitly disabled on remote target
|
||||||
|
if tgt.disableProxy {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idx := idx
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, tgt *TargetClient) {
|
||||||
|
defer wg.Done()
|
||||||
|
var err error
|
||||||
|
gopts := minio.GetObjectTaggingOptions{
|
||||||
|
VersionID: opts.VersionID,
|
||||||
|
Internal: minio.AdvancedObjectTaggingOptions{
|
||||||
|
ReplicationProxyRequest: "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tgs, err = tgt.GetObjectTagging(ctx, tgt.Bucket, object, gopts)
|
||||||
|
if err != nil {
|
||||||
|
errs[idx] = err
|
||||||
|
} else {
|
||||||
|
tagSlc[idx] = tgs.ToMap()
|
||||||
|
}
|
||||||
|
}(idx, tgt)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for idx, err := range errs {
|
||||||
|
errCode := minio.ToErrorResponse(err).Code
|
||||||
|
if err != nil && errCode != "NoSuchKey" && errCode != "NoSuchVersion" {
|
||||||
|
return nil, proxyResult{Err: err}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
tgs, _ = tags.MapToObjectTags(tagSlc[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tgs, proxy
|
||||||
|
}
|
||||||
|
|
||||||
func scheduleReplicationDelete(ctx context.Context, dv DeletedObjectReplicationInfo, o ObjectLayer) {
|
func scheduleReplicationDelete(ctx context.Context, dv DeletedObjectReplicationInfo, o ObjectLayer) {
|
||||||
globalReplicationPool.queueReplicaDeleteTask(dv)
|
globalReplicationPool.queueReplicaDeleteTask(dv)
|
||||||
for arn := range dv.ReplicationState.Targets {
|
for arn := range dv.ReplicationState.Targets {
|
||||||
@@ -2485,7 +2655,7 @@ func newresyncer() *replicationResyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mark status of replication resync on remote target for the bucket
|
// mark status of replication resync on remote target for the bucket
|
||||||
func (s *replicationResyncer) markStatus(status ResyncStatusType, opts resyncOpts) {
|
func (s *replicationResyncer) markStatus(status ResyncStatusType, opts resyncOpts, objAPI ObjectLayer) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
|
|
||||||
@@ -2496,6 +2666,10 @@ func (s *replicationResyncer) markStatus(status ResyncStatusType, opts resyncOpt
|
|||||||
m.TargetsMap[opts.arn] = st
|
m.TargetsMap[opts.arn] = st
|
||||||
m.LastUpdate = UTCNow()
|
m.LastUpdate = UTCNow()
|
||||||
s.statusMap[opts.bucket] = m
|
s.statusMap[opts.bucket] = m
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
saveResyncStatus(ctx, opts.bucket, m, objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update replication resync stats for bucket's remote target
|
// update replication resync stats for bucket's remote target
|
||||||
@@ -2525,7 +2699,7 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
|
|
||||||
resyncStatus := ResyncFailed
|
resyncStatus := ResyncFailed
|
||||||
defer func() {
|
defer func() {
|
||||||
s.markStatus(resyncStatus, opts)
|
s.markStatus(resyncStatus, opts, objectAPI)
|
||||||
globalSiteResyncMetrics.incBucket(opts, resyncStatus)
|
globalSiteResyncMetrics.incBucket(opts, resyncStatus)
|
||||||
s.workerCh <- struct{}{}
|
s.workerCh <- struct{}{}
|
||||||
}()
|
}()
|
||||||
@@ -2554,19 +2728,19 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
logger.LogIf(ctx, fmt.Errorf("replication resync failed for %s - arn specified %s is missing in the replication config", opts.bucket, opts.arn))
|
logger.LogIf(ctx, fmt.Errorf("replication resync failed for %s - arn specified %s is missing in the replication config", opts.bucket, opts.arn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(opts.arn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(opts.bucket, opts.arn)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("replication resync failed for %s - target could not be created for arn %s", opts.bucket, opts.arn))
|
logger.LogIf(ctx, fmt.Errorf("replication resync failed for %s - target could not be created for arn %s", opts.bucket, opts.arn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// mark resync status as resync started
|
// mark resync status as resync started
|
||||||
if !heal {
|
if !heal {
|
||||||
s.markStatus(ResyncStarted, opts)
|
s.markStatus(ResyncStarted, opts, objectAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk through all object versions - Walk() is always in ascending order needed to ensure
|
// Walk through all object versions - Walk() is always in ascending order needed to ensure
|
||||||
// delete marker replicated to target after object version is first created.
|
// delete marker replicated to target after object version is first created.
|
||||||
if err := objectAPI.Walk(ctx, opts.bucket, "", objInfoCh, ObjectOptions{}); err != nil {
|
if err := objectAPI.Walk(ctx, opts.bucket, "", objInfoCh, WalkOptions{}); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2632,16 +2806,18 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
roi.EventType = ReplicateExisting
|
roi.EventType = ReplicateExisting
|
||||||
replicateObject(ctx, roi, objectAPI)
|
replicateObject(ctx, roi, objectAPI)
|
||||||
}
|
}
|
||||||
_, err = tgt.StatObject(ctx, tgt.Bucket, roi.Name, minio.StatObjectOptions{
|
|
||||||
|
st := TargetReplicationResyncStatus{
|
||||||
|
Object: roi.Name,
|
||||||
|
Bucket: roi.Bucket,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := tgt.StatObject(ctx, tgt.Bucket, roi.Name, minio.StatObjectOptions{
|
||||||
VersionID: roi.VersionID,
|
VersionID: roi.VersionID,
|
||||||
Internal: minio.AdvancedGetOptions{
|
Internal: minio.AdvancedGetOptions{
|
||||||
ReplicationProxyRequest: "false",
|
ReplicationProxyRequest: "false",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
st := TargetReplicationResyncStatus{
|
|
||||||
Object: roi.Name,
|
|
||||||
Bucket: roi.Bucket,
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if roi.DeleteMarker && isErrMethodNotAllowed(ErrorRespToObjectError(err, opts.bucket, roi.Name)) {
|
if roi.DeleteMarker && isErrMethodNotAllowed(ErrorRespToObjectError(err, opts.bucket, roi.Name)) {
|
||||||
st.ReplicatedCount++
|
st.ReplicatedCount++
|
||||||
@@ -2937,7 +3113,7 @@ func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
objInfoCh := make(chan ObjectInfo, 10)
|
objInfoCh := make(chan ObjectInfo, 10)
|
||||||
if err := objAPI.Walk(ctx, bucket, opts.Prefix, objInfoCh, ObjectOptions{}); err != nil {
|
if err := objAPI.Walk(ctx, bucket, opts.Prefix, objInfoCh, WalkOptions{}); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -3148,10 +3324,11 @@ func (p *ReplicationPool) persistMRF() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
entries[e.versionID] = e
|
||||||
|
|
||||||
if len(entries) >= mrfMaxEntries {
|
if len(entries) >= mrfMaxEntries {
|
||||||
saveMRFToDisk()
|
saveMRFToDisk()
|
||||||
}
|
}
|
||||||
entries[e.versionID] = e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3175,16 +3352,22 @@ func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
|
|||||||
select {
|
select {
|
||||||
case p.mrfSaveCh <- entry:
|
case p.mrfSaveCh <- entry:
|
||||||
default:
|
default:
|
||||||
|
atomic.AddUint64(&globalReplicationStats.mrfStats.TotalDroppedCount, 1)
|
||||||
|
atomic.AddUint64(&globalReplicationStats.mrfStats.TotalDroppedBytes, uint64(entry.sz))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntries, data []byte) {
|
func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntries) {
|
||||||
newReader := func() io.ReadCloser {
|
newReader := func() io.ReadCloser {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
|
// Initialize MRF meta header.
|
||||||
|
var data [4]byte
|
||||||
|
binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat)
|
||||||
|
binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion)
|
||||||
mw := msgp.NewWriter(w)
|
mw := msgp.NewWriter(w)
|
||||||
n, err := mw.Write(data)
|
n, err := mw.Write(data[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.CloseWithError(err)
|
w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
@@ -3200,7 +3383,11 @@ func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntr
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, localDrive := range globalLocalDrives {
|
globalLocalDrivesMu.RLock()
|
||||||
|
localDrives := globalLocalDrives
|
||||||
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
|
for _, localDrive := range localDrives {
|
||||||
r := newReader()
|
r := newReader()
|
||||||
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
||||||
r.Close()
|
r.Close()
|
||||||
@@ -3222,15 +3409,10 @@ func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string
|
|||||||
|
|
||||||
v := MRFReplicateEntries{
|
v := MRFReplicateEntries{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
Version: mrfMetaVersionV1,
|
Version: mrfMetaVersion,
|
||||||
}
|
}
|
||||||
data := make([]byte, 4, v.Msgsize()+4)
|
|
||||||
|
|
||||||
// Initialize the resync meta header.
|
p.persistToDrive(ctx, v)
|
||||||
binary.LittleEndian.PutUint16(data[0:2], mrfMetaFormat)
|
|
||||||
binary.LittleEndian.PutUint16(data[2:4], mrfMetaVersion)
|
|
||||||
|
|
||||||
p.persistToDrive(ctx, v, data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load mrf entries from disk
|
// load mrf entries from disk
|
||||||
@@ -3241,8 +3423,8 @@ func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
|
|||||||
if !p.initialized() {
|
if !p.initialized() {
|
||||||
return re, nil
|
return re, nil
|
||||||
}
|
}
|
||||||
data := make([]byte, 4)
|
var data [4]byte
|
||||||
n, err := rc.Read(data)
|
n, err := rc.Read(data[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return re, err
|
return re, err
|
||||||
}
|
}
|
||||||
@@ -3267,7 +3449,12 @@ func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
|
|||||||
|
|
||||||
return re, nil
|
return re, nil
|
||||||
}
|
}
|
||||||
for _, localDrive := range globalLocalDrives {
|
|
||||||
|
globalLocalDrivesMu.RLock()
|
||||||
|
localDrives := globalLocalDrives
|
||||||
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
|
for _, localDrive := range localDrives {
|
||||||
rc, err := localDrive.ReadFileStream(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), 0, -1)
|
rc, err := localDrive.ReadFileStream(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), 0, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -3356,7 +3543,7 @@ func (p *ReplicationPool) initialized() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getMRF returns MRF entries for this node.
|
// getMRF returns MRF entries for this node.
|
||||||
func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch chan madmin.ReplicationMRF, err error) {
|
func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch <-chan madmin.ReplicationMRF, err error) {
|
||||||
mrfRec, err := p.loadMRF()
|
mrfRec, err := p.loadMRF()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -3366,7 +3553,7 @@ func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch chan ma
|
|||||||
go func() {
|
go func() {
|
||||||
defer close(mrfCh)
|
defer close(mrfCh)
|
||||||
for vID, e := range mrfRec.Entries {
|
for vID, e := range mrfRec.Entries {
|
||||||
if e.Bucket != bucket && bucket != "" {
|
if bucket != "" && e.Bucket != bucket {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
|
|||||||
+109
-8
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -40,14 +41,29 @@ const (
|
|||||||
defaultHealthCheckReloadDuration = 30 * time.Minute
|
defaultHealthCheckReloadDuration = 30 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type arnTarget struct {
|
||||||
|
Client *TargetClient
|
||||||
|
lastRefresh time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// arnErrs represents number of errors seen for a ARN and if update is in progress
|
||||||
|
// to refresh remote targets from bucket metadata.
|
||||||
|
type arnErrs struct {
|
||||||
|
count int64
|
||||||
|
updateInProgress bool
|
||||||
|
bucket string
|
||||||
|
}
|
||||||
|
|
||||||
// BucketTargetSys represents bucket targets subsystem
|
// BucketTargetSys represents bucket targets subsystem
|
||||||
type BucketTargetSys struct {
|
type BucketTargetSys struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
arnRemotesMap map[string]*TargetClient
|
arnRemotesMap map[string]arnTarget
|
||||||
targetsMap map[string][]madmin.BucketTarget
|
targetsMap map[string][]madmin.BucketTarget
|
||||||
hMutex sync.RWMutex
|
hMutex sync.RWMutex
|
||||||
hc map[string]epHealth
|
hc map[string]epHealth
|
||||||
hcClient *madmin.AnonymousClient
|
hcClient *madmin.AnonymousClient
|
||||||
|
aMutex sync.RWMutex
|
||||||
|
arnErrsMap map[string]arnErrs // map of ARN to error count of failures to get target
|
||||||
}
|
}
|
||||||
|
|
||||||
type latencyStat struct {
|
type latencyStat struct {
|
||||||
@@ -336,6 +352,27 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if target is a MinIO server and alive
|
||||||
|
hcCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||||
|
scheme := "http"
|
||||||
|
if tgt.Secure {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
result := <-sys.hcClient.Alive(hcCtx, madmin.AliveOpts{}, madmin.ServerProperties{
|
||||||
|
Endpoint: tgt.Endpoint,
|
||||||
|
Scheme: scheme,
|
||||||
|
})
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
if result.Error != nil {
|
||||||
|
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: result.Error, AccessKey: tgt.Credentials.AccessKey}
|
||||||
|
}
|
||||||
|
if !result.Online {
|
||||||
|
err := errors.New("Health check timed out after 3 seconds")
|
||||||
|
return RemoteTargetConnectionErr{Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
|
|
||||||
@@ -364,7 +401,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
sys.targetsMap[bucket] = newtgts
|
sys.targetsMap[bucket] = newtgts
|
||||||
sys.arnRemotesMap[tgt.Arn] = clnt
|
sys.arnRemotesMap[tgt.Arn] = arnTarget{Client: clnt}
|
||||||
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -432,11 +469,71 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sys *BucketTargetSys) markRefreshInProgress(bucket, arn string) {
|
||||||
|
sys.aMutex.Lock()
|
||||||
|
defer sys.aMutex.Unlock()
|
||||||
|
if v, ok := sys.arnErrsMap[arn]; !ok {
|
||||||
|
sys.arnErrsMap[arn] = arnErrs{
|
||||||
|
updateInProgress: true,
|
||||||
|
count: v.count + 1,
|
||||||
|
bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *BucketTargetSys) markRefreshDone(bucket, arn string) {
|
||||||
|
sys.aMutex.Lock()
|
||||||
|
defer sys.aMutex.Unlock()
|
||||||
|
if v, ok := sys.arnErrsMap[arn]; ok {
|
||||||
|
sys.arnErrsMap[arn] = arnErrs{
|
||||||
|
updateInProgress: false,
|
||||||
|
count: v.count,
|
||||||
|
bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *BucketTargetSys) isReloadingTarget(bucket, arn string) bool {
|
||||||
|
sys.aMutex.RLock()
|
||||||
|
defer sys.aMutex.RUnlock()
|
||||||
|
if v, ok := sys.arnErrsMap[arn]; ok {
|
||||||
|
return v.updateInProgress
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *BucketTargetSys) incTargetErr(bucket, arn string) {
|
||||||
|
sys.aMutex.Lock()
|
||||||
|
defer sys.aMutex.Unlock()
|
||||||
|
if v, ok := sys.arnErrsMap[arn]; ok {
|
||||||
|
sys.arnErrsMap[arn] = arnErrs{
|
||||||
|
updateInProgress: v.updateInProgress,
|
||||||
|
count: v.count + 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetRemoteTargetClient returns minio-go client for replication target instance
|
// GetRemoteTargetClient returns minio-go client for replication target instance
|
||||||
func (sys *BucketTargetSys) GetRemoteTargetClient(arn string) *TargetClient {
|
func (sys *BucketTargetSys) GetRemoteTargetClient(bucket, arn string) *TargetClient {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
defer sys.RUnlock()
|
tgt := sys.arnRemotesMap[arn]
|
||||||
return sys.arnRemotesMap[arn]
|
sys.RUnlock()
|
||||||
|
|
||||||
|
if tgt.Client != nil {
|
||||||
|
return tgt.Client
|
||||||
|
}
|
||||||
|
defer func() { // lazy refresh remote targets
|
||||||
|
if tgt.Client == nil && !sys.isReloadingTarget(bucket, arn) && (tgt.lastRefresh.Equal(timeSentinel) || tgt.lastRefresh.Before(UTCNow().Add(-5*time.Minute))) {
|
||||||
|
tgts, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket)
|
||||||
|
if err == nil {
|
||||||
|
sys.markRefreshInProgress(bucket, arn)
|
||||||
|
sys.UpdateAllTargets(bucket, tgts)
|
||||||
|
sys.markRefreshDone(bucket, arn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sys.incTargetErr(bucket, arn)
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRemoteBucketTargetByArn returns BucketTarget for a ARN
|
// GetRemoteBucketTargetByArn returns BucketTarget for a ARN
|
||||||
@@ -457,8 +554,9 @@ func (sys *BucketTargetSys) GetRemoteBucketTargetByArn(ctx context.Context, buck
|
|||||||
// NewBucketTargetSys - creates new replication system.
|
// NewBucketTargetSys - creates new replication system.
|
||||||
func NewBucketTargetSys(ctx context.Context) *BucketTargetSys {
|
func NewBucketTargetSys(ctx context.Context) *BucketTargetSys {
|
||||||
sys := &BucketTargetSys{
|
sys := &BucketTargetSys{
|
||||||
arnRemotesMap: make(map[string]*TargetClient),
|
arnRemotesMap: make(map[string]arnTarget),
|
||||||
targetsMap: make(map[string][]madmin.BucketTarget),
|
targetsMap: make(map[string][]madmin.BucketTarget),
|
||||||
|
arnErrsMap: make(map[string]arnErrs),
|
||||||
hc: make(map[string]epHealth),
|
hc: make(map[string]epHealth),
|
||||||
hcClient: newHCClient(),
|
hcClient: newHCClient(),
|
||||||
}
|
}
|
||||||
@@ -502,7 +600,10 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = arnTarget{
|
||||||
|
Client: tgtClient,
|
||||||
|
lastRefresh: UTCNow(),
|
||||||
|
}
|
||||||
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,7 +627,7 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
|
|||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = arnTarget{Client: tgtClient}
|
||||||
sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit)
|
sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit)
|
||||||
}
|
}
|
||||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
sys.targetsMap[bucket.Name] = cfg.Targets
|
||||||
|
|||||||
+3
-7
@@ -131,7 +131,7 @@ func performCallhome(ctx context.Context) {
|
|||||||
Version: madmin.HealthInfoVersion,
|
Version: madmin.HealthInfoVersion,
|
||||||
Minio: madmin.MinioHealthInfo{
|
Minio: madmin.MinioHealthInfo{
|
||||||
Info: madmin.MinioInfo{
|
Info: madmin.MinioInfo{
|
||||||
DeploymentID: globalDeploymentID,
|
DeploymentID: globalDeploymentID(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -157,15 +157,11 @@ func performCallhome(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
healthURL = "https://subnet.min.io/api/health/upload"
|
subnetHealthPath = "/api/health/upload"
|
||||||
healthURLDev = "http://localhost:9000/api/health/upload"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func sendHealthInfo(ctx context.Context, healthInfo madmin.HealthInfo) error {
|
func sendHealthInfo(ctx context.Context, healthInfo madmin.HealthInfo) error {
|
||||||
url := healthURL
|
url := globalSubnetConfig.BaseURL + subnetHealthPath
|
||||||
if globalIsCICD {
|
|
||||||
url = healthURLDev
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("health_%s.json.gz", UTCNow().Format("20060102150405"))
|
filename := fmt.Sprintf("health_%s.json.gz", UTCNow().Format("20060102150405"))
|
||||||
url += "?filename=" + filename
|
url += "?filename=" + filename
|
||||||
|
|||||||
+144
-86
@@ -46,10 +46,10 @@ import (
|
|||||||
"github.com/inconshreveable/mousetrap"
|
"github.com/inconshreveable/mousetrap"
|
||||||
dns2 "github.com/miekg/dns"
|
dns2 "github.com/miekg/dns"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
|
consoleapi "github.com/minio/console/api"
|
||||||
|
"github.com/minio/console/api/operations"
|
||||||
consoleoauth2 "github.com/minio/console/pkg/auth/idp/oauth2"
|
consoleoauth2 "github.com/minio/console/pkg/auth/idp/oauth2"
|
||||||
consoleCerts "github.com/minio/console/pkg/certs"
|
consoleCerts "github.com/minio/console/pkg/certs"
|
||||||
"github.com/minio/console/restapi"
|
|
||||||
"github.com/minio/console/restapi/operations"
|
|
||||||
"github.com/minio/kes-go"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
@@ -122,8 +122,8 @@ func init() {
|
|||||||
const consolePrefix = "CONSOLE_"
|
const consolePrefix = "CONSOLE_"
|
||||||
|
|
||||||
func minioConfigToConsoleFeatures() {
|
func minioConfigToConsoleFeatures() {
|
||||||
os.Setenv("CONSOLE_PBKDF_SALT", globalDeploymentID)
|
os.Setenv("CONSOLE_PBKDF_SALT", globalDeploymentID())
|
||||||
os.Setenv("CONSOLE_PBKDF_PASSPHRASE", globalDeploymentID)
|
os.Setenv("CONSOLE_PBKDF_PASSPHRASE", globalDeploymentID())
|
||||||
if globalMinioEndpoint != "" {
|
if globalMinioEndpoint != "" {
|
||||||
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
|
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
|
||||||
} else {
|
} else {
|
||||||
@@ -167,14 +167,34 @@ func minioConfigToConsoleFeatures() {
|
|||||||
if value := env.Get(config.EnvBrowserLoginAnimation, "on"); value != "" {
|
if value := env.Get(config.EnvBrowserLoginAnimation, "on"); value != "" {
|
||||||
os.Setenv("CONSOLE_ANIMATED_LOGIN", value)
|
os.Setenv("CONSOLE_ANIMATED_LOGIN", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass on the session duration environment variable, else we will default to 12 hours
|
// Pass on the session duration environment variable, else we will default to 12 hours
|
||||||
if value := env.Get(config.EnvBrowserSessionDuration, ""); value != "" {
|
if valueSts := env.Get(config.EnvMinioStsDuration, ""); valueSts != "" {
|
||||||
os.Setenv("CONSOLE_STS_DURATION", value)
|
os.Setenv("CONSOLE_STS_DURATION", valueSts)
|
||||||
|
} else if valueSession := env.Get(config.EnvBrowserSessionDuration, ""); valueSession != "" {
|
||||||
|
os.Setenv("CONSOLE_STS_DURATION", valueSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
||||||
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
|
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
|
||||||
|
|
||||||
|
// This section sets Browser (console) stored config
|
||||||
|
if valueSCP := globalBrowserConfig.GetCSPolicy(); valueSCP != "" {
|
||||||
|
os.Setenv("CONSOLE_SECURE_CONTENT_SECURITY_POLICY", valueSCP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hstsSeconds := globalBrowserConfig.GetHSTSSeconds(); hstsSeconds > 0 {
|
||||||
|
isubdom := globalBrowserConfig.IsHSTSIncludeSubdomains()
|
||||||
|
isprel := globalBrowserConfig.IsHSTSPreload()
|
||||||
|
os.Setenv("CONSOLE_SECURE_STS_SECONDS", strconv.Itoa(hstsSeconds))
|
||||||
|
os.Setenv("CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS", isubdom)
|
||||||
|
os.Setenv("CONSOLE_SECURE_STS_PRELOAD", isprel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if valueRefer := globalBrowserConfig.GetReferPolicy(); valueRefer != "" {
|
||||||
|
os.Setenv("CONSOLE_SECURE_REFERRER_POLICY", valueRefer)
|
||||||
|
}
|
||||||
|
|
||||||
globalSubnetConfig.ApplyEnv()
|
globalSubnetConfig.ApplyEnv()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +211,7 @@ func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
|
|||||||
DisplayName: cfg.DisplayName,
|
DisplayName: cfg.DisplayName,
|
||||||
ClientID: cfg.ClientID,
|
ClientID: cfg.ClientID,
|
||||||
ClientSecret: cfg.ClientSecret,
|
ClientSecret: cfg.ClientSecret,
|
||||||
HMACSalt: globalDeploymentID,
|
HMACSalt: globalDeploymentID(),
|
||||||
HMACPassphrase: cfg.ClientID,
|
HMACPassphrase: cfg.ClientID,
|
||||||
Scopes: strings.Join(cfg.DiscoveryDoc.ScopesSupported, ","),
|
Scopes: strings.Join(cfg.DiscoveryDoc.ScopesSupported, ","),
|
||||||
Userinfo: cfg.ClaimUserinfo,
|
Userinfo: cfg.ClaimUserinfo,
|
||||||
@@ -204,7 +224,7 @@ func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConsoleServer() (*restapi.Server, error) {
|
func initConsoleServer() (*consoleapi.Server, error) {
|
||||||
// unset all console_ environment variables.
|
// unset all console_ environment variables.
|
||||||
for _, cenv := range env.List(consolePrefix) {
|
for _, cenv := range env.List(consolePrefix) {
|
||||||
os.Unsetenv(cenv)
|
os.Unsetenv(cenv)
|
||||||
@@ -222,9 +242,9 @@ func initConsoleServer() (*restapi.Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set certs before other console initialization
|
// set certs before other console initialization
|
||||||
restapi.GlobalRootCAs, restapi.GlobalPublicCerts, restapi.GlobalTLSCertsManager = globalRootCAs, globalPublicCerts, globalTLSCerts
|
consoleapi.GlobalRootCAs, consoleapi.GlobalPublicCerts, consoleapi.GlobalTLSCertsManager = globalRootCAs, globalPublicCerts, globalTLSCerts
|
||||||
|
|
||||||
swaggerSpec, err := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON)
|
swaggerSpec, err := loads.Embedded(consoleapi.SwaggerJSON, consoleapi.FlatSwaggerJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -235,18 +255,18 @@ func initConsoleServer() (*restapi.Server, error) {
|
|||||||
// Disable console logging if server debug log is not enabled
|
// Disable console logging if server debug log is not enabled
|
||||||
noLog := func(string, ...interface{}) {}
|
noLog := func(string, ...interface{}) {}
|
||||||
|
|
||||||
restapi.LogInfo = noLog
|
consoleapi.LogInfo = noLog
|
||||||
restapi.LogError = noLog
|
consoleapi.LogError = noLog
|
||||||
api.Logger = noLog
|
api.Logger = noLog
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass in console application config. This needs to happen before the
|
// Pass in console application config. This needs to happen before the
|
||||||
// ConfigureAPI() call.
|
// ConfigureAPI() call.
|
||||||
restapi.GlobalMinIOConfig = restapi.MinIOConfig{
|
consoleapi.GlobalMinIOConfig = consoleapi.MinIOConfig{
|
||||||
OpenIDProviders: buildOpenIDConsoleConfig(),
|
OpenIDProviders: buildOpenIDConsoleConfig(),
|
||||||
}
|
}
|
||||||
|
|
||||||
server := restapi.NewServer(api)
|
server := consoleapi.NewServer(api)
|
||||||
// register all APIs
|
// register all APIs
|
||||||
server.ConfigureAPI()
|
server.ConfigureAPI()
|
||||||
|
|
||||||
@@ -254,16 +274,16 @@ func initConsoleServer() (*restapi.Server, error) {
|
|||||||
|
|
||||||
server.Host = globalMinioConsoleHost
|
server.Host = globalMinioConsoleHost
|
||||||
server.Port = consolePort
|
server.Port = consolePort
|
||||||
restapi.Port = globalMinioConsolePort
|
consoleapi.Port = globalMinioConsolePort
|
||||||
restapi.Hostname = globalMinioConsoleHost
|
consoleapi.Hostname = globalMinioConsoleHost
|
||||||
|
|
||||||
if globalIsTLS {
|
if globalIsTLS {
|
||||||
// If TLS certificates are provided enforce the HTTPS.
|
// If TLS certificates are provided enforce the HTTPS.
|
||||||
server.EnabledListeners = []string{"https"}
|
server.EnabledListeners = []string{"https"}
|
||||||
server.TLSPort = consolePort
|
server.TLSPort = consolePort
|
||||||
// Need to store tls-port, tls-host un config variables so secure.middleware can read from there
|
// Need to store tls-port, tls-host un config variables so secure.middleware can read from there
|
||||||
restapi.TLSPort = globalMinioConsolePort
|
consoleapi.TLSPort = globalMinioConsolePort
|
||||||
restapi.Hostname = globalMinioConsoleHost
|
consoleapi.Hostname = globalMinioConsoleHost
|
||||||
}
|
}
|
||||||
|
|
||||||
return server, nil
|
return server, nil
|
||||||
@@ -307,84 +327,121 @@ func checkUpdate(mode string) {
|
|||||||
logger.Info(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
|
logger.Info(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) {
|
func newConfigDir(dir string, dirSet bool, getDefaultDir func() string) (*ConfigDir, error) {
|
||||||
var dir string
|
if dir == "" {
|
||||||
var dirSet bool
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case ctx.IsSet(option):
|
|
||||||
dir = ctx.String(option)
|
|
||||||
dirSet = true
|
|
||||||
case ctx.GlobalIsSet(option):
|
|
||||||
dir = ctx.GlobalString(option)
|
|
||||||
dirSet = true
|
|
||||||
// cli package does not expose parent's option option. Below code is workaround.
|
|
||||||
if dir == "" || dir == getDefaultDir() {
|
|
||||||
dirSet = false // Unset to false since GlobalIsSet() true is a false positive.
|
|
||||||
if ctx.Parent().GlobalIsSet(option) {
|
|
||||||
dir = ctx.Parent().GlobalString(option)
|
|
||||||
dirSet = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Neither local nor global option is provided. In this case, try to use
|
|
||||||
// default directory.
|
|
||||||
dir = getDefaultDir()
|
dir = getDefaultDir()
|
||||||
if dir == "" {
|
|
||||||
logger.FatalIf(errInvalidArgument, "%s option must be provided", option)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
logger.FatalIf(errors.New("empty directory"), "%s directory cannot be empty", option)
|
if !dirSet {
|
||||||
|
return nil, fmt.Errorf("missing option must be provided")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("provided option cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disallow relative paths, figure out absolute paths.
|
// Disallow relative paths, figure out absolute paths.
|
||||||
dirAbs, err := filepath.Abs(dir)
|
dirAbs, err := filepath.Abs(dir)
|
||||||
logger.FatalIf(err, "Unable to fetch absolute path for %s=%s", option, dir)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = mkdirAllIgnorePerm(dirAbs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create the directory `%s`: %w", dirAbs, err)
|
||||||
|
}
|
||||||
|
|
||||||
logger.FatalIf(mkdirAllIgnorePerm(dirAbs), "Unable to create directory specified %s=%s", option, dir)
|
return &ConfigDir{path: dirAbs}, nil
|
||||||
|
|
||||||
return &ConfigDir{path: dirAbs}, dirSet
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCommonCmdArgs(ctx *cli.Context) {
|
func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
|
||||||
// Get "json" flag from command line argument and
|
// Get "json" flag from command line argument and
|
||||||
// enable json and quite modes if json flag is turned on.
|
ctxt.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json")
|
||||||
globalCLIContext.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json")
|
|
||||||
if globalCLIContext.JSON {
|
|
||||||
logger.EnableJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get quiet flag from command line argument.
|
// Get quiet flag from command line argument.
|
||||||
globalCLIContext.Quiet = ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet")
|
ctxt.Quiet = ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet")
|
||||||
if globalCLIContext.Quiet {
|
|
||||||
logger.EnableQuiet()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get anonymous flag from command line argument.
|
// Get anonymous flag from command line argument.
|
||||||
globalCLIContext.Anonymous = ctx.IsSet("anonymous") || ctx.GlobalIsSet("anonymous")
|
ctxt.Anonymous = ctx.IsSet("anonymous") || ctx.GlobalIsSet("anonymous")
|
||||||
if globalCLIContext.Anonymous {
|
|
||||||
logger.EnableAnonymous()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch address option
|
// Fetch address option
|
||||||
addr := ctx.GlobalString("address")
|
ctxt.Addr = ctx.GlobalString("address")
|
||||||
if addr == "" || addr == ":"+GlobalMinioDefaultPort {
|
if ctxt.Addr == "" || ctxt.Addr == ":"+GlobalMinioDefaultPort {
|
||||||
addr = ctx.String("address")
|
ctxt.Addr = ctx.String("address")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch console address option
|
// Fetch console address option
|
||||||
consoleAddr := ctx.GlobalString("console-address")
|
ctxt.ConsoleAddr = ctx.GlobalString("console-address")
|
||||||
if consoleAddr == "" {
|
if ctxt.ConsoleAddr == "" {
|
||||||
consoleAddr = ctx.String("console-address")
|
ctxt.ConsoleAddr = ctx.String("console-address")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check "no-compat" flag from command line argument.
|
||||||
|
ctxt.StrictS3Compat = !(ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat"))
|
||||||
|
ctxt.PreAllocate = ctx.IsSet("pre-allocate") || ctx.GlobalIsSet("pre-allocate")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("config-dir"):
|
||||||
|
ctxt.ConfigDir = ctx.String("config-dir")
|
||||||
|
ctxt.configDirSet = true
|
||||||
|
case ctx.GlobalIsSet("config-dir"):
|
||||||
|
ctxt.ConfigDir = ctx.GlobalString("config-dir")
|
||||||
|
ctxt.configDirSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("certs-dir"):
|
||||||
|
ctxt.CertsDir = ctx.String("certs-dir")
|
||||||
|
ctxt.certsDirSet = true
|
||||||
|
case ctx.GlobalIsSet("certs-dir"):
|
||||||
|
ctxt.CertsDir = ctx.GlobalString("certs-dir")
|
||||||
|
ctxt.certsDirSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt.FTP = ctx.StringSlice("ftp")
|
||||||
|
ctxt.SFTP = ctx.StringSlice("sftp")
|
||||||
|
|
||||||
|
ctxt.Interface = ctx.String("interface")
|
||||||
|
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
|
||||||
|
ctxt.ConnReadDeadline = ctx.Duration("conn-read-deadline")
|
||||||
|
ctxt.ConnWriteDeadline = ctx.Duration("conn-write-deadline")
|
||||||
|
|
||||||
|
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
|
||||||
|
ctxt.IdleTimeout = ctx.Duration("idle-timeout")
|
||||||
|
ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")
|
||||||
|
|
||||||
|
if conf := ctx.String("config"); len(conf) > 0 {
|
||||||
|
err = mergeServerCtxtFromConfigFile(conf, ctxt)
|
||||||
|
} else {
|
||||||
|
err = mergeDisksLayoutFromArgs(serverCmdArgs(ctx), ctxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCommonArgs(ctxt serverCtxt) {
|
||||||
|
if ctxt.JSON {
|
||||||
|
logger.EnableJSON()
|
||||||
|
}
|
||||||
|
if ctxt.Quiet {
|
||||||
|
logger.EnableQuiet()
|
||||||
|
}
|
||||||
|
if ctxt.Anonymous {
|
||||||
|
logger.EnableAnonymous()
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleAddr := ctxt.ConsoleAddr
|
||||||
|
addr := ctxt.Addr
|
||||||
|
configDir := ctxt.ConfigDir
|
||||||
|
configSet := ctxt.configDirSet
|
||||||
|
certsDir := ctxt.CertsDir
|
||||||
|
certsSet := ctxt.certsDirSet
|
||||||
|
|
||||||
if consoleAddr == "" {
|
if consoleAddr == "" {
|
||||||
p, err := xnet.GetFreePort()
|
p, err := xnet.GetFreePort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.FatalIf(err, "Unable to get free port for Console UI on the host")
|
logger.FatalIf(err, "Unable to get free port for Console UI on the host")
|
||||||
}
|
}
|
||||||
|
// hold the port
|
||||||
|
l, err := net.Listen("TCP", fmt.Sprintf(":%s", p.String()))
|
||||||
|
if err == nil {
|
||||||
|
defer l.Close()
|
||||||
|
}
|
||||||
consoleAddr = net.JoinHostPort("", p.String())
|
consoleAddr = net.JoinHostPort("", p.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,16 +471,12 @@ func handleCommonCmdArgs(ctx *cli.Context) {
|
|||||||
|
|
||||||
globalMinioAddr = addr
|
globalMinioAddr = addr
|
||||||
|
|
||||||
// Check "no-compat" flag from command line argument.
|
|
||||||
globalCLIContext.StrictS3Compat = true
|
|
||||||
if ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat") {
|
|
||||||
globalCLIContext.StrictS3Compat = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all config, certs and CAs directories.
|
// Set all config, certs and CAs directories.
|
||||||
var configSet, certsSet bool
|
var err error
|
||||||
globalConfigDir, configSet = newConfigDirFromCtx(ctx, "config-dir", defaultConfigDir.Get)
|
globalConfigDir, err = newConfigDir(configDir, configSet, defaultConfigDir.Get)
|
||||||
globalCertsDir, certsSet = newConfigDirFromCtx(ctx, "certs-dir", defaultCertsDir.Get)
|
logger.FatalIf(err, "Unable to initialize the (deprecated) config directory")
|
||||||
|
globalCertsDir, err = newConfigDir(certsDir, certsSet, defaultCertsDir.Get)
|
||||||
|
logger.FatalIf(err, "Unable to initialize the certs directory")
|
||||||
|
|
||||||
// Remove this code when we deprecate and remove config-dir.
|
// Remove this code when we deprecate and remove config-dir.
|
||||||
// This code is to make sure we inherit from the config-dir
|
// This code is to make sure we inherit from the config-dir
|
||||||
@@ -435,9 +488,11 @@ func handleCommonCmdArgs(ctx *cli.Context) {
|
|||||||
globalCertsCADir = &ConfigDir{path: filepath.Join(globalCertsDir.Get(), certsCADir)}
|
globalCertsCADir = &ConfigDir{path: filepath.Join(globalCertsDir.Get(), certsCADir)}
|
||||||
|
|
||||||
logger.FatalIf(mkdirAllIgnorePerm(globalCertsCADir.Get()), "Unable to create certs CA directory at %s", globalCertsCADir.Get())
|
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.
|
func runDNSCache(ctx *cli.Context) {
|
||||||
dnsTTL := ctx.Duration("dns-cache-ttl")
|
dnsTTL := ctx.Duration("dns-cache-ttl")
|
||||||
|
// Check if we have configured a custom DNS cache TTL.
|
||||||
if dnsTTL <= 0 {
|
if dnsTTL <= 0 {
|
||||||
dnsTTL = 10 * time.Minute
|
dnsTTL = 10 * time.Minute
|
||||||
}
|
}
|
||||||
@@ -618,9 +673,7 @@ func loadEnvVarsFromFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCommonEnvVars() {
|
func serverHandleEnvVars() {
|
||||||
loadEnvVarsFromFiles()
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
globalBrowserEnabled, err = config.ParseBool(env.Get(config.EnvBrowser, config.EnableOn))
|
globalBrowserEnabled, err = config.ParseBool(env.Get(config.EnvBrowser, config.EnableOn))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -748,6 +801,10 @@ func handleCommonEnvVars() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadRootCredentials() {
|
||||||
// At this point, either both environment variables
|
// At this point, either both environment variables
|
||||||
// are defined or both are not defined.
|
// are defined or both are not defined.
|
||||||
// Check both cases and authenticate them if correctly defined
|
// Check both cases and authenticate them if correctly defined
|
||||||
@@ -762,6 +819,9 @@ func handleCommonEnvVars() {
|
|||||||
user = env.Get(config.EnvAccessKey, "")
|
user = env.Get(config.EnvAccessKey, "")
|
||||||
password = env.Get(config.EnvSecretKey, "")
|
password = env.Get(config.EnvSecretKey, "")
|
||||||
hasCredentials = true
|
hasCredentials = true
|
||||||
|
} else if globalServerCtxt.RootUser != "" && globalServerCtxt.RootPwd != "" {
|
||||||
|
user, password = globalServerCtxt.RootUser, globalServerCtxt.RootPwd
|
||||||
|
hasCredentials = true
|
||||||
}
|
}
|
||||||
if hasCredentials {
|
if hasCredentials {
|
||||||
cred, err := auth.CreateCredentials(user, password)
|
cred, err := auth.CreateCredentials(user, password)
|
||||||
@@ -781,8 +841,6 @@ func handleCommonEnvVars() {
|
|||||||
} else {
|
} else {
|
||||||
globalActiveCred = auth.DefaultCredentials
|
globalActiveCred = auth.DefaultCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize KMS global variable after valiadating and loading the configuration.
|
// Initialize KMS global variable after valiadating and loading the configuration.
|
||||||
|
|||||||
+74
-23
@@ -24,13 +24,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/config/browser"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/api"
|
"github.com/minio/minio/internal/config/api"
|
||||||
|
"github.com/minio/minio/internal/config/batch"
|
||||||
"github.com/minio/minio/internal/config/cache"
|
"github.com/minio/minio/internal/config/cache"
|
||||||
"github.com/minio/minio/internal/config/callhome"
|
"github.com/minio/minio/internal/config/callhome"
|
||||||
"github.com/minio/minio/internal/config/compress"
|
"github.com/minio/minio/internal/config/compress"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
|
"github.com/minio/minio/internal/config/drive"
|
||||||
"github.com/minio/minio/internal/config/etcd"
|
"github.com/minio/minio/internal/config/etcd"
|
||||||
"github.com/minio/minio/internal/config/heal"
|
"github.com/minio/minio/internal/config/heal"
|
||||||
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
@@ -46,7 +50,6 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/subnet"
|
"github.com/minio/minio/internal/config/subnet"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/kms"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
@@ -54,7 +57,6 @@ import (
|
|||||||
func initHelp() {
|
func initHelp() {
|
||||||
kvs := map[string]config.KVS{
|
kvs := map[string]config.KVS{
|
||||||
config.EtcdSubSys: etcd.DefaultKVS,
|
config.EtcdSubSys: etcd.DefaultKVS,
|
||||||
config.CacheSubSys: cache.DefaultKVS,
|
|
||||||
config.CompressionSubSys: compress.DefaultKVS,
|
config.CompressionSubSys: compress.DefaultKVS,
|
||||||
config.IdentityLDAPSubSys: xldap.DefaultKVS,
|
config.IdentityLDAPSubSys: xldap.DefaultKVS,
|
||||||
config.IdentityOpenIDSubSys: openid.DefaultKVS,
|
config.IdentityOpenIDSubSys: openid.DefaultKVS,
|
||||||
@@ -71,6 +73,10 @@ func initHelp() {
|
|||||||
config.ScannerSubSys: scanner.DefaultKVS,
|
config.ScannerSubSys: scanner.DefaultKVS,
|
||||||
config.SubnetSubSys: subnet.DefaultKVS,
|
config.SubnetSubSys: subnet.DefaultKVS,
|
||||||
config.CallhomeSubSys: callhome.DefaultKVS,
|
config.CallhomeSubSys: callhome.DefaultKVS,
|
||||||
|
config.DriveSubSys: drive.DefaultKVS,
|
||||||
|
config.CacheSubSys: cache.DefaultKVS,
|
||||||
|
config.BatchSubSys: batch.DefaultKVS,
|
||||||
|
config.BrowserSubSys: browser.DefaultKVS,
|
||||||
}
|
}
|
||||||
for k, v := range notify.DefaultNotificationKVS {
|
for k, v := range notify.DefaultNotificationKVS {
|
||||||
kvs[k] = v
|
kvs[k] = v
|
||||||
@@ -98,6 +104,10 @@ func initHelp() {
|
|||||||
Description: "enable callhome to MinIO SUBNET",
|
Description: "enable callhome to MinIO SUBNET",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.DriveSubSys,
|
||||||
|
Description: "enable drive specific settings",
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.SiteSubSys,
|
Key: config.SiteSubSys,
|
||||||
Description: "label the server and its location",
|
Description: "label the server and its location",
|
||||||
@@ -110,6 +120,10 @@ func initHelp() {
|
|||||||
Key: config.ScannerSubSys,
|
Key: config.ScannerSubSys,
|
||||||
Description: "manage namespace scanning for usage calculation, lifecycle, healing and more",
|
Description: "manage namespace scanning for usage calculation, lifecycle, healing and more",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.BatchSubSys,
|
||||||
|
Description: "manage batch job workers and wait times",
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.CompressionSubSys,
|
Key: config.CompressionSubSys,
|
||||||
Description: "enable server side compression of objects",
|
Description: "enable server side compression of objects",
|
||||||
@@ -211,7 +225,14 @@ func initHelp() {
|
|||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.CacheSubSys,
|
Key: config.CacheSubSys,
|
||||||
Description: "[DEPRECATED] add caching storage tier",
|
Type: "string",
|
||||||
|
Description: "enable cache plugin on MinIO for GET/HEAD requests",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.BrowserSubSys,
|
||||||
|
Description: "manage Browser HTTP specific features, such as Security headers, etc.",
|
||||||
|
Optional: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,9 +253,9 @@ func initHelp() {
|
|||||||
config.APISubSys: api.Help,
|
config.APISubSys: api.Help,
|
||||||
config.StorageClassSubSys: storageclass.Help,
|
config.StorageClassSubSys: storageclass.Help,
|
||||||
config.EtcdSubSys: etcd.Help,
|
config.EtcdSubSys: etcd.Help,
|
||||||
config.CacheSubSys: cache.Help,
|
|
||||||
config.CompressionSubSys: compress.Help,
|
config.CompressionSubSys: compress.Help,
|
||||||
config.HealSubSys: heal.Help,
|
config.HealSubSys: heal.Help,
|
||||||
|
config.BatchSubSys: batch.Help,
|
||||||
config.ScannerSubSys: scanner.Help,
|
config.ScannerSubSys: scanner.Help,
|
||||||
config.IdentityOpenIDSubSys: openid.Help,
|
config.IdentityOpenIDSubSys: openid.Help,
|
||||||
config.IdentityLDAPSubSys: xldap.Help,
|
config.IdentityLDAPSubSys: xldap.Help,
|
||||||
@@ -258,6 +279,9 @@ func initHelp() {
|
|||||||
config.LambdaWebhookSubSys: lambda.HelpWebhook,
|
config.LambdaWebhookSubSys: lambda.HelpWebhook,
|
||||||
config.SubnetSubSys: subnet.HelpSubnet,
|
config.SubnetSubSys: subnet.HelpSubnet,
|
||||||
config.CallhomeSubSys: callhome.HelpCallhome,
|
config.CallhomeSubSys: callhome.HelpCallhome,
|
||||||
|
config.DriveSubSys: drive.HelpDrive,
|
||||||
|
config.CacheSubSys: cache.Help,
|
||||||
|
config.BrowserSubSys: browser.Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
config.RegisterHelpSubSys(helpMap)
|
config.RegisterHelpSubSys(helpMap)
|
||||||
@@ -293,6 +317,10 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
|
|||||||
if _, err := api.LookupConfig(s[config.APISubSys][config.Default]); err != nil {
|
if _, err := api.LookupConfig(s[config.APISubSys][config.Default]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case config.BatchSubSys:
|
||||||
|
if _, err := batch.LookupConfig(s[config.BatchSubSys][config.Default]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case config.StorageClassSubSys:
|
case config.StorageClassSubSys:
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
@@ -302,10 +330,6 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case config.CacheSubSys:
|
|
||||||
if _, err := cache.LookupConfig(s[config.CacheSubSys][config.Default]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case config.CompressionSubSys:
|
case config.CompressionSubSys:
|
||||||
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
|
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -369,6 +393,14 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
|
|||||||
if cfg.Enabled() && !globalSubnetConfig.Registered() {
|
if cfg.Enabled() && !globalSubnetConfig.Registered() {
|
||||||
return errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
|
return errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
|
||||||
}
|
}
|
||||||
|
case config.DriveSubSys:
|
||||||
|
if _, err := drive.LookupConfig(s[config.DriveSubSys][config.Default]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case config.CacheSubSys:
|
||||||
|
if _, err := cache.LookupConfig(s[config.CacheSubSys][config.Default], globalRemoteTargetTransport); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case config.PolicyOPASubSys:
|
case config.PolicyOPASubSys:
|
||||||
// In case legacy OPA config is being set, we treat it as if the
|
// In case legacy OPA config is being set, we treat it as if the
|
||||||
// AuthZPlugin is being set.
|
// AuthZPlugin is being set.
|
||||||
@@ -384,6 +416,10 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case config.BrowserSubSys:
|
||||||
|
if _, err := browser.LookupConfig(s[config.BrowserSubSys][config.Default]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if config.LoggerSubSystems.Contains(subSys) {
|
if config.LoggerSubSystems.Contains(subSys) {
|
||||||
if err := logger.ValidateSubSysConfig(ctx, s, subSys); err != nil {
|
if err := logger.ValidateSubSysConfig(ctx, s, subSys); err != nil {
|
||||||
@@ -493,20 +529,6 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
|||||||
logger.LogIf(ctx, fmt.Errorf("Invalid site configuration: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Invalid site configuration: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
globalCacheConfig, err = cache.LookupConfig(s[config.CacheSubSys][config.Default])
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup cache: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if globalCacheConfig.Enabled {
|
|
||||||
if cacheEncKey := env.Get(cache.EnvCacheEncryptionKey, ""); cacheEncKey != "" {
|
|
||||||
globalCacheKMS, err = kms.Parse(cacheEncKey)
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup encryption cache: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled
|
globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled
|
||||||
if globalAutoEncryption && GlobalKMS == nil {
|
if globalAutoEncryption && GlobalKMS == nil {
|
||||||
logger.Fatal(errors.New("no KMS configured"), "MINIO_KMS_AUTO_ENCRYPTION requires a valid KMS configuration")
|
logger.Fatal(errors.New("no KMS configured"), "MINIO_KMS_AUTO_ENCRYPTION requires a valid KMS configuration")
|
||||||
@@ -566,12 +588,19 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
return fmt.Errorf("Unable to apply heal config: %w", err)
|
return fmt.Errorf("Unable to apply heal config: %w", err)
|
||||||
}
|
}
|
||||||
globalHealConfig.Update(healCfg)
|
globalHealConfig.Update(healCfg)
|
||||||
|
case config.BatchSubSys:
|
||||||
|
batchCfg, err := batch.LookupConfig(s[config.BatchSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to apply batch config: %w", err)
|
||||||
|
}
|
||||||
|
globalBatchConfig.Update(batchCfg)
|
||||||
case config.ScannerSubSys:
|
case config.ScannerSubSys:
|
||||||
scannerCfg, err := scanner.LookupConfig(s[config.ScannerSubSys][config.Default])
|
scannerCfg, err := scanner.LookupConfig(s[config.ScannerSubSys][config.Default])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to apply scanner config: %w", err)
|
return fmt.Errorf("Unable to apply scanner config: %w", err)
|
||||||
}
|
}
|
||||||
// update dynamic scanner values.
|
// update dynamic scanner values.
|
||||||
|
scannerIdleMode.Store(scannerCfg.IdleMode)
|
||||||
scannerCycle.Store(scannerCfg.Cycle)
|
scannerCycle.Store(scannerCfg.Cycle)
|
||||||
logger.LogIf(ctx, scannerSleeper.Update(scannerCfg.Delay, scannerCfg.MaxWait))
|
logger.LogIf(ctx, scannerSleeper.Update(scannerCfg.Delay, scannerCfg.MaxWait))
|
||||||
case config.LoggerWebhookSubSys:
|
case config.LoggerWebhookSubSys:
|
||||||
@@ -644,7 +673,7 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to parse subnet configuration: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to parse subnet configuration: %w", err))
|
||||||
} else {
|
} else {
|
||||||
globalSubnetConfig.Update(subnetConfig)
|
globalSubnetConfig.Update(subnetConfig, globalIsCICD)
|
||||||
globalSubnetConfig.ApplyEnv() // update environment settings for Console UI
|
globalSubnetConfig.ApplyEnv() // update environment settings for Console UI
|
||||||
}
|
}
|
||||||
case config.CallhomeSubSys:
|
case config.CallhomeSubSys:
|
||||||
@@ -658,6 +687,28 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
initCallhome(ctx, objAPI)
|
initCallhome(ctx, objAPI)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case config.DriveSubSys:
|
||||||
|
if driveConfig, err := drive.LookupConfig(s[config.DriveSubSys][config.Default]); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to load drive config: %w", err))
|
||||||
|
} else {
|
||||||
|
err := globalDriveConfig.Update(driveConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to update drive config: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case config.CacheSubSys:
|
||||||
|
cacheCfg, err := cache.LookupConfig(s[config.CacheSubSys][config.Default], globalRemoteTargetTransport)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to load cache config: %w", err))
|
||||||
|
} else {
|
||||||
|
globalCacheConfig.Update(cacheCfg)
|
||||||
|
}
|
||||||
|
case config.BrowserSubSys:
|
||||||
|
browserCfg, err := browser.LookupConfig(s[config.BrowserSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to apply browser config: %w", err)
|
||||||
|
}
|
||||||
|
globalBrowserConfig.Update(browserCfg)
|
||||||
}
|
}
|
||||||
globalServerConfigMu.Lock()
|
globalServerConfigMu.Lock()
|
||||||
defer globalServerConfigMu.Unlock()
|
defer globalServerConfigMu.Unlock()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,336 +0,0 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of MinIO Object Storage stack
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test if config v1 is purged
|
|
||||||
func TestServerConfigMigrateV1(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(fsDir)
|
|
||||||
err = newTestConfig(globalMinioDefaultRegion, objLayer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Init Test config failed")
|
|
||||||
}
|
|
||||||
rootPath := t.TempDir()
|
|
||||||
globalConfigDir = &ConfigDir{path: rootPath}
|
|
||||||
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
globalObjectAPI = objLayer
|
|
||||||
globalObjLayerMutex.Unlock()
|
|
||||||
|
|
||||||
// Create a V1 config json file and store it
|
|
||||||
configJSON := "{ \"version\":\"1\", \"accessKeyId\":\"abcde\", \"secretAccessKey\":\"abcdefgh\"}"
|
|
||||||
configPath := rootPath + "/fsUsers.json"
|
|
||||||
if err := os.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire a migrateConfig()
|
|
||||||
if err := migrateConfig(); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if config v1 is removed from filesystem
|
|
||||||
if _, err := os.Stat(configPath); err == nil || !osIsNotExist(err) {
|
|
||||||
t.Fatal("Config V1 file is not purged")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize server config and check again if everything is fine
|
|
||||||
if err := loadConfig(objLayer, nil); err != nil {
|
|
||||||
t.Fatalf("Unable to initialize from updated config file %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if all migrate code returns nil when config file does not
|
|
||||||
// exist
|
|
||||||
func TestServerConfigMigrateInexistentConfig(t *testing.T) {
|
|
||||||
globalConfigDir = &ConfigDir{path: t.TempDir()}
|
|
||||||
|
|
||||||
if err := migrateV2ToV3(); err != nil {
|
|
||||||
t.Fatal("migrate v2 to v3 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV3ToV4(); err != nil {
|
|
||||||
t.Fatal("migrate v3 to v4 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV4ToV5(); err != nil {
|
|
||||||
t.Fatal("migrate v4 to v5 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV5ToV6(); err != nil {
|
|
||||||
t.Fatal("migrate v5 to v6 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV6ToV7(); err != nil {
|
|
||||||
t.Fatal("migrate v6 to v7 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV7ToV8(); err != nil {
|
|
||||||
t.Fatal("migrate v7 to v8 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV8ToV9(); err != nil {
|
|
||||||
t.Fatal("migrate v8 to v9 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV9ToV10(); err != nil {
|
|
||||||
t.Fatal("migrate v9 to v10 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV10ToV11(); err != nil {
|
|
||||||
t.Fatal("migrate v10 to v11 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV11ToV12(); err != nil {
|
|
||||||
t.Fatal("migrate v11 to v12 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV12ToV13(); err != nil {
|
|
||||||
t.Fatal("migrate v12 to v13 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV13ToV14(); err != nil {
|
|
||||||
t.Fatal("migrate v13 to v14 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV14ToV15(); err != nil {
|
|
||||||
t.Fatal("migrate v14 to v15 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV15ToV16(); err != nil {
|
|
||||||
t.Fatal("migrate v15 to v16 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV16ToV17(); err != nil {
|
|
||||||
t.Fatal("migrate v16 to v17 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV17ToV18(); err != nil {
|
|
||||||
t.Fatal("migrate v17 to v18 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV18ToV19(); err != nil {
|
|
||||||
t.Fatal("migrate v18 to v19 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV19ToV20(); err != nil {
|
|
||||||
t.Fatal("migrate v19 to v20 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV20ToV21(); err != nil {
|
|
||||||
t.Fatal("migrate v20 to v21 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV21ToV22(); err != nil {
|
|
||||||
t.Fatal("migrate v21 to v22 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV22ToV23(); err != nil {
|
|
||||||
t.Fatal("migrate v22 to v23 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV23ToV24(); err != nil {
|
|
||||||
t.Fatal("migrate v23 to v24 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV24ToV25(); err != nil {
|
|
||||||
t.Fatal("migrate v24 to v25 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV25ToV26(); err != nil {
|
|
||||||
t.Fatal("migrate v25 to v26 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV26ToV27(); err != nil {
|
|
||||||
t.Fatal("migrate v26 to v27 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
if err := migrateV27ToV28(); err != nil {
|
|
||||||
t.Fatal("migrate v27 to v28 should succeed when no config file is found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if a config migration from v2 to v33 is successfully done
|
|
||||||
func TestServerConfigMigrateV2toV33(t *testing.T) {
|
|
||||||
rootPath := t.TempDir()
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
globalConfigDir = &ConfigDir{path: rootPath}
|
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(fsDir)
|
|
||||||
|
|
||||||
configPath := rootPath + SlashSeparator + minioConfigFile
|
|
||||||
|
|
||||||
// Create a corrupted config file
|
|
||||||
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\","), 0o644); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
// Fire a migrateConfig()
|
|
||||||
if err := migrateConfig(); err == nil {
|
|
||||||
t.Fatal("migration should fail with corrupted config file")
|
|
||||||
}
|
|
||||||
|
|
||||||
accessKey := "accessfoo"
|
|
||||||
secretKey := "secretfoo"
|
|
||||||
|
|
||||||
// Create a V2 config json file and store it
|
|
||||||
configJSON := "{ \"version\":\"2\", \"credentials\": {\"accessKeyId\":\"" + accessKey + "\", \"secretAccessKey\":\"" + secretKey + "\", \"region\":\"us-east-1\"}, \"mongoLogger\":{\"addr\":\"127.0.0.1:3543\", \"db\":\"foodb\", \"collection\":\"foo\"}, \"syslogLogger\":{\"network\":\"127.0.0.1:543\", \"addr\":\"addr\"}, \"fileLogger\":{\"filename\":\"log.out\"}}"
|
|
||||||
if err := os.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire a migrateConfig()
|
|
||||||
if err := migrateConfig(); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := migrateConfigToMinioSys(objLayer); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srvCfg, err := readConfigWithoutMigrate(context.Background(), objLayer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = saveServerConfig(GlobalContext, objLayer, srvCfg); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize server config and check again if everything is fine
|
|
||||||
if err := loadConfig(objLayer, nil); err != nil {
|
|
||||||
t.Fatalf("Unable to initialize from updated config file %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if all migrate code returns error with corrupted config files
|
|
||||||
func TestServerConfigMigrateFaultyConfig(t *testing.T) {
|
|
||||||
rootPath := t.TempDir()
|
|
||||||
|
|
||||||
globalConfigDir = &ConfigDir{path: rootPath}
|
|
||||||
configPath := rootPath + SlashSeparator + minioConfigFile
|
|
||||||
|
|
||||||
// Create a corrupted config file
|
|
||||||
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\", \"test\":"), 0o644); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test different migrate versions and be sure they are returning an error
|
|
||||||
if err := migrateV2ToV3(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV2ToV3() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV3ToV4(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV3ToV4() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV4ToV5(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV4ToV5() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV5ToV6(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV5ToV6() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV6ToV7(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV6ToV7() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV7ToV8(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV7ToV8() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV8ToV9(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV8ToV9() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV9ToV10(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV9ToV10() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV10ToV11(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV10ToV11() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV11ToV12(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV11ToV12() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV12ToV13(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV12ToV13() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV13ToV14(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV13ToV14() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV14ToV15(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV14ToV15() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV15ToV16(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV15ToV16() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV16ToV17(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV16ToV17() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV17ToV18(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV17ToV18() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV18ToV19(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV18ToV19() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV19ToV20(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV19ToV20() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV20ToV21(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV20ToV21() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV21ToV22(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV21ToV22() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV22ToV23(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV22ToV23() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV23ToV24(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV23ToV24() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV24ToV25(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV24ToV25() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV25ToV26(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV25ToV26() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV26ToV27(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV26ToV27() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
if err := migrateV27ToV28(); err == nil {
|
|
||||||
t.Fatal("migrateConfigV27ToV28() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if all migrate code returns error with corrupted config files
|
|
||||||
func TestServerConfigMigrateCorruptedConfig(t *testing.T) {
|
|
||||||
rootPath := t.TempDir()
|
|
||||||
|
|
||||||
globalConfigDir = &ConfigDir{path: rootPath}
|
|
||||||
configPath := rootPath + SlashSeparator + minioConfigFile
|
|
||||||
|
|
||||||
for i := 3; i <= 17; i++ {
|
|
||||||
// Create a corrupted config file
|
|
||||||
if err := os.WriteFile(configPath, []byte(fmt.Sprintf("{ \"version\":\"%d\", \"credential\": { \"accessKey\": 1 } }", i)),
|
|
||||||
0o644); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test different migrate versions and be sure they are returning an error
|
|
||||||
if err := migrateConfig(); err == nil {
|
|
||||||
t.Fatal("migrateConfig() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a corrupted config file for version '2'.
|
|
||||||
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\", \"credentials\": { \"accessKeyId\": 1 } }"), 0o644); err != nil {
|
|
||||||
t.Fatal("Unexpected error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test different migrate versions and be sure they are returning an error
|
|
||||||
if err := migrateConfig(); err == nil {
|
|
||||||
t.Fatal("migrateConfig() should fail with a corrupted json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,418 +18,18 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/cache"
|
|
||||||
"github.com/minio/minio/internal/config/compress"
|
"github.com/minio/minio/internal/config/compress"
|
||||||
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/config/notify"
|
"github.com/minio/minio/internal/config/notify"
|
||||||
"github.com/minio/minio/internal/config/policy/opa"
|
"github.com/minio/minio/internal/config/policy/opa"
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
"github.com/minio/minio/internal/event/target"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/quick"
|
"github.com/minio/pkg/v2/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configV1 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
AccessKey string `json:"accessKeyId"`
|
|
||||||
SecretKey string `json:"secretAccessKey"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type configV2 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Credentials struct {
|
|
||||||
AccessKey string `json:"accessKeyId"`
|
|
||||||
SecretKey string `json:"secretAccessKey"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
} `json:"credentials"`
|
|
||||||
MongoLogger struct {
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
DB string `json:"db"`
|
|
||||||
Collection string `json:"collection"`
|
|
||||||
} `json:"mongoLogger"`
|
|
||||||
SyslogLogger struct {
|
|
||||||
Network string `json:"network"`
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
} `json:"syslogLogger"`
|
|
||||||
FileLogger struct {
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
} `json:"fileLogger"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// backendV3 type.
|
|
||||||
type backendV3 struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Disk string `json:"disk,omitempty"`
|
|
||||||
Disks []string `json:"disks,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// syslogLogger v3
|
|
||||||
type syslogLoggerV3 struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Addr string `json:"address"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// loggerV3 type.
|
|
||||||
type loggerV3 struct {
|
|
||||||
Console struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
}
|
|
||||||
File struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Filename string `json:"fileName"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
}
|
|
||||||
Syslog struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Addr string `json:"address"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
} `json:"syslog"`
|
|
||||||
// Add new loggers here.
|
|
||||||
}
|
|
||||||
|
|
||||||
// configV3 server configuration version '3'.
|
|
||||||
type configV3 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// Backend configuration.
|
|
||||||
Backend backendV3 `json:"backend"`
|
|
||||||
|
|
||||||
// http Server configuration.
|
|
||||||
Addr string `json:"address"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV3 `json:"logger"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// logger type representing version '4' logger config.
|
|
||||||
type loggerV4 struct {
|
|
||||||
Console struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
} `json:"console"`
|
|
||||||
File struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Filename string `json:"fileName"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
} `json:"file"`
|
|
||||||
Syslog struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Addr string `json:"address"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
} `json:"syslog"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// configV4 server configuration version '4'.
|
|
||||||
type configV4 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV4 `json:"logger"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// logger type representing version '5' logger config.
|
|
||||||
type loggerV5 struct {
|
|
||||||
Console struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
} `json:"console"`
|
|
||||||
File struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Filename string `json:"fileName"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
} `json:"file"`
|
|
||||||
Syslog struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Addr string `json:"address"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
} `json:"syslog"`
|
|
||||||
AMQP struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Exchange string `json:"exchange"`
|
|
||||||
RoutingKey string `json:"routingKey"`
|
|
||||||
ExchangeType string `json:"exchangeType"`
|
|
||||||
Mandatory bool `json:"mandatory"`
|
|
||||||
Immediate bool `json:"immediate"`
|
|
||||||
Durable bool `json:"durable"`
|
|
||||||
Internal bool `json:"internal"`
|
|
||||||
NoWait bool `json:"noWait"`
|
|
||||||
AutoDeleted bool `json:"autoDeleted"`
|
|
||||||
} `json:"amqp"`
|
|
||||||
ElasticSearch struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Index string `json:"index"`
|
|
||||||
} `json:"elasticsearch"`
|
|
||||||
Redis struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
Addr string `json:"address"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
} `json:"redis"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// configV5 server configuration version '5'.
|
|
||||||
type configV5 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV5 `json:"logger"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// consoleLogger - default logger if not other logging is enabled.
|
|
||||||
type consoleLoggerV1 struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileLoggerV1 struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Filename string `json:"fileName"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type loggerV6 struct {
|
|
||||||
Console consoleLoggerV1 `json:"console"`
|
|
||||||
File fileLoggerV1 `json:"file"`
|
|
||||||
Syslog syslogLoggerV3 `json:"syslog"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// configV6 server configuration version '6'.
|
|
||||||
type configV6 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV6 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV1 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notifier represents collection of supported notification queues in version
|
|
||||||
// 1 without NATS streaming.
|
|
||||||
type notifierV1 struct {
|
|
||||||
AMQP map[string]target.AMQPArgs `json:"amqp"`
|
|
||||||
NATS map[string]natsNotifyV1 `json:"nats"`
|
|
||||||
ElasticSearch map[string]target.ElasticsearchArgs `json:"elasticsearch"`
|
|
||||||
Redis map[string]target.RedisArgs `json:"redis"`
|
|
||||||
PostgreSQL map[string]target.PostgreSQLArgs `json:"postgresql"`
|
|
||||||
Kafka map[string]target.KafkaArgs `json:"kafka"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notifier represents collection of supported notification queues in version 2
|
|
||||||
// with NATS streaming but without webhook.
|
|
||||||
type notifierV2 struct {
|
|
||||||
AMQP map[string]target.AMQPArgs `json:"amqp"`
|
|
||||||
NATS map[string]target.NATSArgs `json:"nats"`
|
|
||||||
ElasticSearch map[string]target.ElasticsearchArgs `json:"elasticsearch"`
|
|
||||||
Redis map[string]target.RedisArgs `json:"redis"`
|
|
||||||
PostgreSQL map[string]target.PostgreSQLArgs `json:"postgresql"`
|
|
||||||
Kafka map[string]target.KafkaArgs `json:"kafka"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// configV7 server configuration version '7'.
|
|
||||||
type serverConfigV7 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV6 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV1 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV8 server configuration version '8'. Adds NATS notify.Config
|
|
||||||
// configuration.
|
|
||||||
type serverConfigV8 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV6 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV1 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV9 server configuration version '9'. Adds PostgreSQL
|
|
||||||
// notify.Config configuration.
|
|
||||||
type serverConfigV9 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV6 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV1 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type loggerV7 struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Console consoleLoggerV1 `json:"console"`
|
|
||||||
File fileLoggerV1 `json:"file"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV10 server configuration version '10' which is like
|
|
||||||
// version '9' except it drops support of syslog config, and makes the
|
|
||||||
// RWMutex global (so it does not exist in this struct).
|
|
||||||
type serverConfigV10 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV7 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV1 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// natsNotifyV1 - structure was valid until config V 11
|
|
||||||
type natsNotifyV1 struct {
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
Secure bool `json:"secure"`
|
|
||||||
PingInterval int64 `json:"pingInterval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV11 server configuration version '11' which is like
|
|
||||||
// version '10' except it adds support for Kafka notifications.
|
|
||||||
type serverConfigV11 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV7 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV1 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV12 server configuration version '12' which is like
|
|
||||||
// version '11' except it adds support for NATS streaming notifications.
|
|
||||||
type serverConfigV12 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger loggerV7 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV2 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type notifierV3 struct {
|
|
||||||
AMQP map[string]target.AMQPArgs `json:"amqp"`
|
|
||||||
Elasticsearch map[string]target.ElasticsearchArgs `json:"elasticsearch"`
|
|
||||||
Kafka map[string]target.KafkaArgs `json:"kafka"`
|
|
||||||
MQTT map[string]target.MQTTArgs `json:"mqtt"`
|
|
||||||
MySQL map[string]target.MySQLArgs `json:"mysql"`
|
|
||||||
NATS map[string]target.NATSArgs `json:"nats"`
|
|
||||||
PostgreSQL map[string]target.PostgreSQLArgs `json:"postgresql"`
|
|
||||||
Redis map[string]target.RedisArgs `json:"redis"`
|
|
||||||
Webhook map[string]target.WebhookArgs `json:"webhook"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV13 server configuration version '13' which is like
|
|
||||||
// version '12' except it adds support for webhook notification.
|
|
||||||
type serverConfigV13 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggerV7 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV14 server configuration version '14' which is like
|
|
||||||
// version '13' except it adds support of browser param.
|
|
||||||
type serverConfigV14 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggerV7 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV15 server configuration version '15' which is like
|
|
||||||
// version '14' except it adds mysql support
|
|
||||||
type serverConfigV15 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggerV7 `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileLogger is introduced to workaround the dependency about logrus
|
// FileLogger is introduced to workaround the dependency about logrus
|
||||||
type FileLogger struct {
|
type FileLogger struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
@@ -441,282 +41,6 @@ type ConsoleLogger struct {
|
|||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loggers struct is defined with FileLogger and ConsoleLogger
|
|
||||||
// although they are removed from logging logic. They are
|
|
||||||
// kept here just to workaround the dependency migration
|
|
||||||
// code/logic has on them.
|
|
||||||
type loggers struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Console ConsoleLogger `json:"console"`
|
|
||||||
File FileLogger `json:"file"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV16 server configuration version '16' which is like
|
|
||||||
// version '15' except it makes a change to logging configuration.
|
|
||||||
type serverConfigV16 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggers `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV17 server configuration version '17' which is like
|
|
||||||
// version '16' except it adds support for "format" parameter in
|
|
||||||
// database event notification targets: PostgreSQL, MySQL, Redis and
|
|
||||||
// Elasticsearch.
|
|
||||||
type serverConfigV17 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggers `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV18 server configuration version '18' which is like
|
|
||||||
// version '17' except it adds support for "deliveryMode" parameter in
|
|
||||||
// the AMQP notification target.
|
|
||||||
type serverConfigV18 struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggers `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV19 server configuration version '19' which is like
|
|
||||||
// version '18' except it adds support for MQTT notifications.
|
|
||||||
type serverConfigV19 struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggers `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV20 server configuration version '20' which is like
|
|
||||||
// version '19' except it adds support for VirtualHostDomain
|
|
||||||
type serverConfigV20 struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Additional error logging configuration.
|
|
||||||
Logger *loggers `json:"logger"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV21 is just like version '20' without logger field
|
|
||||||
type serverConfigV21 struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify *notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV22 is just like version '21' with added support
|
|
||||||
// for StorageClass.
|
|
||||||
type serverConfigV22 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Storage class configuration
|
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV23 is just like version '22' with addition of cache field.
|
|
||||||
type serverConfigV23 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Storage class configuration
|
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
|
||||||
|
|
||||||
// Cache configuration
|
|
||||||
Cache cache.Config `json:"cache"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV24 is just like version '23', we had to revert
|
|
||||||
// the changes which were made in 6fb06045028b7a57c37c60a612c8e50735279ab4
|
|
||||||
type serverConfigV24 struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Storage class configuration
|
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
|
||||||
|
|
||||||
// Cache configuration
|
|
||||||
Cache cache.Config `json:"cache"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV25 is just like version '24', stores additionally
|
|
||||||
// worm variable.
|
|
||||||
type serverConfigV25 struct {
|
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
|
||||||
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Worm config.BoolFlag `json:"worm"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Storage class configuration
|
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
|
||||||
|
|
||||||
// Cache configuration
|
|
||||||
Cache cache.Config `json:"cache"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV26 is just like version '25', stores additionally
|
|
||||||
// cache max use value in 'cache.Config'.
|
|
||||||
type serverConfigV26 struct {
|
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
|
||||||
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Worm config.BoolFlag `json:"worm"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Storage class configuration
|
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
|
||||||
|
|
||||||
// Cache configuration
|
|
||||||
Cache cache.Config `json:"cache"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV3 `json:"notify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV27 is just like version '26', stores additionally
|
|
||||||
// the logger field
|
|
||||||
type serverConfigV27 struct {
|
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
|
||||||
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Browser config.BoolFlag `json:"browser"`
|
|
||||||
Worm config.BoolFlag `json:"worm"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
|
|
||||||
// Storage class configuration
|
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
|
||||||
|
|
||||||
// Cache configuration
|
|
||||||
Cache cache.Config `json:"cache"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV3 `json:"notify"`
|
|
||||||
|
|
||||||
// Logger configuration
|
|
||||||
Logger logger.Config `json:"logger"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV28 is just like version '27', additionally
|
|
||||||
// storing KMS config
|
|
||||||
type serverConfigV28 struct {
|
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
|
||||||
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// S3 API configuration.
|
|
||||||
Credential auth.Credentials `json:"credential"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
Worm config.BoolFlag `json:"worm"`
|
|
||||||
|
|
||||||
// Storage class configuration
|
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
|
||||||
|
|
||||||
// Cache configuration
|
|
||||||
Cache cache.Config `json:"cache"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
|
||||||
Notify notifierV3 `json:"notify"`
|
|
||||||
|
|
||||||
// Logger configuration
|
|
||||||
Logger logger.Config `json:"logger"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit in all notification targets.
|
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit in all notification targets.
|
||||||
type serverConfigV33 struct {
|
type serverConfigV33 struct {
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
quick.Config `json:"-"` // ignore interfaces
|
||||||
@@ -731,9 +55,6 @@ type serverConfigV33 struct {
|
|||||||
// Storage class configuration
|
// Storage class configuration
|
||||||
StorageClass storageclass.Config `json:"storageclass"`
|
StorageClass storageclass.Config `json:"storageclass"`
|
||||||
|
|
||||||
// Cache configuration
|
|
||||||
Cache cache.Config `json:"cache"`
|
|
||||||
|
|
||||||
// Notification queue configuration.
|
// Notification queue configuration.
|
||||||
Notify notify.Config `json:"notify"`
|
Notify notify.Config `json:"notify"`
|
||||||
|
|
||||||
|
|||||||
+74
-40
@@ -65,8 +65,9 @@ var (
|
|||||||
globalHealConfig heal.Config
|
globalHealConfig heal.Config
|
||||||
|
|
||||||
// Sleeper values are updated when config is loaded.
|
// Sleeper values are updated when config is loaded.
|
||||||
scannerSleeper = newDynamicSleeper(2, time.Second, true) // Keep defaults same as config defaults
|
scannerSleeper = newDynamicSleeper(2, time.Second, true) // Keep defaults same as config defaults
|
||||||
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
|
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
|
||||||
|
scannerIdleMode = uatomic.NewInt32(0) // default is throttled when idle
|
||||||
)
|
)
|
||||||
|
|
||||||
// initDataScanner will start the scanner in the background.
|
// initDataScanner will start the scanner in the background.
|
||||||
@@ -121,13 +122,13 @@ func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundH
|
|||||||
buf, err := readConfig(ctx, objAPI, backgroundHealInfoPath)
|
buf, err := readConfig(ctx, objAPI, backgroundHealInfoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errConfigNotFound) {
|
if !errors.Is(err, errConfigNotFound) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, backgroundHealInfoPath)
|
||||||
}
|
}
|
||||||
return backgroundHealInfo{}
|
return backgroundHealInfo{}
|
||||||
}
|
}
|
||||||
var info backgroundHealInfo
|
var info backgroundHealInfo
|
||||||
if err = json.Unmarshal(buf, &info); err != nil {
|
if err = json.Unmarshal(buf, &info); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, backgroundHealInfoPath)
|
||||||
}
|
}
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
@@ -203,7 +204,7 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
results := make(chan DataUsageInfo, 1)
|
results := make(chan DataUsageInfo, 1)
|
||||||
go storeDataUsageInBackend(ctx, objAPI, results)
|
go storeDataUsageInBackend(ctx, objAPI, results)
|
||||||
err := objAPI.NSScanner(ctx, results, uint32(cycleInfo.current), scanMode)
|
err := objAPI.NSScanner(ctx, results, uint32(cycleInfo.current), scanMode)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, "ns-scanner")
|
||||||
res := map[string]string{"cycle": strconv.FormatUint(cycleInfo.current, 10)}
|
res := map[string]string{"cycle": strconv.FormatUint(cycleInfo.current, 10)}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res["error"] = err.Error()
|
res["error"] = err.Error()
|
||||||
@@ -223,7 +224,7 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
binary.LittleEndian.PutUint64(tmp, cycleInfo.next)
|
binary.LittleEndian.PutUint64(tmp, cycleInfo.next)
|
||||||
tmp, _ = cycleInfo.MarshalMsg(tmp)
|
tmp, _ = cycleInfo.MarshalMsg(tmp)
|
||||||
err = saveConfig(ctx, objAPI, dataUsageBloomNamePath, tmp)
|
err = saveConfig(ctx, objAPI, dataUsageBloomNamePath, tmp)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, dataUsageBloomNamePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +247,8 @@ type folderScanner struct {
|
|||||||
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
||||||
scanMode madmin.HealScanMode
|
scanMode madmin.HealScanMode
|
||||||
|
|
||||||
|
weSleep func() bool
|
||||||
|
|
||||||
disks []StorageAPI
|
disks []StorageAPI
|
||||||
disksQuorum int
|
disksQuorum int
|
||||||
|
|
||||||
@@ -299,7 +302,7 @@ type folderScanner struct {
|
|||||||
// The returned cache will always be valid, but may not be updated from the existing.
|
// 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.
|
// 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.
|
// If the supplied context is canceled the function will return at the first chance.
|
||||||
func scanDataFolder(ctx context.Context, disks []StorageAPI, 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, weSleep func() bool) (dataUsageCache, error) {
|
||||||
switch cache.Info.Name {
|
switch cache.Info.Name {
|
||||||
case "", dataUsageRoot:
|
case "", dataUsageRoot:
|
||||||
return cache, errors.New("internal error: root scan attempted")
|
return cache, errors.New("internal error: root scan attempted")
|
||||||
@@ -316,6 +319,7 @@ func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, ca
|
|||||||
dataUsageScannerDebug: false,
|
dataUsageScannerDebug: false,
|
||||||
healObjectSelect: 0,
|
healObjectSelect: 0,
|
||||||
scanMode: scanMode,
|
scanMode: scanMode,
|
||||||
|
weSleep: weSleep,
|
||||||
updates: cache.Info.updates,
|
updates: cache.Info.updates,
|
||||||
updateCurrentPath: updatePath,
|
updateCurrentPath: updatePath,
|
||||||
disks: disks,
|
disks: disks,
|
||||||
@@ -343,7 +347,6 @@ func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, ca
|
|||||||
// No useful information...
|
// No useful information...
|
||||||
return cache, err
|
return cache, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.newCache.Info.LastUpdate = UTCNow()
|
s.newCache.Info.LastUpdate = UTCNow()
|
||||||
s.newCache.Info.NextCycle = cache.Info.NextCycle
|
s.newCache.Info.NextCycle = cache.Info.NextCycle
|
||||||
return s.newCache, nil
|
return s.newCache, nil
|
||||||
@@ -373,6 +376,8 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
done := ctx.Done()
|
done := ctx.Done()
|
||||||
scannerLogPrefix := color.Green("folder-scanner:")
|
scannerLogPrefix := color.Green("folder-scanner:")
|
||||||
|
|
||||||
|
noWait := func() {}
|
||||||
|
|
||||||
thisHash := hashPath(folder.name)
|
thisHash := hashPath(folder.name)
|
||||||
// Store initial compaction state.
|
// Store initial compaction state.
|
||||||
wasCompacted := into.Compacted
|
wasCompacted := into.Compacted
|
||||||
@@ -402,8 +407,10 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
if !f.oldCache.Info.replication.Empty() && f.oldCache.Info.replication.Config.HasActiveRules(prefix, true) {
|
if !f.oldCache.Info.replication.Empty() && f.oldCache.Info.replication.Config.HasActiveRules(prefix, true) {
|
||||||
replicationCfg = f.oldCache.Info.replication
|
replicationCfg = f.oldCache.Info.replication
|
||||||
}
|
}
|
||||||
// Check if we can skip it due to bloom filter...
|
|
||||||
scannerSleeper.Sleep(ctx, dataScannerSleepPerFolder)
|
if f.weSleep() {
|
||||||
|
scannerSleeper.Sleep(ctx, dataScannerSleepPerFolder)
|
||||||
|
}
|
||||||
|
|
||||||
var existingFolders, newFolders []cachedFolder
|
var existingFolders, newFolders []cachedFolder
|
||||||
var foundObjects bool
|
var foundObjects bool
|
||||||
@@ -454,8 +461,11 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
wait := noWait
|
||||||
wait := scannerSleeper.Timer(ctx)
|
if f.weSleep() {
|
||||||
|
// Dynamic time delay.
|
||||||
|
wait = scannerSleeper.Timer(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Get file size, ignore errors.
|
// Get file size, ignore errors.
|
||||||
item := scannerItem{
|
item := scannerItem{
|
||||||
@@ -478,7 +488,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
item.heal.enabled = item.heal.enabled && f.healObjectSelect > 0
|
item.heal.enabled = item.heal.enabled && f.healObjectSelect > 0
|
||||||
|
|
||||||
sz, err := f.getSize(item)
|
sz, err := f.getSize(item)
|
||||||
if err != nil {
|
if err != nil && err != errIgnoreFileContrib {
|
||||||
wait() // wait to proceed to next entry.
|
wait() // wait to proceed to next entry.
|
||||||
if err != errSkipFile && f.dataUsageScannerDebug {
|
if err != errSkipFile && f.dataUsageScannerDebug {
|
||||||
console.Debugf(scannerLogPrefix+" getSize \"%v/%v\" returned err: %v\n", bucket, item.objectPath(), err)
|
console.Debugf(scannerLogPrefix+" getSize \"%v/%v\" returned err: %v\n", bucket, item.objectPath(), err)
|
||||||
@@ -496,8 +506,10 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
// object.
|
// object.
|
||||||
delete(abandonedChildren, pathJoin(item.bucket, item.objectPath()))
|
delete(abandonedChildren, pathJoin(item.bucket, item.objectPath()))
|
||||||
|
|
||||||
into.addSizes(sz)
|
if err != errIgnoreFileContrib {
|
||||||
into.Objects++
|
into.addSizes(sz)
|
||||||
|
into.Objects++
|
||||||
|
}
|
||||||
|
|
||||||
wait() // wait to proceed to next entry.
|
wait() // wait to proceed to next entry.
|
||||||
|
|
||||||
@@ -580,12 +592,12 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
foundAny = true
|
foundAny = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if next := f.updateCache.searchParent(parent); next == nil {
|
next := f.updateCache.searchParent(parent)
|
||||||
|
if next == nil {
|
||||||
foundAny = true
|
foundAny = true
|
||||||
break
|
break
|
||||||
} else {
|
|
||||||
parent = *next
|
|
||||||
}
|
}
|
||||||
|
parent = *next
|
||||||
}
|
}
|
||||||
if !foundAny {
|
if !foundAny {
|
||||||
// Add non-compacted empty entry.
|
// Add non-compacted empty entry.
|
||||||
@@ -703,8 +715,11 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
// this object might be dangling.
|
// this object might be dangling.
|
||||||
entry, _ = entries.firstFound()
|
entry, _ = entries.firstFound()
|
||||||
}
|
}
|
||||||
// wait timer per object.
|
wait := noWait
|
||||||
wait := scannerSleeper.Timer(ctx)
|
if f.weSleep() {
|
||||||
|
// wait timer per object.
|
||||||
|
wait = scannerSleeper.Timer(ctx)
|
||||||
|
}
|
||||||
defer wait()
|
defer wait()
|
||||||
f.updateCurrentPath(entry.name)
|
f.updateCurrentPath(entry.name)
|
||||||
stopFn := globalScannerMetrics.log(scannerMetricHealAbandonedObject, f.root, entry.name)
|
stopFn := globalScannerMetrics.log(scannerMetricHealAbandonedObject, f.root, entry.name)
|
||||||
@@ -728,7 +743,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
versionID: "",
|
versionID: "",
|
||||||
}, madmin.HealItemObject)
|
}, madmin.HealItemObject)
|
||||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, entry.name)
|
||||||
}
|
}
|
||||||
foundObjs = foundObjs || err == nil
|
foundObjs = foundObjs || err == nil
|
||||||
return
|
return
|
||||||
@@ -745,7 +760,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
}, madmin.HealItemObject)
|
}, madmin.HealItemObject)
|
||||||
stopFn(int(ver.Size))
|
stopFn(int(ver.Size))
|
||||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, fiv.Name)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
successVersions++
|
successVersions++
|
||||||
@@ -918,18 +933,20 @@ func (i *scannerItem) applyHealing(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi ObjectInfo) (applied bool, size int64) {
|
func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi ObjectInfo) (action lifecycle.Action, size int64) {
|
||||||
size, err := oi.GetActualSize()
|
size, err := oi.GetActualSize()
|
||||||
if i.debug {
|
if i.debug {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
if i.lifeCycle == nil {
|
if i.lifeCycle == nil {
|
||||||
return false, size
|
return action, size
|
||||||
}
|
}
|
||||||
|
|
||||||
versionID := oi.VersionID
|
versionID := oi.VersionID
|
||||||
|
vcfg, _ := globalBucketVersioningSys.Get(i.bucket)
|
||||||
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
|
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
|
||||||
lcEvt := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi)
|
replcfg, _ := getReplicationConfig(ctx, i.bucket)
|
||||||
|
lcEvt := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, replcfg, oi)
|
||||||
if i.debug {
|
if i.debug {
|
||||||
if versionID != "" {
|
if versionID != "" {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, lcEvt.Action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, lcEvt.Action)
|
||||||
@@ -938,7 +955,7 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if applied {
|
if lcEvt.Action != lifecycle.NoneAction {
|
||||||
numVersions := uint64(1)
|
numVersions := uint64(1)
|
||||||
if lcEvt.Action == lifecycle.DeleteAllVersionsAction {
|
if lcEvt.Action == lifecycle.DeleteAllVersionsAction {
|
||||||
numVersions = uint64(oi.NumVersions)
|
numVersions = uint64(oi.NumVersions)
|
||||||
@@ -948,14 +965,21 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
switch lcEvt.Action {
|
switch lcEvt.Action {
|
||||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction, lifecycle.DeleteAllVersionsAction:
|
// This version doesn't contribute towards sizeS only when it is permanently deleted.
|
||||||
return applyLifecycleAction(lcEvt, lcEventSrc_Scanner, oi), 0
|
// This can happen when,
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
// - ExpireObjectAllVersions flag is enabled
|
||||||
return applyLifecycleAction(lcEvt, lcEventSrc_Scanner, oi), size
|
// - NoncurrentVersionExpiration is applicable
|
||||||
default:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAllVersionsAction:
|
||||||
// No action.
|
size = 0
|
||||||
return false, size
|
case lifecycle.DeleteAction:
|
||||||
|
// On a non-versioned bucket, DeleteObject removes the only version permanently.
|
||||||
|
if !vcfg.PrefixEnabled(oi.Name) {
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyLifecycleAction(lcEvt, lcEventSrc_Scanner, oi)
|
||||||
|
return lcEvt.Action, size
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyTierObjSweep removes remote object pending deletion and the free-version
|
// applyTierObjSweep removes remote object pending deletion and the free-version
|
||||||
@@ -1097,15 +1121,22 @@ func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fi
|
|||||||
// The resulting size on disk will always be returned.
|
// The resulting size on disk will always be returned.
|
||||||
// The metadata will be compared to consensus on the object layer before any changes are applied.
|
// The metadata will be compared to consensus on the object layer before any changes are applied.
|
||||||
// If no metadata is supplied, -1 is returned if no action is taken.
|
// If no metadata is supplied, -1 is returned if no action is taken.
|
||||||
func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi ObjectInfo, sizeS *sizeSummary) int64 {
|
func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi ObjectInfo, sizeS *sizeSummary) (objDeleted bool, size int64) {
|
||||||
done := globalScannerMetrics.time(scannerMetricILM)
|
done := globalScannerMetrics.time(scannerMetricILM)
|
||||||
applied, size := i.applyLifecycle(ctx, o, oi)
|
var action lifecycle.Action
|
||||||
|
action, size = i.applyLifecycle(ctx, o, oi)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
// Note: objDeleted is true if and only if action ==
|
||||||
|
// lifecycle.DeleteAllVersionsAction
|
||||||
|
if action == lifecycle.DeleteAllVersionsAction {
|
||||||
|
return true, 0
|
||||||
|
}
|
||||||
|
|
||||||
// For instance, an applied lifecycle means we remove/transitioned an object
|
// For instance, an applied lifecycle means we remove/transitioned an object
|
||||||
// from the current deployment, which means we don't have to call healing
|
// from the current deployment, which means we don't have to call healing
|
||||||
// routine even if we are asked to do via heal flag.
|
// routine even if we are asked to do via heal flag.
|
||||||
if !applied {
|
if action == lifecycle.NoneAction {
|
||||||
if i.heal.enabled {
|
if i.heal.enabled {
|
||||||
done := globalScannerMetrics.time(scannerMetricHealCheck)
|
done := globalScannerMetrics.time(scannerMetricHealCheck)
|
||||||
size = i.applyHealing(ctx, o, oi)
|
size = i.applyHealing(ctx, o, oi)
|
||||||
@@ -1116,7 +1147,7 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
|
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
|
||||||
done()
|
done()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
|
logger.LogOnceIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err), i.objectPath())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1126,10 +1157,10 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
i.healReplication(ctx, o, oi.Clone(), sizeS)
|
i.healReplication(ctx, o, oi.Clone(), sizeS)
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
return size
|
return false, size
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) lifecycle.Event {
|
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, rcfg *replication.Config, obj ObjectInfo) lifecycle.Event {
|
||||||
event := lc.Eval(obj.ToLifecycleOpts())
|
event := lc.Eval(obj.ToLifecycleOpts())
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
|
||||||
@@ -1161,6 +1192,9 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
|
|||||||
}
|
}
|
||||||
return lifecycle.Event{Action: lifecycle.NoneAction}
|
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||||
}
|
}
|
||||||
|
if rcfg != nil && !obj.VersionPurgeStatus.Empty() && rcfg.HasActiveRules(obj.Name, true) {
|
||||||
|
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
return event
|
||||||
@@ -1179,7 +1213,7 @@ func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer,
|
|||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, obj.Name)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Notification already sent in *expireTransitionedObject*, just return 'true' here.
|
// Notification already sent in *expireTransitionedObject*, just return 'true' here.
|
||||||
|
|||||||
+128
-44
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
@@ -46,6 +47,10 @@ import (
|
|||||||
// dataUsageHash is the hash type used.
|
// dataUsageHash is the hash type used.
|
||||||
type dataUsageHash string
|
type dataUsageHash string
|
||||||
|
|
||||||
|
// sizeHistogramV1 is size histogram V1, which has fewer intervals esp. between
|
||||||
|
// 1024B and 1MiB.
|
||||||
|
type sizeHistogramV1 [dataUsageBucketLenV1]uint64
|
||||||
|
|
||||||
// sizeHistogram is a size histogram.
|
// sizeHistogram is a size histogram.
|
||||||
type sizeHistogram [dataUsageBucketLen]uint64
|
type sizeHistogram [dataUsageBucketLen]uint64
|
||||||
|
|
||||||
@@ -78,8 +83,8 @@ func newAllTierStats() *allTierStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ats *allTierStats) addSizes(sz sizeSummary) {
|
func (ats *allTierStats) addSizes(tiers map[string]tierStats) {
|
||||||
for tier, st := range sz.tiers {
|
for tier, st := range tiers {
|
||||||
ats.Tiers[tier] = ats.Tiers[tier].add(st)
|
ats.Tiers[tier] = ats.Tiers[tier].add(st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,18 +100,16 @@ func (ats *allTierStats) clone() *allTierStats {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dst := *ats
|
dst := *ats
|
||||||
if dst.Tiers != nil {
|
dst.Tiers = make(map[string]tierStats, len(ats.Tiers))
|
||||||
dst.Tiers = make(map[string]tierStats, len(dst.Tiers))
|
for tier, st := range ats.Tiers {
|
||||||
for tier, st := range dst.Tiers {
|
dst.Tiers[tier] = st
|
||||||
dst.Tiers[tier] = st
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &dst
|
return &dst
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ats *allTierStats) adminStats(stats map[string]madmin.TierStats) map[string]madmin.TierStats {
|
func (ats *allTierStats) populateStats(stats map[string]madmin.TierStats) {
|
||||||
if ats == nil {
|
if ats == nil {
|
||||||
return stats
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stats for tiers as they become available.
|
// Update stats for tiers as they become available.
|
||||||
@@ -117,7 +120,7 @@ func (ats *allTierStats) adminStats(stats map[string]madmin.TierStats) map[strin
|
|||||||
NumObjects: st.NumObjects,
|
NumObjects: st.NumObjects,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stats
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// tierStats holds per-tier stats of a remote tier.
|
// tierStats holds per-tier stats of a remote tier.
|
||||||
@@ -128,10 +131,11 @@ type tierStats struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ts tierStats) add(u tierStats) tierStats {
|
func (ts tierStats) add(u tierStats) tierStats {
|
||||||
ts.TotalSize += u.TotalSize
|
return tierStats{
|
||||||
ts.NumVersions += u.NumVersions
|
TotalSize: ts.TotalSize + u.TotalSize,
|
||||||
ts.NumObjects += u.NumObjects
|
NumVersions: ts.NumVersions + u.NumVersions,
|
||||||
return ts
|
NumObjects: ts.NumObjects + u.NumObjects,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//msgp:tuple replicationStatsV1
|
//msgp:tuple replicationStatsV1
|
||||||
@@ -197,17 +201,16 @@ func (r *replicationAllStats) clone() *replicationAllStats {
|
|||||||
dst := *r
|
dst := *r
|
||||||
|
|
||||||
// Copy individual targets.
|
// Copy individual targets.
|
||||||
if dst.Targets != nil {
|
dst.Targets = make(map[string]replicationStats, len(r.Targets))
|
||||||
dst.Targets = make(map[string]replicationStats, len(dst.Targets))
|
for k, v := range r.Targets {
|
||||||
for k, v := range r.Targets {
|
dst.Targets[k] = v
|
||||||
dst.Targets[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &dst
|
return &dst
|
||||||
}
|
}
|
||||||
|
|
||||||
//msgp:encode ignore dataUsageEntryV2 dataUsageEntryV3 dataUsageEntryV4 dataUsageEntryV5 dataUsageEntryV6
|
//msgp:encode ignore dataUsageEntryV2 dataUsageEntryV3 dataUsageEntryV4 dataUsageEntryV5 dataUsageEntryV6 dataUsageEntryV7
|
||||||
//msgp:marshal ignore dataUsageEntryV2 dataUsageEntryV3 dataUsageEntryV4 dataUsageEntryV5 dataUsageEntryV6
|
//msgp:marshal ignore dataUsageEntryV2 dataUsageEntryV3 dataUsageEntryV4 dataUsageEntryV5 dataUsageEntryV6 dataUsageEntryV7
|
||||||
|
|
||||||
//msgp:tuple dataUsageEntryV2
|
//msgp:tuple dataUsageEntryV2
|
||||||
type dataUsageEntryV2 struct {
|
type dataUsageEntryV2 struct {
|
||||||
@@ -265,14 +268,28 @@ type dataUsageEntryV6 struct {
|
|||||||
Compacted bool
|
Compacted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dataUsageEntryV7 struct {
|
||||||
|
Children dataUsageHashMap `msg:"ch"`
|
||||||
|
// These fields do no include any children.
|
||||||
|
Size int64 `msg:"sz"`
|
||||||
|
Objects uint64 `msg:"os"`
|
||||||
|
Versions uint64 `msg:"vs"` // Versions that are not delete markers.
|
||||||
|
DeleteMarkers uint64 `msg:"dms"`
|
||||||
|
ObjSizes sizeHistogramV1 `msg:"szs"`
|
||||||
|
ObjVersions versionsHistogram `msg:"vh"`
|
||||||
|
ReplicationStats *replicationAllStats `msg:"rs,omitempty"`
|
||||||
|
AllTierStats *allTierStats `msg:"ats,omitempty"`
|
||||||
|
Compacted bool `msg:"c"`
|
||||||
|
}
|
||||||
|
|
||||||
// dataUsageCache contains a cache of data usage entries latest version.
|
// dataUsageCache contains a cache of data usage entries latest version.
|
||||||
type dataUsageCache struct {
|
type dataUsageCache struct {
|
||||||
Info dataUsageCacheInfo
|
Info dataUsageCacheInfo
|
||||||
Cache map[string]dataUsageEntry
|
Cache map[string]dataUsageEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
//msgp:encode ignore dataUsageCacheV2 dataUsageCacheV3 dataUsageCacheV4 dataUsageCacheV5 dataUsageCacheV6
|
//msgp:encode ignore dataUsageCacheV2 dataUsageCacheV3 dataUsageCacheV4 dataUsageCacheV5 dataUsageCacheV6 dataUsageCacheV7
|
||||||
//msgp:marshal ignore dataUsageCacheV2 dataUsageCacheV3 dataUsageCacheV4 dataUsageCacheV5 dataUsageCacheV6
|
//msgp:marshal ignore dataUsageCacheV2 dataUsageCacheV3 dataUsageCacheV4 dataUsageCacheV5 dataUsageCacheV6 dataUsageCacheV7
|
||||||
|
|
||||||
// dataUsageCacheV2 contains a cache of data usage entries version 2.
|
// dataUsageCacheV2 contains a cache of data usage entries version 2.
|
||||||
type dataUsageCacheV2 struct {
|
type dataUsageCacheV2 struct {
|
||||||
@@ -304,6 +321,12 @@ type dataUsageCacheV6 struct {
|
|||||||
Cache map[string]dataUsageEntryV6
|
Cache map[string]dataUsageEntryV6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dataUsageCacheV7 contains a cache of data usage entries version 7.
|
||||||
|
type dataUsageCacheV7 struct {
|
||||||
|
Info dataUsageCacheInfo
|
||||||
|
Cache map[string]dataUsageEntryV7
|
||||||
|
}
|
||||||
|
|
||||||
//msgp:ignore dataUsageEntryInfo
|
//msgp:ignore dataUsageEntryInfo
|
||||||
type dataUsageEntryInfo struct {
|
type dataUsageEntryInfo struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -360,11 +383,11 @@ func (e *dataUsageEntry) addSizes(summary sizeSummary) {
|
|||||||
tgtStat.PendingCount += st.pendingCount
|
tgtStat.PendingCount += st.pendingCount
|
||||||
e.ReplicationStats.Targets[arn] = tgtStat
|
e.ReplicationStats.Targets[arn] = tgtStat
|
||||||
}
|
}
|
||||||
if summary.tiers != nil {
|
if len(summary.tiers) != 0 {
|
||||||
if e.AllTierStats == nil {
|
if e.AllTierStats == nil {
|
||||||
e.AllTierStats = newAllTierStats()
|
e.AllTierStats = newAllTierStats()
|
||||||
}
|
}
|
||||||
e.AllTierStats.addSizes(summary)
|
e.AllTierStats.addSizes(summary.tiers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +426,7 @@ func (e *dataUsageEntry) merge(other dataUsageEntry) {
|
|||||||
e.ObjVersions[i] += v
|
e.ObjVersions[i] += v
|
||||||
}
|
}
|
||||||
|
|
||||||
if other.AllTierStats != nil {
|
if other.AllTierStats != nil && len(other.AllTierStats.Tiers) != 0 {
|
||||||
if e.AllTierStats == nil {
|
if e.AllTierStats == nil {
|
||||||
e.AllTierStats = newAllTierStats()
|
e.AllTierStats = newAllTierStats()
|
||||||
}
|
}
|
||||||
@@ -752,11 +775,42 @@ func (h *sizeHistogram) add(size int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeV1 is used to migrate data usage cache from sizeHistogramV1 to
|
||||||
|
// sizeHistogram
|
||||||
|
func (h *sizeHistogram) mergeV1(v sizeHistogramV1) {
|
||||||
|
var oidx, nidx int
|
||||||
|
for oidx < len(v) {
|
||||||
|
intOld, intNew := ObjectsHistogramIntervalsV1[oidx], ObjectsHistogramIntervals[nidx]
|
||||||
|
// skip intervals that aren't common to both histograms
|
||||||
|
if intOld.start != intNew.start || intOld.end != intNew.end {
|
||||||
|
nidx++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h[nidx] += v[oidx]
|
||||||
|
oidx++
|
||||||
|
nidx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// toMap returns the map to a map[string]uint64.
|
// toMap returns the map to a map[string]uint64.
|
||||||
func (h *sizeHistogram) toMap() map[string]uint64 {
|
func (h *sizeHistogram) toMap() map[string]uint64 {
|
||||||
res := make(map[string]uint64, dataUsageBucketLen)
|
res := make(map[string]uint64, dataUsageBucketLen)
|
||||||
|
var splCount uint64
|
||||||
for i, count := range h {
|
for i, count := range h {
|
||||||
res[ObjectsHistogramIntervals[i].name] = count
|
szInt := ObjectsHistogramIntervals[i]
|
||||||
|
switch {
|
||||||
|
case humanize.KiByte == szInt.start && szInt.end == humanize.MiByte-1:
|
||||||
|
// spl interval: [1024B, 1MiB)
|
||||||
|
res[szInt.name] = splCount
|
||||||
|
case humanize.KiByte <= szInt.start && szInt.end <= humanize.MiByte-1:
|
||||||
|
// intervals that fall within the spl interval above; they
|
||||||
|
// appear earlier in this array of intervals, see
|
||||||
|
// ObjectsHistogramIntervals
|
||||||
|
splCount += count
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
res[szInt.name] = count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -924,7 +978,7 @@ type objectIO interface {
|
|||||||
// load the cache content with name from minioMetaBackgroundOpsBucket.
|
// load the cache content with name from minioMetaBackgroundOpsBucket.
|
||||||
// Only backend errors are returned as errors.
|
// Only backend errors are returned as errors.
|
||||||
// The loader is optimistic and has no locking, but tries 5 times before giving up.
|
// The loader is optimistic and has no locking, but tries 5 times before giving up.
|
||||||
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
|
// If the object is not found, a nil error with empty data usage cache is returned.
|
||||||
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
||||||
// By defaut, empty data usage cache
|
// By defaut, empty data usage cache
|
||||||
*d = dataUsageCache{}
|
*d = dataUsageCache{}
|
||||||
@@ -960,13 +1014,19 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
|
|||||||
if !retry {
|
if !retry {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
retry, _ = load(name+".bkp", 30*time.Second)
|
retry, err = load(name+".bkp", 30*time.Second)
|
||||||
if !retry {
|
if err == nil && !retry {
|
||||||
|
// Only return when we have valid data from the backup
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
retries++
|
retries++
|
||||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if retries == 5 {
|
||||||
|
logger.LogOnceIf(ctx, fmt.Errorf("maximum retry reached to load the data usage cache `%s`", name), "retry-loading-data-usage-cache")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -981,15 +1041,10 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case maxConcurrentScannerSaves <- struct{}{}:
|
case maxConcurrentScannerSaves <- struct{}{}:
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-maxConcurrentScannerSaves:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
buf := bytebufferpool.Get()
|
buf := bytebufferpool.Get()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
<-maxConcurrentScannerSaves
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
bytebufferpool.Put(buf)
|
bytebufferpool.Put(buf)
|
||||||
}()
|
}()
|
||||||
@@ -998,12 +1053,12 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hr, err := hash.NewReader(ctx, bytes.NewReader(buf.Bytes()), int64(buf.Len()), "", "", int64(buf.Len()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
save := func(name string, timeout time.Duration) error {
|
save := func(name string, timeout time.Duration) error {
|
||||||
|
hr, err := hash.NewReader(ctx, bytes.NewReader(buf.Bytes()), int64(buf.Len()), "", "", int64(buf.Len()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Abandon if more than a minute, so we don't hold up scanner.
|
// Abandon if more than a minute, so we don't hold up scanner.
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -1018,7 +1073,7 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer save(name+".bkp", 30*time.Second) // Keep a backup as well
|
defer save(name+".bkp", 5*time.Second) // Keep a backup as well
|
||||||
|
|
||||||
// drive timeout by default is 2 minutes, we do not need to wait longer.
|
// drive timeout by default is 2 minutes, we do not need to wait longer.
|
||||||
return save(name, time.Minute)
|
return save(name, time.Minute)
|
||||||
@@ -1028,7 +1083,8 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
// Bumping the cache version will drop data from previous versions
|
// Bumping the cache version will drop data from previous versions
|
||||||
// and write new data with the new version.
|
// and write new data with the new version.
|
||||||
const (
|
const (
|
||||||
dataUsageCacheVerCurrent = 7
|
dataUsageCacheVerCurrent = 8
|
||||||
|
dataUsageCacheVerV7 = 7
|
||||||
dataUsageCacheVerV6 = 6
|
dataUsageCacheVerV6 = 6
|
||||||
dataUsageCacheVerV5 = 5
|
dataUsageCacheVerV5 = 5
|
||||||
dataUsageCacheVerV4 = 4
|
dataUsageCacheVerV4 = 4
|
||||||
@@ -1276,6 +1332,34 @@ func (d *dataUsageCache) deserialize(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
d.Cache[k] = due
|
d.Cache[k] = due
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
case dataUsageCacheVerV7:
|
||||||
|
// Zstd compressed.
|
||||||
|
dec, err := zstd.NewReader(r, zstd.WithDecoderConcurrency(2))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dec.Close()
|
||||||
|
dold := &dataUsageCacheV7{}
|
||||||
|
if err = dold.DecodeMsg(msgp.NewReader(dec)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Info = dold.Info
|
||||||
|
d.Cache = make(map[string]dataUsageEntry, len(dold.Cache))
|
||||||
|
for k, v := range dold.Cache {
|
||||||
|
var szHist sizeHistogram
|
||||||
|
szHist.mergeV1(v.ObjSizes)
|
||||||
|
d.Cache[k] = dataUsageEntry{
|
||||||
|
Children: v.Children,
|
||||||
|
Size: v.Size,
|
||||||
|
Objects: v.Objects,
|
||||||
|
Versions: v.Versions,
|
||||||
|
ObjSizes: szHist,
|
||||||
|
ReplicationStats: v.ReplicationStats,
|
||||||
|
Compacted: v.Compacted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
case dataUsageCacheVerCurrent:
|
case dataUsageCacheVerCurrent:
|
||||||
// Zstd compressed.
|
// Zstd compressed.
|
||||||
|
|||||||
@@ -1480,6 +1480,149 @@ func (z *dataUsageCacheV6) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *dataUsageCacheV7) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Info":
|
||||||
|
err = z.Info.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Cache":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Cache == nil {
|
||||||
|
z.Cache = make(map[string]dataUsageEntryV7, zb0002)
|
||||||
|
} else if len(z.Cache) > 0 {
|
||||||
|
for key := range z.Cache {
|
||||||
|
delete(z.Cache, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0002 > 0 {
|
||||||
|
zb0002--
|
||||||
|
var za0001 string
|
||||||
|
var za0002 dataUsageEntryV7
|
||||||
|
za0001, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = za0002.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Cache[za0001] = za0002
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *dataUsageCacheV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Info":
|
||||||
|
bts, err = z.Info.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Cache":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Cache == nil {
|
||||||
|
z.Cache = make(map[string]dataUsageEntryV7, zb0002)
|
||||||
|
} else if len(z.Cache) > 0 {
|
||||||
|
for key := range z.Cache {
|
||||||
|
delete(z.Cache, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0002 > 0 {
|
||||||
|
var za0001 string
|
||||||
|
var za0002 dataUsageEntryV7
|
||||||
|
zb0002--
|
||||||
|
za0001, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bts, err = za0002.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Cache[za0001] = za0002
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *dataUsageCacheV7) Msgsize() (s int) {
|
||||||
|
s = 1 + 5 + z.Info.Msgsize() + 6 + msgp.MapHeaderSize
|
||||||
|
if z.Cache != nil {
|
||||||
|
for za0001, za0002 := range z.Cache {
|
||||||
|
_ = za0002
|
||||||
|
s += msgp.StringPrefixSize + len(za0001) + za0002.Msgsize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
var field []byte
|
var field []byte
|
||||||
@@ -2684,6 +2827,297 @@ func (z *dataUsageEntryV6) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *dataUsageEntryV7) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ch":
|
||||||
|
err = z.Children.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Children")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "sz":
|
||||||
|
z.Size, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "os":
|
||||||
|
z.Objects, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "vs":
|
||||||
|
z.Versions, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
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()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0002 != uint32(dataUsageBucketLenV1) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLenV1), Got: zb0002}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.ObjSizes {
|
||||||
|
z.ObjSizes[za0001], err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "vh":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0003 != uint32(dataUsageVersionLen) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageVersionLen), Got: zb0003}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0002 := range z.ObjVersions {
|
||||||
|
z.ObjVersions[za0002], err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjVersions", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "rs":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicationStats = nil
|
||||||
|
} else {
|
||||||
|
if z.ReplicationStats == nil {
|
||||||
|
z.ReplicationStats = new(replicationAllStats)
|
||||||
|
}
|
||||||
|
err = z.ReplicationStats.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "ats":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.AllTierStats = nil
|
||||||
|
} else {
|
||||||
|
if z.AllTierStats == nil {
|
||||||
|
z.AllTierStats = new(allTierStats)
|
||||||
|
}
|
||||||
|
err = z.AllTierStats.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "c":
|
||||||
|
z.Compacted, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Compacted")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *dataUsageEntryV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ch":
|
||||||
|
bts, err = z.Children.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Children")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "sz":
|
||||||
|
z.Size, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "os":
|
||||||
|
z.Objects, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "vs":
|
||||||
|
z.Versions, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0002 != uint32(dataUsageBucketLenV1) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLenV1), Got: zb0002}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.ObjSizes {
|
||||||
|
z.ObjSizes[za0001], bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "vh":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0003 != uint32(dataUsageVersionLen) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageVersionLen), Got: zb0003}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0002 := range z.ObjVersions {
|
||||||
|
z.ObjVersions[za0002], bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjVersions", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "rs":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicationStats = nil
|
||||||
|
} else {
|
||||||
|
if z.ReplicationStats == nil {
|
||||||
|
z.ReplicationStats = new(replicationAllStats)
|
||||||
|
}
|
||||||
|
bts, err = z.ReplicationStats.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "ats":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.AllTierStats = nil
|
||||||
|
} else {
|
||||||
|
if z.AllTierStats == nil {
|
||||||
|
z.AllTierStats = new(allTierStats)
|
||||||
|
}
|
||||||
|
bts, err = z.AllTierStats.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "c":
|
||||||
|
z.Compacted, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Compacted")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *dataUsageEntryV7) Msgsize() (s int) {
|
||||||
|
s = 1 + 3 + z.Children.Msgsize() + 3 + msgp.Int64Size + 3 + msgp.Uint64Size + 3 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.ArrayHeaderSize + (dataUsageBucketLenV1 * (msgp.Uint64Size)) + 3 + msgp.ArrayHeaderSize + (dataUsageVersionLen * (msgp.Uint64Size)) + 3
|
||||||
|
if z.ReplicationStats == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += z.ReplicationStats.Msgsize()
|
||||||
|
}
|
||||||
|
s += 4
|
||||||
|
if z.AllTierStats == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += z.AllTierStats.Msgsize()
|
||||||
|
}
|
||||||
|
s += 2 + msgp.BoolSize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *dataUsageHash) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *dataUsageHash) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
{
|
{
|
||||||
@@ -3705,6 +4139,84 @@ func (z *sizeHistogram) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *sizeHistogramV1) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0001 != uint32(dataUsageBucketLenV1) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLenV1), Got: zb0001}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z {
|
||||||
|
z[za0001], err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *sizeHistogramV1) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
err = en.WriteArrayHeader(uint32(dataUsageBucketLenV1))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z {
|
||||||
|
err = en.WriteUint64(z[za0001])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *sizeHistogramV1) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLenV1))
|
||||||
|
for za0001 := range z {
|
||||||
|
o = msgp.AppendUint64(o, z[za0001])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *sizeHistogramV1) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0001 != uint32(dataUsageBucketLenV1) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLenV1), Got: zb0001}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z {
|
||||||
|
z[za0001], bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *sizeHistogramV1) Msgsize() (s int) {
|
||||||
|
s = msgp.ArrayHeaderSize + (dataUsageBucketLenV1 * (msgp.Uint64Size))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *tierStats) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *tierStats) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
var field []byte
|
var field []byte
|
||||||
|
|||||||
@@ -1084,6 +1084,119 @@ func BenchmarkDecodesizeHistogram(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalsizeHistogramV1(t *testing.T) {
|
||||||
|
v := sizeHistogramV1{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgsizeHistogramV1(b *testing.B) {
|
||||||
|
v := sizeHistogramV1{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgsizeHistogramV1(b *testing.B) {
|
||||||
|
v := sizeHistogramV1{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalsizeHistogramV1(b *testing.B) {
|
||||||
|
v := sizeHistogramV1{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodesizeHistogramV1(t *testing.T) {
|
||||||
|
v := sizeHistogramV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodesizeHistogramV1 Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := sizeHistogramV1{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodesizeHistogramV1(b *testing.B) {
|
||||||
|
v := sizeHistogramV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodesizeHistogramV1(b *testing.B) {
|
||||||
|
v := sizeHistogramV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshaltierStats(t *testing.T) {
|
func TestMarshalUnmarshaltierStats(t *testing.T) {
|
||||||
v := tierStats{}
|
v := tierStats{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSizeHistogramToMap(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
sizes []int64
|
||||||
|
want map[string]uint64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
sizes: []int64{100, 1000, 72_000, 100_000},
|
||||||
|
want: map[string]uint64{
|
||||||
|
"LESS_THAN_1024_B": 2,
|
||||||
|
"BETWEEN_64_KB_AND_256_KB": 2,
|
||||||
|
"BETWEEN_1024B_AND_1_MB": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sizes: []int64{100, 1000, 2000, 100_000, 13 * humanize.MiByte},
|
||||||
|
want: map[string]uint64{
|
||||||
|
"LESS_THAN_1024_B": 2,
|
||||||
|
"BETWEEN_1024_B_AND_64_KB": 1,
|
||||||
|
"BETWEEN_64_KB_AND_256_KB": 1,
|
||||||
|
"BETWEEN_1024B_AND_1_MB": 2,
|
||||||
|
"BETWEEN_10_MB_AND_64_MB": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("Test-%d", i), func(t *testing.T) {
|
||||||
|
var h sizeHistogram
|
||||||
|
for _, sz := range test.sizes {
|
||||||
|
h.add(sz)
|
||||||
|
}
|
||||||
|
got := h.toMap()
|
||||||
|
exp := test.want
|
||||||
|
// what is in exp is in got
|
||||||
|
for k := range exp {
|
||||||
|
if exp[k] != got[k] {
|
||||||
|
t.Fatalf("interval %s: Expected %d values but got %d values\n", k, exp[k], got[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// what is absent in exp is absent in got too
|
||||||
|
for k := range got {
|
||||||
|
if _, ok := exp[k]; !ok && got[k] > 0 {
|
||||||
|
t.Fatalf("Unexpected interval: %s has value %d\n", k, got[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateSizeHistogramFromV1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
v sizeHistogramV1
|
||||||
|
want sizeHistogram
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
v: sizeHistogramV1{0: 10, 1: 20, 2: 3},
|
||||||
|
want: sizeHistogram{0: 10, 5: 20, 6: 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: sizeHistogramV1{0: 10, 1: 20, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7},
|
||||||
|
want: sizeHistogram{0: 10, 5: 20, 6: 3, 7: 4, 8: 5, 9: 6, 10: 7},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
||||||
|
var got sizeHistogram
|
||||||
|
got.mergeV1(test.v)
|
||||||
|
if got != test.want {
|
||||||
|
t.Fatalf("Expected %v but got %v", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-21
@@ -107,35 +107,22 @@ func (dui DataUsageInfo) tierStats() []madmin.TierInfo {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgs := globalTierConfigMgr.ListTiers()
|
if globalTierConfigMgr.Empty() {
|
||||||
if len(cfgs) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := make(map[string]madmin.TierStats, len(cfgs)+1)
|
ts := make(map[string]madmin.TierStats)
|
||||||
|
dui.TierStats.populateStats(ts)
|
||||||
|
|
||||||
infos := make([]madmin.TierInfo, 0, len(ts))
|
infos := make([]madmin.TierInfo, 0, len(ts))
|
||||||
|
for tier, stats := range ts {
|
||||||
// Add STANDARD (hot-tier)
|
|
||||||
ts[minioHotTier] = madmin.TierStats{}
|
|
||||||
infos = append(infos, madmin.TierInfo{
|
|
||||||
Name: minioHotTier,
|
|
||||||
Type: "internal",
|
|
||||||
})
|
|
||||||
// Add configured remote tiers
|
|
||||||
for _, cfg := range cfgs {
|
|
||||||
ts[cfg.Name] = madmin.TierStats{}
|
|
||||||
infos = append(infos, madmin.TierInfo{
|
infos = append(infos, madmin.TierInfo{
|
||||||
Name: cfg.Name,
|
Name: tier,
|
||||||
Type: cfg.Type.String(),
|
Type: globalTierConfigMgr.TierType(tier),
|
||||||
|
Stats: stats,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ts = dui.TierStats.adminStats(ts)
|
|
||||||
for i := range infos {
|
|
||||||
info := infos[i]
|
|
||||||
infos[i].Stats = ts[info.Name]
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(infos, func(i, j int) bool {
|
sort.Slice(infos, func(i, j int) bool {
|
||||||
if infos[i].Type == "internal" {
|
if infos[i].Type == "internal" {
|
||||||
return true
|
return true
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
|||||||
attempts = 1
|
attempts = 1
|
||||||
}
|
}
|
||||||
if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil {
|
if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogOnceIf(ctx, err, dataUsageObjNamePath)
|
||||||
}
|
}
|
||||||
attempts++
|
attempts++
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-14
@@ -62,7 +62,9 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0)
|
weSleep := func() bool { return false }
|
||||||
|
|
||||||
|
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
size: 1322310,
|
size: 1322310,
|
||||||
flatten: true,
|
flatten: true,
|
||||||
objs: 8,
|
objs: 8,
|
||||||
oSizes: sizeHistogram{0: 2, 1: 6},
|
oSizes: sizeHistogram{0: 2, 1: 3, 2: 2, 4: 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
@@ -92,7 +94,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
path: "/dir1",
|
path: "/dir1",
|
||||||
size: 1302010,
|
size: 1302010,
|
||||||
objs: 5,
|
objs: 5,
|
||||||
oSizes: sizeHistogram{0: 1, 1: 4},
|
oSizes: sizeHistogram{0: 1, 1: 1, 2: 2, 4: 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/dir1/dira",
|
path: "/dir1/dira",
|
||||||
@@ -165,7 +167,6 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
size: 200,
|
size: 200,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
createUsageTestFiles(t, base, bucket, files)
|
createUsageTestFiles(t, base, bucket, files)
|
||||||
err = os.RemoveAll(filepath.Join(base, bucket, "dir1/dira/dirasub/dcfile"))
|
err = os.RemoveAll(filepath.Join(base, bucket, "dir1/dira/dirasub/dcfile"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -173,7 +174,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Changed dir must be picked up in this many cycles.
|
// Changed dir must be picked up in this many cycles.
|
||||||
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
||||||
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0)
|
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0, weSleep)
|
||||||
got.Info.NextCycle++
|
got.Info.NextCycle++
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -192,14 +193,14 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
size: 363515,
|
size: 363515,
|
||||||
flatten: true,
|
flatten: true,
|
||||||
objs: 14,
|
objs: 14,
|
||||||
oSizes: sizeHistogram{0: 7, 1: 7},
|
oSizes: sizeHistogram{0: 7, 1: 5, 2: 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/dir1",
|
path: "/dir1",
|
||||||
size: 342210,
|
size: 342210,
|
||||||
objs: 7,
|
objs: 7,
|
||||||
flatten: false,
|
flatten: false,
|
||||||
oSizes: sizeHistogram{0: 2, 1: 5},
|
oSizes: sizeHistogram{0: 2, 1: 3, 2: 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/newfolder",
|
path: "/newfolder",
|
||||||
@@ -280,7 +281,10 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize, 0)
|
|
||||||
|
weSleep := func() bool { return false }
|
||||||
|
|
||||||
|
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize, 0, weSleep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -303,7 +307,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
path: "flat",
|
path: "flat",
|
||||||
size: 1322310 + expectSize,
|
size: 1322310 + expectSize,
|
||||||
objs: 8 + expectSize,
|
objs: 8 + expectSize,
|
||||||
oSizes: sizeHistogram{0: 2 + expectSize, 1: 6},
|
oSizes: sizeHistogram{0: 2 + expectSize, 1: 3, 2: 2, 4: 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "bucket/",
|
path: "bucket/",
|
||||||
@@ -316,7 +320,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
path: "bucket/dir1",
|
path: "bucket/dir1",
|
||||||
size: 1302010,
|
size: 1302010,
|
||||||
objs: 5,
|
objs: 5,
|
||||||
oSizes: sizeHistogram{0: 1, 1: 4},
|
oSizes: sizeHistogram{0: 1, 1: 1, 2: 2, 4: 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Gets compacted at this level...
|
// Gets compacted at this level...
|
||||||
@@ -414,7 +418,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Changed dir must be picked up in this many cycles.
|
// Changed dir must be picked up in this many cycles.
|
||||||
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
||||||
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0)
|
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0, weSleep)
|
||||||
got.Info.NextCycle++
|
got.Info.NextCycle++
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -431,13 +435,13 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
path: "flat",
|
path: "flat",
|
||||||
size: 363515 + expectSize,
|
size: 363515 + expectSize,
|
||||||
objs: 14 + expectSize,
|
objs: 14 + expectSize,
|
||||||
oSizes: sizeHistogram{0: 7 + expectSize, 1: 7},
|
oSizes: sizeHistogram{0: 7 + expectSize, 1: 5, 2: 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "bucket/dir1",
|
path: "bucket/dir1",
|
||||||
size: 342210,
|
size: 342210,
|
||||||
objs: 7,
|
objs: 7,
|
||||||
oSizes: sizeHistogram{0: 2, 1: 5},
|
oSizes: sizeHistogram{0: 2, 1: 3, 2: 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "bucket/",
|
path: "bucket/",
|
||||||
@@ -562,7 +566,8 @@ func TestDataUsageCacheSerialize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
want, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0)
|
weSleep := func() bool { return false }
|
||||||
|
want, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,60 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
/*
|
|
||||||
* MinIO Object Storage (c) 2021 MinIO, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/djherbis/atime"
|
|
||||||
"golang.org/x/sys/windows/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return error if Atime is disabled on the O/S
|
|
||||||
func checkAtimeSupport(dir string) (err error) {
|
|
||||||
file, err := os.CreateTemp(dir, "prefix")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file.Name())
|
|
||||||
defer file.Close()
|
|
||||||
finfo1, err := os.Stat(file.Name())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
atime.Get(finfo1)
|
|
||||||
|
|
||||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\FileSystem`, registry.QUERY_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer k.Close()
|
|
||||||
|
|
||||||
setting, _, err := k.GetIntegerValue("NtfsDisableLastAccessUpdate")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
lowSetting := setting & 0xFFFF
|
|
||||||
if lowSetting != uint64(0x0000) && lowSetting != uint64(0x0002) {
|
|
||||||
return errors.New("Atime not supported")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of MinIO Object Storage stack
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CacheDiskStats represents cache disk statistics
|
|
||||||
// such as current disk usage and available.
|
|
||||||
type CacheDiskStats struct {
|
|
||||||
// used cache size
|
|
||||||
UsageSize uint64
|
|
||||||
// total cache disk capacity
|
|
||||||
TotalCapacity uint64
|
|
||||||
// indicates if usage is high or low, if high value is '1', if low its '0'
|
|
||||||
UsageState int32
|
|
||||||
// indicates the current usage percentage of this cache disk
|
|
||||||
UsagePercent uint64
|
|
||||||
Dir string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsageLevelString gets the string representation for the usage level.
|
|
||||||
func (c *CacheDiskStats) GetUsageLevelString() (u string) {
|
|
||||||
if atomic.LoadInt32(&c.UsageState) == 0 {
|
|
||||||
return "low"
|
|
||||||
}
|
|
||||||
return "high"
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheStats - represents bytes served from cache,
|
|
||||||
// cache hits and cache misses.
|
|
||||||
type CacheStats struct {
|
|
||||||
BytesServed uint64
|
|
||||||
Hits uint64
|
|
||||||
Misses uint64
|
|
||||||
GetDiskStats func() []CacheDiskStats
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase total bytes served from cache
|
|
||||||
func (s *CacheStats) incBytesServed(n int64) {
|
|
||||||
atomic.AddUint64(&s.BytesServed, uint64(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase cache hit by 1
|
|
||||||
func (s *CacheStats) incHit() {
|
|
||||||
atomic.AddUint64(&s.Hits, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase cache miss by 1
|
|
||||||
func (s *CacheStats) incMiss() {
|
|
||||||
atomic.AddUint64(&s.Misses, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get total bytes served
|
|
||||||
func (s *CacheStats) getBytesServed() uint64 {
|
|
||||||
return atomic.LoadUint64(&s.BytesServed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get total cache hits
|
|
||||||
func (s *CacheStats) getHits() uint64 {
|
|
||||||
return atomic.LoadUint64(&s.Hits)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get total cache misses
|
|
||||||
func (s *CacheStats) getMisses() uint64 {
|
|
||||||
return atomic.LoadUint64(&s.Misses)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare new CacheStats structure
|
|
||||||
func newCacheStats() *CacheStats {
|
|
||||||
return &CacheStats{}
|
|
||||||
}
|
|
||||||
@@ -1,587 +0,0 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of MinIO Object Storage stack
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
|
||||||
"github.com/minio/minio/internal/etag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CacheStatusType - whether the request was served from cache.
|
|
||||||
type CacheStatusType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// CacheHit - whether object was served from cache.
|
|
||||||
CacheHit CacheStatusType = "HIT"
|
|
||||||
|
|
||||||
// CacheMiss - object served from backend.
|
|
||||||
CacheMiss CacheStatusType = "MISS"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c CacheStatusType) String() string {
|
|
||||||
if c != "" {
|
|
||||||
return string(c)
|
|
||||||
}
|
|
||||||
return string(CacheMiss)
|
|
||||||
}
|
|
||||||
|
|
||||||
type cacheControl struct {
|
|
||||||
expiry time.Time
|
|
||||||
maxAge int
|
|
||||||
sMaxAge int
|
|
||||||
minFresh int
|
|
||||||
maxStale int
|
|
||||||
noStore bool
|
|
||||||
onlyIfCached bool
|
|
||||||
noCache bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cacheControl) isStale(modTime time.Time) bool {
|
|
||||||
if c == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// response will never be stale if only-if-cached is set
|
|
||||||
if c.onlyIfCached {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Cache-Control value no-store indicates never cache
|
|
||||||
if c.noStore {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Cache-Control value no-cache indicates cache entry needs to be revalidated before
|
|
||||||
// serving from cache
|
|
||||||
if c.noCache {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
if c.sMaxAge > 0 && c.sMaxAge < int(now.Sub(modTime).Seconds()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if c.maxAge > 0 && c.maxAge < int(now.Sub(modTime).Seconds()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.expiry.Equal(time.Time{}) && c.expiry.Before(time.Now().Add(time.Duration(c.maxStale))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.minFresh > 0 && c.minFresh <= int(now.Sub(modTime).Seconds()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns struct with cache-control settings from user metadata.
|
|
||||||
func cacheControlOpts(o ObjectInfo) *cacheControl {
|
|
||||||
c := cacheControl{}
|
|
||||||
m := o.UserDefined
|
|
||||||
if !o.Expires.Equal(timeSentinel) {
|
|
||||||
c.expiry = o.Expires
|
|
||||||
}
|
|
||||||
|
|
||||||
var headerVal string
|
|
||||||
for k, v := range m {
|
|
||||||
if strings.EqualFold(k, "cache-control") {
|
|
||||||
headerVal = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if headerVal == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
headerVal = strings.ToLower(headerVal)
|
|
||||||
headerVal = strings.TrimSpace(headerVal)
|
|
||||||
|
|
||||||
vals := strings.Split(headerVal, ",")
|
|
||||||
for _, val := range vals {
|
|
||||||
val = strings.TrimSpace(val)
|
|
||||||
|
|
||||||
if val == "no-store" {
|
|
||||||
c.noStore = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if val == "only-if-cached" {
|
|
||||||
c.onlyIfCached = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if val == "no-cache" {
|
|
||||||
c.noCache = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p := strings.Split(val, "=")
|
|
||||||
|
|
||||||
if len(p) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if p[0] == "max-age" ||
|
|
||||||
p[0] == "s-maxage" ||
|
|
||||||
p[0] == "min-fresh" ||
|
|
||||||
p[0] == "max-stale" {
|
|
||||||
i, err := strconv.Atoi(p[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if p[0] == "max-age" {
|
|
||||||
c.maxAge = i
|
|
||||||
}
|
|
||||||
if p[0] == "s-maxage" {
|
|
||||||
c.sMaxAge = i
|
|
||||||
}
|
|
||||||
if p[0] == "min-fresh" {
|
|
||||||
c.minFresh = i
|
|
||||||
}
|
|
||||||
if p[0] == "max-stale" {
|
|
||||||
c.maxStale = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
// backendDownError returns true if err is due to backend failure or faulty disk if in server mode
|
|
||||||
func backendDownError(err error) bool {
|
|
||||||
_, backendDown := err.(BackendDown)
|
|
||||||
return backendDown || IsErr(err, baseErrs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCacheable returns if the object should be saved in the cache.
|
|
||||||
func (o ObjectInfo) IsCacheable() bool {
|
|
||||||
if globalCacheKMS != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
_, ok := crypto.IsEncrypted(o.UserDefined)
|
|
||||||
return !ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// reads file cached on disk from offset upto length
|
|
||||||
func readCacheFileStream(filePath string, offset, length int64) (io.ReadCloser, error) {
|
|
||||||
if filePath == "" || offset < 0 {
|
|
||||||
return nil, errInvalidArgument
|
|
||||||
}
|
|
||||||
if err := checkPathLength(filePath); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fr, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, osErrToFileErr(err)
|
|
||||||
}
|
|
||||||
// Stat to get the size of the file at path.
|
|
||||||
st, err := fr.Stat()
|
|
||||||
if err != nil {
|
|
||||||
err = osErrToFileErr(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Chtimes(filePath, time.Now(), st.ModTime()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify if its not a regular file, since subsequent Seek is undefined.
|
|
||||||
if !st.Mode().IsRegular() {
|
|
||||||
return nil, errIsNotRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Chtimes(filePath, time.Now(), st.ModTime()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek to the requested offset.
|
|
||||||
if offset > 0 {
|
|
||||||
_, err = fr.Seek(offset, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return struct {
|
|
||||||
io.Reader
|
|
||||||
io.Closer
|
|
||||||
}{Reader: io.LimitReader(fr, length), Closer: fr}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCacheEncrypted(meta map[string]string) bool {
|
|
||||||
_, ok := meta[SSECacheEncrypted]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// decryptCacheObjectETag tries to decrypt the ETag saved in encrypted format using the cache KMS
|
|
||||||
func decryptCacheObjectETag(info *ObjectInfo) error {
|
|
||||||
if info.IsDir {
|
|
||||||
return nil // Directories are never encrypted.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Depending on the SSE type we handle ETags slightly
|
|
||||||
// differently. ETags encrypted with SSE-S3 must be
|
|
||||||
// decrypted first, since the client expects that
|
|
||||||
// a single-part SSE-S3 ETag is equal to the content MD5.
|
|
||||||
//
|
|
||||||
// For all other SSE types, the ETag is not the content MD5.
|
|
||||||
// Therefore, we don't decrypt but only format it.
|
|
||||||
switch kind, ok := crypto.IsEncrypted(info.UserDefined); {
|
|
||||||
case ok && kind == crypto.S3 && isCacheEncrypted(info.UserDefined):
|
|
||||||
ETag, err := etag.Parse(info.ETag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ETag.IsEncrypted() {
|
|
||||||
info.ETag = ETag.Format().String()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := crypto.S3.UnsealObjectKey(globalCacheKMS, info.UserDefined, info.Bucket, info.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ETag, err = etag.Decrypt(key[:], ETag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info.ETag = ETag.Format().String()
|
|
||||||
case ok && (kind == crypto.S3KMS || kind == crypto.SSEC) && isCacheEncrypted(info.UserDefined):
|
|
||||||
ETag, err := etag.Parse(info.ETag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info.ETag = ETag.Format().String()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decryptCacheObjectETag tries to decrypt the ETag saved in encrypted format using the cache KMS
|
|
||||||
func decryptCachePartETags(c *cacheMeta) ([]string, error) {
|
|
||||||
// Depending on the SSE type we handle ETags slightly
|
|
||||||
// differently. ETags encrypted with SSE-S3 must be
|
|
||||||
// decrypted first, since the client expects that
|
|
||||||
// a single-part SSE-S3 ETag is equal to the content MD5.
|
|
||||||
//
|
|
||||||
// For all other SSE types, the ETag is not the content MD5.
|
|
||||||
// Therefore, we don't decrypt but only format it.
|
|
||||||
switch kind, ok := crypto.IsEncrypted(c.Meta); {
|
|
||||||
case ok && kind == crypto.S3 && isCacheEncrypted(c.Meta):
|
|
||||||
key, err := crypto.S3.UnsealObjectKey(globalCacheKMS, c.Meta, c.Bucket, c.Object)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
etags := make([]string, 0, len(c.PartETags))
|
|
||||||
for i := range c.PartETags {
|
|
||||||
ETag, err := etag.Parse(c.PartETags[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ETag, err = etag.Decrypt(key[:], ETag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
etags = append(etags, ETag.Format().String())
|
|
||||||
}
|
|
||||||
return etags, nil
|
|
||||||
case ok && (kind == crypto.S3KMS || kind == crypto.SSEC) && isCacheEncrypted(c.Meta):
|
|
||||||
etags := make([]string, 0, len(c.PartETags))
|
|
||||||
for i := range c.PartETags {
|
|
||||||
ETag, err := etag.Parse(c.PartETags[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
etags = append(etags, ETag.Format().String())
|
|
||||||
}
|
|
||||||
return etags, nil
|
|
||||||
default:
|
|
||||||
return c.PartETags, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isMetadataSame(m1, m2 map[string]string) bool {
|
|
||||||
if m1 == nil && m2 == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (m1 == nil && m2 != nil) || (m2 == nil && m1 != nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(m1) != len(m2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for k1, v1 := range m1 {
|
|
||||||
if v2, ok := m2[k1]; !ok || (v1 != v2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileScorer struct {
|
|
||||||
saveBytes uint64
|
|
||||||
now int64
|
|
||||||
maxHits int
|
|
||||||
// 1/size for consistent score.
|
|
||||||
sizeMult float64
|
|
||||||
|
|
||||||
// queue is a linked list of files we want to delete.
|
|
||||||
// The list is kept sorted according to score, highest at top, lowest at bottom.
|
|
||||||
queue list.List
|
|
||||||
queuedBytes uint64
|
|
||||||
seenBytes uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type queuedFile struct {
|
|
||||||
name string
|
|
||||||
versionID string
|
|
||||||
size uint64
|
|
||||||
score float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFileScorer allows to collect files to save a specific number of bytes.
|
|
||||||
// Each file is assigned a score based on its age, size and number of hits.
|
|
||||||
// A list of files is maintained
|
|
||||||
func newFileScorer(saveBytes uint64, now int64, maxHits int) (*fileScorer, error) {
|
|
||||||
if saveBytes == 0 {
|
|
||||||
return nil, errors.New("newFileScorer: saveBytes = 0")
|
|
||||||
}
|
|
||||||
if now < 0 {
|
|
||||||
return nil, errors.New("newFileScorer: now < 0")
|
|
||||||
}
|
|
||||||
if maxHits <= 0 {
|
|
||||||
return nil, errors.New("newFileScorer: maxHits <= 0")
|
|
||||||
}
|
|
||||||
f := fileScorer{saveBytes: saveBytes, maxHits: maxHits, now: now, sizeMult: 1 / float64(saveBytes)}
|
|
||||||
f.queue.Init()
|
|
||||||
return &f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fileScorer) addFile(name string, accTime time.Time, size int64, hits int) {
|
|
||||||
f.addFileWithObjInfo(ObjectInfo{
|
|
||||||
Name: name,
|
|
||||||
AccTime: accTime,
|
|
||||||
Size: size,
|
|
||||||
}, hits)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fileScorer) addFileWithObjInfo(objInfo ObjectInfo, hits int) {
|
|
||||||
// Calculate how much we want to delete this object.
|
|
||||||
file := queuedFile{
|
|
||||||
name: objInfo.Name,
|
|
||||||
versionID: objInfo.VersionID,
|
|
||||||
size: uint64(objInfo.Size),
|
|
||||||
}
|
|
||||||
f.seenBytes += uint64(objInfo.Size)
|
|
||||||
|
|
||||||
var score float64
|
|
||||||
if objInfo.ModTime.IsZero() {
|
|
||||||
// Mod time is not available with disk cache use atime.
|
|
||||||
score = float64(f.now - objInfo.AccTime.Unix())
|
|
||||||
} else {
|
|
||||||
// if not used mod time when mod time is available.
|
|
||||||
score = float64(f.now - objInfo.ModTime.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size as fraction of how much we want to save, 0->1.
|
|
||||||
szWeight := math.Max(0, (math.Min(1, float64(file.size)*f.sizeMult)))
|
|
||||||
// 0 at f.maxHits, 1 at 0.
|
|
||||||
hitsWeight := (1.0 - math.Max(0, math.Min(1.0, float64(hits)/float64(f.maxHits))))
|
|
||||||
file.score = score * (1 + 0.25*szWeight + 0.25*hitsWeight)
|
|
||||||
// If we still haven't saved enough, just add the file
|
|
||||||
if f.queuedBytes < f.saveBytes {
|
|
||||||
f.insertFile(file)
|
|
||||||
f.trimQueue()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// If we score less than the worst, don't insert.
|
|
||||||
worstE := f.queue.Back()
|
|
||||||
if worstE != nil && file.score < worstE.Value.(queuedFile).score {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.insertFile(file)
|
|
||||||
f.trimQueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjustSaveBytes allows to adjust the number of bytes to save.
|
|
||||||
// This can be used to adjust the count on the fly.
|
|
||||||
// Returns true if there still is a need to delete files (n+saveBytes >0),
|
|
||||||
// false if no more bytes needs to be saved.
|
|
||||||
func (f *fileScorer) adjustSaveBytes(n int64) bool {
|
|
||||||
if f == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if int64(f.saveBytes)+n <= 0 {
|
|
||||||
f.saveBytes = 0
|
|
||||||
f.trimQueue()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if n < 0 {
|
|
||||||
f.saveBytes -= ^uint64(n - 1)
|
|
||||||
} else {
|
|
||||||
f.saveBytes += uint64(n)
|
|
||||||
}
|
|
||||||
if f.saveBytes == 0 {
|
|
||||||
f.queue.Init()
|
|
||||||
f.saveBytes = 0
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if n < 0 {
|
|
||||||
f.trimQueue()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// insertFile will insert a file into the list, sorted by its score.
|
|
||||||
func (f *fileScorer) insertFile(file queuedFile) {
|
|
||||||
e := f.queue.Front()
|
|
||||||
for e != nil {
|
|
||||||
v := e.Value.(queuedFile)
|
|
||||||
if v.score < file.score {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
e = e.Next()
|
|
||||||
}
|
|
||||||
f.queuedBytes += file.size
|
|
||||||
// We reached the end.
|
|
||||||
if e == nil {
|
|
||||||
f.queue.PushBack(file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.queue.InsertBefore(file, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// trimQueue will trim the back of queue and still keep below wantSave.
|
|
||||||
func (f *fileScorer) trimQueue() {
|
|
||||||
for {
|
|
||||||
e := f.queue.Back()
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v := e.Value.(queuedFile)
|
|
||||||
if f.queuedBytes-v.size < f.saveBytes {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.queue.Remove(e)
|
|
||||||
f.queuedBytes -= v.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fileScorer) purgeFunc(p func(qfile queuedFile)) {
|
|
||||||
e := f.queue.Front()
|
|
||||||
for e != nil {
|
|
||||||
p(e.Value.(queuedFile))
|
|
||||||
e = e.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fileNames returns all queued file names.
|
|
||||||
func (f *fileScorer) fileNames() []string {
|
|
||||||
res := make([]string, 0, f.queue.Len())
|
|
||||||
e := f.queue.Front()
|
|
||||||
for e != nil {
|
|
||||||
res = append(res, e.Value.(queuedFile).name)
|
|
||||||
e = e.Next()
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fileScorer) reset() {
|
|
||||||
f.queue.Init()
|
|
||||||
f.queuedBytes = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fileScorer) queueString() string {
|
|
||||||
var res strings.Builder
|
|
||||||
e := f.queue.Front()
|
|
||||||
i := 0
|
|
||||||
for e != nil {
|
|
||||||
v := e.Value.(queuedFile)
|
|
||||||
if i > 0 {
|
|
||||||
res.WriteByte('\n')
|
|
||||||
}
|
|
||||||
res.WriteString(fmt.Sprintf("%03d: %s (score: %.3f, bytes: %d)", i, v.name, v.score, v.size))
|
|
||||||
i++
|
|
||||||
e = e.Next()
|
|
||||||
}
|
|
||||||
return res.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// bytesToClear() returns the number of bytes to clear to reach low watermark
|
|
||||||
// w.r.t quota given disk total and free space, quota in % allocated to cache
|
|
||||||
// and low watermark % w.r.t allowed quota.
|
|
||||||
// If the high watermark hasn't been reached 0 will be returned.
|
|
||||||
func bytesToClear(total, free int64, quotaPct, lowWatermark, highWatermark uint64) uint64 {
|
|
||||||
used := total - free
|
|
||||||
quotaAllowed := total * (int64)(quotaPct) / 100
|
|
||||||
highWMUsage := total * (int64)(highWatermark*quotaPct) / (100 * 100)
|
|
||||||
if used < highWMUsage {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
// Return bytes needed to reach low watermark.
|
|
||||||
lowWMUsage := total * (int64)(lowWatermark*quotaPct) / (100 * 100)
|
|
||||||
return (uint64)(math.Min(float64(quotaAllowed), math.Max(0.0, float64(used-lowWMUsage))))
|
|
||||||
}
|
|
||||||
|
|
||||||
type multiWriter struct {
|
|
||||||
backendWriter io.Writer
|
|
||||||
cacheWriter *io.PipeWriter
|
|
||||||
pipeClosed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiWriter writes to backend and cache - if cache write
|
|
||||||
// fails close the pipe, but continue writing to the backend
|
|
||||||
func (t *multiWriter) Write(p []byte) (n int, err error) {
|
|
||||||
n, err = t.backendWriter.Write(p)
|
|
||||||
if err == nil && n != len(p) {
|
|
||||||
err = io.ErrShortWrite
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !t.pipeClosed {
|
|
||||||
t.cacheWriter.CloseWithError(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore errors writing to cache
|
|
||||||
if !t.pipeClosed {
|
|
||||||
_, cerr := t.cacheWriter.Write(p)
|
|
||||||
if cerr != nil {
|
|
||||||
t.pipeClosed = true
|
|
||||||
t.cacheWriter.CloseWithError(cerr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cacheMultiWriter(w1 io.Writer, w2 *io.PipeWriter) io.Writer {
|
|
||||||
return &multiWriter{backendWriter: w1, cacheWriter: w2}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writebackInProgress returns true if writeback commit is not complete
|
|
||||||
func writebackInProgress(m map[string]string) bool {
|
|
||||||
if v, ok := m[writeBackStatusHeader]; ok {
|
|
||||||
switch cacheCommitStatus(v) {
|
|
||||||
case CommitPending, CommitFailed:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of MinIO Object Storage stack
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetCacheControlOpts(t *testing.T) {
|
|
||||||
expiry, _ := time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
cacheControlHeaderVal string
|
|
||||||
expiryHeaderVal time.Time
|
|
||||||
expectedCacheControl *cacheControl
|
|
||||||
expectedErr bool
|
|
||||||
}{
|
|
||||||
{"", timeSentinel, nil, false},
|
|
||||||
{"max-age=2592000, public", timeSentinel, &cacheControl{maxAge: 2592000, sMaxAge: 0, minFresh: 0, expiry: time.Time{}}, false},
|
|
||||||
{"max-age=2592000, no-store", timeSentinel, &cacheControl{maxAge: 2592000, sMaxAge: 0, noStore: true, minFresh: 0, expiry: time.Time{}}, false},
|
|
||||||
{"must-revalidate, max-age=600", timeSentinel, &cacheControl{maxAge: 600, sMaxAge: 0, minFresh: 0, expiry: time.Time{}}, false},
|
|
||||||
{"s-maxAge=2500, max-age=600", timeSentinel, &cacheControl{maxAge: 600, sMaxAge: 2500, minFresh: 0, expiry: time.Time{}}, false},
|
|
||||||
{"s-maxAge=2500, max-age=600", expiry, &cacheControl{maxAge: 600, sMaxAge: 2500, minFresh: 0, expiry: time.Date(2015, time.October, 21, 0o7, 28, 0o0, 0o0, time.UTC)}, false},
|
|
||||||
{"s-maxAge=2500, max-age=600s", timeSentinel, &cacheControl{maxAge: 600, sMaxAge: 2500, minFresh: 0, expiry: time.Time{}}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run("", func(t *testing.T) {
|
|
||||||
m := make(map[string]string)
|
|
||||||
m["cache-control"] = testCase.cacheControlHeaderVal
|
|
||||||
if !testCase.expiryHeaderVal.Equal(timeSentinel) {
|
|
||||||
m["expires"] = testCase.expiryHeaderVal.String()
|
|
||||||
}
|
|
||||||
c := cacheControlOpts(ObjectInfo{UserDefined: m, Expires: testCase.expiryHeaderVal})
|
|
||||||
if testCase.expectedErr && (c != nil) {
|
|
||||||
t.Errorf("expected err, got <nil>")
|
|
||||||
}
|
|
||||||
if !testCase.expectedErr && !reflect.DeepEqual(c, testCase.expectedCacheControl) {
|
|
||||||
t.Errorf("expected %v, got %v", testCase.expectedCacheControl, c)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsMetadataSame(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
m1 map[string]string
|
|
||||||
m2 map[string]string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{nil, nil, true},
|
|
||||||
{nil, map[string]string{}, false},
|
|
||||||
{map[string]string{"k": "v"}, map[string]string{"k": "v"}, true},
|
|
||||||
{map[string]string{"k": "v"}, map[string]string{"a": "b"}, false},
|
|
||||||
{map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "v1", "k2": "v1"}, false},
|
|
||||||
{map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "v1", "k2": "v2"}, true},
|
|
||||||
{map[string]string{"K1": "v1", "k2": "v2"}, map[string]string{"k1": "v1", "k2": "v2"}, false},
|
|
||||||
{map[string]string{"k1": "v1", "k2": "v2", "k3": "v3"}, map[string]string{"k1": "v1", "k2": "v2"}, false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
actual := isMetadataSame(testCase.m1, testCase.m2)
|
|
||||||
if testCase.expected != actual {
|
|
||||||
t.Errorf("test %d expected %v, got %v", i, testCase.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewFileScorer(t *testing.T) {
|
|
||||||
fs, err := newFileScorer(1000, time.Now().Unix(), 10)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(fs.fileNames()) != 0 {
|
|
||||||
t.Fatal("non zero files??")
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
fs.addFile("recent", now.Add(-time.Minute), 1000, 10)
|
|
||||||
fs.addFile("older", now.Add(-time.Hour), 1000, 10)
|
|
||||||
if !reflect.DeepEqual(fs.fileNames(), []string{"older"}) {
|
|
||||||
t.Fatal("unexpected file list", fs.queueString())
|
|
||||||
}
|
|
||||||
fs.reset()
|
|
||||||
fs.addFile("bigger", now.Add(-time.Minute), 2000, 10)
|
|
||||||
fs.addFile("recent", now.Add(-time.Minute), 1000, 10)
|
|
||||||
if !reflect.DeepEqual(fs.fileNames(), []string{"bigger"}) {
|
|
||||||
t.Fatal("unexpected file list", fs.queueString())
|
|
||||||
}
|
|
||||||
fs.reset()
|
|
||||||
fs.addFile("less", now.Add(-time.Minute), 1000, 5)
|
|
||||||
fs.addFile("recent", now.Add(-time.Minute), 1000, 10)
|
|
||||||
if !reflect.DeepEqual(fs.fileNames(), []string{"less"}) {
|
|
||||||
t.Fatal("unexpected file list", fs.queueString())
|
|
||||||
}
|
|
||||||
fs.reset()
|
|
||||||
fs.addFile("small", now.Add(-time.Minute), 200, 10)
|
|
||||||
fs.addFile("medium", now.Add(-time.Minute), 300, 10)
|
|
||||||
if !reflect.DeepEqual(fs.fileNames(), []string{"medium", "small"}) {
|
|
||||||
t.Fatal("unexpected file list", fs.queueString())
|
|
||||||
}
|
|
||||||
fs.addFile("large", now.Add(-time.Minute), 700, 10)
|
|
||||||
fs.addFile("xsmol", now.Add(-time.Minute), 7, 10)
|
|
||||||
if !reflect.DeepEqual(fs.fileNames(), []string{"large", "medium"}) {
|
|
||||||
t.Fatal("unexpected file list", fs.queueString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.reset()
|
|
||||||
fs.addFile("less", now.Add(-time.Minute), 500, 5)
|
|
||||||
fs.addFile("recent", now.Add(-time.Minute), 500, 10)
|
|
||||||
if !fs.adjustSaveBytes(-500) {
|
|
||||||
t.Fatal("we should still need more bytes, got false")
|
|
||||||
}
|
|
||||||
// We should only need 500 bytes now.
|
|
||||||
if !reflect.DeepEqual(fs.fileNames(), []string{"less"}) {
|
|
||||||
t.Fatal("unexpected file list", fs.queueString())
|
|
||||||
}
|
|
||||||
if fs.adjustSaveBytes(-500) {
|
|
||||||
t.Fatal("we shouldn't need any more bytes, got true")
|
|
||||||
}
|
|
||||||
fs, err = newFileScorer(1000, time.Now().Unix(), 10)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fs.addFile("bigger", now.Add(-time.Minute), 50, 10)
|
|
||||||
// sorting should be consistent after adjusting savebytes.
|
|
||||||
fs.adjustSaveBytes(-800)
|
|
||||||
fs.addFile("smaller", now.Add(-time.Minute), 40, 10)
|
|
||||||
if !reflect.DeepEqual(fs.fileNames(), []string{"bigger", "smaller"}) {
|
|
||||||
t.Fatal("unexpected file list", fs.queueString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBytesToClear(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
total int64
|
|
||||||
free int64
|
|
||||||
quotaPct uint64
|
|
||||||
watermarkLow uint64
|
|
||||||
watermarkHigh uint64
|
|
||||||
expected uint64
|
|
||||||
}{
|
|
||||||
{total: 1000, free: 800, quotaPct: 40, watermarkLow: 90, watermarkHigh: 90, expected: 0},
|
|
||||||
{total: 1000, free: 200, quotaPct: 40, watermarkLow: 90, watermarkHigh: 90, expected: 400},
|
|
||||||
{total: 1000, free: 400, quotaPct: 40, watermarkLow: 90, watermarkHigh: 90, expected: 240},
|
|
||||||
{total: 1000, free: 600, quotaPct: 40, watermarkLow: 90, watermarkHigh: 90, expected: 40},
|
|
||||||
{total: 1000, free: 600, quotaPct: 40, watermarkLow: 70, watermarkHigh: 70, expected: 120},
|
|
||||||
{total: 1000, free: 1000, quotaPct: 90, watermarkLow: 70, watermarkHigh: 70, expected: 0},
|
|
||||||
|
|
||||||
// High not yet reached..
|
|
||||||
{total: 1000, free: 250, quotaPct: 100, watermarkLow: 50, watermarkHigh: 90, expected: 0},
|
|
||||||
{total: 1000, free: 250, quotaPct: 100, watermarkLow: 50, watermarkHigh: 90, expected: 0},
|
|
||||||
}
|
|
||||||
for i, tc := range testCases {
|
|
||||||
toClear := bytesToClear(tc.total, tc.free, tc.quotaPct, tc.watermarkLow, tc.watermarkHigh)
|
|
||||||
if tc.expected != toClear {
|
|
||||||
t.Errorf("test %d expected %v, got %v", i, tc.expected, toClear)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user