mirror of
https://github.com/pgsty/minio.git
synced 2026-11-13 02:55:42 +03:00
Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20960b6a2d | |||
| 3bd3470d0b | |||
| ba39ed9af7 | |||
| 62e6dc950d | |||
| 5a5046ce45 | |||
| ad04afe381 | |||
| ba9f0f2480 | |||
| d06b63d056 | |||
| 7ce28c3b1d | |||
| e3ac4035b9 | |||
| d21b6daa49 | |||
| 76ebb16688 | |||
| 55aa431578 | |||
| 614981e566 | |||
| b8b956a05d | |||
| d2eed44c78 | |||
| 789cbc6fb2 | |||
| 0662c90b5c | |||
| a2cab02554 | |||
| 6c7a21df6b | |||
| f933b0b708 | |||
| 9f305273a7 | |||
| cbd9efcb43 | |||
| 29a25a538f | |||
| 2dd8faaedc | |||
| f00187033d | |||
| c5141d65ac | |||
| 069c4015cd | |||
| 2f6e03fb60 | |||
| 0fbb945e13 | |||
| b94dd835c9 | |||
| 44fc707423 | |||
| 5aaef9790f | |||
| 7edc352d23 | |||
| 850a84b08a | |||
| 4148754ce0 | |||
| 2107722829 | |||
| d326ba52e9 | |||
| 91e1487de4 | |||
| 5ffb2a9605 | |||
| 17fe91d6d1 | |||
| 90a9f2dd70 | |||
| d5e48cfd65 | |||
| d274566463 | |||
| 39ac720826 | |||
| 21b6204692 | |||
| d98faeb26a | |||
| 0a63dc199c | |||
| 3ba857dfa1 | |||
| a8554c4022 | |||
| ba54b39c02 | |||
| 2a75225569 | |||
| e72429c79c | |||
| c5b3f5553f | |||
| d3ae0aaad3 | |||
| d67bccf861 | |||
| 1277ad69a6 | |||
| 8f93e81afb | |||
| 4af31e654b | |||
| aad50579ba | |||
| 38d059b0ae | |||
| bd4eeb4522 | |||
| 03e3493288 | |||
| 64baedf5a4 | |||
| 2f64d5f77e | |||
| f79a4ef4d0 | |||
| 2d53854b19 | |||
| e5c83535af | |||
| c904ef966e | |||
| 8f266e0772 | |||
| e0fe7cc391 | |||
| 9d20dec56a | |||
| 597a785253 | |||
| 7d75b1e758 | |||
| 5f78691fcf | |||
| a591e06ae5 | |||
| 443c93c634 | |||
| 5659cddc84 | |||
| 2a03a34bde | |||
| 1654a9b7e6 | |||
| 673a521711 | |||
| 2e23076688 | |||
| b92ac55250 | |||
| 7981509cc8 | |||
| 6d5bc045bc | |||
| d38e020b29 | |||
| 7d29030292 | |||
| 7c7650b7c3 | |||
| ca80eced24 | |||
| d0e0b81d8e | |||
| 391baa1c9a | |||
| ae14681c3e | |||
| 4d698841f4 | |||
| 9906b3ade9 | |||
| bf1769d3e0 | |||
| 63e1ad9f29 | |||
| 2c7bcee53f | |||
| 1fd90c93ff | |||
| e947a844c9 | |||
| 4e2d39293a | |||
| 1228d6bf1a | |||
| fc4561c64c | |||
| 3b7747b42b | |||
| e432e79324 | |||
| 08d74819b6 | |||
| aa3fde1784 | |||
| 0b3eb7f218 | |||
| 69c9496c71 | |||
| b792b36495 | |||
| d3db7d31a3 | |||
| c05ca63158 | |||
| 6d3e0c7db6 | |||
| 0e59e50b39 | |||
| d4b391de1b | |||
| de4d3dac00 | |||
| 534e7161df | |||
| 9b219cd646 | |||
| 3bab4822f3 | |||
| 3c5f2d8916 | |||
| 5808190398 | |||
| b2a82248b1 | |||
| 4e5fcca8b9 | |||
| c36eaedb93 | |||
| 7752b03add | |||
| 01bfc78535 | |||
| 074d70112d | |||
| e8d14c0d90 | |||
| 60d7e8143a | |||
| 9667a170de | |||
| abae30f9e1 | |||
| f9311bc9d1 | |||
| b598402738 | |||
| bd026b913f | |||
| 72ff69d9bb | |||
| f30417d9a8 | |||
| 47a4ad3cd7 | |||
| 2f7a10ab31 | |||
| b534dc69ab | |||
| 7b7d2ea7d4 | |||
| e00de1c302 | |||
| 3549e583a6 | |||
| f5e3eedf34 | |||
| 519dbfebf6 | |||
| 9a267f9270 | |||
| 67bd71b7a5 | |||
| ec49fff583 | |||
| 8b660e18f2 | |||
| 981497799a | |||
| b9bdc17465 | |||
| b413ff9fdb | |||
| 6a15580817 | |||
| 39633a5581 | |||
| 1e83f15e2f | |||
| 888d2bb1d8 | |||
| 847ee5ac45 | |||
| 9a9a49aa84 | |||
| a03ca80269 | |||
| 523bd769f1 | |||
| 8ff70ea5a9 | |||
| da3e7747ca | |||
| 4afb59e63f | |||
| 1526e7ece3 | |||
| 6c07bfee8a | |||
| 446c760820 | |||
| 04f92f1291 | |||
| 4a60a7794d | |||
| e5b16adb1c | |||
| 402a3ac719 | |||
| f3d61c51fc | |||
| 0cde17ae5d | |||
| 8c1bba681b | |||
| dbfb5e797b | |||
| 08ff702434 | |||
| 0e2148264a | |||
| a75f42344b | |||
| 7926401cbd | |||
| 8161411c5d | |||
| f64dea2aac | |||
| 6579304d8c | |||
| 6bb10a81a6 | |||
| 3cf8a7c888 | |||
| 2e38bb5175 |
@@ -4,7 +4,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: FIPS Build Test
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Healing Functional Tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Linters and Tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,8 +20,8 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, Windows]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
@@ -30,14 +29,15 @@ jobs:
|
|||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'Windows'
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
run: |
|
run: |
|
||||||
|
Set-MpPreference -DisableRealtimeMonitoring $true
|
||||||
netsh int ipv4 set dynamicport tcp start=60000 num=61000
|
netsh int ipv4 set dynamicport tcp start=60000 num=61000
|
||||||
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
||||||
go test -v --timeout 50m ./...
|
go test -v --timeout 120m ./...
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Functional Tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Helm Chart linting
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -23,7 +22,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Helm
|
- name: Install Helm
|
||||||
uses: azure/setup-helm@v3
|
uses: azure/setup-helm@v4
|
||||||
|
|
||||||
- name: Run helm lint
|
- name: Run helm lint
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -62,7 +61,7 @@ jobs:
|
|||||||
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
||||||
# the tests.
|
# the tests.
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
ldap: ["", "localhost:389"]
|
ldap: ["", "localhost:389"]
|
||||||
etcd: ["", "http://localhost:2379"]
|
etcd: ["", "http://localhost:2379"]
|
||||||
openid: ["", "http://127.0.0.1:5556/dex"]
|
openid: ["", "http://127.0.0.1:5556/dex"]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -30,7 +29,7 @@ jobs:
|
|||||||
- name: setup-go-step
|
- name: setup-go-step
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.x
|
go-version: 1.22.x
|
||||||
|
|
||||||
- name: github sha short
|
- name: github sha short
|
||||||
id: vars
|
id: vars
|
||||||
@@ -56,6 +55,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
|
# FIXME: renable this back when we have a valid way to add deadlines for PUT()s (internode CreateFile)
|
||||||
|
# - name: resiliency
|
||||||
|
# run: |
|
||||||
|
# ${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "resiliency" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
- name: The job must cleanup
|
- name: The job must cleanup
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
|
command: server --console-address ":9001" http://minio{1...4}/rdata{1...2}
|
||||||
|
expose:
|
||||||
|
- "9000"
|
||||||
|
- "9001"
|
||||||
|
environment:
|
||||||
|
MINIO_CI_CD: "on"
|
||||||
|
MINIO_ROOT_USER: "minio"
|
||||||
|
MINIO_ROOT_PASSWORD: "minio123"
|
||||||
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
|
MINIO_DRIVE_MAX_TIMEOUT: "5s"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio1
|
||||||
|
volumes:
|
||||||
|
- rdata1-1:/rdata1
|
||||||
|
- rdata1-2:/rdata2
|
||||||
|
|
||||||
|
minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio2
|
||||||
|
volumes:
|
||||||
|
- rdata2-1:/rdata1
|
||||||
|
- rdata2-2:/rdata2
|
||||||
|
|
||||||
|
minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio3
|
||||||
|
volumes:
|
||||||
|
- rdata3-1:/rdata1
|
||||||
|
- rdata3-2:/rdata2
|
||||||
|
|
||||||
|
minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio4
|
||||||
|
volumes:
|
||||||
|
- rdata4-1:/rdata1
|
||||||
|
- rdata4-2:/rdata2
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-4-node.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- minio1
|
||||||
|
- minio2
|
||||||
|
- minio3
|
||||||
|
- minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
rdata1-1:
|
||||||
|
rdata1-2:
|
||||||
|
rdata2-1:
|
||||||
|
rdata2-2:
|
||||||
|
rdata3-1:
|
||||||
|
rdata3-2:
|
||||||
|
rdata4-1:
|
||||||
|
rdata4-2:
|
||||||
@@ -23,10 +23,9 @@ http {
|
|||||||
# include /etc/nginx/conf.d/*.conf;
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
upstream minio {
|
upstream minio {
|
||||||
server minio1:9000;
|
server minio1:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio2:9000;
|
server minio2:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio3:9000;
|
server minio3:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio4:9000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream console {
|
upstream console {
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ http {
|
|||||||
# include /etc/nginx/conf.d/*.conf;
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
upstream minio {
|
upstream minio {
|
||||||
server minio1:9000;
|
server minio1:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio2:9000;
|
server minio2:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio3:9000;
|
server minio3:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio4:9000;
|
server minio4:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio5:9000;
|
server minio5:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio6:9000;
|
server minio6:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio7:9000;
|
server minio7:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio8:9000;
|
server minio8:9000 max_fails=1 fail_timeout=10s;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream console {
|
upstream console {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ http {
|
|||||||
# include /etc/nginx/conf.d/*.conf;
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
upstream minio {
|
upstream minio {
|
||||||
server minio1:9000;
|
server minio1:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio2:9000;
|
server minio2:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio3:9000;
|
server minio3:9000 max_fails=1 fail_timeout=10s;
|
||||||
server minio4:9000;
|
server minio4:9000 max_fails=1 fail_timeout=10s;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream console {
|
upstream console {
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ if [ ! -f ./mc ]; then
|
|||||||
chmod +x mc
|
chmod +x mc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
go install -v github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
|
||||||
|
|
||||||
export RELEASE=RELEASE.2023-08-29T23-07-35Z
|
export RELEASE=RELEASE.2023-08-29T23-07-35Z
|
||||||
|
|
||||||
docker-compose -f docker-compose-site1.yaml up -d
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
@@ -45,10 +43,10 @@ sleep 30s
|
|||||||
|
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
s3-check-md5 -h
|
./s3-check-md5 -h
|
||||||
|
|
||||||
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
if [ $failed_count_site1 -ne 0 ]; then
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
echo "failed with multipart on site1 uploads"
|
echo "failed with multipart on site1 uploads"
|
||||||
@@ -64,8 +62,8 @@ fi
|
|||||||
|
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
## we do not need to fail here, since we are going to test
|
## we do not need to fail here, since we are going to test
|
||||||
## upgrading to master, healing and being able to recover
|
## upgrading to master, healing and being able to recover
|
||||||
@@ -93,8 +91,8 @@ for i in $(seq 1 10); do
|
|||||||
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
|
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
|
||||||
done
|
done
|
||||||
|
|
||||||
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
if [ $failed_count_site1 -ne 0 ]; then
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
echo "failed with multipart on site1 uploads"
|
echo "failed with multipart on site1 uploads"
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: MinIO advanced tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -22,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -42,6 +41,12 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-ilm
|
make test-ilm
|
||||||
|
|
||||||
|
- name: Test PBAC
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
make test-pbac
|
||||||
|
|
||||||
- name: Test Config File
|
- name: Test Config File
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Root lockdown tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ docker volume rm $(docker volume ls -f dangling=true) || true
|
|||||||
cd .github/workflows/mint
|
cd .github/workflows/mint
|
||||||
|
|
||||||
docker-compose -f minio-${MODE}.yaml up -d
|
docker-compose -f minio-${MODE}.yaml up -d
|
||||||
sleep 30s
|
sleep 1m
|
||||||
|
|
||||||
docker system prune -f || true
|
docker system prune -f || true
|
||||||
docker volume prune -f || true
|
docker volume prune -f || true
|
||||||
@@ -26,6 +26,9 @@ docker volume rm $(docker volume ls -q -f dangling=true) || true
|
|||||||
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2
|
||||||
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6
|
||||||
|
|
||||||
|
# Pause one node, to check that all S3 calls work while one node goes wrong
|
||||||
|
[ "${MODE}" == "resiliency" ] && docker-compose -f minio-${MODE}.yaml pause minio4
|
||||||
|
|
||||||
docker run --rm --net=mint_default \
|
docker run --rm --net=mint_default \
|
||||||
--name="mint-${MODE}-${JOB_NAME}" \
|
--name="mint-${MODE}-${JOB_NAME}" \
|
||||||
-e SERVER_ENDPOINT="nginx:9000" \
|
-e SERVER_ENDPOINT="nginx:9000" \
|
||||||
@@ -35,6 +38,18 @@ docker run --rm --net=mint_default \
|
|||||||
-e MINT_MODE="${MINT_MODE}" \
|
-e MINT_MODE="${MINT_MODE}" \
|
||||||
docker.io/minio/mint:edge
|
docker.io/minio/mint:edge
|
||||||
|
|
||||||
|
# FIXME: enable this after fixing aws-sdk-java-v2 tests
|
||||||
|
# # unpause the node, to check that all S3 calls work while one node goes wrong
|
||||||
|
# [ "${MODE}" == "resiliency" ] && docker-compose -f minio-${MODE}.yaml unpause minio4
|
||||||
|
# [ "${MODE}" == "resiliency" ] && docker run --rm --net=mint_default \
|
||||||
|
# --name="mint-${MODE}-${JOB_NAME}" \
|
||||||
|
# -e SERVER_ENDPOINT="nginx:9000" \
|
||||||
|
# -e ACCESS_KEY="${ACCESS_KEY}" \
|
||||||
|
# -e SECRET_KEY="${SECRET_KEY}" \
|
||||||
|
# -e ENABLE_HTTPS=0 \
|
||||||
|
# -e MINT_MODE="${MINT_MODE}" \
|
||||||
|
# docker.io/minio/mint:edge
|
||||||
|
|
||||||
docker-compose -f minio-${MODE}.yaml down || true
|
docker-compose -f minio-${MODE}.yaml down || true
|
||||||
sleep 10s
|
sleep 10s
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Shell formatting checks
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Upgrade old version tests
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.9
|
go-version: 1.22.4
|
||||||
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
|
||||||
|
|||||||
@@ -44,3 +44,12 @@ docs/debugging/pprofgoparser/pprofgoparser
|
|||||||
docs/debugging/reorder-disks/reorder-disks
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
docs/debugging/populate-hard-links/populate-hardlinks
|
docs/debugging/populate-hard-links/populate-hardlinks
|
||||||
docs/debugging/xattr/xattr
|
docs/debugging/xattr/xattr
|
||||||
|
hash-set
|
||||||
|
healing-bin
|
||||||
|
inspect
|
||||||
|
pprofgoparser
|
||||||
|
reorder-disks
|
||||||
|
s3-check-md5
|
||||||
|
s3-verify
|
||||||
|
xattr
|
||||||
|
xl-meta
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
|||||||
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
||||||
|
|
||||||
check: test
|
check: test
|
||||||
test: verifiers build build-debugging ## builds minio, runs linters, tests
|
test: verifiers build ## builds minio, runs linters, tests
|
||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue ./...
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue,dev ./...
|
||||||
|
|
||||||
test-root-disable: install-race
|
test-root-disable: install-race
|
||||||
@echo "Running minio root lockdown tests"
|
@echo "Running minio root lockdown tests"
|
||||||
@@ -57,12 +57,17 @@ test-ilm: install-race
|
|||||||
@echo "Running ILM tests"
|
@echo "Running ILM tests"
|
||||||
@env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh
|
@env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh
|
||||||
|
|
||||||
|
test-pbac: install-race
|
||||||
|
@echo "Running bucket policies tests"
|
||||||
|
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh
|
||||||
|
|
||||||
test-decom: install-race
|
test-decom: install-race
|
||||||
@echo "Running minio decom tests"
|
@echo "Running minio decom tests"
|
||||||
@env bash $(PWD)/docs/distributed/decom.sh
|
@env bash $(PWD)/docs/distributed/decom.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-encrypted-kes.sh
|
||||||
|
|
||||||
test-versioning: install-race
|
test-versioning: install-race
|
||||||
@echo "Running minio versioning tests"
|
@echo "Running minio versioning tests"
|
||||||
@@ -79,13 +84,13 @@ test-race: verifiers build ## builds minio, runs linters, tests (race)
|
|||||||
@echo "Running unit tests under -race"
|
@echo "Running unit tests under -race"
|
||||||
@(env bash $(PWD)/buildscripts/race.sh)
|
@(env bash $(PWD)/buildscripts/race.sh)
|
||||||
|
|
||||||
test-iam: build ## verify IAM (external IDP, etcd backends)
|
test-iam: install-race ## verify IAM (external IDP, etcd backends)
|
||||||
@echo "Running tests for IAM (external IDP, etcd backends)"
|
@echo "Running tests for IAM (external IDP, etcd backends)"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue -v -run TestIAM* ./cmd
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||||
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||||
|
|
||||||
test-iam-ldap-upgrade-import: build ## verify IAM (external LDAP IDP)
|
test-iam-ldap-upgrade-import: install-race ## verify IAM (external LDAP IDP)
|
||||||
@echo "Running upgrade tests for IAM (LDAP backend)"
|
@echo "Running upgrade tests for IAM (LDAP backend)"
|
||||||
@env bash $(PWD)/buildscripts/minio-iam-ldap-upgrade-import-test.sh
|
@env bash $(PWD)/buildscripts/minio-iam-ldap-upgrade-import-test.sh
|
||||||
|
|
||||||
@@ -101,7 +106,10 @@ test-replication-3site:
|
|||||||
test-delete-replication:
|
test-delete-replication:
|
||||||
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||||
|
|
||||||
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
|
test-delete-marker-proxying:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/test_del_marker_proxying.sh)
|
||||||
|
|
||||||
|
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error test-delete-marker-proxying ## verify multi site replication
|
||||||
@echo "Running tests for replicating three sites"
|
@echo "Running tests for replicating three sites"
|
||||||
|
|
||||||
test-site-replication-ldap: install-race ## verify automatic site replication
|
test-site-replication-ldap: install-race ## verify automatic site replication
|
||||||
@@ -122,37 +130,32 @@ test-site-replication-minio: install-race ## verify automatic site replication
|
|||||||
@echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site"
|
@echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh)
|
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh)
|
||||||
|
|
||||||
verify: ## verify minio various setups
|
verify: install-race ## verify minio various setups
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||||
|
|
||||||
verify-healing: ## verify healing and replacing disks with minio binary
|
verify-healing: install-race ## verify healing and replacing disks with minio binary
|
||||||
@echo "Verify healing build with race"
|
@echo "Verify healing build with race"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
||||||
@(env bash $(PWD)/buildscripts/verify-healing-empty-erasure-set.sh)
|
@(env bash $(PWD)/buildscripts/verify-healing-empty-erasure-set.sh)
|
||||||
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
|
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
|
||||||
|
|
||||||
verify-healing-with-root-disks: ## verify healing root disks
|
verify-healing-with-root-disks: install-race ## verify healing root disks
|
||||||
@echo "Verify healing with root drives"
|
@echo "Verify healing with root drives"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh)
|
@(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh)
|
||||||
|
|
||||||
verify-healing-with-rewrite: ## verify healing to rewrite old xl.meta -> new xl.meta
|
verify-healing-with-rewrite: install-race ## verify healing to rewrite old xl.meta -> new xl.meta
|
||||||
@echo "Verify healing with rewrite"
|
@echo "Verify healing with rewrite"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/rewrite-old-new.sh)
|
@(env bash $(PWD)/buildscripts/rewrite-old-new.sh)
|
||||||
|
|
||||||
verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
|
verify-healing-inconsistent-versions: install-race ## verify resolving inconsistent versions
|
||||||
@echo "Verify resolving inconsistent versions build with race"
|
@echo "Verify resolving inconsistent versions build with race"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
||||||
|
|
||||||
build-debugging:
|
build-debugging:
|
||||||
@(env bash $(PWD)/docs/debugging/build.sh)
|
@(env bash $(PWD)/docs/debugging/build.sh)
|
||||||
|
|
||||||
build: checks ## builds minio to $(PWD)
|
build: checks build-debugging ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|
||||||
@@ -191,15 +194,15 @@ docker: build ## builds minio docker container
|
|||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
||||||
|
|
||||||
install-race: checks ## builds minio to $(PWD)
|
install-race: checks build-debugging ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary with -race to './minio'"
|
@echo "Building minio binary with -race to './minio'"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue,dev -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@echo "Installing minio binary with -race to '$(GOPATH)/bin/minio'"
|
@echo "Installing minio binary with -race to '$(GOPATH)/bin/minio'"
|
||||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
|
|
||||||
install: build ## builds minio and installs it to $GOPATH/bin.
|
install: build ## builds minio and installs it to $GOPATH/bin.
|
||||||
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
@echo "Installation successful. To learn more, try \"minio --help\"."
|
@echo "Installation successful. To learn more, try \"minio --help\"."
|
||||||
|
|
||||||
clean: ## cleanup all generated assets
|
clean: ## cleanup all generated assets
|
||||||
|
|||||||
@@ -210,10 +210,6 @@ For deployments behind a load balancer, proxy, or ingress rule where the MinIO h
|
|||||||
|
|
||||||
For example, consider a MinIO deployment behind a proxy `https://minio.example.net`, `https://console.minio.example.net` with rules for forwarding traffic on port :9000 and :9001 to MinIO and the MinIO Console respectively on the internal network. Set `MINIO_BROWSER_REDIRECT_URL` to `https://console.minio.example.net` to ensure the browser receives a valid reachable URL.
|
For example, consider a MinIO deployment behind a proxy `https://minio.example.net`, `https://console.minio.example.net` with rules for forwarding traffic on port :9000 and :9001 to MinIO and the MinIO Console respectively on the internal network. Set `MINIO_BROWSER_REDIRECT_URL` to `https://console.minio.example.net` to ensure the browser receives a valid reachable URL.
|
||||||
|
|
||||||
Similarly, if your TLS certificates do not have the IP SAN for the MinIO server host, the MinIO Console may fail to validate the connection to the server. Use the `MINIO_SERVER_URL` environment variable and specify the proxy-accessible hostname of the MinIO server to allow the Console to use the MinIO server API using the TLS certificate.
|
|
||||||
|
|
||||||
For example: `export MINIO_SERVER_URL="https://minio.example.net"`
|
|
||||||
|
|
||||||
| Dashboard | Creating a bucket |
|
| Dashboard | Creating a bucket |
|
||||||
| ------------- | ------------- |
|
| ------------- | ------------- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|||||||
Regular → Executable
@@ -32,6 +32,7 @@ fi
|
|||||||
set +e
|
set +e
|
||||||
|
|
||||||
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
|
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
|
||||||
|
./mc ready minioadm
|
||||||
|
|
||||||
./mc ls minioadm/
|
./mc ls minioadm/
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ done
|
|||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
sleep 10
|
./mc ready minioadm/
|
||||||
|
|
||||||
./mc ls minioadm/
|
./mc ls minioadm/
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
@@ -81,11 +82,12 @@ minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data
|
|||||||
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
sleep 20s
|
|
||||||
|
|
||||||
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
|
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
|
||||||
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
|
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc ready sitea
|
||||||
|
./mc ready siteb
|
||||||
|
|
||||||
./mc admin replicate add sitea siteb
|
./mc admin replicate add sitea siteb
|
||||||
|
|
||||||
./mc admin user add sitea foobar foo12345
|
./mc admin user add sitea foobar foo12345
|
||||||
@@ -109,11 +111,12 @@ minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data
|
|||||||
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
sleep 20s
|
|
||||||
|
|
||||||
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
|
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
|
||||||
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
|
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc ready sitea
|
||||||
|
./mc ready siteb
|
||||||
|
|
||||||
./mc admin user add sitea foobar-admin foo12345
|
./mc admin user add sitea foobar-admin foo12345
|
||||||
|
|
||||||
sleep 2s
|
sleep 2s
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ create_iam_content_in_old_minio() {
|
|||||||
|
|
||||||
set -x
|
set -x
|
||||||
mc alias set old-minio http://localhost:9000 minioadmin minioadmin
|
mc alias set old-minio http://localhost:9000 minioadmin minioadmin
|
||||||
|
mc ready old-minio
|
||||||
mc idp ldap add old-minio \
|
mc idp ldap add old-minio \
|
||||||
server_addr=localhost:1389 \
|
server_addr=localhost:1389 \
|
||||||
server_insecure=on \
|
server_insecure=on \
|
||||||
@@ -87,6 +88,7 @@ import_iam_content_in_new_minio() {
|
|||||||
set -x
|
set -x
|
||||||
mc alias set new-minio http://localhost:9000 minioadmin minioadmin
|
mc alias set new-minio http://localhost:9000 minioadmin minioadmin
|
||||||
echo "BEFORE IMPORT mappings:"
|
echo "BEFORE IMPORT mappings:"
|
||||||
|
mc ready new-minio
|
||||||
mc idp ldap policy entities new-minio
|
mc idp ldap policy entities new-minio
|
||||||
mc admin cluster iam import new-minio ./old-minio-iam-info.zip
|
mc admin cluster iam import new-minio ./old-minio-iam-info.zip
|
||||||
echo "AFTER IMPORT mappings:"
|
echo "AFTER IMPORT mappings:"
|
||||||
|
|||||||
Regular → Executable
+18
-4
@@ -4,10 +4,22 @@ trap 'cleanup $LINENO' ERR
|
|||||||
|
|
||||||
# shellcheck disable=SC2120
|
# shellcheck disable=SC2120
|
||||||
cleanup() {
|
cleanup() {
|
||||||
MINIO_VERSION=dev docker-compose \
|
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose \
|
||||||
-f "buildscripts/upgrade-tests/compose.yml" \
|
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||||
rm -s -f
|
down || true
|
||||||
|
|
||||||
|
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose \
|
||||||
|
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||||
|
rm || true
|
||||||
|
|
||||||
|
for volume in $(docker volume ls -q | grep upgrade); do
|
||||||
|
docker volume rm ${volume} || true
|
||||||
|
done
|
||||||
|
|
||||||
docker volume prune -f
|
docker volume prune -f
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_checksum_after_heal() {
|
verify_checksum_after_heal() {
|
||||||
@@ -60,6 +72,8 @@ __init__() {
|
|||||||
go install github.com/docker/compose/v2/cmd@latest
|
go install github.com/docker/compose/v2/cmd@latest
|
||||||
mv -v /tmp/gopath/bin/cmd /tmp/gopath/bin/docker-compose
|
mv -v /tmp/gopath/bin/cmd /tmp/gopath/bin/docker-compose
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
TAG=minio/minio:dev make docker
|
TAG=minio/minio:dev make docker
|
||||||
|
|
||||||
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
||||||
@@ -77,11 +91,11 @@ __init__() {
|
|||||||
|
|
||||||
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
|
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
|
||||||
|
|
||||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
|
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
|
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
|
||||||
|
|
||||||
add_alias
|
add_alias
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ function verify_rewrite() {
|
|||||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
pid=$!
|
pid=$!
|
||||||
disown $pid
|
disown $pid
|
||||||
sleep 10
|
|
||||||
|
"${WORK_DIR}/mc" ready minio/
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
@@ -77,7 +78,8 @@ function verify_rewrite() {
|
|||||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
pid=$!
|
pid=$!
|
||||||
disown $pid
|
disown $pid
|
||||||
sleep 10
|
|
||||||
|
"${WORK_DIR}/mc" ready minio/
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
@@ -87,14 +89,12 @@ function verify_rewrite() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
go install -v github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
if ! ./s3-check-md5 \
|
||||||
|
|
||||||
if ! s3-check-md5 \
|
|
||||||
-debug \
|
-debug \
|
||||||
-versions \
|
-versions \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
-secret-key minio123 \
|
-secret-key minio123 \
|
||||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
-endpoint "http://127.0.0.1:${start_port}/" 2>&1 | grep INTACT; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
cat "${WORK_DIR}/server1.log"
|
cat "${WORK_DIR}/server1.log"
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
@@ -114,7 +114,7 @@ function verify_rewrite() {
|
|||||||
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
if ! s3-check-md5 \
|
if ! ./s3-check-md5 \
|
||||||
-debug \
|
-debug \
|
||||||
-versions \
|
-versions \
|
||||||
-access-key minio \
|
-access-key minio \
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
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:${MINIO_VERSION}
|
image: minio/minio:${MINIO_VERSION}
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ WORK_DIR="$PWD/.verify-$RANDOM"
|
|||||||
export MINT_MODE=core
|
export MINT_MODE=core
|
||||||
export MINT_DATA_DIR="$WORK_DIR/data"
|
export MINT_DATA_DIR="$WORK_DIR/data"
|
||||||
export SERVER_ENDPOINT="127.0.0.1:9000"
|
export SERVER_ENDPOINT="127.0.0.1:9000"
|
||||||
|
export MC_HOST_verify="http://minio:minio123@${SERVER_ENDPOINT}/"
|
||||||
|
export MC_HOST_verify_ipv6="http://minio:minio123@[::1]:9000/"
|
||||||
export ACCESS_KEY="minio"
|
export ACCESS_KEY="minio"
|
||||||
export SECRET_KEY="minio123"
|
export SECRET_KEY="minio123"
|
||||||
export ENABLE_HTTPS=0
|
export ENABLE_HTTPS=0
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
export GOGC=25
|
export GOGC=25
|
||||||
export ENABLE_ADMIN=1
|
export ENABLE_ADMIN=1
|
||||||
|
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
@@ -36,18 +37,21 @@ function start_minio_fs() {
|
|||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
||||||
sleep 10
|
|
||||||
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure() {
|
function start_minio_erasure() {
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
||||||
sleep 15
|
|
||||||
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure_sets() {
|
function start_minio_erasure_sets() {
|
||||||
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||||
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||||
sleep 15
|
|
||||||
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets() {
|
function start_minio_pool_erasure_sets() {
|
||||||
@@ -57,7 +61,7 @@ function start_minio_pool_erasure_sets() {
|
|||||||
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets_ipv6() {
|
function start_minio_pool_erasure_sets_ipv6() {
|
||||||
@@ -67,7 +71,7 @@ function start_minio_pool_erasure_sets_ipv6() {
|
|||||||
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify_ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_dist_erasure() {
|
function start_minio_dist_erasure() {
|
||||||
@@ -78,7 +82,7 @@ function start_minio_dist_erasure() {
|
|||||||
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||||
done
|
done
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_fs() {
|
function run_test_fs() {
|
||||||
@@ -222,7 +226,7 @@ function __init__() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
(cd "${MC_BUILD_DIR}" && go build -o "${WORK_DIR}/mc")
|
||||||
|
|
||||||
# remove mc source.
|
# remove mc source.
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function start_minio_3_node() {
|
|||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
start_port=$2
|
start_port=$1
|
||||||
args=""
|
args=""
|
||||||
for i in $(seq 1 3); do
|
for i in $(seq 1 3); do
|
||||||
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
|
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
|
||||||
@@ -37,7 +37,40 @@ function start_minio_3_node() {
|
|||||||
pid3=$!
|
pid3=$!
|
||||||
disown $pid3
|
disown $pid3
|
||||||
|
|
||||||
sleep "$1"
|
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
|
||||||
|
|
||||||
|
/tmp/mc ready myminio
|
||||||
|
|
||||||
|
# Wait for all drives to be online and formatted
|
||||||
|
while [ $(/tmp/mc admin info --json myminio | jq '.info.servers[].drives[].state | select(. != "ok")' | wc -l) -gt 0 ]; do sleep 1; done
|
||||||
|
# Wait for all drives to be healed
|
||||||
|
while [ $(/tmp/mc admin info --json myminio | jq '.info.servers[].drives[].healing | select(. != null) | select(. == true)' | wc -l) -gt 0 ]; do sleep 1; done
|
||||||
|
|
||||||
|
# Wait for Status: in MinIO output
|
||||||
|
while true; do
|
||||||
|
rv=$(check_online)
|
||||||
|
if [ "$rv" != "1" ]; then
|
||||||
|
# success
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we should retry
|
||||||
|
retry=$((retry + 1))
|
||||||
|
if [ $retry -le 20 ]; then
|
||||||
|
sleep 5
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Failure
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
pkill -9 minio
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
done
|
||||||
|
|
||||||
if ! ps -p $pid1 1>&2 >/dev/null; then
|
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
@@ -89,7 +122,7 @@ function check_online() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function purge() {
|
function purge() {
|
||||||
rm -rf "$1"
|
echo rm -rf "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
function __init__() {
|
function __init__() {
|
||||||
@@ -99,10 +132,15 @@ function __init__() {
|
|||||||
|
|
||||||
## version is purposefully set to '3' for minio to migrate configuration file
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
||||||
|
|
||||||
|
if [ ! -f /tmp/mc ]; then
|
||||||
|
wget --quiet -O /tmp/mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x /tmp/mc
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function perform_test() {
|
function perform_test() {
|
||||||
start_minio_3_node 120 $2
|
start_minio_3_node $2
|
||||||
|
|
||||||
echo "Testing Distributed Erasure setup healing of drives"
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
||||||
@@ -110,19 +148,7 @@ function perform_test() {
|
|||||||
rm -rf ${WORK_DIR}/${1}/*/
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
start_minio_3_node 120 $2
|
start_minio_3_node $2
|
||||||
|
|
||||||
rv=$(check_online)
|
|
||||||
if [ "$rv" == "1" ]; then
|
|
||||||
for i in $(seq 1 3); do
|
|
||||||
echo "server$i log:"
|
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
|
||||||
done
|
|
||||||
pkill -9 minio
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
|||||||
GOPATH=/tmp/gopath
|
GOPATH=/tmp/gopath
|
||||||
|
|
||||||
function start_minio_3_node() {
|
function start_minio_3_node() {
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
rm "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
|
||||||
export MINIO_ROOT_USER=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
@@ -22,7 +26,7 @@ function start_minio_3_node() {
|
|||||||
|
|
||||||
first_time=$(find ${WORK_DIR}/ | grep format.json | wc -l)
|
first_time=$(find ${WORK_DIR}/ | grep format.json | wc -l)
|
||||||
|
|
||||||
start_port=$2
|
start_port=$1
|
||||||
args=""
|
args=""
|
||||||
for d in $(seq 1 3 5); do
|
for d in $(seq 1 3 5); do
|
||||||
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
|
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
|
||||||
@@ -42,9 +46,11 @@ function start_minio_3_node() {
|
|||||||
pid3=$!
|
pid3=$!
|
||||||
disown $pid3
|
disown $pid3
|
||||||
|
|
||||||
sleep "$1"
|
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
|
||||||
|
/tmp/mc ready myminio
|
||||||
|
|
||||||
[ ${first_time} -eq 0 ] && upload_objects $start_port
|
[ ${first_time} -eq 0 ] && upload_objects
|
||||||
|
[ ${first_time} -ne 0 ] && sleep 120
|
||||||
|
|
||||||
if ! ps -p $pid1 1>&2 >/dev/null; then
|
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
@@ -127,10 +133,6 @@ function __init__() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function upload_objects() {
|
function upload_objects() {
|
||||||
start_port=$1
|
|
||||||
|
|
||||||
/tmp/mc alias set myminio http://127.0.0.1:$((start_port + 1)) minio minio123 --api=s3v4
|
|
||||||
/tmp/mc ready myminio
|
|
||||||
/tmp/mc mb myminio/testbucket/
|
/tmp/mc mb myminio/testbucket/
|
||||||
for ((i = 0; i < 20; i++)); do
|
for ((i = 0; i < 20; i++)); do
|
||||||
echo "my content" | /tmp/mc pipe myminio/testbucket/file-$i
|
echo "my content" | /tmp/mc pipe myminio/testbucket/file-$i
|
||||||
@@ -140,7 +142,7 @@ function upload_objects() {
|
|||||||
function perform_test() {
|
function perform_test() {
|
||||||
start_port=$2
|
start_port=$2
|
||||||
|
|
||||||
start_minio_3_node 120 $start_port
|
start_minio_3_node $start_port
|
||||||
|
|
||||||
echo "Testing Distributed Erasure setup healing of drives"
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
echo "Remove the contents of the disks belonging to '${1}' node"
|
echo "Remove the contents of the disks belonging to '${1}' node"
|
||||||
@@ -148,7 +150,7 @@ function perform_test() {
|
|||||||
rm -rf ${WORK_DIR}/${1}/*/
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
start_minio_3_node 120 $start_port
|
start_minio_3_node $start_port
|
||||||
|
|
||||||
check_heal ${1}
|
check_heal ${1}
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy access control
|
// Data types used for returning dummy access control
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -735,7 +735,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf("An Object Lock configuration is present on this bucket, so the versioning state cannot be suspended."))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("An Object Lock configuration is present on this bucket, so the versioning state cannot be suspended."))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err := getReplicationConfig(ctx, bucket); err == nil && v.Suspended() {
|
if rcfg, _ := getReplicationConfig(ctx, bucket); rcfg != nil && v.Suspended() {
|
||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf("A replication configuration is present on this bucket, so the versioning state cannot be suspended."))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("A replication configuration is present on this bucket, so the versioning state cannot be suspended."))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -783,7 +783,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
switch fileName {
|
switch fileName {
|
||||||
case bucketNotificationConfig:
|
case bucketNotificationConfig:
|
||||||
config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region, globalEventNotifier.targetList)
|
config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region(), globalEventNotifier.targetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
|
||||||
continue
|
continue
|
||||||
@@ -837,9 +837,13 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
rcfg, err := globalBucketObjectLockSys.Get(bucket)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Validate the received bucket policy document
|
// Validate the received bucket policy document
|
||||||
if err = bucketLifecycle.Validate(); err != nil {
|
if err = bucketLifecycle.Validate(rcfg); err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -874,8 +878,10 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
kmsKey := encConfig.KeyID()
|
kmsKey := encConfig.KeyID()
|
||||||
if kmsKey != "" {
|
if kmsKey != "" {
|
||||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
_, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{
|
||||||
_, err := GlobalKMS.GenerateKey(ctx, kmsKey, kmsContext)
|
Name: kmsKey,
|
||||||
|
AssociatedData: kms.Context{"MinIO admin API": "ServerInfoHandler"}, // Context for a test key operation
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, kes.ErrKeyNotFound) {
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
rpt.SetStatus(bucket, fileName, errKMSKeyNotFound)
|
rpt.SetStatus(bucket, fileName, errKMSKeyNotFound)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validateAdminReq will validate request against and return whether it is allowed.
|
// validateAdminReq will validate request against and return whether it is allowed.
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/subnet"
|
"github.com/minio/minio/internal/config/subnet"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ import (
|
|||||||
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/ldap"
|
"github.com/minio/pkg/v3/ldap"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -27,7 +28,8 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
xldap "github.com/minio/pkg/v3/ldap"
|
||||||
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
||||||
@@ -236,12 +238,12 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
targetGroups = requestorGroups
|
targetGroups = requestorGroups
|
||||||
|
|
||||||
// Deny if the target user is not LDAP
|
// Deny if the target user is not LDAP
|
||||||
foundLDAPDN, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(targetUser)
|
foundResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if foundLDAPDN == "" {
|
if foundResult == nil {
|
||||||
err := errors.New("Specified user does not exist on LDAP server")
|
err := errors.New("Specified user does not exist on LDAP server")
|
||||||
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
||||||
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
||||||
@@ -264,7 +266,8 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
isDN := globalIAMSys.LDAPConfig.ParsesAsDN(targetUser)
|
isDN := globalIAMSys.LDAPConfig.ParsesAsDN(targetUser)
|
||||||
opts.claims[ldapUserN] = targetUser // simple username
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
var lookupResult *xldap.DNSearchResult
|
||||||
|
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if not found, check if DN
|
// if not found, check if DN
|
||||||
if strings.Contains(err.Error(), "User DN not found for:") {
|
if strings.Contains(err.Error(), "User DN not found for:") {
|
||||||
@@ -278,7 +281,26 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
targetUser = lookupResult.NormDN
|
||||||
opts.claims[ldapUser] = targetUser // DN
|
opts.claims[ldapUser] = targetUser // DN
|
||||||
|
opts.claims[ldapActualUser] = lookupResult.ActualDN
|
||||||
|
|
||||||
|
// Check if this user or their groups have a policy applied.
|
||||||
|
ldapPolicies, err := globalIAMSys.PolicyDBGet(targetUser, targetGroups...)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ldapPolicies) == 0 {
|
||||||
|
err = fmt.Errorf("No policy set for user `%s` or any of their groups: `%s`", opts.claims[ldapActualUser], strings.Join(targetGroups, "`,`"))
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add LDAP attributes that were looked up into the claims.
|
||||||
|
for attribKey, attribValue := range lookupResult.Attributes {
|
||||||
|
opts.claims[ldapAttribPrefix+attribKey] = attribValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||||
@@ -385,15 +407,16 @@ func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAccount, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN)
|
dnResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if targetAccount == "" {
|
if dnResult == nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
targetAccount := dnResult.NormDN
|
||||||
|
|
||||||
listType := r.Form.Get("listType")
|
listType := r.Form.Get("listType")
|
||||||
if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
|
if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
||||||
|
|||||||
+57
-19
@@ -29,6 +29,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/klauspost/compress/zip"
|
"github.com/klauspost/compress/zip"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
@@ -36,7 +37,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/cachevalue"
|
"github.com/minio/minio/internal/cachevalue"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
xldap "github.com/minio/pkg/v3/ldap"
|
||||||
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -473,6 +475,11 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utf8.ValidString(accessKey) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserValidUTF), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
checkDenyOnly := false
|
checkDenyOnly := false
|
||||||
if accessKey == cred.AccessKey {
|
if accessKey == cred.AccessKey {
|
||||||
// Check that there is no explicit deny - otherwise it's allowed
|
// Check that there is no explicit deny - otherwise it's allowed
|
||||||
@@ -700,12 +707,20 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||||
// query their groups:
|
// query their groups:
|
||||||
opts.claims[ldapUserN] = targetUser // simple username
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
var lookupResult *xldap.DNSearchResult
|
||||||
|
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
targetUser = lookupResult.NormDN
|
||||||
opts.claims[ldapUser] = targetUser // username DN
|
opts.claims[ldapUser] = targetUser // username DN
|
||||||
|
opts.claims[ldapActualUser] = lookupResult.ActualDN
|
||||||
|
|
||||||
|
// Add LDAP attributes that were looked up into the claims.
|
||||||
|
for attribKey, attribValue := range lookupResult.Attributes {
|
||||||
|
opts.claims[ldapAttribPrefix+attribKey] = attribValue
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: if not using LDAP, then internal IDP or open ID is
|
// NOTE: if not using LDAP, then internal IDP or open ID is
|
||||||
// being used - in the former, group info is enforced when
|
// being used - in the former, group info is enforced when
|
||||||
@@ -815,7 +830,11 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
condValues := getConditionValues(r, "", cred)
|
condValues := getConditionValues(r, "", cred)
|
||||||
addExpirationToCondValues(updateReq.NewExpiration, condValues)
|
err = addExpirationToCondValues(updateReq.NewExpiration, condValues)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Permission checks:
|
// Permission checks:
|
||||||
//
|
//
|
||||||
@@ -1039,8 +1058,13 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
|||||||
for _, svc := range serviceAccounts {
|
for _, svc := range serviceAccounts {
|
||||||
expiryTime := svc.Expiration
|
expiryTime := svc.Expiration
|
||||||
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
||||||
AccessKey: svc.AccessKey,
|
Description: svc.Description,
|
||||||
Expiration: &expiryTime,
|
ParentUser: svc.ParentUser,
|
||||||
|
Name: svc.Name,
|
||||||
|
AccountStatus: svc.Status,
|
||||||
|
AccessKey: svc.AccessKey,
|
||||||
|
ImpliedPolicy: svc.IsImpliedPolicy(),
|
||||||
|
Expiration: &expiryTime,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1212,9 +1236,9 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
bucketStorageCache.InitOnce(10*time.Second,
|
bucketStorageCache.InitOnce(10*time.Second,
|
||||||
cachevalue.Opts{ReturnLastGood: true, NoWait: true},
|
cachevalue.Opts{ReturnLastGood: true},
|
||||||
func() (DataUsageInfo, error) {
|
func(ctx context.Context) (DataUsageInfo, error) {
|
||||||
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
ctx, done := context.WithTimeout(ctx, 2*time.Second)
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
return loadDataUsageFromBackend(ctx, objectAPI)
|
return loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
@@ -1631,22 +1655,22 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
// form of the entityName (which will be an LDAP DN).
|
// form of the entityName (which will be an LDAP DN).
|
||||||
var err error
|
var err error
|
||||||
if isGroup {
|
if isGroup {
|
||||||
var foundGroupDN string
|
var foundGroupDN *xldap.DNSearchResult
|
||||||
var underBaseDN bool
|
var underBaseDN bool
|
||||||
if foundGroupDN, underBaseDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil {
|
if foundGroupDN, underBaseDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil {
|
||||||
iamLogIf(ctx, err)
|
iamLogIf(ctx, err)
|
||||||
} else if foundGroupDN == "" || !underBaseDN {
|
} else if foundGroupDN == nil || !underBaseDN {
|
||||||
err = errNoSuchGroup
|
err = errNoSuchGroup
|
||||||
}
|
}
|
||||||
entityName = foundGroupDN
|
entityName = foundGroupDN.NormDN
|
||||||
} else {
|
} else {
|
||||||
var foundUserDN string
|
var foundUserDN *xldap.DNSearchResult
|
||||||
if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil {
|
if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil {
|
||||||
iamLogIf(ctx, err)
|
iamLogIf(ctx, err)
|
||||||
} else if foundUserDN == "" {
|
} else if foundUserDN == nil {
|
||||||
err = errNoSuchUser
|
err = errNoSuchUser
|
||||||
}
|
}
|
||||||
entityName = foundUserDN
|
entityName = foundUserDN.NormDN
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -2439,11 +2463,16 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addExpirationToCondValues(exp *time.Time, condValues map[string][]string) {
|
func addExpirationToCondValues(exp *time.Time, condValues map[string][]string) error {
|
||||||
if exp == nil {
|
if exp == nil || exp.IsZero() || exp.Equal(timeSentinel) {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
condValues["DurationSeconds"] = []string{strconv.FormatInt(int64(exp.Sub(time.Now()).Seconds()), 10)}
|
dur := exp.Sub(time.Now())
|
||||||
|
if dur <= 0 {
|
||||||
|
return errors.New("unsupported expiration time")
|
||||||
|
}
|
||||||
|
condValues["DurationSeconds"] = []string{strconv.FormatInt(int64(dur.Seconds()), 10)}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
|
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
|
||||||
@@ -2471,6 +2500,12 @@ func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials
|
|||||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if createReq.Expiration != nil && !createReq.Expiration.IsZero() {
|
||||||
|
// truncate expiration at the second.
|
||||||
|
truncateTime := createReq.Expiration.Truncate(time.Second)
|
||||||
|
createReq.Expiration = &truncateTime
|
||||||
|
}
|
||||||
|
|
||||||
// service account access key cannot have space characters beginning and end of the string.
|
// service account access key cannot have space characters beginning and end of the string.
|
||||||
if hasSpaceBE(createReq.AccessKey) {
|
if hasSpaceBE(createReq.AccessKey) {
|
||||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||||
@@ -2502,7 +2537,10 @@ func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials
|
|||||||
}
|
}
|
||||||
|
|
||||||
condValues := getConditionValues(r, "", cred)
|
condValues := getConditionValues(r, "", cred)
|
||||||
addExpirationToCondValues(createReq.Expiration, condValues)
|
err = addExpirationToCondValues(createReq.Expiration, condValues)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if action is allowed if creating access key for another user
|
// Check if action is allowed if creating access key for another user
|
||||||
// Check if action is explicitly denied if for self
|
// Check if action is explicitly denied if for self
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/signer"
|
"github.com/minio/minio-go/v7/pkg/signer"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+119
-92
@@ -50,6 +50,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/madmin-go/v3/estream"
|
"github.com/minio/madmin-go/v3/estream"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/dsync"
|
"github.com/minio/minio/internal/dsync"
|
||||||
"github.com/minio/minio/internal/grid"
|
"github.com/minio/minio/internal/grid"
|
||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
@@ -58,9 +59,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/logger/message/log"
|
"github.com/minio/pkg/v3/logger/message/log"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/secure-io/sio-go"
|
"github.com/secure-io/sio-go"
|
||||||
"github.com/zeebo/xxh3"
|
"github.com/zeebo/xxh3"
|
||||||
)
|
)
|
||||||
@@ -1430,7 +1431,7 @@ func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmi
|
|||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Get heal status from other peers
|
// Get heal status from other peers
|
||||||
peersHealStates, nerrs := globalNotificationSys.BackgroundHealStatus()
|
peersHealStates, nerrs := globalNotificationSys.BackgroundHealStatus(ctx)
|
||||||
var errCount int
|
var errCount int
|
||||||
for _, nerr := range nerrs {
|
for _, nerr := range nerrs {
|
||||||
if nerr.Err != nil {
|
if nerr.Err != nil {
|
||||||
@@ -1576,7 +1577,8 @@ func (a adminAPIHandlers) ClientDevNull(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
// NetperfHandler - perform mesh style network throughput test
|
// NetperfHandler - perform mesh style network throughput test
|
||||||
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx, cancel := context.WithCancel(r.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -1597,6 +1599,15 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer nsLock.Unlock(lkctx)
|
defer nsLock.Unlock(lkctx)
|
||||||
|
|
||||||
|
// Freeze all incoming S3 API calls before running speedtest.
|
||||||
|
globalNotificationSys.ServiceFreeze(ctx, true)
|
||||||
|
|
||||||
|
// Unfreeze as soon as request context is canceled or when the function returns.
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
globalNotificationSys.ServiceFreeze(ctx, false)
|
||||||
|
}()
|
||||||
|
|
||||||
durationStr := r.Form.Get(peerRESTDuration)
|
durationStr := r.Form.Get(peerRESTDuration)
|
||||||
duration, err := time.ParseDuration(durationStr)
|
duration, err := time.ParseDuration(durationStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1617,18 +1628,73 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAllowedRWAccess(r *http.Request, cred auth.Credentials, bucketName string) (rd, wr bool) {
|
||||||
|
owner := cred.AccessKey == globalActiveCred.AccessKey
|
||||||
|
|
||||||
|
// Set prefix value for "s3:prefix" policy conditionals.
|
||||||
|
r.Header.Set("prefix", "")
|
||||||
|
|
||||||
|
// Set delimiter value for "s3:delimiter" policy conditionals.
|
||||||
|
r.Header.Set("delimiter", SlashSeparator)
|
||||||
|
|
||||||
|
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
||||||
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.GetObjectAction,
|
||||||
|
BucketName: bucketName,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
ObjectName: "",
|
||||||
|
Claims: cred.Claims,
|
||||||
|
}) {
|
||||||
|
rd = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.PutObjectAction,
|
||||||
|
BucketName: bucketName,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
ObjectName: "",
|
||||||
|
Claims: cred.Claims,
|
||||||
|
}) {
|
||||||
|
wr = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return rd, wr
|
||||||
|
}
|
||||||
|
return isAllowedAccess(bucketName)
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectSpeedTestHandler - reports maximum speed of a cluster by performing PUT and
|
// ObjectSpeedTestHandler - reports maximum speed of a cluster by performing PUT and
|
||||||
// GET operations on the server, supports auto tuning by default by automatically
|
// GET operations on the server, supports auto tuning by default by automatically
|
||||||
// increasing concurrency and stopping when we have reached the limits on the
|
// increasing concurrency and stopping when we have reached the limits on the
|
||||||
// system.
|
// system.
|
||||||
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx, cancel := context.WithCancel(r.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
objectAPI, creds := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !globalAPIConfig.permitRootAccess() {
|
||||||
|
rd, wr := isAllowedRWAccess(r, creds, globalObjectPerfBucket)
|
||||||
|
if !rd || !wr {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, AdminError{
|
||||||
|
Code: "XMinioSpeedtestInsufficientPermissions",
|
||||||
|
Message: fmt.Sprintf("%s does not have read and write access to '%s' bucket", creds.AccessKey,
|
||||||
|
globalObjectPerfBucket),
|
||||||
|
StatusCode: http.StatusForbidden,
|
||||||
|
}), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sizeStr := r.Form.Get(peerRESTSize)
|
sizeStr := r.Form.Get(peerRESTSize)
|
||||||
durationStr := r.Form.Get(peerRESTDuration)
|
durationStr := r.Form.Get(peerRESTDuration)
|
||||||
concurrentStr := r.Form.Get(peerRESTConcurrent)
|
concurrentStr := r.Form.Get(peerRESTConcurrent)
|
||||||
@@ -1637,6 +1703,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
|||||||
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"
|
enableSha256 := r.Form.Get("enableSha256") == "true"
|
||||||
|
enableMultipart := r.Form.Get("enableMultipart") == "true"
|
||||||
|
|
||||||
size, err := strconv.Atoi(sizeStr)
|
size, err := strconv.Atoi(sizeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1692,8 +1759,11 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
|||||||
// Freeze all incoming S3 API calls before running speedtest.
|
// Freeze all incoming S3 API calls before running speedtest.
|
||||||
globalNotificationSys.ServiceFreeze(ctx, true)
|
globalNotificationSys.ServiceFreeze(ctx, true)
|
||||||
|
|
||||||
// unfreeze all incoming S3 API calls after speedtest.
|
// Unfreeze as soon as request context is canceled or when the function returns.
|
||||||
defer globalNotificationSys.ServiceFreeze(ctx, false)
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
globalNotificationSys.ServiceFreeze(ctx, false)
|
||||||
|
}()
|
||||||
|
|
||||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||||
defer keepAliveTicker.Stop()
|
defer keepAliveTicker.Stop()
|
||||||
@@ -1707,6 +1777,8 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
|||||||
storageClass: storageClass,
|
storageClass: storageClass,
|
||||||
bucketName: customBucket,
|
bucketName: customBucket,
|
||||||
enableSha256: enableSha256,
|
enableSha256: enableSha256,
|
||||||
|
enableMultipart: enableMultipart,
|
||||||
|
creds: creds,
|
||||||
})
|
})
|
||||||
var prevResult madmin.SpeedTestResult
|
var prevResult madmin.SpeedTestResult
|
||||||
for {
|
for {
|
||||||
@@ -1793,7 +1865,8 @@ func validateObjPerfOptions(ctx context.Context, storageInfo madmin.StorageInfo,
|
|||||||
|
|
||||||
// DriveSpeedtestHandler - reports throughput of drives available in the cluster
|
// DriveSpeedtestHandler - reports throughput of drives available in the cluster
|
||||||
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx, cancel := context.WithCancel(r.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -1803,8 +1876,11 @@ func (a adminAPIHandlers) DriveSpeedtestHandler(w http.ResponseWriter, r *http.R
|
|||||||
// Freeze all incoming S3 API calls before running speedtest.
|
// Freeze all incoming S3 API calls before running speedtest.
|
||||||
globalNotificationSys.ServiceFreeze(ctx, true)
|
globalNotificationSys.ServiceFreeze(ctx, true)
|
||||||
|
|
||||||
// unfreeze all incoming S3 API calls after speedtest.
|
// Unfreeze as soon as request context is canceled or when the function returns.
|
||||||
defer globalNotificationSys.ServiceFreeze(ctx, false)
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
globalNotificationSys.ServiceFreeze(ctx, false)
|
||||||
|
}()
|
||||||
|
|
||||||
serial := r.Form.Get("serial") == "true"
|
serial := r.Form.Get("serial") == "true"
|
||||||
blockSizeStr := r.Form.Get("blocksize")
|
blockSizeStr := r.Form.Get("blocksize")
|
||||||
@@ -2097,7 +2173,9 @@ func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := GlobalKMS.CreateKey(ctx, r.Form.Get("key-id")); err != nil {
|
if err := GlobalKMS.CreateKey(ctx, &kms.CreateKeyRequest{
|
||||||
|
Name: r.Form.Get("key-id"),
|
||||||
|
}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2118,22 +2196,12 @@ func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, err := GlobalKMS.Stat(ctx)
|
stat, err := GlobalKMS.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resp, err := json.Marshal(stat)
|
||||||
status := madmin.KMSStatus{
|
|
||||||
Name: stat.Name,
|
|
||||||
DefaultKeyID: stat.DefaultKey,
|
|
||||||
Endpoints: make(map[string]madmin.ItemState, len(stat.Endpoints)),
|
|
||||||
}
|
|
||||||
for _, endpoint := range stat.Endpoints {
|
|
||||||
status.Endpoints[endpoint] = madmin.ItemOnline // TODO(aead): Implement an online check for mTLS
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := json.Marshal(status)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
@@ -2155,15 +2223,9 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, err := GlobalKMS.Stat(ctx)
|
|
||||||
if err != nil {
|
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keyID := r.Form.Get("key-id")
|
keyID := r.Form.Get("key-id")
|
||||||
if keyID == "" {
|
if keyID == "" {
|
||||||
keyID = stat.DefaultKey
|
keyID = GlobalKMS.DefaultKey
|
||||||
}
|
}
|
||||||
response := madmin.KMSKeyStatus{
|
response := madmin.KMSKeyStatus{
|
||||||
KeyID: keyID,
|
KeyID: keyID,
|
||||||
@@ -2171,7 +2233,10 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
kmsContext := kms.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
|
kmsContext := kms.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
|
||||||
// 1. Generate a new key using the KMS.
|
// 1. Generate a new key using the KMS.
|
||||||
key, err := GlobalKMS.GenerateKey(ctx, keyID, kmsContext)
|
key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{
|
||||||
|
Name: keyID,
|
||||||
|
AssociatedData: kmsContext,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.EncryptionErr = err.Error()
|
response.EncryptionErr = err.Error()
|
||||||
resp, err := json.Marshal(response)
|
resp, err := json.Marshal(response)
|
||||||
@@ -2184,7 +2249,11 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Verify that we can indeed decrypt the (encrypted) key
|
// 2. Verify that we can indeed decrypt the (encrypted) key
|
||||||
decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext)
|
decryptedKey, err := GlobalKMS.Decrypt(ctx, &kms.DecryptRequest{
|
||||||
|
Name: key.KeyID,
|
||||||
|
Ciphertext: key.Ciphertext,
|
||||||
|
AssociatedData: kmsContext,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.DecryptionErr = err.Error()
|
response.DecryptionErr = err.Error()
|
||||||
resp, err := json.Marshal(response)
|
resp, err := json.Marshal(response)
|
||||||
@@ -2278,7 +2347,7 @@ func getServerInfo(ctx context.Context, pools, metrics bool, r *http.Request) ma
|
|||||||
notifyTarget := fetchLambdaInfo()
|
notifyTarget := fetchLambdaInfo()
|
||||||
|
|
||||||
local := getLocalServerProperty(globalEndpoints, r, metrics)
|
local := getLocalServerProperty(globalEndpoints, r, metrics)
|
||||||
servers := globalNotificationSys.ServerInfo(metrics)
|
servers := globalNotificationSys.ServerInfo(ctx, metrics)
|
||||||
servers = append(servers, local)
|
servers = append(servers, local)
|
||||||
|
|
||||||
var poolsInfo map[int]map[int]madmin.ErasureSetInfo
|
var poolsInfo map[int]map[int]madmin.ErasureSetInfo
|
||||||
@@ -2337,8 +2406,7 @@ func getServerInfo(ctx context.Context, pools, metrics bool, r *http.Request) ma
|
|||||||
|
|
||||||
domain := globalDomainNames
|
domain := globalDomainNames
|
||||||
services := madmin.Services{
|
services := madmin.Services{
|
||||||
KMS: fetchKMSStatus(),
|
KMSStatus: fetchKMSStatus(ctx),
|
||||||
KMSStatus: fetchKMSStatusV2(ctx),
|
|
||||||
LDAP: ldap,
|
LDAP: ldap,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
Audit: audit,
|
Audit: audit,
|
||||||
@@ -2348,7 +2416,7 @@ func getServerInfo(ctx context.Context, pools, metrics bool, r *http.Request) ma
|
|||||||
return madmin.InfoMessage{
|
return madmin.InfoMessage{
|
||||||
Mode: string(mode),
|
Mode: string(mode),
|
||||||
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,
|
||||||
@@ -2948,66 +3016,25 @@ func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
|||||||
return notify
|
return notify
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchKMSStatus fetches KMS-related status information.
|
// fetchKMSStatus fetches KMS-related status information for all instances
|
||||||
func fetchKMSStatus() madmin.KMS {
|
func fetchKMSStatus(ctx context.Context) []madmin.KMS {
|
||||||
kmsStat := madmin.KMS{}
|
|
||||||
if GlobalKMS == nil {
|
|
||||||
kmsStat.Status = "disabled"
|
|
||||||
return kmsStat
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := GlobalKMS.Stat(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
kmsStat.Status = string(madmin.ItemOffline)
|
|
||||||
return kmsStat
|
|
||||||
}
|
|
||||||
if len(stat.Endpoints) == 0 {
|
|
||||||
kmsStat.Status = stat.Name
|
|
||||||
return kmsStat
|
|
||||||
}
|
|
||||||
kmsStat.Status = string(madmin.ItemOnline)
|
|
||||||
|
|
||||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
|
||||||
// 1. Generate a new key using the KMS.
|
|
||||||
key, err := GlobalKMS.GenerateKey(context.Background(), "", kmsContext)
|
|
||||||
if err != nil {
|
|
||||||
kmsStat.Encrypt = fmt.Sprintf("Encryption failed: %v", err)
|
|
||||||
} else {
|
|
||||||
kmsStat.Encrypt = "success"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Verify that we can indeed decrypt the (encrypted) key
|
|
||||||
decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext)
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
kmsStat.Decrypt = fmt.Sprintf("Decryption failed: %v", err)
|
|
||||||
case subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1:
|
|
||||||
kmsStat.Decrypt = "Decryption failed: decrypted key does not match generated key"
|
|
||||||
default:
|
|
||||||
kmsStat.Decrypt = "success"
|
|
||||||
}
|
|
||||||
return kmsStat
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchKMSStatusV2 fetches KMS-related status information for all instances
|
|
||||||
func fetchKMSStatusV2(ctx context.Context) []madmin.KMS {
|
|
||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
return []madmin.KMS{}
|
return []madmin.KMS{}
|
||||||
}
|
}
|
||||||
|
|
||||||
results := GlobalKMS.Verify(ctx)
|
stat, err := GlobalKMS.Status(ctx)
|
||||||
|
if err != nil {
|
||||||
stats := []madmin.KMS{}
|
kmsLogIf(ctx, err, "failed to fetch KMS status information")
|
||||||
for _, result := range results {
|
return []madmin.KMS{}
|
||||||
stats = append(stats, madmin.KMS{
|
|
||||||
Status: result.Status,
|
|
||||||
Endpoint: result.Endpoint,
|
|
||||||
Encrypt: result.Encrypt,
|
|
||||||
Decrypt: result.Decrypt,
|
|
||||||
Version: result.Version,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stats := make([]madmin.KMS, 0, len(stat.Endpoints))
|
||||||
|
for endpoint, state := range stat.Endpoints {
|
||||||
|
stats = append(stats, madmin.KMS{
|
||||||
|
Status: string(state),
|
||||||
|
Endpoint: endpoint,
|
||||||
|
})
|
||||||
|
}
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-21
@@ -63,8 +63,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errHealIdleTimeout = fmt.Errorf("healing results were not consumed for too long")
|
errHealIdleTimeout = errors.New("healing results were not consumed for too long")
|
||||||
errHealStopSignalled = fmt.Errorf("heal stop signaled")
|
errHealStopSignalled = errors.New("heal stop signaled")
|
||||||
|
|
||||||
errFnHealFromAPIErr = func(ctx context.Context, err error) error {
|
errFnHealFromAPIErr = func(ctx context.Context, err error) error {
|
||||||
apiErr := toAdminAPIErr(ctx, err)
|
apiErr := toAdminAPIErr(ctx, err)
|
||||||
@@ -455,8 +455,8 @@ type healSequence struct {
|
|||||||
// Number of total items healed against item type
|
// Number of total items healed against item type
|
||||||
healedItemsMap map[madmin.HealItemType]int64
|
healedItemsMap map[madmin.HealItemType]int64
|
||||||
|
|
||||||
// Number of total items where healing failed against endpoint and drive state
|
// Number of total items where healing failed against item type
|
||||||
healFailedItemsMap map[string]int64
|
healFailedItemsMap map[madmin.HealItemType]int64
|
||||||
|
|
||||||
// The time of the last scan/heal activity
|
// The time of the last scan/heal activity
|
||||||
lastHealActivity time.Time
|
lastHealActivity time.Time
|
||||||
@@ -497,7 +497,7 @@ func newHealSequence(ctx context.Context, bucket, objPrefix, clientAddr string,
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
scannedItemsMap: make(map[madmin.HealItemType]int64),
|
scannedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
healedItemsMap: make(map[madmin.HealItemType]int64),
|
healedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
healFailedItemsMap: make(map[string]int64),
|
healFailedItemsMap: make(map[madmin.HealItemType]int64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,12 +543,12 @@ func (h *healSequence) getHealedItemsMap() map[madmin.HealItemType]int64 {
|
|||||||
|
|
||||||
// getHealFailedItemsMap - returns map of all items where heal failed against
|
// getHealFailedItemsMap - returns map of all items where heal failed against
|
||||||
// drive endpoint and status
|
// drive endpoint and status
|
||||||
func (h *healSequence) getHealFailedItemsMap() map[string]int64 {
|
func (h *healSequence) getHealFailedItemsMap() map[madmin.HealItemType]int64 {
|
||||||
h.mutex.RLock()
|
h.mutex.RLock()
|
||||||
defer h.mutex.RUnlock()
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
// Make a copy before returning the value
|
// Make a copy before returning the value
|
||||||
retMap := make(map[string]int64, len(h.healFailedItemsMap))
|
retMap := make(map[madmin.HealItemType]int64, len(h.healFailedItemsMap))
|
||||||
for k, v := range h.healFailedItemsMap {
|
for k, v := range h.healFailedItemsMap {
|
||||||
retMap[k] = v
|
retMap[k] = v
|
||||||
}
|
}
|
||||||
@@ -556,29 +556,27 @@ func (h *healSequence) getHealFailedItemsMap() map[string]int64 {
|
|||||||
return retMap
|
return retMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) countFailed(res madmin.HealResultItem) {
|
func (h *healSequence) countFailed(healType madmin.HealItemType) {
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
defer h.mutex.Unlock()
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
for _, d := range res.After.Drives {
|
h.healFailedItemsMap[healType]++
|
||||||
// For failed items we report the endpoint and drive state
|
|
||||||
// This will help users take corrective actions for drives
|
|
||||||
h.healFailedItemsMap[d.Endpoint+","+d.State]++
|
|
||||||
}
|
|
||||||
|
|
||||||
h.lastHealActivity = UTCNow()
|
h.lastHealActivity = UTCNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) countHeals(healType madmin.HealItemType, healed bool) {
|
func (h *healSequence) countScanned(healType madmin.HealItemType) {
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
defer h.mutex.Unlock()
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
if !healed {
|
h.scannedItemsMap[healType]++
|
||||||
h.scannedItemsMap[healType]++
|
h.lastHealActivity = UTCNow()
|
||||||
} else {
|
}
|
||||||
h.healedItemsMap[healType]++
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (h *healSequence) countHealed(healType madmin.HealItemType) {
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
h.healedItemsMap[healType]++
|
||||||
h.lastHealActivity = UTCNow()
|
h.lastHealActivity = UTCNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,7 +732,7 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
task.opts.ScanMode = madmin.HealNormalScan
|
task.opts.ScanMode = madmin.HealNormalScan
|
||||||
}
|
}
|
||||||
|
|
||||||
h.countHeals(healType, false)
|
h.countScanned(healType)
|
||||||
|
|
||||||
if source.noWait {
|
if source.noWait {
|
||||||
select {
|
select {
|
||||||
@@ -766,6 +764,11 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
// task queued, now wait for the response.
|
// task queued, now wait for the response.
|
||||||
select {
|
select {
|
||||||
case res := <-task.respCh:
|
case res := <-task.respCh:
|
||||||
|
if res.err == nil {
|
||||||
|
h.countHealed(healType)
|
||||||
|
} else {
|
||||||
|
h.countFailed(healType)
|
||||||
|
}
|
||||||
if !h.reportProgress {
|
if !h.reportProgress {
|
||||||
if errors.Is(res.err, errSkipFile) { // this is only sent usually by nopHeal
|
if errors.Is(res.err, errSkipFile) { // this is only sent usually by nopHeal
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -31,6 +30,7 @@ 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/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
||||||
@@ -64,9 +64,11 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
if err := isServerResolvable(endpoint, 5*time.Second); err == nil {
|
if err := isServerResolvable(endpoint, 5*time.Second); err == nil {
|
||||||
network[nodeName] = string(madmin.ItemOnline)
|
network[nodeName] = string(madmin.ItemOnline)
|
||||||
} else {
|
} else {
|
||||||
network[nodeName] = string(madmin.ItemOffline)
|
if xnet.IsNetworkOrHostDown(err, false) {
|
||||||
// log once the error
|
network[nodeName] = string(madmin.ItemOffline)
|
||||||
peersLogOnceIf(context.Background(), err, nodeName)
|
} else if xnet.IsNetworkOrHostDown(err, true) {
|
||||||
|
network[nodeName] = "connection attempt timedout"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ type ObjectToDelete struct {
|
|||||||
ReplicateDecisionStr string `xml:"-"`
|
ReplicateDecisionStr string `xml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// createBucketConfiguration container for bucket configuration request from client.
|
// createBucketLocationConfiguration container for bucket configuration request from client.
|
||||||
// Used for parsing the location from the request body for Makebucket.
|
// Used for parsing the location from the request body for Makebucket.
|
||||||
type createBucketLocationConfiguration struct {
|
type createBucketLocationConfiguration struct {
|
||||||
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
|
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
|
||||||
|
|||||||
+67
-27
@@ -48,7 +48,7 @@ import (
|
|||||||
levent "github.com/minio/minio/internal/config/lambda/event"
|
levent "github.com/minio/minio/internal/config/lambda/event"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError structure
|
// APIError structure
|
||||||
@@ -56,19 +56,23 @@ type APIError struct {
|
|||||||
Code string
|
Code string
|
||||||
Description string
|
Description string
|
||||||
HTTPStatusCode int
|
HTTPStatusCode int
|
||||||
|
ObjectSize string
|
||||||
|
RangeRequested string
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIErrorResponse - error response format
|
// APIErrorResponse - error response format
|
||||||
type APIErrorResponse struct {
|
type APIErrorResponse struct {
|
||||||
XMLName xml.Name `xml:"Error" json:"-"`
|
XMLName xml.Name `xml:"Error" json:"-"`
|
||||||
Code string
|
Code string
|
||||||
Message string
|
Message string
|
||||||
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
||||||
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
||||||
Resource string
|
Resource string
|
||||||
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
|
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
|
||||||
RequestID string `xml:"RequestId" json:"RequestId"`
|
RequestID string `xml:"RequestId" json:"RequestId"`
|
||||||
HostID string `xml:"HostId" json:"HostId"`
|
HostID string `xml:"HostId" json:"HostId"`
|
||||||
|
ActualObjectSize string `xml:"ActualObjectSize,omitempty" json:"ActualObjectSize,omitempty"`
|
||||||
|
RangeRequested string `xml:"RangeRequested,omitempty" json:"RangeRequested,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIErrorCode type of error status.
|
// APIErrorCode type of error status.
|
||||||
@@ -283,6 +287,7 @@ const (
|
|||||||
ErrAdminNoSuchGroup
|
ErrAdminNoSuchGroup
|
||||||
ErrAdminGroupNotEmpty
|
ErrAdminGroupNotEmpty
|
||||||
ErrAdminGroupDisabled
|
ErrAdminGroupDisabled
|
||||||
|
ErrAdminInvalidGroupName
|
||||||
ErrAdminNoSuchJob
|
ErrAdminNoSuchJob
|
||||||
ErrAdminNoSuchPolicy
|
ErrAdminNoSuchPolicy
|
||||||
ErrAdminPolicyChangeAlreadyApplied
|
ErrAdminPolicyChangeAlreadyApplied
|
||||||
@@ -421,6 +426,7 @@ const (
|
|||||||
ErrAdminProfilerNotEnabled
|
ErrAdminProfilerNotEnabled
|
||||||
ErrInvalidDecompressedSize
|
ErrInvalidDecompressedSize
|
||||||
ErrAddUserInvalidArgument
|
ErrAddUserInvalidArgument
|
||||||
|
ErrAddUserValidUTF
|
||||||
ErrAdminResourceInvalidArgument
|
ErrAdminResourceInvalidArgument
|
||||||
ErrAdminAccountNotEligible
|
ErrAdminAccountNotEligible
|
||||||
ErrAccountNotEligible
|
ErrAccountNotEligible
|
||||||
@@ -439,6 +445,8 @@ const (
|
|||||||
ErrAdminNoAccessKey
|
ErrAdminNoAccessKey
|
||||||
ErrAdminNoSecretKey
|
ErrAdminNoSecretKey
|
||||||
|
|
||||||
|
ErrIAMNotInitialized
|
||||||
|
|
||||||
apiErrCodeEnd // This is used only for the testing code
|
apiErrCodeEnd // This is used only for the testing code
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -452,9 +460,9 @@ func (e errorCodeMap) ToAPIErrWithErr(errCode APIErrorCode, err error) APIError
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||||
}
|
}
|
||||||
if globalSite.Region != "" {
|
if region := globalSite.Region(); region != "" {
|
||||||
if errCode == ErrAuthorizationHeaderMalformed {
|
if errCode == ErrAuthorizationHeaderMalformed {
|
||||||
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", region)
|
||||||
return apiErr
|
return apiErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -961,7 +969,7 @@ var errorCodes = errorCodeMap{
|
|||||||
ErrReplicationRemoteConnectionError: {
|
ErrReplicationRemoteConnectionError: {
|
||||||
Code: "XMinioAdminReplicationRemoteConnectionError",
|
Code: "XMinioAdminReplicationRemoteConnectionError",
|
||||||
Description: "Remote service connection error",
|
Description: "Remote service connection error",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
ErrReplicationBandwidthLimitError: {
|
ErrReplicationBandwidthLimitError: {
|
||||||
Code: "XMinioAdminReplicationBandwidthLimitError",
|
Code: "XMinioAdminReplicationBandwidthLimitError",
|
||||||
@@ -1299,6 +1307,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Server not initialized yet, please try again.",
|
Description: "Server not initialized yet, please try again.",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
|
ErrIAMNotInitialized: {
|
||||||
|
Code: "XMinioIAMNotInitialized",
|
||||||
|
Description: "IAM sub-system not initialized yet, please try again.",
|
||||||
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
|
},
|
||||||
ErrBucketMetadataNotInitialized: {
|
ErrBucketMetadataNotInitialized: {
|
||||||
Code: "XMinioBucketMetadataNotInitialized",
|
Code: "XMinioBucketMetadataNotInitialized",
|
||||||
Description: "Bucket metadata not initialized yet, please try again.",
|
Description: "Bucket metadata not initialized yet, please try again.",
|
||||||
@@ -1474,7 +1487,7 @@ var errorCodes = errorCodeMap{
|
|||||||
ErrTooManyRequests: {
|
ErrTooManyRequests: {
|
||||||
Code: "TooManyRequests",
|
Code: "TooManyRequests",
|
||||||
Description: "Deadline exceeded while waiting in incoming queue, please reduce your request rate",
|
Description: "Deadline exceeded while waiting in incoming queue, please reduce your request rate",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusTooManyRequests,
|
||||||
},
|
},
|
||||||
ErrUnsupportedMetadata: {
|
ErrUnsupportedMetadata: {
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -2097,6 +2110,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Expected LDAP short username but was given full DN.",
|
Description: "Expected LDAP short username but was given full DN.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminInvalidGroupName: {
|
||||||
|
Code: "XMinioInvalidGroupName",
|
||||||
|
Description: "The group name is invalid.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAddUserValidUTF: {
|
||||||
|
Code: "XMinioInvalidUTF",
|
||||||
|
Description: "Invalid UTF-8 character detected.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// toAPIErrorCode - Converts embedded errors. Convenience
|
// toAPIErrorCode - Converts embedded errors. Convenience
|
||||||
@@ -2136,6 +2159,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminNoSuchGroup
|
apiErr = ErrAdminNoSuchGroup
|
||||||
case errGroupNotEmpty:
|
case errGroupNotEmpty:
|
||||||
apiErr = ErrAdminGroupNotEmpty
|
apiErr = ErrAdminGroupNotEmpty
|
||||||
|
case errGroupNameContainsReservedChars:
|
||||||
|
apiErr = ErrAdminInvalidGroupName
|
||||||
case errNoSuchJob:
|
case errNoSuchJob:
|
||||||
apiErr = ErrAdminNoSuchJob
|
apiErr = ErrAdminNoSuchJob
|
||||||
case errNoPolicyToAttachOrDetach:
|
case errNoPolicyToAttachOrDetach:
|
||||||
@@ -2150,6 +2175,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrEntityTooSmall
|
apiErr = ErrEntityTooSmall
|
||||||
case errAuthentication:
|
case errAuthentication:
|
||||||
apiErr = ErrAccessDenied
|
apiErr = ErrAccessDenied
|
||||||
|
case auth.ErrContainsReservedChars:
|
||||||
|
apiErr = ErrAdminInvalidAccessKey
|
||||||
case auth.ErrInvalidAccessKeyLength:
|
case auth.ErrInvalidAccessKeyLength:
|
||||||
apiErr = ErrAdminInvalidAccessKey
|
apiErr = ErrAdminInvalidAccessKey
|
||||||
case auth.ErrInvalidSecretKeyLength:
|
case auth.ErrInvalidSecretKeyLength:
|
||||||
@@ -2412,10 +2439,9 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
apiErr := errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
apiErr := errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
||||||
switch apiErr.Code {
|
switch apiErr.Code {
|
||||||
case "NotImplemented":
|
case "NotImplemented":
|
||||||
desc := fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: apiErr.Code,
|
Code: apiErr.Code,
|
||||||
Description: desc,
|
Description: fmt.Sprintf("%s (%v)", apiErr.Description, err),
|
||||||
HTTPStatusCode: apiErr.HTTPStatusCode,
|
HTTPStatusCode: apiErr.HTTPStatusCode,
|
||||||
}
|
}
|
||||||
case "XMinioBackendDown":
|
case "XMinioBackendDown":
|
||||||
@@ -2427,12 +2453,24 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case kms.Error:
|
case kms.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Description: e.Err.Error(),
|
|
||||||
Code: e.APICode,
|
Code: e.APICode,
|
||||||
HTTPStatusCode: e.HTTPStatusCode,
|
Description: e.Err,
|
||||||
|
HTTPStatusCode: e.Code,
|
||||||
}
|
}
|
||||||
case batchReplicationJobError:
|
case batchReplicationJobError:
|
||||||
apiErr = APIError(e)
|
apiErr = APIError{
|
||||||
|
Description: e.Description,
|
||||||
|
Code: e.Code,
|
||||||
|
HTTPStatusCode: e.HTTPStatusCode,
|
||||||
|
}
|
||||||
|
case InvalidRange:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "InvalidRange",
|
||||||
|
Description: e.Error(),
|
||||||
|
HTTPStatusCode: errorCodes[ErrInvalidRange].HTTPStatusCode,
|
||||||
|
ObjectSize: strconv.FormatInt(e.ResourceSize, 10),
|
||||||
|
RangeRequested: fmt.Sprintf("%d-%d", e.OffsetBegin, e.OffsetEnd),
|
||||||
|
}
|
||||||
case InvalidArgument:
|
case InvalidArgument:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -2559,13 +2597,15 @@ func getAPIError(code APIErrorCode) APIError {
|
|||||||
func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID, hostID string) APIErrorResponse {
|
func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID, hostID string) APIErrorResponse {
|
||||||
reqInfo := logger.GetReqInfo(ctx)
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
return APIErrorResponse{
|
return APIErrorResponse{
|
||||||
Code: err.Code,
|
Code: err.Code,
|
||||||
Message: err.Description,
|
Message: err.Description,
|
||||||
BucketName: reqInfo.BucketName,
|
BucketName: reqInfo.BucketName,
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
Resource: resource,
|
Resource: resource,
|
||||||
Region: globalSite.Region,
|
Region: globalSite.Region(),
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
HostID: hostID,
|
HostID: hostID,
|
||||||
|
ActualObjectSize: err.ObjectSize,
|
||||||
|
RangeRequested: err.RangeRequested,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -53,7 +54,7 @@ func setCommonHeaders(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Set `x-amz-bucket-region` only if region is set on the server
|
// Set `x-amz-bucket-region` only if region is set on the server
|
||||||
// by default minio uses an empty region.
|
// by default minio uses an empty region.
|
||||||
if region := globalSite.Region; region != "" {
|
if region := globalSite.Region(); region != "" {
|
||||||
w.Header().Set(xhttp.AmzBucketRegion, region)
|
w.Header().Set(xhttp.AmzBucketRegion, region)
|
||||||
}
|
}
|
||||||
w.Header().Set(xhttp.AcceptRanges, "bytes")
|
w.Header().Set(xhttp.AcceptRanges, "bytes")
|
||||||
@@ -107,7 +108,7 @@ func setPartsCountHeaders(w http.ResponseWriter, objInfo ObjectInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write object header
|
// Write object header
|
||||||
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) {
|
func setObjectHeaders(ctx context.Context, w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) {
|
||||||
// set common headers
|
// set common headers
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
// Set tag count if object has tags
|
// Set tag count if object has tags
|
||||||
if len(objInfo.UserTags) > 0 {
|
if len(objInfo.UserTags) > 0 {
|
||||||
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
if tags.Count() > 0 {
|
if tags != nil && tags.Count() > 0 {
|
||||||
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())}
|
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())}
|
||||||
if opts.Tagging {
|
if opts.Tagging {
|
||||||
// This is MinIO only extension to return back tags along with the count.
|
// This is MinIO only extension to return back tags along with the count.
|
||||||
@@ -212,7 +213,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
if objInfo.IsRemote() {
|
if objInfo.IsRemote() {
|
||||||
// Check if object is being restored. For more information on x-amz-restore header see
|
// Check if object is being restored. For more information on x-amz-restore header see
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
|
||||||
w.Header()[xhttp.AmzStorageClass] = []string{objInfo.TransitionedObject.Tier}
|
w.Header()[xhttp.AmzStorageClass] = []string{filterStorageClass(ctx, objInfo.TransitionedObject.Tier)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
||||||
|
|||||||
+11
-11
@@ -35,7 +35,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
xxml "github.com/minio/xxml"
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -544,7 +544,7 @@ func cleanReservedKeys(metadata map[string]string) map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
func generateListVersionsResponse(ctx context.Context, bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
|
|
||||||
owner := &Owner{
|
owner := &Owner{
|
||||||
@@ -573,7 +573,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
}
|
}
|
||||||
content.Size = object.Size
|
content.Size = object.Size
|
||||||
if object.StorageClass != "" {
|
if object.StorageClass != "" {
|
||||||
content.StorageClass = object.StorageClass
|
content.StorageClass = filterStorageClass(ctx, object.StorageClass)
|
||||||
} else {
|
} else {
|
||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
@@ -634,7 +634,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
func generateListObjectsV1Response(ctx context.Context, bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
||||||
contents := make([]Object, 0, len(resp.Objects))
|
contents := make([]Object, 0, len(resp.Objects))
|
||||||
owner := &Owner{
|
owner := &Owner{
|
||||||
ID: globalMinioDefaultOwnerID,
|
ID: globalMinioDefaultOwnerID,
|
||||||
@@ -654,7 +654,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
}
|
}
|
||||||
content.Size = object.Size
|
content.Size = object.Size
|
||||||
if object.StorageClass != "" {
|
if object.StorageClass != "" {
|
||||||
content.StorageClass = object.StorageClass
|
content.StorageClass = filterStorageClass(ctx, object.StorageClass)
|
||||||
} else {
|
} else {
|
||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
@@ -683,7 +683,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
|
func generateListObjectsV2Response(ctx context.Context, bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
|
||||||
contents := make([]Object, 0, len(objects))
|
contents := make([]Object, 0, len(objects))
|
||||||
var owner *Owner
|
var owner *Owner
|
||||||
if fetchOwner {
|
if fetchOwner {
|
||||||
@@ -707,7 +707,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
}
|
}
|
||||||
content.Size = object.Size
|
content.Size = object.Size
|
||||||
if object.StorageClass != "" {
|
if object.StorageClass != "" {
|
||||||
content.StorageClass = object.StorageClass
|
content.StorageClass = filterStorageClass(ctx, object.StorageClass)
|
||||||
} else {
|
} else {
|
||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
@@ -789,8 +789,8 @@ func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) Initi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
|
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
|
||||||
func generateCompleteMultpartUploadResponse(bucket, key, location string, oi ObjectInfo) CompleteMultipartUploadResponse {
|
func generateCompleteMultipartUploadResponse(bucket, key, location string, oi ObjectInfo, h http.Header) CompleteMultipartUploadResponse {
|
||||||
cs := oi.decryptChecksums(0)
|
cs := oi.decryptChecksums(0, h)
|
||||||
c := CompleteMultipartUploadResponse{
|
c := CompleteMultipartUploadResponse{
|
||||||
Location: location,
|
Location: location,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
@@ -954,9 +954,9 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
|||||||
|
|
||||||
switch err.Code {
|
switch err.Code {
|
||||||
case "InvalidRegion":
|
case "InvalidRegion":
|
||||||
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region)
|
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region())
|
||||||
case "AuthorizationHeaderMalformed":
|
case "AuthorizationHeaderMalformed":
|
||||||
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similar check to http.checkWriteHeaderCode
|
// Similar check to http.checkWriteHeaderCode
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ import (
|
|||||||
consoleapi "github.com/minio/console/api"
|
consoleapi "github.com/minio/console/api"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/wildcard"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ func setObjectLayer(o ObjectLayer) {
|
|||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectAPIHandler implements and provides http handlers for S3 API.
|
// objectAPIHandlers implements and provides http handlers for S3 API.
|
||||||
type objectAPIHandlers struct {
|
type objectAPIHandlers struct {
|
||||||
ObjectAPI func() ObjectLayer
|
ObjectAPI func() ObjectLayer
|
||||||
}
|
}
|
||||||
|
|||||||
+145
-142
File diff suppressed because one or more lines are too long
+6
-6
@@ -41,7 +41,7 @@ import (
|
|||||||
xjwt "github.com/minio/minio/internal/jwt"
|
xjwt "github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/mcontext"
|
"github.com/minio/minio/internal/mcontext"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify if request has JWT.
|
// Verify if request has JWT.
|
||||||
@@ -178,7 +178,7 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
|
|
||||||
logger.GetReqInfo(ctx).Cred = cred
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
logger.GetReqInfo(ctx).Owner = owner
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
logger.GetReqInfo(ctx).Region = globalSite.Region
|
logger.GetReqInfo(ctx).Region = globalSite.Region()
|
||||||
|
|
||||||
return cred, owner, ErrNone
|
return cred, owner, ErrNone
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
|
|||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypeSigned, authTypePresigned:
|
case authTypeSigned, authTypePresigned:
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
switch action {
|
switch action {
|
||||||
case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
|
case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
|
||||||
region = ""
|
region = ""
|
||||||
@@ -384,7 +384,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
|
|||||||
|
|
||||||
logger.GetReqInfo(ctx).Cred = cred
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
logger.GetReqInfo(ctx).Owner = owner
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
logger.GetReqInfo(ctx).Region = globalSite.Region
|
logger.GetReqInfo(ctx).Region = globalSite.Region()
|
||||||
|
|
||||||
// region is valid only for CreateBucketAction.
|
// region is valid only for CreateBucketAction.
|
||||||
var region string
|
var region string
|
||||||
@@ -684,7 +684,7 @@ func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool,
|
|||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypePresigned, authTypeSigned:
|
case authTypePresigned, authTypeSigned:
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
|
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return cred, owner, s3Err
|
||||||
}
|
}
|
||||||
@@ -745,7 +745,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
switch atype {
|
switch atype {
|
||||||
case authTypeUnknown:
|
case authTypeUnknown:
|
||||||
return ErrSignatureVersionNotSupported
|
return ErrSignatureVersionNotSupported
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nullReader struct{}
|
type nullReader struct{}
|
||||||
@@ -403,7 +403,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
|
|
||||||
// Validates all testcases.
|
// Validates all testcases.
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3)
|
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region(), serviceS3)
|
||||||
if s3Error != testCase.s3Error {
|
if s3Error != testCase.s3Error {
|
||||||
if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
|
||||||
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
|
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
|
||||||
@@ -443,7 +443,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region()); s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-10
@@ -25,7 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// healTask represents what to heal along with options
|
// healTask represents what to heal along with options
|
||||||
@@ -133,19 +133,20 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer, bgSeq *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bgSeq != nil {
|
|
||||||
// We increment relevant counter based on the heal result for prometheus reporting.
|
|
||||||
if err != nil {
|
|
||||||
bgSeq.countFailed(res)
|
|
||||||
} else {
|
|
||||||
bgSeq.countHeals(res.Type, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if task.respCh != nil {
|
if task.respCh != nil {
|
||||||
task.respCh <- healResult{result: res, err: err}
|
task.respCh <- healResult{result: res, err: err}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when respCh is not set caller is not waiting but we
|
||||||
|
// update the relevant metrics for them
|
||||||
|
if bgSeq != nil {
|
||||||
|
if err == nil {
|
||||||
|
bgSeq.countHealed(res.Type)
|
||||||
|
} else {
|
||||||
|
bgSeq.countFailed(res.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -141,14 +141,14 @@ func initHealingTracker(disk StorageAPI, healID string) *healingTracker {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h healingTracker) getLastUpdate() time.Time {
|
func (h *healingTracker) getLastUpdate() time.Time {
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
defer h.mu.RUnlock()
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
return h.LastUpdate
|
return h.LastUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h healingTracker) getBucket() string {
|
func (h *healingTracker) getBucket() string {
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
defer h.mu.RUnlock()
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ func (h *healingTracker) setBucket(bucket string) {
|
|||||||
h.Bucket = bucket
|
h.Bucket = bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h healingTracker) getObject() string {
|
func (h *healingTracker) getObject() string {
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
defer h.mu.RUnlock()
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
@@ -453,10 +453,6 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
|
|
||||||
healingLogEvent(ctx, "Healing of drive '%s' is finished (healed: %d, skipped: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
|
healingLogEvent(ctx, "Healing of drive '%s' is finished (healed: %d, skipped: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
|
||||||
|
|
||||||
if len(tracker.QueuedBuckets) > 0 {
|
|
||||||
return fmt.Errorf("not all buckets were healed: %v", tracker.QueuedBuckets)
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
tracker.printTo(os.Stdout)
|
tracker.printTo(os.Stdout)
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
|
|||||||
+29
-22
@@ -33,9 +33,9 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/versioning"
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/wildcard"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"github.com/minio/pkg/v2/workers"
|
"github.com/minio/pkg/v3/workers"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -432,7 +432,7 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stopFn(exp, err)
|
stopFn(exp, err)
|
||||||
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", toExpire[i].Bucket, toExpire[i].Name, toExpire[i].VersionID, err, attempts))
|
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s due to %v (attempts=%d)", exp.Bucket, exp.Name, err, attempts))
|
||||||
} else {
|
} else {
|
||||||
stopFn(exp, err)
|
stopFn(exp, err)
|
||||||
success = true
|
success = true
|
||||||
@@ -464,25 +464,25 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
|
|||||||
copy(toDelCopy, toDel)
|
copy(toDelCopy, toDel)
|
||||||
var failed int
|
var failed int
|
||||||
errs := r.Expire(ctx, api, vc, toDel)
|
errs := r.Expire(ctx, api, vc, toDel)
|
||||||
// reslice toDel in preparation for next retry
|
// reslice toDel in preparation for next retry attempt
|
||||||
// attempt
|
|
||||||
toDel = toDel[:0]
|
toDel = toDel[:0]
|
||||||
for i, err := range errs {
|
for i, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stopFn(toDelCopy[i], err)
|
stopFn(toDelCopy[i], err)
|
||||||
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", ri.Bucket, toDelCopy[i].ObjectName, toDelCopy[i].VersionID, err, attempts))
|
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", ri.Bucket, toDelCopy[i].ObjectName, toDelCopy[i].VersionID,
|
||||||
|
err, attempts))
|
||||||
failed++
|
failed++
|
||||||
if attempts == retryAttempts { // all retry attempts failed, record failure
|
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
||||||
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
ri.trackCurrentBucketObject(r.Bucket, *oi, false, attempts)
|
||||||
ri.trackCurrentBucketObject(r.Bucket, *oi, false)
|
}
|
||||||
}
|
if attempts != retryAttempts {
|
||||||
} else {
|
// retry
|
||||||
toDel = append(toDel, toDelCopy[i])
|
toDel = append(toDel, toDelCopy[i])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stopFn(toDelCopy[i], nil)
|
stopFn(toDelCopy[i], nil)
|
||||||
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
if oi, ok := oiCache.Get(toDelCopy[i]); ok {
|
||||||
ri.trackCurrentBucketObject(r.Bucket, *oi, true)
|
ri.trackCurrentBucketObject(r.Bucket, *oi, true, attempts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,7 +537,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
results := make(chan ObjectInfo, workerSize)
|
results := make(chan itemOrErr[ObjectInfo], workerSize)
|
||||||
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
||||||
Marker: lastObject,
|
Marker: lastObject,
|
||||||
LatestOnly: false, // we need to visit all versions of the object to implement purge: retainVersions
|
LatestOnly: false, // we need to visit all versions of the object to implement purge: retainVersions
|
||||||
@@ -584,11 +584,18 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
versionsCount int
|
versionsCount int
|
||||||
toDel []expireObjInfo
|
toDel []expireObjInfo
|
||||||
)
|
)
|
||||||
|
failed := true
|
||||||
for result := range results {
|
for result := range results {
|
||||||
|
if result.Err != nil {
|
||||||
|
failed = true
|
||||||
|
batchLogIf(ctx, result.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Apply filter to find the matching rule to apply expiry
|
// Apply filter to find the matching rule to apply expiry
|
||||||
// actions accordingly.
|
// actions accordingly.
|
||||||
// nolint:gocritic
|
// nolint:gocritic
|
||||||
if result.IsLatest {
|
if result.Item.IsLatest {
|
||||||
// send down filtered entries to be deleted using
|
// send down filtered entries to be deleted using
|
||||||
// DeleteObjects method
|
// DeleteObjects method
|
||||||
if len(toDel) > 10 { // batch up to 10 objects/versions to be expired simultaneously.
|
if len(toDel) > 10 { // batch up to 10 objects/versions to be expired simultaneously.
|
||||||
@@ -609,7 +616,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
var match BatchJobExpireFilter
|
var match BatchJobExpireFilter
|
||||||
var found bool
|
var found bool
|
||||||
for _, rule := range r.Rules {
|
for _, rule := range r.Rules {
|
||||||
if rule.Matches(result, now) {
|
if rule.Matches(result.Item, now) {
|
||||||
match = rule
|
match = rule
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
@@ -619,18 +626,18 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prevObj = result
|
prevObj = result.Item
|
||||||
matchedFilter = match
|
matchedFilter = match
|
||||||
versionsCount = 1
|
versionsCount = 1
|
||||||
// Include the latest version
|
// Include the latest version
|
||||||
if matchedFilter.Purge.RetainVersions == 0 {
|
if matchedFilter.Purge.RetainVersions == 0 {
|
||||||
toDel = append(toDel, expireObjInfo{
|
toDel = append(toDel, expireObjInfo{
|
||||||
ObjectInfo: result,
|
ObjectInfo: result.Item,
|
||||||
ExpireAll: true,
|
ExpireAll: true,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if prevObj.Name == result.Name {
|
} else if prevObj.Name == result.Item.Name {
|
||||||
if matchedFilter.Purge.RetainVersions == 0 {
|
if matchedFilter.Purge.RetainVersions == 0 {
|
||||||
continue // including latest version in toDel suffices, skipping other versions
|
continue // including latest version in toDel suffices, skipping other versions
|
||||||
}
|
}
|
||||||
@@ -643,7 +650,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
continue // retain versions
|
continue // retain versions
|
||||||
}
|
}
|
||||||
toDel = append(toDel, expireObjInfo{
|
toDel = append(toDel, expireObjInfo{
|
||||||
ObjectInfo: result,
|
ObjectInfo: result.Item,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Send any remaining objects downstream
|
// Send any remaining objects downstream
|
||||||
@@ -658,8 +665,8 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
<-expireDoneCh // waits for the expire goroutine to complete
|
<-expireDoneCh // waits for the expire goroutine to complete
|
||||||
wk.Wait() // waits for all expire workers to retire
|
wk.Wait() // waits for all expire workers to retire
|
||||||
|
|
||||||
ri.Complete = ri.ObjectsFailed == 0
|
ri.Complete = !failed && ri.ObjectsFailed == 0
|
||||||
ri.Failed = ri.ObjectsFailed > 0
|
ri.Failed = failed || ri.ObjectsFailed > 0
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
|
||||||
// Close the saverQuitCh - this also triggers saving in-memory state
|
// Close the saverQuitCh - this also triggers saving in-memory state
|
||||||
|
|||||||
+53
-24
@@ -48,10 +48,10 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/pkg/v2/console"
|
"github.com/minio/pkg/v3/console"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/workers"
|
"github.com/minio/pkg/v3/workers"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -447,7 +447,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
} else {
|
} else {
|
||||||
stopFn(oi, nil)
|
stopFn(oi, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Target.Bucket, oi, success)
|
ri.trackCurrentBucketObject(r.Target.Bucket, oi, success, attempts)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
@@ -690,6 +690,7 @@ type batchJobInfo struct {
|
|||||||
StartTime time.Time `json:"startTime" msg:"st"`
|
StartTime time.Time `json:"startTime" msg:"st"`
|
||||||
LastUpdate time.Time `json:"lastUpdate" msg:"lu"`
|
LastUpdate time.Time `json:"lastUpdate" msg:"lu"`
|
||||||
RetryAttempts int `json:"retryAttempts" msg:"ra"`
|
RetryAttempts int `json:"retryAttempts" msg:"ra"`
|
||||||
|
Attempts int `json:"attempts" msg:"at"`
|
||||||
|
|
||||||
Complete bool `json:"complete" msg:"cmp"`
|
Complete bool `json:"complete" msg:"cmp"`
|
||||||
Failed bool `json:"failed" msg:"fld"`
|
Failed bool `json:"failed" msg:"fld"`
|
||||||
@@ -833,13 +834,15 @@ func (ri *batchJobInfo) clone() *batchJobInfo {
|
|||||||
ObjectsFailed: ri.ObjectsFailed,
|
ObjectsFailed: ri.ObjectsFailed,
|
||||||
BytesTransferred: ri.BytesTransferred,
|
BytesTransferred: ri.BytesTransferred,
|
||||||
BytesFailed: ri.BytesFailed,
|
BytesFailed: ri.BytesFailed,
|
||||||
|
Attempts: ri.Attempts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) {
|
func (ri *batchJobInfo) countItem(size int64, dmarker, success bool, attempt int) {
|
||||||
if ri == nil {
|
if ri == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ri.Attempts++
|
||||||
if success {
|
if success {
|
||||||
if dmarker {
|
if dmarker {
|
||||||
ri.DeleteMarkers++
|
ri.DeleteMarkers++
|
||||||
@@ -847,7 +850,19 @@ func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) {
|
|||||||
ri.Objects++
|
ri.Objects++
|
||||||
ri.BytesTransferred += size
|
ri.BytesTransferred += size
|
||||||
}
|
}
|
||||||
|
if attempt > 1 {
|
||||||
|
if dmarker {
|
||||||
|
ri.DeleteMarkersFailed--
|
||||||
|
} else {
|
||||||
|
ri.ObjectsFailed--
|
||||||
|
ri.BytesFailed += size
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if attempt > 1 {
|
||||||
|
// Only count first attempt
|
||||||
|
return
|
||||||
|
}
|
||||||
if dmarker {
|
if dmarker {
|
||||||
ri.DeleteMarkersFailed++
|
ri.DeleteMarkersFailed++
|
||||||
} else {
|
} else {
|
||||||
@@ -921,7 +936,7 @@ func (ri *batchJobInfo) trackMultipleObjectVersions(bucket string, info ObjectIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo, success bool) {
|
func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo, success bool, attempt int) {
|
||||||
if ri == nil {
|
if ri == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -931,7 +946,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, success)
|
ri.countItem(info.Size, info.DeleteMarker, success, attempt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri *batchJobInfo) trackCurrentBucketBatch(bucket string, batch []ObjectInfo) {
|
func (ri *batchJobInfo) trackCurrentBucketBatch(bucket string, batch []ObjectInfo) {
|
||||||
@@ -945,7 +960,7 @@ func (ri *batchJobInfo) trackCurrentBucketBatch(bucket string, batch []ObjectInf
|
|||||||
ri.Bucket = bucket
|
ri.Bucket = bucket
|
||||||
for i := range batch {
|
for i := range batch {
|
||||||
ri.Object = batch[i].Name
|
ri.Object = batch[i].Name
|
||||||
ri.countItem(batch[i].Size, batch[i].DeleteMarker, true)
|
ri.countItem(batch[i].Size, batch[i].DeleteMarker, true, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,8 +1072,8 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
walkCh = make(chan ObjectInfo, 100)
|
walkCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||||
slowCh = make(chan ObjectInfo, 100)
|
slowCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||||
)
|
)
|
||||||
|
|
||||||
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
||||||
@@ -1084,7 +1099,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
||||||
batchLogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
for _, b := range batch {
|
for _, b := range batch {
|
||||||
slowCh <- b
|
slowCh <- itemOrErr[ObjectInfo]{Item: b}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
|
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
|
||||||
@@ -1095,12 +1110,12 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for obj := range walkCh {
|
for obj := range walkCh {
|
||||||
if obj.DeleteMarker || !obj.VersionPurgeStatus.Empty() || obj.Size >= int64(smallerThan) {
|
if obj.Item.DeleteMarker || !obj.Item.VersionPurgeStatus.Empty() || obj.Item.Size >= int64(smallerThan) {
|
||||||
slowCh <- obj
|
slowCh <- obj
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
batch = append(batch, obj)
|
batch = append(batch, obj.Item)
|
||||||
|
|
||||||
if len(batch) < *r.Source.Snowball.Batch {
|
if len(batch) < *r.Source.Snowball.Batch {
|
||||||
continue
|
continue
|
||||||
@@ -1153,8 +1168,13 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
prevObj := ""
|
prevObj := ""
|
||||||
|
|
||||||
skipReplicate := false
|
skipReplicate := false
|
||||||
for result := range slowCh {
|
for res := range slowCh {
|
||||||
result := result
|
if res.Err != nil {
|
||||||
|
ri.Failed = true
|
||||||
|
batchLogIf(ctx, res.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result := res.Item
|
||||||
if result.Name != prevObj {
|
if result.Name != prevObj {
|
||||||
prevObj = result.Name
|
prevObj = result.Name
|
||||||
skipReplicate = result.DeleteMarker && s3Type
|
skipReplicate = result.DeleteMarker && s3Type
|
||||||
@@ -1183,7 +1203,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
} else {
|
} else {
|
||||||
stopFn(result, nil)
|
stopFn(result, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
|
ri.trackCurrentBucketObject(r.Source.Bucket, result, success, attempts)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
@@ -1231,6 +1251,7 @@ type batchReplicationJobError struct {
|
|||||||
Code string
|
Code string
|
||||||
Description string
|
Description string
|
||||||
HTTPStatusCode int
|
HTTPStatusCode int
|
||||||
|
ObjectSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e batchReplicationJobError) Error() string {
|
func (e batchReplicationJobError) Error() string {
|
||||||
@@ -1455,7 +1476,7 @@ func (j *BatchJobRequest) load(ctx context.Context, api ObjectLayer, name string
|
|||||||
|
|
||||||
func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, err error) {
|
func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, err error) {
|
||||||
// TODO: support custom storage class for remote replication
|
// TODO: support custom storage class for remote replication
|
||||||
putOpts, err = putReplicationOpts(ctx, "", objInfo)
|
putOpts, err = putReplicationOpts(ctx, "", objInfo, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return putOpts, err
|
return putOpts, err
|
||||||
}
|
}
|
||||||
@@ -1483,7 +1504,7 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
|||||||
jobType = string(madmin.BatchJobReplicate)
|
jobType = string(madmin.BatchJobReplicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultCh := make(chan ObjectInfo)
|
resultCh := make(chan itemOrErr[ObjectInfo])
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -1495,8 +1516,12 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
listResult := madmin.ListBatchJobsResult{}
|
listResult := madmin.ListBatchJobsResult{}
|
||||||
for result := range resultCh {
|
for result := range resultCh {
|
||||||
|
if result.Err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, result.Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
req := &BatchJobRequest{}
|
req := &BatchJobRequest{}
|
||||||
if err := req.load(ctx, objectAPI, result.Name); err != nil {
|
if err := req.load(ctx, objectAPI, result.Item.Name); err != nil {
|
||||||
if !errors.Is(err, errNoSuchJob) {
|
if !errors.Is(err, errNoSuchJob) {
|
||||||
batchLogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -1608,7 +1633,7 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job.ID = fmt.Sprintf("%s:%d", shortuuid.New(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
job.ID = fmt.Sprintf("%s%s%d", shortuuid.New(), getKeySeparator(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
job.User = user
|
job.User = user
|
||||||
job.Started = time.Now()
|
job.Started = time.Now()
|
||||||
|
|
||||||
@@ -1701,7 +1726,7 @@ func newBatchJobPool(ctx context.Context, o ObjectLayer, workers int) *BatchJobP
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *BatchJobPool) resume() {
|
func (j *BatchJobPool) resume() {
|
||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan itemOrErr[ObjectInfo], 100)
|
||||||
ctx, cancel := context.WithCancel(j.ctx)
|
ctx, cancel := context.WithCancel(j.ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobPrefix, results, WalkOptions{}); err != nil {
|
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobPrefix, results, WalkOptions{}); err != nil {
|
||||||
@@ -1709,12 +1734,16 @@ func (j *BatchJobPool) resume() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for result := range results {
|
for result := range results {
|
||||||
|
if result.Err != nil {
|
||||||
|
batchLogIf(j.ctx, result.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// ignore batch-replicate.bin and batch-rotate.bin entries
|
// ignore batch-replicate.bin and batch-rotate.bin entries
|
||||||
if strings.HasSuffix(result.Name, slashSeparator) {
|
if strings.HasSuffix(result.Item.Name, slashSeparator) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
req := &BatchJobRequest{}
|
req := &BatchJobRequest{}
|
||||||
if err := req.load(ctx, j.objLayer, result.Name); err != nil {
|
if err := req.load(ctx, j.objLayer, result.Item.Name); err != nil {
|
||||||
batchLogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,6 +419,12 @@ func (z *batchJobInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "RetryAttempts")
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "at":
|
||||||
|
z.Attempts, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
case "cmp":
|
case "cmp":
|
||||||
z.Complete, err = dc.ReadBool()
|
z.Complete, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -492,9 +498,9 @@ func (z *batchJobInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 16
|
// map header, size 17
|
||||||
// write "v"
|
// write "v"
|
||||||
err = en.Append(0xde, 0x0, 0x10, 0xa1, 0x76)
|
err = en.Append(0xde, 0x0, 0x11, 0xa1, 0x76)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -553,6 +559,16 @@ func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "RetryAttempts")
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "at"
|
||||||
|
err = en.Append(0xa2, 0x61, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.Attempts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
// write "cmp"
|
// write "cmp"
|
||||||
err = en.Append(0xa3, 0x63, 0x6d, 0x70)
|
err = en.Append(0xa3, 0x63, 0x6d, 0x70)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -659,9 +675,9 @@ func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *batchJobInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *batchJobInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 16
|
// map header, size 17
|
||||||
// string "v"
|
// string "v"
|
||||||
o = append(o, 0xde, 0x0, 0x10, 0xa1, 0x76)
|
o = append(o, 0xde, 0x0, 0x11, 0xa1, 0x76)
|
||||||
o = msgp.AppendInt(o, z.Version)
|
o = msgp.AppendInt(o, z.Version)
|
||||||
// string "jid"
|
// string "jid"
|
||||||
o = append(o, 0xa3, 0x6a, 0x69, 0x64)
|
o = append(o, 0xa3, 0x6a, 0x69, 0x64)
|
||||||
@@ -678,6 +694,9 @@ func (z *batchJobInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "ra"
|
// string "ra"
|
||||||
o = append(o, 0xa2, 0x72, 0x61)
|
o = append(o, 0xa2, 0x72, 0x61)
|
||||||
o = msgp.AppendInt(o, z.RetryAttempts)
|
o = msgp.AppendInt(o, z.RetryAttempts)
|
||||||
|
// string "at"
|
||||||
|
o = append(o, 0xa2, 0x61, 0x74)
|
||||||
|
o = msgp.AppendInt(o, z.Attempts)
|
||||||
// string "cmp"
|
// string "cmp"
|
||||||
o = append(o, 0xa3, 0x63, 0x6d, 0x70)
|
o = append(o, 0xa3, 0x63, 0x6d, 0x70)
|
||||||
o = msgp.AppendBool(o, z.Complete)
|
o = msgp.AppendBool(o, z.Complete)
|
||||||
@@ -765,6 +784,12 @@ func (z *batchJobInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "RetryAttempts")
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "at":
|
||||||
|
z.Attempts, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
case "cmp":
|
case "cmp":
|
||||||
z.Complete, bts, err = msgp.ReadBoolBytes(bts)
|
z.Complete, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -839,6 +864,6 @@ func (z *batchJobInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *batchJobInfo) Msgsize() (s int) {
|
func (z *batchJobInfo) Msgsize() (s int) {
|
||||||
s = 3 + 2 + msgp.IntSize + 4 + msgp.StringPrefixSize + len(z.JobID) + 3 + msgp.StringPrefixSize + len(z.JobType) + 3 + msgp.TimeSize + 3 + msgp.TimeSize + 3 + msgp.IntSize + 4 + msgp.BoolSize + 4 + msgp.BoolSize + 5 + msgp.StringPrefixSize + len(z.Bucket) + 5 + msgp.StringPrefixSize + len(z.Object) + 3 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 3 + msgp.Int64Size
|
s = 3 + 2 + msgp.IntSize + 4 + msgp.StringPrefixSize + len(z.JobID) + 3 + msgp.StringPrefixSize + len(z.JobType) + 3 + msgp.TimeSize + 3 + msgp.TimeSize + 3 + msgp.IntSize + 3 + msgp.IntSize + 4 + msgp.BoolSize + 4 + msgp.BoolSize + 5 + msgp.StringPrefixSize + len(z.Bucket) + 5 + msgp.StringPrefixSize + len(z.Object) + 3 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 3 + msgp.Int64Size
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/pkg/v2/wildcard"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+17
-16
@@ -33,8 +33,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/workers"
|
"github.com/minio/pkg/v3/workers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keyrotate:
|
// keyrotate:
|
||||||
@@ -95,6 +95,7 @@ func (e BatchJobKeyRotateEncryption) Validate() error {
|
|||||||
if e.Type == ssekms && spaces {
|
if e.Type == ssekms && spaces {
|
||||||
return crypto.ErrInvalidEncryptionKeyID
|
return crypto.ErrInvalidEncryptionKeyID
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Type == ssekms && GlobalKMS != nil {
|
if e.Type == ssekms && GlobalKMS != nil {
|
||||||
ctx := kms.Context{}
|
ctx := kms.Context{}
|
||||||
if e.Context != "" {
|
if e.Context != "" {
|
||||||
@@ -113,7 +114,7 @@ func (e BatchJobKeyRotateEncryption) Validate() error {
|
|||||||
e.kmsContext[k] = v
|
e.kmsContext[k] = v
|
||||||
}
|
}
|
||||||
ctx["MinIO batch API"] = "batchrotate" // Context for a test key operation
|
ctx["MinIO batch API"] = "batchrotate" // Context for a test key operation
|
||||||
if _, err := GlobalKMS.GenerateKey(GlobalContext, e.Key, ctx); err != nil {
|
if _, err := GlobalKMS.GenerateKey(GlobalContext, &kms.GenerateKeyRequest{Name: e.Key, AssociatedData: ctx}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,7 +357,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
retryAttempts := ri.RetryAttempts
|
retryAttempts := ri.RetryAttempts
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan itemOrErr[ObjectInfo], 100)
|
||||||
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
||||||
Marker: lastObject,
|
Marker: lastObject,
|
||||||
Filter: selectObj,
|
Filter: selectObj,
|
||||||
@@ -365,9 +366,14 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
// Do not need to retry if we can't list objects on source.
|
// Do not need to retry if we can't list objects on source.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
failed := false
|
||||||
for result := range results {
|
for res := range results {
|
||||||
result := result
|
if res.Err != nil {
|
||||||
|
failed = true
|
||||||
|
batchLogIf(ctx, res.Err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result := res.Item
|
||||||
sseKMS := crypto.S3KMS.IsEncrypted(result.UserDefined)
|
sseKMS := crypto.S3KMS.IsEncrypted(result.UserDefined)
|
||||||
sseS3 := crypto.S3.IsEncrypted(result.UserDefined)
|
sseS3 := crypto.S3.IsEncrypted(result.UserDefined)
|
||||||
if !sseKMS && !sseS3 { // neither sse-s3 nor sse-kms disallowed
|
if !sseKMS && !sseS3 { // neither sse-s3 nor sse-kms disallowed
|
||||||
@@ -377,7 +383,6 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
go func() {
|
go func() {
|
||||||
defer wk.Give()
|
defer wk.Give()
|
||||||
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
attempts := attempts
|
|
||||||
stopFn := globalBatchJobsMetrics.trace(batchJobMetricKeyRotation, job.ID, attempts)
|
stopFn := globalBatchJobsMetrics.trace(batchJobMetricKeyRotation, job.ID, attempts)
|
||||||
success := true
|
success := true
|
||||||
if err := r.KeyRotate(ctx, api, result); err != nil {
|
if err := r.KeyRotate(ctx, api, result); err != nil {
|
||||||
@@ -387,8 +392,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
} else {
|
} else {
|
||||||
stopFn(result, nil)
|
stopFn(result, nil)
|
||||||
}
|
}
|
||||||
ri.trackCurrentBucketObject(r.Bucket, result, success)
|
ri.trackCurrentBucketObject(r.Bucket, result, success, attempts)
|
||||||
ri.RetryAttempts = attempts
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
@@ -407,8 +411,8 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
}
|
}
|
||||||
wk.Wait()
|
wk.Wait()
|
||||||
|
|
||||||
ri.Complete = ri.ObjectsFailed == 0
|
ri.Complete = !failed && ri.ObjectsFailed == 0
|
||||||
ri.Failed = ri.ObjectsFailed > 0
|
ri.Failed = failed || ri.ObjectsFailed > 0
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
@@ -475,8 +479,5 @@ func (r *BatchJobKeyRotateV1) Validate(ctx context.Context, job BatchJobRequest,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.Flags.Retry.Validate(); err != nil {
|
return r.Flags.Retry.Validate()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-9
@@ -26,15 +26,17 @@ import (
|
|||||||
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
|
"github.com/minio/minio/internal/ringbuffer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Calculates bitrot in chunks and writes the hash into the stream.
|
// Calculates bitrot in chunks and writes the hash into the stream.
|
||||||
type streamingBitrotWriter struct {
|
type streamingBitrotWriter struct {
|
||||||
iow io.WriteCloser
|
iow io.WriteCloser
|
||||||
closeWithErr func(err error) error
|
closeWithErr func(err error)
|
||||||
h hash.Hash
|
h hash.Hash
|
||||||
shardSize int64
|
shardSize int64
|
||||||
canClose *sync.WaitGroup
|
canClose *sync.WaitGroup
|
||||||
|
byteBuf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
||||||
@@ -62,7 +64,10 @@ func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *streamingBitrotWriter) Close() error {
|
func (b *streamingBitrotWriter) Close() error {
|
||||||
|
// Close the underlying writer.
|
||||||
|
// This will also flush the ring buffer if used.
|
||||||
err := b.iow.Close()
|
err := b.iow.Close()
|
||||||
|
|
||||||
// Wait for all data to be written before returning else it causes race conditions.
|
// Wait for all data to be written before returning else it causes race conditions.
|
||||||
// Race condition is because of io.PipeWriter implementation. i.e consider the following
|
// Race condition is because of io.PipeWriter implementation. i.e consider the following
|
||||||
// sequent of operations:
|
// sequent of operations:
|
||||||
@@ -73,29 +78,34 @@ func (b *streamingBitrotWriter) Close() error {
|
|||||||
if b.canClose != nil {
|
if b.canClose != nil {
|
||||||
b.canClose.Wait()
|
b.canClose.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recycle the buffer.
|
||||||
|
if b.byteBuf != nil {
|
||||||
|
globalBytePoolCap.Load().Put(b.byteBuf)
|
||||||
|
b.byteBuf = nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStreamingBitrotWriterBuffer returns streaming bitrot writer implementation.
|
// newStreamingBitrotWriterBuffer returns streaming bitrot writer implementation.
|
||||||
// The output is written to the supplied writer w.
|
// The output is written to the supplied writer w.
|
||||||
func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||||
return &streamingBitrotWriter{iow: ioutil.NopCloser(w), h: algo.New(), shardSize: shardSize, canClose: nil, closeWithErr: func(err error) error {
|
return &streamingBitrotWriter{iow: ioutil.NopCloser(w), h: algo.New(), shardSize: shardSize, canClose: nil, closeWithErr: func(err error) {}}
|
||||||
// Similar to CloseWithError on pipes we always return nil.
|
|
||||||
return nil
|
|
||||||
}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns streaming bitrot writer implementation.
|
// Returns streaming bitrot writer implementation.
|
||||||
func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||||
r, w := io.Pipe()
|
|
||||||
h := algo.New()
|
h := algo.New()
|
||||||
|
buf := globalBytePoolCap.Load().Get()
|
||||||
|
rb := ringbuffer.NewBuffer(buf[:cap(buf)]).SetBlocking(true)
|
||||||
|
|
||||||
bw := &streamingBitrotWriter{
|
bw := &streamingBitrotWriter{
|
||||||
iow: ioutil.NewDeadlineWriter(w, globalDriveConfig.GetMaxTimeout()),
|
iow: ioutil.NewDeadlineWriter(rb.WriteCloser(), globalDriveConfig.GetMaxTimeout()),
|
||||||
closeWithErr: w.CloseWithError,
|
closeWithErr: rb.CloseWithError,
|
||||||
h: h,
|
h: h,
|
||||||
shardSize: shardSize,
|
shardSize: shardSize,
|
||||||
canClose: &sync.WaitGroup{},
|
canClose: &sync.WaitGroup{},
|
||||||
|
byteBuf: buf,
|
||||||
}
|
}
|
||||||
bw.canClose.Add(1)
|
bw.canClose.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -106,7 +116,7 @@ func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath stri
|
|||||||
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
||||||
totalFileSize = bitrotSumsTotalSize + length
|
totalFileSize = bitrotSumsTotalSize + length
|
||||||
}
|
}
|
||||||
r.CloseWithError(disk.CreateFile(context.TODO(), origvolume, volume, filePath, totalFileSize, r))
|
rb.CloseWithError(disk.CreateFile(context.TODO(), origvolume, volume, filePath, totalFileSize, rb))
|
||||||
}()
|
}()
|
||||||
return bw
|
return bw
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/grid"
|
"github.com/minio/minio/internal/grid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// To abstract a node over network.
|
// To abstract a node over network.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -85,7 +85,7 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
kmsKey := encConfig.KeyID()
|
kmsKey := encConfig.KeyID()
|
||||||
if kmsKey != "" {
|
if kmsKey != "" {
|
||||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
||||||
_, err := GlobalKMS.GenerateKey(ctx, kmsKey, kmsContext)
|
_, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{Name: kmsKey, AssociatedData: kmsContext})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, kes.ErrKeyNotFound) {
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errKMSKeyNotFound), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, errKMSKeyNotFound), r.URL)
|
||||||
|
|||||||
+18
-3
@@ -61,8 +61,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/ioutil"
|
"github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -72,6 +72,8 @@ const (
|
|||||||
|
|
||||||
xMinIOErrCodeHeader = "x-minio-error-code"
|
xMinIOErrCodeHeader = "x-minio-error-code"
|
||||||
xMinIOErrDescHeader = "x-minio-error-desc"
|
xMinIOErrDescHeader = "x-minio-error-desc"
|
||||||
|
|
||||||
|
postPolicyBucketTagging = "tagging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if there are buckets on server without corresponding entry in etcd backend and
|
// Check if there are buckets on server without corresponding entry in etcd backend and
|
||||||
@@ -227,7 +229,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
|||||||
// Generate response.
|
// Generate response.
|
||||||
encodedSuccessResponse := encodeResponse(LocationResponse{})
|
encodedSuccessResponse := encodeResponse(LocationResponse{})
|
||||||
// Get current region.
|
// Get current region.
|
||||||
region := globalSite.Region
|
region := globalSite.Region()
|
||||||
if region != globalMinioDefaultRegion {
|
if region != globalMinioDefaultRegion {
|
||||||
encodedSuccessResponse = encodeResponse(LocationResponse{
|
encodedSuccessResponse = encodeResponse(LocationResponse{
|
||||||
Location: region,
|
Location: region,
|
||||||
@@ -1415,6 +1417,19 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if formValues.Get(postPolicyBucketTagging) != "" {
|
||||||
|
tags, err := tags.ParseObjectXML(strings.NewReader(formValues.Get(postPolicyBucketTagging)))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tagsStr := tags.String()
|
||||||
|
opts.UserDefined[xhttp.AmzObjectTagging] = tagsStr
|
||||||
|
} else {
|
||||||
|
// avoid user set an invalid tag using `X-Amz-Tagging`
|
||||||
|
delete(opts.UserDefined, xhttp.AmzObjectTagging)
|
||||||
|
}
|
||||||
|
|
||||||
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
|
|
||||||
// Wrapper for calling RemoveBucket HTTP handler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling RemoveBucket HTTP handler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestRemoveBucketHandler(t *testing.T) {
|
func TestRemoveBucketHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testRemoveBucketHandler, []string{"RemoveBucket"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testRemoveBucketHandler, endpoints: []string{"RemoveBucket"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
@@ -78,7 +78,7 @@ func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, a
|
|||||||
|
|
||||||
// Wrapper for calling GetBucketPolicy HTTP handler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling GetBucketPolicy HTTP handler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestGetBucketLocationHandler(t *testing.T) {
|
func TestGetBucketLocationHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testGetBucketLocationHandler, []string{"GetBucketLocation"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testGetBucketLocationHandler, endpoints: []string{"GetBucketLocation"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
@@ -220,7 +220,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
|||||||
|
|
||||||
// Wrapper for calling HeadBucket HTTP handler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling HeadBucket HTTP handler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestHeadBucketHandler(t *testing.T) {
|
func TestHeadBucketHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testHeadBucketHandler, []string{"HeadBucket"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testHeadBucketHandler, endpoints: []string{"HeadBucket"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
@@ -322,7 +322,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
|||||||
|
|
||||||
// Wrapper for calling TestListMultipartUploadsHandler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling TestListMultipartUploadsHandler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestListMultipartUploadsHandler(t *testing.T) {
|
func TestListMultipartUploadsHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testListMultipartUploadsHandler, []string{"ListMultipartUploads"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testListMultipartUploadsHandler, endpoints: []string{"ListMultipartUploads"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testListMultipartUploadsHandler - Tests validate listing of multipart uploads.
|
// testListMultipartUploadsHandler - Tests validate listing of multipart uploads.
|
||||||
@@ -558,7 +558,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
|||||||
|
|
||||||
// Wrapper for calling TestListBucketsHandler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling TestListBucketsHandler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestListBucketsHandler(t *testing.T) {
|
func TestListBucketsHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testListBucketsHandler, []string{"ListBuckets"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testListBucketsHandler, endpoints: []string{"ListBuckets"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testListBucketsHandler - Tests validate listing of buckets.
|
// testListBucketsHandler - Tests validate listing of buckets.
|
||||||
@@ -649,7 +649,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
|||||||
|
|
||||||
// Wrapper for calling DeleteMultipleObjects HTTP handler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling DeleteMultipleObjects HTTP handler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestAPIDeleteMultipleObjectsHandler(t *testing.T) {
|
func TestAPIDeleteMultipleObjectsHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testAPIDeleteMultipleObjectsHandler, []string{"DeleteMultipleObjects", "PutBucketPolicy"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testAPIDeleteMultipleObjectsHandler, endpoints: []string{"DeleteMultipleObjects", "PutBucketPolicy"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -64,7 +64,8 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bucket exists.
|
// Check if bucket exists.
|
||||||
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
rcfg, err := globalBucketObjectLockSys.Get(bucket)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,7 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate the received bucket policy document
|
// Validate the received bucket policy document
|
||||||
if err = bucketLifecycle.Validate(); err != nil {
|
if err = bucketLifecycle.Validate(rcfg); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
|
|
||||||
// Test S3 Bucket lifecycle APIs with wrong credentials
|
// Test S3 Bucket lifecycle APIs with wrong credentials
|
||||||
func TestBucketLifecycleWrongCredentials(t *testing.T) {
|
func TestBucketLifecycleWrongCredentials(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testBucketLifecycleHandlersWrongCredentials, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testBucketLifecycleHandlersWrongCredentials, endpoints: []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for authentication
|
// Test for authentication
|
||||||
@@ -145,7 +145,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
|
|||||||
|
|
||||||
// Test S3 Bucket lifecycle APIs
|
// Test S3 Bucket lifecycle APIs
|
||||||
func TestBucketLifecycle(t *testing.T) {
|
func TestBucketLifecycle(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testBucketLifecycleHandlers, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testBucketLifecycleHandlers, endpoints: []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple tests of bucket lifecycle: PUT, GET, DELETE.
|
// Simple tests of bucket lifecycle: PUT, GET, DELETE.
|
||||||
|
|||||||
+18
-3
@@ -40,7 +40,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/s3select"
|
"github.com/minio/minio/internal/s3select"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
"github.com/zeebo/xxh3"
|
"github.com/zeebo/xxh3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ func NewLifecycleSys() *LifecycleSys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string) madmin.TraceInfo {
|
func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string) madmin.TraceInfo {
|
||||||
|
sz, _ := oi.GetActualSize()
|
||||||
return madmin.TraceInfo{
|
return madmin.TraceInfo{
|
||||||
TraceType: madmin.TraceILM,
|
TraceType: madmin.TraceILM,
|
||||||
Time: startTime,
|
Time: startTime,
|
||||||
@@ -79,6 +80,7 @@ func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event
|
|||||||
FuncName: event,
|
FuncName: event,
|
||||||
Duration: duration,
|
Duration: duration,
|
||||||
Path: pathJoin(oi.Bucket, oi.Name),
|
Path: pathJoin(oi.Bucket, oi.Name),
|
||||||
|
Bytes: sz,
|
||||||
Error: "",
|
Error: "",
|
||||||
Message: getSource(4),
|
Message: getSource(4),
|
||||||
Custom: map[string]string{"version-id": oi.VersionID},
|
Custom: map[string]string{"version-id": oi.VersionID},
|
||||||
@@ -277,6 +279,10 @@ func (es *expiryState) getWorkerCh(h uint64) chan<- expiryOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (es *expiryState) ResizeWorkers(n int) {
|
func (es *expiryState) ResizeWorkers(n int) {
|
||||||
|
if n == 0 {
|
||||||
|
n = 100
|
||||||
|
}
|
||||||
|
|
||||||
// Lock to avoid multiple resizes to happen at the same time.
|
// Lock to avoid multiple resizes to happen at the same time.
|
||||||
es.mu.Lock()
|
es.mu.Lock()
|
||||||
defer es.mu.Unlock()
|
defer es.mu.Unlock()
|
||||||
@@ -538,6 +544,10 @@ func (t *transitionState) UpdateWorkers(n int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *transitionState) updateWorkers(n int) {
|
func (t *transitionState) updateWorkers(n int) {
|
||||||
|
if n == 0 {
|
||||||
|
n = 100
|
||||||
|
}
|
||||||
|
|
||||||
for t.numWorkers < n {
|
for t.numWorkers < n {
|
||||||
go t.worker(t.objAPI)
|
go t.worker(t.objAPI)
|
||||||
t.numWorkers++
|
t.numWorkers++
|
||||||
@@ -573,6 +583,10 @@ func enqueueTransitionImmediate(obj ObjectInfo, src lcEventSrc) {
|
|||||||
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
|
||||||
switch event := lc.Eval(obj.ToLifecycleOpts()); event.Action {
|
switch event := lc.Eval(obj.ToLifecycleOpts()); event.Action {
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
|
if obj.DeleteMarker || obj.IsDir {
|
||||||
|
// nothing to transition
|
||||||
|
return
|
||||||
|
}
|
||||||
globalTransitionState.queueTransitionTask(obj, event, src)
|
globalTransitionState.queueTransitionTask(obj, event, src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,11 +677,12 @@ func genTransitionObjName(bucket string) (string, error) {
|
|||||||
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
||||||
// to the transition tier without decrypting or re-encrypting.
|
// to the transition tier without decrypting or re-encrypting.
|
||||||
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) (err error) {
|
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) (err error) {
|
||||||
|
timeILM := globalScannerMetrics.timeILM(lae.Action)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
globalScannerMetrics.timeILM(lae.Action)(1)
|
timeILM(1)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
@@ -726,7 +741,7 @@ func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs
|
|||||||
return nil, fmt.Errorf("transition storage class not configured: %w", err)
|
return nil, fmt.Errorf("transition storage class not configured: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn, off, length, err := NewGetObjectReader(rs, oi, opts)
|
fn, off, length, err := NewGetObjectReader(rs, oi, opts, h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorRespToObjectError(err, bucket, object)
|
return nil, ErrorRespToObjectError(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
|
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
||||||
@@ -124,7 +124,7 @@ func (api objectAPIHandlers) listObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo, checkObjMeta)
|
response := generateListVersionsResponse(ctx, bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo, checkObjMeta)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
@@ -202,7 +202,7 @@ func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.Re
|
|||||||
|
|
||||||
if r.Header.Get(xMinIOExtract) == "true" && strings.Contains(prefix, archivePattern) {
|
if r.Header.Get(xMinIOExtract) == "true" && strings.Contains(prefix, archivePattern) {
|
||||||
// Initiate a list objects operation inside a zip file based in the input params
|
// Initiate a list objects operation inside a zip file based in the input params
|
||||||
listObjectsV2Info, err = listObjectsV2InArchive(ctx, objectAPI, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter)
|
listObjectsV2Info, err = listObjectsV2InArchive(ctx, objectAPI, bucket, prefix, token, delimiter, maxKeys, startAfter, r.Header)
|
||||||
} else {
|
} else {
|
||||||
// Initiate a list objects operation based on the input params.
|
// Initiate a list objects operation based on the input params.
|
||||||
// On success would return back ListObjectsInfo object to be
|
// On success would return back ListObjectsInfo object to be
|
||||||
@@ -219,7 +219,7 @@ func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := generateListObjectsV2Response(bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter,
|
response := generateListObjectsV2Response(ctx, bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter,
|
||||||
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
|
||||||
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, checkObjMeta)
|
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, checkObjMeta)
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
|||||||
if token == "" {
|
if token == "" {
|
||||||
return token, -1
|
return token, -1
|
||||||
}
|
}
|
||||||
i := strings.Index(token, ":")
|
i := strings.Index(token, getKeySeparator())
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return token, -1
|
return token, -1
|
||||||
}
|
}
|
||||||
@@ -318,7 +318,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
|
response := generateListObjectsV1Response(ctx, bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/fips"
|
"github.com/minio/minio/internal/fips"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -490,7 +490,7 @@ func encryptBucketMetadata(ctx context.Context, bucket string, input []byte, kms
|
|||||||
}
|
}
|
||||||
|
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
key, err := GlobalKMS.GenerateKey(ctx, "", kmsContext)
|
key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{AssociatedData: kmsContext})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -519,7 +519,11 @@ func decryptBucketMetadata(input []byte, bucket string, meta map[string]string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extKey, err := GlobalKMS.DecryptKey(keyID, kmsKey, kmsContext)
|
extKey, err := GlobalKMS.Decrypt(context.TODO(), &kms.DecryptRequest{
|
||||||
|
Name: keyID,
|
||||||
|
Ciphertext: kmsKey,
|
||||||
|
AssociatedData: kmsContext,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -66,8 +66,9 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config.SetRegion(globalSite.Region)
|
region := globalSite.Region()
|
||||||
if err = config.Validate(globalSite.Region, globalEventNotifier.targetList); err != nil {
|
config.SetRegion(region)
|
||||||
|
if err = config.Validate(region, globalEventNotifier.targetList); err != nil {
|
||||||
arnErr, ok := err.(*event.ErrARNNotFound)
|
arnErr, ok := err.(*event.ErrARNNotFound)
|
||||||
if ok {
|
if ok {
|
||||||
for i, queue := range config.QueueList {
|
for i, queue := range config.QueueList {
|
||||||
@@ -134,7 +135,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region, globalEventNotifier.targetList)
|
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region(), globalEventNotifier.targetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
|
||||||
if event.IsEventError(err) {
|
if event.IsEventError(err) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketObjectLockSys - map of bucket and retention configuration.
|
// BucketObjectLockSys - map of bucket and retention configuration.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"github.com/minio/pkg/v2/policy/condition"
|
"github.com/minio/pkg/v3/policy/condition"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
|
||||||
@@ -107,7 +107,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolic
|
|||||||
|
|
||||||
// Wrapper for calling Create Bucket and ensure we get one and only one success.
|
// Wrapper for calling Create Bucket and ensure we get one and only one success.
|
||||||
func TestCreateBucket(t *testing.T) {
|
func TestCreateBucket(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testCreateBucket, []string{"MakeBucket"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testCreateBucket, endpoints: []string{"MakeBucket"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success.
|
// testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success.
|
||||||
@@ -154,7 +154,7 @@ func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRoute
|
|||||||
|
|
||||||
// Wrapper for calling Put Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling Put Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestPutBucketPolicyHandler(t *testing.T) {
|
func TestPutBucketPolicyHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testPutBucketPolicyHandler, []string{"PutBucketPolicy"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testPutBucketPolicyHandler, endpoints: []string{"PutBucketPolicy"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testPutBucketPolicyHandler - Test for Bucket policy end point.
|
// testPutBucketPolicyHandler - Test for Bucket policy end point.
|
||||||
@@ -373,7 +373,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
|
|
||||||
// Wrapper for calling Get Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling Get Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestGetBucketPolicyHandler(t *testing.T) {
|
func TestGetBucketPolicyHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testGetBucketPolicyHandler, []string{"PutBucketPolicy", "GetBucketPolicy"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testGetBucketPolicyHandler, endpoints: []string{"PutBucketPolicy", "GetBucketPolicy"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket.
|
// testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket.
|
||||||
@@ -577,7 +577,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
|
|
||||||
// Wrapper for calling Delete Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
|
// Wrapper for calling Delete Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
|
||||||
func TestDeleteBucketPolicyHandler(t *testing.T) {
|
func TestDeleteBucketPolicyHandler(t *testing.T) {
|
||||||
ExecObjectLayerAPITest(t, testDeleteBucketPolicyHandler, []string{"PutBucketPolicy", "DeleteBucketPolicy"})
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testDeleteBucketPolicyHandler, endpoints: []string{"PutBucketPolicy", "DeleteBucketPolicy"}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testDeleteBucketPolicyHandler - Test for Delete bucket policy end point.
|
// testDeleteBucketPolicyHandler - Test for Delete bucket policy end point.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PolicySys - policy subsystem.
|
// PolicySys - policy subsystem.
|
||||||
|
|||||||
+5
-5
@@ -49,8 +49,8 @@ var bucketStorageCache = cachevalue.New[DataUsageInfo]()
|
|||||||
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
||||||
bucketStorageCache.InitOnce(10*time.Second,
|
bucketStorageCache.InitOnce(10*time.Second,
|
||||||
cachevalue.Opts{ReturnLastGood: true, NoWait: true},
|
cachevalue.Opts{ReturnLastGood: true, NoWait: true},
|
||||||
func() (DataUsageInfo, error) {
|
func(ctx context.Context) (DataUsageInfo, error) {
|
||||||
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
ctx, done := context.WithTimeout(ctx, 2*time.Second)
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
return loadDataUsageFromBackend(ctx, objAPI)
|
return loadDataUsageFromBackend(ctx, objAPI)
|
||||||
@@ -59,8 +59,8 @@ func (sys *BucketQuotaSys) Init(objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketUsageInfo return bucket usage info for a given bucket
|
// GetBucketUsageInfo return bucket usage info for a given bucket
|
||||||
func (sys *BucketQuotaSys) GetBucketUsageInfo(bucket string) (BucketUsageInfo, error) {
|
func (sys *BucketQuotaSys) GetBucketUsageInfo(ctx context.Context, bucket string) (BucketUsageInfo, error) {
|
||||||
dui, err := bucketStorageCache.Get()
|
dui, err := bucketStorageCache.GetWithCtx(ctx)
|
||||||
timedout := OperationTimedOut{}
|
timedout := OperationTimedOut{}
|
||||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
|
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &timedout) {
|
||||||
if len(dui.BucketsUsage) > 0 {
|
if len(dui.BucketsUsage) > 0 {
|
||||||
@@ -118,7 +118,7 @@ func (sys *BucketQuotaSys) enforceQuotaHard(ctx context.Context, bucket string,
|
|||||||
return BucketQuotaExceeded{Bucket: bucket}
|
return BucketQuotaExceeded{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
bui, err := sys.GetBucketUsageInfo(bucket)
|
bui, err := sys.GetBucketUsageInfo(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) Replicate
|
|||||||
rstate.ReplicateDecisionStr = dsc.String()
|
rstate.ReplicateDecisionStr = dsc.String()
|
||||||
asz, _ := oi.GetActualSize()
|
asz, _ := oi.GetActualSize()
|
||||||
|
|
||||||
return ReplicateObjectInfo{
|
r := ReplicateObjectInfo{
|
||||||
Name: oi.Name,
|
Name: oi.Name,
|
||||||
Size: oi.Size,
|
Size: oi.Size,
|
||||||
ActualSize: asz,
|
ActualSize: asz,
|
||||||
@@ -558,6 +558,10 @@ func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) Replicate
|
|||||||
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
UserTags: oi.UserTags,
|
UserTags: oi.UserTags,
|
||||||
}
|
}
|
||||||
|
if r.SSEC {
|
||||||
|
r.Checksum = oi.Checksum
|
||||||
|
}
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplicationState - returns replication state using other internal replication metadata in ObjectInfo
|
// ReplicationState - returns replication state using other internal replication metadata in ObjectInfo
|
||||||
|
|||||||
+135
-49
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -74,8 +75,9 @@ const (
|
|||||||
ObjectLockRetentionTimestamp = "objectlock-retention-timestamp"
|
ObjectLockRetentionTimestamp = "objectlock-retention-timestamp"
|
||||||
// ObjectLockLegalHoldTimestamp - the last time a legal hold metadata modification happened on this cluster for this object version
|
// ObjectLockLegalHoldTimestamp - the last time a legal hold metadata modification happened on this cluster for this object version
|
||||||
ObjectLockLegalHoldTimestamp = "objectlock-legalhold-timestamp"
|
ObjectLockLegalHoldTimestamp = "objectlock-legalhold-timestamp"
|
||||||
// ReplicationWorkerMultiplier is suggested worker multiplier if traffic exceeds replication worker capacity
|
|
||||||
ReplicationWorkerMultiplier = 1.5
|
// ReplicationSsecChecksumHeader - the encrypted checksum of the SSE-C encrypted object.
|
||||||
|
ReplicationSsecChecksumHeader = "X-Minio-Replication-Ssec-Crc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// gets replication config associated to a given bucket name.
|
// gets replication config associated to a given bucket name.
|
||||||
@@ -763,12 +765,34 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) {
|
func getCRCMeta(oi ObjectInfo, partNum int, h http.Header) map[string]string {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
|
cs := oi.decryptChecksums(partNum, h)
|
||||||
|
for k, v := range cs {
|
||||||
|
cksum := hash.NewChecksumString(k, v)
|
||||||
|
if cksum == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cksum.Valid() {
|
||||||
|
meta[cksum.Type.Key()] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, partNum int) (putOpts minio.PutObjectOptions, err error) {
|
||||||
|
meta := make(map[string]string)
|
||||||
|
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
|
||||||
|
|
||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
// In case of SSE-C objects copy the allowed internal headers as well
|
// In case of SSE-C objects copy the allowed internal headers as well
|
||||||
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
if !isSSEC || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
|
if strings.EqualFold(k, ReservedMetadataPrefixLower+"crc") {
|
||||||
|
for k, v := range getCRCMeta(objInfo, partNum, nil) {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isStandardHeader(k) {
|
if isStandardHeader(k) {
|
||||||
@@ -782,6 +806,17 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (put
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(objInfo.Checksum) > 0 {
|
||||||
|
// Add encrypted CRC to metadata for SSE-C objects.
|
||||||
|
if isSSEC {
|
||||||
|
meta[ReplicationSsecChecksumHeader] = base64.StdEncoding.EncodeToString(objInfo.Checksum)
|
||||||
|
} else {
|
||||||
|
for k, v := range getCRCMeta(objInfo, 0, nil) {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
||||||
sc = objInfo.StorageClass
|
sc = objInfo.StorageClass
|
||||||
}
|
}
|
||||||
@@ -998,7 +1033,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
object := ri.Name
|
object := ri.Name
|
||||||
|
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil || cfg == nil {
|
||||||
replLogOnceIf(ctx, err, "get-replication-config-"+bucket)
|
replLogOnceIf(ctx, err, "get-replication-config-"+bucket)
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
@@ -1201,17 +1236,23 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
// make sure we have the latest metadata for metrics calculation
|
// make sure we have the latest metadata for metrics calculation
|
||||||
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
||||||
|
|
||||||
size, err := objInfo.GetActualSize()
|
// Set the encrypted size for SSE-C objects
|
||||||
if err != nil {
|
var size int64
|
||||||
replLogIf(ctx, err)
|
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||||
sendEvent(eventArgs{
|
size = objInfo.Size
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
} else {
|
||||||
BucketName: bucket,
|
size, err = objInfo.GetActualSize()
|
||||||
Object: objInfo,
|
if err != nil {
|
||||||
UserAgent: "Internal: [Replication]",
|
replLogIf(ctx, err)
|
||||||
Host: globalLocalNodeName,
|
sendEvent(eventArgs{
|
||||||
})
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
return
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
UserAgent: "Internal: [Replication]",
|
||||||
|
Host: globalLocalNodeName,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tgt.Bucket == "" {
|
if tgt.Bucket == "" {
|
||||||
@@ -1239,7 +1280,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
// use core client to avoid doing multipart on PUT
|
// use core client to avoid doing multipart on PUT
|
||||||
c := &minio.Core{Client: tgt.Client}
|
c := &minio.Core{Client: tgt.Client}
|
||||||
|
|
||||||
putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err))
|
replLogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
@@ -1377,7 +1418,8 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the encrypted size for SSE-C objects
|
// Set the encrypted size for SSE-C objects
|
||||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
|
||||||
|
if isSSEC {
|
||||||
size = objInfo.Size
|
size = objInfo.Size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1437,6 +1479,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// SSEC objects will refuse HeadObject without the decryption key.
|
||||||
|
// Ignore the error, since we know the object exists and versioning prevents overwriting existing versions.
|
||||||
|
if isSSEC && strings.Contains(cerr.Error(), errorCodes[ErrSSEEncryptedObject].Description) {
|
||||||
|
rinfo.ReplicationStatus = replication.Completed
|
||||||
|
rinfo.ReplicationAction = replicateNone
|
||||||
|
goto applyAction
|
||||||
|
}
|
||||||
// if target returns error other than NoSuchKey, defer replication attempt
|
// if target returns error other than NoSuchKey, defer replication attempt
|
||||||
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())
|
||||||
@@ -1464,6 +1513,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
applyAction:
|
||||||
rinfo.ReplicationStatus = replication.Completed
|
rinfo.ReplicationStatus = replication.Completed
|
||||||
rinfo.Size = size
|
rinfo.Size = size
|
||||||
rinfo.ReplicationAction = rAction
|
rinfo.ReplicationAction = rAction
|
||||||
@@ -1506,7 +1556,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var putOpts minio.PutObjectOptions
|
var putOpts minio.PutObjectOptions
|
||||||
putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err))
|
replLogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err))
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
@@ -1542,13 +1592,15 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
} else {
|
} else {
|
||||||
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
|
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
|
||||||
}
|
}
|
||||||
if rinfo.Err != nil && minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
if rinfo.Err != nil {
|
||||||
rinfo.ReplicationStatus = replication.Failed
|
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||||
replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w",
|
rinfo.ReplicationStatus = replication.Failed
|
||||||
bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w",
|
||||||
}
|
bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||||
if rinfo.Err != nil && minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
}
|
||||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
if minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||||
|
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -1568,7 +1620,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if minio.ToErrorResponse(err).Code == "PreconditionFailed" {
|
if minio.ToErrorResponse(err).Code == "PreconditionFailed" {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
attempts++
|
attempts++
|
||||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||||
@@ -1595,13 +1647,14 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hr *hash.Reader
|
hr *hash.Reader
|
||||||
pInfo minio.ObjectPart
|
pInfo minio.ObjectPart
|
||||||
|
isSSEC = crypto.SSEC.IsEncrypted(objInfo.UserDefined)
|
||||||
)
|
)
|
||||||
|
|
||||||
var objectSize int64
|
var objectSize int64
|
||||||
for _, partInfo := range objInfo.Parts {
|
for _, partInfo := range objInfo.Parts {
|
||||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
if isSSEC {
|
||||||
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize)
|
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize)
|
||||||
} else {
|
} else {
|
||||||
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
||||||
@@ -1612,12 +1665,18 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
|
|
||||||
cHeader := http.Header{}
|
cHeader := http.Header{}
|
||||||
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
|
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
|
||||||
|
if !isSSEC {
|
||||||
|
crc := getCRCMeta(objInfo, partInfo.Number, nil) // No SSE-C keys here.
|
||||||
|
for k, v := range crc {
|
||||||
|
cHeader.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
popts := minio.PutObjectPartOptions{
|
popts := minio.PutObjectPartOptions{
|
||||||
SSE: opts.ServerSideEncryption,
|
SSE: opts.ServerSideEncryption,
|
||||||
CustomHeader: cHeader,
|
CustomHeader: cHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
if isSSEC {
|
||||||
objectSize += partInfo.Size
|
objectSize += partInfo.Size
|
||||||
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts)
|
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts)
|
||||||
} else {
|
} else {
|
||||||
@@ -1627,22 +1686,33 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) && pInfo.Size != partInfo.ActualSize {
|
if !isSSEC && pInfo.Size != partInfo.ActualSize {
|
||||||
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
|
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
|
||||||
}
|
}
|
||||||
uploadedParts = append(uploadedParts, minio.CompletePart{
|
uploadedParts = append(uploadedParts, minio.CompletePart{
|
||||||
PartNumber: pInfo.PartNumber,
|
PartNumber: pInfo.PartNumber,
|
||||||
ETag: pInfo.ETag,
|
ETag: pInfo.ETag,
|
||||||
|
ChecksumCRC32: pInfo.ChecksumCRC32,
|
||||||
|
ChecksumCRC32C: pInfo.ChecksumCRC32C,
|
||||||
|
ChecksumSHA1: pInfo.ChecksumSHA1,
|
||||||
|
ChecksumSHA256: pInfo.ChecksumSHA256,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
userMeta := map[string]string{
|
||||||
|
validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"],
|
||||||
|
}
|
||||||
|
if isSSEC && objInfo.UserDefined[ReplicationSsecChecksumHeader] != "" {
|
||||||
|
userMeta[ReplicationSsecChecksumHeader] = objInfo.UserDefined[ReplicationSsecChecksumHeader]
|
||||||
|
}
|
||||||
|
|
||||||
// really big value but its okay on heavily loaded systems. This is just tail end timeout.
|
// really big value but its okay on heavily loaded systems. This is just tail end timeout.
|
||||||
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
|
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||||
defer ccancel()
|
defer ccancel()
|
||||||
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
|
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
|
||||||
UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]},
|
UserMetadata: userMeta,
|
||||||
Internal: minio.AdvancedPutOptions{
|
Internal: minio.AdvancedPutOptions{
|
||||||
SourceMTime: objInfo.ModTime,
|
SourceMTime: objInfo.ModTime,
|
||||||
|
SourceETag: objInfo.ETag,
|
||||||
// always set this to distinguish between `mc mirror` replication and serverside
|
// always set this to distinguish between `mc mirror` replication and serverside
|
||||||
ReplicationRequest: true,
|
ReplicationRequest: true,
|
||||||
},
|
},
|
||||||
@@ -2176,12 +2246,12 @@ type proxyResult struct {
|
|||||||
|
|
||||||
// get Reader from replication target if active-active replication is in place and
|
// get Reader from replication target if active-active replication is in place and
|
||||||
// this node returns a 404
|
// this node returns a 404
|
||||||
func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, _ http.Header, opts ObjectOptions, proxyTargets *madmin.BucketTargets) (gr *GetObjectReader, proxy proxyResult, err error) {
|
func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions, proxyTargets *madmin.BucketTargets) (gr *GetObjectReader, proxy proxyResult, err error) {
|
||||||
tgt, oi, proxy := proxyHeadToRepTarget(ctx, bucket, object, rs, opts, proxyTargets)
|
tgt, oi, proxy := proxyHeadToRepTarget(ctx, bucket, object, rs, opts, proxyTargets)
|
||||||
if !proxy.Proxy {
|
if !proxy.Proxy {
|
||||||
return nil, proxy, nil
|
return nil, proxy, nil
|
||||||
}
|
}
|
||||||
fn, _, _, err := NewGetObjectReader(nil, oi, opts)
|
fn, _, _, err := NewGetObjectReader(nil, oi, opts, h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, proxy, err
|
return nil, proxy, err
|
||||||
}
|
}
|
||||||
@@ -2366,7 +2436,9 @@ func scheduleReplication(ctx context.Context, oi ObjectInfo, o ObjectLayer, dsc
|
|||||||
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
UserTags: oi.UserTags,
|
UserTags: oi.UserTags,
|
||||||
}
|
}
|
||||||
|
if ri.SSEC {
|
||||||
|
ri.Checksum = oi.Checksum
|
||||||
|
}
|
||||||
if dsc.Synchronous() {
|
if dsc.Synchronous() {
|
||||||
replicateObject(ctx, ri, o)
|
replicateObject(ctx, ri, o)
|
||||||
} else {
|
} else {
|
||||||
@@ -2732,7 +2804,7 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
s.workerCh <- struct{}{}
|
s.workerCh <- struct{}{}
|
||||||
}()
|
}()
|
||||||
// Allocate new results channel to receive ObjectInfo.
|
// Allocate new results channel to receive ObjectInfo.
|
||||||
objInfoCh := make(chan ObjectInfo)
|
objInfoCh := make(chan itemOrErr[ObjectInfo])
|
||||||
cfg, err := getReplicationConfig(ctx, opts.bucket)
|
cfg, err := getReplicationConfig(ctx, opts.bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogIf(ctx, fmt.Errorf("replication resync of %s for arn %s failed with %w", opts.bucket, opts.arn, err))
|
replLogIf(ctx, fmt.Errorf("replication resync of %s for arn %s failed with %w", opts.bucket, opts.arn, err))
|
||||||
@@ -2846,17 +2918,19 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
ReplicationProxyRequest: "false",
|
ReplicationProxyRequest: "false",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
sz := roi.Size
|
||||||
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++
|
||||||
} else {
|
} else {
|
||||||
st.FailedCount++
|
st.FailedCount++
|
||||||
}
|
}
|
||||||
|
sz = 0
|
||||||
} else {
|
} else {
|
||||||
st.ReplicatedCount++
|
st.ReplicatedCount++
|
||||||
st.ReplicatedSize += roi.Size
|
st.ReplicatedSize += roi.Size
|
||||||
}
|
}
|
||||||
traceFn(err)
|
traceFn(sz, err)
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
@@ -2867,7 +2941,12 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
}
|
}
|
||||||
}(ctx, i)
|
}(ctx, i)
|
||||||
}
|
}
|
||||||
for obj := range objInfoCh {
|
for res := range objInfoCh {
|
||||||
|
if res.Err != nil {
|
||||||
|
resyncStatus = ResyncFailed
|
||||||
|
replLogIf(ctx, res.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-s.resyncCancelCh:
|
case <-s.resyncCancelCh:
|
||||||
resyncStatus = ResyncCanceled
|
resyncStatus = ResyncCanceled
|
||||||
@@ -2876,11 +2955,11 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if heal && lastCheckpoint != "" && lastCheckpoint != obj.Name {
|
if heal && lastCheckpoint != "" && lastCheckpoint != res.Item.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastCheckpoint = ""
|
lastCheckpoint = ""
|
||||||
roi := getHealReplicateObjectInfo(obj, rcfg)
|
roi := getHealReplicateObjectInfo(res.Item, rcfg)
|
||||||
if !roi.ExistingObjResync.mustResync() {
|
if !roi.ExistingObjResync.mustResync() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2966,17 +3045,17 @@ func (s *replicationResyncer) start(ctx context.Context, objAPI ObjectLayer, opt
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *replicationResyncer) trace(resyncID string, path string) func(err error) {
|
func (s *replicationResyncer) trace(resyncID string, path string) func(sz int64, err error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
return func(err error) {
|
return func(sz int64, err error) {
|
||||||
duration := time.Since(startTime)
|
duration := time.Since(startTime)
|
||||||
if globalTrace.NumSubscribers(madmin.TraceReplicationResync) > 0 {
|
if globalTrace.NumSubscribers(madmin.TraceReplicationResync) > 0 {
|
||||||
globalTrace.Publish(replicationResyncTrace(resyncID, startTime, duration, path, err))
|
globalTrace.Publish(replicationResyncTrace(resyncID, startTime, duration, path, err, sz))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func replicationResyncTrace(resyncID string, startTime time.Time, duration time.Duration, path string, err error) madmin.TraceInfo {
|
func replicationResyncTrace(resyncID string, startTime time.Time, duration time.Duration, path string, err error, sz int64) madmin.TraceInfo {
|
||||||
var errStr string
|
var errStr string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStr = err.Error()
|
errStr = err.Error()
|
||||||
@@ -2990,6 +3069,7 @@ func replicationResyncTrace(resyncID string, startTime time.Time, duration time.
|
|||||||
Duration: duration,
|
Duration: duration,
|
||||||
Path: path,
|
Path: path,
|
||||||
Error: errStr,
|
Error: errStr,
|
||||||
|
Bytes: sz,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3140,7 +3220,7 @@ func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfoCh := make(chan ObjectInfo, 10)
|
objInfoCh := make(chan itemOrErr[ObjectInfo], 10)
|
||||||
if err := objAPI.Walk(ctx, bucket, opts.Prefix, objInfoCh, WalkOptions{}); err != nil {
|
if err := objAPI.Walk(ctx, bucket, opts.Prefix, objInfoCh, WalkOptions{}); err != nil {
|
||||||
replLogIf(ctx, err)
|
replLogIf(ctx, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -3152,11 +3232,17 @@ func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string,
|
|||||||
diffCh := make(chan madmin.DiffInfo, 4000)
|
diffCh := make(chan madmin.DiffInfo, 4000)
|
||||||
go func() {
|
go func() {
|
||||||
defer xioutil.SafeClose(diffCh)
|
defer xioutil.SafeClose(diffCh)
|
||||||
for obj := range objInfoCh {
|
for res := range objInfoCh {
|
||||||
|
if res.Err != nil {
|
||||||
|
diffCh <- madmin.DiffInfo{Err: res.Err}
|
||||||
|
return
|
||||||
|
}
|
||||||
if contextCanceled(ctx) {
|
if contextCanceled(ctx) {
|
||||||
// Just consume input...
|
// Just consume input...
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
obj := res.Item
|
||||||
|
|
||||||
// Ignore object prefixes which are excluded
|
// Ignore object prefixes which are excluded
|
||||||
// from versioning via the MinIO bucket versioning extension.
|
// from versioning via the MinIO bucket versioning extension.
|
||||||
if globalBucketVersioningSys.PrefixSuspended(bucket, obj.Name) {
|
if globalBucketVersioningSys.PrefixSuspended(bucket, obj.Name) {
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
if arn.Type == madmin.ReplicationService {
|
if arn.Type == madmin.ReplicationService {
|
||||||
// reject removal of remote target if replication configuration is present
|
// reject removal of remote target if replication configuration is present
|
||||||
rcfg, err := getReplicationConfig(ctx, bucket)
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err == nil {
|
if err == nil && rcfg != nil {
|
||||||
for _, tgtArn := range rcfg.FilterTargetArns(replication.ObjectOpts{OpType: replication.AllReplicationType}) {
|
for _, tgtArn := range rcfg.FilterTargetArns(replication.ObjectOpts{OpType: replication.AllReplicationType}) {
|
||||||
if err == nil && (tgtArn == arnStr || rcfg.RoleArn == arnStr) {
|
if err == nil && (tgtArn == arnStr || rcfg.RoleArn == arnStr) {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/versioning"
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -65,5 +65,5 @@ var (
|
|||||||
MinioBannerName = "MinIO Object Storage Server"
|
MinioBannerName = "MinIO Object Storage Server"
|
||||||
|
|
||||||
// MinioLicense - MinIO server license.
|
// MinioLicense - MinIO server license.
|
||||||
MinioLicense = "GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>"
|
MinioLicense = "GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ func runCallhome(ctx context.Context, objAPI ObjectLayer) bool {
|
|||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer locker.Unlock(lkctx)
|
defer locker.Unlock(lkctx)
|
||||||
|
|
||||||
|
// Perform callhome once and then keep running it at regular intervals.
|
||||||
|
performCallhome(ctx)
|
||||||
|
|
||||||
callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur())
|
callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur())
|
||||||
defer callhomeTimer.Stop()
|
defer callhomeTimer.Stop()
|
||||||
|
|
||||||
@@ -141,11 +144,14 @@ func performCallhome(ctx context.Context) {
|
|||||||
select {
|
select {
|
||||||
case hi, hasMore := <-healthInfoCh:
|
case hi, hasMore := <-healthInfoCh:
|
||||||
if !hasMore {
|
if !hasMore {
|
||||||
|
auditOptions := AuditLogOptions{Event: "callhome:diagnostics"}
|
||||||
// Received all data. Send to SUBNET and return
|
// Received all data. Send to SUBNET and return
|
||||||
err := sendHealthInfo(ctx, healthInfo)
|
err := sendHealthInfo(ctx, healthInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalLogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
|
internalLogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
|
||||||
|
auditOptions.Error = err.Error()
|
||||||
}
|
}
|
||||||
|
auditLogInternal(ctx, auditOptions)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
healthInfo = hi
|
healthInfo = hi
|
||||||
|
|||||||
+61
-134
@@ -21,10 +21,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -49,7 +47,6 @@ import (
|
|||||||
"github.com/minio/console/api/operations"
|
"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/kms-go/kes"
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -58,11 +55,11 @@ import (
|
|||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/certs"
|
"github.com/minio/pkg/v3/certs"
|
||||||
"github.com/minio/pkg/v2/console"
|
"github.com/minio/pkg/v3/console"
|
||||||
"github.com/minio/pkg/v2/ellipses"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v2/env"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serverDebugLog will enable debug printing
|
// serverDebugLog will enable debug printing
|
||||||
@@ -73,6 +70,13 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
if !term.IsTerminal(int(os.Stdout.Fd())) || !term.IsTerminal(int(os.Stderr.Fd())) {
|
||||||
|
color.TurnOff()
|
||||||
|
}
|
||||||
|
if env.Get("NO_COLOR", "") != "" || env.Get("TERM", "") == "dumb" {
|
||||||
|
color.TurnOff()
|
||||||
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
if mousetrap.StartedByExplorer() {
|
if mousetrap.StartedByExplorer() {
|
||||||
fmt.Printf("Don't double-click %s\n", os.Args[0])
|
fmt.Printf("Don't double-click %s\n", os.Args[0])
|
||||||
@@ -131,6 +135,9 @@ func minioConfigToConsoleFeatures() {
|
|||||||
os.Setenv("CONSOLE_LOG_QUERY_AUTH_TOKEN", value)
|
os.Setenv("CONSOLE_LOG_QUERY_AUTH_TOKEN", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if value := env.Get(config.EnvBrowserRedirectURL, ""); value != "" {
|
||||||
|
os.Setenv("CONSOLE_BROWSER_REDIRECT_URL", value)
|
||||||
|
}
|
||||||
// pass the console subpath configuration
|
// pass the console subpath configuration
|
||||||
if globalBrowserRedirectURL != nil {
|
if globalBrowserRedirectURL != nil {
|
||||||
subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator))
|
subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator))
|
||||||
@@ -169,7 +176,10 @@ func minioConfigToConsoleFeatures() {
|
|||||||
os.Setenv("CONSOLE_STS_DURATION", valueSession)
|
os.Setenv("CONSOLE_STS_DURATION", valueSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
os.Setenv("CONSOLE_MINIO_SITE_NAME", globalSite.Name())
|
||||||
|
os.Setenv("CONSOLE_MINIO_SITE_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
|
// This section sets Browser (console) stored config
|
||||||
@@ -392,22 +402,38 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
|
|||||||
ctxt.certsDirSet = true
|
ctxt.certsDirSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memAvailable := availableMemory()
|
||||||
|
if ctx.IsSet("memlimit") || ctx.GlobalIsSet("memlimit") {
|
||||||
|
memlimit := ctx.String("memlimit")
|
||||||
|
if memlimit == "" {
|
||||||
|
memlimit = ctx.GlobalString("memlimit")
|
||||||
|
}
|
||||||
|
mlimit, err := humanize.ParseBytes(memlimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mlimit > memAvailable {
|
||||||
|
logger.Info("WARNING: maximum memory available (%s) smaller than specified --memlimit=%s, ignoring --memlimit value",
|
||||||
|
humanize.IBytes(memAvailable), memlimit)
|
||||||
|
}
|
||||||
|
ctxt.MemLimit = mlimit
|
||||||
|
} else {
|
||||||
|
ctxt.MemLimit = memAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
if memAvailable < ctxt.MemLimit {
|
||||||
|
ctxt.MemLimit = memAvailable
|
||||||
|
}
|
||||||
|
|
||||||
ctxt.FTP = ctx.StringSlice("ftp")
|
ctxt.FTP = ctx.StringSlice("ftp")
|
||||||
ctxt.SFTP = ctx.StringSlice("sftp")
|
ctxt.SFTP = ctx.StringSlice("sftp")
|
||||||
|
|
||||||
ctxt.Interface = ctx.String("interface")
|
ctxt.Interface = ctx.String("interface")
|
||||||
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
|
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
|
||||||
ctxt.ConnReadDeadline = ctx.Duration("conn-read-deadline")
|
|
||||||
ctxt.ConnWriteDeadline = ctx.Duration("conn-write-deadline")
|
|
||||||
ctxt.ConnClientReadDeadline = ctx.Duration("conn-client-read-deadline")
|
|
||||||
ctxt.ConnClientWriteDeadline = ctx.Duration("conn-client-write-deadline")
|
|
||||||
ctxt.SendBufSize = ctx.Int("send-buf-size")
|
ctxt.SendBufSize = ctx.Int("send-buf-size")
|
||||||
ctxt.RecvBufSize = ctx.Int("recv-buf-size")
|
ctxt.RecvBufSize = ctx.Int("recv-buf-size")
|
||||||
|
|
||||||
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
|
|
||||||
ctxt.IdleTimeout = ctx.Duration("idle-timeout")
|
ctxt.IdleTimeout = ctx.Duration("idle-timeout")
|
||||||
ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")
|
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
|
||||||
ctxt.MaxIdleConnsPerHost = ctx.Int("max-idle-conns-per-host")
|
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
|
||||||
|
|
||||||
if conf := ctx.String("config"); len(conf) > 0 {
|
if conf := ctx.String("config"); len(conf) > 0 {
|
||||||
err = mergeServerCtxtFromConfigFile(conf, ctxt)
|
err = mergeServerCtxtFromConfigFile(conf, ctxt)
|
||||||
@@ -854,127 +880,28 @@ func loadRootCredentials() {
|
|||||||
// Initialize KMS global variable after valiadating and loading the configuration.
|
// Initialize KMS global variable after valiadating and loading the configuration.
|
||||||
// It depends on KMS env variables and global cli flags.
|
// It depends on KMS env variables and global cli flags.
|
||||||
func handleKMSConfig() {
|
func handleKMSConfig() {
|
||||||
if env.IsSet(kms.EnvKMSSecretKey) && env.IsSet(kms.EnvKESEndpoint) {
|
present, err := kms.IsPresent()
|
||||||
logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKMSSecretKey, kms.EnvKESEndpoint))
|
if err != nil {
|
||||||
|
logger.Fatal(err, "Invalid KMS configuration specified")
|
||||||
|
}
|
||||||
|
if !present {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if env.IsSet(kms.EnvKMSSecretKey) {
|
KMS, err := kms.Connect(GlobalContext, &kms.ConnectionOptions{
|
||||||
KMS, err := kms.Parse(env.Get(kms.EnvKMSSecretKey, ""))
|
CADir: globalCertsCADir.Get(),
|
||||||
if err != nil {
|
})
|
||||||
logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment")
|
if err != nil {
|
||||||
}
|
logger.Fatal(err, "Failed to connect to KMS")
|
||||||
GlobalKMS = KMS
|
|
||||||
}
|
}
|
||||||
if env.IsSet(kms.EnvKESEndpoint) {
|
|
||||||
if env.IsSet(kms.EnvKESAPIKey) {
|
|
||||||
if env.IsSet(kms.EnvKESClientKey) {
|
|
||||||
logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientKey))
|
|
||||||
}
|
|
||||||
if env.IsSet(kms.EnvKESClientCert) {
|
|
||||||
logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !env.IsSet(kms.EnvKESKeyName) {
|
|
||||||
logger.Fatal(errors.New("Invalid KES configuration"), fmt.Sprintf("The mandatory environment variable %q not set", kms.EnvKESKeyName))
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoints []string
|
if _, err = KMS.GenerateKey(GlobalContext, &kms.GenerateKeyRequest{}); errors.Is(err, kms.ErrKeyNotFound) {
|
||||||
for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") {
|
err = KMS.CreateKey(GlobalContext, &kms.CreateKeyRequest{Name: KMS.DefaultKey})
|
||||||
if strings.TrimSpace(endpoint) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !ellipses.HasEllipses(endpoint) {
|
|
||||||
endpoints = append(endpoints, endpoint)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
patterns, err := ellipses.FindEllipsesPatterns(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, fmt.Sprintf("Invalid KES endpoint %q", endpoint))
|
|
||||||
}
|
|
||||||
for _, lbls := range patterns.Expand() {
|
|
||||||
endpoints = append(endpoints, strings.Join(lbls, ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rootCAs, err := certs.GetRootCAs(env.Get(kms.EnvKESServerCA, globalCertsCADir.Get()))
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(kms.EnvKESServerCA, globalCertsCADir.Get())))
|
|
||||||
}
|
|
||||||
|
|
||||||
var kmsConf kms.Config
|
|
||||||
if env.IsSet(kms.EnvKESAPIKey) {
|
|
||||||
key, err := kes.ParseAPIKey(env.Get(kms.EnvKESAPIKey, ""))
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, fmt.Sprintf("Failed to parse KES API key from %q", env.Get(kms.EnvKESAPIKey, "")))
|
|
||||||
}
|
|
||||||
kmsConf = kms.Config{
|
|
||||||
Endpoints: endpoints,
|
|
||||||
DefaultKeyID: env.Get(kms.EnvKESKeyName, ""),
|
|
||||||
APIKey: key,
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) {
|
|
||||||
// Manually load the certificate and private key into memory.
|
|
||||||
// We need to check whether the private key is encrypted, and
|
|
||||||
// if so, decrypt it using the user-provided password.
|
|
||||||
certBytes, err := os.ReadFile(certFile)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
|
|
||||||
}
|
|
||||||
keyBytes, err := os.ReadFile(keyFile)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err)
|
|
||||||
}
|
|
||||||
privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes))
|
|
||||||
if len(rest) != 0 {
|
|
||||||
return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data")
|
|
||||||
}
|
|
||||||
if x509.IsEncryptedPEMBlock(privateKeyPEM) {
|
|
||||||
keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(kms.EnvKESClientPassword, "")))
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err)
|
|
||||||
}
|
|
||||||
keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes})
|
|
||||||
}
|
|
||||||
certificate, err := tls.X509KeyPair(certBytes, keyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
|
|
||||||
}
|
|
||||||
return certificate, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadCertEvents := make(chan tls.Certificate, 1)
|
|
||||||
certificate, err := certs.NewCertificate(env.Get(kms.EnvKESClientCert, ""), env.Get(kms.EnvKESClientKey, ""), loadX509KeyPair)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, "Failed to load KES client certificate")
|
|
||||||
}
|
|
||||||
certificate.Watch(context.Background(), 15*time.Minute, syscall.SIGHUP)
|
|
||||||
certificate.Notify(reloadCertEvents)
|
|
||||||
|
|
||||||
kmsConf = kms.Config{
|
|
||||||
Endpoints: endpoints,
|
|
||||||
DefaultKeyID: env.Get(kms.EnvKESKeyName, ""),
|
|
||||||
Certificate: certificate,
|
|
||||||
ReloadCertEvents: reloadCertEvents,
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KMS, err := kms.NewWithConfig(kmsConf, KMSLogger{})
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
|
|
||||||
}
|
|
||||||
// Try to generate a data encryption key. Only try to create key if this fails.
|
|
||||||
// This implicitly checks that we can communicate to KES.
|
|
||||||
// We don't treat a policy error as failure condition since MinIO may not have the permission
|
|
||||||
// to create keys - just to generate/decrypt data encryption keys.
|
|
||||||
if _, err = KMS.GenerateKey(GlobalContext, env.Get(kms.EnvKESKeyName, ""), kms.Context{}); err != nil && errors.Is(err, kes.ErrKeyNotFound) {
|
|
||||||
if err = KMS.CreateKey(GlobalContext, env.Get(kms.EnvKESKeyName, "")); err != nil && !errors.Is(err, kes.ErrKeyExists) && !errors.Is(err, kes.ErrNotAllowed) {
|
|
||||||
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GlobalKMS = KMS
|
|
||||||
}
|
}
|
||||||
|
if err != nil && !errors.Is(err, kms.ErrKeyExists) && !errors.Is(err, kms.ErrPermission) {
|
||||||
|
logger.Fatal(err, "Failed to connect to KMS")
|
||||||
|
}
|
||||||
|
GlobalKMS = KMS
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secureConn bool, err error) {
|
func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secureConn bool, err error) {
|
||||||
|
|||||||
+36
-37
@@ -56,7 +56,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initHelp() {
|
func initHelp() {
|
||||||
@@ -240,6 +240,11 @@ func initHelp() {
|
|||||||
Description: "manage Browser HTTP specific features, such as Security headers, etc.",
|
Description: "manage Browser HTTP specific features, such as Security headers, etc.",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.ILMSubSys,
|
||||||
|
Description: "manage ILM settings for expiration and transition workers",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIsErasure {
|
if globalIsErasure {
|
||||||
@@ -288,6 +293,7 @@ func initHelp() {
|
|||||||
config.DriveSubSys: drive.HelpDrive,
|
config.DriveSubSys: drive.HelpDrive,
|
||||||
config.CacheSubSys: cache.Help,
|
config.CacheSubSys: cache.Help,
|
||||||
config.BrowserSubSys: browser.Help,
|
config.BrowserSubSys: browser.Help,
|
||||||
|
config.ILMSubSys: ilm.Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
config.RegisterHelpSubSys(helpMap)
|
config.RegisterHelpSubSys(helpMap)
|
||||||
@@ -362,7 +368,7 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
|
|||||||
}
|
}
|
||||||
case config.IdentityOpenIDSubSys:
|
case config.IdentityOpenIDSubSys:
|
||||||
if _, err := openid.LookupConfig(s,
|
if _, err := openid.LookupConfig(s,
|
||||||
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
|
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case config.IdentityLDAPSubSys:
|
case config.IdentityLDAPSubSys:
|
||||||
@@ -383,7 +389,7 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
|
|||||||
}
|
}
|
||||||
case config.IdentityPluginSubSys:
|
case config.IdentityPluginSubSys:
|
||||||
if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default],
|
if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default],
|
||||||
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
|
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case config.SubnetSubSys:
|
case config.SubnetSubSys:
|
||||||
@@ -530,10 +536,11 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
|||||||
// but not federation.
|
// but not federation.
|
||||||
globalBucketFederation = etcdCfg.PathPrefix == "" && etcdCfg.Enabled
|
globalBucketFederation = etcdCfg.PathPrefix == "" && etcdCfg.Enabled
|
||||||
|
|
||||||
globalSite, err = config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default])
|
siteCfg, err := config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
configLogIf(ctx, fmt.Errorf("Invalid site configuration: %w", err))
|
configLogIf(ctx, fmt.Errorf("Invalid site configuration: %w", err))
|
||||||
}
|
}
|
||||||
|
globalSite.Update(siteCfg)
|
||||||
|
|
||||||
globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled
|
globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled
|
||||||
if globalAutoEncryption && GlobalKMS == nil {
|
if globalAutoEncryption && GlobalKMS == nil {
|
||||||
@@ -575,7 +582,7 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
configLogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
|
configLogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
globalAPIConfig.init(apiConfig, setDriveCounts)
|
globalAPIConfig.init(apiConfig, setDriveCounts, objAPI.Legacy())
|
||||||
autoGenerateRootCredentials() // Generate the KMS root credentials here since we don't know whether API root access is disabled until now.
|
autoGenerateRootCredentials() // Generate the KMS root credentials here since we don't know whether API root access is disabled until now.
|
||||||
setRemoteInstanceTransport(NewHTTPTransportWithTimeout(apiConfig.RemoteTransportDeadline))
|
setRemoteInstanceTransport(NewHTTPTransportWithTimeout(apiConfig.RemoteTransportDeadline))
|
||||||
case config.CompressionSubSys:
|
case config.CompressionSubSys:
|
||||||
@@ -753,41 +760,33 @@ func autoGenerateRootCredentials() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if manager, ok := GlobalKMS.(kms.KeyManager); ok {
|
aKey, err := GlobalKMS.MAC(GlobalContext, &kms.MACRequest{Message: []byte("root access key")})
|
||||||
stat, err := GlobalKMS.Stat(GlobalContext)
|
if errors.Is(err, kes.ErrNotAllowed) || errors.Is(err, errors.ErrUnsupported) {
|
||||||
if err != nil {
|
return // If we don't have permission to compute the HMAC, don't change the cred.
|
||||||
kmsLogIf(GlobalContext, err, "Unable to generate root credentials using KMS")
|
}
|
||||||
return
|
if err != nil {
|
||||||
}
|
logger.Fatal(err, "Unable to generate root access key using KMS")
|
||||||
|
}
|
||||||
|
|
||||||
aKey, err := manager.HMAC(GlobalContext, stat.DefaultKey, []byte("root access key"))
|
sKey, err := GlobalKMS.MAC(GlobalContext, &kms.MACRequest{Message: []byte("root secret key")})
|
||||||
if errors.Is(err, kes.ErrNotAllowed) {
|
if err != nil {
|
||||||
return // If we don't have permission to compute the HMAC, don't change the cred.
|
// Here, we must have permission. Otherwise, we would have failed earlier.
|
||||||
}
|
logger.Fatal(err, "Unable to generate root secret key using KMS")
|
||||||
if err != nil {
|
}
|
||||||
logger.Fatal(err, "Unable to generate root access key using KMS")
|
|
||||||
}
|
|
||||||
|
|
||||||
sKey, err := manager.HMAC(GlobalContext, stat.DefaultKey, []byte("root secret key"))
|
accessKey, err := auth.GenerateAccessKey(20, bytes.NewReader(aKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Here, we must have permission. Otherwise, we would have failed earlier.
|
logger.Fatal(err, "Unable to generate root access key")
|
||||||
logger.Fatal(err, "Unable to generate root secret key using KMS")
|
}
|
||||||
}
|
secretKey, err := auth.GenerateSecretKey(32, bytes.NewReader(sKey))
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(err, "Unable to generate root secret key")
|
||||||
|
}
|
||||||
|
|
||||||
accessKey, err := auth.GenerateAccessKey(20, bytes.NewReader(aKey))
|
logger.Info("Automatically generated root access key and secret key with the KMS")
|
||||||
if err != nil {
|
globalActiveCred = auth.Credentials{
|
||||||
logger.Fatal(err, "Unable to generate root access key")
|
AccessKey: accessKey,
|
||||||
}
|
SecretKey: secretKey,
|
||||||
secretKey, err := auth.GenerateSecretKey(32, bytes.NewReader(sKey))
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, "Unable to generate root secret key")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Automatically generated root access key and secret key with the KMS")
|
|
||||||
globalActiveCred = auth.Credentials{
|
|
||||||
AccessKey: accessKey,
|
|
||||||
SecretKey: secretKey,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ func TestServerConfig(t *testing.T) {
|
|||||||
t.Fatalf("Init Test config failed")
|
t.Fatalf("Init Test config failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalSite.Region != globalMinioDefaultRegion {
|
if globalSite.Region() != globalMinioDefaultRegion {
|
||||||
t.Errorf("Expecting region `us-east-1` found %s", globalSite.Region)
|
t.Errorf("Expecting region `us-east-1` found %s", globalSite.Region())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set new region and verify.
|
// Set new region and verify.
|
||||||
@@ -52,8 +52,8 @@ func TestServerConfig(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if site.Region != "us-west-1" {
|
if site.Region() != "us-west-1" {
|
||||||
t.Errorf("Expecting region `us-west-1` found %s", globalSite.Region)
|
t.Errorf("Expecting region `us-west-1` found %s", globalSite.Region())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := saveServerConfig(context.Background(), objLayer, globalServerConfig); err != nil {
|
if err := saveServerConfig(context.Background(), objLayer, globalServerConfig); err != nil {
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ import (
|
|||||||
"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/event/target"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
"github.com/minio/pkg/v2/quick"
|
"github.com/minio/pkg/v3/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Save config file to corresponding backend
|
// Save config file to corresponding backend
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"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/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v2/quick"
|
"github.com/minio/pkg/v3/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileLogger is introduced to workaround the dependency about logrus
|
// FileLogger is introduced to workaround the dependency about logrus
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"container/ring"
|
"container/ring"
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@@ -28,8 +29,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger/target/console"
|
"github.com/minio/minio/internal/logger/target/console"
|
||||||
"github.com/minio/minio/internal/logger/target/types"
|
"github.com/minio/minio/internal/logger/target/types"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/pubsub"
|
||||||
"github.com/minio/pkg/v2/logger/message/log"
|
"github.com/minio/pkg/v3/logger/message/log"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// number of log messages to buffer
|
// number of log messages to buffer
|
||||||
@@ -49,10 +50,10 @@ type HTTPConsoleLoggerSys struct {
|
|||||||
|
|
||||||
// NewConsoleLogger - creates new HTTPConsoleLoggerSys with all nodes subscribed to
|
// NewConsoleLogger - creates new HTTPConsoleLoggerSys with all nodes subscribed to
|
||||||
// the console logging pub sub system
|
// the console logging pub sub system
|
||||||
func NewConsoleLogger(ctx context.Context) *HTTPConsoleLoggerSys {
|
func NewConsoleLogger(ctx context.Context, w io.Writer) *HTTPConsoleLoggerSys {
|
||||||
return &HTTPConsoleLoggerSys{
|
return &HTTPConsoleLoggerSys{
|
||||||
pubsub: pubsub.New[log.Info, madmin.LogMask](8),
|
pubsub: pubsub.New[log.Info, madmin.LogMask](8),
|
||||||
console: console.New(),
|
console: console.New(w),
|
||||||
logBuf: ring.New(defaultLogBufferCount),
|
logBuf: ring.New(defaultLogBufferCount),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-32
@@ -43,7 +43,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/config/heal"
|
"github.com/minio/minio/internal/config/heal"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/pkg/v2/console"
|
"github.com/minio/pkg/v3/console"
|
||||||
uatomic "go.uber.org/atomic"
|
uatomic "go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ const (
|
|||||||
dataScannerCompactLeastObject = 500 // Compact when there is less than this many objects in a branch.
|
dataScannerCompactLeastObject = 500 // Compact when there is less than this many objects in a branch.
|
||||||
dataScannerCompactAtChildren = 10000 // Compact when there are this many children in a branch.
|
dataScannerCompactAtChildren = 10000 // Compact when there are this many children in a branch.
|
||||||
dataScannerCompactAtFolders = dataScannerCompactAtChildren / 4 // Compact when this many subfolders in a single folder.
|
dataScannerCompactAtFolders = dataScannerCompactAtChildren / 4 // Compact when this many subfolders in a single folder.
|
||||||
dataScannerForceCompactAtFolders = 1_000_000 // Compact when this many subfolders in a single folder (even top level).
|
dataScannerForceCompactAtFolders = 250_000 // Compact when this many subfolders in a single folder (even top level).
|
||||||
dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
|
dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
|
||||||
|
|
||||||
healDeleteDangling = true
|
healDeleteDangling = true
|
||||||
@@ -349,6 +349,7 @@ func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, ca
|
|||||||
// No useful information...
|
// No useful information...
|
||||||
return cache, err
|
return cache, err
|
||||||
}
|
}
|
||||||
|
s.newCache.forceCompact(dataScannerCompactAtChildren)
|
||||||
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
|
||||||
@@ -958,7 +959,7 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
|||||||
var vc *versioning.Versioning
|
var vc *versioning.Versioning
|
||||||
var lr objectlock.Retention
|
var lr objectlock.Retention
|
||||||
var rcfg *replication.Config
|
var rcfg *replication.Config
|
||||||
if i.bucket != minioMetaBucket {
|
if !isMinioMetaBucketName(i.bucket) {
|
||||||
vc, err = globalBucketVersioningSys.Get(i.bucket)
|
vc, err = globalBucketVersioningSys.Get(i.bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scannerLogOnceIf(ctx, err, i.bucket)
|
scannerLogOnceIf(ctx, err, i.bucket)
|
||||||
@@ -993,7 +994,7 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
|||||||
// This can happen when,
|
// This can happen when,
|
||||||
// - ExpireObjectAllVersions flag is enabled
|
// - ExpireObjectAllVersions flag is enabled
|
||||||
// - NoncurrentVersionExpiration is applicable
|
// - NoncurrentVersionExpiration is applicable
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAllVersionsAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAllVersionsAction, lifecycle.DelMarkerDeleteAllVersionsAction:
|
||||||
size = 0
|
size = 0
|
||||||
case lifecycle.DeleteAction:
|
case lifecycle.DeleteAction:
|
||||||
// On a non-versioned bucket, DeleteObject removes the only version permanently.
|
// On a non-versioned bucket, DeleteObject removes the only version permanently.
|
||||||
@@ -1162,7 +1163,7 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
|
|
||||||
// Note: objDeleted is true if and only if action ==
|
// Note: objDeleted is true if and only if action ==
|
||||||
// lifecycle.DeleteAllVersionsAction
|
// lifecycle.DeleteAllVersionsAction
|
||||||
if action == lifecycle.DeleteAllVersionsAction {
|
if action.DeleteAll() {
|
||||||
return true, 0
|
return true, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1199,17 +1200,15 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
|
|||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Action == lifecycle.NoneAction {
|
switch event.Action {
|
||||||
return event
|
case lifecycle.DeleteAllVersionsAction, lifecycle.DelMarkerDeleteAllVersionsAction:
|
||||||
}
|
// Skip if bucket has object locking enabled; To prevent the
|
||||||
|
// possibility of violating an object retention on one of the
|
||||||
if obj.IsLatest && event.Action == lifecycle.DeleteAllVersionsAction {
|
// noncurrent versions of this object.
|
||||||
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
if lr.LockEnabled {
|
||||||
return lifecycle.Event{Action: lifecycle.NoneAction}
|
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch event.Action {
|
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
// Defensive code, should never happen
|
// Defensive code, should never happen
|
||||||
if obj.VersionID == "" {
|
if obj.VersionID == "" {
|
||||||
@@ -1234,36 +1233,29 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyTransitionRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo) bool {
|
func applyTransitionRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo) bool {
|
||||||
if obj.DeleteMarker {
|
if obj.DeleteMarker || obj.IsDir {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
globalTransitionState.queueTransitionTask(obj, event, src)
|
globalTransitionState.queueTransitionTask(obj, event, src)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) bool {
|
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) (ok bool) {
|
||||||
var err error
|
timeILM := globalScannerMetrics.timeILM(lcEvent.Action)
|
||||||
defer func() {
|
if err := expireTransitionedObject(ctx, objLayer, &obj, lcEvent, src); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Note: DeleteAllVersions action is not supported for
|
|
||||||
// transitioned objects
|
|
||||||
globalScannerMetrics.timeILM(lcEvent.Action)(1)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = expireTransitionedObject(ctx, objLayer, &obj, lcEvent, src); err != nil {
|
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ilmLogIf(ctx, err)
|
ilmLogIf(ctx, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
timeILM(1)
|
||||||
|
|
||||||
// Notification already sent in *expireTransitionedObject*, just return 'true' here.
|
// Notification already sent in *expireTransitionedObject*, just return 'true' here.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) bool {
|
func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) (ok bool) {
|
||||||
traceFn := globalLifecycleSys.trace(obj)
|
traceFn := globalLifecycleSys.trace(obj)
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
Expiration: ExpirationOptions{Expire: true},
|
Expiration: ExpirationOptions{Expire: true},
|
||||||
@@ -1285,17 +1277,19 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
|||||||
dobj ObjectInfo
|
dobj ObjectInfo
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
timeILM := globalScannerMetrics.timeILM(lcEvent.Action)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if lcEvent.Action != lifecycle.NoneAction {
|
if lcEvent.Action != lifecycle.NoneAction {
|
||||||
numVersions := uint64(1)
|
numVersions := uint64(1)
|
||||||
if lcEvent.Action == lifecycle.DeleteAllVersionsAction {
|
if lcEvent.Action.DeleteAll() {
|
||||||
numVersions = uint64(obj.NumVersions)
|
numVersions = uint64(obj.NumVersions)
|
||||||
}
|
}
|
||||||
globalScannerMetrics.timeILM(lcEvent.Action)(numVersions)
|
timeILM(numVersions)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -1320,8 +1314,11 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
|||||||
if obj.DeleteMarker {
|
if obj.DeleteMarker {
|
||||||
eventName = event.ObjectRemovedDeleteMarkerCreated
|
eventName = event.ObjectRemovedDeleteMarkerCreated
|
||||||
}
|
}
|
||||||
if lcEvent.Action.DeleteAll() {
|
switch lcEvent.Action {
|
||||||
|
case lifecycle.DeleteAllVersionsAction:
|
||||||
eventName = event.ObjectRemovedDeleteAllVersions
|
eventName = event.ObjectRemovedDeleteAllVersions
|
||||||
|
case lifecycle.DelMarkerDeleteAllVersionsAction:
|
||||||
|
eventName = event.ILMDelMarkerExpirationDelete
|
||||||
}
|
}
|
||||||
// Notify object deleted event.
|
// Notify object deleted event.
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
@@ -1346,7 +1343,7 @@ func applyLifecycleAction(event lifecycle.Event, src lcEventSrc, obj ObjectInfo)
|
|||||||
switch action := event.Action; action {
|
switch action := event.Action; action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction,
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction,
|
||||||
lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction,
|
lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction,
|
||||||
lifecycle.DeleteAllVersionsAction:
|
lifecycle.DeleteAllVersionsAction, lifecycle.DelMarkerDeleteAllVersionsAction:
|
||||||
success = applyExpiryRule(event, src, obj)
|
success = applyExpiryRule(event, src, obj)
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
success = applyTransitionRule(event, src, obj)
|
success = applyTransitionRule(event, src, obj)
|
||||||
|
|||||||
@@ -20,12 +20,15 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
|
"github.com/minio/minio/internal/bucket/object/lock"
|
||||||
"github.com/minio/minio/internal/bucket/versioning"
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -141,3 +144,96 @@ func TestApplyNewerNoncurrentVersionsLimit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEvalActionFromLifecycle(t *testing.T) {
|
||||||
|
// Tests cover only ExpiredObjectDeleteAllVersions and DelMarkerExpiration actions
|
||||||
|
obj := ObjectInfo{
|
||||||
|
Name: "foo",
|
||||||
|
ModTime: time.Now().Add(-31 * 24 * time.Hour),
|
||||||
|
Size: 100 << 20,
|
||||||
|
VersionID: uuid.New().String(),
|
||||||
|
IsLatest: true,
|
||||||
|
NumVersions: 4,
|
||||||
|
}
|
||||||
|
delMarker := ObjectInfo{
|
||||||
|
Name: "foo-deleted",
|
||||||
|
ModTime: time.Now().Add(-61 * 24 * time.Hour),
|
||||||
|
Size: 0,
|
||||||
|
VersionID: uuid.New().String(),
|
||||||
|
IsLatest: true,
|
||||||
|
DeleteMarker: true,
|
||||||
|
NumVersions: 4,
|
||||||
|
}
|
||||||
|
deleteAllILM := `<LifecycleConfiguration>
|
||||||
|
<Rule>
|
||||||
|
<Expiration>
|
||||||
|
<Days>30</Days>
|
||||||
|
<ExpiredObjectAllVersions>true</ExpiredObjectAllVersions>
|
||||||
|
</Expiration>
|
||||||
|
<Filter></Filter>
|
||||||
|
<Status>Enabled</Status>
|
||||||
|
<ID>DeleteAllVersions</ID>
|
||||||
|
</Rule>
|
||||||
|
</LifecycleConfiguration>`
|
||||||
|
delMarkerILM := `<LifecycleConfiguration>
|
||||||
|
<Rule>
|
||||||
|
<ID>DelMarkerExpiration</ID>
|
||||||
|
<Filter></Filter>
|
||||||
|
<Status>Enabled</Status>
|
||||||
|
<DelMarkerExpiration>
|
||||||
|
<Days>60</Days>
|
||||||
|
</DelMarkerExpiration>
|
||||||
|
</Rule>
|
||||||
|
</LifecycleConfiguration>`
|
||||||
|
deleteAllLc, err := lifecycle.ParseLifecycleConfig(strings.NewReader(deleteAllILM))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse deleteAllILM test ILM policy %v", err)
|
||||||
|
}
|
||||||
|
delMarkerLc, err := lifecycle.ParseLifecycleConfig(strings.NewReader(delMarkerILM))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse delMarkerILM test ILM policy %v", err)
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
ilm lifecycle.Lifecycle
|
||||||
|
retention lock.Retention
|
||||||
|
obj ObjectInfo
|
||||||
|
want lifecycle.Action
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// with object locking
|
||||||
|
ilm: *deleteAllLc,
|
||||||
|
retention: lock.Retention{LockEnabled: true},
|
||||||
|
obj: obj,
|
||||||
|
want: lifecycle.NoneAction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// without object locking
|
||||||
|
ilm: *deleteAllLc,
|
||||||
|
retention: lock.Retention{},
|
||||||
|
obj: obj,
|
||||||
|
want: lifecycle.DeleteAllVersionsAction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// with object locking
|
||||||
|
ilm: *delMarkerLc,
|
||||||
|
retention: lock.Retention{LockEnabled: true},
|
||||||
|
obj: delMarker,
|
||||||
|
want: lifecycle.NoneAction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// without object locking
|
||||||
|
ilm: *delMarkerLc,
|
||||||
|
retention: lock.Retention{},
|
||||||
|
obj: delMarker,
|
||||||
|
want: lifecycle.DelMarkerDeleteAllVersionsAction,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("TestEvalAction-%d", i), func(t *testing.T) {
|
||||||
|
if got := evalActionFromLifecycle(context.TODO(), test.ilm, test.retention, nil, test.obj); got.Action != test.want {
|
||||||
|
t.Fatalf("Expected %v but got %v", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -729,6 +729,53 @@ func (d *dataUsageCache) reduceChildrenOf(path dataUsageHash, limit int, compact
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forceCompact will force compact the cache of the top entry.
|
||||||
|
// If the number of children is more than limit*100, it will compact self.
|
||||||
|
// When above the limit a cleanup will also be performed to remove any possible abandoned entries.
|
||||||
|
func (d *dataUsageCache) forceCompact(limit int) {
|
||||||
|
if d == nil || len(d.Cache) <= limit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
top := hashPath(d.Info.Name).Key()
|
||||||
|
topE := d.find(top)
|
||||||
|
if topE == nil {
|
||||||
|
scannerLogIf(GlobalContext, errors.New("forceCompact: root not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If off by 2 orders of magnitude, compact self and log error.
|
||||||
|
if len(topE.Children) > dataScannerForceCompactAtFolders {
|
||||||
|
// If we still have too many children, compact self.
|
||||||
|
scannerLogOnceIf(GlobalContext, fmt.Errorf("forceCompact: %q has %d children. Force compacting. Expect reduced scanner performance", d.Info.Name, len(topE.Children)), d.Info.Name)
|
||||||
|
d.reduceChildrenOf(hashPath(d.Info.Name), limit, true)
|
||||||
|
}
|
||||||
|
if len(d.Cache) <= limit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for abandoned entries.
|
||||||
|
found := make(map[string]struct{}, len(d.Cache))
|
||||||
|
|
||||||
|
// Mark all children recursively
|
||||||
|
var mark func(entry dataUsageEntry)
|
||||||
|
mark = func(entry dataUsageEntry) {
|
||||||
|
for k := range entry.Children {
|
||||||
|
found[k] = struct{}{}
|
||||||
|
if ch, ok := d.Cache[k]; ok {
|
||||||
|
mark(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
found[top] = struct{}{}
|
||||||
|
mark(*topE)
|
||||||
|
|
||||||
|
// Delete all entries not found.
|
||||||
|
for k := range d.Cache {
|
||||||
|
if _, ok := found[k]; !ok {
|
||||||
|
delete(d.Cache, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StringAll returns a detailed string representation of all entries in the cache.
|
// StringAll returns a detailed string representation of all entries in the cache.
|
||||||
func (d *dataUsageCache) StringAll() string {
|
func (d *dataUsageCache) StringAll() string {
|
||||||
// Remove bloom filter from print.
|
// Remove bloom filter from print.
|
||||||
|
|||||||
+3
-3
@@ -79,12 +79,12 @@ func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket
|
|||||||
prefixUsageCache.InitOnce(30*time.Second,
|
prefixUsageCache.InitOnce(30*time.Second,
|
||||||
// No need to fail upon Update() error, fallback to old value.
|
// No need to fail upon Update() error, fallback to old value.
|
||||||
cachevalue.Opts{ReturnLastGood: true, NoWait: true},
|
cachevalue.Opts{ReturnLastGood: true, NoWait: true},
|
||||||
func() (map[string]uint64, error) {
|
func(ctx context.Context) (map[string]uint64, error) {
|
||||||
m := make(map[string]uint64)
|
m := make(map[string]uint64)
|
||||||
for _, pool := range z.serverPools {
|
for _, pool := range z.serverPools {
|
||||||
for _, er := range pool.sets {
|
for _, er := range pool.sets {
|
||||||
// Load bucket usage prefixes
|
// Load bucket usage prefixes
|
||||||
ctx, done := context.WithTimeout(context.Background(), 2*time.Second)
|
ctx, done := context.WithTimeout(ctx, 2*time.Second)
|
||||||
ok := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName) == nil
|
ok := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName) == nil
|
||||||
done()
|
done()
|
||||||
if ok {
|
if ok {
|
||||||
@@ -107,7 +107,7 @@ func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return prefixUsageCache.Get()
|
return prefixUsageCache.GetWithCtx(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
|
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy tagging XML.
|
// Data types used for returning dummy tagging XML.
|
||||||
|
|||||||
+39
-18
@@ -110,7 +110,7 @@ func kmsKeyIDFromMetadata(metadata map[string]string) string {
|
|||||||
//
|
//
|
||||||
// DecryptETags uses a KMS bulk decryption API, if available, which
|
// DecryptETags uses a KMS bulk decryption API, if available, which
|
||||||
// is more efficient than decrypting ETags sequentually.
|
// is more efficient than decrypting ETags sequentually.
|
||||||
func DecryptETags(ctx context.Context, k kms.KMS, objects []ObjectInfo) error {
|
func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
|
||||||
const BatchSize = 250 // We process the objects in batches - 250 is a reasonable default.
|
const BatchSize = 250 // We process the objects in batches - 250 is a reasonable default.
|
||||||
var (
|
var (
|
||||||
metadata = make([]map[string]string, 0, BatchSize)
|
metadata = make([]map[string]string, 0, BatchSize)
|
||||||
@@ -267,7 +267,11 @@ func rotateKey(ctx context.Context, oldKey []byte, newKeyID string, newKey []byt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
oldKey, err := GlobalKMS.DecryptKey(keyID, kmsKey, kms.Context{bucket: path.Join(bucket, object)})
|
oldKey, err := GlobalKMS.Decrypt(ctx, &kms.DecryptRequest{
|
||||||
|
Name: keyID,
|
||||||
|
Ciphertext: kmsKey,
|
||||||
|
AssociatedData: kms.Context{bucket: path.Join(bucket, object)},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -276,7 +280,10 @@ func rotateKey(ctx context.Context, oldKey []byte, newKeyID string, newKey []byt
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newKey, err := GlobalKMS.GenerateKey(ctx, "", kms.Context{bucket: path.Join(bucket, object)})
|
newKey, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{
|
||||||
|
Name: GlobalKMS.DefaultKey,
|
||||||
|
AssociatedData: kms.Context{bucket: path.Join(bucket, object)},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -312,7 +319,10 @@ func rotateKey(ctx context.Context, oldKey []byte, newKeyID string, newKey []byt
|
|||||||
if _, ok := kmsCtx[bucket]; !ok {
|
if _, ok := kmsCtx[bucket]; !ok {
|
||||||
kmsCtx[bucket] = path.Join(bucket, object)
|
kmsCtx[bucket] = path.Join(bucket, object)
|
||||||
}
|
}
|
||||||
newKey, err := GlobalKMS.GenerateKey(ctx, newKeyID, kmsCtx)
|
newKey, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{
|
||||||
|
Name: newKeyID,
|
||||||
|
AssociatedData: kmsCtx,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -352,7 +362,9 @@ func newEncryptMetadata(ctx context.Context, kind crypto.Type, keyID string, key
|
|||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
return crypto.ObjectKey{}, errKMSNotConfigured
|
return crypto.ObjectKey{}, errKMSNotConfigured
|
||||||
}
|
}
|
||||||
key, err := GlobalKMS.GenerateKey(ctx, "", kms.Context{bucket: path.Join(bucket, object)})
|
key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{
|
||||||
|
AssociatedData: kms.Context{bucket: path.Join(bucket, object)},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return crypto.ObjectKey{}, err
|
return crypto.ObjectKey{}, err
|
||||||
}
|
}
|
||||||
@@ -379,7 +391,10 @@ func newEncryptMetadata(ctx context.Context, kind crypto.Type, keyID string, key
|
|||||||
if _, ok := kmsCtx[bucket]; !ok {
|
if _, ok := kmsCtx[bucket]; !ok {
|
||||||
kmsCtx[bucket] = path.Join(bucket, object)
|
kmsCtx[bucket] = path.Join(bucket, object)
|
||||||
}
|
}
|
||||||
key, err := GlobalKMS.GenerateKey(ctx, keyID, kmsCtx)
|
key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{
|
||||||
|
Name: keyID,
|
||||||
|
AssociatedData: kmsCtx,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, kes.ErrKeyNotFound) {
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
return crypto.ObjectKey{}, errKMSKeyNotFound
|
return crypto.ObjectKey{}, errKMSKeyNotFound
|
||||||
@@ -475,11 +490,10 @@ func EncryptRequest(content io.Reader, r *http.Request, bucket, object string, m
|
|||||||
func decryptObjectMeta(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
|
func decryptObjectMeta(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
|
||||||
switch kind, _ := crypto.IsEncrypted(metadata); kind {
|
switch kind, _ := crypto.IsEncrypted(metadata); kind {
|
||||||
case crypto.S3:
|
case crypto.S3:
|
||||||
KMS := GlobalKMS
|
if GlobalKMS == nil {
|
||||||
if KMS == nil {
|
|
||||||
return nil, errKMSNotConfigured
|
return nil, errKMSNotConfigured
|
||||||
}
|
}
|
||||||
objectKey, err := crypto.S3.UnsealObjectKey(KMS, metadata, bucket, object)
|
objectKey, err := crypto.S3.UnsealObjectKey(GlobalKMS, metadata, bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1011,7 +1025,9 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e
|
|||||||
if encrypted {
|
if encrypted {
|
||||||
if crypto.SSEC.IsEncrypted(info.UserDefined) {
|
if crypto.SSEC.IsEncrypted(info.UserDefined) {
|
||||||
if !(crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers)) {
|
if !(crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers)) {
|
||||||
return encrypted, errEncryptedObject
|
if r.Header.Get(xhttp.MinIOSourceReplicationRequest) != "true" {
|
||||||
|
return encrypted, errEncryptedObject
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1063,13 +1079,16 @@ func metadataEncrypter(key crypto.ObjectKey) objectMetaEncryptFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// metadataDecrypter reverses metadataEncrypter.
|
// metadataDecrypter reverses metadataEncrypter.
|
||||||
func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn {
|
func (o *ObjectInfo) metadataDecrypter(h http.Header) objectMetaDecryptFn {
|
||||||
return func(baseKey string, input []byte) ([]byte, error) {
|
return func(baseKey string, input []byte) ([]byte, error) {
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
return input, nil
|
return input, nil
|
||||||
}
|
}
|
||||||
|
var key []byte
|
||||||
key, err := decryptObjectMeta(nil, o.Bucket, o.Name, o.UserDefined)
|
if k, err := crypto.SSEC.ParseHTTP(h); err == nil {
|
||||||
|
key = k[:]
|
||||||
|
}
|
||||||
|
key, err := decryptObjectMeta(key, o.Bucket, o.Name, o.UserDefined)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1081,13 +1100,13 @@ func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn {
|
|||||||
|
|
||||||
// decryptChecksums will attempt to decode checksums and return it/them if set.
|
// decryptChecksums will attempt to decode checksums and return it/them if set.
|
||||||
// if part > 0, and we have the checksum for the part that will be returned.
|
// if part > 0, and we have the checksum for the part that will be returned.
|
||||||
func (o *ObjectInfo) decryptPartsChecksums() {
|
func (o *ObjectInfo) decryptPartsChecksums(h http.Header) {
|
||||||
data := o.Checksum
|
data := o.Checksum
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
||||||
decrypted, err := o.metadataDecrypter()("object-checksum", data)
|
decrypted, err := o.metadataDecrypter(h)("object-checksum", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
encLogIf(GlobalContext, err)
|
encLogIf(GlobalContext, err)
|
||||||
return
|
return
|
||||||
@@ -1143,15 +1162,17 @@ func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn
|
|||||||
|
|
||||||
// decryptChecksums will attempt to decode checksums and return it/them if set.
|
// decryptChecksums will attempt to decode checksums and return it/them if set.
|
||||||
// if part > 0, and we have the checksum for the part that will be returned.
|
// if part > 0, and we have the checksum for the part that will be returned.
|
||||||
func (o *ObjectInfo) decryptChecksums(part int) map[string]string {
|
func (o *ObjectInfo) decryptChecksums(part int, h http.Header) map[string]string {
|
||||||
data := o.Checksum
|
data := o.Checksum
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
||||||
decrypted, err := o.metadataDecrypter()("object-checksum", data)
|
decrypted, err := o.metadataDecrypter(h)("object-checksum", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
encLogIf(GlobalContext, err)
|
if err != crypto.ErrSecretKeyMismatch {
|
||||||
|
encLogIf(GlobalContext, err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data = decrypted
|
data = decrypted
|
||||||
|
|||||||
+28
-30
@@ -23,14 +23,13 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/pkg/v2/ellipses"
|
"github.com/minio/pkg/v3/ellipses"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file implements and supports ellipses pattern for
|
// This file implements and supports ellipses pattern for
|
||||||
@@ -134,7 +133,7 @@ func possibleSetCountsWithSymmetry(setCounts []uint64, argPatterns []ellipses.Ar
|
|||||||
// on each index, this function also determines the final set size
|
// on each index, this function also determines the final set size
|
||||||
// The final set size has the affinity towards choosing smaller
|
// The final set size has the affinity towards choosing smaller
|
||||||
// indexes (total sets)
|
// indexes (total sets)
|
||||||
func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint64, argPatterns []ellipses.ArgPattern) (setIndexes [][]uint64, err error) {
|
func getSetIndexes(args []string, totalSizes []uint64, setDriveCount uint64, argPatterns []ellipses.ArgPattern) (setIndexes [][]uint64, err error) {
|
||||||
if len(totalSizes) == 0 || len(args) == 0 {
|
if len(totalSizes) == 0 || len(args) == 0 {
|
||||||
return nil, errInvalidArgument
|
return nil, errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -142,7 +141,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
|
|||||||
setIndexes = make([][]uint64, len(totalSizes))
|
setIndexes = make([][]uint64, len(totalSizes))
|
||||||
for _, totalSize := range totalSizes {
|
for _, totalSize := range totalSizes {
|
||||||
// Check if totalSize has minimum range upto setSize
|
// Check if totalSize has minimum range upto setSize
|
||||||
if totalSize < setSizes[0] || totalSize < customSetDriveCount {
|
if totalSize < setSizes[0] || totalSize < setDriveCount {
|
||||||
msg := fmt.Sprintf("Incorrect number of endpoints provided %s", args)
|
msg := fmt.Sprintf("Incorrect number of endpoints provided %s", args)
|
||||||
return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
|
return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
|
||||||
}
|
}
|
||||||
@@ -167,11 +166,11 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
|
|||||||
var setSize uint64
|
var setSize uint64
|
||||||
// Custom set drive count allows to override automatic distribution.
|
// Custom set drive count allows to override automatic distribution.
|
||||||
// only meant if you want to further optimize drive distribution.
|
// only meant if you want to further optimize drive distribution.
|
||||||
if customSetDriveCount > 0 {
|
if setDriveCount > 0 {
|
||||||
msg := fmt.Sprintf("Invalid set drive count. Acceptable values for %d number drives are %d", commonSize, setCounts)
|
msg := fmt.Sprintf("Invalid set drive count. Acceptable values for %d number drives are %d", commonSize, setCounts)
|
||||||
var found bool
|
var found bool
|
||||||
for _, ss := range setCounts {
|
for _, ss := range setCounts {
|
||||||
if ss == customSetDriveCount {
|
if ss == setDriveCount {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,8 +179,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No automatic symmetry calculation expected, user is on their own
|
// No automatic symmetry calculation expected, user is on their own
|
||||||
setSize = customSetDriveCount
|
setSize = setDriveCount
|
||||||
globalCustomErasureDriveCount = true
|
|
||||||
} else {
|
} else {
|
||||||
// Returns possible set counts with symmetry.
|
// Returns possible set counts with symmetry.
|
||||||
setCounts = possibleSetCountsWithSymmetry(setCounts, argPatterns)
|
setCounts = possibleSetCountsWithSymmetry(setCounts, argPatterns)
|
||||||
@@ -256,7 +254,7 @@ func getTotalSizes(argPatterns []ellipses.ArgPattern) []uint64 {
|
|||||||
// Parses all arguments and returns an endpointSet which is a collection
|
// Parses all arguments and returns an endpointSet which is a collection
|
||||||
// of endpoints following the ellipses pattern, this is what is used
|
// of endpoints following the ellipses pattern, this is what is used
|
||||||
// by the object layer for initializing itself.
|
// by the object layer for initializing itself.
|
||||||
func parseEndpointSet(customSetDriveCount uint64, args ...string) (ep endpointSet, err error) {
|
func parseEndpointSet(setDriveCount uint64, args ...string) (ep endpointSet, err error) {
|
||||||
argPatterns := make([]ellipses.ArgPattern, len(args))
|
argPatterns := make([]ellipses.ArgPattern, len(args))
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
patterns, perr := ellipses.FindEllipsesPatterns(arg)
|
patterns, perr := ellipses.FindEllipsesPatterns(arg)
|
||||||
@@ -266,7 +264,7 @@ func parseEndpointSet(customSetDriveCount uint64, args ...string) (ep endpointSe
|
|||||||
argPatterns[i] = patterns
|
argPatterns[i] = patterns
|
||||||
}
|
}
|
||||||
|
|
||||||
ep.setIndexes, err = getSetIndexes(args, getTotalSizes(argPatterns), customSetDriveCount, argPatterns)
|
ep.setIndexes, err = getSetIndexes(args, getTotalSizes(argPatterns), setDriveCount, argPatterns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return endpointSet{}, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
return endpointSet{}, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
||||||
}
|
}
|
||||||
@@ -281,23 +279,14 @@ func parseEndpointSet(customSetDriveCount uint64, args ...string) (ep endpointSe
|
|||||||
// specific set size.
|
// specific set size.
|
||||||
// For example: {1...64} is divided into 4 sets each of size 16.
|
// For example: {1...64} is divided into 4 sets each of size 16.
|
||||||
// This applies to even distributed setup syntax as well.
|
// This applies to even distributed setup syntax as well.
|
||||||
func GetAllSets(args ...string) ([][]string, error) {
|
func GetAllSets(setDriveCount uint64, args ...string) ([][]string, error) {
|
||||||
var customSetDriveCount uint64
|
|
||||||
if v := env.Get(EnvErasureSetDriveCount, ""); v != "" {
|
|
||||||
driveCount, err := strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, config.ErrInvalidErasureSetSize(err)
|
|
||||||
}
|
|
||||||
customSetDriveCount = uint64(driveCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
var setArgs [][]string
|
var setArgs [][]string
|
||||||
if !ellipses.HasEllipses(args...) {
|
if !ellipses.HasEllipses(args...) {
|
||||||
var setIndexes [][]uint64
|
var setIndexes [][]uint64
|
||||||
// Check if we have more one args.
|
// Check if we have more one args.
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
var err error
|
var err error
|
||||||
setIndexes, err = getSetIndexes(args, []uint64{uint64(len(args))}, customSetDriveCount, nil)
|
setIndexes, err = getSetIndexes(args, []uint64{uint64(len(args))}, setDriveCount, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -311,7 +300,7 @@ func GetAllSets(args ...string) ([][]string, error) {
|
|||||||
}
|
}
|
||||||
setArgs = s.Get()
|
setArgs = s.Get()
|
||||||
} else {
|
} else {
|
||||||
s, err := parseEndpointSet(customSetDriveCount, args...)
|
s, err := parseEndpointSet(setDriveCount, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -336,8 +325,6 @@ const (
|
|||||||
EnvErasureSetDriveCount = "MINIO_ERASURE_SET_DRIVE_COUNT"
|
EnvErasureSetDriveCount = "MINIO_ERASURE_SET_DRIVE_COUNT"
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalCustomErasureDriveCount = false
|
|
||||||
|
|
||||||
type node struct {
|
type node struct {
|
||||||
nodeName string
|
nodeName string
|
||||||
disks []string
|
disks []string
|
||||||
@@ -366,8 +353,13 @@ func (el *endpointsList) add(arg string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type poolArgs struct {
|
||||||
|
args []string
|
||||||
|
setDriveCount uint64
|
||||||
|
}
|
||||||
|
|
||||||
// buildDisksLayoutFromConfFile supports with and without ellipses transparently.
|
// buildDisksLayoutFromConfFile supports with and without ellipses transparently.
|
||||||
func buildDisksLayoutFromConfFile(pools [][]string) (layout disksLayout, err error) {
|
func buildDisksLayoutFromConfFile(pools []poolArgs) (layout disksLayout, err error) {
|
||||||
if len(pools) == 0 {
|
if len(pools) == 0 {
|
||||||
return layout, errInvalidArgument
|
return layout, errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -375,7 +367,7 @@ func buildDisksLayoutFromConfFile(pools [][]string) (layout disksLayout, err err
|
|||||||
for _, list := range pools {
|
for _, list := range pools {
|
||||||
var endpointsList endpointsList
|
var endpointsList endpointsList
|
||||||
|
|
||||||
for _, arg := range list {
|
for _, arg := range list.args {
|
||||||
switch {
|
switch {
|
||||||
case ellipses.HasList(arg):
|
case ellipses.HasList(arg):
|
||||||
patterns, err := ellipses.FindListPatterns(arg)
|
patterns, err := ellipses.FindListPatterns(arg)
|
||||||
@@ -436,7 +428,7 @@ func buildDisksLayoutFromConfFile(pools [][]string) (layout disksLayout, err err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setArgs, err := GetAllSets(eps...)
|
setArgs, err := GetAllSets(list.setDriveCount, eps...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return layout, err
|
return layout, err
|
||||||
}
|
}
|
||||||
@@ -469,9 +461,15 @@ func mergeDisksLayoutFromArgs(args []string, ctxt *serverCtxt) (err error) {
|
|||||||
|
|
||||||
var setArgs [][]string
|
var setArgs [][]string
|
||||||
|
|
||||||
|
v, err := env.GetInt(EnvErasureSetDriveCount, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
setDriveCount := uint64(v)
|
||||||
|
|
||||||
// None of the args have ellipses use the old style.
|
// None of the args have ellipses use the old style.
|
||||||
if ok {
|
if ok {
|
||||||
setArgs, err = GetAllSets(args...)
|
setArgs, err = GetAllSets(setDriveCount, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -487,7 +485,7 @@ func mergeDisksLayoutFromArgs(args []string, ctxt *serverCtxt) (err error) {
|
|||||||
// TODO: support SNSD deployments to be decommissioned in future
|
// TODO: support SNSD deployments to be decommissioned in future
|
||||||
return fmt.Errorf("all args must have ellipses for pool expansion (%w) args: %s", errInvalidArgument, args)
|
return fmt.Errorf("all args must have ellipses for pool expansion (%w) args: %s", errInvalidArgument, args)
|
||||||
}
|
}
|
||||||
setArgs, err = GetAllSets(arg)
|
setArgs, err = GetAllSets(setDriveCount, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/pkg/v2/ellipses"
|
"github.com/minio/pkg/v3/ellipses"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests create endpoints with ellipses and without.
|
// Tests create endpoints with ellipses and without.
|
||||||
|
|||||||
+2
-2
@@ -36,8 +36,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/mountinfo"
|
"github.com/minio/minio/internal/mountinfo"
|
||||||
"github.com/minio/pkg/v2/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
xnet "github.com/minio/pkg/v2/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user