mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 02:33:16 +03:00
Compare commits
379 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f678bcf7ba | |||
| a0f06eac2a | |||
| 83fe1a2732 | |||
| 5c98223c89 | |||
| 663a0b7783 | |||
| 6efe4d1df6 | |||
| fb17f97cf3 | |||
| 6b65ba1551 | |||
| 9202c6e26a | |||
| 59a5456091 | |||
| 8bfe972bab | |||
| fd6622458b | |||
| 8a08861dd9 | |||
| 82dcfd4e10 | |||
| b66d7dc708 | |||
| eebdd2b31d | |||
| b94733ab31 | |||
| 7f2c90a0ed | |||
| 84bb7d05a9 | |||
| 98a84d88e2 | |||
| e470268c7c | |||
| 3a6cd4f73d | |||
| 6ea150fd68 | |||
| a7188bc9d0 | |||
| e1e9ddd4a4 | |||
| a1dd08f2e6 | |||
| d136ac0596 | |||
| c33a237067 | |||
| eb7d3da994 | |||
| 37134e42d4 | |||
| 626a4efaad | |||
| 0c1f8b4e0f | |||
| 857674c3a0 | |||
| 15a75bd79b | |||
| 74887c7372 | |||
| 31188e9327 | |||
| ee6d96eb46 | |||
| 11fe2fd79a | |||
| c2863cc6ef | |||
| d6d01067a0 | |||
| 1d3b18c3f4 | |||
| a15b6f21b8 | |||
| 689179bf18 | |||
| bf749eec61 | |||
| d0f4cc89a5 | |||
| d65debb6bc | |||
| 72daccd468 | |||
| b363400587 | |||
| 6b41f941b6 | |||
| b9f5f9ba3f | |||
| c8ffa59d28 | |||
| 1141187bf2 | |||
| 52aeebebea | |||
| e101384aa4 | |||
| 71f02adfca | |||
| 9de26531e4 | |||
| fadc46b906 | |||
| b1d98febfd | |||
| 1828fb212a | |||
| c97f50e274 | |||
| be92046dfd | |||
| 990fc415f7 | |||
| d8daabae9b | |||
| 84fe4fd156 | |||
| 747d475e76 | |||
| 095b518802 | |||
| 0319ae756a | |||
| 11c7ecb5cf | |||
| 422c396d73 | |||
| ffd57fde90 | |||
| a451d1cb8d | |||
| 14cf8f1b22 | |||
| 5996c8c4d5 | |||
| 21885f9457 | |||
| 6ac48aff46 | |||
| 85ff76e7b0 | |||
| e47a31f9fc | |||
| 517fcd423d | |||
| b780359598 | |||
| aa8b9572b9 | |||
| 8ca14e6267 | |||
| 876e1a91b2 | |||
| 0b7989aa4b | |||
| 2278fc8f47 | |||
| a91f353621 | |||
| cdb1b48ad9 | |||
| a24037bfec | |||
| 2d0f30f062 | |||
| cea2ca8c8e | |||
| f713436dd0 | |||
| b923a62425 | |||
| 67fce4a5b3 | |||
| eaa65b7ade | |||
| 820d94447c | |||
| ed20134a7b | |||
| d19cbc81b5 | |||
| 1fd7946dce | |||
| 027ff0f3a8 | |||
| 8fa80874a6 | |||
| 430669cfad | |||
| 54b561898f | |||
| 65c104a589 | |||
| 0a0416b6ea | |||
| 441babdc41 | |||
| 1bf1fafc86 | |||
| 50d58e9b2d | |||
| e64b9f6751 | |||
| d67a846ec4 | |||
| ca2a1c3f60 | |||
| 93fbb228bf | |||
| 3683673fb0 | |||
| f37a5b6dae | |||
| 31b0decd46 | |||
| eb561e1c05 | |||
| 54c9ecff5b | |||
| edcd72585d | |||
| 1a17fc17bb | |||
| ddad231921 | |||
| e73894fa50 | |||
| 03b94f907f | |||
| 3fa7218c44 | |||
| 0f591d245d | |||
| 1b02e046c2 | |||
| d08e3cc895 | |||
| d98116559b | |||
| 71c95ad0d0 | |||
| 5c1a4ba5f9 | |||
| 698862ec5d | |||
| b4ef5ff294 | |||
| 3db658e51e | |||
| 5a9f7516d6 | |||
| 3039fd4519 | |||
| 095fc0561d | |||
| beb1924437 | |||
| c8e1154f1e | |||
| b204c2dbec | |||
| b22b39de96 | |||
| c242e6c391 | |||
| e05205756f | |||
| d03b244fcd | |||
| 5ef679d8f1 | |||
| d33c527e39 | |||
| 7bc95c47a3 | |||
| 475a88b555 | |||
| 9815dac48f | |||
| 1ece3d1dfe | |||
| 2146ed4033 | |||
| 52b88b52f0 | |||
| ebd4388cca | |||
| 57fd02ee57 | |||
| 0333412148 | |||
| 1c85652cff | |||
| e0086c1be7 | |||
| b29e159604 | |||
| 7883e55da2 | |||
| b197623ed2 | |||
| a15a2556c3 | |||
| 14d29b77ae | |||
| a2514ffeed | |||
| f1bbb7fef5 | |||
| 72394a8319 | |||
| 1cd8e1d8b6 | |||
| 62cd918061 | |||
| 6a04067514 | |||
| 49b3908635 | |||
| 75faef888e | |||
| b67d97b1ba | |||
| b8943fdf19 | |||
| f93183f66e | |||
| 2937711390 | |||
| aa56c6d51d | |||
| 27417459fb | |||
| 5b8fe2e89a | |||
| acc9c033ed | |||
| 8528b265a9 | |||
| 44250f1a52 | |||
| f7560670d9 | |||
| 3891885800 | |||
| b882310e2b | |||
| de0b43de32 | |||
| 48152a56ac | |||
| 29dd7f1d68 | |||
| 6423e4c767 | |||
| 1dd8f0e8f3 | |||
| 2fa35def2c | |||
| 34167c51d5 | |||
| a5f8af4efb | |||
| 5a218f38a1 | |||
| e57e946206 | |||
| b4f71362e9 | |||
| ed37b7a9d5 | |||
| 6511021fbe | |||
| 6197ba851b | |||
| 3ae1f9d852 | |||
| 0db1930f48 | |||
| 89db3fdb5d | |||
| 80fc3a8a52 | |||
| 988a2e8fed | |||
| 2433698372 | |||
| 5d7e8f79ed | |||
| bad229e16e | |||
| d37e514733 | |||
| c73ea27ed7 | |||
| 0159b56717 | |||
| 9e6cc847f8 | |||
| 709eb283d9 | |||
| 76dde82b41 | |||
| 939c0100a6 | |||
| 2d60bf8c50 | |||
| 37e20f6ef2 | |||
| 76905b7a67 | |||
| a469e6768d | |||
| 2fc182d8e6 | |||
| a2cbeaa9e6 | |||
| 444ff20bc5 | |||
| 20ef5e7a6a | |||
| c233c8e329 | |||
| e06127566d | |||
| dfe73629a3 | |||
| b03dd1af17 | |||
| 4bc367c490 | |||
| 3eb2d086b2 | |||
| 70986b6e6e | |||
| 8edc2faaa9 | |||
| ebe395788b | |||
| 12fd6678ee | |||
| 90d35b70b4 | |||
| 9f71369b67 | |||
| 04ae9058ed | |||
| a30cfdd88f | |||
| 1bae32dc96 | |||
| 932d2c3c62 | |||
| 52f4124678 | |||
| 8d8d07ac5c | |||
| 44735be38e | |||
| 1ef1b2ba50 | |||
| 6fdbd778d5 | |||
| 419f351df3 | |||
| 180d6b30ca | |||
| 3fd9059b4e | |||
| a713aee3d5 | |||
| a9f5b58a01 | |||
| d882ba2cb4 | |||
| 90e37a8745 | |||
| 6086f45d25 | |||
| d6351879f3 | |||
| 5655272f5a | |||
| 9b35c72349 | |||
| 98cffbce03 | |||
| 1cd875de1e | |||
| 5a8df7efb3 | |||
| c84e2939e4 | |||
| 641ab24aec | |||
| 71133105d7 | |||
| 625677b189 | |||
| 76943ac05e | |||
| 87cbd41265 | |||
| be92cf5959 | |||
| cc1d8f0057 | |||
| 1f1dcdce65 | |||
| 98a67a3776 | |||
| 9b1e70e4f9 | |||
| 09d4f8cd0f | |||
| 53cbc020b9 | |||
| 63fc6ba2cd | |||
| ce53d7f6c2 | |||
| fe8eed963e | |||
| 97eb7dbf5f | |||
| 59f877fc64 | |||
| f96fe9773c | |||
| 04948b4d55 | |||
| 98ba622679 | |||
| 08103870a5 | |||
| 993e586855 | |||
| 58ec835af0 | |||
| 6aea950d74 | |||
| 7198be5be9 | |||
| 3661aaf8a1 | |||
| a22b4adf4c | |||
| b7bb122be8 | |||
| 8441a3bf5f | |||
| 853c4de75a | |||
| 3597af789e | |||
| 4c9cac0b47 | |||
| 1a0b68498b | |||
| 5246e3be84 | |||
| 8a07000e58 | |||
| 3bb82ef60d | |||
| c8a221a9a7 | |||
| 91f45c4aa6 | |||
| 7c5e4da90c | |||
| d6bc141bd1 | |||
| 7ac64ad24a | |||
| 14e52f29b0 | |||
| 344ae9f84e | |||
| f7db12c7ef | |||
| 962d1f1a71 | |||
| 6d76db9d6c | |||
| 00857f8f59 | |||
| 66239f30ce | |||
| bf89f79694 | |||
| ce299b47ea | |||
| 6dc7109a9f | |||
| bdcb485740 | |||
| e32b948a49 | |||
| 4fe9cbb973 | |||
| 5b242f1d11 | |||
| 34d28dd79f | |||
| 6eef9b4a23 | |||
| 5f1999cc71 | |||
| 40a2c6b882 | |||
| 7ba281728f | |||
| 7b7356f04c | |||
| bbc312fce6 | |||
| 1b0dfb0f58 | |||
| 7260241511 | |||
| 3b1a9b9fdf | |||
| 52769e1e71 | |||
| 72afc2727a | |||
| 808739867c | |||
| 752e18e795 | |||
| 76d822bf1e | |||
| ddeca9f12a | |||
| 19d0340ddf | |||
| 21251d8c22 | |||
| 1f3db03bf0 | |||
| 944c62daf4 | |||
| 9547b7d0e9 | |||
| 76c4ea7682 | |||
| 808ecfe0f2 | |||
| 2894dd4d1a | |||
| 797fa7f97b | |||
| fd8750e959 | |||
| 7be65f66b8 | |||
| 4f5d38a4b1 | |||
| 7e73fc2870 | |||
| d2c9a9e395 | |||
| 0d49b365ff | |||
| 7721595aa9 | |||
| fd6f6fc8df | |||
| 4fb47cd568 | |||
| ecc932d5dd | |||
| b57fbff7c1 | |||
| 4892a766a8 | |||
| 0303cd8625 | |||
| d765b89a63 | |||
| 6e4acf0504 | |||
| 71954faa3a | |||
| 6d22e74d11 | |||
| dc92bb4646 | |||
| 0f0e154315 | |||
| 136d41775f | |||
| ec77d28e62 | |||
| 86420a1f46 | |||
| 7dd8b6c8ed | |||
| 8afa6fefd8 | |||
| 533c9d4fe3 | |||
| a35ef155fc | |||
| 8dd3c41b2a | |||
| 4523da6543 | |||
| ce8456a1a9 | |||
| 1673778633 | |||
| 9ce1884732 | |||
| 23b329b9df | |||
| 0c34e51a75 | |||
| 1633b30979 | |||
| 630dabf4b9 | |||
| fc6c794972 | |||
| 2e33b99c6b | |||
| 3b7292b637 | |||
| e4f469ae7a | |||
| c921dc75c7 | |||
| 86d543d0f6 | |||
| e4e90b53c1 | |||
| 58d776daa0 | |||
| f6b2e89109 | |||
| ac85c2af76 | |||
| 5aba2aedb3 | |||
| bd77f1df4c |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.5b7]
|
go-version: [1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -30,9 +30,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup dockerfile for build test
|
- name: Setup dockerfile for build test
|
||||||
run: |
|
run: |
|
||||||
echo "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:${{ matrix.go-version }}" > Dockerfile.fips.test
|
echo "FROM golang:1.19.4" >> Dockerfile.fips.test
|
||||||
echo "COPY . /minio" >> Dockerfile.fips.test
|
echo "COPY . /minio" >> Dockerfile.fips.test
|
||||||
echo "WORKDIR /minio" >> Dockerfile.fips.test
|
echo "WORKDIR /minio" >> Dockerfile.fips.test
|
||||||
|
echo "ENV GOEXPERIMENT=boringcrypto" >> Dockerfile.fips.test
|
||||||
echo "RUN make" >> Dockerfile.fips.test
|
echo "RUN make" >> Dockerfile.fips.test
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.19.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -34,6 +34,7 @@ jobs:
|
|||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
run: |
|
run: |
|
||||||
|
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 50m ./...
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
||||||
# the tests.
|
# the tests.
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x]
|
go-version: [1.19.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"]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.19.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.19.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vulncheck:
|
vulncheck:
|
||||||
name: Analysis
|
name: Analysis
|
||||||
@@ -16,7 +20,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.19.x
|
go-version: 1.19
|
||||||
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
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ mc.*
|
|||||||
s3-check-md5*
|
s3-check-md5*
|
||||||
xl-meta*
|
xl-meta*
|
||||||
healing-*
|
healing-*
|
||||||
inspect*
|
inspect*.zip
|
||||||
200M*
|
200M*
|
||||||
hash-set
|
hash-set
|
||||||
minio.RELEASE*
|
minio.RELEASE*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -1,4 +1,4 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
@@ -29,13 +29,15 @@ COPY LICENSE /licenses/LICENSE
|
|||||||
RUN \
|
RUN \
|
||||||
microdnf clean all && \
|
microdnf clean all && \
|
||||||
microdnf update --nodocs && \
|
microdnf update --nodocs && \
|
||||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
microdnf install curl ca-certificates shadow-utils util-linux gzip lsof tar net-tools --nodocs && \
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
||||||
microdnf install minisign --nodocs && \
|
microdnf install minisign --nodocs && \
|
||||||
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /opt/bin/minio && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /opt/bin/minio && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
|
||||||
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /opt/bin/mc && \
|
||||||
|
gzip /opt/bin/mc && \
|
||||||
microdnf clean all && \
|
microdnf clean all && \
|
||||||
chmod +x /opt/bin/minio && \
|
chmod +x /opt/bin/minio && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ help: ## print this help
|
|||||||
getdeps: ## fetch necessary dependencies
|
getdeps: ## fetch necessary dependencies
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
|
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
|
||||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@f3635b96e4838a6c773babb65ef35297fe5fe2f9
|
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.7
|
||||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||||
|
@echo "Installing staticcheck" && go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
|
|
||||||
crosscompile: ## cross compile minio
|
crosscompile: ## cross compile minio
|
||||||
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
||||||
@@ -35,6 +36,7 @@ check-gen: ## check for updated autogenerated files
|
|||||||
lint: ## runs golangci-lint suite of linters
|
lint: ## runs golangci-lint suite of linters
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
@${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
||||||
|
@${GOPATH}/bin/staticcheck --tests=false ./...
|
||||||
|
|
||||||
check: test
|
check: test
|
||||||
test: verifiers build ## builds minio, runs linters, tests
|
test: verifiers build ## builds minio, runs linters, tests
|
||||||
@@ -62,11 +64,18 @@ test-iam: build ## verify IAM (external IDP, etcd backends)
|
|||||||
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
||||||
|
|
||||||
test-replication: install ## verify multi site replication
|
test-replication-2site:
|
||||||
@echo "Running tests for replicating three sites"
|
|
||||||
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
|
||||||
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
||||||
|
|
||||||
|
test-replication-3site:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
||||||
|
|
||||||
|
test-delete-replication:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||||
|
|
||||||
|
test-replication: install test-replication-2site test-replication-3site test-delete-replication ## verify multi site replication
|
||||||
|
@echo "Running tests for replicating three sites"
|
||||||
|
|
||||||
test-site-replication-ldap: install ## verify automatic site replication
|
test-site-replication-ldap: install ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
||||||
@@ -133,7 +142,7 @@ docker-hotfix: hotfix-push checks ## builds minio docker container with hotfix t
|
|||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix
|
@docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix
|
||||||
|
|
||||||
docker: build checks ## builds minio docker container
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
MinIO Project, (C) 2015-2021 MinIO, Inc.
|
MinIO Project, (C) 2015-2023 MinIO, Inc.
|
||||||
|
|
||||||
This product includes software developed at MinIO, Inc.
|
This product includes software developed at MinIO, Inc.
|
||||||
(https://min.io/).
|
(https://min.io/).
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ You can also connect using any S3-compatible tool, such as the MinIO Client `mc`
|
|||||||
|
|
||||||
## Install from Source
|
## Install from Source
|
||||||
|
|
||||||
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.18](https://golang.org/dl/#stable)
|
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.19](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install github.com/minio/minio@latest
|
go install github.com/minio/minio@latest
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function _init() {
|
|||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
## List of architectures and OS to test coss compilation.
|
## List of architectures and OS to test coss compilation.
|
||||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
||||||
}
|
}
|
||||||
|
|
||||||
function _build() {
|
function _build() {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
+2
-2
@@ -22,9 +22,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
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/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
if aclHeader == "" {
|
if aclHeader == "" {
|
||||||
acl := &accessControlPolicy{}
|
acl := &accessControlPolicy{}
|
||||||
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
||||||
if err == io.EOF {
|
if terr, ok := err.(*xml.SyntaxError); ok && terr.Msg == io.EOF.Error() {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader),
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader),
|
||||||
r.URL)
|
r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/klauspost/compress/zip"
|
"github.com/klauspost/compress/zip"
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
@@ -41,6 +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/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
@@ -107,10 +107,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil {
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
@@ -160,11 +157,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
update := r.Form.Get("update") == "true"
|
update := r.Form.Get("update") == "true"
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -177,7 +169,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
|
cred, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -206,7 +198,18 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
if update {
|
if update {
|
||||||
ops = madmin.GetTargetUpdateOps(r.Form)
|
ops = madmin.GetTargetUpdateOps(r.Form)
|
||||||
} else {
|
} else {
|
||||||
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
var exists bool // true if arn exists
|
||||||
|
target.Arn, exists = globalBucketTargetSys.getRemoteARN(bucket, &target, "")
|
||||||
|
if exists && target.Arn != "" { // return pre-existing ARN
|
||||||
|
data, err := json.Marshal(target.Arn)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseJSON(w, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if target.Arn == "" {
|
if target.Arn == "" {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
@@ -222,6 +225,10 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
switch op {
|
switch op {
|
||||||
case madmin.CredentialsUpdateType:
|
case madmin.CredentialsUpdateType:
|
||||||
|
if globalSiteReplicationSys.isEnabled() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetDenyEditError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
tgt.Credentials = target.Credentials
|
tgt.Credentials = target.Credentials
|
||||||
tgt.TargetBucket = target.TargetBucket
|
tgt.TargetBucket = target.TargetBucket
|
||||||
tgt.Secure = target.Secure
|
tgt.Secure = target.Secure
|
||||||
@@ -289,10 +296,6 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arnType := vars["type"]
|
arnType := vars["type"]
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -328,10 +331,6 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arn := vars["arn"]
|
arn := vars["arn"]
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -373,10 +372,6 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
bucket := pathClean(r.Form.Get("bucket"))
|
bucket := pathClean(r.Form.Get("bucket"))
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -661,10 +656,6 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -717,7 +708,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
opts := MakeBucketOptions{
|
opts := MakeBucketOptions{
|
||||||
LockEnabled: config.ObjectLockEnabled == "Enabled",
|
LockEnabled: config.ObjectLockEnabled == "Enabled",
|
||||||
}
|
}
|
||||||
err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts)
|
err = objectAPI.MakeBucket(ctx, bucket, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
@@ -778,7 +769,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}); err != nil {
|
if err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
@@ -830,7 +821,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
bucket, fileName := slc[0], slc[1]
|
bucket, fileName := slc[0], slc[1]
|
||||||
// create bucket if it does not exist yet.
|
// create bucket if it does not exist yet.
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
|
err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
rpt.SetStatus(bucket, "", err)
|
rpt.SetStatus(bucket, "", err)
|
||||||
@@ -884,7 +875,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// Version in policy must not be empty
|
// Version in policy must not be empty
|
||||||
if bucketPolicy.Version == "" {
|
if bucketPolicy.Version == "" {
|
||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrMalformedPolicy.String()))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrPolicyInvalidVersion.String()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1091,11 +1082,6 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
+23
-29
@@ -23,8 +23,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
@@ -84,7 +84,13 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case config.Error:
|
case config.ErrConfigNotFound:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioConfigNotFoundError",
|
||||||
|
Description: e.Error(),
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
case config.ErrConfigGeneric:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioConfigError",
|
Code: "XMinioConfigError",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
@@ -124,6 +130,18 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
|
case errors.Is(err, errDecommissionRebalanceAlreadyRunning):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioDecommissionNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
case errors.Is(err, errRebalanceDecommissionAlreadyRunning):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioRebalanceNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
case errors.Is(err, errConfigNotFound):
|
case errors.Is(err, errConfigNotFound):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioConfigError",
|
Code: "XMinioConfigError",
|
||||||
@@ -136,15 +154,9 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
}
|
}
|
||||||
case errors.Is(err, errIAMServiceAccount):
|
case errors.Is(err, errIAMServiceAccountNotAllowed):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioIAMServiceAccount",
|
Code: "XMinioIAMServiceAccountNotAllowed",
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errors.Is(err, errIAMServiceAccountUsed):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioIAMServiceAccountUsed",
|
|
||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
@@ -192,24 +204,6 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case errors.Is(err, errTierBackendInUse):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierBackendInUse",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errors.Is(err, errTierBackendNotEmpty):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierBackendNotEmpty",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errors.Is(err, errTierInsufficientCreds):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierInsufficientCreds",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errIsTierPermError(err):
|
case errIsTierPermError(err):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioAdminTierInsufficientPermissions",
|
Code: "XMinioAdminTierInsufficientPermissions",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -26,8 +26,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/cache"
|
"github.com/minio/minio/internal/config/cache"
|
||||||
"github.com/minio/minio/internal/config/etcd"
|
"github.com/minio/minio/internal/config/etcd"
|
||||||
@@ -37,6 +36,7 @@ import (
|
|||||||
polplugin "github.com/minio/minio/internal/config/policy/plugin"
|
polplugin "github.com/minio/minio/internal/config/policy/plugin"
|
||||||
"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/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -117,6 +117,26 @@ func applyDynamic(ctx context.Context, objectAPI ObjectLayer, cfg config.Config,
|
|||||||
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type badConfigErr struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error - return the error message
|
||||||
|
func (bce badConfigErr) Error() string {
|
||||||
|
return bce.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap the error to its underlying error.
|
||||||
|
func (bce badConfigErr) Unwrap() error {
|
||||||
|
return bce.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
type setConfigResult struct {
|
||||||
|
Cfg config.Config
|
||||||
|
SubSys string
|
||||||
|
Dynamic bool
|
||||||
|
}
|
||||||
|
|
||||||
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
||||||
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetConfigKV")
|
ctx := newContext(r, w, "SetConfigKV")
|
||||||
@@ -142,46 +162,53 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
result, err := setConfigKV(ctx, objectAPI, kvBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
switch err.(type) {
|
||||||
|
case badConfigErr:
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
|
default:
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
|
if result.Dynamic {
|
||||||
|
applyDynamic(ctx, objectAPI, result.Cfg, result.SubSys, r, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (result setConfigResult, err error) {
|
||||||
|
result.Cfg, err = readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
subSys, _, _, err := config.GetSubSys(string(kvBytes))
|
result.Dynamic, err = result.Cfg.ReadConfig(bytes.NewReader(kvBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, subSys); err != nil {
|
result.SubSys, _, _, err = config.GetSubSys(string(kvBytes))
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if verr := validateConfig(result.Cfg, result.SubSys); verr != nil {
|
||||||
|
err = badConfigErr{Err: verr}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the actual server config on disk.
|
// Update the actual server config on disk.
|
||||||
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
if err = saveServerConfig(ctx, objectAPI, result.Cfg); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to the config input KV to history.
|
// Write the config input KV to history.
|
||||||
if err = saveServerConfigHistory(ctx, objectAPI, kvBytes); err != nil {
|
err = saveServerConfigHistory(ctx, objectAPI, kvBytes)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if dynamic {
|
|
||||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
||||||
@@ -225,7 +252,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
for _, subSysConfig := range subSysConfigs {
|
for _, subSysConfig := range subSysConfigs {
|
||||||
subSysConfig.AddString(&s, false)
|
subSysConfig.WriteTo(&s, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
@@ -296,7 +323,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -454,10 +481,6 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
hkvs := config.HelpSubSysMap[""]
|
hkvs := config.HelpSubSysMap[""]
|
||||||
var count int
|
|
||||||
for _, hkv := range hkvs {
|
|
||||||
count += len(cfg[hkv.Key])
|
|
||||||
}
|
|
||||||
for _, hkv := range hkvs {
|
for _, hkv := range hkvs {
|
||||||
// We ignore the error below, as we cannot get one.
|
// We ignore the error below, as we cannot get one.
|
||||||
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "")
|
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "")
|
||||||
@@ -482,7 +505,7 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
case config.IdentityPluginSubSys:
|
case config.IdentityPluginSubSys:
|
||||||
off = !idplugin.Enabled(item.Config)
|
off = !idplugin.Enabled(item.Config)
|
||||||
}
|
}
|
||||||
item.AddString(&s, off)
|
item.WriteTo(&s, off)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,23 +26,18 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
|
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
"github.com/minio/pkg/ldap"
|
"github.com/minio/pkg/ldap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetIdentityProviderCfg:
|
func (a adminAPIHandlers) addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
||||||
//
|
|
||||||
// PUT <admin-prefix>/id-cfg?type=openid&name=dex1
|
|
||||||
func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "SetIdentityCfg")
|
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
@@ -54,6 +49,14 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure body content type is opaque to ensure that request body has not
|
||||||
|
// been interpreted as form data.
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType != "application/octet-stream" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -68,44 +71,43 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfgDataBuilder strings.Builder
|
var subSys string
|
||||||
switch idpCfgType {
|
switch idpCfgType {
|
||||||
case madmin.OpenidIDPCfg:
|
case madmin.OpenidIDPCfg:
|
||||||
fmt.Fprintf(&cfgDataBuilder, "identity_openid")
|
subSys = madmin.IdentityOpenIDSubSys
|
||||||
case madmin.LDAPIDPCfg:
|
case madmin.LDAPIDPCfg:
|
||||||
fmt.Fprintf(&cfgDataBuilder, "identity_ldap")
|
subSys = madmin.IdentityLDAPSubSys
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure body content type is opaque.
|
|
||||||
contentType := r.Header.Get("Content-Type")
|
|
||||||
if contentType != "application/octet-stream" {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subsystem configuration name could be empty.
|
|
||||||
cfgName := mux.Vars(r)["name"]
|
cfgName := mux.Vars(r)["name"]
|
||||||
|
cfgTarget := madmin.Default
|
||||||
if cfgName != "" {
|
if cfgName != "" {
|
||||||
if idpCfgType == madmin.LDAPIDPCfg {
|
cfgTarget = cfgName
|
||||||
// LDAP does not support multiple configurations. So this must be
|
if idpCfgType == madmin.LDAPIDPCfg && cfgName != madmin.Default {
|
||||||
// empty.
|
// LDAP does not support multiple configurations. So cfgName must be
|
||||||
|
// empty or `madmin.Default`.
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.SubSystemSeparator, cfgName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.KvSpaceSeparator, string(reqBytes))
|
// Check that this is a valid Create vs Update API call.
|
||||||
|
s := globalServerConfig.Clone()
|
||||||
cfgData := cfgDataBuilder.String()
|
if apiErrCode := handleCreateUpdateValidation(s, subSys, cfgTarget, isUpdate); apiErrCode != ErrNone {
|
||||||
subSys, _, _, err := config.GetSubSys(cfgData)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL)
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfgData := ""
|
||||||
|
{
|
||||||
|
tgtSuffix := ""
|
||||||
|
if cfgTarget != madmin.Default {
|
||||||
|
tgtSuffix = config.SubSystemSeparator + cfgTarget
|
||||||
|
}
|
||||||
|
cfgData = subSys + tgtSuffix + config.KvSpaceSeparator + string(reqBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -153,79 +155,86 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIdentityProviderCfg:
|
func handleCreateUpdateValidation(s config.Config, subSys, cfgTarget string, isUpdate bool) APIErrorCode {
|
||||||
//
|
if cfgTarget != madmin.Default {
|
||||||
// GET <admin-prefix>/id-cfg?type=openid&name=dex_test
|
// This cannot give an error at this point.
|
||||||
//
|
subSysTargets, _ := s.GetAvailableTargets(subSys)
|
||||||
// GetIdentityProviderCfg returns a list of configured IDPs on the server if
|
subSysTargetsSet := set.CreateStringSet(subSysTargets...)
|
||||||
// name is empty. If name is non-empty, returns the configuration details for
|
if isUpdate && !subSysTargetsSet.Contains(cfgTarget) {
|
||||||
// the IDP of the given type and configuration name. The configuration name for
|
return ErrAdminConfigIDPCfgNameDoesNotExist
|
||||||
// the default ("un-named") configuration target is `_`.
|
}
|
||||||
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
if !isUpdate && subSysTargetsSet.Contains(cfgTarget) {
|
||||||
ctx := newContext(r, w, "GetIdentityProviderCfg")
|
return ErrAdminConfigIDPCfgNameAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the default configuration name, since it will always be an available
|
||||||
|
// target, we need to check if a configuration value has been set previously
|
||||||
|
// to figure out if this is a valid create or update API call.
|
||||||
|
|
||||||
|
// This cannot really error (FIXME: improve the type for GetConfigInfo)
|
||||||
|
var cfgInfos []madmin.IDPCfgInfo
|
||||||
|
switch subSys {
|
||||||
|
case madmin.IdentityOpenIDSubSys:
|
||||||
|
cfgInfos, _ = globalOpenIDConfig.GetConfigInfo(s, cfgTarget)
|
||||||
|
case madmin.IdentityLDAPSubSys:
|
||||||
|
cfgInfos, _ = globalLDAPConfig.GetConfigInfo(s, cfgTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfgInfos) > 0 && !isUpdate {
|
||||||
|
return ErrAdminConfigIDPCfgNameAlreadyExists
|
||||||
|
}
|
||||||
|
if len(cfgInfos) == 0 && isUpdate {
|
||||||
|
return ErrAdminConfigIDPCfgNameDoesNotExist
|
||||||
|
}
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIdentityProviderCfg: adds a new IDP config for openid/ldap.
|
||||||
|
//
|
||||||
|
// PUT <admin-prefix>/idp-cfg/openid/dex1 -> create named config `dex1`
|
||||||
|
//
|
||||||
|
// PUT <admin-prefix>/idp-cfg/openid/_ -> create (default) named config `_`
|
||||||
|
func (a adminAPIHandlers) AddIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "AddIdentityProviderCfg")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
a.addOrUpdateIDPHandler(ctx, w, r, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateIdentityProviderCfg: updates an existing IDP config for openid/ldap.
|
||||||
|
//
|
||||||
|
// PATCH <admin-prefix>/idp-cfg/openid/dex1 -> update named config `dex1`
|
||||||
|
//
|
||||||
|
// PATCH <admin-prefix>/idp-cfg/openid/_ -> update (default) named config `_`
|
||||||
|
func (a adminAPIHandlers) UpdateIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "UpdateIdentityProviderCfg")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
a.addOrUpdateIDPHandler(ctx, w, r, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIdentityProviderCfg:
|
||||||
|
//
|
||||||
|
// GET <admin-prefix>/idp-cfg/openid -> lists openid provider configs.
|
||||||
|
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListIdentityProviderCfg")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
idpCfgType := mux.Vars(r)["type"]
|
|
||||||
cfgName := r.Form.Get("name")
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
|
|
||||||
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no cfgName is provided, we list.
|
|
||||||
if cfgName == "" {
|
|
||||||
a.listIdentityProviders(ctx, w, r, idpCfgType, password)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
|
||||||
var cfgInfos []madmin.IDPCfgInfo
|
|
||||||
var err error
|
|
||||||
switch idpCfgType {
|
|
||||||
case madmin.OpenidIDPCfg:
|
|
||||||
cfgInfos, err = globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
|
||||||
case madmin.LDAPIDPCfg:
|
|
||||||
cfgInfos, err = globalLDAPConfig.GetConfigInfo(cfg, cfgName)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := madmin.IDPConfig{
|
|
||||||
Type: idpCfgType,
|
|
||||||
Name: cfgName,
|
|
||||||
Info: cfgInfos,
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
econfigData, err := madmin.EncryptData(password, data)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessResponseJSON(w, econfigData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, idpCfgType, password string) {
|
|
||||||
var cfgList []madmin.IDPListItem
|
var cfgList []madmin.IDPListItem
|
||||||
var err error
|
var err error
|
||||||
switch idpCfgType {
|
switch idpCfgType {
|
||||||
@@ -261,9 +270,69 @@ func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.Resp
|
|||||||
writeSuccessResponseJSON(w, econfigData)
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIdentityProviderCfg:
|
||||||
|
//
|
||||||
|
// GET <admin-prefix>/idp-cfg/openid/dex_test
|
||||||
|
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetIdentityProviderCfg")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
|
cfgName := mux.Vars(r)["name"]
|
||||||
|
password := cred.SecretKey
|
||||||
|
|
||||||
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := globalServerConfig.Clone()
|
||||||
|
var cfgInfos []madmin.IDPCfgInfo
|
||||||
|
var err error
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
cfgInfos, err = globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
cfgInfos, err = globalLDAPConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) || errors.Is(err, cfgldap.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := madmin.IDPConfig{
|
||||||
|
Type: idpCfgType,
|
||||||
|
Name: cfgName,
|
||||||
|
Info: cfgInfos,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
econfigData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteIdentityProviderCfg:
|
// DeleteIdentityProviderCfg:
|
||||||
//
|
//
|
||||||
// DELETE <admin-prefix>/id-cfg?type=openid&name=dex_test
|
// DELETE <admin-prefix>/idp-cfg/openid/dex_test
|
||||||
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteIdentityProviderCfg")
|
ctx := newContext(r, w, "DeleteIdentityProviderCfg")
|
||||||
|
|
||||||
@@ -339,7 +408,7 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v2"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
||||||
|
//
|
||||||
|
// GET <admin-prefix>/idp/ldap/policy-entities?[query-params]
|
||||||
|
//
|
||||||
|
// Query params:
|
||||||
|
//
|
||||||
|
// user=... -> repeatable query parameter, specifying users to query for
|
||||||
|
// policy mapping
|
||||||
|
//
|
||||||
|
// group=... -> repeatable query parameter, specifying groups to query for
|
||||||
|
// policy mapping
|
||||||
|
//
|
||||||
|
// policy=... -> repeatable query parameter, specifying policy to query for
|
||||||
|
// user/group mapping
|
||||||
|
//
|
||||||
|
// When all query parameters are omitted, returns mappings for all policies.
|
||||||
|
func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListLDAPPolicyMappingEntities")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Check authorization.
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r,
|
||||||
|
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate API arguments.
|
||||||
|
|
||||||
|
q := madmin.PolicyEntitiesQuery{
|
||||||
|
Users: r.Form["user"],
|
||||||
|
Groups: r.Form["group"],
|
||||||
|
Policy: r.Form["policy"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query IAM
|
||||||
|
|
||||||
|
res, err := globalIAMSys.QueryLDAPPolicyEntities(r.Context(), q)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode result and send response.
|
||||||
|
|
||||||
|
data, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password := cred.SecretKey
|
||||||
|
econfigData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachDetachPolicyLDAP attaches or detaches policies from an LDAP entity
|
||||||
|
// (user or group).
|
||||||
|
//
|
||||||
|
// POST <admin-prefix>/idp/ldap/policy/{operation}
|
||||||
|
func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "AttachDetachPolicyLDAP")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Check authorization.
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
|
// More than maxConfigSize bytes were available
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure body content type is opaque to ensure that request body has not
|
||||||
|
// been interpreted as form data.
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType != "application/octet-stream" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate operation
|
||||||
|
operation := mux.Vars(r)["operation"]
|
||||||
|
if operation != "attach" && operation != "detach" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isAttach := operation == "attach"
|
||||||
|
|
||||||
|
// Validate API arguments in body.
|
||||||
|
password := cred.SecretKey
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err, logger.Application)
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var par madmin.PolicyAssociationReq
|
||||||
|
err = json.Unmarshal(reqBytes, &par)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := par.IsValid(); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call IAM subsystem
|
||||||
|
updatedAt, addedOrRemoved, err := globalIAMSys.PolicyDBUpdateLDAP(ctx, isAttach, par)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody := madmin.PolicyAssociationResp{
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
if isAttach {
|
||||||
|
respBody.PoliciesAttached = addedOrRemoved
|
||||||
|
} else {
|
||||||
|
respBody.PoliciesDetached = addedOrRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(respBody)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
|
}
|
||||||
+181
-10
@@ -19,14 +19,21 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errRebalanceDecommissionAlreadyRunning = errors.New("Rebalance cannot be started, decommission is aleady in progress")
|
||||||
|
errDecommissionRebalanceAlreadyRunning = errors.New("Decommission cannot be started, rebalance is already in progress")
|
||||||
|
)
|
||||||
|
|
||||||
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StartDecommission")
|
ctx := newContext(r, w, "StartDecommission")
|
||||||
|
|
||||||
@@ -43,23 +50,53 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pools, ok := objectAPI.(*erasureServerPools)
|
z, ok := objectAPI.(*erasureServerPools)
|
||||||
if !ok {
|
if !ok || len(z.serverPools) == 1 {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if z.IsDecommissionRunning() {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errDecommissionAlreadyRunning), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if z.IsRebalanceStarted() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
v := vars["pool"]
|
v := vars["pool"]
|
||||||
|
|
||||||
idx := globalEndpoints.GetPoolIdx(v)
|
pools := strings.Split(v, ",")
|
||||||
if idx == -1 {
|
poolIndices := make([]int, 0, len(pools))
|
||||||
// We didn't find any matching pools, invalid input
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
for _, pool := range pools {
|
||||||
return
|
idx := globalEndpoints.GetPoolIdx(pool)
|
||||||
|
if idx == -1 {
|
||||||
|
// We didn't find any matching pools, invalid input
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pool *erasureSets
|
||||||
|
for pidx := range z.serverPools {
|
||||||
|
if pidx == idx {
|
||||||
|
pool = z.serverPools[idx]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pool == nil {
|
||||||
|
// We didn't find any matching pools, invalid input
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poolIndices = append(poolIndices, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal {
|
if len(poolIndices) > 0 && globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
||||||
|
ep := globalEndpoints[poolIndices[0]].Endpoints[0]
|
||||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
if proxyEp.Endpoint.Host == ep.Host {
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
@@ -69,7 +106,7 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pools.Decommission(r.Context(), idx); err != nil {
|
if err := z.Decommission(r.Context(), poolIndices...); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -200,3 +237,137 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus))
|
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "RebalanceStart")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB rebalance-start admin API is always coordinated from first pool's
|
||||||
|
// first node. The following is required to serialize (the effects of)
|
||||||
|
// concurrent rebalance-start commands.
|
||||||
|
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
|
||||||
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pools, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok || len(pools.serverPools) == 1 {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pools.IsDecommissionRunning() {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errRebalanceDecommissionAlreadyRunning), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pools.IsRebalanceStarted() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketInfos, err := objectAPI.ListBuckets(ctx, BucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets := make([]string, 0, len(bucketInfos))
|
||||||
|
for _, bInfo := range bucketInfos {
|
||||||
|
buckets = append(buckets, bInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var id string
|
||||||
|
if id, err = pools.initRebalanceMeta(ctx, buckets); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebalance routine is run on the first node of any pool participating in rebalance.
|
||||||
|
pools.StartRebalance()
|
||||||
|
|
||||||
|
b, err := json.Marshal(struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}{ID: id})
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, b)
|
||||||
|
// Notify peers to load rebalance.bin and start rebalance routine if they happen to be
|
||||||
|
// participating pool's leader node
|
||||||
|
globalNotificationSys.LoadRebalanceMeta(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "RebalanceStatus")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy rebalance-status to first pool first node, so that users see a
|
||||||
|
// consistent view of rebalance progress even though different rebalancing
|
||||||
|
// pools may temporarily have out of date info on the others.
|
||||||
|
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
|
||||||
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pools, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := rebalanceStatus(ctx, pools)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errRebalanceNotStarted) || errors.Is(err, errConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceNotStarted), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("failed to fetch rebalance status: %w", err))
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(rs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "RebalanceStop")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.RebalanceAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pools, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel any ongoing rebalance operation
|
||||||
|
globalNotificationSys.StopRebalance(r.Context())
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
logger.LogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/mux"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
@@ -114,37 +114,23 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
|||||||
default:
|
default:
|
||||||
err = errSRInvalidRequest(errInvalidArgument)
|
err = errSRInvalidRequest(errInvalidArgument)
|
||||||
case madmin.MakeWithVersioningBktOp:
|
case madmin.MakeWithVersioningBktOp:
|
||||||
_, isLockEnabled := r.Form["lockEnabled"]
|
createdAt, cerr := time.Parse(time.RFC3339Nano, strings.TrimSpace(r.Form.Get("createdAt")))
|
||||||
_, isVersioningEnabled := r.Form["versioningEnabled"]
|
|
||||||
_, isForceCreate := r.Form["forceCreate"]
|
|
||||||
createdAtStr := strings.TrimSpace(r.Form.Get("createdAt"))
|
|
||||||
createdAt, cerr := time.Parse(time.RFC3339Nano, createdAtStr)
|
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
createdAt = timeSentinel
|
createdAt = timeSentinel
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := MakeBucketOptions{
|
opts := MakeBucketOptions{
|
||||||
Location: r.Form.Get("location"),
|
LockEnabled: r.Form.Get("lockEnabled") == "true",
|
||||||
LockEnabled: isLockEnabled,
|
VersioningEnabled: r.Form.Get("versioningEnabled") == "true",
|
||||||
VersioningEnabled: isVersioningEnabled,
|
ForceCreate: r.Form.Get("forceCreate") == "true",
|
||||||
ForceCreate: isForceCreate,
|
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
}
|
}
|
||||||
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
|
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
|
||||||
case madmin.ConfigureReplBktOp:
|
case madmin.ConfigureReplBktOp:
|
||||||
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
|
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
|
||||||
case madmin.DeleteBucketBktOp:
|
case madmin.DeleteBucketBktOp, madmin.ForceDeleteBucketBktOp:
|
||||||
_, noRecreate := r.Form["noRecreate"]
|
|
||||||
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
||||||
Force: false,
|
Force: operation == madmin.ForceDeleteBucketBktOp,
|
||||||
NoRecreate: noRecreate,
|
|
||||||
SRDeleteOp: getSRBucketDeleteOp(true),
|
|
||||||
})
|
|
||||||
case madmin.ForceDeleteBucketBktOp:
|
|
||||||
_, noRecreate := r.Form["noRecreate"]
|
|
||||||
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
|
||||||
Force: true,
|
|
||||||
NoRecreate: noRecreate,
|
|
||||||
SRDeleteOp: getSRBucketDeleteOp(true),
|
SRDeleteOp: getSRBucketDeleteOp(true),
|
||||||
})
|
})
|
||||||
case madmin.PurgeDeletedBucketOp:
|
case madmin.PurgeDeletedBucketOp:
|
||||||
@@ -515,3 +501,45 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SiteReplicationResyncOp - PUT /minio/admin/v3/site-replication/resync/op
|
||||||
|
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SiteReplicationResyncOp")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationResyncAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var peerSite madmin.PeerInfo
|
||||||
|
if err := parseJSONBody(ctx, r.Body, &peerSite, ""); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
op := madmin.SiteResyncOp(vars["operation"])
|
||||||
|
var (
|
||||||
|
status madmin.SRResyncOpStatus
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch op {
|
||||||
|
case madmin.SiteResyncStart:
|
||||||
|
status, err = globalSiteReplicationSys.startResync(ctx, objectAPI, peerSite)
|
||||||
|
case madmin.SiteResyncCancel:
|
||||||
|
status, err = globalSiteReplicationSys.cancelResync(ctx, objectAPI, peerSite)
|
||||||
|
default:
|
||||||
|
err = errSRInvalidRequest(errInvalidArgument)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(status)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, body)
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+471
-113
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
@@ -1136,7 +1136,7 @@ func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
|
|||||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
||||||
|
|
||||||
// 6. Check that service account **can** be created for some other user.
|
// 6. Check that service account **can** be created for some other user.
|
||||||
// This is possible because of the policy enforced in the plugin.
|
// This is possible because the policy enforced in the plugin.
|
||||||
c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1261,6 +1261,52 @@ func (c *check) mustListBuckets(ctx context.Context, client *minio.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *check) mustNotDelete(ctx context.Context, client *minio.Client, bucket string, vid string) {
|
||||||
|
c.Helper()
|
||||||
|
|
||||||
|
err := client.RemoveObject(ctx, bucket, "some-object", minio.RemoveObjectOptions{VersionID: vid})
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("user must not be allowed to delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.RemoveObject(ctx, bucket, "some-object", minio.RemoveObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal("user must be able to create delete marker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustDownload(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
|
c.Helper()
|
||||||
|
rd, err := client.GetObject(ctx, bucket, "some-object", minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("download did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(io.Discard, rd); err != nil {
|
||||||
|
c.Fatalf("download did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustUploadReturnVersions(ctx context.Context, client *minio.Client, bucket string) []string {
|
||||||
|
c.Helper()
|
||||||
|
versions := []string{}
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
ui, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("upload did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
versions = append(versions, ui.VersionID)
|
||||||
|
}
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustUpload(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
|
c.Helper()
|
||||||
|
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("upload did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) {
|
func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
c.Helper()
|
c.Helper()
|
||||||
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
||||||
|
|||||||
+329
-318
File diff suppressed because it is too large
Load Diff
@@ -29,9 +29,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
|
"github.com/minio/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// adminErasureTestBed - encapsulates subsystems that need to be setup for
|
// adminErasureTestBed - encapsulates subsystems that need to be setup for
|
||||||
|
|||||||
+29
-13
@@ -27,7 +27,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -176,11 +176,12 @@ func (ahs *allHealState) getHealLocalDiskEndpoints() Endpoints {
|
|||||||
return endpoints
|
return endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) markDiskForHealing(ep Endpoint) {
|
// Set, in the memory, the state of the disk as currently healing or not
|
||||||
|
func (ahs *allHealState) setDiskHealingStatus(ep Endpoint, healing bool) {
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
defer ahs.Unlock()
|
defer ahs.Unlock()
|
||||||
|
|
||||||
ahs.healLocalDisks[ep] = true
|
ahs.healLocalDisks[ep] = healing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||||
@@ -222,8 +223,8 @@ func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
|||||||
// getHealSequenceByToken - Retrieve a heal sequence by token. The second
|
// getHealSequenceByToken - Retrieve a heal sequence by token. The second
|
||||||
// argument returns if a heal sequence actually exists.
|
// argument returns if a heal sequence actually exists.
|
||||||
func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence, exists bool) {
|
func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence, exists bool) {
|
||||||
ahs.Lock()
|
ahs.RLock()
|
||||||
defer ahs.Unlock()
|
defer ahs.RUnlock()
|
||||||
for _, healSeq := range ahs.healSeqMap {
|
for _, healSeq := range ahs.healSeqMap {
|
||||||
if healSeq.clientToken == token {
|
if healSeq.clientToken == token {
|
||||||
return healSeq, true
|
return healSeq, true
|
||||||
@@ -235,8 +236,8 @@ func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence,
|
|||||||
// getHealSequence - Retrieve a heal sequence by path. The second
|
// getHealSequence - Retrieve a heal sequence by path. The second
|
||||||
// argument returns if a heal sequence actually exists.
|
// argument returns if a heal sequence actually exists.
|
||||||
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
|
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
|
||||||
ahs.Lock()
|
ahs.RLock()
|
||||||
defer ahs.Unlock()
|
defer ahs.RUnlock()
|
||||||
h, exists = ahs.healSeqMap[path]
|
h, exists = ahs.healSeqMap[path]
|
||||||
return h, exists
|
return h, exists
|
||||||
}
|
}
|
||||||
@@ -396,6 +397,7 @@ type healSource struct {
|
|||||||
bucket string
|
bucket string
|
||||||
object string
|
object string
|
||||||
versionID string
|
versionID string
|
||||||
|
noWait bool // a non blocking call, if task queue is full return right away.
|
||||||
opts *madmin.HealOpts // optional heal option overrides default setting
|
opts *madmin.HealOpts // optional heal option overrides default setting
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,9 +407,6 @@ type healSequence struct {
|
|||||||
// bucket, and object on which heal seq. was initiated
|
// bucket, and object on which heal seq. was initiated
|
||||||
bucket, object string
|
bucket, object string
|
||||||
|
|
||||||
// A channel of entities with heal result
|
|
||||||
respCh chan healResult
|
|
||||||
|
|
||||||
// Report healing progress
|
// Report healing progress
|
||||||
reportProgress bool
|
reportProgress bool
|
||||||
|
|
||||||
@@ -470,7 +469,6 @@ func newHealSequence(ctx context.Context, bucket, objPrefix, clientAddr string,
|
|||||||
clientToken := mustGetUUID()
|
clientToken := mustGetUUID()
|
||||||
|
|
||||||
return &healSequence{
|
return &healSequence{
|
||||||
respCh: make(chan healResult),
|
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: objPrefix,
|
object: objPrefix,
|
||||||
reportProgress: true,
|
reportProgress: true,
|
||||||
@@ -699,7 +697,6 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
object: source.object,
|
object: source.object,
|
||||||
versionID: source.versionID,
|
versionID: source.versionID,
|
||||||
opts: h.settings,
|
opts: h.settings,
|
||||||
respCh: h.respCh,
|
|
||||||
}
|
}
|
||||||
if source.opts != nil {
|
if source.opts != nil {
|
||||||
task.opts = *source.opts
|
task.opts = *source.opts
|
||||||
@@ -712,6 +709,24 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
h.lastHealActivity = UTCNow()
|
h.lastHealActivity = UTCNow()
|
||||||
h.mutex.Unlock()
|
h.mutex.Unlock()
|
||||||
|
|
||||||
|
if source.noWait {
|
||||||
|
select {
|
||||||
|
case globalBackgroundHealRoutine.tasks <- task:
|
||||||
|
if serverDebugLog {
|
||||||
|
logger.Info("Task in the queue: %#v", task)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// task queue is full, no more workers, we shall move on and heal later.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Don't wait for result
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// respCh must be set to wait for result.
|
||||||
|
// We make it size 1, so a result can always be written
|
||||||
|
// even if we aren't listening.
|
||||||
|
task.respCh = make(chan healResult, 1)
|
||||||
select {
|
select {
|
||||||
case globalBackgroundHealRoutine.tasks <- task:
|
case globalBackgroundHealRoutine.tasks <- task:
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
@@ -721,8 +736,9 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// task queued, now wait for the response.
|
||||||
select {
|
select {
|
||||||
case res := <-h.respCh:
|
case res := <-task.respCh:
|
||||||
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
|
||||||
|
|||||||
+35
-16
@@ -20,11 +20,11 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/klauspost/compress/gzip"
|
"github.com/klauspost/compress/gzip"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -60,7 +60,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
|
|
||||||
// Info operations
|
// Info operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler)))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/inspect-data").HandlerFunc(httpTraceHdrs(adminAPI.InspectDataHandler)).Queries("volume", "{volume:.*}", "file", "{file:.*}")
|
adminRouter.Methods(http.MethodGet, http.MethodPost).Path(adminVersion + "/inspect-data").HandlerFunc(httpTraceAll(adminAPI.InspectDataHandler))
|
||||||
|
|
||||||
// StorageInfo operations
|
// StorageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
|
||||||
@@ -84,6 +84,11 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
|
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(gz(httpTraceAll(adminAPI.StartDecommission))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(gz(httpTraceAll(adminAPI.StartDecommission))).Queries("pool", "{pool:.*}")
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}")
|
||||||
|
|
||||||
|
// Rebalance operations
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/start").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStart)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/rebalance/status").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStatus)))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/stop").HandlerFunc(gz(httpTraceAll(adminAPI.RebalanceStop)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profiling operations - deprecated API
|
// Profiling operations - deprecated API
|
||||||
@@ -137,12 +142,18 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
|
// STS accounts ops
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.TemporaryAccountInfo))).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// Info policy IAM latest
|
// Info policy IAM latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}")
|
||||||
// List policies latest
|
// List policies latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
|
||||||
|
|
||||||
|
// Builtin IAM policy associations
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/builtin/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListPolicyMappingEntities)))
|
||||||
|
|
||||||
// Remove policy IAM
|
// Remove policy IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
@@ -151,6 +162,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
|
||||||
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
||||||
|
|
||||||
|
// Attach policies to user or group
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/attach").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachPolicyBuiltin)))
|
||||||
|
|
||||||
|
// Detach policies from user or group
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/detach").HandlerFunc(gz(httpTraceHdrs(adminAPI.DetachPolicyBuiltin)))
|
||||||
|
|
||||||
// Remove user IAM
|
// Remove user IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
@@ -179,10 +196,15 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM))
|
||||||
|
|
||||||
// IDentity Provider configuration APIs
|
// IDentity Provider configuration APIs
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddIdentityProviderCfg)))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg))).Queries("type", "{type:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateIdentityProviderCfg)))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListIdentityProviderCfg)))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg)))
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg)))
|
||||||
|
|
||||||
|
// LDAP IAM operations
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListLDAPPolicyMappingEntities)))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachDetachPolicyLDAP)))
|
||||||
// -- END IAM APIs --
|
// -- END IAM APIs --
|
||||||
|
|
||||||
// GetBucketQuotaConfig
|
// GetBucketQuotaConfig
|
||||||
@@ -248,6 +270,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove)))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationResyncOp))).Queries("operation", "{operation:.*}")
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Top locks
|
// Top locks
|
||||||
@@ -274,16 +297,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler)))
|
||||||
|
|
||||||
if !globalIsGateway {
|
// Keep obdinfo for backward compatibility with mc
|
||||||
// Keep obdinfo for backward compatibility with mc
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
// -- Health API --
|
||||||
// -- Health API --
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
|
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.BandwidthMonitorHandler)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If none of the routes match add default error handler routes
|
// If none of the routes match add default error handler routes
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
||||||
var localEndpoints Endpoints
|
|
||||||
addr := globalLocalNodeName
|
addr := globalLocalNodeName
|
||||||
if r != nil {
|
if r != nil {
|
||||||
addr = r.Host
|
addr = r.Host
|
||||||
@@ -52,7 +52,6 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal {
|
||||||
// Only proceed for local endpoints
|
// Only proceed for local endpoints
|
||||||
network[nodeName] = string(madmin.ItemOnline)
|
network[nodeName] = string(madmin.ItemOnline)
|
||||||
localEndpoints = append(localEndpoints, endpoint)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, present := network[nodeName]
|
_, present := network[nodeName]
|
||||||
@@ -120,7 +119,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
config.EnvRootUser: {},
|
config.EnvRootUser: {},
|
||||||
config.EnvRootPassword: {},
|
config.EnvRootPassword: {},
|
||||||
config.EnvMinIOSubnetAPIKey: {},
|
config.EnvMinIOSubnetAPIKey: {},
|
||||||
config.EnvKMSSecretKey: {},
|
kms.EnvKMSSecretKey: {},
|
||||||
}
|
}
|
||||||
for _, v := range os.Environ() {
|
for _, v := range os.Environ() {
|
||||||
if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") {
|
if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") {
|
||||||
@@ -142,11 +141,12 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
objLayer := newObjectLayerFn()
|
objLayer := newObjectLayerFn()
|
||||||
if objLayer != nil && !globalIsGateway {
|
if objLayer != nil {
|
||||||
// only need Disks information in server mode.
|
storageInfo := objLayer.LocalStorageInfo(GlobalContext)
|
||||||
storageInfo, _ := objLayer.LocalStorageInfo(GlobalContext)
|
|
||||||
props.State = string(madmin.ItemOnline)
|
props.State = string(madmin.ItemOnline)
|
||||||
props.Disks = storageInfo.Disks
|
props.Disks = storageInfo.Disks
|
||||||
|
} else {
|
||||||
|
props.State = string(madmin.ItemOffline)
|
||||||
}
|
}
|
||||||
|
|
||||||
return props
|
return props
|
||||||
|
|||||||
+82
-22
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
@@ -132,6 +132,7 @@ const (
|
|||||||
ErrReplicationNeedsVersioningError
|
ErrReplicationNeedsVersioningError
|
||||||
ErrReplicationBucketNeedsVersioningError
|
ErrReplicationBucketNeedsVersioningError
|
||||||
ErrReplicationDenyEditError
|
ErrReplicationDenyEditError
|
||||||
|
ErrRemoteTargetDenyEditError
|
||||||
ErrReplicationNoExistingObjects
|
ErrReplicationNoExistingObjects
|
||||||
ErrObjectRestoreAlreadyInProgress
|
ErrObjectRestoreAlreadyInProgress
|
||||||
ErrNoSuchKey
|
ErrNoSuchKey
|
||||||
@@ -151,7 +152,7 @@ const (
|
|||||||
ErrSignatureVersionNotSupported
|
ErrSignatureVersionNotSupported
|
||||||
ErrBucketNotEmpty
|
ErrBucketNotEmpty
|
||||||
ErrAllAccessDisabled
|
ErrAllAccessDisabled
|
||||||
ErrMalformedPolicy
|
ErrPolicyInvalidVersion
|
||||||
ErrMissingFields
|
ErrMissingFields
|
||||||
ErrMissingCredTag
|
ErrMissingCredTag
|
||||||
ErrCredMalformed
|
ErrCredMalformed
|
||||||
@@ -177,7 +178,6 @@ const (
|
|||||||
ErrBucketAlreadyOwnedByYou
|
ErrBucketAlreadyOwnedByYou
|
||||||
ErrInvalidDuration
|
ErrInvalidDuration
|
||||||
ErrBucketAlreadyExists
|
ErrBucketAlreadyExists
|
||||||
ErrTooManyBuckets
|
|
||||||
ErrMetadataTooLarge
|
ErrMetadataTooLarge
|
||||||
ErrUnsupportedMetadata
|
ErrUnsupportedMetadata
|
||||||
ErrMaximumExpires
|
ErrMaximumExpires
|
||||||
@@ -196,6 +196,8 @@ const (
|
|||||||
ErrBucketTaggingNotFound
|
ErrBucketTaggingNotFound
|
||||||
ErrObjectLockInvalidHeaders
|
ErrObjectLockInvalidHeaders
|
||||||
ErrInvalidTagDirective
|
ErrInvalidTagDirective
|
||||||
|
ErrPolicyAlreadyAttached
|
||||||
|
ErrPolicyNotAttached
|
||||||
// Add new error codes here.
|
// Add new error codes here.
|
||||||
|
|
||||||
// SSE-S3/SSE-KMS related API errors
|
// SSE-S3/SSE-KMS related API errors
|
||||||
@@ -207,6 +209,8 @@ const (
|
|||||||
ErrSSEMultipartEncrypted
|
ErrSSEMultipartEncrypted
|
||||||
ErrSSEEncryptedObject
|
ErrSSEEncryptedObject
|
||||||
ErrInvalidEncryptionParameters
|
ErrInvalidEncryptionParameters
|
||||||
|
ErrInvalidEncryptionParametersSSEC
|
||||||
|
|
||||||
ErrInvalidSSECustomerAlgorithm
|
ErrInvalidSSECustomerAlgorithm
|
||||||
ErrInvalidSSECustomerKey
|
ErrInvalidSSECustomerKey
|
||||||
ErrMissingSSECustomerKey
|
ErrMissingSSECustomerKey
|
||||||
@@ -216,6 +220,7 @@ const (
|
|||||||
ErrIncompatibleEncryptionMethod
|
ErrIncompatibleEncryptionMethod
|
||||||
ErrKMSNotConfigured
|
ErrKMSNotConfigured
|
||||||
ErrKMSKeyNotFoundException
|
ErrKMSKeyNotFoundException
|
||||||
|
ErrKMSDefaultKeyAlreadyConfigured
|
||||||
|
|
||||||
ErrNoAccessKey
|
ErrNoAccessKey
|
||||||
ErrInvalidToken
|
ErrInvalidToken
|
||||||
@@ -266,6 +271,7 @@ const (
|
|||||||
ErrAdminGroupNotEmpty
|
ErrAdminGroupNotEmpty
|
||||||
ErrAdminNoSuchJob
|
ErrAdminNoSuchJob
|
||||||
ErrAdminNoSuchPolicy
|
ErrAdminNoSuchPolicy
|
||||||
|
ErrAdminPolicyChangeAlreadyApplied
|
||||||
ErrAdminInvalidArgument
|
ErrAdminInvalidArgument
|
||||||
ErrAdminInvalidAccessKey
|
ErrAdminInvalidAccessKey
|
||||||
ErrAdminInvalidSecretKey
|
ErrAdminInvalidSecretKey
|
||||||
@@ -277,6 +283,8 @@ const (
|
|||||||
ErrAdminConfigDuplicateKeys
|
ErrAdminConfigDuplicateKeys
|
||||||
ErrAdminConfigInvalidIDPType
|
ErrAdminConfigInvalidIDPType
|
||||||
ErrAdminConfigLDAPValidation
|
ErrAdminConfigLDAPValidation
|
||||||
|
ErrAdminConfigIDPCfgNameAlreadyExists
|
||||||
|
ErrAdminConfigIDPCfgNameDoesNotExist
|
||||||
ErrAdminCredentialsMismatch
|
ErrAdminCredentialsMismatch
|
||||||
ErrInsecureClientRequest
|
ErrInsecureClientRequest
|
||||||
ErrObjectTampered
|
ErrObjectTampered
|
||||||
@@ -291,6 +299,10 @@ const (
|
|||||||
ErrSiteReplicationIAMError
|
ErrSiteReplicationIAMError
|
||||||
ErrSiteReplicationConfigMissing
|
ErrSiteReplicationConfigMissing
|
||||||
|
|
||||||
|
// Pool rebalance errors
|
||||||
|
ErrAdminRebalanceAlreadyStarted
|
||||||
|
ErrAdminRebalanceNotStarted
|
||||||
|
|
||||||
// Bucket Quota error codes
|
// Bucket Quota error codes
|
||||||
ErrAdminBucketQuotaExceeded
|
ErrAdminBucketQuotaExceeded
|
||||||
ErrAdminNoSuchQuotaConfiguration
|
ErrAdminNoSuchQuotaConfiguration
|
||||||
@@ -689,11 +701,6 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrTooManyBuckets: {
|
|
||||||
Code: "TooManyBuckets",
|
|
||||||
Description: "You have attempted to create more buckets than allowed",
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
ErrBucketNotEmpty: {
|
ErrBucketNotEmpty: {
|
||||||
Code: "BucketNotEmpty",
|
Code: "BucketNotEmpty",
|
||||||
Description: "The bucket you tried to delete is not empty",
|
Description: "The bucket you tried to delete is not empty",
|
||||||
@@ -709,9 +716,9 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "All access to this resource has been disabled.",
|
Description: "All access to this resource has been disabled.",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
ErrMalformedPolicy: {
|
ErrPolicyInvalidVersion: {
|
||||||
Code: "MalformedPolicy",
|
Code: "MalformedPolicy",
|
||||||
Description: "Policy has invalid resource.",
|
Description: "The policy must contain a valid version string",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrMissingFields: {
|
ErrMissingFields: {
|
||||||
@@ -914,6 +921,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "No matching ExistingsObjects rule enabled",
|
Description: "No matching ExistingsObjects rule enabled",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrRemoteTargetDenyEditError: {
|
||||||
|
Code: "XMinioAdminRemoteTargetDenyEdit",
|
||||||
|
Description: "Cannot alter remote target endpoint since this server is in a cluster replication setup. use `mc admin replicate update`",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrReplicationDenyEditError: {
|
ErrReplicationDenyEditError: {
|
||||||
Code: "XMinioReplicationDenyEdit",
|
Code: "XMinioReplicationDenyEdit",
|
||||||
Description: "Cannot alter local replication config since this server is in a cluster replication setup",
|
Description: "Cannot alter local replication config since this server is in a cluster replication setup",
|
||||||
@@ -1111,6 +1123,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The encryption parameters are not applicable to this object.",
|
Description: "The encryption parameters are not applicable to this object.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidEncryptionParametersSSEC: {
|
||||||
|
Code: "InvalidRequest",
|
||||||
|
Description: "SSE-C encryption parameters are not supported on replicated bucket.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInvalidSSECustomerAlgorithm: {
|
ErrInvalidSSECustomerAlgorithm: {
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
|
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
|
||||||
@@ -1156,6 +1173,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Invalid keyId",
|
Description: "Invalid keyId",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrKMSDefaultKeyAlreadyConfigured: {
|
||||||
|
Code: "KMS.DefaultKeyAlreadyConfiguredException",
|
||||||
|
Description: "A default encryption already exists and cannot be changed on KMS",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
ErrNoAccessKey: {
|
ErrNoAccessKey: {
|
||||||
Code: "AccessDenied",
|
Code: "AccessDenied",
|
||||||
Description: "No AWSAccessKey was presented",
|
Description: "No AWSAccessKey was presented",
|
||||||
@@ -1245,6 +1267,12 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The canned policy does not exist.",
|
Description: "The canned policy does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrAdminPolicyChangeAlreadyApplied: {
|
||||||
|
Code: "XMinioAdminPolicyChangeAlreadyApplied",
|
||||||
|
Description: "The specified policy change is already in effect.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
ErrAdminInvalidArgument: {
|
ErrAdminInvalidArgument: {
|
||||||
Code: "XMinioAdminInvalidArgument",
|
Code: "XMinioAdminInvalidArgument",
|
||||||
Description: "Invalid arguments specified.",
|
Description: "Invalid arguments specified.",
|
||||||
@@ -1301,6 +1329,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "LDAP Configuration validation failed",
|
Description: "LDAP Configuration validation failed",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminConfigIDPCfgNameAlreadyExists: {
|
||||||
|
Code: "XMinioAdminConfigIDPCfgNameAlreadyExists",
|
||||||
|
Description: "An IDP configuration with the given name aleady exists",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigIDPCfgNameDoesNotExist: {
|
||||||
|
Code: "XMinioAdminConfigIDPCfgNameDoesNotExist",
|
||||||
|
Description: "No such IDP configuration exists",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminConfigNotificationTargetsFailed: {
|
ErrAdminConfigNotificationTargetsFailed: {
|
||||||
Code: "XMinioAdminNotificationTargetsTestFailed",
|
Code: "XMinioAdminNotificationTargetsTestFailed",
|
||||||
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
||||||
@@ -1365,7 +1403,7 @@ var errorCodes = errorCodeMap{
|
|||||||
ErrSiteReplicationPeerResp: {
|
ErrSiteReplicationPeerResp: {
|
||||||
Code: "XMinioSiteReplicationPeerResp",
|
Code: "XMinioSiteReplicationPeerResp",
|
||||||
Description: "Error received when contacting a peer site",
|
Description: "Error received when contacting a peer site",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrSiteReplicationBackendIssue: {
|
ErrSiteReplicationBackendIssue: {
|
||||||
Code: "XMinioSiteReplicationBackendIssue",
|
Code: "XMinioSiteReplicationBackendIssue",
|
||||||
@@ -1397,6 +1435,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Site not found in site replication configuration",
|
Description: "Site not found in site replication configuration",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminRebalanceAlreadyStarted: {
|
||||||
|
Code: "XMinioAdminRebalanceAlreadyStarted",
|
||||||
|
Description: "Pool rebalance is already started",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
|
ErrAdminRebalanceNotStarted: {
|
||||||
|
Code: "XMinioAdminRebalanceNotStarted",
|
||||||
|
Description: "Pool rebalance is not started",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrMaximumExpires: {
|
ErrMaximumExpires: {
|
||||||
Code: "AuthorizationQueryParametersError",
|
Code: "AuthorizationQueryParametersError",
|
||||||
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
|
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
|
||||||
@@ -1912,6 +1960,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Invalid checksum provided.",
|
Description: "Invalid checksum provided.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrPolicyAlreadyAttached: {
|
||||||
|
Code: "XMinioPolicyAlreadyAttached",
|
||||||
|
Description: "The specified policy is already attached.",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
|
ErrPolicyNotAttached: {
|
||||||
|
Code: "XMinioPolicyNotAttached",
|
||||||
|
Description: "The specified policy is not found.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
// Add your error structure here.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1946,6 +2004,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminNoSuchJob
|
apiErr = ErrAdminNoSuchJob
|
||||||
case errNoSuchPolicy:
|
case errNoSuchPolicy:
|
||||||
apiErr = ErrAdminNoSuchPolicy
|
apiErr = ErrAdminNoSuchPolicy
|
||||||
|
case errNoPolicyToAttachOrDetach:
|
||||||
|
apiErr = ErrAdminPolicyChangeAlreadyApplied
|
||||||
case errSignatureMismatch:
|
case errSignatureMismatch:
|
||||||
apiErr = ErrSignatureDoesNotMatch
|
apiErr = ErrSignatureDoesNotMatch
|
||||||
case errInvalidRange:
|
case errInvalidRange:
|
||||||
@@ -1965,6 +2025,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
// SSE errors
|
// SSE errors
|
||||||
case errInvalidEncryptionParameters:
|
case errInvalidEncryptionParameters:
|
||||||
apiErr = ErrInvalidEncryptionParameters
|
apiErr = ErrInvalidEncryptionParameters
|
||||||
|
case errInvalidEncryptionParametersSSEC:
|
||||||
|
apiErr = ErrInvalidEncryptionParametersSSEC
|
||||||
case crypto.ErrInvalidEncryptionMethod:
|
case crypto.ErrInvalidEncryptionMethod:
|
||||||
apiErr = ErrInvalidEncryptionMethod
|
apiErr = ErrInvalidEncryptionMethod
|
||||||
case crypto.ErrInvalidEncryptionKeyID:
|
case crypto.ErrInvalidEncryptionKeyID:
|
||||||
@@ -1991,6 +2053,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrKMSNotConfigured
|
apiErr = ErrKMSNotConfigured
|
||||||
case errKMSKeyNotFound:
|
case errKMSKeyNotFound:
|
||||||
apiErr = ErrKMSKeyNotFoundException
|
apiErr = ErrKMSKeyNotFoundException
|
||||||
|
case errKMSDefaultKeyAlreadyConfigured:
|
||||||
|
apiErr = ErrKMSDefaultKeyAlreadyConfigured
|
||||||
|
|
||||||
case context.Canceled, context.DeadlineExceeded:
|
case context.Canceled, context.DeadlineExceeded:
|
||||||
apiErr = ErrOperationTimedOut
|
apiErr = ErrOperationTimedOut
|
||||||
@@ -2136,7 +2200,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrTransitionStorageClassNotFoundError
|
apiErr = ErrTransitionStorageClassNotFoundError
|
||||||
case InvalidObjectState:
|
case InvalidObjectState:
|
||||||
apiErr = ErrInvalidObjectState
|
apiErr = ErrInvalidObjectState
|
||||||
|
case PreConditionFailed:
|
||||||
|
apiErr = ErrPreconditionFailed
|
||||||
case BucketQuotaExceeded:
|
case BucketQuotaExceeded:
|
||||||
apiErr = ErrAdminBucketQuotaExceeded
|
apiErr = ErrAdminBucketQuotaExceeded
|
||||||
case *event.ErrInvalidEventName:
|
case *event.ErrInvalidEventName:
|
||||||
@@ -2238,15 +2303,10 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
if apiErr.Code == "InternalError" {
|
if apiErr.Code == "InternalError" {
|
||||||
// If we see an internal error try to interpret
|
// If we see an internal error try to interpret
|
||||||
// any underlying errors if possible depending on
|
// any underlying errors if possible depending on
|
||||||
// their internal error types. This code is only
|
// their internal error types.
|
||||||
// useful with gateway implementations.
|
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case batchReplicationJobError:
|
case batchReplicationJobError:
|
||||||
apiErr = APIError{
|
apiErr = APIError(e)
|
||||||
Code: e.Code,
|
|
||||||
Description: e.Description,
|
|
||||||
HTTPStatusCode: e.HTTPStatusCode,
|
|
||||||
}
|
|
||||||
case InvalidArgument:
|
case InvalidArgument:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -2309,7 +2369,7 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
Description: e.Message,
|
Description: e.Message,
|
||||||
HTTPStatusCode: e.StatusCode,
|
HTTPStatusCode: e.StatusCode,
|
||||||
}
|
}
|
||||||
if globalIsGateway && strings.Contains(e.Message, "KMS is not configured") {
|
if strings.Contains(e.Message, "KMS is not configured") {
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "NotImplemented",
|
Code: "NotImplemented",
|
||||||
Description: e.Message,
|
Description: e.Message,
|
||||||
@@ -2333,7 +2393,7 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: e.Response().StatusCode,
|
HTTPStatusCode: e.Response().StatusCode,
|
||||||
}
|
}
|
||||||
// Add more Gateway SDKs here if any in future.
|
// Add more other SDK related errors here if any in future.
|
||||||
default:
|
default:
|
||||||
//nolint:gocritic
|
//nolint:gocritic
|
||||||
if errors.Is(err, errMalformedEncoding) {
|
if errors.Is(err, errMalformedEncoding) {
|
||||||
|
|||||||
+23
-6
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -64,15 +65,31 @@ func setCommonHeaders(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Encodes the response headers into XML format.
|
// Encodes the response headers into XML format.
|
||||||
func encodeResponse(response interface{}) []byte {
|
func encodeResponse(response interface{}) []byte {
|
||||||
var bytesBuffer bytes.Buffer
|
var buf bytes.Buffer
|
||||||
bytesBuffer.WriteString(xxml.Header)
|
buf.WriteString(xml.Header)
|
||||||
buf, err := xxml.Marshal(response)
|
if err := xml.NewEncoder(&buf).Encode(response); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
bytesBuffer.Write(buf)
|
return buf.Bytes()
|
||||||
return bytesBuffer.Bytes()
|
}
|
||||||
|
|
||||||
|
// Use this encodeResponseList() to support control characters
|
||||||
|
// this function must be used by only ListObjects() for objects
|
||||||
|
// with control characters, this is a specialized extension
|
||||||
|
// to support AWS S3 compatible behavior.
|
||||||
|
//
|
||||||
|
// Do not use this function for anything other than ListObjects()
|
||||||
|
// variants, please open a github discussion if you wish to use
|
||||||
|
// this in other places.
|
||||||
|
func encodeResponseList(response interface{}) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(xxml.Header)
|
||||||
|
if err := xxml.NewEncoder(&buf).Encode(response); err != nil {
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes the response headers into JSON format.
|
// Encodes the response headers into JSON format.
|
||||||
|
|||||||
+19
-20
@@ -29,6 +29,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/amztime"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
@@ -38,12 +39,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
|
maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
|
||||||
iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision.
|
maxDeleteList = 1000 // Limit number of objects deleted in a delete call.
|
||||||
maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
|
maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
|
||||||
maxDeleteList = 1000 // Limit number of objects deleted in a delete call.
|
maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
|
||||||
maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
|
|
||||||
maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LocationResponse - format for location response.
|
// LocationResponse - format for location response.
|
||||||
@@ -315,12 +314,12 @@ func (s *Metadata) Set(k, v string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type xmlKeyEntry struct {
|
type xmlKeyEntry struct {
|
||||||
XMLName xml.Name
|
XMLName xxml.Name
|
||||||
Value string `xml:",chardata"`
|
Value string `xml:",chardata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalXML - StringMap marshals into XML.
|
// MarshalXML - StringMap marshals into XML.
|
||||||
func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (s *Metadata) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -335,7 +334,7 @@ func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
|
|
||||||
for _, item := range s.Items {
|
for _, item := range s.Items {
|
||||||
if err := e.Encode(xmlKeyEntry{
|
if err := e.Encode(xmlKeyEntry{
|
||||||
XMLName: xml.Name{Local: item.Key},
|
XMLName: xxml.Name{Local: item.Key},
|
||||||
Value: item.Value,
|
Value: item.Value,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -482,7 +481,7 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
listbuckets = append(listbuckets, Bucket{
|
listbuckets = append(listbuckets, Bucket{
|
||||||
Name: bucket.Name,
|
Name: bucket.Name,
|
||||||
CreationDate: bucket.Created.UTC().Format(iso8601TimeFormat),
|
CreationDate: amztime.ISO8601Format(bucket.Created.UTC()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +507,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
}
|
}
|
||||||
content := ObjectVersion{}
|
content := ObjectVersion{}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
|
||||||
if object.ETag != "" {
|
if object.ETag != "" {
|
||||||
content.ETag = "\"" + object.ETag + "\""
|
content.ETag = "\"" + object.ETag + "\""
|
||||||
}
|
}
|
||||||
@@ -566,7 +565,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
|
||||||
if object.ETag != "" {
|
if object.ETag != "" {
|
||||||
content.ETag = "\"" + object.ETag + "\""
|
content.ETag = "\"" + object.ETag + "\""
|
||||||
}
|
}
|
||||||
@@ -615,7 +614,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
|
||||||
if object.ETag != "" {
|
if object.ETag != "" {
|
||||||
content.ETag = "\"" + object.ETag + "\""
|
content.ETag = "\"" + object.ETag + "\""
|
||||||
}
|
}
|
||||||
@@ -636,7 +635,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
case crypto.SSEC:
|
case crypto.SSEC:
|
||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) {
|
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||||
// Do not need to send any internal metadata
|
// Do not need to send any internal metadata
|
||||||
// values to client.
|
// values to client.
|
||||||
@@ -678,7 +677,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
|
func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
|
||||||
return CopyObjectResponse{
|
return CopyObjectResponse{
|
||||||
ETag: "\"" + etag + "\"",
|
ETag: "\"" + etag + "\"",
|
||||||
LastModified: lastModified.UTC().Format(iso8601TimeFormat),
|
LastModified: amztime.ISO8601Format(lastModified.UTC()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +685,7 @@ func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectR
|
|||||||
func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
|
func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
|
||||||
return CopyObjectPartResponse{
|
return CopyObjectPartResponse{
|
||||||
ETag: "\"" + etag + "\"",
|
ETag: "\"" + etag + "\"",
|
||||||
LastModified: lastModified.UTC().Format(iso8601TimeFormat),
|
LastModified: amztime.ISO8601Format(lastModified.UTC()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,7 +745,7 @@ func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) Lis
|
|||||||
newPart.PartNumber = part.PartNumber
|
newPart.PartNumber = part.PartNumber
|
||||||
newPart.ETag = "\"" + part.ETag + "\""
|
newPart.ETag = "\"" + part.ETag + "\""
|
||||||
newPart.Size = part.Size
|
newPart.Size = part.Size
|
||||||
newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat)
|
newPart.LastModified = amztime.ISO8601Format(part.LastModified.UTC())
|
||||||
newPart.ChecksumCRC32 = part.ChecksumCRC32
|
newPart.ChecksumCRC32 = part.ChecksumCRC32
|
||||||
newPart.ChecksumCRC32C = part.ChecksumCRC32C
|
newPart.ChecksumCRC32C = part.ChecksumCRC32C
|
||||||
newPart.ChecksumSHA1 = part.ChecksumSHA1
|
newPart.ChecksumSHA1 = part.ChecksumSHA1
|
||||||
@@ -780,7 +779,7 @@ func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMult
|
|||||||
newUpload := Upload{}
|
newUpload := Upload{}
|
||||||
newUpload.UploadID = upload.UploadID
|
newUpload.UploadID = upload.UploadID
|
||||||
newUpload.Key = s3EncodeName(upload.Object, encodingType)
|
newUpload.Key = s3EncodeName(upload.Object, encodingType)
|
||||||
newUpload.Initiated = upload.Initiated.UTC().Format(iso8601TimeFormat)
|
newUpload.Initiated = amztime.ISO8601Format(upload.Initiated.UTC())
|
||||||
listMultipartUploadsResponse.Uploads[index] = newUpload
|
listMultipartUploadsResponse.Uploads[index] = newUpload
|
||||||
}
|
}
|
||||||
return listMultipartUploadsResponse
|
return listMultipartUploadsResponse
|
||||||
@@ -876,7 +875,7 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
|||||||
|
|
||||||
// Generate error response.
|
// Generate error response.
|
||||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
||||||
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
|
||||||
encodedErrorResponse := encodeResponse(errorResponse)
|
encodedErrorResponse := encodeResponse(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||||
}
|
}
|
||||||
@@ -894,7 +893,7 @@ func writeErrorResponseString(ctx context.Context, w http.ResponseWriter, err AP
|
|||||||
// useful for admin APIs.
|
// useful for admin APIs.
|
||||||
func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
||||||
// Generate error response.
|
// Generate error response.
|
||||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
|
||||||
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -22,11 +22,11 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/minio/console/restapi"
|
"github.com/minio/console/restapi"
|
||||||
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/pkg/wildcard"
|
"github.com/minio/pkg/wildcard"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|||||||
+233
-224
File diff suppressed because one or more lines are too long
+52
-26
@@ -39,6 +39,7 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
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/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
@@ -134,7 +135,7 @@ func getRequestAuthType(r *http.Request) authType {
|
|||||||
return authTypeUnknown
|
return authTypeUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, map[string]interface{}, bool, APIErrorCode) {
|
func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, bool, APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
s3Err := ErrAccessDenied
|
s3Err := ErrAccessDenied
|
||||||
@@ -143,27 +144,24 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
// We only support admin credentials to access admin APIs.
|
// We only support admin credentials to access admin APIs.
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, nil, owner, s3Err
|
return cred, owner, s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only support V4 (no presign) with auth body
|
// we only support V4 (no presign) with auth body
|
||||||
s3Err = isReqAuthenticated(ctx, r, region, serviceS3)
|
s3Err = isReqAuthenticated(ctx, r, region, serviceS3)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
reqInfo := (&logger.ReqInfo{}).AppendTags("requestHeaders", dumpRequest(r))
|
return cred, owner, s3Err
|
||||||
ctx := logger.SetReqInfo(ctx, reqInfo)
|
|
||||||
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
|
|
||||||
return cred, nil, owner, s3Err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cred, cred.Claims, owner, ErrNone
|
return cred, owner, ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
||||||
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
||||||
// are automatically rejected.
|
// are automatically rejected.
|
||||||
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
||||||
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
|
cred, owner, s3Err := validateAdminSignature(ctx, r, region)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, s3Err
|
||||||
}
|
}
|
||||||
@@ -171,9 +169,9 @@ func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolic
|
|||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: iampolicy.Action(action),
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims,
|
Claims: cred.Claims,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, ErrNone
|
return cred, ErrNone
|
||||||
@@ -302,6 +300,17 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac
|
|||||||
return s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkRequestAuthTypeWithVID is similar to checkRequestAuthType
|
||||||
|
// passes versionID additionally.
|
||||||
|
func checkRequestAuthTypeWithVID(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName, versionID string) (s3Err APIErrorCode) {
|
||||||
|
logger.GetReqInfo(ctx).BucketName = bucketName
|
||||||
|
logger.GetReqInfo(ctx).ObjectName = objectName
|
||||||
|
logger.GetReqInfo(ctx).VersionID = versionID
|
||||||
|
|
||||||
|
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action)
|
||||||
|
return s3Err
|
||||||
|
}
|
||||||
|
|
||||||
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
if logger.GetReqInfo(ctx) == nil {
|
if logger.GetReqInfo(ctx) == nil {
|
||||||
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
||||||
@@ -373,14 +382,16 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
region := reqInfo.Region
|
region := reqInfo.Region
|
||||||
bucket := reqInfo.BucketName
|
bucket := reqInfo.BucketName
|
||||||
object := reqInfo.ObjectName
|
object := reqInfo.ObjectName
|
||||||
|
versionID := reqInfo.VersionID
|
||||||
|
|
||||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||||
// Anonymous checks are not meant for ListAllBuckets action
|
// Anonymous checks are not meant for ListAllBuckets action
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, region, "", nil),
|
ConditionValues: getConditionValues(r, region, auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
@@ -393,9 +404,10 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, region, "", nil),
|
ConditionValues: getConditionValues(r, region, auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
@@ -406,13 +418,27 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
|
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
if action == policy.DeleteObjectAction && versionID != "" {
|
||||||
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: iampolicy.Action(policy.DeleteObjectVersionAction),
|
||||||
|
BucketName: bucket,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
ObjectName: object,
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
DenyOnly: true,
|
||||||
|
}) { // Request is not allowed if Deny action on DeleteObjectVersionAction
|
||||||
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: iampolicy.Action(action),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -429,7 +455,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -544,7 +570,7 @@ func isSupportedS3AuthType(aType authType) bool {
|
|||||||
func setAuthHandler(h http.Handler) http.Handler {
|
func setAuthHandler(h http.Handler) http.Handler {
|
||||||
// handler for validating incoming authorization headers.
|
// handler for validating incoming authorization headers.
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
tc, ok := r.Context().Value(contextTraceReqKey).(*traceCtxt)
|
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
|
||||||
|
|
||||||
aType := getRequestAuthType(r)
|
aType := getRequestAuthType(r)
|
||||||
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
|
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
|
||||||
@@ -552,8 +578,8 @@ func setAuthHandler(h http.Handler) http.Handler {
|
|||||||
amzDate, errCode := parseAmzDateHeader(r)
|
amzDate, errCode := parseAmzDateHeader(r)
|
||||||
if errCode != ErrNone {
|
if errCode != ErrNone {
|
||||||
if ok {
|
if ok {
|
||||||
tc.funcName = "handler.Auth"
|
tc.FuncName = "handler.Auth"
|
||||||
tc.responseRecorder.LogErrBody = true
|
tc.ResponseRecorder.LogErrBody = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// All our internal APIs are sensitive towards Date
|
// All our internal APIs are sensitive towards Date
|
||||||
@@ -568,8 +594,8 @@ func setAuthHandler(h http.Handler) http.Handler {
|
|||||||
curTime := UTCNow()
|
curTime := UTCNow()
|
||||||
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
|
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
|
||||||
if ok {
|
if ok {
|
||||||
tc.funcName = "handler.Auth"
|
tc.FuncName = "handler.Auth"
|
||||||
tc.responseRecorder.LogErrBody = true
|
tc.ResponseRecorder.LogErrBody = true
|
||||||
}
|
}
|
||||||
|
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL)
|
||||||
@@ -583,8 +609,8 @@ func setAuthHandler(h http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
tc.funcName = "handler.Auth"
|
tc.FuncName = "handler.Auth"
|
||||||
tc.responseRecorder.LogErrBody = true
|
tc.ResponseRecorder.LogErrBody = true
|
||||||
}
|
}
|
||||||
|
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL)
|
||||||
@@ -624,9 +650,9 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions := getConditionValues(r, "", cred.AccessKey, cred.Claims)
|
conditions := getConditionValues(r, "", cred)
|
||||||
conditions["object-lock-mode"] = []string{string(retMode)}
|
conditions["object-lock-mode"] = []string{string(retMode)}
|
||||||
conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
|
conditions["object-lock-retain-until-date"] = []string{retDate.UTC().Format(time.RFC3339)}
|
||||||
if retDays > 0 {
|
if retDays > 0 {
|
||||||
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
||||||
}
|
}
|
||||||
@@ -698,7 +724,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.Action(action),
|
Action: policy.Action(action),
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", "", nil),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
}) {
|
}) {
|
||||||
@@ -712,7 +738,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
|
if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
|
||||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||||
}
|
}
|
||||||
_, _, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)
|
_, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)
|
||||||
if s3Error != testCase.ErrCode {
|
if s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error)
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-12
@@ -19,10 +19,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// healTask represents what to heal along with options
|
// healTask represents what to heal along with options
|
||||||
@@ -54,16 +58,46 @@ type healRoutine struct {
|
|||||||
func activeListeners() int {
|
func activeListeners() int {
|
||||||
// Bucket notification and http trace are not costly, it is okay to ignore them
|
// Bucket notification and http trace are not costly, it is okay to ignore them
|
||||||
// while counting the number of concurrent connections
|
// while counting the number of concurrent connections
|
||||||
return int(globalHTTPListen.NumSubscribers(pubsub.MaskAll)) + int(globalTrace.NumSubscribers(pubsub.MaskAll))
|
return int(globalHTTPListen.Subscribers()) + int(globalTrace.Subscribers())
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForLowIO(maxIO int, maxWait time.Duration, currentIO func() int) {
|
||||||
|
// No need to wait run at full speed.
|
||||||
|
if maxIO <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitTick = 100 * time.Millisecond
|
||||||
|
|
||||||
|
tmpMaxWait := maxWait
|
||||||
|
|
||||||
|
for currentIO() >= maxIO {
|
||||||
|
if tmpMaxWait > 0 {
|
||||||
|
if tmpMaxWait < waitTick {
|
||||||
|
time.Sleep(tmpMaxWait)
|
||||||
|
} else {
|
||||||
|
time.Sleep(waitTick)
|
||||||
|
}
|
||||||
|
tmpMaxWait -= waitTick
|
||||||
|
}
|
||||||
|
if tmpMaxWait <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func currentHTTPIO() int {
|
||||||
|
httpServer := newHTTPServerFn()
|
||||||
|
if httpServer == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpServer.GetRequestCount() - activeListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForLowHTTPReq() {
|
func waitForLowHTTPReq() {
|
||||||
var currentIO func() int
|
maxIO, maxWait, _ := globalHealConfig.Clone()
|
||||||
if httpServer := newHTTPServerFn(); httpServer != nil {
|
waitForLowIO(maxIO, maxWait, currentHTTPIO)
|
||||||
currentIO = httpServer.GetRequestCount
|
|
||||||
}
|
|
||||||
|
|
||||||
globalHealConfig.Wait(currentIO, activeListeners)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
||||||
@@ -89,8 +123,7 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
var err error
|
var err error
|
||||||
switch task.bucket {
|
switch task.bucket {
|
||||||
case nopHeal:
|
case nopHeal:
|
||||||
task.respCh <- healResult{err: errSkipFile}
|
err = errSkipFile
|
||||||
continue
|
|
||||||
case SlashSeparator:
|
case SlashSeparator:
|
||||||
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
||||||
default:
|
default:
|
||||||
@@ -101,7 +134,10 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task.respCh <- healResult{result: res, err: err}
|
if task.respCh != nil {
|
||||||
|
task.respCh <- healResult{result: res, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -110,9 +146,19 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
func newHealRoutine() *healRoutine {
|
func newHealRoutine() *healRoutine {
|
||||||
workers := runtime.GOMAXPROCS(0) / 2
|
workers := runtime.GOMAXPROCS(0) / 2
|
||||||
|
|
||||||
|
if envHealWorkers := env.Get("_MINIO_HEAL_WORKERS", ""); envHealWorkers != "" {
|
||||||
|
if numHealers, err := strconv.Atoi(envHealWorkers); err != nil {
|
||||||
|
logger.LogIf(context.Background(), fmt.Errorf("invalid _MINIO_HEAL_WORKERS value: %w", err))
|
||||||
|
} else {
|
||||||
|
workers = numHealers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if workers == 0 {
|
if workers == 0 {
|
||||||
workers = 4
|
workers = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
return &healRoutine{
|
return &healRoutine{
|
||||||
tasks: make(chan healTask),
|
tasks: make(chan healTask),
|
||||||
workers: workers,
|
workers: workers,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
@@ -79,6 +79,10 @@ type healingTracker struct {
|
|||||||
|
|
||||||
// Filled during heal.
|
// Filled during heal.
|
||||||
HealedBuckets []string
|
HealedBuckets []string
|
||||||
|
|
||||||
|
// ID of the current healing operation
|
||||||
|
HealID string
|
||||||
|
|
||||||
// Add future tracking capabilities
|
// Add future tracking capabilities
|
||||||
// Be sure that they are included in toHealingDisk
|
// Be sure that they are included in toHealingDisk
|
||||||
}
|
}
|
||||||
@@ -112,11 +116,12 @@ func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newHealingTracker will create a new healing tracker for the disk.
|
// newHealingTracker will create a new healing tracker for the disk.
|
||||||
func newHealingTracker(disk StorageAPI) *healingTracker {
|
func newHealingTracker(disk StorageAPI, healID string) *healingTracker {
|
||||||
diskID, _ := disk.GetDiskID()
|
diskID, _ := disk.GetDiskID()
|
||||||
h := healingTracker{
|
h := healingTracker{
|
||||||
disk: disk,
|
disk: disk,
|
||||||
ID: diskID,
|
ID: diskID,
|
||||||
|
HealID: healID,
|
||||||
Path: disk.String(),
|
Path: disk.String(),
|
||||||
Endpoint: disk.Endpoint().String(),
|
Endpoint: disk.Endpoint().String(),
|
||||||
Started: time.Now().UTC(),
|
Started: time.Now().UTC(),
|
||||||
@@ -227,6 +232,7 @@ func (h *healingTracker) printTo(writer io.Writer) {
|
|||||||
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
||||||
return madmin.HealingDisk{
|
return madmin.HealingDisk{
|
||||||
ID: h.ID,
|
ID: h.ID,
|
||||||
|
HealID: h.HealID,
|
||||||
Endpoint: h.Endpoint,
|
Endpoint: h.Endpoint,
|
||||||
PoolIndex: h.PoolIndex,
|
PoolIndex: h.PoolIndex,
|
||||||
SetIndex: h.SetIndex,
|
SetIndex: h.SetIndex,
|
||||||
@@ -286,13 +292,11 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
|||||||
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||||
|
|
||||||
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
|
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
|
||||||
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
|
|
||||||
|
|
||||||
disk, format, err := connectEndpoint(endpoint)
|
disk, format, err := connectEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error: %w, %s", err, endpoint)
|
return fmt.Errorf("Error: %w, %s", err, endpoint)
|
||||||
}
|
}
|
||||||
|
defer disk.Close()
|
||||||
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
||||||
if poolIdx < 0 {
|
if poolIdx < 0 {
|
||||||
return fmt.Errorf("unexpected pool index (%d) found in %s", poolIdx, disk.Endpoint())
|
return fmt.Errorf("unexpected pool index (%d) found in %s", poolIdx, disk.Endpoint())
|
||||||
@@ -310,13 +314,27 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent parallel erasure set healing
|
// Prevent parallel erasure set healing
|
||||||
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%s/%d/%d", endpoint, poolIdx, setIdx))
|
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%d/%d", poolIdx, setIdx))
|
||||||
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
|
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer locker.Unlock(lkctx.Cancel)
|
defer locker.Unlock(lkctx)
|
||||||
|
|
||||||
|
// Load healing tracker in this disk
|
||||||
|
tracker, err := loadHealingTracker(ctx, disk)
|
||||||
|
if err != nil {
|
||||||
|
// A healing track can be not found when another disk in the same
|
||||||
|
// erasure set and same healing-id successfully finished healing.
|
||||||
|
if err == errFileNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to load a healing tracker on '%s': %w", disk, err))
|
||||||
|
tracker = newHealingTracker(disk, mustGetUUID())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
|
||||||
|
|
||||||
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
|
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
|
||||||
// Buckets data are dispersed in multiple zones/sets, make
|
// Buckets data are dispersed in multiple zones/sets, make
|
||||||
@@ -340,15 +358,6 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
|
logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load healing tracker in this disk
|
|
||||||
tracker, err := loadHealingTracker(ctx, disk)
|
|
||||||
if err != nil {
|
|
||||||
// So someone changed the drives underneath, healing tracker missing.
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', drive was swapped again on %s pool: %w",
|
|
||||||
disk, humanize.Ordinal(poolIdx+1), err))
|
|
||||||
tracker = newHealingTracker(disk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load bucket totals
|
// Load bucket totals
|
||||||
cache := dataUsageCache{}
|
cache := dataUsageCache{}
|
||||||
if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil {
|
if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil {
|
||||||
@@ -374,12 +383,33 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
logger.Info("Healing drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
logger.Info("Healing drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, 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)
|
||||||
logger.Info("\n")
|
logger.Info("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, tracker.delete(ctx))
|
if tracker.HealID == "" { // HealID is empty only before Feb 2023
|
||||||
|
logger.LogIf(ctx, tracker.delete(ctx))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove .healing.bin from all disks with similar heal-id
|
||||||
|
for _, disk := range z.serverPools[poolIdx].sets[setIdx].getDisks() {
|
||||||
|
t, err := loadHealingTracker(ctx, disk)
|
||||||
|
if err != nil {
|
||||||
|
if err != errFileNotFound {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.HealID == tracker.HealID {
|
||||||
|
t.delete(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -415,9 +445,10 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
|
|||||||
|
|
||||||
for _, disk := range healDisks {
|
for _, disk := range healDisks {
|
||||||
go func(disk Endpoint) {
|
go func(disk Endpoint) {
|
||||||
globalBackgroundHealState.markDiskForHealing(disk)
|
globalBackgroundHealState.setDiskHealingStatus(disk, true)
|
||||||
err := healFreshDisk(ctx, z, disk)
|
err := healFreshDisk(ctx, z, disk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
globalBackgroundHealState.setDiskHealingStatus(disk, false)
|
||||||
printEndpointError(disk, err, false)
|
printEndpointError(disk, err, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "HealID":
|
||||||
|
z.HealID, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealID")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,9 +201,9 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 22
|
// map header, size 23
|
||||||
// write "ID"
|
// write "ID"
|
||||||
err = en.Append(0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
|
err = en.Append(0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -430,15 +436,25 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// write "HealID"
|
||||||
|
err = en.Append(0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.HealID)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealID")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 22
|
// map header, size 23
|
||||||
// string "ID"
|
// string "ID"
|
||||||
o = append(o, 0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
|
o = append(o, 0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
|
||||||
o = msgp.AppendString(o, z.ID)
|
o = msgp.AppendString(o, z.ID)
|
||||||
// string "PoolIndex"
|
// string "PoolIndex"
|
||||||
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
@@ -509,6 +525,9 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
for za0002 := range z.HealedBuckets {
|
for za0002 := range z.HealedBuckets {
|
||||||
o = msgp.AppendString(o, z.HealedBuckets[za0002])
|
o = msgp.AppendString(o, z.HealedBuckets[za0002])
|
||||||
}
|
}
|
||||||
|
// string "HealID"
|
||||||
|
o = append(o, 0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
|
||||||
|
o = msgp.AppendString(o, z.HealID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,6 +707,12 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "HealID":
|
||||||
|
z.HealID, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealID")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -710,5 +735,6 @@ func (z *healingTracker) Msgsize() (s int) {
|
|||||||
for za0002 := range z.HealedBuckets {
|
for za0002 := range z.HealedBuckets {
|
||||||
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
|
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
|
||||||
}
|
}
|
||||||
|
s += 7 + msgp.StringPrefixSize + len(z.HealID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-47
@@ -28,20 +28,24 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/lithammer/shortuuid/v4"
|
"github.com/lithammer/shortuuid/v4"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
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/workers"
|
||||||
"github.com/minio/pkg/console"
|
"github.com/minio/pkg/console"
|
||||||
|
"github.com/minio/pkg/env"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/wildcard"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
@@ -356,6 +360,8 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
|
|||||||
|
|
||||||
// batchJobInfo current batch replication information
|
// batchJobInfo current batch replication information
|
||||||
type batchJobInfo struct {
|
type batchJobInfo struct {
|
||||||
|
mu sync.RWMutex `json:"-" msg:"-"`
|
||||||
|
|
||||||
Version int `json:"-" msg:"v"`
|
Version int `json:"-" msg:"v"`
|
||||||
JobID string `json:"jobID" msg:"jid"`
|
JobID string `json:"jobID" msg:"jid"`
|
||||||
JobType string `json:"jobType" msg:"jt"`
|
JobType string `json:"jobType" msg:"jt"`
|
||||||
@@ -425,6 +431,9 @@ func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobR
|
|||||||
return fmt.Errorf("batchRepl: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
|
return fmt.Errorf("batchRepl: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ri.mu.Lock()
|
||||||
|
defer ri.mu.Unlock()
|
||||||
|
|
||||||
// OK, parse data.
|
// OK, parse data.
|
||||||
if _, err = ri.UnmarshalMsg(data[4:]); err != nil {
|
if _, err = ri.UnmarshalMsg(data[4:]); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -439,8 +448,11 @@ func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobR
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri batchJobInfo) clone() batchJobInfo {
|
func (ri *batchJobInfo) clone() *batchJobInfo {
|
||||||
return batchJobInfo{
|
ri.mu.RLock()
|
||||||
|
defer ri.mu.RUnlock()
|
||||||
|
|
||||||
|
return &batchJobInfo{
|
||||||
Version: ri.Version,
|
Version: ri.Version,
|
||||||
JobID: ri.JobID,
|
JobID: ri.JobID,
|
||||||
JobType: ri.JobType,
|
JobType: ri.JobType,
|
||||||
@@ -458,21 +470,6 @@ func (ri batchJobInfo) clone() batchJobInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ri batchJobInfo) save(ctx context.Context, api ObjectLayer, jobLocation string) error {
|
|
||||||
data := make([]byte, 4, ri.Msgsize()+4)
|
|
||||||
|
|
||||||
// Initialize the header.
|
|
||||||
binary.LittleEndian.PutUint16(data[0:2], batchReplFormat)
|
|
||||||
binary.LittleEndian.PutUint16(data[2:4], batchReplVersion)
|
|
||||||
|
|
||||||
buf, err := ri.MarshalMsg(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return saveConfig(ctx, api, pathJoin(jobLocation, batchReplName), buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) {
|
func (ri *batchJobInfo) countItem(size int64, dmarker, success bool) {
|
||||||
if ri == nil {
|
if ri == nil {
|
||||||
return
|
return
|
||||||
@@ -499,14 +496,28 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
|
|||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
now := UTCNow()
|
now := UTCNow()
|
||||||
|
ri.mu.Lock()
|
||||||
if now.Sub(ri.LastUpdate) >= duration {
|
if now.Sub(ri.LastUpdate) >= duration {
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
console.Debugf("batchReplicate: persisting batchReplication info on drive: threshold:%s, batchRepl:%#v\n", now.Sub(ri.LastUpdate), ri)
|
console.Debugf("batchReplicate: persisting batchReplication info on drive: threshold:%s, batchRepl:%#v\n", now.Sub(ri.LastUpdate), ri)
|
||||||
}
|
}
|
||||||
ri.LastUpdate = now
|
ri.LastUpdate = now
|
||||||
ri.Version = batchReplVersionV1
|
ri.Version = batchReplVersionV1
|
||||||
return ri.save(ctx, api, jobLocation)
|
|
||||||
|
data := make([]byte, 4, ri.Msgsize()+4)
|
||||||
|
|
||||||
|
// Initialize the header.
|
||||||
|
binary.LittleEndian.PutUint16(data[0:2], batchReplFormat)
|
||||||
|
binary.LittleEndian.PutUint16(data[2:4], batchReplVersion)
|
||||||
|
|
||||||
|
buf, err := ri.MarshalMsg(data)
|
||||||
|
ri.mu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return saveConfig(ctx, api, pathJoin(jobLocation, batchReplName), buf)
|
||||||
}
|
}
|
||||||
|
ri.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,6 +525,10 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
|
|||||||
if ri == nil {
|
if ri == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ri.mu.Lock()
|
||||||
|
defer ri.mu.Unlock()
|
||||||
|
|
||||||
ri.Bucket = bucket
|
ri.Bucket = bucket
|
||||||
ri.Object = info.Name
|
ri.Object = info.Name
|
||||||
ri.countItem(info.Size, info.DeleteMarker, failed)
|
ri.countItem(info.Size, info.DeleteMarker, failed)
|
||||||
@@ -529,7 +544,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if err := ri.load(ctx, api, job); err != nil {
|
if err := ri.load(ctx, api, job); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
globalBatchJobsMetrics.save(job.ID, ri.clone())
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
lastObject := ri.Object
|
lastObject := ri.Object
|
||||||
|
|
||||||
delay := job.Replicate.Flags.Retry.Delay
|
delay := job.Replicate.Flags.Retry.Delay
|
||||||
@@ -618,10 +633,24 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
||||||
|
|
||||||
|
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wk, err := workers.New(workerSize)
|
||||||
|
if err != nil {
|
||||||
|
// invalid worker size.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
retryAttempts := ri.RetryAttempts
|
retryAttempts := ri.RetryAttempts
|
||||||
retry := false
|
retry := false
|
||||||
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
|
attempts := attempts
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
results := make(chan ObjectInfo, 100)
|
results := make(chan ObjectInfo, 100)
|
||||||
@@ -635,31 +664,43 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
}
|
}
|
||||||
|
|
||||||
for result := range results {
|
for result := range results {
|
||||||
stopFn := globalBatchJobsMetrics.trace(batchReplicationMetricObject, job.ID, attempts, result)
|
result := result
|
||||||
success := true
|
wk.Take()
|
||||||
if err := r.ReplicateToTarget(ctx, api, c, result, retry); err != nil {
|
go func() {
|
||||||
if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
|
defer wk.Give()
|
||||||
// object must be deleted concurrently, allow
|
|
||||||
// these failures but do not count them
|
stopFn := globalBatchJobsMetrics.trace(batchReplicationMetricObject, job.ID, attempts, result)
|
||||||
continue
|
success := true
|
||||||
|
if err := r.ReplicateToTarget(ctx, api, c, result, retry); err != nil {
|
||||||
|
if miniogo.ToErrorResponse(err).Code == "PreconditionFailed" {
|
||||||
|
// pre-condition failed means we already have the object copied over.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// object must be deleted concurrently, allow these failures but do not count them
|
||||||
|
if isErrVersionNotFound(err) || isErrObjectNotFound(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stopFn(err)
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
success = false
|
||||||
|
} else {
|
||||||
|
stopFn(nil)
|
||||||
}
|
}
|
||||||
stopFn(err)
|
ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
|
||||||
logger.LogIf(ctx, err)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
success = false
|
// persist in-memory state to disk after every 10secs.
|
||||||
} else {
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job.Location))
|
||||||
stopFn(nil)
|
}()
|
||||||
}
|
|
||||||
ri.trackCurrentBucketObject(r.Source.Bucket, result, success)
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri.clone())
|
|
||||||
// persist in-memory state to disk after every 10secs.
|
|
||||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job.Location))
|
|
||||||
}
|
}
|
||||||
|
wk.Wait()
|
||||||
|
|
||||||
ri.RetryAttempts = attempts
|
ri.RetryAttempts = attempts
|
||||||
ri.Complete = ri.ObjectsFailed == 0
|
ri.Complete = ri.ObjectsFailed == 0
|
||||||
ri.Failed = ri.ObjectsFailed > 0
|
ri.Failed = ri.ObjectsFailed > 0
|
||||||
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri.clone())
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
// persist in-memory state to disk.
|
||||||
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job.Location))
|
||||||
|
|
||||||
buf, _ := json.Marshal(ri)
|
buf, _ := json.Marshal(ri)
|
||||||
if err := r.Notify(ctx, bytes.NewReader(buf)); err != nil {
|
if err := r.Notify(ctx, bytes.NewReader(buf)); err != nil {
|
||||||
@@ -697,7 +738,7 @@ func (e batchReplicationJobError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the job definition input
|
// Validate validates the job definition input
|
||||||
func (r *BatchJobReplicateV1) Validate(ctx context.Context, o ObjectLayer) error {
|
func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest, o ObjectLayer) error {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -773,6 +814,7 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, o ObjectLayer) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
|
||||||
|
|
||||||
vcfg, err := c.GetBucketVersioning(ctx, r.Target.Bucket)
|
vcfg, err := c.GetBucketVersioning(ctx, r.Target.Bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -811,7 +853,7 @@ func (j BatchJobRequest) Type() madmin.BatchJobType {
|
|||||||
// persisting the job request
|
// persisting the job request
|
||||||
func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error {
|
func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error {
|
||||||
if j.Replicate != nil {
|
if j.Replicate != nil {
|
||||||
return j.Replicate.Validate(ctx, o)
|
return j.Replicate.Validate(ctx, j, o)
|
||||||
}
|
}
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -1029,7 +1071,6 @@ type BatchJobPool struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
jobCh chan *BatchJobRequest
|
jobCh chan *BatchJobRequest
|
||||||
workerKillCh chan struct{}
|
workerKillCh chan struct{}
|
||||||
workerWg sync.WaitGroup
|
|
||||||
workerSize int
|
workerSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,7 +1115,6 @@ func (j *BatchJobPool) AddWorker() {
|
|||||||
if j == nil {
|
if j == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer j.workerWg.Done()
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-j.ctx.Done():
|
case <-j.ctx.Done():
|
||||||
@@ -1110,7 +1150,6 @@ func (j *BatchJobPool) ResizeWorkers(n int) {
|
|||||||
|
|
||||||
for j.workerSize < n {
|
for j.workerSize < n {
|
||||||
j.workerSize++
|
j.workerSize++
|
||||||
j.workerWg.Add(1)
|
|
||||||
go j.AddWorker()
|
go j.AddWorker()
|
||||||
}
|
}
|
||||||
for j.workerSize > n {
|
for j.workerSize > n {
|
||||||
@@ -1138,11 +1177,11 @@ func (j *BatchJobPool) queueJob(req *BatchJobRequest) error {
|
|||||||
//msgp:ignore batchJobMetrics
|
//msgp:ignore batchJobMetrics
|
||||||
type batchJobMetrics struct {
|
type batchJobMetrics struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
metrics map[string]batchJobInfo
|
metrics map[string]*batchJobInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalBatchJobsMetrics = batchJobMetrics{
|
var globalBatchJobsMetrics = batchJobMetrics{
|
||||||
metrics: make(map[string]batchJobInfo),
|
metrics: make(map[string]*batchJobInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
//msgp:ignore batchReplicationMetric
|
//msgp:ignore batchReplicationMetric
|
||||||
@@ -1210,11 +1249,11 @@ func (m *batchJobMetrics) delete(jobID string) {
|
|||||||
delete(m.metrics, jobID)
|
delete(m.metrics, jobID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *batchJobMetrics) save(jobID string, ri batchJobInfo) {
|
func (m *batchJobMetrics) save(jobID string, ri *batchJobInfo) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
m.metrics[jobID] = ri
|
m.metrics[jobID] = ri.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *batchJobMetrics) trace(d batchReplicationMetric, job string, attempts int, info ObjectInfo) func(err error) {
|
func (m *batchJobMetrics) trace(d batchReplicationMetric, job string, attempts int, info ObjectInfo) func(err error) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
|
|||||||
// obtains random bucket name.
|
// obtains random bucket name.
|
||||||
bucket := getRandomBucketName()
|
bucket := getRandomBucketName()
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
|
|||||||
object := getRandomObjectName()
|
object := getRandomObjectName()
|
||||||
|
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
|
|||||||
// obtains random bucket name.
|
// obtains random bucket name.
|
||||||
bucket := getRandomBucketName()
|
bucket := getRandomBucketName()
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -25,14 +25,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
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/rest"
|
"github.com/minio/minio/internal/rest"
|
||||||
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,23 +52,22 @@ type bootstrapRESTServer struct{}
|
|||||||
|
|
||||||
// ServerSystemConfig - captures information about server configuration.
|
// ServerSystemConfig - captures information about server configuration.
|
||||||
type ServerSystemConfig struct {
|
type ServerSystemConfig struct {
|
||||||
MinioPlatform string
|
|
||||||
MinioEndpoints EndpointServerPools
|
MinioEndpoints EndpointServerPools
|
||||||
MinioEnv map[string]string
|
MinioEnv map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff - returns error on first difference found in two configs.
|
// Diff - returns error on first difference found in two configs.
|
||||||
func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
|
func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
|
||||||
if s1.MinioPlatform != s2.MinioPlatform {
|
|
||||||
return fmt.Errorf("Expected platform '%s', found to be running '%s'",
|
|
||||||
s1.MinioPlatform, s2.MinioPlatform)
|
|
||||||
}
|
|
||||||
if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
|
if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
|
||||||
return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
|
return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
|
||||||
s2.MinioEndpoints.NEndpoints())
|
s2.MinioEndpoints.NEndpoints())
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, ep := range s1.MinioEndpoints {
|
for i, ep := range s1.MinioEndpoints {
|
||||||
|
if ep.CmdLine != s2.MinioEndpoints[i].CmdLine {
|
||||||
|
return fmt.Errorf("Expected command line argument %s, seen %s", ep.CmdLine,
|
||||||
|
s2.MinioEndpoints[i].CmdLine)
|
||||||
|
}
|
||||||
if ep.SetCount != s2.MinioEndpoints[i].SetCount {
|
if ep.SetCount != s2.MinioEndpoints[i].SetCount {
|
||||||
return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
|
return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
|
||||||
s2.MinioEndpoints[i].SetCount)
|
s2.MinioEndpoints[i].SetCount)
|
||||||
@@ -78,11 +76,9 @@ func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
|
|||||||
return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
|
return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
|
||||||
s2.MinioEndpoints[i].DrivesPerSet)
|
s2.MinioEndpoints[i].DrivesPerSet)
|
||||||
}
|
}
|
||||||
for j, endpoint := range ep.Endpoints {
|
if ep.Platform != s2.MinioEndpoints[i].Platform {
|
||||||
if endpoint.String() != s2.MinioEndpoints[i].Endpoints[j].String() {
|
return fmt.Errorf("Expected platform '%s', found to be on '%s'",
|
||||||
return fmt.Errorf("Expected endpoint %s, seen %s", endpoint,
|
ep.Platform, s2.MinioEndpoints[i].Platform)
|
||||||
s2.MinioEndpoints[i].Endpoints[j])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
|
if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
|
||||||
@@ -125,7 +121,6 @@ func getServerSystemCfg() ServerSystemConfig {
|
|||||||
envValues[envK] = env.Get(envK, "")
|
envValues[envK] = env.Get(envK, "")
|
||||||
}
|
}
|
||||||
return ServerSystemConfig{
|
return ServerSystemConfig{
|
||||||
MinioPlatform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
|
||||||
MinioEndpoints: globalEndpoints,
|
MinioEndpoints: globalEndpoints,
|
||||||
MinioEnv: envValues,
|
MinioEnv: envValues,
|
||||||
}
|
}
|
||||||
@@ -200,15 +195,18 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
|||||||
srcCfg := getServerSystemCfg()
|
srcCfg := getServerSystemCfg()
|
||||||
clnts := newBootstrapRESTClients(endpointServerPools)
|
clnts := newBootstrapRESTClients(endpointServerPools)
|
||||||
var onlineServers int
|
var onlineServers int
|
||||||
var offlineEndpoints []string
|
var offlineEndpoints []error
|
||||||
|
var incorrectConfigs []error
|
||||||
var retries int
|
var retries int
|
||||||
for onlineServers < len(clnts)/2 {
|
for onlineServers < len(clnts)/2 {
|
||||||
for _, clnt := range clnts {
|
for _, clnt := range clnts {
|
||||||
if err := clnt.Verify(ctx, srcCfg); err != nil {
|
if err := clnt.Verify(ctx, srcCfg); err != nil {
|
||||||
if !isNetworkError(err) {
|
if !isNetworkError(err) {
|
||||||
logger.LogIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err))
|
logger.LogOnceIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err), clnt.String())
|
||||||
|
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err))
|
||||||
|
} else {
|
||||||
|
offlineEndpoints = append(offlineEndpoints, fmt.Errorf("%s is unreachable: %w", clnt.String(), err))
|
||||||
}
|
}
|
||||||
offlineEndpoints = append(offlineEndpoints, clnt.String())
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
onlineServers++
|
onlineServers++
|
||||||
@@ -221,15 +219,19 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
|||||||
// 100% CPU when half the endpoints are offline.
|
// 100% CPU when half the endpoints are offline.
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
retries++
|
retries++
|
||||||
// after 5 retries start logging that servers are not reachable yet
|
// after 20 retries start logging that servers are not reachable yet
|
||||||
if retries >= 5 {
|
if retries >= 20 {
|
||||||
logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers to be online for bootstrap check", len(clnts)/2))
|
logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers with valid configuration to be online", len(clnts)/2))
|
||||||
if len(offlineEndpoints) > 0 {
|
if len(offlineEndpoints) > 0 {
|
||||||
logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
|
logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
|
||||||
}
|
}
|
||||||
|
if len(incorrectConfigs) > 0 {
|
||||||
|
logger.Info(fmt.Sprintf("Following servers have mismatching configuration %s", incorrectConfigs))
|
||||||
|
}
|
||||||
retries = 0 // reset to log again after 5 retries.
|
retries = 0 // reset to log again after 5 retries.
|
||||||
}
|
}
|
||||||
offlineEndpoints = nil
|
offlineEndpoints = nil
|
||||||
|
incorrectConfigs = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/kes"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"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/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,11 +51,6 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !objAPI.IsEncryptionSupported() {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
@@ -119,15 +114,12 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||||
// errors.
|
// errors.
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
SSEConfig: &cfgStr,
|
SSEConfig: &cfgStr,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
@@ -209,16 +201,14 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
//
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
SSEConfig: nil,
|
SSEConfig: nil,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,15 +34,6 @@ func NewBucketSSEConfigSys() *BucketSSEConfigSys {
|
|||||||
|
|
||||||
// Get - gets bucket encryption config for the given bucket.
|
// Get - gets bucket encryption config for the given bucket.
|
||||||
func (sys *BucketSSEConfigSys) Get(bucket string) (*sse.BucketSSEConfig, error) {
|
func (sys *BucketSSEConfigSys) Get(bucket string) (*sse.BucketSSEConfig, error) {
|
||||||
if globalIsGateway {
|
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI == nil {
|
|
||||||
return nil, errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, BucketSSEConfigNotFound{Bucket: bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
sseCfg, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
sseCfg, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
||||||
return sseCfg, err
|
return sseCfg, err
|
||||||
}
|
}
|
||||||
|
|||||||
+87
-99
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -34,11 +34,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/mux"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
sse "github.com/minio/minio/internal/bucket/encryption"
|
sse "github.com/minio/minio/internal/bucket/encryption"
|
||||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
@@ -359,7 +360,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: bucketInfo.Name,
|
BucketName: bucketInfo.Name,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: "",
|
ObjectName: "",
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -371,7 +372,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.GetBucketLocationAction,
|
Action: iampolicy.GetBucketLocationAction,
|
||||||
BucketName: bucketInfo.Name,
|
BucketName: bucketInfo.Name,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: "",
|
ObjectName: "",
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -431,7 +432,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
// Unmarshal list of keys to be deleted.
|
// Unmarshal list of keys to be deleted.
|
||||||
deleteObjectsReq := &DeleteObjectsRequest{}
|
deleteObjectsReq := &DeleteObjectsRequest{}
|
||||||
if err := xmlDecoder(r.Body, deleteObjectsReq, maxBodySize); err != nil {
|
if err := xmlDecoder(r.Body, deleteObjectsReq, maxBodySize); err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -495,7 +495,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
vc, _ := globalBucketVersioningSys.Get(bucket)
|
vc, _ := globalBucketVersioningSys.Get(bucket)
|
||||||
oss := make([]*objSweeper, len(deleteObjectsReq.Objects))
|
oss := make([]*objSweeper, len(deleteObjectsReq.Objects))
|
||||||
for index, object := range deleteObjectsReq.Objects {
|
for index, object := range deleteObjectsReq.Objects {
|
||||||
if apiErrCode := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName); apiErrCode != ErrNone {
|
if apiErrCode := checkRequestAuthTypeWithVID(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName, object.VersionID); apiErrCode != ErrNone {
|
||||||
if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID {
|
if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL)
|
||||||
return
|
return
|
||||||
@@ -511,11 +511,10 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
if object.VersionID != "" && object.VersionID != nullVersionID {
|
if object.VersionID != "" && object.VersionID != nullVersionID {
|
||||||
if _, err := uuid.Parse(object.VersionID); err != nil {
|
if _, err := uuid.Parse(object.VersionID); err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("invalid version-id specified %w", err))
|
|
||||||
apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion)
|
apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion)
|
||||||
deleteResults[index].errInfo = DeleteError{
|
deleteResults[index].errInfo = DeleteError{
|
||||||
Code: apiErr.Code,
|
Code: apiErr.Code,
|
||||||
Message: apiErr.Description,
|
Message: fmt.Sprintf("%s (%s)", apiErr.Description, err),
|
||||||
Key: object.ObjectName,
|
Key: object.ObjectName,
|
||||||
VersionID: object.VersionID,
|
VersionID: object.VersionID,
|
||||||
}
|
}
|
||||||
@@ -744,7 +743,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -769,16 +768,12 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if client is attempting to create more buckets than allowed maximum.
|
// check if client is attempting to create more buckets, complain about it.
|
||||||
if currBuckets := globalBucketMetadataSys.Count(); currBuckets+1 > maxBuckets {
|
if currBuckets := globalBucketMetadataSys.Count(); currBuckets+1 > maxBuckets {
|
||||||
apiErr := errorCodes.ToAPIErr(ErrTooManyBuckets)
|
logger.LogIf(ctx, fmt.Errorf("An attempt to create %d buckets beyond recommended %d", currBuckets+1, maxBuckets))
|
||||||
apiErr.Description = fmt.Sprintf("You have attempted to create %d buckets than allowed %d", currBuckets+1, maxBuckets)
|
|
||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := MakeBucketOptions{
|
opts := MakeBucketOptions{
|
||||||
Location: location,
|
|
||||||
LockEnabled: objectLockEnabled,
|
LockEnabled: objectLockEnabled,
|
||||||
ForceCreate: forceCreate,
|
ForceCreate: forceCreate,
|
||||||
}
|
}
|
||||||
@@ -790,15 +785,14 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
// exists elsewhere
|
// exists elsewhere
|
||||||
if err == dns.ErrNoEntriesFound || err == dns.ErrNotImplemented {
|
if err == dns.ErrNoEntriesFound || err == dns.ErrNotImplemented {
|
||||||
// Proceed to creating a bucket.
|
// Proceed to creating a bucket.
|
||||||
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts); err != nil {
|
if err = objectAPI.MakeBucket(ctx, bucket, opts); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalDNSConfig.Put(bucket); err != nil {
|
if err = globalDNSConfig.Put(bucket); err != nil {
|
||||||
objectAPI.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{
|
objectAPI.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{
|
||||||
Force: false,
|
Force: true,
|
||||||
NoRecreate: true,
|
|
||||||
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
|
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
|
||||||
})
|
})
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -842,7 +836,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Proceed to creating a bucket.
|
// Proceed to creating a bucket.
|
||||||
if err := objectAPI.MakeBucketWithLocation(ctx, bucket, opts); err != nil {
|
if err := objectAPI.MakeBucket(ctx, bucket, opts); err != nil {
|
||||||
if _, ok := err.(BucketExists); ok {
|
if _, ok := err.(BucketExists); ok {
|
||||||
// Though bucket exists locally, we send the site-replication
|
// Though bucket exists locally, we send the site-replication
|
||||||
// hook to ensure all sites have this bucket. If the hook
|
// hook to ensure all sites have this bucket. If the hook
|
||||||
@@ -858,10 +852,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
globalNotificationSys.LoadBucketMetadata(GlobalContext, bucket)
|
globalNotificationSys.LoadBucketMetadata(GlobalContext, bucket)
|
||||||
|
|
||||||
// Call site replication hook
|
// Call site replication hook
|
||||||
if err := globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts); err != nil {
|
logger.LogIf(ctx, globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure to add Location information here only for bucket
|
// Make sure to add Location information here only for bucket
|
||||||
if cp := pathClean(r.URL.Path); cp != "" {
|
if cp := pathClean(r.URL.Path); cp != "" {
|
||||||
@@ -900,11 +891,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket := mux.Vars(r)["bucket"]
|
bucket := mux.Vars(r)["bucket"]
|
||||||
|
|
||||||
// Require Content-Length to be set in the request
|
// Require Content-Length to be set in the request
|
||||||
@@ -930,16 +916,18 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
// be loaded in memory, the remaining being put in temporary files.
|
// be loaded in memory, the remaining being put in temporary files.
|
||||||
reader, err := r.MultipartReader()
|
reader, err := r.MultipartReader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||||
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read multipart data and save in memory and in the disk if needed
|
// Read multipart data and save in memory and in the disk if needed
|
||||||
form, err := reader.ReadForm(maxFormMemory)
|
form, err := reader.ReadForm(maxFormMemory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||||
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,8 +937,9 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
// Extract all form fields
|
// Extract all form fields
|
||||||
fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form)
|
fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||||
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -995,7 +984,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectAction,
|
Action: iampolicy.PutObjectAction,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
|
IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
|
||||||
@@ -1065,61 +1054,64 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
sseConfig, _ := globalBucketSSEConfigSys.Get(bucket)
|
sseConfig, _ := globalBucketSSEConfigSys.Get(bucket)
|
||||||
sseConfig.Apply(r.Header, sse.ApplyOptions{
|
sseConfig.Apply(r.Header, sse.ApplyOptions{
|
||||||
AutoEncrypt: globalAutoEncryption,
|
AutoEncrypt: globalAutoEncryption,
|
||||||
Passthrough: globalIsGateway && globalGatewayName == S3BackendGateway,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// get gateway encryption options
|
|
||||||
var opts ObjectOptions
|
var opts ObjectOptions
|
||||||
opts, err = putOpts(ctx, r, bucket, object, metadata)
|
opts, err = putOpts(ctx, r, bucket, object, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if objectAPI.IsEncryptionSupported() {
|
|
||||||
if crypto.Requested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
if crypto.Requested(formValues) {
|
||||||
if crypto.SSECopy.IsRequested(r.Header) {
|
if crypto.SSECopy.IsRequested(r.Header) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
|
||||||
reader io.Reader
|
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||||
keyID string
|
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||||
key []byte
|
return
|
||||||
kmsCtx kms.Context
|
}
|
||||||
)
|
|
||||||
kind, _ := crypto.IsRequested(formValues)
|
var (
|
||||||
switch kind {
|
reader io.Reader
|
||||||
case crypto.SSEC:
|
keyID string
|
||||||
key, err = ParseSSECustomerHeader(formValues)
|
key []byte
|
||||||
if err != nil {
|
kmsCtx kms.Context
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
)
|
||||||
return
|
kind, _ := crypto.IsRequested(formValues)
|
||||||
}
|
switch kind {
|
||||||
case crypto.S3KMS:
|
case crypto.SSEC:
|
||||||
keyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(formValues)
|
key, err = ParseSSECustomerHeader(formValues)
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info := ObjectInfo{Size: fileSize}
|
case crypto.S3KMS:
|
||||||
// do not try to verify encrypted content
|
keyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(formValues)
|
||||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := ObjectInfo{Size: fileSize}
|
||||||
|
// do not try to verify encrypted content
|
||||||
|
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
||||||
@@ -1150,6 +1142,17 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
UserAgent: r.UserAgent(),
|
UserAgent: r.UserAgent(),
|
||||||
Host: handlers.GetSourceIP(r),
|
Host: handlers.GetSourceIP(r),
|
||||||
})
|
})
|
||||||
|
if objInfo.NumVersions > dataScannerExcessiveVersionsThreshold {
|
||||||
|
defer sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectManyVersions,
|
||||||
|
BucketName: objInfo.Bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
ReqParams: extractReqParams(r),
|
||||||
|
RespElements: extractRespElements(w),
|
||||||
|
UserAgent: r.UserAgent(),
|
||||||
|
Host: handlers.GetSourceIP(r),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if redirectURL != nil { // success_action_redirect is valid and set.
|
if redirectURL != nil { // success_action_redirect is valid and set.
|
||||||
v := redirectURL.Query()
|
v := redirectURL.Query()
|
||||||
@@ -1209,7 +1212,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
|
|||||||
readable := globalPolicySys.IsAllowed(policy.Args{
|
readable := globalPolicySys.IsAllowed(policy.Args{
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", "", nil),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1217,7 +1220,7 @@ func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter,
|
|||||||
writable := globalPolicySys.IsAllowed(policy.Args{
|
writable := globalPolicySys.IsAllowed(policy.Args{
|
||||||
Action: policy.PutObjectAction,
|
Action: policy.PutObjectAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", "", nil),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1361,11 +1364,9 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
globalNotificationSys.DeleteBucketMetadata(ctx, bucket)
|
globalNotificationSys.DeleteBucketMetadata(ctx, bucket)
|
||||||
globalReplicationPool.deleteResyncMetadata(ctx, bucket)
|
globalReplicationPool.deleteResyncMetadata(ctx, bucket)
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
if err := globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete); err != nil {
|
logger.LogIf(ctx, globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
@@ -1398,10 +1399,6 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketObjectLockConfigurationAction, bucket, ""); s3Error != ErrNone {
|
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketObjectLockConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1438,15 +1435,12 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||||
// errors.
|
// errors.
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
ObjectLockConfig: &cfgStr,
|
ObjectLockConfig: &cfgStr,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
@@ -1545,15 +1539,12 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||||
// errors.
|
// errors.
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
Type: madmin.SRBucketMetaTypeTags,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Tags: &cfgStr,
|
Tags: &cfgStr,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
@@ -1624,14 +1615,11 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
Type: madmin.SRBucketMetaTypeTags,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
|||||||
+24
-11
@@ -662,6 +662,9 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
var objectNames []string
|
var objectNames []string
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
objectName := "test-object-" + strconv.Itoa(i)
|
objectName := "test-object-" + strconv.Itoa(i)
|
||||||
|
if i == 0 {
|
||||||
|
objectName += "/"
|
||||||
|
}
|
||||||
// uploading the object.
|
// uploading the object.
|
||||||
_, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
|
_, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
|
||||||
// if object upload fails stop the test.
|
// if object upload fails stop the test.
|
||||||
@@ -793,9 +796,9 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
expectedContent []byte
|
expectedContent []byte
|
||||||
expectedRespStatus int
|
expectedRespStatus int
|
||||||
}{
|
}{
|
||||||
// Test case - 1.
|
// Test case - 0.
|
||||||
// Delete objects with invalid access key.
|
// Delete objects with invalid access key.
|
||||||
{
|
0: {
|
||||||
bucket: bucketName,
|
bucket: bucketName,
|
||||||
objects: successRequest0,
|
objects: successRequest0,
|
||||||
accessKey: "Invalid-AccessID",
|
accessKey: "Invalid-AccessID",
|
||||||
@@ -803,9 +806,19 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
expectedContent: nil,
|
expectedContent: nil,
|
||||||
expectedRespStatus: http.StatusForbidden,
|
expectedRespStatus: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
// Test case - 2.
|
// Test case - 1.
|
||||||
// Delete valid objects with quiet flag off.
|
// Delete valid objects with quiet flag off.
|
||||||
{
|
1: {
|
||||||
|
bucket: bucketName,
|
||||||
|
objects: successRequest0,
|
||||||
|
accessKey: credentials.AccessKey,
|
||||||
|
secretKey: credentials.SecretKey,
|
||||||
|
expectedContent: encodedSuccessResponse0,
|
||||||
|
expectedRespStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
// Test case - 2.
|
||||||
|
// Delete deleted objects with quiet flag off.
|
||||||
|
2: {
|
||||||
bucket: bucketName,
|
bucket: bucketName,
|
||||||
objects: successRequest0,
|
objects: successRequest0,
|
||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
@@ -815,7 +828,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
},
|
},
|
||||||
// Test case - 3.
|
// Test case - 3.
|
||||||
// Delete valid objects with quiet flag on.
|
// Delete valid objects with quiet flag on.
|
||||||
{
|
3: {
|
||||||
bucket: bucketName,
|
bucket: bucketName,
|
||||||
objects: successRequest1,
|
objects: successRequest1,
|
||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
@@ -825,7 +838,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
},
|
},
|
||||||
// Test case - 4.
|
// Test case - 4.
|
||||||
// Delete previously deleted objects.
|
// Delete previously deleted objects.
|
||||||
{
|
4: {
|
||||||
bucket: bucketName,
|
bucket: bucketName,
|
||||||
objects: successRequest1,
|
objects: successRequest1,
|
||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
@@ -836,7 +849,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
// Test case - 5.
|
// Test case - 5.
|
||||||
// Anonymous user access denied response
|
// Anonymous user access denied response
|
||||||
// Currently anonymous users cannot delete multiple objects in MinIO server
|
// Currently anonymous users cannot delete multiple objects in MinIO server
|
||||||
{
|
5: {
|
||||||
bucket: bucketName,
|
bucket: bucketName,
|
||||||
objects: anonRequest,
|
objects: anonRequest,
|
||||||
accessKey: "",
|
accessKey: "",
|
||||||
@@ -847,7 +860,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
// Test case - 6.
|
// Test case - 6.
|
||||||
// Anonymous user has access to some public folder, issue removing with
|
// Anonymous user has access to some public folder, issue removing with
|
||||||
// another private object as well
|
// another private object as well
|
||||||
{
|
6: {
|
||||||
bucket: bucketName,
|
bucket: bucketName,
|
||||||
objects: anonRequestWithPartialPublicAccess,
|
objects: anonRequestWithPartialPublicAccess,
|
||||||
accessKey: "",
|
accessKey: "",
|
||||||
@@ -881,19 +894,19 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
|||||||
apiRouter.ServeHTTP(rec, req)
|
apiRouter.ServeHTTP(rec, req)
|
||||||
// Assert the response code with the expected status.
|
// Assert the response code with the expected status.
|
||||||
if rec.Code != testCase.expectedRespStatus {
|
if rec.Code != testCase.expectedRespStatus {
|
||||||
t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
|
t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i, instanceType, testCase.expectedRespStatus, rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the response body.
|
// read the response body.
|
||||||
actualContent, err = io.ReadAll(rec.Body)
|
actualContent, err = io.ReadAll(rec.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i, instanceType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify whether the bucket obtained object is same as the one created.
|
// Verify whether the bucket obtained object is same as the one created.
|
||||||
if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) {
|
if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) {
|
||||||
t.Log(string(testCase.expectedContent), string(actualContent))
|
t.Log(string(testCase.expectedContent), string(actualContent))
|
||||||
t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i+1, instanceType)
|
t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i, instanceType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+51
-51
@@ -58,15 +58,6 @@ type LifecycleSys struct{}
|
|||||||
|
|
||||||
// Get - gets lifecycle config associated to a given bucket name.
|
// Get - gets lifecycle config associated to a given bucket name.
|
||||||
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
|
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
|
||||||
if globalIsGateway {
|
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI == nil {
|
|
||||||
return nil, errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, BucketLifecycleNotFound{Bucket: bucketName}
|
|
||||||
}
|
|
||||||
|
|
||||||
return globalBucketMetadataSys.GetLifecycleConfig(bucketName)
|
return globalBucketMetadataSys.GetLifecycleConfig(bucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +146,13 @@ type newerNoncurrentTask struct {
|
|||||||
versions []ObjectToDelete
|
versions []ObjectToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type transitionTask struct {
|
||||||
|
tier string
|
||||||
|
objInfo ObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
type transitionState struct {
|
type transitionState struct {
|
||||||
once sync.Once
|
transitionCh chan transitionTask
|
||||||
transitionCh chan ObjectInfo
|
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
objAPI ObjectLayer
|
objAPI ObjectLayer
|
||||||
@@ -171,33 +166,42 @@ type transitionState struct {
|
|||||||
lastDayStats map[string]*lastDayTierStats
|
lastDayStats map[string]*lastDayTierStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transitionState) queueTransitionTask(oi ObjectInfo) {
|
func (t *transitionState) queueTransitionTask(oi ObjectInfo, sc string) {
|
||||||
select {
|
select {
|
||||||
case <-GlobalContext.Done():
|
case <-t.ctx.Done():
|
||||||
t.once.Do(func() {
|
case t.transitionCh <- transitionTask{objInfo: oi, tier: sc}:
|
||||||
close(t.transitionCh)
|
|
||||||
})
|
|
||||||
case t.transitionCh <- oi:
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalTransitionState *transitionState
|
var globalTransitionState *transitionState
|
||||||
|
|
||||||
func newTransitionState(ctx context.Context, objAPI ObjectLayer) *transitionState {
|
// newTransitionState returns a transitionState object ready to be initialized
|
||||||
|
// via its Init method.
|
||||||
|
func newTransitionState(ctx context.Context) *transitionState {
|
||||||
return &transitionState{
|
return &transitionState{
|
||||||
transitionCh: make(chan ObjectInfo, 10000),
|
transitionCh: make(chan transitionTask, 10000),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
objAPI: objAPI,
|
|
||||||
killCh: make(chan struct{}),
|
killCh: make(chan struct{}),
|
||||||
lastDayStats: make(map[string]*lastDayTierStats),
|
lastDayStats: make(map[string]*lastDayTierStats),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init initializes t with given objAPI and instantiates the configured number
|
||||||
|
// of transition workers.
|
||||||
|
func (t *transitionState) Init(objAPI ObjectLayer) {
|
||||||
|
n := globalAPIConfig.getTransitionWorkers()
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
t.objAPI = objAPI
|
||||||
|
t.updateWorkers(n)
|
||||||
|
}
|
||||||
|
|
||||||
// PendingTasks returns the number of ILM transition tasks waiting for a worker
|
// PendingTasks returns the number of ILM transition tasks waiting for a worker
|
||||||
// goroutine.
|
// goroutine.
|
||||||
func (t *transitionState) PendingTasks() int {
|
func (t *transitionState) PendingTasks() int {
|
||||||
return len(globalTransitionState.transitionCh)
|
return len(t.transitionCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActiveTasks returns the number of active (ongoing) ILM transition tasks.
|
// ActiveTasks returns the number of active (ongoing) ILM transition tasks.
|
||||||
@@ -206,34 +210,32 @@ func (t *transitionState) ActiveTasks() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// worker waits for transition tasks
|
// worker waits for transition tasks
|
||||||
func (t *transitionState) worker(ctx context.Context, objectAPI ObjectLayer) {
|
func (t *transitionState) worker(objectAPI ObjectLayer) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-t.killCh:
|
case <-t.killCh:
|
||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-t.ctx.Done():
|
||||||
return
|
return
|
||||||
case oi, ok := <-t.transitionCh:
|
case task, ok := <-t.transitionCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&t.activeTasks, 1)
|
atomic.AddInt32(&t.activeTasks, 1)
|
||||||
var tier string
|
if err := transitionObject(t.ctx, objectAPI, task.objInfo, task.tier); err != nil {
|
||||||
var err error
|
logger.LogIf(t.ctx, fmt.Errorf("Transition failed for %s/%s version:%s with %w",
|
||||||
if tier, err = transitionObject(ctx, objectAPI, oi); err != nil {
|
task.objInfo.Bucket, task.objInfo.Name, task.objInfo.VersionID, err))
|
||||||
logger.LogIf(ctx, fmt.Errorf("Transition failed for %s/%s version:%s with %w", oi.Bucket, oi.Name, oi.VersionID, err))
|
|
||||||
} else {
|
} else {
|
||||||
ts := tierStats{
|
ts := tierStats{
|
||||||
TotalSize: uint64(oi.Size),
|
TotalSize: uint64(task.objInfo.Size),
|
||||||
NumVersions: 1,
|
NumVersions: 1,
|
||||||
}
|
}
|
||||||
if oi.IsLatest {
|
if task.objInfo.IsLatest {
|
||||||
ts.NumObjects = 1
|
ts.NumObjects = 1
|
||||||
}
|
}
|
||||||
t.addLastDayStats(tier, ts)
|
t.addLastDayStats(task.tier, ts)
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&t.activeTasks, -1)
|
atomic.AddInt32(&t.activeTasks, -1)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,9 +266,15 @@ func (t *transitionState) getDailyAllTierStats() DailyAllTierStats {
|
|||||||
func (t *transitionState) UpdateWorkers(n int) {
|
func (t *transitionState) UpdateWorkers(n int) {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
|
if t.objAPI == nil { // Init hasn't been called yet.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.updateWorkers(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transitionState) updateWorkers(n int) {
|
||||||
for t.numWorkers < n {
|
for t.numWorkers < n {
|
||||||
go t.worker(t.ctx, t.objAPI)
|
go t.worker(t.objAPI)
|
||||||
t.numWorkers++
|
t.numWorkers++
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,12 +284,6 @@ func (t *transitionState) UpdateWorkers(n int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundTransition(ctx context.Context, objectAPI ObjectLayer) {
|
|
||||||
globalTransitionState = newTransitionState(ctx, objectAPI)
|
|
||||||
n := globalAPIConfig.getTransitionWorkers()
|
|
||||||
globalTransitionState.UpdateWorkers(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidStorageClass = errors.New("invalid storage class")
|
var errInvalidStorageClass = errors.New("invalid storage class")
|
||||||
|
|
||||||
func validateTransitionTier(lc *lifecycle.Lifecycle) error {
|
func validateTransitionTier(lc *lifecycle.Lifecycle) error {
|
||||||
@@ -304,9 +306,10 @@ func validateTransitionTier(lc *lifecycle.Lifecycle) error {
|
|||||||
// This is to be called after a successful upload of an object (version).
|
// This is to be called after a successful upload of an object (version).
|
||||||
func enqueueTransitionImmediate(obj ObjectInfo) {
|
func enqueueTransitionImmediate(obj ObjectInfo) {
|
||||||
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
|
||||||
switch lc.ComputeAction(obj.ToLifecycleOpts()) {
|
event := lc.Eval(obj.ToLifecycleOpts())
|
||||||
|
switch event.Action {
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
globalTransitionState.queueTransitionTask(obj)
|
globalTransitionState.queueTransitionTask(obj, event.StorageClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +374,7 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
|
|||||||
EventName: eventName,
|
EventName: eventName,
|
||||||
BucketName: oi.Bucket,
|
BucketName: oi.Bucket,
|
||||||
Object: objInfo,
|
Object: objInfo,
|
||||||
Host: "Internal: [ILM-EXPIRY]",
|
Host: "Internal: [ILM-Expiry]",
|
||||||
})
|
})
|
||||||
|
|
||||||
case expireRestoredObj:
|
case expireRestoredObj:
|
||||||
@@ -403,12 +406,7 @@ func genTransitionObjName(bucket string) (string, error) {
|
|||||||
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content
|
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content
|
||||||
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
||||||
// to the transition tier without decrypting or re-encrypting.
|
// to the transition tier without decrypting or re-encrypting.
|
||||||
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo) (string, error) {
|
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, tier string) error {
|
||||||
lc, err := globalLifecycleSys.Get(oi.Bucket)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
tier := lc.TransitionTier(oi.ToLifecycleOpts())
|
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
Transition: TransitionOptions{
|
Transition: TransitionOptions{
|
||||||
Status: lifecycle.TransitionPending,
|
Status: lifecycle.TransitionPending,
|
||||||
@@ -420,7 +418,7 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo)
|
|||||||
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
||||||
MTime: oi.ModTime,
|
MTime: oi.ModTime,
|
||||||
}
|
}
|
||||||
return tier, objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
return objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
type auditTierOp struct {
|
type auditTierOp struct {
|
||||||
@@ -550,7 +548,7 @@ type RestoreObjectRequest struct {
|
|||||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RestoreRequest" json:"-"`
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RestoreRequest" json:"-"`
|
||||||
Days int `xml:"Days,omitempty"`
|
Days int `xml:"Days,omitempty"`
|
||||||
Type RestoreRequestType `xml:"Type,omitempty"`
|
Type RestoreRequestType `xml:"Type,omitempty"`
|
||||||
Tier string `xml:"Tier,-"`
|
Tier string `xml:"Tier"`
|
||||||
Description string `xml:"Description,omitempty"`
|
Description string `xml:"Description,omitempty"`
|
||||||
SelectParameters *SelectParameters `xml:"SelectParameters,omitempty"`
|
SelectParameters *SelectParameters `xml:"SelectParameters,omitempty"`
|
||||||
OutputLocation OutputLocation `xml:"OutputLocation,omitempty"`
|
OutputLocation OutputLocation `xml:"OutputLocation,omitempty"`
|
||||||
@@ -670,7 +668,9 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
|
|||||||
if len(objInfo.UserTags) != 0 {
|
if len(objInfo.UserTags) != 0 {
|
||||||
meta[xhttp.AmzObjectTagging] = objInfo.UserTags
|
meta[xhttp.AmzObjectTagging] = objInfo.UserTags
|
||||||
}
|
}
|
||||||
|
// Set restore object status
|
||||||
|
restoreExpiry := lifecycle.ExpectedExpiryTime(time.Now().UTC(), rreq.Days)
|
||||||
|
meta[xhttp.AmzRestore] = completedRestoreObj(restoreExpiry).String()
|
||||||
return ObjectOptions{
|
return ObjectOptions{
|
||||||
Versioned: globalBucketVersioningSys.PrefixEnabled(bucket, object),
|
Versioned: globalBucketVersioningSys.PrefixEnabled(bucket, object),
|
||||||
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object),
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object),
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
|
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
// - delimiter if set should be equal to '/', otherwise the request is rejected.
|
// - delimiter if set should be equal to '/', otherwise the request is rejected.
|
||||||
// - marker if set should have a common prefix with 'prefix' param, otherwise
|
// - marker if set should have a common prefix with 'prefix' param, otherwise
|
||||||
// the request is rejected.
|
// the request is rejected.
|
||||||
func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int) APIErrorCode {
|
func validateListObjectsArgs(prefix, marker, delimiter, encodingType string, maxKeys int) APIErrorCode {
|
||||||
// Max keys cannot be negative.
|
// Max keys cannot be negative.
|
||||||
if maxKeys < 0 {
|
if maxKeys < 0 {
|
||||||
return ErrInvalidMaxKeys
|
return ErrInvalidMaxKeys
|
||||||
@@ -48,6 +48,14 @@ func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !IsValidObjectPrefix(prefix) {
|
||||||
|
return ErrInvalidObjectName
|
||||||
|
}
|
||||||
|
|
||||||
|
if marker != "" && !HasPrefix(marker, prefix) {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +91,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate the query params before beginning to serve the request.
|
// Validate the query params before beginning to serve the request.
|
||||||
if s3Error := validateListObjectsArgs(marker, delimiter, encodingType, maxkeys); s3Error != ErrNone {
|
if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxkeys); s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -107,7 +115,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo)
|
response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponse(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata.
|
// ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata.
|
||||||
@@ -148,7 +156,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
// Validate the query params before beginning to serve the request.
|
// Validate the query params before beginning to serve the request.
|
||||||
// fetch-owner is not validated since it is a boolean
|
// fetch-owner is not validated since it is a boolean
|
||||||
if s3Error := validateListObjectsArgs(token, delimiter, encodingType, maxKeys); s3Error != ErrNone {
|
if s3Error := validateListObjectsArgs(prefix, token, delimiter, encodingType, maxKeys); s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -177,7 +185,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
|
|||||||
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, true)
|
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, true)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponse(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListObjectsV2Handler - GET Bucket (List Objects) Version 2.
|
// ListObjectsV2Handler - GET Bucket (List Objects) Version 2.
|
||||||
@@ -218,7 +226,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
|||||||
|
|
||||||
// Validate the query params before beginning to serve the request.
|
// Validate the query params before beginning to serve the request.
|
||||||
// fetch-owner is not validated since it is a boolean
|
// fetch-owner is not validated since it is a boolean
|
||||||
if s3Error := validateListObjectsArgs(token, delimiter, encodingType, maxKeys); s3Error != ErrNone {
|
if s3Error := validateListObjectsArgs(prefix, token, delimiter, encodingType, maxKeys); s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -252,7 +260,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
|||||||
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false)
|
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponse(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
||||||
@@ -325,7 +333,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate all the query params before beginning to serve the request.
|
// Validate all the query params before beginning to serve the request.
|
||||||
if s3Error := validateListObjectsArgs(marker, delimiter, encodingType, maxKeys); s3Error != ErrNone {
|
if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxKeys); s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -349,5 +357,5 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
|
response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodeResponse(response))
|
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||||
}
|
}
|
||||||
|
|||||||
+161
-116
@@ -18,14 +18,15 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
bucketsse "github.com/minio/minio/internal/bucket/encryption"
|
bucketsse "github.com/minio/minio/internal/bucket/encryption"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
@@ -41,6 +42,8 @@ import (
|
|||||||
|
|
||||||
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
||||||
type BucketMetadataSys struct {
|
type BucketMetadataSys struct {
|
||||||
|
objAPI ObjectLayer
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
metadataMap map[string]BucketMetadata
|
metadataMap map[string]BucketMetadata
|
||||||
}
|
}
|
||||||
@@ -54,27 +57,36 @@ func (sys *BucketMetadataSys) Count() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove bucket metadata from memory.
|
// Remove bucket metadata from memory.
|
||||||
func (sys *BucketMetadataSys) Remove(bucket string) {
|
func (sys *BucketMetadataSys) Remove(buckets ...string) {
|
||||||
if globalIsGateway {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
delete(sys.metadataMap, bucket)
|
for _, bucket := range buckets {
|
||||||
globalBucketMonitor.DeleteBucket(bucket)
|
delete(sys.metadataMap, bucket)
|
||||||
|
globalBucketMonitor.DeleteBucket(bucket)
|
||||||
|
}
|
||||||
sys.Unlock()
|
sys.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveStaleBuckets removes all stale buckets in memory that are not on disk.
|
||||||
|
func (sys *BucketMetadataSys) RemoveStaleBuckets(diskBuckets set.StringSet) {
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
for bucket := range sys.metadataMap {
|
||||||
|
if diskBuckets.Contains(bucket) {
|
||||||
|
continue
|
||||||
|
} // doesn't exist on disk remove from memory.
|
||||||
|
delete(sys.metadataMap, bucket)
|
||||||
|
globalBucketMonitor.DeleteBucket(bucket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set - sets a new metadata in-memory.
|
// Set - sets a new metadata in-memory.
|
||||||
// Only a shallow copy is saved and fields with references
|
// Only a shallow copy is saved and fields with references
|
||||||
// cannot be modified without causing a race condition,
|
// cannot be modified without causing a race condition,
|
||||||
// so they should be replaced atomically and not appended to, etc.
|
// so they should be replaced atomically and not appended to, etc.
|
||||||
// Data is not persisted to disk.
|
// Data is not persisted to disk.
|
||||||
func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
|
func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
|
||||||
if globalIsGateway {
|
if !isMinioMetaBucketName(bucket) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if bucket != minioMetaBucket {
|
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
sys.metadataMap[bucket] = meta
|
sys.metadataMap[bucket] = meta
|
||||||
sys.Unlock()
|
sys.Unlock()
|
||||||
@@ -87,21 +99,7 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
|
|||||||
return updatedAt, errServerNotInitialized
|
return updatedAt, errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIsGateway && globalGatewayName != NASBackendGateway {
|
if isMinioMetaBucketName(bucket) {
|
||||||
if configFile == bucketPolicyConfig {
|
|
||||||
if configData == nil {
|
|
||||||
return updatedAt, objAPI.DeleteBucketPolicy(ctx, bucket)
|
|
||||||
}
|
|
||||||
config, err := policy.ParseConfig(bytes.NewReader(configData), bucket)
|
|
||||||
if err != nil {
|
|
||||||
return updatedAt, err
|
|
||||||
}
|
|
||||||
return updatedAt, objAPI.SetBucketPolicy(ctx, bucket, config)
|
|
||||||
}
|
|
||||||
return updatedAt, NotImplemented{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bucket == minioMetaBucket {
|
|
||||||
return updatedAt, errInvalidArgument
|
return updatedAt, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +184,7 @@ func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configF
|
|||||||
// For all other bucket specific metadata, use the relevant
|
// For all other bucket specific metadata, use the relevant
|
||||||
// calls implemented specifically for each of those features.
|
// calls implemented specifically for each of those features.
|
||||||
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
||||||
if globalIsGateway || bucket == minioMetaBucket {
|
if isMinioMetaBucketName(bucket) {
|
||||||
return newBucketMetadata(bucket), errConfigNotFound
|
return newBucketMetadata(bucket), errConfigNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +202,7 @@ func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
|||||||
// GetVersioningConfig returns configured versioning config
|
// GetVersioningConfig returns configured versioning config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Versioning, time.Time, error) {
|
func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Versioning, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, meta.Created, nil
|
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, meta.Created, nil
|
||||||
@@ -217,7 +215,7 @@ func (sys *BucketMetadataSys) GetVersioningConfig(bucket string) (*versioning.Ve
|
|||||||
// GetTaggingConfig returns configured tagging config
|
// GetTaggingConfig returns configured tagging config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.Time, error) {
|
func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketTaggingNotFound{Bucket: bucket}
|
||||||
@@ -233,7 +231,7 @@ func (sys *BucketMetadataSys) GetTaggingConfig(bucket string) (*tags.Tags, time.
|
|||||||
// GetObjectLockConfig returns configured object lock config
|
// GetObjectLockConfig returns configured object lock config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Config, time.Time, error) {
|
func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Config, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketObjectLockConfigNotFound{Bucket: bucket}
|
||||||
@@ -249,23 +247,7 @@ func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Co
|
|||||||
// GetLifecycleConfig returns configured lifecycle config
|
// GetLifecycleConfig returns configured lifecycle config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, error) {
|
func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, error) {
|
||||||
if globalIsGateway && globalGatewayName == NASBackendGateway {
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
// Only needed in case of NAS gateway.
|
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI == nil {
|
|
||||||
return nil, errServerNotInitialized
|
|
||||||
}
|
|
||||||
meta, err := loadBucketMetadata(GlobalContext, objAPI, bucket)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if meta.lifecycleConfig == nil {
|
|
||||||
return nil, BucketLifecycleNotFound{Bucket: bucket}
|
|
||||||
}
|
|
||||||
return meta.lifecycleConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, BucketLifecycleNotFound{Bucket: bucket}
|
return nil, BucketLifecycleNotFound{Bucket: bucket}
|
||||||
@@ -281,20 +263,7 @@ func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Life
|
|||||||
// GetNotificationConfig returns configured notification config
|
// GetNotificationConfig returns configured notification config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) {
|
func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) {
|
||||||
if globalIsGateway && globalGatewayName == NASBackendGateway {
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
// Only needed in case of NAS gateway.
|
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI == nil {
|
|
||||||
return nil, errServerNotInitialized
|
|
||||||
}
|
|
||||||
meta, err := loadBucketMetadata(GlobalContext, objAPI, bucket)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return meta.notificationConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -304,7 +273,7 @@ func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Confi
|
|||||||
// GetSSEConfig returns configured SSE config
|
// GetSSEConfig returns configured SSE config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEConfig, time.Time, error) {
|
func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEConfig, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketSSEConfigNotFound{Bucket: bucket}
|
||||||
@@ -319,7 +288,7 @@ func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEC
|
|||||||
|
|
||||||
// CreatedAt returns the time of creation of bucket
|
// CreatedAt returns the time of creation of bucket
|
||||||
func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
|
func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
@@ -329,16 +298,7 @@ func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) {
|
|||||||
// GetPolicyConfig returns configured bucket policy
|
// GetPolicyConfig returns configured bucket policy
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) {
|
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, time.Time, error) {
|
||||||
if globalIsGateway {
|
meta, _, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI == nil {
|
|
||||||
return nil, time.Time{}, errServerNotInitialized
|
|
||||||
}
|
|
||||||
p, err := objAPI.GetBucketPolicy(GlobalContext, bucket)
|
|
||||||
return p, UTCNow(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketPolicyNotFound{Bucket: bucket}
|
||||||
@@ -354,7 +314,7 @@ func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, ti
|
|||||||
// GetQuotaConfig returns configured bucket quota
|
// GetQuotaConfig returns configured bucket quota
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string) (*madmin.BucketQuota, time.Time, error) {
|
func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string) (*madmin.BucketQuota, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(ctx, bucket)
|
meta, _, err := sys.GetConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, time.Time{}, BucketQuotaConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketQuotaConfigNotFound{Bucket: bucket}
|
||||||
@@ -367,7 +327,7 @@ func (sys *BucketMetadataSys) GetQuotaConfig(ctx context.Context, bucket string)
|
|||||||
// GetReplicationConfig returns configured bucket replication config
|
// GetReplicationConfig returns configured bucket replication config
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket string) (*replication.Config, time.Time, error) {
|
func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket string) (*replication.Config, time.Time, error) {
|
||||||
meta, err := sys.GetConfig(ctx, bucket)
|
meta, reloaded, err := sys.GetConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
|
||||||
@@ -378,13 +338,18 @@ func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket s
|
|||||||
if meta.replicationConfig == nil {
|
if meta.replicationConfig == nil {
|
||||||
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
|
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
if reloaded {
|
||||||
|
globalBucketTargetSys.set(BucketInfo{
|
||||||
|
Name: bucket,
|
||||||
|
}, meta)
|
||||||
|
}
|
||||||
return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil
|
return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketTargetsConfig returns configured bucket targets for this bucket
|
// GetBucketTargetsConfig returns configured bucket targets for this bucket
|
||||||
// The returned object may not be modified.
|
// The returned object may not be modified.
|
||||||
func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) {
|
func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) {
|
||||||
meta, err := sys.GetConfig(GlobalContext, bucket)
|
meta, reloaded, err := sys.GetConfig(GlobalContext, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errConfigNotFound) {
|
if errors.Is(err, errConfigNotFound) {
|
||||||
return nil, BucketRemoteTargetNotFound{Bucket: bucket}
|
return nil, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
@@ -394,40 +359,56 @@ func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.Buc
|
|||||||
if meta.bucketTargetConfig == nil {
|
if meta.bucketTargetConfig == nil {
|
||||||
return nil, BucketRemoteTargetNotFound{Bucket: bucket}
|
return nil, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
if reloaded {
|
||||||
|
globalBucketTargetSys.set(BucketInfo{
|
||||||
|
Name: bucket,
|
||||||
|
}, meta)
|
||||||
|
}
|
||||||
return meta.bucketTargetConfig, nil
|
return meta.bucketTargetConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig returns a specific configuration from the bucket metadata.
|
// GetConfigFromDisk read bucket metadata config from disk.
|
||||||
// The returned object may not be modified.
|
func (sys *BucketMetadataSys) GetConfigFromDisk(ctx context.Context, bucket string) (BucketMetadata, error) {
|
||||||
func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (BucketMetadata, error) {
|
|
||||||
objAPI := newObjectLayerFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return newBucketMetadata(bucket), errServerNotInitialized
|
return newBucketMetadata(bucket), errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIsGateway {
|
if isMinioMetaBucketName(bucket) {
|
||||||
return newBucketMetadata(bucket), NotImplemented{}
|
return newBucketMetadata(bucket), errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
if bucket == minioMetaBucket {
|
return loadBucketMetadata(ctx, objAPI, bucket)
|
||||||
return newBucketMetadata(bucket), errInvalidArgument
|
}
|
||||||
|
|
||||||
|
// GetConfig returns a specific configuration from the bucket metadata.
|
||||||
|
// The returned object may not be modified.
|
||||||
|
// reloaded will be true if metadata refreshed from disk
|
||||||
|
func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (meta BucketMetadata, reloaded bool, err error) {
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
return newBucketMetadata(bucket), reloaded, errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMinioMetaBucketName(bucket) {
|
||||||
|
return newBucketMetadata(bucket), reloaded, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
meta, ok := sys.metadataMap[bucket]
|
meta, ok := sys.metadataMap[bucket]
|
||||||
sys.RUnlock()
|
sys.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
return meta, nil
|
return meta, reloaded, nil
|
||||||
}
|
}
|
||||||
meta, err := loadBucketMetadata(ctx, objAPI, bucket)
|
meta, err = loadBucketMetadata(ctx, objAPI, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return meta, err
|
return meta, reloaded, err
|
||||||
}
|
}
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
sys.metadataMap[bucket] = meta
|
sys.metadataMap[bucket] = meta
|
||||||
sys.Unlock()
|
sys.Unlock()
|
||||||
|
|
||||||
return meta, nil
|
return meta, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init - initializes bucket metadata system for all buckets.
|
// Init - initializes bucket metadata system for all buckets.
|
||||||
@@ -436,65 +417,129 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// In gateway mode, we don't need to load bucket metadata except
|
sys.objAPI = objAPI
|
||||||
// NAS gateway backend.
|
|
||||||
if globalIsGateway && !objAPI.IsNotificationSupported() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load bucket metadata sys in background
|
// Load bucket metadata sys in background
|
||||||
go sys.load(ctx, buckets, objAPI)
|
go sys.init(ctx, buckets)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *BucketMetadataSys) loadBucketMetadata(ctx context.Context, bucket BucketInfo) error {
|
||||||
|
meta, err := loadBucketMetadata(ctx, sys.objAPI, bucket.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
sys.metadataMap[bucket.Name] = meta
|
||||||
|
sys.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// concurrently load bucket metadata to speed up loading bucket metadata.
|
// concurrently load bucket metadata to speed up loading bucket metadata.
|
||||||
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo) {
|
||||||
g := errgroup.WithNErrs(len(buckets))
|
g := errgroup.WithNErrs(len(buckets))
|
||||||
|
bucketMetas := make([]BucketMetadata, len(buckets))
|
||||||
for index := range buckets {
|
for index := range buckets {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
_, _ = objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
|
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
|
||||||
// Ensure heal opts for bucket metadata be deep healed all the time.
|
// Ensure heal opts for bucket metadata be deep healed all the time.
|
||||||
ScanMode: madmin.HealDeepScan,
|
ScanMode: madmin.HealDeepScan,
|
||||||
Recreate: true,
|
Recreate: true,
|
||||||
})
|
})
|
||||||
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name)
|
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
|
return err
|
||||||
meta = newBucketMetadata(buckets[index].Name)
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sys.Lock()
|
bucketMetas[index] = meta
|
||||||
sys.metadataMap[buckets[index].Name] = meta
|
|
||||||
sys.Unlock()
|
|
||||||
|
|
||||||
globalEventNotifier.set(buckets[index], meta) // set notification targets
|
|
||||||
|
|
||||||
globalBucketTargetSys.set(buckets[index], meta) // set remote replication targets
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
for _, err := range g.Wait() {
|
|
||||||
|
errs := g.Wait()
|
||||||
|
for _, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hold lock here to update in-memory map at once,
|
||||||
|
// instead of serializing the Go routines.
|
||||||
|
sys.Lock()
|
||||||
|
for i, meta := range bucketMetas {
|
||||||
|
if errs[i] != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sys.metadataMap[buckets[i].Name] = meta
|
||||||
|
}
|
||||||
|
sys.Unlock()
|
||||||
|
|
||||||
|
for i, meta := range bucketMetas {
|
||||||
|
if errs[i] != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
globalEventNotifier.set(buckets[i], meta) // set notification targets
|
||||||
|
globalBucketTargetSys.set(buckets[i], meta) // set remote replication targets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
||||||
|
const bucketMetadataRefresh = 15 * time.Minute
|
||||||
|
|
||||||
|
t := time.NewTimer(bucketMetadataRefresh)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle if we have some buckets in-memory those are stale.
|
||||||
|
// first delete them and then replace the newer state()
|
||||||
|
// from disk.
|
||||||
|
diskBuckets := set.CreateStringSet()
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
diskBuckets.Add(bucket.Name)
|
||||||
|
}
|
||||||
|
sys.RemoveStaleBuckets(diskBuckets)
|
||||||
|
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
err := sys.loadBucketMetadata(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if there is a spare procs, wait 100ms instead
|
||||||
|
waitForLowIO(runtime.GOMAXPROCS(0), 100*time.Millisecond, currentHTTPIO)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Reset(bucketMetadataRefresh)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
||||||
func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
||||||
count := 100 // load 100 bucket metadata at a time.
|
count := 100 // load 100 bucket metadata at a time.
|
||||||
for {
|
for {
|
||||||
if len(buckets) < count {
|
if len(buckets) < count {
|
||||||
sys.concurrentLoad(ctx, buckets, objAPI)
|
sys.concurrentLoad(ctx, buckets)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
sys.concurrentLoad(ctx, buckets[:count], objAPI)
|
sys.concurrentLoad(ctx, buckets[:count])
|
||||||
buckets = buckets[count:]
|
buckets = buckets[count:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalIsDistErasure {
|
||||||
|
go sys.refreshBucketsMetadataLoop(ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the state of the BucketMetadataSys.
|
// Reset the state of the BucketMetadataSys.
|
||||||
|
|||||||
+2
-19
@@ -29,7 +29,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
bucketsse "github.com/minio/minio/internal/bucket/encryption"
|
bucketsse "github.com/minio/minio/internal/bucket/encryption"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
@@ -135,7 +135,7 @@ func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
|
|||||||
func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error {
|
func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
|
logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
|
||||||
return errors.New("bucket name cannot be empty")
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile)
|
configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile)
|
||||||
data, err := readConfig(ctx, api, configFile)
|
data, err := readConfig(ctx, api, configFile)
|
||||||
@@ -442,23 +442,6 @@ func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
|
|||||||
return saveConfig(ctx, api, configFile, data)
|
return saveConfig(ctx, api, configFile, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteBucketMetadata deletes bucket metadata
|
|
||||||
// If config does not exist no error is returned.
|
|
||||||
func deleteBucketMetadata(ctx context.Context, obj objectDeleter, bucket string) error {
|
|
||||||
metadataFiles := []string{
|
|
||||||
dataUsageCacheName,
|
|
||||||
bucketMetadataFile,
|
|
||||||
path.Join(replicationDir, resyncFileName),
|
|
||||||
}
|
|
||||||
for _, metaFile := range metadataFiles {
|
|
||||||
configFile := path.Join(bucketMetaPrefix, bucket, metaFile)
|
|
||||||
if err := deleteConfig(ctx, obj, configFile); err != nil && err != errConfigNotFound {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
|
// migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
|
||||||
func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error {
|
func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error {
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"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/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,11 +50,6 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !objAPI.IsNotificationSupported() {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
|
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
@@ -119,11 +114,6 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !objectAPI.IsNotificationSupported() {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucketName := vars["bucket"]
|
bucketName := vars["bucket"]
|
||||||
|
|
||||||
|
|||||||
@@ -36,15 +36,6 @@ type BucketObjectLockSys struct{}
|
|||||||
|
|
||||||
// Get - Get retention configuration.
|
// Get - Get retention configuration.
|
||||||
func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) {
|
func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) {
|
||||||
if globalIsGateway {
|
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI == nil {
|
|
||||||
return r, errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucketName)
|
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, BucketObjectLockConfigNotFound{Bucket: bucketName}) {
|
if errors.Is(err, BucketObjectLockConfigNotFound{Bucket: bucketName}) {
|
||||||
@@ -165,11 +156,8 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
|||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
|
||||||
// If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention
|
// If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention, the operation will succeed.
|
||||||
// or s3:GetBucketObjectLockConfiguration permissions, the operation will succeed.
|
if checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName) != ErrNone {
|
||||||
govBypassPerms1 := checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName)
|
|
||||||
govBypassPerms2 := checkRequestAuthType(ctx, r, policy.GetBucketObjectLockConfigurationAction, bucket, object.ObjectName)
|
|
||||||
if govBypassPerms1 != ErrNone && govBypassPerms2 != ErrNone {
|
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,13 +86,17 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, APIError{
|
||||||
|
Code: "ErrMalformedXML",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
Description: err.Error(),
|
||||||
|
}, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version in policy must not be empty
|
// Version in policy must not be empty
|
||||||
if bucketPolicy.Version == "" {
|
if bucketPolicy.Version == "" {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPolicyInvalidVersion), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,15 +113,12 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
Type: madmin.SRBucketMetaTypePolicy,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Policy: bucketPolicyBytes,
|
Policy: bucketPolicyBytes,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success.
|
// Success.
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
@@ -156,14 +157,11 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
Type: madmin.SRBucketMetaTypePolicy,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success.
|
// Success.
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
|
|||||||
|
|
||||||
// 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{"MakeBucketWithLocation"})
|
ExecObjectLayerAPITest(t, testCreateBucket, []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.
|
||||||
@@ -127,7 +127,7 @@ func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRoute
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
// Sync start.
|
// Sync start.
|
||||||
<-start
|
<-start
|
||||||
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
|
if err := obj.MakeBucket(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
t.Logf("unexpected error: %T: %v", err, err)
|
t.Logf("unexpected error: %T: %v", err, err)
|
||||||
return
|
return
|
||||||
@@ -162,7 +162,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
|||||||
credentials auth.Credentials, t *testing.T,
|
credentials auth.Credentials, t *testing.T,
|
||||||
) {
|
) {
|
||||||
bucketName1 := fmt.Sprintf("%s-1", bucketName)
|
bucketName1 := fmt.Sprintf("%s-1", bucketName)
|
||||||
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
|
if err := obj.MakeBucket(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-10
@@ -28,6 +28,7 @@ import (
|
|||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/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"
|
||||||
@@ -65,9 +66,20 @@ func NewPolicySys() *PolicySys {
|
|||||||
return &PolicySys{}
|
return &PolicySys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConditionValues(r *http.Request, lc string, username string, claims map[string]interface{}) map[string][]string {
|
func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[string][]string {
|
||||||
currTime := UTCNow()
|
currTime := UTCNow()
|
||||||
|
|
||||||
|
var (
|
||||||
|
username = cred.AccessKey
|
||||||
|
claims = cred.Claims
|
||||||
|
groups = cred.Groups
|
||||||
|
)
|
||||||
|
|
||||||
|
if cred.IsTemp() || cred.IsServiceAccount() {
|
||||||
|
// For derived credentials, check the parent user's permissions.
|
||||||
|
username = cred.ParentUser
|
||||||
|
}
|
||||||
|
|
||||||
principalType := "Anonymous"
|
principalType := "Anonymous"
|
||||||
if username != "" {
|
if username != "" {
|
||||||
principalType = "User"
|
principalType = "User"
|
||||||
@@ -192,17 +204,11 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
|||||||
for k, v := range claims {
|
for k, v := range claims {
|
||||||
vStr, ok := v.(string)
|
vStr, ok := v.(string)
|
||||||
if ok {
|
if ok {
|
||||||
// Special case for AD/LDAP STS users
|
// Trim any LDAP specific prefix
|
||||||
switch k {
|
args[strings.ToLower(strings.TrimPrefix(k, "ldap"))] = []string{vStr}
|
||||||
case ldapUser:
|
|
||||||
args["user"] = []string{vStr}
|
|
||||||
case ldapUserN:
|
|
||||||
args["username"] = []string{vStr}
|
|
||||||
default:
|
|
||||||
args[k] = []string{vStr}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add groups claim which could be a list. This will ensure that the claim
|
// Add groups claim which could be a list. This will ensure that the claim
|
||||||
// `jwt:groups` works.
|
// `jwt:groups` works.
|
||||||
if grpsVal, ok := claims["groups"]; ok {
|
if grpsVal, ok := claims["groups"]; ok {
|
||||||
@@ -219,6 +225,13 @@ func getConditionValues(r *http.Request, lc string, username string, claims map[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if not claim groups are available use the one with auth.Credentials
|
||||||
|
if _, ok := args["groups"]; !ok {
|
||||||
|
if len(groups) > 0 {
|
||||||
|
args["groups"] = groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-8
@@ -24,7 +24,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,13 +35,6 @@ type BucketQuotaSys struct {
|
|||||||
|
|
||||||
// Get - Get quota configuration.
|
// Get - Get quota configuration.
|
||||||
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
|
func (sys *BucketQuotaSys) Get(ctx context.Context, bucketName string) (*madmin.BucketQuota, error) {
|
||||||
if globalIsGateway {
|
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI == nil {
|
|
||||||
return nil, errServerNotInitialized
|
|
||||||
}
|
|
||||||
return &madmin.BucketQuota{}, nil
|
|
||||||
}
|
|
||||||
qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
qCfg, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucketName)
|
||||||
return qCfg, err
|
return qCfg, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,10 +46,6 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
@@ -59,7 +55,10 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if globalSiteReplicationSys.isEnabled() && logger.GetReqInfo(ctx).Cred.AccessKey != globalActiveCred.AccessKey {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned {
|
if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL)
|
||||||
return
|
return
|
||||||
@@ -170,6 +169,21 @@ func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.Respons
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, tgt := range targets.Targets {
|
||||||
|
if err := globalBucketTargetSys.RemoveTarget(ctx, bucket, tgt.Arn); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketTargetsFile); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
@@ -217,7 +231,19 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseW
|
|||||||
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
if err = enc.Encode(globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)); err != nil {
|
stats := globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)
|
||||||
|
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
||||||
|
bwMap := bwRpt.BucketStats[bucket]
|
||||||
|
for arn, st := range stats.Stats {
|
||||||
|
if bwMap != nil {
|
||||||
|
if bw, ok := bwMap[arn]; ok {
|
||||||
|
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
||||||
|
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
||||||
|
stats.Stats[arn] = st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = enc.Encode(stats); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -277,7 +303,13 @@ func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseW
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !config.HasExistingObjectReplication(arn) {
|
hasARN, hasExistingObjEnabled := config.HasExistingObjectReplication(arn)
|
||||||
|
if !hasARN {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrRemoteTargetNotFoundError), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasExistingObjEnabled {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoExistingObjects), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoExistingObjects), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -316,7 +348,27 @@ func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseW
|
|||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := startReplicationResync(ctx, bucket, arn, resetID, resetBeforeDate, objectAPI); err != nil {
|
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tgtBytes, err := json.Marshal(&targets)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalReplicationPool.resyncer.start(ctx, objectAPI, resyncOpts{
|
||||||
|
bucket: bucket,
|
||||||
|
arn: arn,
|
||||||
|
resyncID: resetID,
|
||||||
|
resyncBefore: resetBeforeDate,
|
||||||
|
}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Err: err,
|
Err: err,
|
||||||
@@ -365,10 +417,13 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var tgtStats map[string]TargetReplicationResyncStatus
|
||||||
globalReplicationPool.resyncState.RLock()
|
globalReplicationPool.resyncer.RLock()
|
||||||
brs, ok := globalReplicationPool.resyncState.statusMap[bucket]
|
brs, ok := globalReplicationPool.resyncer.statusMap[bucket]
|
||||||
globalReplicationPool.resyncState.RUnlock()
|
if ok {
|
||||||
|
tgtStats = brs.cloneTgtStats()
|
||||||
|
}
|
||||||
|
globalReplicationPool.resyncer.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -378,10 +433,11 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
|
|||||||
}), r.URL)
|
}), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
tgtStats = brs.cloneTgtStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
var rinfo ResyncTargetsInfo
|
var rinfo ResyncTargetsInfo
|
||||||
for tarn, st := range brs.TargetsMap {
|
for tarn, st := range tgtStats {
|
||||||
if arn != "" && tarn != arn {
|
if arn != "" && tarn != arn {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -389,7 +445,7 @@ func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.Response
|
|||||||
Arn: tarn,
|
Arn: tarn,
|
||||||
ResetID: st.ResyncID,
|
ResetID: st.ResyncID,
|
||||||
StartTime: st.StartTime,
|
StartTime: st.StartTime,
|
||||||
EndTime: st.EndTime,
|
EndTime: st.LastUpdate,
|
||||||
ResyncStatus: st.ResyncStatus.String(),
|
ResyncStatus: st.ResyncStatus.String(),
|
||||||
ReplicatedSize: st.ReplicatedSize,
|
ReplicatedSize: st.ReplicatedSize,
|
||||||
ReplicatedCount: st.ReplicatedCount,
|
ReplicatedCount: st.ReplicatedCount,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -37,12 +38,12 @@ func (b *BucketReplicationStats) hasReplicationUsage() bool {
|
|||||||
|
|
||||||
// ReplicationStats holds the global in-memory replication stats
|
// ReplicationStats holds the global in-memory replication stats
|
||||||
type ReplicationStats struct {
|
type ReplicationStats struct {
|
||||||
Cache map[string]*BucketReplicationStats
|
Cache map[string]*BucketReplicationStats
|
||||||
UsageCache map[string]*BucketReplicationStats
|
UsageCache map[string]*BucketReplicationStats
|
||||||
mostRecentStats BucketStatsMap
|
mostRecentStats BucketStatsMap
|
||||||
sync.RWMutex // mutex for Cache
|
sync.RWMutex // mutex for Cache
|
||||||
ulock sync.RWMutex // mutex for UsageCache
|
ulock sync.RWMutex // mutex for UsageCache
|
||||||
dlock sync.RWMutex // mutex for mostRecentStats
|
mostRecentStatsMu sync.Mutex // mutex for mostRecentStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes in-memory replication statistics for a bucket.
|
// Delete deletes in-memory replication statistics for a bucket.
|
||||||
@@ -196,7 +197,8 @@ func (r *ReplicationStats) loadInitialReplicationMetrics(ctx context.Context) {
|
|||||||
m := make(map[string]*BucketReplicationStats)
|
m := make(map[string]*BucketReplicationStats)
|
||||||
if stats, err := globalReplicationPool.loadStatsFromDisk(); err == nil {
|
if stats, err := globalReplicationPool.loadStatsFromDisk(); err == nil {
|
||||||
for b, st := range stats {
|
for b, st := range stats {
|
||||||
m[b] = &st
|
c := st.Clone()
|
||||||
|
m[b] = &c
|
||||||
}
|
}
|
||||||
r.ulock.Lock()
|
r.ulock.Lock()
|
||||||
r.UsageCache = m
|
r.UsageCache = m
|
||||||
@@ -245,13 +247,23 @@ outer:
|
|||||||
r.ulock.Unlock()
|
r.ulock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReplicationStats) getAllCachedLatest() BucketStatsMap {
|
// serializeStats will serialize the current stats.
|
||||||
|
// Will return (nil, nil) if no data.
|
||||||
|
func (r *ReplicationStats) serializeStats() ([]byte, error) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return BucketStatsMap{}
|
return nil, nil
|
||||||
}
|
}
|
||||||
r.dlock.RLock()
|
r.mostRecentStatsMu.Lock()
|
||||||
defer r.dlock.RUnlock()
|
defer r.mostRecentStatsMu.Unlock()
|
||||||
return r.mostRecentStats
|
if len(r.mostRecentStats.Stats) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
data := make([]byte, 4, 4+r.mostRecentStats.Msgsize())
|
||||||
|
// Add the replication stats meta header.
|
||||||
|
binary.LittleEndian.PutUint16(data[0:2], replStatsMetaFormat)
|
||||||
|
binary.LittleEndian.PutUint16(data[2:4], replStatsVersion)
|
||||||
|
// Add data
|
||||||
|
return r.mostRecentStats.MarshalMsg(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo) (bucketsReplicationStats map[string]BucketReplicationStats) {
|
func (r *ReplicationStats) getAllLatest(bucketsUsage map[string]BucketUsageInfo) (bucketsReplicationStats map[string]BucketReplicationStats) {
|
||||||
@@ -321,6 +333,7 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u Buck
|
|||||||
s = BucketReplicationStats{
|
s = BucketReplicationStats{
|
||||||
Stats: make(map[string]*BucketReplicationStat, len(stats)),
|
Stats: make(map[string]*BucketReplicationStat, len(stats)),
|
||||||
}
|
}
|
||||||
|
|
||||||
var latestTotReplicatedSize int64
|
var latestTotReplicatedSize int64
|
||||||
for _, st := range u.ReplicationInfo {
|
for _, st := range u.ReplicationInfo {
|
||||||
latestTotReplicatedSize += int64(st.ReplicatedSize)
|
latestTotReplicatedSize += int64(st.ReplicatedSize)
|
||||||
@@ -356,13 +369,15 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u Buck
|
|||||||
// normalize overall stats
|
// normalize overall stats
|
||||||
s.ReplicaSize = int64(math.Max(float64(totReplicaSize), float64(u.ReplicaSize)))
|
s.ReplicaSize = int64(math.Max(float64(totReplicaSize), float64(u.ReplicaSize)))
|
||||||
s.ReplicatedSize = int64(math.Max(float64(s.ReplicatedSize), float64(latestTotReplicatedSize)))
|
s.ReplicatedSize = int64(math.Max(float64(s.ReplicatedSize), float64(latestTotReplicatedSize)))
|
||||||
r.dlock.Lock()
|
r.mostRecentStatsMu.Lock()
|
||||||
if len(r.mostRecentStats.Stats) == 0 {
|
if len(r.mostRecentStats.Stats) == 0 {
|
||||||
r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()}
|
r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()}
|
||||||
}
|
}
|
||||||
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s}
|
if len(s.Stats) > 0 {
|
||||||
|
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s}
|
||||||
|
}
|
||||||
r.mostRecentStats.Timestamp = UTCNow()
|
r.mostRecentStats.Timestamp = UTCNow()
|
||||||
r.dlock.Unlock()
|
r.mostRecentStatsMu.Unlock()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@@ -628,26 +628,40 @@ func (v VersionPurgeStatusType) Pending() bool {
|
|||||||
return v == Pending || v == Failed
|
return v == Pending || v == Failed
|
||||||
}
|
}
|
||||||
|
|
||||||
type replicationResyncState struct {
|
type replicationResyncer struct {
|
||||||
// map of bucket to their resync status
|
// map of bucket to their resync status
|
||||||
statusMap map[string]BucketReplicationResyncStatus
|
statusMap map[string]BucketReplicationResyncStatus
|
||||||
|
workerSize int
|
||||||
|
resyncCancelCh chan struct{}
|
||||||
|
workerCh chan struct{}
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
replicationDir = "replication"
|
replicationDir = ".replication"
|
||||||
resyncFileName = "resync.bin"
|
resyncFileName = "resync.bin"
|
||||||
resyncMetaFormat = 1
|
resyncMetaFormat = 1
|
||||||
resyncMetaVersionV1 = 1
|
resyncMetaVersionV1 = 1
|
||||||
resyncMetaVersion = resyncMetaVersionV1
|
resyncMetaVersion = resyncMetaVersionV1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type resyncOpts struct {
|
||||||
|
bucket string
|
||||||
|
arn string
|
||||||
|
resyncID string
|
||||||
|
resyncBefore time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// ResyncStatusType status of resync operation
|
// ResyncStatusType status of resync operation
|
||||||
type ResyncStatusType int
|
type ResyncStatusType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NoResync - no resync in progress
|
// NoResync - no resync in progress
|
||||||
NoResync ResyncStatusType = iota
|
NoResync ResyncStatusType = iota
|
||||||
|
// ResyncPending - resync pending
|
||||||
|
ResyncPending
|
||||||
|
// ResyncCanceled - resync canceled
|
||||||
|
ResyncCanceled
|
||||||
// ResyncStarted - resync in progress
|
// ResyncStarted - resync in progress
|
||||||
ResyncStarted
|
ResyncStarted
|
||||||
// ResyncCompleted - resync finished
|
// ResyncCompleted - resync finished
|
||||||
@@ -656,6 +670,10 @@ const (
|
|||||||
ResyncFailed
|
ResyncFailed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (rt ResyncStatusType) isValid() bool {
|
||||||
|
return rt != NoResync
|
||||||
|
}
|
||||||
|
|
||||||
func (rt ResyncStatusType) String() string {
|
func (rt ResyncStatusType) String() string {
|
||||||
switch rt {
|
switch rt {
|
||||||
case ResyncStarted:
|
case ResyncStarted:
|
||||||
@@ -664,6 +682,10 @@ func (rt ResyncStatusType) String() string {
|
|||||||
return "Completed"
|
return "Completed"
|
||||||
case ResyncFailed:
|
case ResyncFailed:
|
||||||
return "Failed"
|
return "Failed"
|
||||||
|
case ResyncPending:
|
||||||
|
return "Pending"
|
||||||
|
case ResyncCanceled:
|
||||||
|
return "Canceled"
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -671,8 +693,8 @@ func (rt ResyncStatusType) String() string {
|
|||||||
|
|
||||||
// TargetReplicationResyncStatus status of resync of bucket for a specific target
|
// TargetReplicationResyncStatus status of resync of bucket for a specific target
|
||||||
type TargetReplicationResyncStatus struct {
|
type TargetReplicationResyncStatus struct {
|
||||||
StartTime time.Time `json:"startTime" msg:"st"`
|
StartTime time.Time `json:"startTime" msg:"st"`
|
||||||
EndTime time.Time `json:"endTime" msg:"et"`
|
LastUpdate time.Time `json:"lastUpdated" msg:"lst"`
|
||||||
// Resync ID assigned to this reset
|
// Resync ID assigned to this reset
|
||||||
ResyncID string `json:"resyncID" msg:"id"`
|
ResyncID string `json:"resyncID" msg:"id"`
|
||||||
// ResyncBeforeDate - resync all objects created prior to this date
|
// ResyncBeforeDate - resync all objects created prior to this date
|
||||||
@@ -701,6 +723,14 @@ type BucketReplicationResyncStatus struct {
|
|||||||
LastUpdate time.Time `json:"lastUpdate" msg:"lu"`
|
LastUpdate time.Time `json:"lastUpdate" msg:"lu"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rs *BucketReplicationResyncStatus) cloneTgtStats() (m map[string]TargetReplicationResyncStatus) {
|
||||||
|
m = make(map[string]TargetReplicationResyncStatus)
|
||||||
|
for arn, st := range rs.TargetsMap {
|
||||||
|
m[arn] = st
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func newBucketResyncStatus(bucket string) BucketReplicationResyncStatus {
|
func newBucketResyncStatus(bucket string) BucketReplicationResyncStatus {
|
||||||
return BucketReplicationResyncStatus{
|
return BucketReplicationResyncStatus{
|
||||||
TargetsMap: make(map[string]TargetReplicationResyncStatus),
|
TargetsMap: make(map[string]TargetReplicationResyncStatus),
|
||||||
@@ -767,9 +797,8 @@ func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getReplicationStatsPath(nodeName string) string {
|
func getReplicationStatsPath() string {
|
||||||
nodeStr := strings.ReplaceAll(nodeName, ":", "_")
|
return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + "replication.stats"
|
||||||
return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + nodeStr + ".stats"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -2117,10 +2117,10 @@ func (z *TargetReplicationResyncStatus) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "StartTime")
|
err = msgp.WrapError(err, "StartTime")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "et":
|
case "lst":
|
||||||
z.EndTime, err = dc.ReadTime()
|
z.LastUpdate, err = dc.ReadTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "EndTime")
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "id":
|
case "id":
|
||||||
@@ -2205,14 +2205,14 @@ func (z *TargetReplicationResyncStatus) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "StartTime")
|
err = msgp.WrapError(err, "StartTime")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "et"
|
// write "lst"
|
||||||
err = en.Append(0xa2, 0x65, 0x74)
|
err = en.Append(0xa3, 0x6c, 0x73, 0x74)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteTime(z.EndTime)
|
err = en.WriteTime(z.LastUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "EndTime")
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "id"
|
// write "id"
|
||||||
@@ -2315,9 +2315,9 @@ func (z *TargetReplicationResyncStatus) MarshalMsg(b []byte) (o []byte, err erro
|
|||||||
// string "st"
|
// string "st"
|
||||||
o = append(o, 0x8b, 0xa2, 0x73, 0x74)
|
o = append(o, 0x8b, 0xa2, 0x73, 0x74)
|
||||||
o = msgp.AppendTime(o, z.StartTime)
|
o = msgp.AppendTime(o, z.StartTime)
|
||||||
// string "et"
|
// string "lst"
|
||||||
o = append(o, 0xa2, 0x65, 0x74)
|
o = append(o, 0xa3, 0x6c, 0x73, 0x74)
|
||||||
o = msgp.AppendTime(o, z.EndTime)
|
o = msgp.AppendTime(o, z.LastUpdate)
|
||||||
// string "id"
|
// string "id"
|
||||||
o = append(o, 0xa2, 0x69, 0x64)
|
o = append(o, 0xa2, 0x69, 0x64)
|
||||||
o = msgp.AppendString(o, z.ResyncID)
|
o = msgp.AppendString(o, z.ResyncID)
|
||||||
@@ -2372,10 +2372,10 @@ func (z *TargetReplicationResyncStatus) UnmarshalMsg(bts []byte) (o []byte, err
|
|||||||
err = msgp.WrapError(err, "StartTime")
|
err = msgp.WrapError(err, "StartTime")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "et":
|
case "lst":
|
||||||
z.EndTime, bts, err = msgp.ReadTimeBytes(bts)
|
z.LastUpdate, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "EndTime")
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "id":
|
case "id":
|
||||||
@@ -2450,7 +2450,7 @@ func (z *TargetReplicationResyncStatus) UnmarshalMsg(bts []byte) (o []byte, err
|
|||||||
|
|
||||||
// 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 *TargetReplicationResyncStatus) Msgsize() (s int) {
|
func (z *TargetReplicationResyncStatus) Msgsize() (s int) {
|
||||||
s = 1 + 3 + msgp.TimeSize + 3 + msgp.TimeSize + 3 + msgp.StringPrefixSize + len(z.ResyncID) + 4 + msgp.TimeSize + 4 + msgp.IntSize + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len(z.Bucket) + 4 + msgp.StringPrefixSize + len(z.Object)
|
s = 1 + 3 + msgp.TimeSize + 4 + msgp.TimeSize + 3 + msgp.StringPrefixSize + len(z.ResyncID) + 4 + msgp.TimeSize + 4 + msgp.IntSize + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len(z.Bucket) + 4 + msgp.StringPrefixSize + len(z.Object)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+431
-309
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ type BucketReplicationStat struct {
|
|||||||
FailedCount int64 `json:"failedReplicationCount"`
|
FailedCount int64 `json:"failedReplicationCount"`
|
||||||
// Replication latency information
|
// Replication latency information
|
||||||
Latency ReplicationLatency `json:"replicationLatency"`
|
Latency ReplicationLatency `json:"replicationLatency"`
|
||||||
|
// bandwidth limit for target
|
||||||
|
BandWidthLimitInBytesPerSecond int64 `json:"limitInBits"`
|
||||||
|
// current bandwidth reported
|
||||||
|
CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BucketReplicationStat) hasReplicationUsage() bool {
|
func (bs *BucketReplicationStat) hasReplicationUsage() bool {
|
||||||
|
|||||||
+55
-5
@@ -89,6 +89,18 @@ func (z *BucketReplicationStat) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "BandWidthLimitInBytesPerSecond":
|
||||||
|
z.BandWidthLimitInBytesPerSecond, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "CurrentBandwidthInBytesPerSecond":
|
||||||
|
z.CurrentBandwidthInBytesPerSecond, err = dc.ReadFloat64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,9 +114,9 @@ func (z *BucketReplicationStat) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 7
|
// map header, size 9
|
||||||
// write "PendingSize"
|
// write "PendingSize"
|
||||||
err = en.Append(0x87, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
err = en.Append(0x89, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -179,15 +191,35 @@ func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Latency", "UploadHistogram")
|
err = msgp.WrapError(err, "Latency", "UploadHistogram")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "BandWidthLimitInBytesPerSecond"
|
||||||
|
err = en.Append(0xbe, 0x42, 0x61, 0x6e, 0x64, 0x57, 0x69, 0x64, 0x74, 0x68, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.BandWidthLimitInBytesPerSecond)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "CurrentBandwidthInBytesPerSecond"
|
||||||
|
err = en.Append(0xd9, 0x20, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteFloat64(z.CurrentBandwidthInBytesPerSecond)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 7
|
// map header, size 9
|
||||||
// string "PendingSize"
|
// string "PendingSize"
|
||||||
o = append(o, 0x87, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
o = append(o, 0x89, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
||||||
o = msgp.AppendInt64(o, z.PendingSize)
|
o = msgp.AppendInt64(o, z.PendingSize)
|
||||||
// string "ReplicatedSize"
|
// string "ReplicatedSize"
|
||||||
o = append(o, 0xae, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65)
|
o = append(o, 0xae, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65)
|
||||||
@@ -214,6 +246,12 @@ func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Latency", "UploadHistogram")
|
err = msgp.WrapError(err, "Latency", "UploadHistogram")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// string "BandWidthLimitInBytesPerSecond"
|
||||||
|
o = append(o, 0xbe, 0x42, 0x61, 0x6e, 0x64, 0x57, 0x69, 0x64, 0x74, 0x68, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||||
|
o = msgp.AppendInt64(o, z.BandWidthLimitInBytesPerSecond)
|
||||||
|
// string "CurrentBandwidthInBytesPerSecond"
|
||||||
|
o = append(o, 0xd9, 0x20, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||||
|
o = msgp.AppendFloat64(o, z.CurrentBandwidthInBytesPerSecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +338,18 @@ func (z *BucketReplicationStat) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "BandWidthLimitInBytesPerSecond":
|
||||||
|
z.BandWidthLimitInBytesPerSecond, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "CurrentBandwidthInBytesPerSecond":
|
||||||
|
z.CurrentBandwidthInBytesPerSecond, bts, err = msgp.ReadFloat64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -314,7 +364,7 @@ func (z *BucketReplicationStat) 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 *BucketReplicationStat) Msgsize() (s int) {
|
func (z *BucketReplicationStat) Msgsize() (s int) {
|
||||||
s = 1 + 12 + msgp.Int64Size + 15 + msgp.Int64Size + 12 + msgp.Int64Size + 11 + msgp.Int64Size + 13 + msgp.Int64Size + 12 + msgp.Int64Size + 8 + 1 + 16 + z.Latency.UploadHistogram.Msgsize()
|
s = 1 + 12 + msgp.Int64Size + 15 + msgp.Int64Size + 12 + msgp.Int64Size + 11 + msgp.Int64Size + 13 + msgp.Int64Size + 12 + msgp.Int64Size + 8 + 1 + 16 + z.Latency.UploadHistogram.Msgsize() + 31 + msgp.Int64Size + 34 + msgp.Float64Size
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-40
@@ -25,9 +25,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
@@ -99,7 +98,7 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
|||||||
select {
|
select {
|
||||||
case <-hcTimer.C:
|
case <-hcTimer.C:
|
||||||
sys.hMutex.RLock()
|
sys.hMutex.RLock()
|
||||||
var eps []madmin.ServerProperties
|
eps := make([]madmin.ServerProperties, 0, len(sys.hc))
|
||||||
for _, ep := range sys.hc {
|
for _, ep := range sys.hc {
|
||||||
eps = append(eps, madmin.ServerProperties{Endpoint: ep.Endpoint, Scheme: ep.Scheme})
|
eps = append(eps, madmin.ServerProperties{Endpoint: ep.Endpoint, Scheme: ep.Scheme})
|
||||||
}
|
}
|
||||||
@@ -107,16 +106,12 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
|||||||
|
|
||||||
if len(eps) > 0 {
|
if len(eps) > 0 {
|
||||||
cctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
cctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||||
m := map[string]epHealth{}
|
m := make(map[string]epHealth, len(eps))
|
||||||
for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) {
|
for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) {
|
||||||
var online bool
|
|
||||||
if result.Error == nil {
|
|
||||||
online = result.Online
|
|
||||||
}
|
|
||||||
m[result.Endpoint.Host] = epHealth{
|
m[result.Endpoint.Host] = epHealth{
|
||||||
Endpoint: result.Endpoint.Host,
|
Endpoint: result.Endpoint.Host,
|
||||||
Scheme: result.Endpoint.Scheme,
|
Scheme: result.Endpoint.Scheme,
|
||||||
Online: online,
|
Online: result.Online,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
@@ -208,9 +203,6 @@ func (sys *BucketTargetSys) Delete(bucket string) {
|
|||||||
|
|
||||||
// SetTarget - sets a new minio-go client target for this bucket.
|
// SetTarget - sets a new minio-go client target for this bucket.
|
||||||
func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget, update bool) error {
|
func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget, update bool) error {
|
||||||
if globalIsGateway {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !tgt.Type.IsValid() && !update {
|
if !tgt.Type.IsValid() && !update {
|
||||||
return BucketRemoteArnTypeInvalid{Bucket: bucket}
|
return BucketRemoteArnTypeInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
@@ -221,10 +213,13 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
// validate if target credentials are ok
|
// validate if target credentials are ok
|
||||||
exists, err := clnt.BucketExists(ctx, tgt.TargetBucket)
|
exists, err := clnt.BucketExists(ctx, tgt.TargetBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
switch minio.ToErrorResponse(err).Code {
|
||||||
|
case "NoSuchBucket":
|
||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
|
case "AccessDenied":
|
||||||
|
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err}
|
||||||
}
|
}
|
||||||
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err}
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
@@ -235,7 +230,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
}
|
}
|
||||||
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err, AccessKey: tgt.Credentials.AccessKey}
|
||||||
}
|
}
|
||||||
if !vcfg.Enabled() {
|
if !vcfg.Enabled() {
|
||||||
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||||
@@ -257,6 +252,10 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
found = true
|
found = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// fail if endpoint is already present in list of targets and not a matching ARN
|
||||||
|
if t.Endpoint == tgt.Endpoint {
|
||||||
|
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newtgts[idx] = t
|
newtgts[idx] = t
|
||||||
}
|
}
|
||||||
@@ -266,29 +265,22 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
|
|
||||||
sys.targetsMap[bucket] = newtgts
|
sys.targetsMap[bucket] = newtgts
|
||||||
sys.arnRemotesMap[tgt.Arn] = clnt
|
sys.arnRemotesMap[tgt.Arn] = clnt
|
||||||
sys.updateBandwidthLimit(bucket, tgt.BandwidthLimit)
|
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *BucketTargetSys) updateBandwidthLimit(bucket string, limit int64) {
|
func (sys *BucketTargetSys) updateBandwidthLimit(bucket, arn string, limit int64) {
|
||||||
if globalIsGateway {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
globalBucketMonitor.DeleteBucket(bucket)
|
globalBucketMonitor.DeleteBucketThrottle(bucket, arn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Setup bandwidth throttling
|
// Setup bandwidth throttling
|
||||||
|
|
||||||
globalBucketMonitor.SetBandwidthLimit(bucket, limit)
|
globalBucketMonitor.SetBandwidthLimit(bucket, arn, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveTarget - removes a remote bucket target for this source bucket.
|
// RemoveTarget - removes a remote bucket target for this source bucket.
|
||||||
func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr string) error {
|
func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr string) error {
|
||||||
if globalIsGateway {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if arnStr == "" {
|
if arnStr == "" {
|
||||||
return BucketRemoteArnInvalid{Bucket: bucket}
|
return BucketRemoteArnInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
@@ -336,7 +328,7 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
}
|
}
|
||||||
sys.targetsMap[bucket] = targets
|
sys.targetsMap[bucket] = targets
|
||||||
delete(sys.arnRemotesMap, arnStr)
|
delete(sys.arnRemotesMap, arnStr)
|
||||||
sys.updateBandwidthLimit(bucket, 0)
|
sys.updateBandwidthLimit(bucket, arnStr, 0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +398,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
|||||||
|
|
||||||
// No need for more if not adding anything
|
// No need for more if not adding anything
|
||||||
if tgts == nil || tgts.Empty() {
|
if tgts == nil || tgts.Empty() {
|
||||||
sys.updateBandwidthLimit(bucket, 0)
|
globalBucketMonitor.DeleteBucket(bucket)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +411,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||||
sys.updateBandwidthLimit(bucket, tgt.BandwidthLimit)
|
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||||
}
|
}
|
||||||
sys.targetsMap[bucket] = tgts.Targets
|
sys.targetsMap[bucket] = tgts.Targets
|
||||||
}
|
}
|
||||||
@@ -442,7 +434,7 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||||
sys.updateBandwidthLimit(bucket.Name, tgt.BandwidthLimit)
|
sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit)
|
||||||
}
|
}
|
||||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
sys.targetsMap[bucket.Name] = cfg.Targets
|
||||||
}
|
}
|
||||||
@@ -452,7 +444,7 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
|
|||||||
config := tcfg.Credentials
|
config := tcfg.Credentials
|
||||||
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
||||||
|
|
||||||
api, err := minio.New(tcfg.Endpoint, &miniogo.Options{
|
api, err := minio.New(tcfg.Endpoint, &minio.Options{
|
||||||
Creds: creds,
|
Creds: creds,
|
||||||
Secure: tcfg.Secure,
|
Secure: tcfg.Secure,
|
||||||
Region: tcfg.Region,
|
Region: tcfg.Region,
|
||||||
@@ -461,6 +453,8 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
api.SetAppInfo("minio-replication-target", ReleaseTag+" "+tcfg.Arn)
|
||||||
|
|
||||||
hcDuration := defaultHealthCheckDuration
|
hcDuration := defaultHealthCheckDuration
|
||||||
if tcfg.HealthCheckDuration >= 1 { // require minimum health check duration of 1 sec.
|
if tcfg.HealthCheckDuration >= 1 { // require minimum health check duration of 1 sec.
|
||||||
hcDuration = tcfg.HealthCheckDuration
|
hcDuration = tcfg.HealthCheckDuration
|
||||||
@@ -481,27 +475,50 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getRemoteARN gets existing ARN for an endpoint or generates a new one.
|
// getRemoteARN gets existing ARN for an endpoint or generates a new one.
|
||||||
func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget) string {
|
func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget, deplID string) (arn string, exists bool) {
|
||||||
if target == nil {
|
if target == nil {
|
||||||
return ""
|
return
|
||||||
}
|
}
|
||||||
tgts := sys.targetsMap[bucket]
|
tgts := sys.targetsMap[bucket]
|
||||||
for _, tgt := range tgts {
|
for _, tgt := range tgts {
|
||||||
if tgt.Type == target.Type && tgt.TargetBucket == target.TargetBucket && target.URL().String() == tgt.URL().String() {
|
if tgt.Type == target.Type &&
|
||||||
return tgt.Arn
|
tgt.TargetBucket == target.TargetBucket &&
|
||||||
|
target.URL().String() == tgt.URL().String() &&
|
||||||
|
tgt.Credentials.AccessKey == target.Credentials.AccessKey {
|
||||||
|
return tgt.Arn, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !target.Type.IsValid() {
|
if !target.Type.IsValid() {
|
||||||
return ""
|
return
|
||||||
}
|
}
|
||||||
return generateARN(target)
|
return generateARN(target, deplID), false
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRemoteARNForPeer returns the remote target for a peer site in site replication
|
||||||
|
func (sys *BucketTargetSys) getRemoteARNForPeer(bucket string, peer madmin.PeerInfo) string {
|
||||||
|
tgts := sys.targetsMap[bucket]
|
||||||
|
for _, target := range tgts {
|
||||||
|
ep, _ := url.Parse(peer.Endpoint)
|
||||||
|
if target.SourceBucket == bucket &&
|
||||||
|
target.TargetBucket == bucket &&
|
||||||
|
target.Endpoint == ep.Host &&
|
||||||
|
target.Secure == (ep.Scheme == "https") &&
|
||||||
|
target.Type == madmin.ReplicationService {
|
||||||
|
return target.Arn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate ARN that is unique to this target type
|
// generate ARN that is unique to this target type
|
||||||
func generateARN(t *madmin.BucketTarget) string {
|
func generateARN(t *madmin.BucketTarget, deplID string) string {
|
||||||
|
uuid := deplID
|
||||||
|
if uuid == "" {
|
||||||
|
uuid = mustGetUUID()
|
||||||
|
}
|
||||||
arn := madmin.ARN{
|
arn := madmin.ARN{
|
||||||
Type: t.Type,
|
Type: t.Type,
|
||||||
ID: mustGetUUID(),
|
ID: uuid,
|
||||||
Region: t.Region,
|
Region: t.Region,
|
||||||
Bucket: t.TargetBucket,
|
Bucket: t.TargetBucket,
|
||||||
}
|
}
|
||||||
@@ -543,7 +560,7 @@ func parseBucketTargetConfig(bucket string, cdata, cmetadata []byte) (*madmin.Bu
|
|||||||
|
|
||||||
// TargetClient is the struct for remote target client.
|
// TargetClient is the struct for remote target client.
|
||||||
type TargetClient struct {
|
type TargetClient struct {
|
||||||
*miniogo.Client
|
*minio.Client
|
||||||
healthCheckDuration time.Duration
|
healthCheckDuration time.Duration
|
||||||
Bucket string // remote bucket target
|
Bucket string // remote bucket target
|
||||||
replicateSync bool
|
replicateSync bool
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"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/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,15 +108,12 @@ func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r
|
|||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||||
// errors.
|
// errors.
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
Type: madmin.SRBucketMetaTypeVersionConfig,
|
Type: madmin.SRBucketMetaTypeVersionConfig,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Versioning: &cfgStr,
|
Versioning: &cfgStr,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}); err != nil {
|
}))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ func (sys *BucketVersioningSys) PrefixSuspended(bucket, prefix string) bool {
|
|||||||
|
|
||||||
// Get returns stored bucket policy
|
// Get returns stored bucket policy
|
||||||
func (sys *BucketVersioningSys) Get(bucket string) (*versioning.Versioning, error) {
|
func (sys *BucketVersioningSys) Get(bucket string) (*versioning.Versioning, error) {
|
||||||
if globalIsGateway {
|
|
||||||
// Gateway does not implement versioning.
|
|
||||||
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if bucket == minioMetaBucket || strings.HasPrefix(bucket, minioMetaBucket) {
|
if bucket == minioMetaBucket || strings.HasPrefix(bucket, minioMetaBucket) {
|
||||||
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, nil
|
return &versioning.Versioning{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/"}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+114
-64
@@ -18,53 +18,28 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
uatomic "go.uber.org/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var callhomeLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||||
// callhomeSchemaVersion1 is callhome schema version 1
|
|
||||||
callhomeSchemaVersion1 = "1"
|
|
||||||
|
|
||||||
// callhomeSchemaVersion is current callhome schema version.
|
|
||||||
callhomeSchemaVersion = callhomeSchemaVersion1
|
|
||||||
|
|
||||||
// callhomeCycleDefault is the default interval between two callhome cycles (24hrs)
|
|
||||||
callhomeCycleDefault = 24 * time.Hour
|
|
||||||
)
|
|
||||||
|
|
||||||
// CallhomeInfo - Contains callhome information
|
|
||||||
type CallhomeInfo struct {
|
|
||||||
SchemaVersion string `json:"schema_version"`
|
|
||||||
AdminInfo madmin.InfoMessage `json:"admin_info"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
enableCallhome = uatomic.NewBool(false)
|
|
||||||
callhomeLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
|
||||||
callhomeFreq = uatomic.NewDuration(callhomeCycleDefault)
|
|
||||||
)
|
|
||||||
|
|
||||||
func updateCallhomeParams(ctx context.Context, objAPI ObjectLayer) {
|
|
||||||
alreadyEnabled := enableCallhome.Load()
|
|
||||||
enableCallhome.Store(globalCallhomeConfig.Enable)
|
|
||||||
callhomeFreq.Store(globalCallhomeConfig.Frequency)
|
|
||||||
|
|
||||||
// If callhome was disabled earlier and has now been enabled,
|
|
||||||
// initialize the callhome process again.
|
|
||||||
if !alreadyEnabled && enableCallhome.Load() {
|
|
||||||
initCallhome(ctx, objAPI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initCallhome will start the callhome task in the background.
|
// initCallhome will start the callhome task in the background.
|
||||||
func initCallhome(ctx context.Context, objAPI ObjectLayer) {
|
func initCallhome(ctx context.Context, objAPI ObjectLayer) {
|
||||||
|
if !globalCallhomeConfig.Enabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
// Leader node (that successfully acquires the lock inside runCallhome)
|
// Leader node (that successfully acquires the lock inside runCallhome)
|
||||||
@@ -72,79 +47,154 @@ func initCallhome(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
// the lock will be released and another node will acquire it and take over
|
// the lock will be released and another node will acquire it and take over
|
||||||
// because of this loop.
|
// because of this loop.
|
||||||
for {
|
for {
|
||||||
runCallhome(ctx, objAPI)
|
if !globalCallhomeConfig.Enabled() {
|
||||||
if !enableCallhome.Load() {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runCallhome(ctx, objAPI) {
|
||||||
|
// callhome was disabled or context was canceled
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// callhome running on a different node.
|
// callhome running on a different node.
|
||||||
// sleep for some time and try again.
|
// sleep for some time and try again.
|
||||||
duration := time.Duration(r.Float64() * float64(callhomeFreq.Load()))
|
duration := time.Duration(r.Float64() * float64(globalCallhomeConfig.FrequencyDur()))
|
||||||
if duration < time.Second {
|
if duration < time.Second {
|
||||||
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
||||||
duration = time.Second
|
duration = time.Second
|
||||||
}
|
}
|
||||||
time.Sleep(duration)
|
time.Sleep(duration)
|
||||||
|
|
||||||
if !enableCallhome.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCallhome(ctx context.Context, objAPI ObjectLayer) {
|
func runCallhome(ctx context.Context, objAPI ObjectLayer) bool {
|
||||||
// Make sure only 1 callhome is running on the cluster.
|
// Make sure only 1 callhome is running on the cluster.
|
||||||
locker := objAPI.NewNSLock(minioMetaBucket, "callhome/runCallhome.lock")
|
locker := objAPI.NewNSLock(minioMetaBucket, "callhome/runCallhome.lock")
|
||||||
lkctx, err := locker.GetLock(ctx, callhomeLeaderLockTimeout)
|
lkctx, err := locker.GetLock(ctx, callhomeLeaderLockTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
// lock timedout means some other node is the leader,
|
||||||
|
// cycle back return 'true'
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer locker.Unlock(lkctx.Cancel)
|
defer locker.Unlock(lkctx)
|
||||||
|
|
||||||
callhomeTimer := time.NewTimer(callhomeFreq.Load())
|
callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur())
|
||||||
defer callhomeTimer.Stop()
|
defer callhomeTimer.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if !globalCallhomeConfig.Enabled() {
|
||||||
|
// Stop the processing as callhome got disabled
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
// indicates that we do not need to run callhome anymore
|
||||||
|
return false
|
||||||
case <-callhomeTimer.C:
|
case <-callhomeTimer.C:
|
||||||
if !enableCallhome.Load() {
|
if !globalCallhomeConfig.Enabled() {
|
||||||
// Stop the processing as callhome got disabled
|
// Stop the processing as callhome got disabled
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
performCallhome(ctx)
|
performCallhome(ctx)
|
||||||
|
|
||||||
// Reset the timer for next cycle.
|
// Reset the timer for next cycle.
|
||||||
callhomeTimer.Reset(callhomeFreq.Load())
|
callhomeTimer.Reset(globalCallhomeConfig.FrequencyDur())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func performCallhome(ctx context.Context) {
|
func performCallhome(ctx context.Context) {
|
||||||
err := sendCallhomeInfo(
|
deadline := 10 * time.Second // Default deadline is 10secs for callhome
|
||||||
CallhomeInfo{
|
objectAPI := newObjectLayerFn()
|
||||||
SchemaVersion: callhomeSchemaVersion,
|
if objectAPI == nil {
|
||||||
AdminInfo: getServerInfo(ctx, nil),
|
logger.LogIf(ctx, errors.New("Callhome: object layer not ready"))
|
||||||
})
|
return
|
||||||
if err != nil {
|
}
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
|
|
||||||
|
healthCtx, healthCancel := context.WithTimeout(ctx, deadline)
|
||||||
|
defer healthCancel()
|
||||||
|
|
||||||
|
healthInfoCh := make(chan madmin.HealthInfo)
|
||||||
|
|
||||||
|
query := url.Values{}
|
||||||
|
for _, k := range madmin.HealthDataTypesList {
|
||||||
|
query.Set(string(k), "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
healthInfo := madmin.HealthInfo{
|
||||||
|
TimeStamp: time.Now().UTC(),
|
||||||
|
Version: madmin.HealthInfoVersion,
|
||||||
|
Minio: madmin.MinioHealthInfo{
|
||||||
|
Info: madmin.MinioInfo{
|
||||||
|
DeploymentID: globalDeploymentID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
go fetchHealthInfo(healthCtx, objectAPI, &query, healthInfoCh, healthInfo)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case hi, hasMore := <-healthInfoCh:
|
||||||
|
if !hasMore {
|
||||||
|
// Received all data. Send to SUBNET and return
|
||||||
|
err := sendHealthInfo(ctx, healthInfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
healthInfo = hi
|
||||||
|
case <-healthCtx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
callhomeURL = "https://subnet.min.io/api/callhome"
|
healthURL = "https://subnet.min.io/api/health/upload"
|
||||||
callhomeURLDev = "http://localhost:9000/api/callhome"
|
healthURLDev = "http://localhost:9000/api/health/upload"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sendCallhomeInfo(ch CallhomeInfo) error {
|
func sendHealthInfo(ctx context.Context, healthInfo madmin.HealthInfo) error {
|
||||||
url := callhomeURL
|
url := healthURL
|
||||||
if globalIsCICD {
|
if globalIsCICD {
|
||||||
url = callhomeURLDev
|
url = healthURLDev
|
||||||
}
|
}
|
||||||
_, err := globalSubnetConfig.Post(url, ch)
|
|
||||||
|
filename := fmt.Sprintf("health_%s.json.gz", UTCNow().Format("20060102150405"))
|
||||||
|
url += "?filename=" + filename
|
||||||
|
|
||||||
|
_, err := globalSubnetConfig.Upload(url, filename, createHealthJSONGzip(ctx, healthInfo))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createHealthJSONGzip(ctx context.Context, healthInfo madmin.HealthInfo) []byte {
|
||||||
|
var b bytes.Buffer
|
||||||
|
gzWriter := gzip.NewWriter(&b)
|
||||||
|
|
||||||
|
header := struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
}{Version: healthInfo.Version}
|
||||||
|
|
||||||
|
enc := json.NewEncoder(gzWriter)
|
||||||
|
if e := enc.Encode(header); e != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Could not encode health info header: %w", e))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if e := enc.Encode(healthInfo); e != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Could not encode health info: %w", e))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gzWriter.Flush()
|
||||||
|
gzWriter.Close()
|
||||||
|
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|||||||
+89
-81
@@ -51,8 +51,8 @@ import (
|
|||||||
consoleCerts "github.com/minio/console/pkg/certs"
|
consoleCerts "github.com/minio/console/pkg/certs"
|
||||||
"github.com/minio/console/restapi"
|
"github.com/minio/console/restapi"
|
||||||
"github.com/minio/console/restapi/operations"
|
"github.com/minio/console/restapi/operations"
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -117,7 +117,8 @@ func init() {
|
|||||||
go func() {
|
go func() {
|
||||||
var t *time.Ticker
|
var t *time.Ticker
|
||||||
if containers {
|
if containers {
|
||||||
t = time.NewTicker(1 * time.Minute)
|
// k8s DNS TTL is 30s (Attempt a refresh only after)
|
||||||
|
t = time.NewTicker(30 * time.Second)
|
||||||
} else {
|
} else {
|
||||||
t = time.NewTicker(10 * time.Minute)
|
t = time.NewTicker(10 * time.Minute)
|
||||||
}
|
}
|
||||||
@@ -134,7 +135,7 @@ func init() {
|
|||||||
|
|
||||||
globalForwarder = handlers.NewForwarder(&handlers.Forwarder{
|
globalForwarder = handlers.NewForwarder(&handlers.Forwarder{
|
||||||
PassHost: true,
|
PassHost: true,
|
||||||
RoundTripper: newGatewayHTTPTransport(1 * time.Hour),
|
RoundTripper: NewHTTPTransportWithTimeout(1 * time.Hour),
|
||||||
Logger: func(err error) {
|
Logger: func(err error) {
|
||||||
if err != nil && !errors.Is(err, context.Canceled) {
|
if err != nil && !errors.Is(err, context.Canceled) {
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
@@ -151,7 +152,7 @@ func init() {
|
|||||||
defaultAWSCredProvider = []credentials.Provider{
|
defaultAWSCredProvider = []credentials.Provider{
|
||||||
&credentials.IAM{
|
&credentials.IAM{
|
||||||
Client: &http.Client{
|
Client: &http.Client{
|
||||||
Transport: NewGatewayHTTPTransport(),
|
Transport: NewHTTPTransport(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,9 @@ func minioConfigToConsoleFeatures() {
|
|||||||
if globalMinioEndpoint != "" {
|
if globalMinioEndpoint != "" {
|
||||||
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
|
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
|
||||||
} else {
|
} else {
|
||||||
os.Setenv("CONSOLE_MINIO_SERVER", getAPIEndpoints()[0])
|
// Explicitly set 127.0.0.1 so Console will automatically bypass TLS verification to the local S3 API.
|
||||||
|
// This will save users from providing a certificate with IP or FQDN SAN that points to the local host.
|
||||||
|
os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort))
|
||||||
}
|
}
|
||||||
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
|
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
|
||||||
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
||||||
@@ -207,15 +210,8 @@ func minioConfigToConsoleFeatures() {
|
|||||||
}
|
}
|
||||||
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
||||||
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
|
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
|
||||||
if globalSubnetConfig.License != "" {
|
|
||||||
os.Setenv("CONSOLE_SUBNET_LICENSE", globalSubnetConfig.License)
|
globalSubnetConfig.ApplyEnv()
|
||||||
}
|
|
||||||
if globalSubnetConfig.APIKey != "" {
|
|
||||||
os.Setenv("CONSOLE_SUBNET_API_KEY", globalSubnetConfig.APIKey)
|
|
||||||
}
|
|
||||||
if globalSubnetConfig.ProxyURL != nil {
|
|
||||||
os.Setenv("CONSOLE_SUBNET_PROXY", globalSubnetConfig.ProxyURL.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
|
func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
|
||||||
@@ -236,6 +232,7 @@ func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
|
|||||||
Userinfo: cfg.ClaimUserinfo,
|
Userinfo: cfg.ClaimUserinfo,
|
||||||
RedirectCallbackDynamic: cfg.RedirectURIDynamic,
|
RedirectCallbackDynamic: cfg.RedirectURIDynamic,
|
||||||
RedirectCallback: callback,
|
RedirectCallback: callback,
|
||||||
|
EndSessionEndpoint: cfg.DiscoveryDoc.EndSessionEndpoint,
|
||||||
RoleArn: cfg.GetRoleArn(),
|
RoleArn: cfg.GetRoleArn(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,22 +303,6 @@ func initConsoleServer() (*restapi.Server, error) {
|
|||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyObjectLayerFeatures(name string, objAPI ObjectLayer) {
|
|
||||||
if strings.HasPrefix(name, "gateway") {
|
|
||||||
if GlobalGatewaySSE.IsSet() && GlobalKMS == nil {
|
|
||||||
uiErr := config.ErrInvalidGWSSEEnvValue(nil).Msg("MINIO_GATEWAY_SSE set but KMS is not configured")
|
|
||||||
logger.Fatal(uiErr, "Unable to start gateway with SSE")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalCompressConfigMu.Lock()
|
|
||||||
if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() {
|
|
||||||
logger.Fatal(errInvalidArgument,
|
|
||||||
"Compression support is requested but '%s' does not support compression", name)
|
|
||||||
}
|
|
||||||
globalCompressConfigMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for updates and print a notification message
|
// Check for updates and print a notification message
|
||||||
func checkUpdate(mode string) {
|
func checkUpdate(mode string) {
|
||||||
updateURL := minioReleaseInfoURL
|
updateURL := minioReleaseInfoURL
|
||||||
@@ -619,13 +600,13 @@ func loadEnvVarsFromFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if env.IsSet(config.EnvKMSSecretKeyFile) {
|
if env.IsSet(kms.EnvKMSSecretKeyFile) {
|
||||||
kmsSecret, err := readFromSecret(env.Get(config.EnvKMSSecretKeyFile, ""))
|
kmsSecret, err := readFromSecret(env.Get(kms.EnvKMSSecretKeyFile, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err, "Unable to read the KMS secret key inherited from secret file")
|
logger.Fatal(err, "Unable to read the KMS secret key inherited from secret file")
|
||||||
}
|
}
|
||||||
if kmsSecret != "" {
|
if kmsSecret != "" {
|
||||||
os.Setenv(config.EnvKMSSecretKey, kmsSecret)
|
os.Setenv(kms.EnvKMSSecretKey, kmsSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +639,7 @@ func handleCommonEnvVars() {
|
|||||||
if !((u.Scheme == "http" || u.Scheme == "https") &&
|
if !((u.Scheme == "http" || u.Scheme == "https") &&
|
||||||
u.Opaque == "" &&
|
u.Opaque == "" &&
|
||||||
!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
|
!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
|
||||||
err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u)
|
err := fmt.Errorf("URL contains unexpected resources, expected URL to be one of http(s)://console.example.com or as a subpath via API endpoint http(s)://minio.example.com/minio format: %v", u)
|
||||||
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable")
|
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable")
|
||||||
}
|
}
|
||||||
globalBrowserRedirectURL = u
|
globalBrowserRedirectURL = u
|
||||||
@@ -793,6 +774,8 @@ func handleCommonEnvVars() {
|
|||||||
logger.Info(color.RedBold(msg))
|
logger.Info(color.RedBold(msg))
|
||||||
}
|
}
|
||||||
globalActiveCred = cred
|
globalActiveCred = cred
|
||||||
|
} else {
|
||||||
|
globalActiveCred = auth.DefaultCredentials
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,20 +783,29 @@ func handleCommonEnvVars() {
|
|||||||
// It depends on KMS env variables and global cli flags.
|
// It depends on KMS env variables and global cli flags.
|
||||||
func handleKMSConfig() {
|
func handleKMSConfig() {
|
||||||
switch {
|
switch {
|
||||||
case env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint):
|
case env.IsSet(kms.EnvKMSSecretKey) && env.IsSet(kms.EnvKESEndpoint):
|
||||||
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint))
|
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKMSSecretKey, kms.EnvKESEndpoint))
|
||||||
}
|
}
|
||||||
|
|
||||||
if env.IsSet(config.EnvKMSSecretKey) {
|
if env.IsSet(kms.EnvKMSSecretKey) {
|
||||||
KMS, err := kms.Parse(env.Get(config.EnvKMSSecretKey, ""))
|
KMS, err := kms.Parse(env.Get(kms.EnvKMSSecretKey, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment")
|
logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment")
|
||||||
}
|
}
|
||||||
GlobalKMS = KMS
|
GlobalKMS = KMS
|
||||||
}
|
}
|
||||||
if env.IsSet(config.EnvKESEndpoint) {
|
if env.IsSet(kms.EnvKESEndpoint) {
|
||||||
|
if env.IsSet(kms.EnvKESAPIKey) {
|
||||||
|
if env.IsSet(kms.EnvKESClientKey) {
|
||||||
|
logger.Fatal(errors.New("ambigious 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("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var endpoints []string
|
var endpoints []string
|
||||||
for _, endpoint := range strings.Split(env.Get(config.EnvKESEndpoint, ""), ",") {
|
for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") {
|
||||||
if strings.TrimSpace(endpoint) == "" {
|
if strings.TrimSpace(endpoint) == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -829,66 +821,82 @@ func handleKMSConfig() {
|
|||||||
endpoints = append(endpoints, strings.Join(lbls, ""))
|
endpoints = append(endpoints, strings.Join(lbls, ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootCAs, err := certs.GetRootCAs(env.Get(config.EnvKESServerCA, globalCertsCADir.Get()))
|
rootCAs, err := certs.GetRootCAs(env.Get(kms.EnvKESServerCA, globalCertsCADir.Get()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(config.EnvKESServerCA, globalCertsCADir.Get())))
|
logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(kms.EnvKESServerCA, globalCertsCADir.Get())))
|
||||||
}
|
}
|
||||||
|
|
||||||
loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) {
|
var kmsConf kms.Config
|
||||||
// Manually load the certificate and private key into memory.
|
if env.IsSet(kms.EnvKESAPIKey) {
|
||||||
// We need to check whether the private key is encrypted, and
|
key, err := kes.ParseAPIKey(env.Get(kms.EnvKESAPIKey, ""))
|
||||||
// if so, decrypt it using the user-provided password.
|
|
||||||
certBytes, err := os.ReadFile(certFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
|
logger.Fatal(err, fmt.Sprintf("Failed to parse KES API key from %q", env.Get(kms.EnvKESAPIKey, "")))
|
||||||
}
|
}
|
||||||
keyBytes, err := os.ReadFile(keyFile)
|
kmsConf = kms.Config{
|
||||||
if err != nil {
|
Endpoints: endpoints,
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err)
|
Enclave: env.Get(kms.EnvKESEnclave, ""),
|
||||||
|
DefaultKeyID: env.Get(kms.EnvKESKeyName, ""),
|
||||||
|
APIKey: key,
|
||||||
|
RootCAs: rootCAs,
|
||||||
}
|
}
|
||||||
privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes))
|
} else {
|
||||||
if len(rest) != 0 {
|
loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) {
|
||||||
return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data")
|
// Manually load the certificate and private key into memory.
|
||||||
}
|
// We need to check whether the private key is encrypted, and
|
||||||
if x509.IsEncryptedPEMBlock(privateKeyPEM) {
|
// if so, decrypt it using the user-provided password.
|
||||||
keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(config.EnvKESClientPassword, "")))
|
certBytes, err := os.ReadFile(certFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err)
|
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
|
||||||
}
|
}
|
||||||
keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes})
|
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
|
||||||
}
|
}
|
||||||
certificate, err := tls.X509KeyPair(certBytes, keyBytes)
|
|
||||||
|
reloadCertEvents := make(chan tls.Certificate, 1)
|
||||||
|
certificate, err := certs.NewCertificate(env.Get(kms.EnvKESClientCert, ""), env.Get(kms.EnvKESClientKey, ""), loadX509KeyPair)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
|
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,
|
||||||
|
Enclave: env.Get(kms.EnvKESEnclave, ""),
|
||||||
|
DefaultKeyID: env.Get(kms.EnvKESKeyName, ""),
|
||||||
|
Certificate: certificate,
|
||||||
|
ReloadCertEvents: reloadCertEvents,
|
||||||
|
RootCAs: rootCAs,
|
||||||
}
|
}
|
||||||
return certificate, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadCertEvents := make(chan tls.Certificate, 1)
|
KMS, err := kms.NewWithConfig(kmsConf)
|
||||||
certificate, err := certs.NewCertificate(env.Get(config.EnvKESClientCert, ""), env.Get(config.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)
|
|
||||||
|
|
||||||
defaultKeyID := env.Get(config.EnvKESKeyName, "")
|
|
||||||
KMS, err := kms.NewWithConfig(kms.Config{
|
|
||||||
Endpoints: endpoints,
|
|
||||||
DefaultKeyID: defaultKeyID,
|
|
||||||
Certificate: certificate,
|
|
||||||
ReloadCertEvents: reloadCertEvents,
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
|
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We check that the default key ID exists or try to create it otherwise.
|
// We check that the default key ID exists or try to create it otherwise.
|
||||||
// This implicitly checks that we can communicate to KES. We don't treat
|
// 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
|
// a policy error as failure condition since MinIO may not have the permission
|
||||||
// to create keys - just to generate/decrypt data encryption keys.
|
// to create keys - just to generate/decrypt data encryption keys.
|
||||||
if err = KMS.CreateKey(context.Background(), defaultKeyID); err != nil && !errors.Is(err, kes.ErrKeyExists) && !errors.Is(err, kes.ErrNotAllowed) {
|
if err = KMS.CreateKey(context.Background(), 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")
|
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
|
||||||
}
|
}
|
||||||
GlobalKMS = KMS
|
GlobalKMS = KMS
|
||||||
|
|||||||
+14
-6
@@ -29,10 +29,14 @@ import (
|
|||||||
|
|
||||||
var errConfigNotFound = errors.New("config file not found")
|
var errConfigNotFound = errors.New("config file not found")
|
||||||
|
|
||||||
func readConfigWithMetadata(ctx context.Context, store objectIO, configFile string) ([]byte, ObjectInfo, error) {
|
func readConfigWithMetadata(ctx context.Context, store objectIO, configFile string, opts ObjectOptions) ([]byte, ObjectInfo, error) {
|
||||||
r, err := store.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, readLock, ObjectOptions{})
|
lockType := readLock
|
||||||
|
if opts.NoLock {
|
||||||
|
lockType = noLock // erasureObjects.GetObjectNInfo honors lockType argument but not opts.NoLock.
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := store.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, lockType, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Treat object not found as config not found.
|
|
||||||
if isErrObjectNotFound(err) {
|
if isErrObjectNotFound(err) {
|
||||||
return nil, ObjectInfo{}, errConfigNotFound
|
return nil, ObjectInfo{}, errConfigNotFound
|
||||||
}
|
}
|
||||||
@@ -52,7 +56,7 @@ func readConfigWithMetadata(ctx context.Context, store objectIO, configFile stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readConfig(ctx context.Context, store objectIO, configFile string) ([]byte, error) {
|
func readConfig(ctx context.Context, store objectIO, configFile string) ([]byte, error) {
|
||||||
buf, _, err := readConfigWithMetadata(ctx, store, configFile)
|
buf, _, err := readConfigWithMetadata(ctx, store, configFile, ObjectOptions{})
|
||||||
return buf, err
|
return buf, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,16 +74,20 @@ func deleteConfig(ctx context.Context, objAPI objectDeleter, configFile string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveConfig(ctx context.Context, store objectIO, configFile string, data []byte) error {
|
func saveConfigWithOpts(ctx context.Context, store objectIO, configFile string, data []byte, opts ObjectOptions) error {
|
||||||
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
|
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = store.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader), ObjectOptions{MaxParity: true})
|
_, err = store.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader), opts)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveConfig(ctx context.Context, store objectIO, configFile string, data []byte) error {
|
||||||
|
return saveConfigWithOpts(ctx, store, configFile, data, ObjectOptions{MaxParity: true})
|
||||||
|
}
|
||||||
|
|
||||||
func checkConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error {
|
func checkConfig(ctx context.Context, objAPI ObjectLayer, configFile string) error {
|
||||||
if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile, ObjectOptions{}); err != nil {
|
if _, err := objAPI.GetObjectInfo(ctx, minioMetaBucket, configFile, ObjectOptions{}); err != nil {
|
||||||
// Treat object not found as config not found.
|
// Treat object not found as config not found.
|
||||||
|
|||||||
+104
-141
@@ -24,8 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/auth"
|
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/api"
|
"github.com/minio/minio/internal/config/api"
|
||||||
"github.com/minio/minio/internal/config/cache"
|
"github.com/minio/minio/internal/config/cache"
|
||||||
@@ -65,7 +64,6 @@ func initHelp() {
|
|||||||
config.SiteSubSys: config.DefaultSiteKVS,
|
config.SiteSubSys: config.DefaultSiteKVS,
|
||||||
config.RegionSubSys: config.DefaultRegionKVS,
|
config.RegionSubSys: config.DefaultRegionKVS,
|
||||||
config.APISubSys: api.DefaultKVS,
|
config.APISubSys: api.DefaultKVS,
|
||||||
config.CredentialsSubSys: config.DefaultCredentialKVS,
|
|
||||||
config.LoggerWebhookSubSys: logger.DefaultLoggerWebhookKVS,
|
config.LoggerWebhookSubSys: logger.DefaultLoggerWebhookKVS,
|
||||||
config.AuditWebhookSubSys: logger.DefaultAuditWebhookKVS,
|
config.AuditWebhookSubSys: logger.DefaultAuditWebhookKVS,
|
||||||
config.AuditKafkaSubSys: logger.DefaultAuditKafkaKVS,
|
config.AuditKafkaSubSys: logger.DefaultAuditKafkaKVS,
|
||||||
@@ -84,22 +82,34 @@ func initHelp() {
|
|||||||
|
|
||||||
// Captures help for each sub-system
|
// Captures help for each sub-system
|
||||||
helpSubSys := config.HelpKVS{
|
helpSubSys := config.HelpKVS{
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.SubnetSubSys,
|
||||||
|
Type: "string",
|
||||||
|
Description: "register the cluster to MinIO SUBNET",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.CallhomeSubSys,
|
||||||
|
Type: "string",
|
||||||
|
Description: "enable callhome to MinIO SUBNET",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.SiteSubSys,
|
Key: config.SiteSubSys,
|
||||||
Description: "label the server and its location",
|
Description: "label the server and its location",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.CacheSubSys,
|
Key: config.APISubSys,
|
||||||
Description: "add caching storage tier",
|
Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.ScannerSubSys,
|
||||||
|
Description: "manage namespace scanning for usage calculation, lifecycle, healing and more",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.CompressionSubSys,
|
Key: config.CompressionSubSys,
|
||||||
Description: "enable server side compression of objects",
|
Description: "enable server side compression of objects",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
|
||||||
Key: config.EtcdSubSys,
|
|
||||||
Description: "federate multiple clusters for IAM and Bucket DNS",
|
|
||||||
},
|
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.IdentityOpenIDSubSys,
|
Key: config.IdentityOpenIDSubSys,
|
||||||
Description: "enable OpenID SSO support",
|
Description: "enable OpenID SSO support",
|
||||||
@@ -121,14 +131,6 @@ func initHelp() {
|
|||||||
Key: config.PolicyPluginSubSys,
|
Key: config.PolicyPluginSubSys,
|
||||||
Description: "enable Access Management Plugin for policy enforcement",
|
Description: "enable Access Management Plugin for policy enforcement",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
|
||||||
Key: config.APISubSys,
|
|
||||||
Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
|
|
||||||
},
|
|
||||||
config.HelpKV{
|
|
||||||
Key: config.ScannerSubSys,
|
|
||||||
Description: "manage namespace scanning for usage calculation, lifecycle, healing and more",
|
|
||||||
},
|
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.LoggerWebhookSubSys,
|
Key: config.LoggerWebhookSubSys,
|
||||||
Description: "send server logs to webhook endpoints",
|
Description: "send server logs to webhook endpoints",
|
||||||
@@ -195,16 +197,12 @@ func initHelp() {
|
|||||||
MultipleTargets: true,
|
MultipleTargets: true,
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.SubnetSubSys,
|
Key: config.EtcdSubSys,
|
||||||
Type: "string",
|
Description: "persist IAM assets externally to etcd",
|
||||||
Description: "set subnet config for the cluster e.g. api key",
|
|
||||||
Optional: true,
|
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.CallhomeSubSys,
|
Key: config.CacheSubSys,
|
||||||
Type: "string",
|
Description: "[DEPRECATED] add caching storage tier",
|
||||||
Description: "enable callhome for the cluster",
|
|
||||||
Optional: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,16 +297,9 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case config.CompressionSubSys:
|
case config.CompressionSubSys:
|
||||||
compCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if objAPI != nil {
|
|
||||||
if compCfg.Enabled && !objAPI.IsCompressionSupported() {
|
|
||||||
return fmt.Errorf("Backend does not support compression")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case config.HealSubSys:
|
case config.HealSubSys:
|
||||||
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -331,7 +322,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
|||||||
}
|
}
|
||||||
case config.IdentityOpenIDSubSys:
|
case config.IdentityOpenIDSubSys:
|
||||||
if _, err := openid.LookupConfig(s,
|
if _, err := openid.LookupConfig(s,
|
||||||
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
|
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case config.IdentityLDAPSubSys:
|
case config.IdentityLDAPSubSys:
|
||||||
@@ -352,7 +343,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
|||||||
}
|
}
|
||||||
case config.IdentityPluginSubSys:
|
case config.IdentityPluginSubSys:
|
||||||
if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default],
|
if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default],
|
||||||
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
|
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case config.SubnetSubSys:
|
case config.SubnetSubSys:
|
||||||
@@ -360,22 +351,26 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case config.CallhomeSubSys:
|
case config.CallhomeSubSys:
|
||||||
if _, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default]); err != nil {
|
cfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// callhome cannot be enabled if license is not registered yet, throw an error.
|
||||||
|
if cfg.Enabled() && !globalSubnetConfig.Registered() {
|
||||||
|
return errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
|
||||||
|
}
|
||||||
case config.PolicyOPASubSys:
|
case config.PolicyOPASubSys:
|
||||||
// In case legacy OPA config is being set, we treat it as if the
|
// In case legacy OPA config is being set, we treat it as if the
|
||||||
// AuthZPlugin is being set.
|
// AuthZPlugin is being set.
|
||||||
subSys = config.PolicyPluginSubSys
|
subSys = config.PolicyPluginSubSys
|
||||||
fallthrough
|
fallthrough
|
||||||
case config.PolicyPluginSubSys:
|
case config.PolicyPluginSubSys:
|
||||||
if ppargs, err := polplugin.LookupConfig(s[config.PolicyPluginSubSys][config.Default],
|
if ppargs, err := polplugin.LookupConfig(s, GetDefaultConnSettings(), xhttp.DrainBody); err != nil {
|
||||||
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {
|
|
||||||
return err
|
return err
|
||||||
} else if ppargs.URL == nil {
|
} else if ppargs.URL == nil {
|
||||||
// Check if legacy opa is configured.
|
// Check if legacy opa is configured.
|
||||||
if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default],
|
if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default],
|
||||||
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {
|
NewHTTPTransport(), xhttp.DrainBody); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +383,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.NotifySubSystems.Contains(subSys) {
|
if config.NotifySubSystems.Contains(subSys) {
|
||||||
if err := notify.TestSubSysNotificationTargets(GlobalContext, s, subSys, NewGatewayHTTPTransport()); err != nil {
|
if err := notify.TestSubSysNotificationTargets(GlobalContext, s, subSys, NewHTTPTransport()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,53 +418,28 @@ func validateConfig(s config.Config, subSys string) error {
|
|||||||
func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
||||||
ctx := GlobalContext
|
ctx := GlobalContext
|
||||||
|
|
||||||
var err error
|
|
||||||
if !globalActiveCred.IsValid() {
|
|
||||||
// Env doesn't seem to be set, we fallback to lookup creds from the config.
|
|
||||||
globalActiveCred, err = config.LookupCreds(s[config.CredentialsSubSys][config.Default])
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Invalid credentials configuration: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsURL, dnsUser, dnsPass, err := env.LookupEnv(config.EnvDNSWebhook)
|
dnsURL, dnsUser, dnsPass, err := env.LookupEnv(config.EnvDNSWebhook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalIsGateway {
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
|
||||||
logger.FatalIf(err, "Unable to initialize remote webhook DNS config")
|
|
||||||
} else {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err == nil && dnsURL != "" {
|
if err == nil && dnsURL != "" {
|
||||||
globalDNSConfig, err = dns.NewOperatorDNS(dnsURL,
|
globalDNSConfig, err = dns.NewOperatorDNS(dnsURL,
|
||||||
dns.Authentication(dnsUser, dnsPass),
|
dns.Authentication(dnsUser, dnsPass),
|
||||||
dns.RootCAs(globalRootCAs))
|
dns.RootCAs(globalRootCAs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalIsGateway {
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
|
||||||
logger.FatalIf(err, "Unable to initialize remote webhook DNS config")
|
|
||||||
} else {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs)
|
etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalIsGateway {
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
|
||||||
logger.FatalIf(err, "Unable to initialize etcd config")
|
|
||||||
} else {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if etcdCfg.Enabled {
|
if etcdCfg.Enabled {
|
||||||
globalEtcdClient, err = etcd.New(etcdCfg)
|
globalEtcdClient, err = etcd.New(etcdCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalIsGateway {
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
|
||||||
logger.FatalIf(err, "Unable to initialize etcd config")
|
|
||||||
} else {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize etcd config: %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
|
if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
|
||||||
@@ -485,12 +455,8 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
|||||||
dns.CoreDNSPath(etcdCfg.CoreDNSPath),
|
dns.CoreDNSPath(etcdCfg.CoreDNSPath),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalIsGateway {
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize DNS config for %s: %w",
|
||||||
logger.FatalIf(err, "Unable to initialize DNS config")
|
globalDomainNames, err))
|
||||||
} else {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize DNS config for %s: %w",
|
|
||||||
globalDomainNames, err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -510,11 +476,7 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
globalCacheConfig, err = cache.LookupConfig(s[config.CacheSubSys][config.Default])
|
globalCacheConfig, err = cache.LookupConfig(s[config.CacheSubSys][config.Default])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalIsGateway {
|
logger.LogIf(ctx, fmt.Errorf("Unable to setup cache: %w", err))
|
||||||
logger.FatalIf(err, "Unable to setup cache")
|
|
||||||
} else {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup cache: %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalCacheConfig.Enabled {
|
if globalCacheConfig.Enabled {
|
||||||
@@ -540,25 +502,18 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
|||||||
logger.LogIf(ctx, fmt.Errorf("CRITICAL: enabling %s is not recommended in a production environment", xtls.EnvIdentityTLSSkipVerify))
|
logger.LogIf(ctx, fmt.Errorf("CRITICAL: enabling %s is not recommended in a production environment", xtls.EnvIdentityTLSSkipVerify))
|
||||||
}
|
}
|
||||||
|
|
||||||
globalSubnetConfig, err = subnet.LookupConfig(s[config.SubnetSubSys][config.Default], globalProxyTransport)
|
transport := NewHTTPTransport()
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to parse subnet configuration: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := NewGatewayHTTPTransport()
|
|
||||||
|
|
||||||
|
bootstrapTrace("lookup the event notification targets")
|
||||||
globalConfigTargetList, err = notify.FetchEnabledTargets(GlobalContext, s, transport)
|
globalConfigTargetList, err = notify.FetchEnabledTargets(GlobalContext, s, transport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bootstrapTrace("applying the dynamic configuration")
|
||||||
// Apply dynamic config values
|
// Apply dynamic config values
|
||||||
if err := applyDynamicConfig(ctx, objAPI, s); err != nil {
|
if err := applyDynamicConfig(ctx, objAPI, s); err != nil {
|
||||||
if globalIsGateway {
|
logger.LogIf(ctx, err)
|
||||||
logger.FatalIf(err, "Unable to initialize dynamic configuration")
|
|
||||||
} else {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,17 +534,13 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
|
|
||||||
// Initialize remote instance transport once.
|
// Initialize remote instance transport once.
|
||||||
getRemoteInstanceTransportOnce.Do(func() {
|
getRemoteInstanceTransportOnce.Do(func() {
|
||||||
getRemoteInstanceTransport = newGatewayHTTPTransport(apiConfig.RemoteTransportDeadline)
|
getRemoteInstanceTransport = NewHTTPTransportWithTimeout(apiConfig.RemoteTransportDeadline)
|
||||||
})
|
})
|
||||||
case config.CompressionSubSys:
|
case config.CompressionSubSys:
|
||||||
cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to setup Compression: %w", err)
|
return fmt.Errorf("Unable to setup Compression: %w", err)
|
||||||
}
|
}
|
||||||
// Validate if the object layer supports compression.
|
|
||||||
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
|
||||||
return fmt.Errorf("Backend does not support compression")
|
|
||||||
}
|
|
||||||
globalCompressConfigMu.Lock()
|
globalCompressConfigMu.Lock()
|
||||||
globalCompressConfig = cmpCfg
|
globalCompressConfig = cmpCfg
|
||||||
globalCompressConfigMu.Unlock()
|
globalCompressConfigMu.Unlock()
|
||||||
@@ -617,12 +568,12 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
if l.Enabled {
|
if l.Enabled {
|
||||||
l.LogOnce = logger.LogOnceConsoleIf
|
l.LogOnce = logger.LogOnceConsoleIf
|
||||||
l.UserAgent = userAgent
|
l.UserAgent = userAgent
|
||||||
l.Transport = NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
l.Transport = NewHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
||||||
loggerCfg.HTTP[n] = l
|
|
||||||
}
|
}
|
||||||
|
loggerCfg.HTTP[n] = l
|
||||||
}
|
}
|
||||||
if err = logger.UpdateSystemTargets(loggerCfg); err != nil {
|
if errs := logger.UpdateSystemTargets(loggerCfg); len(errs) > 0 {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to update logger webhook config: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to update logger webhook config: %v", errs))
|
||||||
}
|
}
|
||||||
case config.AuditWebhookSubSys:
|
case config.AuditWebhookSubSys:
|
||||||
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditWebhookSubSys)
|
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditWebhookSubSys)
|
||||||
@@ -634,13 +585,13 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
if l.Enabled {
|
if l.Enabled {
|
||||||
l.LogOnce = logger.LogOnceConsoleIf
|
l.LogOnce = logger.LogOnceConsoleIf
|
||||||
l.UserAgent = userAgent
|
l.UserAgent = userAgent
|
||||||
l.Transport = NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
l.Transport = NewHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
||||||
loggerCfg.AuditWebhook[n] = l
|
|
||||||
}
|
}
|
||||||
|
loggerCfg.AuditWebhook[n] = l
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = logger.UpdateAuditWebhookTargets(loggerCfg); err != nil {
|
if errs := logger.UpdateAuditWebhookTargets(loggerCfg); len(errs) > 0 {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to update audit webhook targets: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to update audit webhook targets: %v", errs))
|
||||||
}
|
}
|
||||||
case config.AuditKafkaSubSys:
|
case config.AuditKafkaSubSys:
|
||||||
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditKafkaSubSys)
|
loggerCfg, err := logger.LookupConfigForSubSys(s, config.AuditKafkaSubSys)
|
||||||
@@ -653,8 +604,8 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
loggerCfg.AuditKafka[n] = l
|
loggerCfg.AuditKafka[n] = l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = logger.UpdateAuditKafkaTargets(loggerCfg); err != nil {
|
if errs := logger.UpdateAuditKafkaTargets(loggerCfg); len(errs) > 0 {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to update audit kafka targets: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to update audit kafka targets: %v", errs))
|
||||||
}
|
}
|
||||||
case config.StorageClassSubSys:
|
case config.StorageClassSubSys:
|
||||||
for i, setDriveCount := range setDriveCounts {
|
for i, setDriveCount := range setDriveCounts {
|
||||||
@@ -669,13 +620,24 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
|||||||
globalStorageClass.Update(sc)
|
globalStorageClass.Update(sc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case config.SubnetSubSys:
|
||||||
|
subnetConfig, err := subnet.LookupConfig(s[config.SubnetSubSys][config.Default], globalProxyTransport)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to parse subnet configuration: %w", err))
|
||||||
|
} else {
|
||||||
|
globalSubnetConfig.Update(subnetConfig)
|
||||||
|
globalSubnetConfig.ApplyEnv() // update environment settings for Console UI
|
||||||
|
}
|
||||||
case config.CallhomeSubSys:
|
case config.CallhomeSubSys:
|
||||||
callhomeCfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default])
|
callhomeCfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err))
|
||||||
} else {
|
} else {
|
||||||
globalCallhomeConfig = callhomeCfg
|
enable := callhomeCfg.Enable && !globalCallhomeConfig.Enabled()
|
||||||
updateCallhomeParams(ctx, objAPI)
|
globalCallhomeConfig.Update(callhomeCfg)
|
||||||
|
if enable {
|
||||||
|
initCallhome(ctx, objAPI)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalServerConfigMu.Lock()
|
globalServerConfigMu.Lock()
|
||||||
@@ -739,37 +701,42 @@ func GetHelp(subSys, key string, envOnly bool) (Help, error) {
|
|||||||
h = config.HelpKVS{value}
|
h = config.HelpKVS{value}
|
||||||
}
|
}
|
||||||
|
|
||||||
envHelp := config.HelpKVS{}
|
help := config.HelpKVS{}
|
||||||
if envOnly {
|
|
||||||
// Only for multiple targets, make sure
|
// Only for multiple targets, make sure
|
||||||
// to list the ENV, for regular k/v EnableKey is
|
// to list the ENV, for regular k/v EnableKey is
|
||||||
// implicit, for ENVs we cannot make it implicit.
|
// implicit, for ENVs we cannot make it implicit.
|
||||||
if subSysHelp.MultipleTargets {
|
if subSysHelp.MultipleTargets {
|
||||||
envK := config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(madmin.EnableKey)
|
key := madmin.EnableKey
|
||||||
envHelp = append(envHelp, config.HelpKV{
|
if envOnly {
|
||||||
Key: envK,
|
key = config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(madmin.EnableKey)
|
||||||
Description: fmt.Sprintf("enable %s target, default is 'off'", subSys),
|
|
||||||
Optional: false,
|
|
||||||
Type: "on|off",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
for _, hkv := range h {
|
help = append(help, config.HelpKV{
|
||||||
envK := config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(hkv.Key)
|
Key: key,
|
||||||
envHelp = append(envHelp, config.HelpKV{
|
Description: fmt.Sprintf("enable %s target, default is 'off'", subSys),
|
||||||
Key: envK,
|
Optional: false,
|
||||||
Description: hkv.Description,
|
Type: "on|off",
|
||||||
Optional: hkv.Optional,
|
})
|
||||||
Type: hkv.Type,
|
}
|
||||||
})
|
|
||||||
|
for _, hkv := range h {
|
||||||
|
key := hkv.Key
|
||||||
|
if envOnly {
|
||||||
|
key = config.EnvPrefix + strings.ToTitle(subSys) + config.EnvWordDelimiter + strings.ToTitle(hkv.Key)
|
||||||
}
|
}
|
||||||
h = envHelp
|
help = append(help, config.HelpKV{
|
||||||
|
Key: key,
|
||||||
|
Description: hkv.Description,
|
||||||
|
Optional: hkv.Optional,
|
||||||
|
Type: hkv.Type,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return Help{
|
return Help{
|
||||||
SubSys: subSys,
|
SubSys: subSys,
|
||||||
Description: subSysHelp.Description,
|
Description: subSysHelp.Description,
|
||||||
MultipleTargets: subSysHelp.MultipleTargets,
|
MultipleTargets: subSysHelp.MultipleTargets,
|
||||||
KeysHelp: h,
|
KeysHelp: help,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,13 +750,6 @@ func newSrvConfig(objAPI ObjectLayer) error {
|
|||||||
// Initialize server config.
|
// Initialize server config.
|
||||||
srvCfg := newServerConfig()
|
srvCfg := newServerConfig()
|
||||||
|
|
||||||
if globalActiveCred.IsValid() && !globalActiveCred.Equal(auth.DefaultCredentials) {
|
|
||||||
kvs := srvCfg[config.CredentialsSubSys][config.Default]
|
|
||||||
kvs.Set(config.AccessKey, globalActiveCred.AccessKey)
|
|
||||||
kvs.Set(config.SecretKey, globalActiveCred.SecretKey)
|
|
||||||
srvCfg[config.CredentialsSubSys][config.Default] = kvs
|
|
||||||
}
|
|
||||||
|
|
||||||
// hold the mutex lock before a new config is assigned.
|
// hold the mutex lock before a new config is assigned.
|
||||||
globalServerConfigMu.Lock()
|
globalServerConfigMu.Lock()
|
||||||
globalServerConfig = srvCfg
|
globalServerConfig = srvCfg
|
||||||
@@ -800,17 +760,20 @@ func newSrvConfig(objAPI ObjectLayer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getValidConfig(objAPI ObjectLayer) (config.Config, error) {
|
func getValidConfig(objAPI ObjectLayer) (config.Config, error) {
|
||||||
return readServerConfig(GlobalContext, objAPI)
|
return readServerConfig(GlobalContext, objAPI, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadConfig - loads a new config from disk, overrides params
|
// loadConfig - loads a new config from disk, overrides params
|
||||||
// from env if found and valid
|
// from env if found and valid
|
||||||
func loadConfig(objAPI ObjectLayer) error {
|
// data is optional. If nil it will be loaded from backend.
|
||||||
srvCfg, err := getValidConfig(objAPI)
|
func loadConfig(objAPI ObjectLayer, data []byte) error {
|
||||||
|
bootstrapTrace("load the configuration")
|
||||||
|
srvCfg, err := readServerConfig(GlobalContext, objAPI, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bootstrapTrace("lookup the configuration")
|
||||||
// Override any values from ENVs.
|
// Override any values from ENVs.
|
||||||
lookupConfigs(srvCfg, objAPI)
|
lookupConfigs(srvCfg, objAPI)
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func TestServerConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize server config.
|
// Initialize server config.
|
||||||
if err := loadConfig(objLayer); err != nil {
|
if err := loadConfig(objLayer, nil); err != nil {
|
||||||
t.Fatalf("Unable to initialize from updated config file %s", err)
|
t.Fatalf("Unable to initialize from updated config file %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-10
@@ -25,7 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"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"
|
||||||
@@ -37,7 +37,10 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to encrypt config %w", err)
|
return fmt.Errorf("Unable to encrypt config %w", err)
|
||||||
}
|
}
|
||||||
if err = migrateConfigPrefixToEncrypted(objAPI, encrypted); err != nil {
|
if !encrypted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err = migrateConfigPrefixToEncrypted(objAPI); err != nil {
|
||||||
return fmt.Errorf("Unable to migrate all config at .minio.sys/config/: %w", err)
|
return fmt.Errorf("Unable to migrate all config at .minio.sys/config/: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -48,6 +51,7 @@ const backendEncryptedFile = "backend-encrypted"
|
|||||||
var backendEncryptedMigrationComplete = []byte("encrypted")
|
var backendEncryptedMigrationComplete = []byte("encrypted")
|
||||||
|
|
||||||
func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) {
|
func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) {
|
||||||
|
bootstrapTrace("check if etcd backend is encrypted")
|
||||||
data, err := readKeyEtcd(ctx, client, backendEncryptedFile)
|
data, err := readKeyEtcd(ctx, client, backendEncryptedFile)
|
||||||
if err != nil && err != errConfigNotFound {
|
if err != nil && err != errConfigNotFound {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -56,6 +60,7 @@ func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) {
|
func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) {
|
||||||
|
bootstrapTrace("check if the config backend is encrypted")
|
||||||
data, err := readConfig(GlobalContext, objAPI, backendEncryptedFile)
|
data, err := readConfig(GlobalContext, objAPI, backendEncryptedFile)
|
||||||
if err != nil && err != errConfigNotFound {
|
if err != nil && err != errConfigNotFound {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -75,7 +80,9 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if encrypted && GlobalKMS != nil {
|
bootstrapTrace("encrypt etcd config")
|
||||||
|
|
||||||
|
if GlobalKMS != nil {
|
||||||
stat, err := GlobalKMS.Stat(ctx)
|
stat, err := GlobalKMS.Stat(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -136,18 +143,16 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if encrypted && GlobalKMS != nil {
|
if GlobalKMS != nil {
|
||||||
logger.Info("Migration of encrypted IAM config data completed. All data is now encrypted on etcd.")
|
logger.Info("Migration of encrypted IAM config data completed. All data is now encrypted on etcd.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteKeyEtcd(ctx, client, backendEncryptedFile)
|
return deleteKeyEtcd(ctx, client, backendEncryptedFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error {
|
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer) error {
|
||||||
if !encrypted {
|
bootstrapTrace("migrating config prefix to encrypted")
|
||||||
return nil
|
if GlobalKMS != nil {
|
||||||
}
|
|
||||||
if encrypted && GlobalKMS != nil {
|
|
||||||
stat, err := GlobalKMS.Stat(context.Background())
|
stat, err := GlobalKMS.Stat(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -202,7 +207,7 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if encrypted && GlobalKMS != nil {
|
if GlobalKMS != nil {
|
||||||
logger.Info("Migration of encrypted config data completed. All config data is now encrypted.")
|
logger.Info("Migration of encrypted config data completed. All config data is now encrypted.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+25
-16
@@ -27,7 +27,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/cache"
|
"github.com/minio/minio/internal/config/cache"
|
||||||
@@ -46,7 +46,7 @@ import (
|
|||||||
"github.com/minio/pkg/quick"
|
"github.com/minio/pkg/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DO NOT EDIT following message template, please open a GitHub issue to discuss instead.
|
// Do not edit following message template, please open a GitHub issue to discuss instead.
|
||||||
var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully."
|
var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully."
|
||||||
|
|
||||||
// Save config file to corresponding backend
|
// Save config file to corresponding backend
|
||||||
@@ -2425,6 +2425,7 @@ func migrateV27ToV28() error {
|
|||||||
// Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/config.json'
|
// Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/config.json'
|
||||||
// if etcd is configured then migrates /config/config.json to '<export_path>/.minio.sys/config/config.json'
|
// if etcd is configured then migrates /config/config.json to '<export_path>/.minio.sys/config/config.json'
|
||||||
func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
||||||
|
bootstrapTrace("migrate config to .minio.sys/config/config.json")
|
||||||
// Construct path to config.json for the given bucket.
|
// Construct path to config.json for the given bucket.
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||||
|
|
||||||
@@ -2472,6 +2473,7 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
|
|||||||
|
|
||||||
// Migrates '.minio.sys/config.json' to v33.
|
// Migrates '.minio.sys/config.json' to v33.
|
||||||
func migrateMinioSysConfig(objAPI ObjectLayer) error {
|
func migrateMinioSysConfig(objAPI ObjectLayer) error {
|
||||||
|
bootstrapTrace("migrate .minio.sys/config/config.json to latest version")
|
||||||
// Construct path to config.json for the given bucket.
|
// Construct path to config.json for the given bucket.
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||||
|
|
||||||
@@ -2513,26 +2515,34 @@ func checkConfigVersion(objAPI ObjectLayer, configFile string, version string) (
|
|||||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||||
minioMetaBucket: path.Join(minioMetaBucket, configFile),
|
minioMetaBucket: path.Join(minioMetaBucket, configFile),
|
||||||
})
|
})
|
||||||
if err != nil {
|
}
|
||||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
|
||||||
if err != nil {
|
if GlobalKMS == nil && err != nil {
|
||||||
if err == madmin.ErrMaliciousData {
|
|
||||||
return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
|
|
||||||
}
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == madmin.ErrMaliciousData {
|
if err == madmin.ErrMaliciousData || err == madmin.ErrUnexpectedHeader {
|
||||||
return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
|
return false, nil, config.ErrInvalidConfigDecryptionKey(nil)
|
||||||
}
|
}
|
||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version == "kvs" {
|
||||||
|
// Check api config values are present.
|
||||||
|
var vcfg struct {
|
||||||
|
API struct {
|
||||||
|
E []struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
} `json:"_"`
|
||||||
|
} `json:"api"`
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &vcfg); err != nil {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
return len(vcfg.API.E) > 0, data, nil
|
||||||
|
}
|
||||||
var versionConfig struct {
|
var versionConfig struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
@@ -2722,6 +2732,7 @@ func migrateV32ToV33MinioSys(objAPI ObjectLayer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
|
func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
|
||||||
|
bootstrapTrace("migrate config to KV style")
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||||
|
|
||||||
// Check if the config version is latest, if not migrate.
|
// Check if the config version is latest, if not migrate.
|
||||||
@@ -2740,9 +2751,7 @@ func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
|
|||||||
|
|
||||||
newCfg := newServerConfig()
|
newCfg := newServerConfig()
|
||||||
|
|
||||||
config.SetCredentials(newCfg, cfg.Credential)
|
|
||||||
config.SetRegion(newCfg, cfg.Region)
|
config.SetRegion(newCfg, cfg.Region)
|
||||||
|
|
||||||
storageclass.SetStorageClass(newCfg, cfg.StorageClass)
|
storageclass.SetStorageClass(newCfg, cfg.StorageClass)
|
||||||
|
|
||||||
for k, loggerArgs := range cfg.Logger.HTTP {
|
for k, loggerArgs := range cfg.Logger.HTTP {
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test if config v1 is purged
|
// Test if config v1 is purged
|
||||||
@@ -65,7 +63,7 @@ func TestServerConfigMigrateV1(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize server config and check again if everything is fine
|
// Initialize server config and check again if everything is fine
|
||||||
if err := loadConfig(objLayer); err != nil {
|
if err := loadConfig(objLayer, nil); err != nil {
|
||||||
t.Fatalf("Unable to initialize from updated config file %s", err)
|
t.Fatalf("Unable to initialize from updated config file %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,20 +205,9 @@ func TestServerConfigMigrateV2toV33(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize server config and check again if everything is fine
|
// Initialize server config and check again if everything is fine
|
||||||
if err := loadConfig(objLayer); err != nil {
|
if err := loadConfig(objLayer, nil); err != nil {
|
||||||
t.Fatalf("Unable to initialize from updated config file %s", err)
|
t.Fatalf("Unable to initialize from updated config file %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if accessKey and secretKey are not altered during migration
|
|
||||||
caccessKey := globalServerConfig[config.CredentialsSubSys][config.Default].Get(config.AccessKey)
|
|
||||||
if caccessKey != accessKey {
|
|
||||||
t.Fatalf("Access key lost during migration, expected: %v, found:%v", accessKey, caccessKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
csecretKey := globalServerConfig[config.CredentialsSubSys][config.Default].Get(config.SecretKey)
|
|
||||||
if csecretKey != secretKey {
|
|
||||||
t.Fatalf("Secret key lost during migration, expected: %v, found: %v", secretKey, csecretKey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test if all migrate code returns error with corrupted config files
|
// Test if all migrate code returns error with corrupted config files
|
||||||
|
|||||||
+34
-19
@@ -28,7 +28,7 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
)
|
)
|
||||||
@@ -152,30 +152,34 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, cfg interface{})
|
|||||||
return saveConfig(ctx, objAPI, configFile, data)
|
return saveConfig(ctx, objAPI, configFile, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, error) {
|
// data is optional. If nil it will be loaded from backend.
|
||||||
|
func readServerConfig(ctx context.Context, objAPI ObjectLayer, data []byte) (config.Config, error) {
|
||||||
srvCfg := config.New()
|
srvCfg := config.New()
|
||||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
var err error
|
||||||
data, err := readConfig(ctx, objAPI, configFile)
|
if len(data) == 0 {
|
||||||
if err != nil {
|
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||||
if errors.Is(err, errConfigNotFound) {
|
data, err = readConfig(ctx, objAPI, configFile)
|
||||||
lookupConfigs(srvCfg, objAPI)
|
|
||||||
return srvCfg, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if GlobalKMS != nil && !utf8.Valid(data) {
|
|
||||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
|
||||||
minioMetaBucket: path.Join(minioMetaBucket, configFile),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lookupConfigs(srvCfg, objAPI)
|
if errors.Is(err, errConfigNotFound) {
|
||||||
|
lookupConfigs(srvCfg, objAPI)
|
||||||
|
return srvCfg, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if GlobalKMS != nil && !utf8.Valid(data) {
|
||||||
|
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||||
|
minioMetaBucket: path.Join(minioMetaBucket, configFile),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
lookupConfigs(srvCfg, objAPI)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
if err = json.Unmarshal(data, &srvCfg); err != nil {
|
if err := json.Unmarshal(data, &srvCfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +206,8 @@ func NewConfigSys() *ConfigSys {
|
|||||||
|
|
||||||
// Initialize and load config from remote etcd or local config directory
|
// Initialize and load config from remote etcd or local config directory
|
||||||
func initConfig(objAPI ObjectLayer) error {
|
func initConfig(objAPI ObjectLayer) error {
|
||||||
|
bootstrapTrace("load the configuration")
|
||||||
|
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -212,6 +218,15 @@ func initConfig(objAPI ObjectLayer) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the config version is latest (kvs), if not migrate.
|
||||||
|
ok, data, err := checkConfigVersion(objAPI, path.Join(minioConfigPrefix, minioConfigFile), "kvs")
|
||||||
|
if err != nil && !errors.Is(err, errConfigNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return loadConfig(objAPI, data)
|
||||||
|
}
|
||||||
|
|
||||||
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
|
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
|
||||||
// to '<export_path>/.minio.sys/config/config.json'
|
// to '<export_path>/.minio.sys/config/config.json'
|
||||||
// ignore if the file doesn't exist.
|
// ignore if the file doesn't exist.
|
||||||
@@ -232,5 +247,5 @@ func initConfig(objAPI ObjectLayer) error {
|
|||||||
return fmt.Errorf("migrateMinioSysConfigToKV: %w", err)
|
return fmt.Errorf("migrateMinioSysConfigToKV: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadConfig(objAPI)
|
return loadConfig(objAPI, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-10
@@ -21,13 +21,14 @@ import (
|
|||||||
"container/ring"
|
"container/ring"
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/logger/message/log"
|
|
||||||
"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/logger/message/log"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,8 +37,11 @@ const defaultLogBufferCount = 10000
|
|||||||
|
|
||||||
// HTTPConsoleLoggerSys holds global console logger state
|
// HTTPConsoleLoggerSys holds global console logger state
|
||||||
type HTTPConsoleLoggerSys struct {
|
type HTTPConsoleLoggerSys struct {
|
||||||
|
totalMessages int64
|
||||||
|
failedMessages int64
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
pubsub *pubsub.PubSub
|
pubsub *pubsub.PubSub[log.Info, madmin.LogMask]
|
||||||
console *console.Target
|
console *console.Target
|
||||||
nodeName string
|
nodeName string
|
||||||
logBuf *ring.Ring
|
logBuf *ring.Ring
|
||||||
@@ -46,14 +50,18 @@ 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) *HTTPConsoleLoggerSys {
|
||||||
ps := pubsub.New(8)
|
|
||||||
return &HTTPConsoleLoggerSys{
|
return &HTTPConsoleLoggerSys{
|
||||||
pubsub: ps,
|
pubsub: pubsub.New[log.Info, madmin.LogMask](8),
|
||||||
console: console.New(),
|
console: console.New(),
|
||||||
logBuf: ring.New(defaultLogBufferCount),
|
logBuf: ring.New(defaultLogBufferCount),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsOnline always true in case of console logger
|
||||||
|
func (sys *HTTPConsoleLoggerSys) IsOnline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// SetNodeName - sets the node name if any after distributed setup has initialized
|
// SetNodeName - sets the node name if any after distributed setup has initialized
|
||||||
func (sys *HTTPConsoleLoggerSys) SetNodeName(nodeName string) {
|
func (sys *HTTPConsoleLoggerSys) SetNodeName(nodeName string) {
|
||||||
if !globalIsDistErasure {
|
if !globalIsDistErasure {
|
||||||
@@ -72,11 +80,11 @@ func (sys *HTTPConsoleLoggerSys) SetNodeName(nodeName string) {
|
|||||||
// HasLogListeners returns true if console log listeners are registered
|
// HasLogListeners returns true if console log listeners are registered
|
||||||
// for this node or peers
|
// for this node or peers
|
||||||
func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool {
|
func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool {
|
||||||
return sys != nil && sys.pubsub.NumSubscribers(madmin.LogMaskAll) > 0
|
return sys != nil && sys.pubsub.Subscribers() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe starts console logging for this node.
|
// Subscribe starts console logging for this node.
|
||||||
func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan pubsub.Maskable, doneCh <-chan struct{}, node string, last int, logKind madmin.LogMask, filter func(entry pubsub.Maskable) bool) error {
|
func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan log.Info, doneCh <-chan struct{}, node string, last int, logKind madmin.LogMask, filter func(entry log.Info) bool) error {
|
||||||
// Enable console logging for remote client.
|
// Enable console logging for remote client.
|
||||||
if !sys.HasLogListeners() {
|
if !sys.HasLogListeners() {
|
||||||
logger.AddSystemTarget(sys)
|
logger.AddSystemTarget(sys)
|
||||||
@@ -116,7 +124,7 @@ func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan pubsub.Maskable, doneCh <-
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sys.pubsub.Subscribe(pubsub.MaskFromMaskable(madmin.LogMaskAll), subCh, doneCh, filter)
|
return sys.pubsub.Subscribe(madmin.LogMaskAll, subCh, doneCh, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init if HTTPConsoleLoggerSys is valid, always returns nil right now
|
// Init if HTTPConsoleLoggerSys is valid, always returns nil right now
|
||||||
@@ -134,6 +142,15 @@ func (sys *HTTPConsoleLoggerSys) String() string {
|
|||||||
return logger.ConsoleLoggerTgt
|
return logger.ConsoleLoggerTgt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stats returns the target statistics.
|
||||||
|
func (sys *HTTPConsoleLoggerSys) Stats() types.TargetStats {
|
||||||
|
return types.TargetStats{
|
||||||
|
TotalMessages: atomic.LoadInt64(&sys.totalMessages),
|
||||||
|
FailedMessages: atomic.LoadInt64(&sys.failedMessages),
|
||||||
|
QueueLength: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Content returns the console stdout log
|
// Content returns the console stdout log
|
||||||
func (sys *HTTPConsoleLoggerSys) Content() (logs []log.Entry) {
|
func (sys *HTTPConsoleLoggerSys) Content() (logs []log.Entry) {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
@@ -171,6 +188,7 @@ func (sys *HTTPConsoleLoggerSys) Send(entry interface{}) error {
|
|||||||
case string:
|
case string:
|
||||||
lg = log.Info{ConsoleMsg: e, NodeName: sys.nodeName}
|
lg = log.Info{ConsoleMsg: e, NodeName: sys.nodeName}
|
||||||
}
|
}
|
||||||
|
atomic.AddInt64(&sys.totalMessages, 1)
|
||||||
|
|
||||||
sys.pubsub.Publish(lg)
|
sys.pubsub.Publish(lg)
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
@@ -178,6 +196,9 @@ func (sys *HTTPConsoleLoggerSys) Send(entry interface{}) error {
|
|||||||
sys.logBuf.Value = lg
|
sys.logBuf.Value = lg
|
||||||
sys.logBuf = sys.logBuf.Next()
|
sys.logBuf = sys.logBuf.Next()
|
||||||
sys.Unlock()
|
sys.Unlock()
|
||||||
|
err := sys.console.Send(entry, string(logger.All))
|
||||||
return sys.console.Send(entry, string(logger.All))
|
if err != nil {
|
||||||
|
atomic.AddInt64(&sys.failedMessages, 1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test cross domain xml handler.
|
// Test cross domain xml handler.
|
||||||
|
|||||||
+14
-18
@@ -7,7 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,6 +47,8 @@ const (
|
|||||||
scannerMetricILM
|
scannerMetricILM
|
||||||
scannerMetricCheckReplication
|
scannerMetricCheckReplication
|
||||||
scannerMetricYield
|
scannerMetricYield
|
||||||
|
scannerMetricCleanAbandoned
|
||||||
|
scannerMetricApplyNonCurrent
|
||||||
|
|
||||||
// START Trace metrics:
|
// START Trace metrics:
|
||||||
scannerMetricStartTrace
|
scannerMetricStartTrace
|
||||||
@@ -56,9 +58,10 @@ const (
|
|||||||
scannerMetricLastRealtime
|
scannerMetricLastRealtime
|
||||||
|
|
||||||
// Trace only metrics:
|
// Trace only metrics:
|
||||||
scannerMetricScanFolder // Scan a folder on disk, recursively.
|
scannerMetricScanFolder // Scan a folder on disk, recursively.
|
||||||
scannerMetricScanCycle // Full cycle, cluster global
|
scannerMetricScanCycle // Full cycle, cluster global
|
||||||
scannerMetricScanBucketDisk // Single bucket on one disk
|
scannerMetricScanBucketDrive // Single bucket on one drive
|
||||||
|
scannerMetricCompactFolder // Folder compacted.
|
||||||
|
|
||||||
// Must be last:
|
// Must be last:
|
||||||
scannerMetricLast
|
scannerMetricLast
|
||||||
@@ -66,9 +69,9 @@ const (
|
|||||||
|
|
||||||
// log scanner action.
|
// log scanner action.
|
||||||
// Use for s > scannerMetricStartTrace
|
// Use for s > scannerMetricStartTrace
|
||||||
func (p *scannerMetrics) log(s scannerMetric, paths ...string) func() {
|
func (p *scannerMetrics) log(s scannerMetric, paths ...string) func(custom map[string]string) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
return func() {
|
return func(custom map[string]string) {
|
||||||
duration := time.Since(startTime)
|
duration := time.Since(startTime)
|
||||||
|
|
||||||
atomic.AddUint64(&p.operations[s], 1)
|
atomic.AddUint64(&p.operations[s], 1)
|
||||||
@@ -77,7 +80,7 @@ func (p *scannerMetrics) log(s scannerMetric, paths ...string) func() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s > scannerMetricStartTrace && globalTrace.NumSubscribers(madmin.TraceScanner) > 0 {
|
if s > scannerMetricStartTrace && globalTrace.NumSubscribers(madmin.TraceScanner) > 0 {
|
||||||
globalTrace.Publish(scannerTrace(s, startTime, duration, strings.Join(paths, " ")))
|
globalTrace.Publish(scannerTrace(s, startTime, duration, strings.Join(paths, " "), custom))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,13 +122,6 @@ func (p *scannerMetrics) incTime(s scannerMetric, d time.Duration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *scannerMetrics) incNoTime(s scannerMetric) {
|
|
||||||
atomic.AddUint64(&p.operations[s], 1)
|
|
||||||
if s < scannerMetricLastRealtime {
|
|
||||||
p.latency[s].add(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// timeILM times an ILM action.
|
// timeILM times an ILM action.
|
||||||
// lifecycle.NoneAction is ignored.
|
// lifecycle.NoneAction is ignored.
|
||||||
// Use for s < scannerMetricLastRealtime
|
// Use for s < scannerMetricLastRealtime
|
||||||
@@ -186,9 +182,9 @@ func (p *scannerMetrics) getCurrentPaths() []string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// activeDisks returns the number of currently active disks.
|
// activeDrives returns the number of currently active disks.
|
||||||
// (since this is concurrent it may not be 100% reliable)
|
// (since this is concurrent it may not be 100% reliable)
|
||||||
func (p *scannerMetrics) activeDisks() int {
|
func (p *scannerMetrics) activeDrives() int {
|
||||||
var i int
|
var i int
|
||||||
p.currentPaths.Range(func(k, v interface{}) bool {
|
p.currentPaths.Range(func(k, v interface{}) bool {
|
||||||
i++
|
i++
|
||||||
@@ -199,7 +195,7 @@ func (p *scannerMetrics) activeDisks() int {
|
|||||||
|
|
||||||
// lifetime returns the lifetime count of the specified metric.
|
// lifetime returns the lifetime count of the specified metric.
|
||||||
func (p *scannerMetrics) lifetime(m scannerMetric) uint64 {
|
func (p *scannerMetrics) lifetime(m scannerMetric) uint64 {
|
||||||
if m < 0 || m >= scannerMetricLast {
|
if m >= scannerMetricLast {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val := atomic.LoadUint64(&p.operations[m])
|
val := atomic.LoadUint64(&p.operations[m])
|
||||||
@@ -209,7 +205,7 @@ func (p *scannerMetrics) lifetime(m scannerMetric) uint64 {
|
|||||||
// lastMinute returns the last minute statistics of a metric.
|
// lastMinute returns the last minute statistics of a metric.
|
||||||
// m should be < scannerMetricLastRealtime
|
// m should be < scannerMetricLastRealtime
|
||||||
func (p *scannerMetrics) lastMinute(m scannerMetric) AccElem {
|
func (p *scannerMetrics) lastMinute(m scannerMetric) AccElem {
|
||||||
if m < 0 || m >= scannerMetricLastRealtime {
|
if m >= scannerMetricLastRealtime {
|
||||||
return AccElem{}
|
return AccElem{}
|
||||||
}
|
}
|
||||||
val := p.latency[m].total()
|
val := p.latency[m].total()
|
||||||
|
|||||||
+117
-67
@@ -29,13 +29,14 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bits-and-blooms/bloom/v3"
|
"github.com/bits-and-blooms/bloom/v3"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"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/object/lock"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
@@ -59,16 +60,14 @@ const (
|
|||||||
healDeleteDangling = true
|
healDeleteDangling = true
|
||||||
healFolderIncludeProb = 32 // Include a clean folder one in n cycles.
|
healFolderIncludeProb = 32 // Include a clean folder one in n cycles.
|
||||||
healObjectSelectProb = 512 // Overall probability of a file being scanned; one in n.
|
healObjectSelectProb = 512 // Overall probability of a file being scanned; one in n.
|
||||||
|
|
||||||
|
dataScannerExcessiveVersionsThreshold = 1000 // Issue a warning when a single object has more versions than this
|
||||||
|
dataScannerExcessiveFoldersThreshold = 50000 // Issue a warning when a folder has more subfolders than this in a *set*
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalHealConfig heal.Config
|
globalHealConfig heal.Config
|
||||||
|
|
||||||
dataScannerLeaderLockTimeout = newDynamicTimeoutWithOpts(dynamicTimeoutOpts{
|
|
||||||
timeout: 30 * time.Second,
|
|
||||||
minimum: 10 * time.Second,
|
|
||||||
retryInterval: time.Second,
|
|
||||||
})
|
|
||||||
// Sleeper values are updated when config is loaded.
|
// Sleeper values are updated when config is loaded.
|
||||||
scannerSleeper = newDynamicSleeper(10, 10*time.Second, true)
|
scannerSleeper = newDynamicSleeper(10, 10*time.Second, true)
|
||||||
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
|
scannerCycle = uatomic.NewDuration(dataScannerStartDelay)
|
||||||
@@ -118,6 +117,10 @@ type backgroundHealInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundHealInfo {
|
func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundHealInfo {
|
||||||
|
if globalIsErasureSD {
|
||||||
|
return backgroundHealInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
// Get last healing information
|
// Get last healing information
|
||||||
buf, err := readConfig(ctx, objAPI, backgroundHealInfoPath)
|
buf, err := readConfig(ctx, objAPI, backgroundHealInfoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -127,15 +130,17 @@ func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundH
|
|||||||
return backgroundHealInfo{}
|
return backgroundHealInfo{}
|
||||||
}
|
}
|
||||||
var info backgroundHealInfo
|
var info backgroundHealInfo
|
||||||
err = json.Unmarshal(buf, &info)
|
if err = json.Unmarshal(buf, &info); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return backgroundHealInfo{}
|
|
||||||
}
|
}
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer, info backgroundHealInfo) {
|
func saveBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer, info backgroundHealInfo) {
|
||||||
|
if globalIsErasureSD {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(info)
|
b, err := json.Marshal(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -151,19 +156,9 @@ func saveBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer, info backgr
|
|||||||
// runDataScanner will start a data scanner.
|
// runDataScanner will start a data scanner.
|
||||||
// The function will block until the context is canceled.
|
// The function will block until the context is canceled.
|
||||||
// There should only ever be one scanner running per cluster.
|
// There should only ever be one scanner running per cluster.
|
||||||
func runDataScanner(pctx context.Context, objAPI ObjectLayer) {
|
func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||||
// Make sure only 1 scanner is running on the cluster.
|
ctx, cancel := globalLeaderLock.GetLock(ctx)
|
||||||
locker := objAPI.NewNSLock(minioMetaBucket, "scanner/runDataScanner.lock")
|
defer cancel()
|
||||||
lkctx, err := locker.GetLock(pctx, dataScannerLeaderLockTimeout)
|
|
||||||
if err != nil {
|
|
||||||
if intDataUpdateTracker.debug {
|
|
||||||
logger.LogIf(pctx, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := lkctx.Context()
|
|
||||||
defer lkctx.Cancel()
|
|
||||||
// No unlock for "leader" lock.
|
|
||||||
|
|
||||||
// Load current bloom cycle
|
// Load current bloom cycle
|
||||||
var cycleInfo currentScannerCycle
|
var cycleInfo currentScannerCycle
|
||||||
@@ -175,7 +170,7 @@ func runDataScanner(pctx context.Context, objAPI ObjectLayer) {
|
|||||||
} else if len(buf) > 8 {
|
} else if len(buf) > 8 {
|
||||||
cycleInfo.next = binary.LittleEndian.Uint64(buf[:8])
|
cycleInfo.next = binary.LittleEndian.Uint64(buf[:8])
|
||||||
buf = buf[8:]
|
buf = buf[8:]
|
||||||
_, err = cycleInfo.UnmarshalMsg(buf)
|
_, err := cycleInfo.UnmarshalMsg(buf)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +211,11 @@ func runDataScanner(pctx context.Context, objAPI ObjectLayer) {
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
err = objAPI.NSScanner(ctx, bf, results, uint32(cycleInfo.current), scanMode)
|
err = objAPI.NSScanner(ctx, bf, results, uint32(cycleInfo.current), scanMode)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
stopFn()
|
res := map[string]string{"cycle": fmt.Sprint(cycleInfo.current)}
|
||||||
|
if err != nil {
|
||||||
|
res["error"] = err.Error()
|
||||||
|
}
|
||||||
|
stopFn(res)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Store new cycle...
|
// Store new cycle...
|
||||||
cycleInfo.next++
|
cycleInfo.next++
|
||||||
@@ -429,7 +428,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
filter := f.withFilter
|
filter := f.withFilter
|
||||||
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
|
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
|
||||||
var activeLifeCycle *lifecycle.Lifecycle
|
var activeLifeCycle *lifecycle.Lifecycle
|
||||||
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) {
|
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix) {
|
||||||
if f.dataUsageScannerDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(scannerLogPrefix+" Prefix %q has active rules\n", prefix)
|
console.Debugf(scannerLogPrefix+" Prefix %q has active rules\n", prefix)
|
||||||
}
|
}
|
||||||
@@ -575,6 +574,19 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
len(existingFolders)+len(newFolders) >= dataScannerCompactAtFolders ||
|
len(existingFolders)+len(newFolders) >= dataScannerCompactAtFolders ||
|
||||||
len(existingFolders)+len(newFolders) >= dataScannerForceCompactAtFolders
|
len(existingFolders)+len(newFolders) >= dataScannerForceCompactAtFolders
|
||||||
|
|
||||||
|
if len(existingFolders)+len(newFolders) > dataScannerExcessiveFoldersThreshold {
|
||||||
|
// Notify object accessed via a GET request.
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.PrefixManyFolders,
|
||||||
|
BucketName: f.root,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Name: strings.TrimSuffix(folder.name, "/") + "/",
|
||||||
|
Size: int64(len(existingFolders) + len(newFolders)),
|
||||||
|
},
|
||||||
|
UserAgent: "scanner",
|
||||||
|
Host: globalMinioHost,
|
||||||
|
})
|
||||||
|
}
|
||||||
if !into.Compacted && shouldCompact {
|
if !into.Compacted && shouldCompact {
|
||||||
into.Compacted = true
|
into.Compacted = true
|
||||||
newFolders = append(newFolders, existingFolders...)
|
newFolders = append(newFolders, existingFolders...)
|
||||||
@@ -641,7 +653,8 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
f.updateCurrentPath(folder.name)
|
f.updateCurrentPath(folder.name)
|
||||||
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name)
|
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name)
|
||||||
scanFolder(folder)
|
scanFolder(folder)
|
||||||
stopFn()
|
stopFn(map[string]string{"type": "new"})
|
||||||
|
|
||||||
// Add new folders if this is new and we don't have existing.
|
// Add new folders if this is new and we don't have existing.
|
||||||
if !into.Compacted {
|
if !into.Compacted {
|
||||||
parent := f.updateCache.find(thisHash.Key())
|
parent := f.updateCache.find(thisHash.Key())
|
||||||
@@ -670,9 +683,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.updateCurrentPath(folder.name)
|
f.updateCurrentPath(folder.name)
|
||||||
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name, "EXISTING")
|
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, folder.name)
|
||||||
scanFolder(folder)
|
scanFolder(folder)
|
||||||
stopFn()
|
stopFn(map[string]string{"type": "existing"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan for healing
|
// Scan for healing
|
||||||
@@ -770,7 +783,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
object: entry.name,
|
object: entry.name,
|
||||||
versionID: "",
|
versionID: "",
|
||||||
}, madmin.HealItemObject)
|
}, madmin.HealItemObject)
|
||||||
logger.LogIf(ctx, err)
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
foundObjs = foundObjs || err == nil
|
foundObjs = foundObjs || err == nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -785,7 +800,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
object: fiv.Name,
|
object: fiv.Name,
|
||||||
versionID: ver.VersionID,
|
versionID: ver.VersionID,
|
||||||
}, madmin.HealItemObject)
|
}, madmin.HealItemObject)
|
||||||
logger.LogIf(ctx, err)
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
foundObjs = foundObjs || err == nil
|
foundObjs = foundObjs || err == nil
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -808,7 +825,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
this := cachedFolder{name: k, parent: &thisHash, objectHealProbDiv: 1}
|
this := cachedFolder{name: k, parent: &thisHash, objectHealProbDiv: 1}
|
||||||
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, this.name, "HEALED")
|
stopFn := globalScannerMetrics.log(scannerMetricScanFolder, f.root, this.name, "HEALED")
|
||||||
scanFolder(this)
|
scanFolder(this)
|
||||||
stopFn()
|
stopFn(map[string]string{"type": "healed"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -822,10 +839,6 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
flat.Compacted = true
|
flat.Compacted = true
|
||||||
var compact bool
|
var compact bool
|
||||||
if flat.Objects < dataScannerCompactLeastObject {
|
if flat.Objects < dataScannerCompactLeastObject {
|
||||||
if f.dataUsageScannerDebug && flat.Objects > 1 {
|
|
||||||
// Disabled, rather chatty:
|
|
||||||
// console.Debugf(scannerLogPrefix+" Only %d objects, compacting %s -> %+v\n", flat.Objects, folder.name, flat)
|
|
||||||
}
|
|
||||||
compact = true
|
compact = true
|
||||||
} else {
|
} else {
|
||||||
// Compact if we only have objects as children...
|
// Compact if we only have objects as children...
|
||||||
@@ -838,14 +851,20 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f.dataUsageScannerDebug && compact {
|
|
||||||
// Disabled, rather chatty:
|
|
||||||
// console.Debugf(scannerLogPrefix+" Only objects (%d), compacting %s -> %+v\n", flat.Objects, folder.name, flat)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if compact {
|
if compact {
|
||||||
|
stop := globalScannerMetrics.log(scannerMetricCompactFolder, folder.name)
|
||||||
f.newCache.deleteRecursive(thisHash)
|
f.newCache.deleteRecursive(thisHash)
|
||||||
f.newCache.replaceHashed(thisHash, folder.parent, *flat)
|
f.newCache.replaceHashed(thisHash, folder.parent, *flat)
|
||||||
|
total := map[string]string{
|
||||||
|
"objects": fmt.Sprint(flat.Objects),
|
||||||
|
"size": fmt.Sprint(flat.Size),
|
||||||
|
}
|
||||||
|
if flat.Versions > 0 {
|
||||||
|
total["versions"] = fmt.Sprint(flat.Versions)
|
||||||
|
}
|
||||||
|
stop(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -954,30 +973,26 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
if i.lifeCycle == nil {
|
if i.lifeCycle == nil {
|
||||||
if i.debug {
|
|
||||||
// disabled, very chatty:
|
|
||||||
// console.Debugf(applyActionsLogPrefix+" no lifecycle rules to apply: %q\n", i.objectPath())
|
|
||||||
}
|
|
||||||
return false, size
|
return false, size
|
||||||
}
|
}
|
||||||
|
|
||||||
versionID := oi.VersionID
|
versionID := oi.VersionID
|
||||||
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
|
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
|
||||||
action := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi)
|
lcEvt := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi)
|
||||||
if i.debug {
|
if i.debug {
|
||||||
if versionID != "" {
|
if versionID != "" {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, lcEvt.Action)
|
||||||
} else {
|
} else {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), lcEvt.Action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer globalScannerMetrics.timeILM(action)
|
defer globalScannerMetrics.timeILM(lcEvt.Action)
|
||||||
|
|
||||||
switch action {
|
switch lcEvt.Action {
|
||||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
return applyLifecycleAction(action, oi), 0
|
return applyLifecycleAction(lcEvt.Action, oi, ""), 0
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
return applyLifecycleAction(action, oi), size
|
return applyLifecycleAction(lcEvt.Action, oi, lcEvt.StorageClass), size
|
||||||
default:
|
default:
|
||||||
// No action.
|
// No action.
|
||||||
return false, size
|
return false, size
|
||||||
@@ -1008,7 +1023,8 @@ func (i *scannerItem) applyTierObjSweep(ctx context.Context, o ObjectLayer, oi O
|
|||||||
|
|
||||||
// Remove this free version
|
// Remove this free version
|
||||||
_, err = o.DeleteObject(ctx, oi.Bucket, oi.Name, ObjectOptions{
|
_, err = o.DeleteObject(ctx, oi.Bucket, oi.Name, ObjectOptions{
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
|
InclFreeVersions: true,
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete)
|
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete)
|
||||||
@@ -1024,6 +1040,8 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
|
|||||||
if i.lifeCycle == nil {
|
if i.lifeCycle == nil {
|
||||||
return fivs, nil
|
return fivs, nil
|
||||||
}
|
}
|
||||||
|
done := globalScannerMetrics.time(scannerMetricApplyNonCurrent)
|
||||||
|
defer done()
|
||||||
|
|
||||||
_, days, lim := i.lifeCycle.NoncurrentVersionsExpirationLimit(lifecycle.ObjectOpts{Name: i.objectPath()})
|
_, days, lim := i.lifeCycle.NoncurrentVersionsExpirationLimit(lifecycle.ObjectOpts{Name: i.objectPath()})
|
||||||
if lim == 0 || len(fivs) <= lim+1 { // fewer than lim _noncurrent_ versions
|
if lim == 0 || len(fivs) <= lim+1 { // fewer than lim _noncurrent_ versions
|
||||||
@@ -1080,7 +1098,37 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
|
|||||||
// applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain
|
// applyVersionActions will apply lifecycle checks on all versions of a scanned item. Returns versions that remain
|
||||||
// after applying lifecycle checks configured.
|
// after applying lifecycle checks configured.
|
||||||
func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]FileInfo, error) {
|
func (i *scannerItem) applyVersionActions(ctx context.Context, o ObjectLayer, fivs []FileInfo) ([]FileInfo, error) {
|
||||||
return i.applyNewerNoncurrentVersionLimit(ctx, o, fivs)
|
if i.heal.enabled {
|
||||||
|
if healDeleteDangling {
|
||||||
|
done := globalScannerMetrics.time(scannerMetricCleanAbandoned)
|
||||||
|
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
|
||||||
|
done()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fivs, err := i.applyNewerNoncurrentVersionLimit(ctx, o, fivs)
|
||||||
|
if err != nil {
|
||||||
|
return fivs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have many versions after applyNewerNoncurrentVersionLimit.
|
||||||
|
if len(fivs) > dataScannerExcessiveVersionsThreshold {
|
||||||
|
// Notify object accessed via a GET request.
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectManyVersions,
|
||||||
|
BucketName: i.bucket,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Name: i.objectPath(),
|
||||||
|
},
|
||||||
|
UserAgent: "scanner",
|
||||||
|
Host: globalMinioHost,
|
||||||
|
RespElements: map[string]string{"x-minio-versions": strconv.Itoa(len(fivs))},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fivs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyActions will apply lifecycle checks on to a scanned item.
|
// applyActions will apply lifecycle checks on to a scanned item.
|
||||||
@@ -1109,21 +1157,21 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) (action lifecycle.Action) {
|
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) lifecycle.Event {
|
||||||
action = lc.ComputeAction(obj.ToLifecycleOpts())
|
event := lc.Eval(obj.ToLifecycleOpts())
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == lifecycle.NoneAction {
|
if event.Action == lifecycle.NoneAction {
|
||||||
return action
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
switch action {
|
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 == "" {
|
||||||
return lifecycle.NoneAction
|
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||||
}
|
}
|
||||||
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
@@ -1133,18 +1181,18 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
|
|||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", obj.Name)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", obj.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lifecycle.NoneAction
|
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return action
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyTransitionRule(obj ObjectInfo) bool {
|
func applyTransitionRule(obj ObjectInfo, storageClass string) bool {
|
||||||
if obj.DeleteMarker {
|
if obj.DeleteMarker {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
globalTransitionState.queueTransitionTask(obj)
|
globalTransitionState.queueTransitionTask(obj, storageClass)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1200,7 +1248,7 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
|||||||
EventName: eventName,
|
EventName: eventName,
|
||||||
BucketName: obj.Bucket,
|
BucketName: obj.Bucket,
|
||||||
Object: obj,
|
Object: obj,
|
||||||
Host: "Internal: [ILM-EXPIRY]",
|
Host: "Internal: [ILM-Expiry]",
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -1213,14 +1261,14 @@ func applyExpiryRule(obj ObjectInfo, restoredObject, applyOnVersion bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform actions (removal or transitioning of objects), return true the action is successfully performed
|
// Perform actions (removal or transitioning of objects), return true the action is successfully performed
|
||||||
func applyLifecycleAction(action lifecycle.Action, obj ObjectInfo) (success bool) {
|
func applyLifecycleAction(action lifecycle.Action, obj ObjectInfo, storageClass string) (success bool) {
|
||||||
switch action {
|
switch action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
success = applyExpiryRule(obj, false, action == lifecycle.DeleteVersionAction)
|
success = applyExpiryRule(obj, false, action == lifecycle.DeleteVersionAction)
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
success = applyExpiryRule(obj, true, action == lifecycle.DeleteRestoredVersionAction)
|
success = applyExpiryRule(obj, true, action == lifecycle.DeleteRestoredVersionAction)
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
success = applyTransitionRule(obj)
|
success = applyTransitionRule(obj, storageClass)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1441,9 +1489,11 @@ func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string) {
|
|||||||
case ILMTransition:
|
case ILMTransition:
|
||||||
apiName = "ILMTransition"
|
apiName = "ILMTransition"
|
||||||
}
|
}
|
||||||
auditLogInternal(ctx, oi.Bucket, oi.Name, AuditLogOptions{
|
auditLogInternal(ctx, AuditLogOptions{
|
||||||
Event: event,
|
Event: event,
|
||||||
APIName: apiName,
|
APIName: apiName,
|
||||||
|
Bucket: oi.Bucket,
|
||||||
|
Object: oi.Name,
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/logger/message/log"
|
|
||||||
"github.com/minio/minio/internal/logger/target/types"
|
"github.com/minio/minio/internal/logger/target/types"
|
||||||
|
"github.com/minio/pkg/logger/message/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testLoggerI interface {
|
type testLoggerI interface {
|
||||||
@@ -62,6 +62,15 @@ func (t *testingLogger) Type() types.TargetType {
|
|||||||
return types.TargetHTTP
|
return types.TargetHTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *testingLogger) IsOnline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats returns the target statistics.
|
||||||
|
func (t *testingLogger) Stats() types.TargetStats {
|
||||||
|
return types.TargetStats{}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *testingLogger) Send(entry interface{}) error {
|
func (t *testingLogger) Send(entry interface{}) error {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
|
|||||||
+24
-52
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -31,7 +32,7 @@ import (
|
|||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -392,13 +393,6 @@ func (e *dataUsageEntry) addChild(hash dataUsageHash) {
|
|||||||
e.Children[hash.Key()] = struct{}{}
|
e.Children[hash.Key()] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeChild will remove a child based on its hash.
|
|
||||||
func (e *dataUsageEntry) removeChild(hash dataUsageHash) {
|
|
||||||
if len(e.Children) > 0 {
|
|
||||||
delete(e.Children, hash.Key())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a clone of the entry.
|
// Create a clone of the entry.
|
||||||
func (e dataUsageEntry) clone() dataUsageEntry {
|
func (e dataUsageEntry) clone() dataUsageEntry {
|
||||||
// We operate on a copy from the receiver.
|
// We operate on a copy from the receiver.
|
||||||
@@ -488,43 +482,6 @@ func (d *dataUsageCache) deleteRecursive(h dataUsageHash) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// keepBuckets will keep only the buckets specified specified by delete all others.
|
|
||||||
func (d *dataUsageCache) keepBuckets(b []BucketInfo) {
|
|
||||||
lu := make(map[dataUsageHash]struct{})
|
|
||||||
for _, v := range b {
|
|
||||||
lu[hashPath(v.Name)] = struct{}{}
|
|
||||||
}
|
|
||||||
d.keepRootChildren(lu)
|
|
||||||
}
|
|
||||||
|
|
||||||
// keepRootChildren will keep the root children specified by delete all others.
|
|
||||||
func (d *dataUsageCache) keepRootChildren(list map[dataUsageHash]struct{}) {
|
|
||||||
root := d.root()
|
|
||||||
if root == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rh := d.rootHash()
|
|
||||||
for k := range d.Cache {
|
|
||||||
h := dataUsageHash(k)
|
|
||||||
if h == rh {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := list[h]; !ok {
|
|
||||||
delete(d.Cache, k)
|
|
||||||
d.deleteRecursive(h)
|
|
||||||
root.removeChild(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clean up abandoned children.
|
|
||||||
for k := range root.Children {
|
|
||||||
h := dataUsageHash(k)
|
|
||||||
if _, ok := list[h]; !ok {
|
|
||||||
delete(root.Children, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.Cache[rh.Key()] = *root
|
|
||||||
}
|
|
||||||
|
|
||||||
// dui converts the flattened version of the path to madmin.DataUsageInfo.
|
// dui converts the flattened version of the path to madmin.DataUsageInfo.
|
||||||
// As a side effect d will be flattened, use a clone if this is not ok.
|
// As a side effect d will be flattened, use a clone if this is not ok.
|
||||||
func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo {
|
func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo {
|
||||||
@@ -923,13 +880,28 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
|
|||||||
|
|
||||||
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
|
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
|
||||||
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
|
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
|
||||||
pr, pw := io.Pipe()
|
var r io.Reader
|
||||||
go func() {
|
|
||||||
pw.CloseWithError(d.serializeTo(pw))
|
|
||||||
}()
|
|
||||||
defer pr.Close()
|
|
||||||
|
|
||||||
r, err := hash.NewReader(pr, -1, "", "", -1)
|
// If big, do streaming...
|
||||||
|
size := int64(-1)
|
||||||
|
if len(d.Cache) > 10000 {
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
go func() {
|
||||||
|
pw.CloseWithError(d.serializeTo(pw))
|
||||||
|
}()
|
||||||
|
defer pr.Close()
|
||||||
|
r = pr
|
||||||
|
} else {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := d.serializeTo(&buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r = &buf
|
||||||
|
size = int64(buf.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
hr, err := hash.NewReader(r, size, "", "", size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -940,7 +912,7 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
_, err = store.PutObject(ctx,
|
_, err = store.PutObject(ctx,
|
||||||
dataUsageBucket,
|
dataUsageBucket,
|
||||||
name,
|
name,
|
||||||
NewPutObjReader(r),
|
NewPutObjReader(hr),
|
||||||
ObjectOptions{})
|
ObjectOptions{})
|
||||||
if isErrBucketNotFound(err) {
|
if isErrBucketNotFound(err) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketTargetUsageInfo - bucket target usage info provides
|
// BucketTargetUsageInfo - bucket target usage info provides
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ func (c *diskCache) statCachedMeta(ctx context.Context, cacheObjPath string) (me
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer cLock.RUnlock(lkctx.Cancel)
|
defer cLock.RUnlock(lkctx)
|
||||||
return c.statCache(ctx, cacheObjPath)
|
return c.statCache(ctx, cacheObjPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,7 +597,7 @@ func (c *diskCache) SaveMetadata(ctx context.Context, bucket, object string, met
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer cLock.Unlock(lkctx.Cancel)
|
defer cLock.Unlock(lkctx)
|
||||||
if err = c.saveMetadata(ctx, bucket, object, meta, actualSize, rs, rsFileName, incHitsOnly); err != nil {
|
if err = c.saveMetadata(ctx, bucket, object, meta, actualSize, rs, rsFileName, incHitsOnly); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -842,7 +842,7 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
|
|||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer cLock.Unlock(lkctx.Cancel)
|
defer cLock.Unlock(lkctx)
|
||||||
|
|
||||||
return c.put(ctx, bucket, object, data, size, rs, opts, incHitsOnly, writeback)
|
return c.put(ctx, bucket, object, data, size, rs, opts, incHitsOnly, writeback)
|
||||||
}
|
}
|
||||||
@@ -953,7 +953,7 @@ func (c *diskCache) putRange(ctx context.Context, bucket, object string, data io
|
|||||||
objSize, _ = sio.EncryptedSize(uint64(size))
|
objSize, _ = sio.EncryptedSize(uint64(size))
|
||||||
|
|
||||||
}
|
}
|
||||||
cacheFile := MustGetUUID()
|
cacheFile := mustGetUUID()
|
||||||
n, _, err := c.bitrotWriteToCache(cachePath, cacheFile, reader, actualSize)
|
n, _, err := c.bitrotWriteToCache(cachePath, cacheFile, reader, actualSize)
|
||||||
if IsErr(err, baseErrs...) {
|
if IsErr(err, baseErrs...) {
|
||||||
// take the cache drive offline
|
// take the cache drive offline
|
||||||
@@ -1003,6 +1003,7 @@ func (c *diskCache) bitrotReadFromCache(ctx context.Context, filePath string, of
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer rc.Close()
|
||||||
bufp := c.pool.Get().(*[]byte)
|
bufp := c.pool.Get().(*[]byte)
|
||||||
defer c.pool.Put(bufp)
|
defer c.pool.Put(bufp)
|
||||||
|
|
||||||
@@ -1075,7 +1076,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
|
|||||||
return nil, numHits, err
|
return nil, numHits, err
|
||||||
}
|
}
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer cLock.RUnlock(lkctx.Cancel)
|
defer cLock.RUnlock(lkctx)
|
||||||
|
|
||||||
var objInfo ObjectInfo
|
var objInfo ObjectInfo
|
||||||
var rngInfo RangeInfo
|
var rngInfo RangeInfo
|
||||||
@@ -1212,7 +1213,7 @@ func (c *diskCache) Delete(ctx context.Context, bucket, object string) (err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cLock.Unlock(lkctx.Cancel)
|
defer cLock.Unlock(lkctx)
|
||||||
return removeAll(cacheObjPath)
|
return removeAll(cacheObjPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1323,7 +1324,7 @@ func (c *diskCache) PutObjectPart(ctx context.Context, bucket, object, uploadID
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer partIDLock.Unlock(lkctx.Cancel)
|
defer partIDLock.Unlock(lkctx)
|
||||||
meta, _, _, err := c.statCache(ctx, uploadIDDir)
|
meta, _, _, err := c.statCache(ctx, uploadIDDir)
|
||||||
// Case where object not yet cached
|
// Case where object not yet cached
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1380,7 +1381,7 @@ func (c *diskCache) SavePartMetadata(ctx context.Context, bucket, object, upload
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer uploadLock.Unlock(ulkctx.Cancel)
|
defer uploadLock.Unlock(ulkctx)
|
||||||
|
|
||||||
metaPath := pathJoin(uploadDir, cacheMetaJSONFile)
|
metaPath := pathJoin(uploadDir, cacheMetaJSONFile)
|
||||||
f, err := OpenFile(metaPath, os.O_RDWR|writeMode, 0o666)
|
f, err := OpenFile(metaPath, os.O_RDWR|writeMode, 0o666)
|
||||||
@@ -1479,7 +1480,7 @@ func (c *diskCache) CompleteMultipartUpload(ctx context.Context, bucket, object,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer cLock.Unlock(lkctx.Cancel)
|
defer cLock.Unlock(lkctx)
|
||||||
mpartCachePath := getMultipartCacheSHADir(c.dir, bucket, object)
|
mpartCachePath := getMultipartCacheSHADir(c.dir, bucket, object)
|
||||||
uploadIDDir := path.Join(mpartCachePath, uploadID)
|
uploadIDDir := path.Join(mpartCachePath, uploadID)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -728,7 +728,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
|
|||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
return putObjectFn(ctx, bucket, object, r, opts)
|
return putObjectFn(ctx, bucket, object, r, opts)
|
||||||
}
|
}
|
||||||
defer cLock.Unlock(lkctx.Cancel)
|
defer cLock.Unlock(lkctx)
|
||||||
// Initialize pipe to stream data to backend
|
// Initialize pipe to stream data to backend
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
hashReader, err := hash.NewReader(pipeReader, size, "", "", r.ActualSize())
|
hashReader, err := hash.NewReader(pipeReader, size, "", "", r.ActualSize())
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+47
-19
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/etag"
|
"github.com/minio/minio/internal/etag"
|
||||||
"github.com/minio/minio/internal/fips"
|
"github.com/minio/minio/internal/fips"
|
||||||
@@ -48,14 +48,16 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// AWS errors for invalid SSE-C requests.
|
// AWS errors for invalid SSE-C requests.
|
||||||
errEncryptedObject = errors.New("The object was stored using a form of SSE")
|
errEncryptedObject = errors.New("The object was stored using a form of SSE")
|
||||||
errInvalidSSEParameters = errors.New("The SSE-C key for key-rotation is not correct") // special access denied
|
errInvalidSSEParameters = errors.New("The SSE-C key for key-rotation is not correct") // special access denied
|
||||||
errKMSNotConfigured = errors.New("KMS not configured for a server side encrypted object")
|
errKMSNotConfigured = errors.New("KMS not configured for a server side encrypted objects")
|
||||||
errKMSKeyNotFound = errors.New("Invalid KMS keyId")
|
errKMSKeyNotFound = errors.New("Unknown KMS key ID")
|
||||||
|
errKMSDefaultKeyAlreadyConfigured = errors.New("A default encryption already exists on KMS")
|
||||||
// Additional MinIO errors for SSE-C requests.
|
// Additional MinIO errors for SSE-C requests.
|
||||||
errObjectTampered = errors.New("The requested object was modified and may be compromised")
|
errObjectTampered = errors.New("The requested object was modified and may be compromised")
|
||||||
// error returned when invalid encryption parameters are specified
|
// error returned when invalid encryption parameters are specified
|
||||||
errInvalidEncryptionParameters = errors.New("The encryption parameters are not applicable to this object")
|
errInvalidEncryptionParameters = errors.New("The encryption parameters are not applicable to this object")
|
||||||
|
errInvalidEncryptionParametersSSEC = errors.New("SSE-C encryption parameters are not supported on this bucket")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -1043,18 +1045,6 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e
|
|||||||
return encrypted, nil
|
return encrypted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The customer key in the header is used by the gateway for encryption in the case of
|
|
||||||
// s3 gateway double encryption. A new client key is derived from the customer provided
|
|
||||||
// key to be sent to the s3 backend for encryption at the backend.
|
|
||||||
func deriveClientKey(clientKey [32]byte, bucket, object string) [32]byte {
|
|
||||||
var key [32]byte
|
|
||||||
mac := hmac.New(sha256.New, clientKey[:])
|
|
||||||
mac.Write([]byte(crypto.SSEC.String()))
|
|
||||||
mac.Write([]byte(path.Join(bucket, object)))
|
|
||||||
mac.Sum(key[:0])
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
objectMetaEncryptFn func(baseKey string, data []byte) []byte
|
objectMetaEncryptFn func(baseKey string, data []byte) []byte
|
||||||
objectMetaDecryptFn func(baseKey string, data []byte) ([]byte, error)
|
objectMetaDecryptFn func(baseKey string, data []byte) ([]byte, error)
|
||||||
@@ -1095,6 +1085,44 @@ func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// metadataEncryptFn provides an encryption function for metadata.
|
||||||
|
// Will return nil, nil if unencrypted.
|
||||||
|
func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn, error) {
|
||||||
|
kind, _ := crypto.IsEncrypted(o.UserDefined)
|
||||||
|
switch kind {
|
||||||
|
case crypto.SSEC:
|
||||||
|
if crypto.SSECopy.IsRequested(headers) {
|
||||||
|
key, err := crypto.SSECopy.ParseHTTP(headers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objectEncryptionKey, err := decryptObjectMeta(key[:], o.Bucket, o.Name, o.UserDefined)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(objectEncryptionKey) == 32 {
|
||||||
|
var key crypto.ObjectKey
|
||||||
|
copy(key[:], objectEncryptionKey)
|
||||||
|
return metadataEncrypter(key), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("metadataEncryptFn: unexpected key size")
|
||||||
|
}
|
||||||
|
case crypto.S3, crypto.S3KMS:
|
||||||
|
objectEncryptionKey, err := decryptObjectMeta(nil, o.Bucket, o.Name, o.UserDefined)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(objectEncryptionKey) == 32 {
|
||||||
|
var key crypto.ObjectKey
|
||||||
|
copy(key[:], objectEncryptionKey)
|
||||||
|
return metadataEncrypter(key), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("metadataEncryptFn: unexpected key size")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// decryptChecksums will attempt to decode checksums and return it/them if set.
|
// decryptChecksums will attempt to decode checksums and return it/them if set.
|
||||||
func (o *ObjectInfo) decryptChecksums() map[string]string {
|
func (o *ObjectInfo) decryptChecksums() map[string]string {
|
||||||
data := o.Checksum
|
data := o.Checksum
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -207,7 +208,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns all the expanded endpoints, each argument is expanded separately.
|
// Returns all the expanded endpoints, each argument is expanded separately.
|
||||||
func (s endpointSet) getEndpoints() (endpoints []string) {
|
func (s *endpointSet) getEndpoints() (endpoints []string) {
|
||||||
if len(s.endpoints) != 0 {
|
if len(s.endpoints) != 0 {
|
||||||
return s.endpoints
|
return s.endpoints
|
||||||
}
|
}
|
||||||
@@ -343,7 +344,13 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
return nil, -1, errInvalidArgument
|
return nil, -1, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ellipses.HasEllipses(args...) {
|
ok := true
|
||||||
|
for _, arg := range args {
|
||||||
|
ok = ok && !ellipses.HasEllipses(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// None of the args have ellipses use the old style.
|
||||||
|
if ok {
|
||||||
setArgs, err := GetAllSets(args...)
|
setArgs, err := GetAllSets(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
@@ -358,6 +365,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
DrivesPerSet: len(setArgs[0]),
|
DrivesPerSet: len(setArgs[0]),
|
||||||
Endpoints: endpointList,
|
Endpoints: endpointList,
|
||||||
CmdLine: strings.Join(args, " "),
|
CmdLine: strings.Join(args, " "),
|
||||||
|
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
||||||
})
|
})
|
||||||
setupType = newSetupType
|
setupType = newSetupType
|
||||||
return endpointServerPools, setupType, nil
|
return endpointServerPools, setupType, nil
|
||||||
@@ -365,6 +373,10 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
|
|
||||||
var foundPrevLocal bool
|
var foundPrevLocal bool
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
|
if !ellipses.HasEllipses(arg) && len(args) > 1 {
|
||||||
|
// TODO: support SNSD deployments to be decommissioned in future
|
||||||
|
return nil, -1, fmt.Errorf("all args must have ellipses for pool expansion (%w) args: %s", errInvalidArgument, args)
|
||||||
|
}
|
||||||
setArgs, err := GetAllSets(arg)
|
setArgs, err := GetAllSets(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
@@ -379,6 +391,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
DrivesPerSet: len(setArgs[0]),
|
DrivesPerSet: len(setArgs[0]),
|
||||||
Endpoints: endpointList,
|
Endpoints: endpointList,
|
||||||
CmdLine: arg,
|
CmdLine: arg,
|
||||||
|
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-4
@@ -203,6 +203,7 @@ type PoolEndpoints struct {
|
|||||||
DrivesPerSet int
|
DrivesPerSet int
|
||||||
Endpoints Endpoints
|
Endpoints Endpoints
|
||||||
CmdLine string
|
CmdLine string
|
||||||
|
Platform string
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndpointServerPools - list of list of endpoints
|
// EndpointServerPools - list of list of endpoints
|
||||||
@@ -417,6 +418,27 @@ func (endpoints Endpoints) atleastOneEndpointLocal() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hostResolveToLocalhost(endpoint Endpoint) bool {
|
||||||
|
hostIPs, err := getHostIP(endpoint.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
// Log the message to console about the host resolving
|
||||||
|
reqInfo := (&logger.ReqInfo{}).AppendTags(
|
||||||
|
"host",
|
||||||
|
endpoint.Hostname(),
|
||||||
|
)
|
||||||
|
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
|
||||||
|
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var loopback int
|
||||||
|
for _, hostIP := range hostIPs.ToSlice() {
|
||||||
|
if net.ParseIP(hostIP).IsLoopback() {
|
||||||
|
loopback++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loopback == len(hostIPs)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateIsLocal - resolves the host and discovers the local host.
|
// UpdateIsLocal - resolves the host and discovers the local host.
|
||||||
func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
|
func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
|
||||||
orchestrated := IsDocker() || IsKubernetes()
|
orchestrated := IsDocker() || IsKubernetes()
|
||||||
@@ -452,6 +474,26 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
|
|||||||
endpoints[i].Hostname(),
|
endpoints[i].Hostname(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if orchestrated && hostResolveToLocalhost(endpoints[i]) {
|
||||||
|
// time elapsed
|
||||||
|
timeElapsed := time.Since(startTime)
|
||||||
|
// log error only if more than a second has elapsed
|
||||||
|
if timeElapsed > time.Second {
|
||||||
|
reqInfo.AppendTags("elapsedTime",
|
||||||
|
humanize.RelTime(startTime,
|
||||||
|
startTime.Add(timeElapsed),
|
||||||
|
"elapsed",
|
||||||
|
"",
|
||||||
|
))
|
||||||
|
ctx := logger.SetReqInfo(GlobalContext,
|
||||||
|
reqInfo)
|
||||||
|
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
|
||||||
|
endpoints[i].Hostname()), endpoints[i].Hostname(), logger.Application)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// return err if not Docker or Kubernetes
|
// return err if not Docker or Kubernetes
|
||||||
// We use IsDocker() to check for Docker environment
|
// We use IsDocker() to check for Docker environment
|
||||||
// We use IsKubernetes() to check for Kubernetes environment
|
// We use IsKubernetes() to check for Kubernetes environment
|
||||||
@@ -465,7 +507,7 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// time elapsed
|
// time elapsed
|
||||||
timeElapsed := time.Since(startTime)
|
timeElapsed := time.Since(startTime)
|
||||||
// log error only if more than 1s elapsed
|
// log error only if more than a second has elapsed
|
||||||
if timeElapsed > time.Second {
|
if timeElapsed > time.Second {
|
||||||
reqInfo.AppendTags("elapsedTime",
|
reqInfo.AppendTags("elapsedTime",
|
||||||
humanize.RelTime(startTime,
|
humanize.RelTime(startTime,
|
||||||
@@ -475,7 +517,7 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
|
|||||||
))
|
))
|
||||||
ctx := logger.SetReqInfo(GlobalContext,
|
ctx := logger.SetReqInfo(GlobalContext,
|
||||||
reqInfo)
|
reqInfo)
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.Application)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolvedList[i] = true
|
resolvedList[i] = true
|
||||||
@@ -592,14 +634,14 @@ func CreateEndpoints(serverAddr string, foundLocal bool, args ...[]string) (Endp
|
|||||||
return endpoints, setupType, err
|
return endpoints, setupType, err
|
||||||
}
|
}
|
||||||
if endpoint.Type() != PathEndpointType {
|
if endpoint.Type() != PathEndpointType {
|
||||||
return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg("use path style endpoint for FS setup")
|
return endpoints, setupType, config.ErrInvalidEndpoint(nil).Msg("use path style endpoint for single node setup")
|
||||||
}
|
}
|
||||||
endpoints = append(endpoints, endpoint)
|
endpoints = append(endpoints, endpoint)
|
||||||
setupType = ErasureSDSetupType
|
setupType = ErasureSDSetupType
|
||||||
|
|
||||||
// Check for cross device mounts if any.
|
// Check for cross device mounts if any.
|
||||||
if err = checkCrossDeviceMounts(endpoints); err != nil {
|
if err = checkCrossDeviceMounts(endpoints); err != nil {
|
||||||
return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg(err.Error())
|
return endpoints, setupType, config.ErrInvalidEndpoint(nil).Msg(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints, setupType, nil
|
return endpoints, setupType, nil
|
||||||
|
|||||||
@@ -1,277 +0,0 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of MinIO Object Storage stack
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/minio/internal/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
// list all errors that can be ignore in a bucket operation.
|
|
||||||
var bucketOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errUnformattedDisk)
|
|
||||||
|
|
||||||
// list all errors that can be ignored in a bucket metadata operation.
|
|
||||||
var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound)
|
|
||||||
|
|
||||||
// Bucket operations
|
|
||||||
|
|
||||||
// MakeBucket - make a bucket.
|
|
||||||
func (er erasureObjects) MakeBucketWithLocation(ctx context.Context, bucket string, opts MakeBucketOptions) error {
|
|
||||||
defer NSUpdated(bucket, slashSeparator)
|
|
||||||
|
|
||||||
// Verify if bucket is valid.
|
|
||||||
if !isMinioMetaBucketName(bucket) {
|
|
||||||
if err := s3utils.CheckValidBucketNameStrict(bucket); err != nil {
|
|
||||||
return BucketNameInvalid{Bucket: bucket}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storageDisks := er.getDisks()
|
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
|
||||||
|
|
||||||
// Make a volume entry on all underlying storage disks.
|
|
||||||
for index := range storageDisks {
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
if storageDisks[index] != nil {
|
|
||||||
if err := storageDisks[index].MakeVol(ctx, bucket); err != nil {
|
|
||||||
if opts.ForceCreate && errors.Is(err, errVolumeExists) {
|
|
||||||
// No need to return error when force create was
|
|
||||||
// requested.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !errors.Is(err, errVolumeExists) {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errDiskNotFound
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
|
|
||||||
return toObjectErr(err, bucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
func undoDeleteBucket(storageDisks []StorageAPI, bucket string) {
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
|
||||||
// Undo previous make bucket entry on all underlying storage disks.
|
|
||||||
for index := range storageDisks {
|
|
||||||
if storageDisks[index] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
_ = storageDisks[index].MakeVol(context.Background(), bucket)
|
|
||||||
return nil
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for all make vol to finish.
|
|
||||||
g.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBucketInfo - returns the BucketInfo from one of the load balanced disks.
|
|
||||||
func (er erasureObjects) getBucketInfo(ctx context.Context, bucketName string, opts BucketOptions) (bucketInfo BucketInfo, err error) {
|
|
||||||
storageDisks := er.getDisks()
|
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
|
||||||
bucketsInfo := make([]BucketInfo, len(storageDisks))
|
|
||||||
// Undo previous make bucket entry on all underlying storage disks.
|
|
||||||
for index := range storageDisks {
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
if storageDisks[index] == nil {
|
|
||||||
return errDiskNotFound
|
|
||||||
}
|
|
||||||
volInfo, err := storageDisks[index].StatVol(ctx, bucketName)
|
|
||||||
if err != nil {
|
|
||||||
if opts.Deleted {
|
|
||||||
dvi, derr := storageDisks[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucketName))
|
|
||||||
if derr != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bucketsInfo[index] = BucketInfo{Name: bucketName, Deleted: dvi.Created}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bucketsInfo[index] = BucketInfo{Name: volInfo.Name, Created: volInfo.Created}
|
|
||||||
return nil
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := g.Wait()
|
|
||||||
|
|
||||||
for i, err := range errs {
|
|
||||||
if err == nil {
|
|
||||||
return bucketsInfo[i], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all our errors were ignored, then we try to
|
|
||||||
// reduce to one error based on read quorum.
|
|
||||||
// `nil` is deliberately passed for ignoredErrs
|
|
||||||
// because these errors were already ignored.
|
|
||||||
return BucketInfo{}, reduceReadQuorumErrs(ctx, errs, nil, er.defaultRQuorum())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBucketInfo - returns BucketInfo for a bucket.
|
|
||||||
func (er erasureObjects) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (bi BucketInfo, e error) {
|
|
||||||
bucketInfo, err := er.getBucketInfo(ctx, bucket, opts)
|
|
||||||
if err != nil {
|
|
||||||
return bi, toObjectErr(err, bucket)
|
|
||||||
}
|
|
||||||
return bucketInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBucket - deletes a bucket.
|
|
||||||
func (er erasureObjects) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
|
|
||||||
// Collect if all disks report volume not found.
|
|
||||||
defer NSUpdated(bucket, slashSeparator)
|
|
||||||
|
|
||||||
storageDisks := er.getDisks()
|
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
|
||||||
|
|
||||||
for index := range storageDisks {
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
if storageDisks[index] != nil {
|
|
||||||
return storageDisks[index].DeleteVol(ctx, bucket, opts.Force)
|
|
||||||
}
|
|
||||||
return errDiskNotFound
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for all the delete vols to finish.
|
|
||||||
dErrs := g.Wait()
|
|
||||||
|
|
||||||
if opts.Force {
|
|
||||||
for _, err := range dErrs {
|
|
||||||
if err != nil {
|
|
||||||
undoDeleteBucket(storageDisks, bucket)
|
|
||||||
return toObjectErr(err, bucket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := reduceWriteQuorumErrs(ctx, dErrs, bucketOpIgnoredErrs, er.defaultWQuorum())
|
|
||||||
if err == errErasureWriteQuorum && !opts.NoRecreate {
|
|
||||||
undoDeleteBucket(storageDisks, bucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil || errors.Is(err, errVolumeNotFound) {
|
|
||||||
var purgedDangling bool
|
|
||||||
// At this point we have `err == nil` but some errors might be `errVolumeNotEmpty`
|
|
||||||
// we should proceed to attempt a force delete of such buckets.
|
|
||||||
for index, err := range dErrs {
|
|
||||||
if err == errVolumeNotEmpty && storageDisks[index] != nil {
|
|
||||||
storageDisks[index].RenameFile(ctx, bucket, "", minioMetaTmpDeletedBucket, mustGetUUID())
|
|
||||||
purgedDangling = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we purged dangling buckets, ignore errVolumeNotFound error.
|
|
||||||
if purgedDangling {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if opts.SRDeleteOp == MarkDelete {
|
|
||||||
er.markDelete(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix, bucket))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toObjectErr(err, bucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
// markDelete creates a vol entry in .minio.sys/buckets/.deleted until site replication
|
|
||||||
// syncs the delete to peers
|
|
||||||
func (er erasureObjects) markDelete(ctx context.Context, bucket, prefix string) error {
|
|
||||||
storageDisks := er.getDisks()
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
|
||||||
// Make a volume entry on all underlying storage disks.
|
|
||||||
for index := range storageDisks {
|
|
||||||
index := index
|
|
||||||
if storageDisks[index] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
g.Go(func() error {
|
|
||||||
if err := storageDisks[index].MakeVol(ctx, pathJoin(bucket, prefix)); err != nil {
|
|
||||||
if errors.Is(err, errVolumeExists) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
|
|
||||||
return toObjectErr(err, bucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
// purgeDelete deletes vol entry in .minio.sys/buckets/.deleted after site replication
|
|
||||||
// syncs the delete to peers OR on a new MakeBucket call.
|
|
||||||
func (er erasureObjects) purgeDelete(ctx context.Context, bucket, prefix string) error {
|
|
||||||
storageDisks := er.getDisks()
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
|
||||||
// Make a volume entry on all underlying storage disks.
|
|
||||||
for index := range storageDisks {
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
if storageDisks[index] != nil {
|
|
||||||
return storageDisks[index].DeleteVol(ctx, pathJoin(bucket, prefix), true)
|
|
||||||
}
|
|
||||||
return errDiskNotFound
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
|
|
||||||
return toObjectErr(err, bucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
|
|
||||||
func (er erasureObjects) IsNotificationSupported() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
|
||||||
func (er erasureObjects) IsListenSupported() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEncryptionSupported returns whether server side encryption is implemented for this layer.
|
|
||||||
func (er erasureObjects) IsEncryptionSupported() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCompressionSupported returns whether compression is applicable for this layer.
|
|
||||||
func (er erasureObjects) IsCompressionSupported() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTaggingSupported indicates whether erasureObjects implements tagging support.
|
|
||||||
func (er erasureObjects) IsTaggingSupported() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
+11
-3
@@ -22,6 +22,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,9 +84,16 @@ func (e *Erasure) Encode(ctx context.Context, src io.Reader, writers []io.Writer
|
|||||||
for {
|
for {
|
||||||
var blocks [][]byte
|
var blocks [][]byte
|
||||||
n, err := io.ReadFull(src, buf)
|
n, err := io.ReadFull(src, buf)
|
||||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
if !IsErrIgnored(err, []error{
|
||||||
return 0, err
|
io.EOF,
|
||||||
|
io.ErrUnexpectedEOF,
|
||||||
|
}...) {
|
||||||
|
if !hash.IsChecksumMismatch(err) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
eof := err == io.EOF || err == io.ErrUnexpectedEOF
|
eof := err == io.EOF || err == io.ErrUnexpectedEOF
|
||||||
if n == 0 && total != 0 {
|
if n == 0 && total != 0 {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// commonTime returns a maximally occurring time from a list of time.
|
// commonTime returns a maximally occurring time from a list of time.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user