mirror of
https://github.com/pgsty/minio.git
synced 2026-11-13 19:17:40 +03:00
Compare commits
216 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1a49be639 | |||
| 03172b89e2 | |||
| b517c791e9 | |||
| 14aef52004 | |||
| 67b20125e4 | |||
| d4b822d697 | |||
| 1b63291ee2 | |||
| aa7244a9a4 | |||
| 2a79ea0332 | |||
| 6e5c61d917 | |||
| 02e7de6367 | |||
| cec12f4c76 | |||
| da676ac298 | |||
| 18ec933085 | |||
| c31d2c3fdc | |||
| 8778828a03 | |||
| 48b212dd8e | |||
| be7de911c4 | |||
| 8cad407e0b | |||
| 85d2187c20 | |||
| 98d3f94996 | |||
| 173284903b | |||
| c70240b893 | |||
| 8ba2136e06 | |||
| 2dce5d9442 | |||
| f28b063091 | |||
| 90abea5b7a | |||
| c5b2a8441b | |||
| 0f5ca83418 | |||
| 8a6b13c239 | |||
| 8e8a792d9d | |||
| 95e0acbb26 | |||
| 55037e6e54 | |||
| 289e1d8b2a | |||
| e07918abe3 | |||
| ffea6fcf09 | |||
| 11b2220696 | |||
| aa8450a2a1 | |||
| 87cce344f6 | |||
| 7d4a2d2b68 | |||
| cfc8b92dff | |||
| c4e12dc846 | |||
| a94a9c37fa | |||
| 79b6a43467 | |||
| 928de04f7a | |||
| 93fd248b52 | |||
| 2a7b123895 | |||
| b3c56b53fb | |||
| 0ef3e359d8 | |||
| f24d8127ab | |||
| 7875d472bc | |||
| 711adb9652 | |||
| e6b4ea7618 | |||
| 466e95bb59 | |||
| 881f98e511 | |||
| cbf4bb62e0 | |||
| 682482459d | |||
| b87fae0049 | |||
| b8b44c879f | |||
| f53d1de87f | |||
| 5a18d437ce | |||
| 93eb549a83 | |||
| fe3c39b583 | |||
| 84d400487f | |||
| 3afa499885 | |||
| 13d015cf93 | |||
| 876b79b8d8 | |||
| 3d74efa6b1 | |||
| 68d299e719 | |||
| f9c5636c2d | |||
| 9b10118d34 | |||
| 0e3211f4ad | |||
| 2e4d9124ad | |||
| 6fef4c21b9 | |||
| 8e1bbd989a | |||
| 152d7cd95b | |||
| 74080bf108 | |||
| 647a209c73 | |||
| 0d057c777a | |||
| 275f7a63e8 | |||
| 97fe57bba9 | |||
| 88c1bb0720 | |||
| 1fdafaf72f | |||
| 5fe4bb6b36 | |||
| 99b733d44c | |||
| b4ac05523b | |||
| c7eacba41c | |||
| 1887c25279 | |||
| c9b0f595b9 | |||
| 8bb580abfc | |||
| af9cb5f5f2 | |||
| 9497dfd804 | |||
| da55a05587 | |||
| 3fc4d6f620 | |||
| 67a8f37df0 | |||
| 075c429021 | |||
| df0c678167 | |||
| f108873c48 | |||
| 871b450dbd | |||
| f71e192343 | |||
| b3f81e75f6 | |||
| a71e0483c9 | |||
| f2d49ec21a | |||
| 4a9d9c8585 | |||
| c885777ac6 | |||
| fe3aca70c3 | |||
| c4848f9b4f | |||
| 838d4dafbd | |||
| 8c663f93f7 | |||
| b4cb7edf85 | |||
| e96fdcd5ec | |||
| 6ef678663e | |||
| f737a027cf | |||
| 547efecd82 | |||
| 65aa2bc614 | |||
| de4421d6a3 | |||
| 6c3467e300 | |||
| 33554651e9 | |||
| 451d9057f3 | |||
| c82aef0a56 | |||
| 1e53bf2789 | |||
| d48ff93ba3 | |||
| c8489a8f0c | |||
| 2680772d4b | |||
| 567f7bdd05 | |||
| 6cd255d516 | |||
| e79829b5b3 | |||
| fd3f02637a | |||
| e019f21bda | |||
| e9ac7b0fb7 | |||
| 1debd722b5 | |||
| e7f6051f19 | |||
| 6717295e18 | |||
| 00cff1aac5 | |||
| 9722531817 | |||
| 5c6bfae4c7 | |||
| 5f51ef0b40 | |||
| 7e266293e6 | |||
| eb6871ecd9 | |||
| 9cdd981ce7 | |||
| bd8020aba8 | |||
| 09bc49bd51 | |||
| 82f0471d1b | |||
| 0bf2d84f96 | |||
| 6a95f412c9 | |||
| 7575c24037 | |||
| 43f973c4cf | |||
| 1b453728a3 | |||
| a6c146bd00 | |||
| a35cbb3ff3 | |||
| c080f04e66 | |||
| 2167ba0111 | |||
| 4e6d717f39 | |||
| 845e251fa9 | |||
| d1a8f0b786 | |||
| dac19d7272 | |||
| 7624c8b9bb | |||
| 19fb1086b2 | |||
| a5e23a40ff | |||
| 1ad2b7b699 | |||
| b5049d541f | |||
| e0055609bb | |||
| e8ce348da1 | |||
| 6bfa162342 | |||
| b4add82bb6 | |||
| 3bda8f755c | |||
| 3ca6330661 | |||
| 3d9000d5b5 | |||
| 3163a660aa | |||
| 0dadfd1b3d | |||
| 98f76008c7 | |||
| 8da0b7cf03 | |||
| 4315f93421 | |||
| ddb5d7043a | |||
| f903cae6ff | |||
| 40d59c1961 | |||
| 7090bcc8e0 | |||
| c222bde14b | |||
| 4e06a72632 | |||
| 16040bc544 | |||
| cc2d887e0e | |||
| c1b4b24236 | |||
| feaf8dfb9a | |||
| 628ef081d1 | |||
| 44dff36ff7 | |||
| b97d53b29c | |||
| 00af9881b0 | |||
| e09196d626 | |||
| 1a5775e2e8 | |||
| e2579b1f5a | |||
| 7824e19d20 | |||
| 317305d5f9 | |||
| e4e117faab | |||
| e8176fe978 | |||
| 828602d672 | |||
| d9224fbc65 | |||
| 51dad1d130 | |||
| 4593b146be | |||
| f21d650ed4 | |||
| a4f6705874 | |||
| b35b537e3f | |||
| 5c52d5ffc7 | |||
| f0808bb2e5 | |||
| a6dee21092 | |||
| 6f781c5e7a | |||
| f8ca859790 | |||
| b78521cd69 | |||
| 76e2713ffe | |||
| b5d291ea88 | |||
| eb9172eecb | |||
| 97a4c120e9 | |||
| 64bddf47d8 | |||
| 4ed45ce543 | |||
| ad511b0eb8 | |||
| cb0eaeaad8 | |||
| f3f0041ad0 |
@@ -1,2 +1,9 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
|
docs
|
||||||
|
default.etcd
|
||||||
|
browser
|
||||||
|
*.gz
|
||||||
|
*.tar.gz
|
||||||
|
*.bzip2
|
||||||
|
*.zip
|
||||||
|
|||||||
@@ -10,9 +10,10 @@
|
|||||||
## Types of changes
|
## Types of changes
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] Optimization (provides speedup with no functional changes)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
|
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
|
||||||
- [ ] Documentation needed
|
- [ ] Documentation updated
|
||||||
- [ ] Unit tests needed
|
- [ ] Unit tests added/updated
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.14.x, 1.15.x]
|
go-version: [1.15.x, 1.16.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
sudo apt-get install devscripts shellcheck
|
sudo apt-get install devscripts shellcheck
|
||||||
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
||||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-linux.amd64-${nancy_version} && chmod +x nancy
|
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
||||||
go list -m all | ./nancy sleuth
|
go list -m all | ./nancy sleuth
|
||||||
make
|
make
|
||||||
diff -au <(gofmt -s -d cmd) <(printf "")
|
diff -au <(gofmt -s -d cmd) <(printf "")
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
|||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ ARG TARGETARCH
|
|||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
||||||
|
|||||||
+5
-6
@@ -6,19 +6,18 @@ LABEL maintainer="MinIO Inc <dev@min.io>"
|
|||||||
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
COPY minio /usr/bin/
|
COPY minio /usr/bin/
|
||||||
COPY CREDITS /licenses/CREDITS
|
|
||||||
COPY LICENSE /licenses/LICENSE
|
|
||||||
|
|
||||||
ENV MINIO_UPDATE=off \
|
ENV MINIO_UPDATE=off \
|
||||||
MINIO_ACCESS_KEY_FILE=access_key \
|
MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
RUN \
|
RUN microdnf update --nodocs
|
||||||
microdnf update --nodocs && \
|
RUN microdnf install curl ca-certificates shadow-utils util-linux --nodocs
|
||||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
RUN microdnf clean all && \
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
chmod +x /usr/bin/minio && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh
|
chmod +x /usr/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -5,13 +5,15 @@ ARG TARGETARCH
|
|||||||
LABEL name="MinIO" \
|
LABEL name="MinIO" \
|
||||||
vendor="MinIO Inc <dev@min.io>" \
|
vendor="MinIO Inc <dev@min.io>" \
|
||||||
maintainer="MinIO Inc <dev@min.io>" \
|
maintainer="MinIO Inc <dev@min.io>" \
|
||||||
version="RELEASE.2020-12-29T23-29-29Z" \
|
version="RELEASE.2021-02-23T20-05-01Z" \
|
||||||
release="RELEASE.2020-12-29T23-29-29Z" \
|
release="RELEASE.2021-02-23T20-05-01Z" \
|
||||||
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
||||||
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ checks:
|
|||||||
getdeps:
|
getdeps:
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
|
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
|
||||||
@which ruleguard 1>/dev/null || (echo "Installing ruleguard" && GO111MODULE=off go get github.com/quasilyte/go-ruleguard/...)
|
@which ruleguard 1>/dev/null || (echo "Installing ruleguard" && go get github.com/quasilyte/go-ruleguard/cmd/ruleguard@v0.2.1)
|
||||||
@which msgp 1>/dev/null || (echo "Installing msgp" && GO111MODULE=off go get github.com/tinylib/msgp)
|
@which msgp 1>/dev/null || (echo "Installing msgp" && go get github.com/tinylib/msgp@v1.1.3)
|
||||||
@which stringer 1>/dev/null || (echo "Installing stringer" && GO111MODULE=off go get golang.org/x/tools/cmd/stringer)
|
@which stringer 1>/dev/null || (echo "Installing stringer" && go get golang.org/x/tools/cmd/stringer)
|
||||||
|
|
||||||
crosscompile:
|
crosscompile:
|
||||||
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
||||||
@@ -38,7 +38,7 @@ fmt:
|
|||||||
lint:
|
lint:
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
|
@GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
|
||||||
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
|
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=10m --config ./.golangci.yml
|
||||||
|
|
||||||
ruleguard:
|
ruleguard:
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@@ -71,13 +71,18 @@ build: checks
|
|||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|
||||||
hotfix: LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
|
hotfix-vars:
|
||||||
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#'))
|
$(eval LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix.$(shell git rev-parse --short HEAD)" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
|
||||||
hotfix: install
|
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#')))
|
||||||
|
$(eval TAG := "minio/minio:$(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD)")
|
||||||
|
hotfix: hotfix-vars install
|
||||||
|
|
||||||
docker: checks
|
docker-hotfix: hotfix checks
|
||||||
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
|
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||||
|
|
||||||
|
docker: build checks
|
||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||||
|
|
||||||
# Builds minio and installs it to $GOPATH/bin.
|
# Builds minio and installs it to $GOPATH/bin.
|
||||||
|
|||||||
@@ -5,80 +5,175 @@
|
|||||||
|
|
||||||
MinIO is a High Performance Object Storage released under Apache License v2.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.
|
MinIO is a High Performance Object Storage released under Apache License v2.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.
|
||||||
|
|
||||||
## Docker Container
|
This README provides quickstart instructions on running MinIO on baremetal hardware, including Docker-based installations. For Kubernetes environments,
|
||||||
### Stable
|
use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
|
||||||
```
|
|
||||||
docker run -p 9000:9000 \
|
# Docker Installation
|
||||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
|
||||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
Use the following commands to run a standalone MinIO server on a Docker container.
|
||||||
minio/minio server /data
|
|
||||||
|
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
||||||
|
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
||||||
|
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
||||||
|
for more complete documentation.
|
||||||
|
|
||||||
|
## Stable
|
||||||
|
|
||||||
|
Run the following command to run the latest stable image of MinIO on a Docker container using an ephemeral data volume:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -p 9000:9000 minio/minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
### Edge
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Browser, an embedded
|
||||||
|
web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
||||||
|
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
|
see https://docs.min.io/docs/ and click **MINIO SDKS** in the navigation to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
|
|
||||||
|
> NOTE: To deploy MinIO on Docker with persistent storage, you must map local persistent directories from the host OS to the container using the
|
||||||
|
`docker -v` option. For example, `-v /mnt/data:/data` maps the host OS drive at `/mnt/data` to `/data` on the Docker container.
|
||||||
|
|
||||||
|
## Edge
|
||||||
|
|
||||||
|
Run the following command to run the bleeding-edge image of MinIO on a Docker container using an ephemeral data volume:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -p 9000:9000 \
|
docker run -p 9000:9000 minio/minio:edge server /data
|
||||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
|
||||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
|
||||||
minio/minio:edge server /data
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> NOTE: Docker will not display the default keys unless you start the container with the `-it`(interactive TTY) argument. Generally, it is not recommended to use default keys with containers. Please visit MinIO Docker quickstart guide for more information [here](https://docs.min.io/docs/minio-docker-quickstart-guide)
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Browser, an embedded
|
||||||
|
web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
||||||
|
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
|
see https://docs.min.io/docs/ and click **MINIO SDKS** in the navigation to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
|
|
||||||
|
> NOTE: To deploy MinIO on Docker with persistent storage, you must map local persistent directories from the host OS to the container using the
|
||||||
|
`docker -v` option. For example, `-v /mnt/data:/data` maps the host OS drive at `/mnt/data` to `/data` on the Docker container.
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
|
||||||
|
Use the following commands to run a standalone MinIO server on macOS.
|
||||||
|
|
||||||
|
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
||||||
|
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
||||||
|
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
||||||
|
for more complete documentation.
|
||||||
|
|
||||||
|
## Homebrew (recommended)
|
||||||
|
|
||||||
|
Run the following command to install the latest stable MinIO package using [Homebrew](https://brew.sh/). Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
||||||
|
|
||||||
## macOS
|
|
||||||
### Homebrew (recommended)
|
|
||||||
Install minio packages using [Homebrew](https://brew.sh/)
|
|
||||||
```sh
|
```sh
|
||||||
brew install minio/stable/minio
|
brew install minio/stable/minio
|
||||||
minio server /data
|
minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
> NOTE: If you previously installed minio using `brew install minio` then it is recommended that you reinstall minio from `minio/stable/minio` official repo instead.
|
> NOTE: If you previously installed minio using `brew install minio` then it is recommended that you reinstall minio from `minio/stable/minio` official repo instead.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
brew uninstall minio
|
brew uninstall minio
|
||||||
brew install minio/stable/minio
|
brew install minio/stable/minio
|
||||||
```
|
```
|
||||||
|
|
||||||
### Binary Download
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Browser, an embedded
|
||||||
| Platform | Architecture | URL |
|
web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
||||||
| ---------- | -------- | ------ |
|
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
| Apple macOS | 64-bit Intel | https://dl.min.io/server/minio/release/darwin-amd64/minio |
|
|
||||||
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
|
see https://docs.min.io/docs/ and click **MINIO SDKS** in the navigation to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
|
## Binary Download
|
||||||
|
|
||||||
|
Use the following command to download and run a standalone MinIO server on macOS. Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
chmod 755 minio
|
wget https://dl.min.io/server/minio/release/darwin-amd64/minio
|
||||||
|
chmod +x minio
|
||||||
./minio server /data
|
./minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
## GNU/Linux
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Browser, an embedded
|
||||||
### Binary Download
|
web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
||||||
| Platform | Architecture | URL |
|
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
| ---------- | -------- | ------ |
|
|
||||||
| GNU/Linux | 64-bit Intel | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
|
see https://docs.min.io/docs/ and click **MINIO SDKS** in the navigation to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
|
|
||||||
|
# GNU/Linux
|
||||||
|
|
||||||
|
Use the following command to run a standalone MinIO server on Linux hosts running 64-bit Intel/AMD architectures. Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
||||||
chmod +x minio
|
chmod +x minio
|
||||||
./minio server /data
|
./minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
| Platform | Architecture | URL |
|
Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
||||||
| ---------- | -------- | ------ |
|
|
||||||
| GNU/Linux | ppc64le | https://dl.min.io/server/minio/release/linux-ppc64le/minio |
|
The following table lists supported architectures. Replace the `wget` URL with the architecture for your Linux host.
|
||||||
|
|
||||||
|
| Architecture | URL |
|
||||||
|
| -------- | ------ |
|
||||||
|
| 64-bit Intel/AMD | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
||||||
|
| 64-bit ARM | https://dl.min.io/server/minio/release/linux-arm64/minio |
|
||||||
|
| 64-bit PowerPC LE (ppc64le) | https://dl.min.io/server/minio/release/linux-ppc64le/minio |
|
||||||
|
| IBM Z-Series (S390X) | https://dl.min.io/server/minio/release/linux-s390x/minio |
|
||||||
|
|
||||||
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Browser, an embedded
|
||||||
|
web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
||||||
|
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
|
see https://docs.min.io/docs/ and click **MINIO SDKS** in the navigation to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
|
|
||||||
|
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
||||||
|
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
||||||
|
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
||||||
|
for more complete documentation.
|
||||||
|
|
||||||
|
# Microsoft Windows
|
||||||
|
|
||||||
|
To run MinIO on 64-bit Windows hosts, download the MinIO executable from the following URL:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
wget https://dl.min.io/server/minio/release/linux-ppc64le/minio
|
https://dl.min.io/server/minio/release/windows-amd64/minio.exe
|
||||||
chmod +x minio
|
|
||||||
./minio server /data
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Microsoft Windows
|
Use the following command to run a standalone MinIO server on the Windows host. Replace ``D:\`` with the path to the drive or directory in which you want MinIO to store data. You must change the terminal or powershell directory to the location of the ``minio.exe`` executable, *or* add the path to that directory to the system ``$PATH``:
|
||||||
### Binary Download
|
|
||||||
| Platform | Architecture | URL |
|
|
||||||
| ---------- | -------- | ------ |
|
|
||||||
| Microsoft Windows | 64-bit | https://dl.min.io/server/minio/release/windows-amd64/minio.exe |
|
|
||||||
```sh
|
```sh
|
||||||
minio.exe server D:\Photos
|
minio.exe server D:\
|
||||||
```
|
```
|
||||||
|
|
||||||
## FreeBSD
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Browser, an embedded
|
||||||
### Port
|
web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
||||||
Install minio packages using [pkg](https://github.com/freebsd/pkg), MinIO doesn't officially build FreeBSD binaries but is maintained by FreeBSD upstream [here](https://www.freshports.org/www/minio).
|
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
|
see https://docs.min.io/docs/ and click **MINIO SDKS** in the navigation to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
|
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
||||||
|
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
||||||
|
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
||||||
|
for more complete documentation.
|
||||||
|
|
||||||
|
# FreeBSD
|
||||||
|
|
||||||
|
MinIO does not provide an official FreeBSD binary. However, FreeBSD maintains an [upstream release](https://www.freshports.org/www/minio) using [pkg](https://github.com/freebsd/pkg):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg install minio
|
pkg install minio
|
||||||
@@ -87,13 +182,32 @@ sysrc minio_disks=/home/user/Photos
|
|||||||
service minio start
|
service minio start
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install from Source
|
# Install 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.15](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.15](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
GO111MODULE=on go get github.com/minio/minio
|
GO111MODULE=on go get github.com/minio/minio
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Browser, an embedded
|
||||||
|
web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
||||||
|
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
||||||
|
|
||||||
|
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
||||||
|
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
||||||
|
see https://docs.min.io/docs/ and click **MINIO SDKS** in the navigation to view MinIO SDKs for supported languages.
|
||||||
|
|
||||||
|
|
||||||
|
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
||||||
|
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
||||||
|
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
||||||
|
for more complete documentation.
|
||||||
|
|
||||||
|
MinIO strongly recommends *against* using compiled-from-source MinIO servers for production environments.
|
||||||
|
|
||||||
|
# Deployment Recommendations
|
||||||
|
|
||||||
## Allow port access for Firewalls
|
## Allow port access for Firewalls
|
||||||
|
|
||||||
By default MinIO uses the port 9000 to listen for incoming connections. If your platform blocks the port by default, you may need to enable access to the port.
|
By default MinIO uses the port 9000 to listen for incoming connections. If your platform blocks the port by default, you may need to enable access to the port.
|
||||||
@@ -149,6 +263,13 @@ iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
|
|||||||
service iptables restart
|
service iptables restart
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Pre-existing data
|
||||||
|
When deployed on a single drive, MinIO server lets clients access any pre-existing data in the data directory. For example, if MinIO is started with the command `minio server /mnt/data`, any pre-existing data in the `/mnt/data` directory would be accessible to the clients.
|
||||||
|
|
||||||
|
The above statement is also valid for all gateway backends.
|
||||||
|
|
||||||
|
# Test MinIO Connectivity
|
||||||
|
|
||||||
## Test using MinIO Browser
|
## Test using MinIO Browser
|
||||||
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 to ensure your server has started successfully.
|
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 to ensure your server has started successfully.
|
||||||
|
|
||||||
@@ -157,12 +278,7 @@ MinIO Server comes with an embedded web based object browser. Point your web bro
|
|||||||
## Test using MinIO Client `mc`
|
## Test using MinIO Client `mc`
|
||||||
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the MinIO Client [Quickstart Guide](https://docs.min.io/docs/minio-client-quickstart-guide) for further instructions.
|
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the MinIO Client [Quickstart Guide](https://docs.min.io/docs/minio-client-quickstart-guide) for further instructions.
|
||||||
|
|
||||||
## Pre-existing data
|
# Upgrading MinIO
|
||||||
When deployed on a single drive, MinIO server lets clients access any pre-existing data in the data directory. For example, if MinIO is started with the command `minio server /mnt/data`, any pre-existing data in the `/mnt/data` directory would be accessible to the clients.
|
|
||||||
|
|
||||||
The above statement is also valid for all gateway backends.
|
|
||||||
|
|
||||||
## Upgrading MinIO
|
|
||||||
MinIO server supports rolling upgrades, i.e. you can update one MinIO instance at a time in a distributed cluster. This allows upgrades with no downtime. Upgrades can be done manually by replacing the binary with the latest release and restarting all servers in a rolling fashion. However, we recommend all our users to use [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) from the client. This will update all the nodes in the cluster simultaneously and restart them, as shown in the following command from the MinIO client (mc):
|
MinIO server supports rolling upgrades, i.e. you can update one MinIO instance at a time in a distributed cluster. This allows upgrades with no downtime. Upgrades can be done manually by replacing the binary with the latest release and restarting all servers in a rolling fashion. However, we recommend all our users to use [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) from the client. This will update all the nodes in the cluster simultaneously and restart them, as shown in the following command from the MinIO client (mc):
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -171,7 +287,7 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
|
|
||||||
> NOTE: some releases might not allow rolling upgrades, this is always called out in the release notes and it is generally advised to read release notes before upgrading. In such a situation `mc admin update` is the recommended upgrading mechanism to upgrade all servers at once.
|
> NOTE: some releases might not allow rolling upgrades, this is always called out in the release notes and it is generally advised to read release notes before upgrading. In such a situation `mc admin update` is the recommended upgrading mechanism to upgrade all servers at once.
|
||||||
|
|
||||||
### Important things to remember during MinIO upgrades
|
## Important things to remember during MinIO upgrades
|
||||||
|
|
||||||
- `mc admin update` will only work if the user running MinIO has write access to the parent directory where the binary is located, for example if the current binary is at `/usr/local/bin/minio`, you would need write access to `/usr/local/bin`.
|
- `mc admin update` will only work if the user running MinIO has write access to the parent directory where the binary is located, for example if the current binary is at `/usr/local/bin/minio`, you would need write access to `/usr/local/bin`.
|
||||||
- `mc admin update` updates and restarts all servers simultaneously, applications would retry and continue their respective operations upon upgrade.
|
- `mc admin update` updates and restarts all servers simultaneously, applications would retry and continue their respective operations upon upgrade.
|
||||||
@@ -181,7 +297,7 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
- If using Vault as KMS with MinIO, ensure you have followed the Vault upgrade procedure outlined here: https://www.vaultproject.io/docs/upgrading/index.html
|
- If using Vault as KMS with MinIO, ensure you have followed the Vault upgrade procedure outlined here: https://www.vaultproject.io/docs/upgrading/index.html
|
||||||
- If using etcd with MinIO for the federation, ensure you have followed the etcd upgrade procedure outlined here: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
- If using etcd with MinIO for the federation, ensure you have followed the etcd upgrade procedure outlined here: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
||||||
|
|
||||||
## Explore Further
|
# Explore Further
|
||||||
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
||||||
- [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide)
|
- [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide)
|
||||||
- [Use `aws-cli` with MinIO Server](https://docs.min.io/docs/aws-cli-with-minio)
|
- [Use `aws-cli` with MinIO Server](https://docs.min.io/docs/aws-cli-with-minio)
|
||||||
@@ -189,8 +305,8 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
- [Use `minio-go` SDK with MinIO Server](https://docs.min.io/docs/golang-client-quickstart-guide)
|
- [Use `minio-go` SDK with MinIO Server](https://docs.min.io/docs/golang-client-quickstart-guide)
|
||||||
- [The MinIO documentation website](https://docs.min.io)
|
- [The MinIO documentation website](https://docs.min.io)
|
||||||
|
|
||||||
## Contribute to MinIO Project
|
# Contribute to MinIO Project
|
||||||
Please follow MinIO [Contributor's Guide](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
Please follow MinIO [Contributor's Guide](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
## License
|
# License
|
||||||
Use of MinIO is governed by the Apache 2.0 License found at [LICENSE](https://github.com/minio/minio/blob/master/LICENSE).
|
Use of MinIO is governed by the Apache 2.0 License found at [LICENSE](https://github.com/minio/minio/blob/master/LICENSE).
|
||||||
|
|||||||
+8
-8
@@ -8,16 +8,16 @@ MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合
|
|||||||
### 稳定版
|
### 稳定版
|
||||||
```
|
```
|
||||||
docker run -p 9000:9000 \
|
docker run -p 9000:9000 \
|
||||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
-e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
|
||||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
-e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||||
minio/minio server /data
|
minio/minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
### 尝鲜版
|
### 尝鲜版
|
||||||
```
|
```
|
||||||
docker run -p 9000:9000 \
|
docker run -p 9000:9000 \
|
||||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
-e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
|
||||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
-e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||||
minio/minio:edge server /data
|
minio/minio:edge server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ minio.exe server D:\Photos
|
|||||||
|
|
||||||
## FreeBSD
|
## FreeBSD
|
||||||
### Port
|
### Port
|
||||||
使用 [pkg](https://github.com/freebsd/pkg)进行安装,, MinIO官方并没有提供FreeBSD二进制文件, 它由FreeBSD上游维护,点击 [这里](https://www.freshports.org/www/minio)查看。
|
使用 [pkg](https://github.com/freebsd/pkg)进行安装,MinIO官方并没有提供FreeBSD二进制文件, 它由FreeBSD上游维护,点击 [这里](https://www.freshports.org/www/minio)查看。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg install minio
|
pkg install minio
|
||||||
@@ -89,7 +89,7 @@ service minio start
|
|||||||
|
|
||||||
## 使用源码安装
|
## 使用源码安装
|
||||||
|
|
||||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install)。最低需要Golang版本为 [go1.14](https://golang.org/dl/#stable)
|
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install)。最低需要Golang版本为 [go1.15](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
GO111MODULE=on go get github.com/minio/minio
|
GO111MODULE=on go get github.com/minio/minio
|
||||||
@@ -179,7 +179,7 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
- 对于联盟部署模式,应分别针对每个群集运行`mc admin update`。 在成功更新所有群集之前,不要将`mc`更新为任何新版本。
|
- 对于联盟部署模式,应分别针对每个群集运行`mc admin update`。 在成功更新所有群集之前,不要将`mc`更新为任何新版本。
|
||||||
- 如果将`kes`用作MinIO的KMS,只需替换二进制文件并重新启动`kes`,可以在 [这里](https://github.com/minio/kes/wiki) 找到有关`kes`的更多信息。
|
- 如果将`kes`用作MinIO的KMS,只需替换二进制文件并重新启动`kes`,可以在 [这里](https://github.com/minio/kes/wiki) 找到有关`kes`的更多信息。
|
||||||
- 如果将Vault作为MinIO的KMS,请确保已遵循如下Vault升级过程的概述:https://www.vaultproject.io/docs/upgrading/index.html
|
- 如果将Vault作为MinIO的KMS,请确保已遵循如下Vault升级过程的概述:https://www.vaultproject.io/docs/upgrading/index.html
|
||||||
- 如果将MindIO与etcd配合使用, 请确保已遵循如下etcd升级过程的概述: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
- 如果将MinIO与etcd配合使用, 请确保已遵循如下etcd升级过程的概述: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
||||||
|
|
||||||
## 了解更多
|
## 了解更多
|
||||||
- [MinIO纠删码入门](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
- [MinIO纠删码入门](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
||||||
@@ -190,7 +190,7 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
- [MinIO文档](https://docs.min.io)
|
- [MinIO文档](https://docs.min.io)
|
||||||
|
|
||||||
## 如何参与到MinIO项目
|
## 如何参与到MinIO项目
|
||||||
请参考 [贡献者指南](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)。欢迎各位中国程序员加到MinIO项目中。
|
请参考 [贡献者指南](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)。欢迎各位中国程序员加入到MinIO项目中。
|
||||||
|
|
||||||
## 授权许可
|
## 授权许可
|
||||||
MinIO的使用受 Apache 2.0 License 约束,你可以在 [LICENSE](./LICENSE) 查看许可。
|
MinIO的使用受 Apache 2.0 License 约束,你可以在 [LICENSE](./LICENSE) 查看许可。
|
||||||
|
|||||||
@@ -138,9 +138,10 @@ describe("Uploads actions", () => {
|
|||||||
objects: { currentPrefix: "pre1/" }
|
objects: { currentPrefix: "pre1/" }
|
||||||
})
|
})
|
||||||
store.dispatch(uploadsActions.uploadFile(file))
|
store.dispatch(uploadsActions.uploadFile(file))
|
||||||
|
const objectPath = encodeURIComponent("pre1/file1")
|
||||||
expect(open).toHaveBeenCalledWith(
|
expect(open).toHaveBeenCalledWith(
|
||||||
"PUT",
|
"PUT",
|
||||||
"https://localhost:8080/upload/test1/pre1/file1",
|
"https://localhost:8080/upload/test1/" + objectPath,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
expect(send).toHaveBeenCalledWith(file)
|
expect(send).toHaveBeenCalledWith(file)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const uploadFile = file => {
|
|||||||
_filePath = _filePath.substring(1)
|
_filePath = _filePath.substring(1)
|
||||||
}
|
}
|
||||||
const filePath = _filePath
|
const filePath = _filePath
|
||||||
const objectName = `${currentPrefix}${filePath}`
|
const objectName = encodeURIComponent(`${currentPrefix}${filePath}`)
|
||||||
const uploadUrl = `${
|
const uploadUrl = `${
|
||||||
window.location.origin
|
window.location.origin
|
||||||
}${minioBrowserPrefix}/upload/${currentBucket}/${objectName}`
|
}${minioBrowserPrefix}/upload/${currentBucket}/${objectName}`
|
||||||
|
|||||||
+47
-45
File diff suppressed because one or more lines are too long
@@ -1,69 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
set -e
|
|
||||||
set -E
|
|
||||||
set -o pipefail
|
|
||||||
|
|
||||||
function start_minio_server()
|
|
||||||
{
|
|
||||||
MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 \
|
|
||||||
minio --quiet --json server /data --address 127.0.0.1:24242 > server.log 2>&1 &
|
|
||||||
server_pid=$!
|
|
||||||
sleep 10
|
|
||||||
|
|
||||||
echo "$server_pid"
|
|
||||||
}
|
|
||||||
|
|
||||||
function start_minio_gateway_s3()
|
|
||||||
{
|
|
||||||
MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 \
|
|
||||||
minio --quiet --json gateway s3 http://127.0.0.1:24242 \
|
|
||||||
--address 127.0.0.1:24240 > gateway.log 2>&1 &
|
|
||||||
gw_pid=$!
|
|
||||||
sleep 10
|
|
||||||
|
|
||||||
echo "$gw_pid"
|
|
||||||
}
|
|
||||||
|
|
||||||
function main()
|
|
||||||
{
|
|
||||||
sr_pid="$(start_minio_server)"
|
|
||||||
gw_pid="$(start_minio_gateway_s3)"
|
|
||||||
|
|
||||||
SERVER_ENDPOINT=127.0.0.1:24240 ENABLE_HTTPS=0 ACCESS_KEY=minio \
|
|
||||||
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh \
|
|
||||||
aws-sdk-go aws-sdk-java aws-sdk-php aws-sdk-ruby awscli \
|
|
||||||
healthcheck mc minio-dotnet minio-js \
|
|
||||||
minio-py s3cmd s3select security
|
|
||||||
rv=$?
|
|
||||||
|
|
||||||
kill "$sr_pid"
|
|
||||||
kill "$gw_pid"
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
|
||||||
echo "=========== Gateway ==========="
|
|
||||||
cat "gateway.log"
|
|
||||||
echo "=========== Server ==========="
|
|
||||||
cat "server.log"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f gateway.log server.log
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
for d in $(go list ./... | grep -v browser); do
|
for d in $(go list ./... | grep -v browser); do
|
||||||
CGO_ENABLED=1 go test -v -race --timeout 50m "$d"
|
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ function start_minio_erasure_sets()
|
|||||||
|
|
||||||
function start_minio_pool_erasure_sets()
|
function start_minio_pool_erasure_sets()
|
||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
"${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||||
@@ -74,8 +74,8 @@ function start_minio_pool_erasure_sets()
|
|||||||
|
|
||||||
function start_minio_pool_erasure_sets_ipv6()
|
function start_minio_pool_erasure_sets_ipv6()
|
||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
"${MINIO[@]}" server --address="[::1]:9000" > "$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9000" > "$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||||
@@ -85,8 +85,8 @@ function start_minio_pool_erasure_sets_ipv6()
|
|||||||
|
|
||||||
function start_minio_dist_erasure()
|
function start_minio_dist_erasure()
|
||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
||||||
for i in $(seq 0 3); do
|
for i in $(seq 0 3); do
|
||||||
"${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
"${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
|||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
function start_minio_3_node() {
|
function start_minio_3_node() {
|
||||||
export MINIO_ACCESS_KEY=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_SECRET_KEY=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
|
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|||||||
+4
-4
@@ -61,7 +61,7 @@ type accessControlPolicy struct {
|
|||||||
func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketACL")
|
ctx := newContext(r, w, "PutBucketACL")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketACL", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -125,7 +125,7 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketACL")
|
ctx := newContext(r, w, "GetBucketACL")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketACL", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -176,7 +176,7 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutObjectACL")
|
ctx := newContext(r, w, "PutObjectACL")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutObjectACL", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -240,7 +240,7 @@ func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.
|
|||||||
func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetObjectACL")
|
ctx := newContext(r, w, "GetObjectACL")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetObjectACL", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const (
|
|||||||
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketQuotaConfig")
|
ctx := newContext(r, w, "PutBucketQuotaConfig")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketQuotaConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -81,7 +81,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketQuotaConfig")
|
ctx := newContext(r, w, "GetBucketQuotaConfig")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketQuotaConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -116,7 +116,7 @@ func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetBucketTarget")
|
ctx := newContext(r, w, "SetBucketTarget")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
update := r.URL.Query().Get("update") == "true"
|
update := r.URL.Query().Get("update") == "true"
|
||||||
@@ -156,7 +156,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sameTarget, _ := isLocalHost(target.URL().Hostname(), target.URL().Port(), globalMinioPort)
|
sameTarget, _ := isLocalHost(target.URL().Hostname(), target.URL().Port(), globalMinioPort)
|
||||||
if sameTarget && bucket == target.TargetBucket {
|
if sameTarget && bucket == target.TargetBucket {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
||||||
@@ -205,7 +204,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListBucketTargets")
|
ctx := newContext(r, w, "ListBucketTargets")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListBucketTargets", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
arnType := vars["type"]
|
arnType := vars["type"]
|
||||||
@@ -244,7 +243,7 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RemoveBucketTarget")
|
ctx := newContext(r, w, "RemoveBucketTarget")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "RemoveBucketTarget", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
arn := vars["arn"]
|
arn := vars["arn"]
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *htt
|
|||||||
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteConfigKV")
|
ctx := newContext(r, w, "DeleteConfigKV")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteConfigKV", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -104,7 +104,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
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")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "SetConfigKV", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -130,13 +130,14 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
|
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, objectAPI.SetDriveCount()); err != nil {
|
if err = validateConfig(cfg, objectAPI.SetDriveCounts()); err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -158,15 +159,14 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply dynamic values.
|
|
||||||
if err := applyDynamicConfig(GlobalContext, cfg); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
globalNotificationSys.SignalService(serviceReloadDynamic)
|
|
||||||
|
|
||||||
// If all values were dynamic, tell the client.
|
|
||||||
if dynamic {
|
if dynamic {
|
||||||
|
// Apply dynamic values.
|
||||||
|
if err := applyDynamicConfig(GlobalContext, objectAPI, cfg); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
globalNotificationSys.SignalService(serviceReloadDynamic)
|
||||||
|
// If all values were dynamic, tell the client.
|
||||||
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
||||||
}
|
}
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
@@ -176,7 +176,7 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetConfigKV")
|
ctx := newContext(r, w, "GetConfigKV")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetConfigKV", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -214,7 +214,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ClearConfigHistoryKV")
|
ctx := newContext(r, w, "ClearConfigHistoryKV")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ClearConfigHistoryKV", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -251,7 +251,7 @@ func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *
|
|||||||
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RestoreConfigHistoryKV")
|
ctx := newContext(r, w, "RestoreConfigHistoryKV")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "RestoreConfigHistoryKV", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -282,7 +282,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, objectAPI.SetDriveCount()); err != nil {
|
if err = validateConfig(cfg, objectAPI.SetDriveCounts()); err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -299,7 +299,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListConfigHistoryKV")
|
ctx := newContext(r, w, "ListConfigHistoryKV")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListConfigHistoryKV", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -339,7 +339,7 @@ func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *h
|
|||||||
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HelpConfigKV")
|
ctx := newContext(r, w, "HelpConfigKV")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "HelpHistoryKV", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
_, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -367,7 +367,7 @@ func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetConfig")
|
ctx := newContext(r, w, "SetConfig")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "SetConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -394,7 +394,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, objectAPI.SetDriveCount()); err != nil {
|
if err = validateConfig(cfg, objectAPI.SetDriveCounts()); err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -424,7 +424,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetConfig")
|
ctx := newContext(r, w, "GetConfig")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
cred, objectAPI := validateAdminReqConfigKV(ctx, w, r)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
|
|||||||
+49
-30
@@ -23,8 +23,10 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/minio/cmd/config/dns"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
@@ -56,7 +58,7 @@ func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.R
|
|||||||
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RemoveUser")
|
ctx := newContext(r, w, "RemoveUser")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "RemoveUser", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -94,7 +96,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListUsers")
|
ctx := newContext(r, w, "ListUsers")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListUsers", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -128,7 +130,7 @@ func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetUserInfo")
|
ctx := newContext(r, w, "GetUserInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetUserInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
name := vars["accessKey"]
|
name := vars["accessKey"]
|
||||||
@@ -184,7 +186,7 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "UpdateGroupMembers")
|
ctx := newContext(r, w, "UpdateGroupMembers")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "UpdateGroupMembers", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -229,7 +231,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetGroup")
|
ctx := newContext(r, w, "GetGroup")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetGroup", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetGroupAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetGroupAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -258,7 +260,7 @@ func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListGroups")
|
ctx := newContext(r, w, "ListGroups")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListGroups", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -284,7 +286,7 @@ func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetGroupStatus")
|
ctx := newContext(r, w, "SetGroupStatus")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "SetGroupStatus", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -321,7 +323,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetUserStatus")
|
ctx := newContext(r, w, "SetUserStatus")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "SetUserStatus", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -356,7 +358,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddUser")
|
ctx := newContext(r, w, "AddUser")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
accessKey := path.Clean(vars["accessKey"])
|
accessKey := path.Clean(vars["accessKey"])
|
||||||
@@ -426,7 +428,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalIAMSys.SetUser(accessKey, uinfo); err != nil {
|
if err = globalIAMSys.CreateUser(accessKey, uinfo); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -444,7 +446,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddServiceAccount")
|
ctx := newContext(r, w, "AddServiceAccount")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "AddServiceAccount", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -523,7 +525,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListServiceAccounts")
|
ctx := newContext(r, w, "ListServiceAccounts")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListServiceAccounts", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -578,7 +580,7 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteServiceAccount")
|
ctx := newContext(r, w, "DeleteServiceAccount")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteServiceAccount", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -637,7 +639,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AccountInfo")
|
ctx := newContext(r, w, "AccountInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "AccountInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -688,12 +690,6 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return rd, wr
|
return rd, wr
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, err := objectAPI.ListBuckets(ctx)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the latest calculated data usage
|
// Load the latest calculated data usage
|
||||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -701,11 +697,34 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountName := cred.AccessKey
|
// If etcd, dns federation configured list buckets from etcd.
|
||||||
if cred.ParentUser != "" {
|
var buckets []BucketInfo
|
||||||
accountName = cred.ParentUser
|
if globalDNSConfig != nil && globalBucketFederation {
|
||||||
|
dnsBuckets, err := globalDNSConfig.List()
|
||||||
|
if err != nil && !IsErrIgnored(err,
|
||||||
|
dns.ErrNoEntriesFound,
|
||||||
|
dns.ErrDomainMissing) {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, dnsRecords := range dnsBuckets {
|
||||||
|
buckets = append(buckets, BucketInfo{
|
||||||
|
Name: dnsRecords[0].Key,
|
||||||
|
Created: dnsRecords[0].CreationDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
return buckets[i].Name < buckets[j].Name
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
buckets, err = objectAPI.ListBuckets(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountName := cred.AccessKey
|
||||||
policies, err := globalIAMSys.PolicyDBGet(accountName, false)
|
policies, err := globalIAMSys.PolicyDBGet(accountName, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -751,7 +770,7 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "InfoCannedPolicyV2")
|
ctx := newContext(r, w, "InfoCannedPolicyV2")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "InfoCannedPolicyV2", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -778,7 +797,7 @@ func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "InfoCannedPolicy")
|
ctx := newContext(r, w, "InfoCannedPolicy")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "InfoCannedPolicy", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -802,7 +821,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
|
|||||||
func (a adminAPIHandlers) ListCannedPoliciesV2(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListCannedPoliciesV2(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListCannedPoliciesV2")
|
ctx := newContext(r, w, "ListCannedPoliciesV2")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListCannedPoliciesV2", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -836,7 +855,7 @@ func (a adminAPIHandlers) ListCannedPoliciesV2(w http.ResponseWriter, r *http.Re
|
|||||||
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListCannedPolicies")
|
ctx := newContext(r, w, "ListCannedPolicies")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListCannedPolicies", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -870,7 +889,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RemoveCannedPolicy")
|
ctx := newContext(r, w, "RemoveCannedPolicy")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "RemoveCannedPolicy", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -898,7 +917,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddCannedPolicy")
|
ctx := newContext(r, w, "AddCannedPolicy")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "AddCannedPolicy", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -950,7 +969,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
|
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "SetPolicyForUserOrGroup", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
|
|||||||
+78
-36
@@ -42,6 +42,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger/message/log"
|
"github.com/minio/minio/cmd/logger/message/log"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/bandwidth"
|
"github.com/minio/minio/pkg/bandwidth"
|
||||||
|
"github.com/minio/minio/pkg/dsync"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
@@ -78,7 +79,7 @@ func updateServer(u *url.URL, sha256Sum []byte, lrTime time.Time, mode string) (
|
|||||||
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ServerUpdate")
|
ctx := newContext(r, w, "ServerUpdate")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ServerUpdate", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -187,7 +188,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "Service")
|
ctx := newContext(r, w, "Service")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "Service", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
action := vars["action"]
|
action := vars["action"]
|
||||||
@@ -258,6 +259,7 @@ type ServerHTTPAPIStats struct {
|
|||||||
// ServerHTTPStats holds all type of http operations performed to/from the server
|
// ServerHTTPStats holds all type of http operations performed to/from the server
|
||||||
// including their average execution time.
|
// including their average execution time.
|
||||||
type ServerHTTPStats struct {
|
type ServerHTTPStats struct {
|
||||||
|
S3RequestsInQueue int32 `json:"s3RequestsInQueue"`
|
||||||
CurrentS3Requests ServerHTTPAPIStats `json:"currentS3Requests"`
|
CurrentS3Requests ServerHTTPAPIStats `json:"currentS3Requests"`
|
||||||
TotalS3Requests ServerHTTPAPIStats `json:"totalS3Requests"`
|
TotalS3Requests ServerHTTPAPIStats `json:"totalS3Requests"`
|
||||||
TotalS3Errors ServerHTTPAPIStats `json:"totalS3Errors"`
|
TotalS3Errors ServerHTTPAPIStats `json:"totalS3Errors"`
|
||||||
@@ -284,7 +286,7 @@ type ServerInfo struct {
|
|||||||
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StorageInfo")
|
ctx := newContext(r, w, "StorageInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "StorageInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.StorageInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.StorageInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -327,7 +329,7 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DataUsageInfo")
|
ctx := newContext(r, w, "DataUsageInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DataUsageInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DataUsageInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DataUsageInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -403,11 +405,50 @@ type PeerLocks struct {
|
|||||||
Locks map[string][]lockRequesterInfo
|
Locks map[string][]lockRequesterInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForceUnlockHandler force unlocks requested resource
|
||||||
|
func (a adminAPIHandlers) ForceUnlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ForceUnlock")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ForceUnlockAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
z, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
|
var args dsync.LockArgs
|
||||||
|
lockersMap := make(map[string]dsync.NetLocker)
|
||||||
|
for _, path := range strings.Split(vars["paths"], ",") {
|
||||||
|
if path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args.Resources = append(args.Resources, path)
|
||||||
|
lockers, _ := z.serverPools[0].getHashedSet(path).getLockers()
|
||||||
|
for _, locker := range lockers {
|
||||||
|
if locker != nil {
|
||||||
|
lockersMap[locker.String()] = locker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, locker := range lockersMap {
|
||||||
|
locker.ForceUnlock(ctx, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TopLocksHandler Get list of locks in use
|
// TopLocksHandler Get list of locks in use
|
||||||
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "TopLocks")
|
ctx := newContext(r, w, "TopLocks")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "TopLocks", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.TopLocksAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.TopLocksAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -459,7 +500,7 @@ type StartProfilingResult struct {
|
|||||||
func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StartProfiling")
|
ctx := newContext(r, w, "StartProfiling")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "StartProfiling", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||||
@@ -564,7 +605,7 @@ func (f dummyFileInfo) Sys() interface{} { return f.sys }
|
|||||||
func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DownloadProfiling")
|
ctx := newContext(r, w, "DownloadProfiling")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DownloadProfiling", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||||
@@ -665,7 +706,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
|
|||||||
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "Heal")
|
ctx := newContext(r, w, "Heal")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "Heal", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -876,7 +917,7 @@ func getAggregatedBackgroundHealState(ctx context.Context) (madmin.BgHealState,
|
|||||||
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HealBackgroundStatus")
|
ctx := newContext(r, w, "HealBackgroundStatus")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "HealBackgroundStatus", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -1097,7 +1138,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ConsoleLog")
|
ctx := newContext(r, w, "ConsoleLog")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ConsoleLog", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConsoleLogAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConsoleLogAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -1168,7 +1209,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
// KMSCreateKeyHandler - POST /minio/admin/v3/kms/key/create?key-id=<master-key-id>
|
// KMSCreateKeyHandler - POST /minio/admin/v3/kms/key/create?key-id=<master-key-id>
|
||||||
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSCreateKey")
|
ctx := newContext(r, w, "KMSCreateKey")
|
||||||
defer logger.AuditLog(w, r, "KMSCreateKey", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -1191,7 +1232,7 @@ func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSKeyStatus")
|
ctx := newContext(r, w, "KMSKeyStatus")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "KMSKeyStatus", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -1264,7 +1305,7 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req
|
|||||||
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HealthInfo")
|
ctx := newContext(r, w, "HealthInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "HealthInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealthInfoAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -1412,7 +1453,7 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -1442,7 +1483,7 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "BandwidthMonitor")
|
ctx := newContext(r, w, "BandwidthMonitor")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "BandwidthMonitor", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.BandwidthMonitorAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.BandwidthMonitorAction, "")
|
||||||
@@ -1495,7 +1536,7 @@ func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http
|
|||||||
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ServerInfo")
|
ctx := newContext(r, w, "ServerInfo")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ServerInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
|
||||||
@@ -1504,7 +1545,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vault := fetchVaultStatus()
|
kmsStat := fetchKMSStatus()
|
||||||
|
|
||||||
ldap := madmin.LDAP{}
|
ldap := madmin.LDAP{}
|
||||||
if globalLDAPConfig.Enabled {
|
if globalLDAPConfig.Enabled {
|
||||||
@@ -1545,6 +1586,10 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
||||||
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
||||||
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
||||||
|
} else {
|
||||||
|
buckets = madmin.Buckets{Error: err.Error()}
|
||||||
|
objects = madmin.Objects{Error: err.Error()}
|
||||||
|
usage = madmin.Usage{Error: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetching the backend information
|
// Fetching the backend information
|
||||||
@@ -1561,9 +1606,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
Type: madmin.ErasureType,
|
Type: madmin.ErasureType,
|
||||||
OnlineDisks: onlineDisks.Sum(),
|
OnlineDisks: onlineDisks.Sum(),
|
||||||
OfflineDisks: offlineDisks.Sum(),
|
OfflineDisks: offlineDisks.Sum(),
|
||||||
StandardSCData: backendInfo.StandardSCData,
|
|
||||||
StandardSCParity: backendInfo.StandardSCParity,
|
StandardSCParity: backendInfo.StandardSCParity,
|
||||||
RRSCData: backendInfo.RRSCData,
|
|
||||||
RRSCParity: backendInfo.RRSCParity,
|
RRSCParity: backendInfo.RRSCParity,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1575,7 +1618,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
domain := globalDomainNames
|
domain := globalDomainNames
|
||||||
services := madmin.Services{
|
services := madmin.Services{
|
||||||
Vault: vault,
|
KMS: kmsStat,
|
||||||
LDAP: ldap,
|
LDAP: ldap,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
Audit: audit,
|
Audit: audit,
|
||||||
@@ -1651,47 +1694,46 @@ func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
|||||||
return notify
|
return notify
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchVaultStatus fetches Vault Info
|
// fetchKMSStatus fetches KMS-related status information.
|
||||||
func fetchVaultStatus() madmin.Vault {
|
func fetchKMSStatus() madmin.KMS {
|
||||||
vault := madmin.Vault{}
|
kmsStat := madmin.KMS{}
|
||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
vault.Status = "disabled"
|
kmsStat.Status = "disabled"
|
||||||
return vault
|
return kmsStat
|
||||||
}
|
}
|
||||||
keyID := GlobalKMS.DefaultKeyID()
|
keyID := GlobalKMS.DefaultKeyID()
|
||||||
kmsInfo := GlobalKMS.Info()
|
kmsInfo := GlobalKMS.Info()
|
||||||
|
|
||||||
if len(kmsInfo.Endpoints) == 0 {
|
if len(kmsInfo.Endpoints) == 0 {
|
||||||
vault.Status = "KMS configured using master key"
|
kmsStat.Status = "KMS configured using master key"
|
||||||
return vault
|
return kmsStat
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkConnection(kmsInfo.Endpoints[0], 15*time.Second); err != nil {
|
if err := checkConnection(kmsInfo.Endpoints[0], 15*time.Second); err != nil {
|
||||||
vault.Status = "offline"
|
kmsStat.Status = "offline"
|
||||||
} else {
|
} else {
|
||||||
vault.Status = "online"
|
kmsStat.Status = "online"
|
||||||
|
|
||||||
kmsContext := crypto.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
kmsContext := crypto.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
||||||
// 1. Generate a new key using the KMS.
|
// 1. Generate a new key using the KMS.
|
||||||
key, sealedKey, err := GlobalKMS.GenerateKey(keyID, kmsContext)
|
key, sealedKey, err := GlobalKMS.GenerateKey(keyID, kmsContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vault.Encrypt = fmt.Sprintf("Encryption failed: %v", err)
|
kmsStat.Encrypt = fmt.Sprintf("Encryption failed: %v", err)
|
||||||
} else {
|
} else {
|
||||||
vault.Encrypt = "Ok"
|
kmsStat.Encrypt = "Ok"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Verify that we can indeed decrypt the (encrypted) key
|
// 2. Verify that we can indeed decrypt the (encrypted) key
|
||||||
decryptedKey, err := GlobalKMS.UnsealKey(keyID, sealedKey, kmsContext)
|
decryptedKey, err := GlobalKMS.UnsealKey(keyID, sealedKey, kmsContext)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
vault.Decrypt = fmt.Sprintf("Decryption failed: %v", err)
|
kmsStat.Decrypt = fmt.Sprintf("Decryption failed: %v", err)
|
||||||
case subtle.ConstantTimeCompare(key[:], decryptedKey[:]) != 1:
|
case subtle.ConstantTimeCompare(key[:], decryptedKey[:]) != 1:
|
||||||
vault.Decrypt = "Decryption failed: decrypted key does not match generated key"
|
kmsStat.Decrypt = "Decryption failed: decrypted key does not match generated key"
|
||||||
default:
|
default:
|
||||||
vault.Decrypt = "Ok"
|
kmsStat.Decrypt = "Ok"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vault
|
return kmsStat
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchLoggerDetails return log info
|
// fetchLoggerDetails return log info
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
|
|||||||
// Initialize boot time
|
// Initialize boot time
|
||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
|
|
||||||
globalEndpoints = mustGetZoneEndpoints(erasureDirs...)
|
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
||||||
|
|
||||||
newAllSubsystems()
|
newAllSubsystems()
|
||||||
|
|
||||||
@@ -97,16 +97,9 @@ func initTestErasureObjLayer(ctx context.Context) (ObjectLayer, []string, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
endpoints := mustGetNewEndpoints(erasureDirs...)
|
endpoints := mustGetPoolEndpoints(erasureDirs...)
|
||||||
storageDisks, format, err := waitForFormatErasure(true, endpoints, 1, 1, 16, "")
|
|
||||||
if err != nil {
|
|
||||||
removeRoots(erasureDirs)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
globalPolicySys = NewPolicySys()
|
globalPolicySys = NewPolicySys()
|
||||||
objLayer := &erasureServerPools{serverPools: make([]*erasureSets, 1)}
|
objLayer, err := newErasureServerPools(ctx, endpoints)
|
||||||
objLayer.serverPools[0], err = newErasureSets(ctx, endpoints, storageDisks, format)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-6
@@ -662,6 +662,13 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *healSequence) logHeal(healType madmin.HealItemType) {
|
||||||
|
h.mutex.Lock()
|
||||||
|
h.scannedItemsMap[healType]++
|
||||||
|
h.lastHealActivity = UTCNow()
|
||||||
|
h.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||||
globalHealConfigMu.Lock()
|
globalHealConfigMu.Lock()
|
||||||
opts := globalHealConfig
|
opts := globalHealConfig
|
||||||
@@ -750,16 +757,19 @@ func (h *healSequence) healItemsFromSourceCh() error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemType madmin.HealItemType
|
var itemType madmin.HealItemType
|
||||||
switch {
|
switch source.bucket {
|
||||||
case source.bucket == nopHeal:
|
case nopHeal:
|
||||||
continue
|
continue
|
||||||
case source.bucket == SlashSeparator:
|
case SlashSeparator:
|
||||||
itemType = madmin.HealItemMetadata
|
itemType = madmin.HealItemMetadata
|
||||||
case source.bucket != "" && source.object == "":
|
|
||||||
itemType = madmin.HealItemBucket
|
|
||||||
default:
|
default:
|
||||||
itemType = madmin.HealItemObject
|
if source.object == "" {
|
||||||
|
itemType = madmin.HealItemBucket
|
||||||
|
} else {
|
||||||
|
itemType = madmin.HealItemObject
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.queueHealTask(source, itemType); err != nil {
|
if err := h.queueHealTask(source, itemType); err != nil {
|
||||||
|
|||||||
+5
-4
@@ -110,10 +110,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
// -- IAM APIs --
|
// -- IAM APIs --
|
||||||
|
|
||||||
// Add policy IAM
|
// Add policy IAM
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.AddCannedPolicy)).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(httpTraceAll(adminAPI.AddCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
// Add user IAM
|
// Add user IAM
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(httpTraceAll(adminAPI.AccountInfoHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(httpTraceAll(adminAPI.AccountInfoHandler))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
@@ -189,10 +188,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Top APIs --
|
|
||||||
// Top locks
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
|
// Top locks
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
|
||||||
|
// Force unlocks paths
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/force-unlock").
|
||||||
|
Queries("paths", "{paths:.*}").HandlerFunc(httpTraceHdrs(adminAPI.ForceUnlockHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Trace
|
// HTTP Trace
|
||||||
|
|||||||
@@ -17,8 +17,11 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,10 +48,12 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
}
|
}
|
||||||
_, present := network[nodeName]
|
_, present := network[nodeName]
|
||||||
if !present {
|
if !present {
|
||||||
if err := IsServerResolvable(endpoint); err == nil {
|
if err := isServerResolvable(endpoint, 2*time.Second); err == nil {
|
||||||
network[nodeName] = "online"
|
network[nodeName] = "online"
|
||||||
} else {
|
} else {
|
||||||
network[nodeName] = "offline"
|
network[nodeName] = "offline"
|
||||||
|
// log once the error
|
||||||
|
logger.LogOnceIf(context.Background(), err, nodeName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,3 +74,20 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
Disks: storageInfo.Disks,
|
Disks: storageInfo.Disks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLocalDisks(endpointServerPools EndpointServerPools) []madmin.Disk {
|
||||||
|
var localEndpoints Endpoints
|
||||||
|
for _, ep := range endpointServerPools {
|
||||||
|
for _, endpoint := range ep.Endpoints {
|
||||||
|
if !endpoint.IsLocal {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
localEndpoints = append(localEndpoints, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localDisks, _ := initStorageDisksWithErrors(localEndpoints)
|
||||||
|
defer closeStorageDisks(localDisks)
|
||||||
|
storageInfo, _ := getStorageInfo(localDisks, localEndpoints.GetAllStrings())
|
||||||
|
return storageInfo.Disks
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ const (
|
|||||||
ErrInvalidMaxUploads
|
ErrInvalidMaxUploads
|
||||||
ErrInvalidMaxParts
|
ErrInvalidMaxParts
|
||||||
ErrInvalidPartNumberMarker
|
ErrInvalidPartNumberMarker
|
||||||
|
ErrInvalidPartNumber
|
||||||
ErrInvalidRequestBody
|
ErrInvalidRequestBody
|
||||||
ErrInvalidCopySource
|
ErrInvalidCopySource
|
||||||
ErrInvalidMetadataDirective
|
ErrInvalidMetadataDirective
|
||||||
@@ -437,6 +438,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Argument partNumberMarker must be an integer.",
|
Description: "Argument partNumberMarker must be an integer.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidPartNumber: {
|
||||||
|
Code: "InvalidPartNumber",
|
||||||
|
Description: "The requested partnumber is not satisfiable",
|
||||||
|
HTTPStatusCode: http.StatusRequestedRangeNotSatisfiable,
|
||||||
|
},
|
||||||
ErrInvalidPolicyDocument: {
|
ErrInvalidPolicyDocument: {
|
||||||
Code: "InvalidPolicyDocument",
|
Code: "InvalidPolicyDocument",
|
||||||
Description: "The content of the form does not meet the conditions specified in the policy document.",
|
Description: "The content of the form does not meet the conditions specified in the policy document.",
|
||||||
@@ -2119,6 +2125,12 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
HTTPStatusCode: e.Response().StatusCode,
|
HTTPStatusCode: e.Response().StatusCode,
|
||||||
}
|
}
|
||||||
// Add more Gateway SDKs here if any in future.
|
// Add more Gateway SDKs here if any in future.
|
||||||
|
default:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: apiErr.Code,
|
||||||
|
Description: fmt.Sprintf("%s: cause(%v)", apiErr.Description, err),
|
||||||
|
HTTPStatusCode: apiErr.HTTPStatusCode,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-12
@@ -133,18 +133,20 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
||||||
if strings.EqualFold(k, xhttp.AmzMetaUnencryptedContentLength) || strings.EqualFold(k, xhttp.AmzMetaUnencryptedContentMD5) {
|
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSet bool
|
var isSet bool
|
||||||
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
|
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
|
||||||
if !strings.HasPrefix(k, userMetadataPrefix) {
|
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(userMetadataPrefix)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w.Header()[strings.ToLower(k)] = []string{v}
|
w.Header()[strings.ToLower(k)] = []string{v}
|
||||||
isSet = true
|
isSet = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isSet {
|
if !isSet {
|
||||||
w.Header().Set(k, v)
|
w.Header().Set(k, v)
|
||||||
}
|
}
|
||||||
@@ -156,16 +158,16 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.PartNumber > 0 {
|
|
||||||
rs = partNumberToRangeSpec(objInfo, opts.PartNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For providing ranged content
|
// For providing ranged content
|
||||||
start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
|
start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rs == nil && opts.PartNumber > 0 {
|
||||||
|
rs = partNumberToRangeSpec(objInfo, opts.PartNumber)
|
||||||
|
}
|
||||||
|
|
||||||
// Set content length.
|
// Set content length.
|
||||||
w.Header().Set(xhttp.ContentLength, strconv.FormatInt(rangeLen, 10))
|
w.Header().Set(xhttp.ContentLength, strconv.FormatInt(rangeLen, 10))
|
||||||
if rs != nil {
|
if rs != nil {
|
||||||
@@ -182,12 +184,13 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
||||||
ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
|
ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
|
||||||
Name: objInfo.Name,
|
Name: objInfo.Name,
|
||||||
UserTags: objInfo.UserTags,
|
UserTags: objInfo.UserTags,
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: objInfo.VersionID,
|
||||||
ModTime: objInfo.ModTime,
|
ModTime: objInfo.ModTime,
|
||||||
IsLatest: objInfo.IsLatest,
|
IsLatest: objInfo.IsLatest,
|
||||||
DeleteMarker: objInfo.DeleteMarker,
|
DeleteMarker: objInfo.DeleteMarker,
|
||||||
|
SuccessorModTime: objInfo.SuccessorModTime,
|
||||||
})
|
})
|
||||||
if !expiryTime.IsZero() {
|
if !expiryTime.IsZero() {
|
||||||
w.Header()[xhttp.AmzExpiration] = []string{
|
w.Header()[xhttp.AmzExpiration] = []string{
|
||||||
|
|||||||
+11
-3
@@ -565,7 +565,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
||||||
if strings.EqualFold(k, xhttp.AmzMetaUnencryptedContentLength) || strings.EqualFold(k, xhttp.AmzMetaUnencryptedContentMD5) {
|
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
content.UserMetadata[k] = v
|
content.UserMetadata[k] = v
|
||||||
@@ -639,8 +639,16 @@ func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) Lis
|
|||||||
listPartsResponse.Key = s3EncodeName(partsInfo.Object, encodingType)
|
listPartsResponse.Key = s3EncodeName(partsInfo.Object, encodingType)
|
||||||
listPartsResponse.UploadID = partsInfo.UploadID
|
listPartsResponse.UploadID = partsInfo.UploadID
|
||||||
listPartsResponse.StorageClass = globalMinioDefaultStorageClass
|
listPartsResponse.StorageClass = globalMinioDefaultStorageClass
|
||||||
listPartsResponse.Initiator.ID = globalMinioDefaultOwnerID
|
|
||||||
listPartsResponse.Owner.ID = globalMinioDefaultOwnerID
|
// Dumb values not meaningful
|
||||||
|
listPartsResponse.Initiator = Initiator{
|
||||||
|
ID: globalMinioDefaultOwnerID,
|
||||||
|
DisplayName: globalMinioDefaultOwnerID,
|
||||||
|
}
|
||||||
|
listPartsResponse.Owner = Owner{
|
||||||
|
ID: globalMinioDefaultOwnerID,
|
||||||
|
DisplayName: globalMinioDefaultOwnerID,
|
||||||
|
}
|
||||||
|
|
||||||
listPartsResponse.MaxParts = partsInfo.MaxParts
|
listPartsResponse.MaxParts = partsInfo.MaxParts
|
||||||
listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
|
listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
|
||||||
|
|||||||
+2
-2
@@ -459,8 +459,7 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
|
|
||||||
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
||||||
// The verification happens implicit during reading.
|
// The verification happens implicit during reading.
|
||||||
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5),
|
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5), hex.EncodeToString(contentSHA256), -1)
|
||||||
hex.EncodeToString(contentSHA256), -1, globalCLIContext.StrictS3Compat)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -498,6 +497,7 @@ func setAuthHandler(h http.Handler) http.Handler {
|
|||||||
// Validate Authorization header if its valid for JWT request.
|
// Validate Authorization header if its valid for JWT request.
|
||||||
if _, _, authErr := webRequestAuthenticate(r); authErr != nil {
|
if _, _, authErr := webRequestAuthenticate(r); authErr != nil {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte(authErr.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
|
|||||||
+21
-13
@@ -61,7 +61,6 @@ func waitForLowHTTPReq(maxIO int, maxWait time.Duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// At max 10 attempts to wait with 100 millisecond interval before proceeding
|
// At max 10 attempts to wait with 100 millisecond interval before proceeding
|
||||||
waitCount := 10
|
|
||||||
waitTick := 100 * time.Millisecond
|
waitTick := 100 * time.Millisecond
|
||||||
|
|
||||||
// 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
|
||||||
@@ -70,14 +69,21 @@ func waitForLowHTTPReq(maxIO int, maxWait time.Duration) {
|
|||||||
return maxIO + int(globalHTTPListen.NumSubscribers()) + int(globalHTTPTrace.NumSubscribers())
|
return maxIO + int(globalHTTPListen.NumSubscribers()) + int(globalHTTPTrace.NumSubscribers())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpMaxWait := maxWait
|
||||||
if httpServer := newHTTPServerFn(); httpServer != nil {
|
if httpServer := newHTTPServerFn(); httpServer != nil {
|
||||||
// Any requests in progress, delay the heal.
|
// Any requests in progress, delay the heal.
|
||||||
for httpServer.GetRequestCount() >= maxIOFn() {
|
for httpServer.GetRequestCount() >= maxIOFn() {
|
||||||
time.Sleep(waitTick)
|
if tmpMaxWait > 0 {
|
||||||
waitCount--
|
if tmpMaxWait < waitTick {
|
||||||
if waitCount == 0 {
|
time.Sleep(tmpMaxWait)
|
||||||
|
} else {
|
||||||
|
time.Sleep(waitTick)
|
||||||
|
}
|
||||||
|
tmpMaxWait = tmpMaxWait - waitTick
|
||||||
|
}
|
||||||
|
if tmpMaxWait <= 0 {
|
||||||
if intDataUpdateTracker.debug {
|
if intDataUpdateTracker.debug {
|
||||||
logger.Info("waitForLowHTTPReq: waited %d times, resuming", waitCount)
|
logger.Info("waitForLowHTTPReq: waited max %s, resuming", maxWait)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -91,20 +97,22 @@ func (h *healRoutine) run(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
select {
|
select {
|
||||||
case task, ok := <-h.tasks:
|
case task, ok := <-h.tasks:
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var res madmin.HealResultItem
|
var res madmin.HealResultItem
|
||||||
var err error
|
var err error
|
||||||
switch {
|
switch task.bucket {
|
||||||
case task.bucket == nopHeal:
|
case nopHeal:
|
||||||
continue
|
continue
|
||||||
case task.bucket == SlashSeparator:
|
case SlashSeparator:
|
||||||
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
||||||
case task.bucket != "" && task.object == "":
|
default:
|
||||||
res, err = objAPI.HealBucket(ctx, task.bucket, task.opts)
|
if task.object == "" {
|
||||||
case task.bucket != "" && task.object != "":
|
res, err = objAPI.HealBucket(ctx, task.bucket, task.opts)
|
||||||
res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts)
|
} else {
|
||||||
|
res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
task.responseCh <- healResult{result: res, err: err}
|
task.responseCh <- healResult{result: res, err: err}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/color"
|
||||||
"github.com/minio/minio/pkg/console"
|
"github.com/minio/minio/pkg/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ wait:
|
|||||||
// Reset to next interval.
|
// Reset to next interval.
|
||||||
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
||||||
|
|
||||||
var erasureSetInZoneDisksToHeal []map[int][]StorageAPI
|
var erasureSetInPoolDisksToHeal []map[int][]StorageAPI
|
||||||
|
|
||||||
healDisks := globalBackgroundHealState.getHealLocalDisks()
|
healDisks := globalBackgroundHealState.getHealLocalDisks()
|
||||||
if len(healDisks) > 0 {
|
if len(healDisks) > 0 {
|
||||||
@@ -135,14 +136,14 @@ wait:
|
|||||||
logger.Info(fmt.Sprintf("Found drives to heal %d, proceeding to heal content...",
|
logger.Info(fmt.Sprintf("Found drives to heal %d, proceeding to heal content...",
|
||||||
len(healDisks)))
|
len(healDisks)))
|
||||||
|
|
||||||
erasureSetInZoneDisksToHeal = make([]map[int][]StorageAPI, len(z.serverPools))
|
erasureSetInPoolDisksToHeal = make([]map[int][]StorageAPI, len(z.serverPools))
|
||||||
for i := range z.serverPools {
|
for i := range z.serverPools {
|
||||||
erasureSetInZoneDisksToHeal[i] = map[int][]StorageAPI{}
|
erasureSetInPoolDisksToHeal[i] = map[int][]StorageAPI{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
console.Debugf("disk check timer fired, attempting to heal %d drives\n", len(healDisks))
|
console.Debugf(color.Green("healDisk:")+" disk check timer fired, attempting to heal %d drives\n", len(healDisks))
|
||||||
}
|
}
|
||||||
|
|
||||||
// heal only if new disks found.
|
// heal only if new disks found.
|
||||||
@@ -153,22 +154,22 @@ wait:
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneIdx := globalEndpoints.GetLocalZoneIdx(disk.Endpoint())
|
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
||||||
if zoneIdx < 0 {
|
if poolIdx < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the set index where the current endpoint belongs
|
// Calculate the set index where the current endpoint belongs
|
||||||
z.serverPools[zoneIdx].erasureDisksMu.RLock()
|
z.serverPools[poolIdx].erasureDisksMu.RLock()
|
||||||
// Protect reading reference format.
|
// Protect reading reference format.
|
||||||
setIndex, _, err := findDiskIndex(z.serverPools[zoneIdx].format, format)
|
setIndex, _, err := findDiskIndex(z.serverPools[poolIdx].format, format)
|
||||||
z.serverPools[zoneIdx].erasureDisksMu.RUnlock()
|
z.serverPools[poolIdx].erasureDisksMu.RUnlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printEndpointError(endpoint, err, false)
|
printEndpointError(endpoint, err, false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
erasureSetInZoneDisksToHeal[zoneIdx][setIndex] = append(erasureSetInZoneDisksToHeal[zoneIdx][setIndex], disk)
|
erasureSetInPoolDisksToHeal[poolIdx][setIndex] = append(erasureSetInPoolDisksToHeal[poolIdx][setIndex], disk)
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, _ := z.ListBuckets(ctx)
|
buckets, _ := z.ListBuckets(ctx)
|
||||||
@@ -178,14 +179,14 @@ wait:
|
|||||||
return buckets[i].Created.After(buckets[j].Created)
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
})
|
})
|
||||||
|
|
||||||
for i, setMap := range erasureSetInZoneDisksToHeal {
|
for i, setMap := range erasureSetInPoolDisksToHeal {
|
||||||
for setIndex, disks := range setMap {
|
for setIndex, disks := range setMap {
|
||||||
for _, disk := range disks {
|
for _, disk := range disks {
|
||||||
logger.Info("Healing disk '%s' on %s zone", disk, humanize.Ordinal(i+1))
|
logger.Info("Healing disk '%s' on %s pool", disk, humanize.Ordinal(i+1))
|
||||||
|
|
||||||
// So someone changed the drives underneath, healing tracker missing.
|
// So someone changed the drives underneath, healing tracker missing.
|
||||||
if !disk.Healing() {
|
if !disk.Healing() {
|
||||||
logger.Info("Healing tracker missing on '%s', disk was swapped again on %s zone", disk, humanize.Ordinal(i+1))
|
logger.Info("Healing tracker missing on '%s', disk was swapped again on %s pool", disk, humanize.Ordinal(i+1))
|
||||||
diskID, err := disk.GetDiskID()
|
diskID, err := disk.GetDiskID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -203,13 +204,13 @@ wait:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lbDisks := z.serverPools[i].sets[setIndex].getOnlineDisks()
|
err := z.serverPools[i].sets[setIndex].healErasureSet(ctx, buckets)
|
||||||
if err := healErasureSet(ctx, setIndex, buckets, lbDisks); err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Healing disk '%s' on %s zone complete", disk, humanize.Ordinal(i+1))
|
logger.Info("Healing disk '%s' on %s pool complete", disk, humanize.Ordinal(i+1))
|
||||||
|
|
||||||
if err := disk.Delete(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix),
|
if err := disk.Delete(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix),
|
||||||
healingTrackerFilename, false); err != nil && !errors.Is(err, errFileNotFound) {
|
healingTrackerFilename, false); err != nil && !errors.Is(err, errFileNotFound) {
|
||||||
|
|||||||
+11
-6
@@ -91,6 +91,7 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
|
|||||||
// ReadAt() implementation which verifies the bitrot hash available as part of the stream.
|
// ReadAt() implementation which verifies the bitrot hash available as part of the stream.
|
||||||
type streamingBitrotReader struct {
|
type streamingBitrotReader struct {
|
||||||
disk StorageAPI
|
disk StorageAPI
|
||||||
|
data []byte
|
||||||
rc io.Reader
|
rc io.Reader
|
||||||
volume string
|
volume string
|
||||||
filePath string
|
filePath string
|
||||||
@@ -122,7 +123,11 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
// For the first ReadAt() call we need to open the stream for reading.
|
// For the first ReadAt() call we need to open the stream for reading.
|
||||||
b.currOffset = offset
|
b.currOffset = offset
|
||||||
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
||||||
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
if len(b.data) == 0 {
|
||||||
|
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
||||||
|
} else {
|
||||||
|
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -144,20 +149,20 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
b.h.Write(buf)
|
b.h.Write(buf)
|
||||||
|
|
||||||
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
||||||
err := &errHashMismatch{fmt.Sprintf("Disk: %s -> %s/%s - content hash does not match - expected %s, got %s",
|
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s -> %s/%s - content hash does not match - expected %s, got %s",
|
||||||
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil)))}
|
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))))
|
||||||
logger.LogIf(GlobalContext, err)
|
return 0, errFileCorrupt
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
b.currOffset += int64(len(buf))
|
b.currOffset += int64(len(buf))
|
||||||
return len(buf), nil
|
return len(buf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns streaming bitrot reader implementation.
|
// Returns streaming bitrot reader implementation.
|
||||||
func newStreamingBitrotReader(disk StorageAPI, volume, filePath string, tillOffset int64, algo BitrotAlgorithm, shardSize int64) *streamingBitrotReader {
|
func newStreamingBitrotReader(disk StorageAPI, data []byte, volume, filePath string, tillOffset int64, algo BitrotAlgorithm, shardSize int64) *streamingBitrotReader {
|
||||||
h := algo.New()
|
h := algo.New()
|
||||||
return &streamingBitrotReader{
|
return &streamingBitrotReader{
|
||||||
disk,
|
disk,
|
||||||
|
data,
|
||||||
nil,
|
nil,
|
||||||
volume,
|
volume,
|
||||||
filePath,
|
filePath,
|
||||||
|
|||||||
+3
-3
@@ -17,13 +17,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/minio/highwayhash"
|
"github.com/minio/highwayhash"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
sha256 "github.com/minio/sha256-simd"
|
|
||||||
"golang.org/x/crypto/blake2b"
|
"golang.org/x/crypto/blake2b"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,9 +103,9 @@ func newBitrotWriter(disk StorageAPI, volume, filePath string, length int64, alg
|
|||||||
return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize)
|
return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBitrotReader(disk StorageAPI, bucket string, filePath string, tillOffset int64, algo BitrotAlgorithm, sum []byte, shardSize int64) io.ReaderAt {
|
func newBitrotReader(disk StorageAPI, data []byte, bucket string, filePath string, tillOffset int64, algo BitrotAlgorithm, sum []byte, shardSize int64) io.ReaderAt {
|
||||||
if algo == HighwayHash256S {
|
if algo == HighwayHash256S {
|
||||||
return newStreamingBitrotReader(disk, bucket, filePath, tillOffset, algo, shardSize)
|
return newStreamingBitrotReader(disk, data, bucket, filePath, tillOffset, algo, shardSize)
|
||||||
}
|
}
|
||||||
return newWholeBitrotReader(disk, bucket, filePath, algo, tillOffset, sum)
|
return newWholeBitrotReader(disk, bucket, filePath, algo, tillOffset, sum)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-11
@@ -20,7 +20,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -28,7 +27,7 @@ import (
|
|||||||
func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
||||||
tmpDir, err := ioutil.TempDir("", "")
|
tmpDir, err := ioutil.TempDir("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
@@ -46,35 +45,35 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
|||||||
|
|
||||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = writer.Write([]byte("aaaaa"))
|
_, err = writer.Write([]byte("aaaaa"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
writer.(io.Closer).Close()
|
writer.(io.Closer).Close()
|
||||||
|
|
||||||
reader := newBitrotReader(disk, volume, filePath, 35, bitrotAlgo, bitrotWriterSum(writer), 10)
|
reader := newBitrotReader(disk, nil, volume, filePath, 35, bitrotAlgo, bitrotWriterSum(writer), 10)
|
||||||
b := make([]byte, 10)
|
b := make([]byte, 10)
|
||||||
if _, err = reader.ReadAt(b, 0); err != nil {
|
if _, err = reader.ReadAt(b, 0); err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err = reader.ReadAt(b, 10); err != nil {
|
if _, err = reader.ReadAt(b, 10); err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err = reader.ReadAt(b, 20); err != nil {
|
if _, err = reader.ReadAt(b, 20); err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err = reader.ReadAt(b[:5], 30); err != nil {
|
if _, err = reader.ReadAt(b[:5], 30); err != nil {
|
||||||
log.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const (
|
|||||||
func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketEncryption")
|
ctx := newContext(r, w, "PutBucketEncryption")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketEncryption", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -102,7 +102,7 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketEncryption")
|
ctx := newContext(r, w, "GetBucketEncryption")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketEncryption", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -145,7 +145,7 @@ func (api objectAPIHandlers) GetBucketEncryptionHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteBucketEncryption")
|
ctx := newContext(r, w, "DeleteBucketEncryption")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteBucketEncryption", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
|
|||||||
+95
-53
@@ -22,11 +22,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -72,7 +75,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
|
|
||||||
// Get buckets in the DNS
|
// Get buckets in the DNS
|
||||||
dnsBuckets, err := globalDNSConfig.List()
|
dnsBuckets, err := globalDNSConfig.List()
|
||||||
if err != nil && err != dns.ErrNoEntriesFound && err != dns.ErrNotImplemented {
|
if err != nil && !IsErrIgnored(err, dns.ErrNoEntriesFound, dns.ErrNotImplemented, dns.ErrDomainMissing) {
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -80,6 +83,10 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
bucketsSet := set.NewStringSet()
|
bucketsSet := set.NewStringSet()
|
||||||
bucketsToBeUpdated := set.NewStringSet()
|
bucketsToBeUpdated := set.NewStringSet()
|
||||||
bucketsInConflict := set.NewStringSet()
|
bucketsInConflict := set.NewStringSet()
|
||||||
|
|
||||||
|
// This means that domain is updated, we should update
|
||||||
|
// all bucket entries with new domain name.
|
||||||
|
domainMissing := err == dns.ErrDomainMissing
|
||||||
if dnsBuckets != nil {
|
if dnsBuckets != nil {
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
bucketsSet.Add(bucket.Name)
|
bucketsSet.Add(bucket.Name)
|
||||||
@@ -89,11 +96,16 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() {
|
if !globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() {
|
||||||
if globalDomainIPs.Difference(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() {
|
if globalDomainIPs.Difference(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() && !domainMissing {
|
||||||
// No difference in terms of domainIPs and nothing
|
// No difference in terms of domainIPs and nothing
|
||||||
// has changed so we don't change anything on the etcd.
|
// has changed so we don't change anything on the etcd.
|
||||||
|
//
|
||||||
|
// Additionally also check if domain is updated/missing with more
|
||||||
|
// entries, if that is the case we should update the
|
||||||
|
// new domain entries as well.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// if domain IPs intersect then it won't be an empty set.
|
// if domain IPs intersect then it won't be an empty set.
|
||||||
// such an intersection means that bucket exists on etcd.
|
// such an intersection means that bucket exists on etcd.
|
||||||
// but if we do see a difference with local domain IPs with
|
// but if we do see a difference with local domain IPs with
|
||||||
@@ -102,6 +114,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
bucketsToBeUpdated.Add(bucket.Name)
|
bucketsToBeUpdated.Add(bucket.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// No IPs seem to intersect, this means that bucket exists but has
|
// No IPs seem to intersect, this means that bucket exists but has
|
||||||
// different IP addresses perhaps from a different deployment.
|
// different IP addresses perhaps from a different deployment.
|
||||||
// bucket names are globally unique in federation at a given
|
// bucket names are globally unique in federation at a given
|
||||||
@@ -112,8 +125,11 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add/update buckets that are not registered with the DNS
|
// Add/update buckets that are not registered with the DNS
|
||||||
g := errgroup.WithNErrs(len(buckets))
|
|
||||||
bucketsToBeUpdatedSlice := bucketsToBeUpdated.ToSlice()
|
bucketsToBeUpdatedSlice := bucketsToBeUpdated.ToSlice()
|
||||||
|
g := errgroup.WithNErrs(len(bucketsToBeUpdatedSlice)).WithConcurrency(50)
|
||||||
|
ctx, cancel := g.WithCancelOnError(GlobalContext)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
for index := range bucketsToBeUpdatedSlice {
|
for index := range bucketsToBeUpdatedSlice {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
@@ -121,16 +137,16 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, err := range g.Wait() {
|
if err := g.WaitErr(); err != nil {
|
||||||
if err != nil {
|
logger.LogIf(ctx, err)
|
||||||
logger.LogIf(GlobalContext, err)
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range bucketsInConflict.ToSlice() {
|
for _, bucket := range bucketsInConflict.ToSlice() {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Unable to add bucket DNS entry for bucket %s, an entry exists for the same bucket. Use one of these IP addresses %v to access the bucket", bucket, globalDomainIPs.ToSlice()))
|
logger.LogIf(ctx, fmt.Errorf("Unable to add bucket DNS entry for bucket %s, an entry exists for the same bucket by a different tenant. This local bucket will be ignored. Bucket names are globally unique in federated deployments. Use path style requests on following addresses '%v' to access this bucket", bucket, globalDomainIPs.ToSlice()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
// Remove buckets that are in DNS for this server, but aren't local
|
// Remove buckets that are in DNS for this server, but aren't local
|
||||||
for bucket, records := range dnsBuckets {
|
for bucket, records := range dnsBuckets {
|
||||||
if bucketsSet.Contains(bucket) {
|
if bucketsSet.Contains(bucket) {
|
||||||
@@ -142,13 +158,18 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// We go to here, so we know the bucket no longer exists,
|
wg.Add(1)
|
||||||
// but is registered in DNS to this server
|
go func(bucket string) {
|
||||||
if err = globalDNSConfig.Delete(bucket); err != nil {
|
defer wg.Done()
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("Failed to remove DNS entry for %s due to %w",
|
// We go to here, so we know the bucket no longer exists,
|
||||||
bucket, err))
|
// but is registered in DNS to this server
|
||||||
}
|
if err := globalDNSConfig.Delete(bucket); err != nil {
|
||||||
|
logger.LogIf(GlobalContext, fmt.Errorf("Failed to remove DNS entry for %s due to %w",
|
||||||
|
bucket, err))
|
||||||
|
}
|
||||||
|
}(bucket)
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketLocationHandler - GET Bucket location.
|
// GetBucketLocationHandler - GET Bucket location.
|
||||||
@@ -157,7 +178,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
|||||||
func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketLocation")
|
ctx := newContext(r, w, "GetBucketLocation")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketLocation", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -205,7 +226,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
|||||||
func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListMultipartUploads")
|
ctx := newContext(r, w, "ListMultipartUploads")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListMultipartUploads", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -260,7 +281,7 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
|
|||||||
func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListBuckets")
|
ctx := newContext(r, w, "ListBuckets")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListBuckets", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
objectAPI := api.ObjectAPI()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -280,7 +301,9 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
var bucketsInfo []BucketInfo
|
var bucketsInfo []BucketInfo
|
||||||
if globalDNSConfig != nil && globalBucketFederation {
|
if globalDNSConfig != nil && globalBucketFederation {
|
||||||
dnsBuckets, err := globalDNSConfig.List()
|
dnsBuckets, err := globalDNSConfig.List()
|
||||||
if err != nil && err != dns.ErrNoEntriesFound {
|
if err != nil && !IsErrIgnored(err,
|
||||||
|
dns.ErrNoEntriesFound,
|
||||||
|
dns.ErrDomainMissing) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -290,6 +313,11 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
Created: dnsRecords[0].CreationDate,
|
Created: dnsRecords[0].CreationDate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(bucketsInfo, func(i, j int) bool {
|
||||||
|
return bucketsInfo[i].Name < bucketsInfo[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Invoke the list buckets.
|
// Invoke the list buckets.
|
||||||
var err error
|
var err error
|
||||||
@@ -347,7 +375,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteMultipleObjects")
|
ctx := newContext(r, w, "DeleteMultipleObjects")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteMultipleObjects", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -399,15 +427,21 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
deleteObjectsFn = api.CacheAPI().DeleteObjects
|
deleteObjectsFn = api.CacheAPI().DeleteObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return Malformed XML as S3 spec if the list of objects is empty
|
||||||
|
if len(deleteObjects.Objects) == 0 {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var objectsToDelete = map[ObjectToDelete]int{}
|
var objectsToDelete = map[ObjectToDelete]int{}
|
||||||
getObjectInfoFn := objectAPI.GetObjectInfo
|
getObjectInfoFn := objectAPI.GetObjectInfo
|
||||||
if api.CacheAPI() != nil {
|
if api.CacheAPI() != nil {
|
||||||
getObjectInfoFn = api.CacheAPI().GetObjectInfo
|
getObjectInfoFn = api.CacheAPI().GetObjectInfo
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
hasLockEnabled, hasLifecycleConfig bool
|
hasLockEnabled, hasLifecycleConfig, replicateSync bool
|
||||||
goi ObjectInfo
|
goi ObjectInfo
|
||||||
gerr error
|
gerr error
|
||||||
)
|
)
|
||||||
replicateDeletes := hasReplicationRules(ctx, bucket, deleteObjects.Objects)
|
replicateDeletes := hasReplicationRules(ctx, bucket, deleteObjects.Objects)
|
||||||
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled {
|
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled {
|
||||||
@@ -455,11 +489,19 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
object.PurgeTransitioned = goi.TransitionStatus
|
object.PurgeTransitioned = goi.TransitionStatus
|
||||||
}
|
}
|
||||||
if replicateDeletes {
|
if replicateDeletes {
|
||||||
delMarker, replicate := checkReplicateDelete(ctx, bucket, ObjectToDelete{
|
delMarker, replicate, repsync := checkReplicateDelete(ctx, bucket, ObjectToDelete{
|
||||||
ObjectName: object.ObjectName,
|
ObjectName: object.ObjectName,
|
||||||
VersionID: object.VersionID,
|
VersionID: object.VersionID,
|
||||||
}, goi, gerr)
|
}, goi, gerr)
|
||||||
|
replicateSync = repsync
|
||||||
if replicate {
|
if replicate {
|
||||||
|
if apiErrCode := checkRequestAuthType(ctx, r, policy.ReplicateDeleteAction, bucket, object.ObjectName); apiErrCode != ErrNone {
|
||||||
|
if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if object.VersionID != "" {
|
if object.VersionID != "" {
|
||||||
object.VersionPurgeStatus = Pending
|
object.VersionPurgeStatus = Pending
|
||||||
if delMarker {
|
if delMarker {
|
||||||
@@ -511,7 +553,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
dindex := objectsToDelete[ObjectToDelete{
|
dindex := objectsToDelete[ObjectToDelete{
|
||||||
ObjectName: dObjects[i].ObjectName,
|
ObjectName: dObjects[i].ObjectName,
|
||||||
VersionID: dObjects[i].VersionID,
|
VersionID: dObjects[i].VersionID,
|
||||||
DeleteMarkerVersionID: dObjects[i].DeleteMarkerVersionID,
|
|
||||||
VersionPurgeStatus: dObjects[i].VersionPurgeStatus,
|
VersionPurgeStatus: dObjects[i].VersionPurgeStatus,
|
||||||
DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus,
|
DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus,
|
||||||
PurgeTransitioned: dObjects[i].PurgeTransitioned,
|
PurgeTransitioned: dObjects[i].PurgeTransitioned,
|
||||||
@@ -547,32 +588,29 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, encodedSuccessResponse)
|
writeSuccessResponseXML(w, encodedSuccessResponse)
|
||||||
for _, dobj := range deletedObjects {
|
for _, dobj := range deletedObjects {
|
||||||
|
if dobj.ObjectName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if replicateDeletes {
|
if replicateDeletes {
|
||||||
if dobj.DeleteMarkerReplicationStatus == string(replication.Pending) || dobj.VersionPurgeStatus == Pending {
|
if dobj.DeleteMarkerReplicationStatus == string(replication.Pending) || dobj.VersionPurgeStatus == Pending {
|
||||||
globalReplicationState.queueReplicaDeleteTask(DeletedObjectVersionInfo{
|
dv := DeletedObjectVersionInfo{
|
||||||
DeletedObject: dobj,
|
DeletedObject: dobj,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
})
|
}
|
||||||
|
scheduleReplicationDelete(ctx, dv, objectAPI, replicateSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasLifecycleConfig && dobj.PurgeTransitioned == lifecycle.TransitionComplete { // clean up transitioned tier
|
if hasLifecycleConfig && dobj.PurgeTransitioned == lifecycle.TransitionComplete { // clean up transitioned tier
|
||||||
action := lifecycle.DeleteAction
|
deleteTransitionedObject(ctx, objectAPI, bucket, dobj.ObjectName, lifecycle.ObjectOpts{
|
||||||
if dobj.VersionID != "" {
|
|
||||||
action = lifecycle.DeleteVersionAction
|
|
||||||
}
|
|
||||||
deleteTransitionedObject(ctx, newObjectLayerFn(), bucket, dobj.ObjectName, lifecycle.ObjectOpts{
|
|
||||||
Name: dobj.ObjectName,
|
Name: dobj.ObjectName,
|
||||||
VersionID: dobj.VersionID,
|
VersionID: dobj.VersionID,
|
||||||
DeleteMarker: dobj.DeleteMarker,
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
}, action, true)
|
}, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
// Notify deleted event for objects.
|
|
||||||
for _, dobj := range deletedObjects {
|
|
||||||
eventName := event.ObjectRemovedDelete
|
eventName := event.ObjectRemovedDelete
|
||||||
|
|
||||||
objInfo := ObjectInfo{
|
objInfo := ObjectInfo{
|
||||||
Name: dobj.ObjectName,
|
Name: dobj.ObjectName,
|
||||||
VersionID: dobj.VersionID,
|
VersionID: dobj.VersionID,
|
||||||
@@ -602,7 +640,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucket")
|
ctx := newContext(r, w, "PutBucket")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucket", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
objectAPI := api.ObjectAPI()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -733,7 +771,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PostPolicyBucket")
|
ctx := newContext(r, w, "PostPolicyBucket")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PostPolicyBucket", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
objectAPI := api.ObjectAPI()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -873,20 +911,20 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
|
|
||||||
// Extract metadata to be saved from received Form.
|
// Extract metadata to be saved from received Form.
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
err = extractMetadataFromMap(ctx, formValues, metadata)
|
err = extractMetadataFromMime(ctx, textproto.MIMEHeader(formValues), metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize, globalCLIContext.StrictS3Compat)
|
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawReader := hashReader
|
rawReader := hashReader
|
||||||
pReader := NewPutObjReader(rawReader, nil, nil)
|
pReader := NewPutObjReader(rawReader)
|
||||||
var objectEncryptionKey crypto.ObjectKey
|
var objectEncryptionKey crypto.ObjectKey
|
||||||
|
|
||||||
// Check if bucket encryption is enabled
|
// Check if bucket encryption is enabled
|
||||||
@@ -926,12 +964,16 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
info := ObjectInfo{Size: fileSize}
|
info := ObjectInfo{Size: fileSize}
|
||||||
// do not try to verify encrypted content
|
// do not try to verify encrypted content
|
||||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize, globalCLIContext.StrictS3Compat)
|
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pReader = NewPutObjReader(rawReader, hashReader, &objectEncryptionKey)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,7 +1039,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HeadBucket")
|
ctx := newContext(r, w, "HeadBucket")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "HeadBucket", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1027,7 +1069,7 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
|
|||||||
func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteBucket")
|
ctx := newContext(r, w, "DeleteBucket")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteBucket", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1115,7 +1157,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
|||||||
func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketObjectLockConfig")
|
ctx := newContext(r, w, "PutBucketObjectLockConfig")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketObjectLockConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1171,7 +1213,7 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
func (api objectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketObjectLockConfig")
|
ctx := newContext(r, w, "GetBucketObjectLockConfig")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketObjectLockConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1209,7 +1251,7 @@ func (api objectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketTagging")
|
ctx := newContext(r, w, "PutBucketTagging")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketTagging", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1253,7 +1295,7 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketTagging")
|
ctx := newContext(r, w, "GetBucketTagging")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketTagging", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1291,7 +1333,7 @@ func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteBucketTagging")
|
ctx := newContext(r, w, "DeleteBucketTagging")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteBucketTagging", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1321,7 +1363,7 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
|||||||
// Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html
|
// Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html
|
||||||
func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketReplicationConfig")
|
ctx := newContext(r, w, "PutBucketReplicationConfig")
|
||||||
defer logger.AuditLog(w, r, "PutBucketReplicationConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1385,7 +1427,7 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
|
|||||||
func (api objectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketReplicationConfig")
|
ctx := newContext(r, w, "GetBucketReplicationConfig")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketReplicationConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -1426,7 +1468,7 @@ func (api objectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWr
|
|||||||
// ----------
|
// ----------
|
||||||
func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteBucketReplicationConfig")
|
ctx := newContext(r, w, "DeleteBucketReplicationConfig")
|
||||||
defer logger.AuditLog(w, r, "DeleteBucketReplicationConfig", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const (
|
|||||||
func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketLifecycle")
|
ctx := newContext(r, w, "PutBucketLifecycle")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketLifecycle", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -103,7 +103,7 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketLifecycle")
|
ctx := newContext(r, w, "GetBucketLifecycle")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketLifecycle", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -145,7 +145,7 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteBucketLifecycle")
|
ctx := newContext(r, w, "DeleteBucketLifecycle")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteBucketLifecycle", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
|
|||||||
+110
-57
@@ -65,6 +65,46 @@ func NewLifecycleSys() *LifecycleSys {
|
|||||||
return &LifecycleSys{}
|
return &LifecycleSys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type expiryTask struct {
|
||||||
|
objInfo ObjectInfo
|
||||||
|
versionExpiry bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type expiryState struct {
|
||||||
|
expiryCh chan expiryTask
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *expiryState) queueExpiryTask(oi ObjectInfo, rmVersion bool) {
|
||||||
|
select {
|
||||||
|
case es.expiryCh <- expiryTask{objInfo: oi, versionExpiry: rmVersion}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalExpiryState *expiryState
|
||||||
|
)
|
||||||
|
|
||||||
|
func newExpiryState() *expiryState {
|
||||||
|
es := &expiryState{
|
||||||
|
expiryCh: make(chan expiryTask, 10000),
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-GlobalContext.Done()
|
||||||
|
close(es.expiryCh)
|
||||||
|
}()
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
|
globalExpiryState = newExpiryState()
|
||||||
|
go func() {
|
||||||
|
for t := range globalExpiryState.expiryCh {
|
||||||
|
applyExpiryRule(ctx, objectAPI, t.objInfo, false, t.versionExpiry)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
type transitionState struct {
|
type transitionState struct {
|
||||||
// add future metrics here
|
// add future metrics here
|
||||||
transitionCh chan ObjectInfo
|
transitionCh chan ObjectInfo
|
||||||
@@ -205,16 +245,11 @@ func transitionSCInUse(ctx context.Context, lfc *lifecycle.Lifecycle, bucket, ar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set PutObjectOptions for PUT operation to transition data to target cluster
|
// set PutObjectOptions for PUT operation to transition data to target cluster
|
||||||
func putTransitionOpts(objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
|
func putTransitionOpts(objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, err error) {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
|
|
||||||
tag, err := tags.ParseObjectTags(objInfo.UserTags)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
putOpts = miniogo.PutObjectOptions{
|
putOpts = miniogo.PutObjectOptions{
|
||||||
UserMetadata: meta,
|
UserMetadata: meta,
|
||||||
UserTags: tag.ToMap(),
|
|
||||||
ContentType: objInfo.ContentType,
|
ContentType: objInfo.ContentType,
|
||||||
ContentEncoding: objInfo.ContentEncoding,
|
ContentEncoding: objInfo.ContentEncoding,
|
||||||
StorageClass: objInfo.StorageClass,
|
StorageClass: objInfo.StorageClass,
|
||||||
@@ -224,29 +259,47 @@ func putTransitionOpts(objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
|
|||||||
SourceETag: objInfo.ETag,
|
SourceETag: objInfo.ETag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok {
|
|
||||||
|
if objInfo.UserTags != "" {
|
||||||
|
tag, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
|
if tag != nil {
|
||||||
|
putOpts.UserTags = tag.ToMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lkMap := caseInsensitiveMap(objInfo.UserDefined)
|
||||||
|
if lang, ok := lkMap.Lookup(xhttp.ContentLanguage); ok {
|
||||||
|
putOpts.ContentLanguage = lang
|
||||||
|
}
|
||||||
|
if disp, ok := lkMap.Lookup(xhttp.ContentDisposition); ok {
|
||||||
|
putOpts.ContentDisposition = disp
|
||||||
|
}
|
||||||
|
if cc, ok := lkMap.Lookup(xhttp.CacheControl); ok {
|
||||||
|
putOpts.CacheControl = cc
|
||||||
|
}
|
||||||
|
if mode, ok := lkMap.Lookup(xhttp.AmzObjectLockMode); ok {
|
||||||
rmode := miniogo.RetentionMode(mode)
|
rmode := miniogo.RetentionMode(mode)
|
||||||
putOpts.Mode = rmode
|
putOpts.Mode = rmode
|
||||||
}
|
}
|
||||||
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
if retainDateStr, ok := lkMap.Lookup(xhttp.AmzObjectLockRetainUntilDate); ok {
|
||||||
rdate, err := time.Parse(time.RFC3339, retainDateStr)
|
rdate, err := time.Parse(time.RFC3339, retainDateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return putOpts, err
|
||||||
}
|
}
|
||||||
putOpts.RetainUntilDate = rdate
|
putOpts.RetainUntilDate = rdate
|
||||||
}
|
}
|
||||||
if lhold, ok := objInfo.UserDefined[xhttp.AmzObjectLockLegalHold]; ok {
|
if lhold, ok := lkMap.Lookup(xhttp.AmzObjectLockLegalHold); ok {
|
||||||
putOpts.LegalHold = miniogo.LegalHoldStatus(lhold)
|
putOpts.LegalHold = miniogo.LegalHoldStatus(lhold)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return putOpts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle deletes of transitioned objects or object versions when one of the following is true:
|
// handle deletes of transitioned objects or object versions when one of the following is true:
|
||||||
// 1. temporarily restored copies of objects (restored with the PostRestoreObject API) expired.
|
// 1. temporarily restored copies of objects (restored with the PostRestoreObject API) expired.
|
||||||
// 2. life cycle expiry date is met on the object.
|
// 2. life cycle expiry date is met on the object.
|
||||||
// 3. Object is removed through DELETE api call
|
// 3. Object is removed through DELETE api call
|
||||||
func deleteTransitionedObject(ctx context.Context, objectAPI ObjectLayer, bucket, object string, lcOpts lifecycle.ObjectOpts, action lifecycle.Action, isDeleteTierOnly bool) error {
|
func deleteTransitionedObject(ctx context.Context, objectAPI ObjectLayer, bucket, object string, lcOpts lifecycle.ObjectOpts, restoredObject, isDeleteTierOnly bool) error {
|
||||||
if lcOpts.TransitionStatus == "" && !isDeleteTierOnly {
|
if lcOpts.TransitionStatus == "" && !isDeleteTierOnly {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -266,46 +319,41 @@ func deleteTransitionedObject(ctx context.Context, objectAPI ObjectLayer, bucket
|
|||||||
var opts ObjectOptions
|
var opts ObjectOptions
|
||||||
opts.Versioned = globalBucketVersioningSys.Enabled(bucket)
|
opts.Versioned = globalBucketVersioningSys.Enabled(bucket)
|
||||||
opts.VersionID = lcOpts.VersionID
|
opts.VersionID = lcOpts.VersionID
|
||||||
switch action {
|
if restoredObject {
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
|
||||||
// delete locally restored copy of object or object version
|
// delete locally restored copy of object or object version
|
||||||
// from the source, while leaving metadata behind. The data on
|
// from the source, while leaving metadata behind. The data on
|
||||||
// transitioned tier lies untouched and still accessible
|
// transitioned tier lies untouched and still accessible
|
||||||
opts.TransitionStatus = lcOpts.TransitionStatus
|
opts.TransitionStatus = lcOpts.TransitionStatus
|
||||||
_, err = objectAPI.DeleteObject(ctx, bucket, object, opts)
|
_, err = objectAPI.DeleteObject(ctx, bucket, object, opts)
|
||||||
return err
|
return err
|
||||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
|
||||||
// When an object is past expiry, delete the data from transitioned tier and
|
|
||||||
// metadata from source
|
|
||||||
if err := tgt.RemoveObject(context.Background(), arn.Bucket, object, miniogo.RemoveObjectOptions{VersionID: lcOpts.VersionID}); err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDeleteTierOnly {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = objectAPI.DeleteObject(ctx, bucket, object, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
eventName := event.ObjectRemovedDelete
|
|
||||||
if lcOpts.DeleteMarker {
|
|
||||||
eventName = event.ObjectRemovedDeleteMarkerCreated
|
|
||||||
}
|
|
||||||
objInfo := ObjectInfo{
|
|
||||||
Name: object,
|
|
||||||
VersionID: lcOpts.VersionID,
|
|
||||||
DeleteMarker: lcOpts.DeleteMarker,
|
|
||||||
}
|
|
||||||
// Notify object deleted event.
|
|
||||||
sendEvent(eventArgs{
|
|
||||||
EventName: eventName,
|
|
||||||
BucketName: bucket,
|
|
||||||
Object: objInfo,
|
|
||||||
Host: "Internal: [ILM-EXPIRY]",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When an object is past expiry, delete the data from transitioned tier and
|
||||||
|
// metadata from source
|
||||||
|
if err := tgt.RemoveObject(context.Background(), arn.Bucket, object, miniogo.RemoveObjectOptions{VersionID: lcOpts.VersionID}); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDeleteTierOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := objectAPI.DeleteObject(ctx, bucket, object, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
eventName := event.ObjectRemovedDelete
|
||||||
|
if lcOpts.DeleteMarker {
|
||||||
|
eventName = event.ObjectRemovedDeleteMarkerCreated
|
||||||
|
}
|
||||||
|
// Notify object deleted event.
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: eventName,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [ILM-EXPIRY]",
|
||||||
|
})
|
||||||
|
|
||||||
// should never reach here
|
// should never reach here
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -340,13 +388,19 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, objInfo Object
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
oi := gr.ObjInfo
|
oi := gr.ObjInfo
|
||||||
|
|
||||||
if oi.TransitionStatus == lifecycle.TransitionComplete {
|
if oi.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
|
gr.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
putOpts := putTransitionOpts(oi)
|
putOpts, err := putTransitionOpts(oi)
|
||||||
if _, err = tgt.PutObject(ctx, arn.Bucket, oi.Name, gr, oi.Size, "", "", putOpts); err != nil {
|
if err != nil {
|
||||||
|
gr.Close()
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
if _, err = tgt.PutObject(ctx, arn.Bucket, oi.Name, gr, oi.Size, putOpts); err != nil {
|
||||||
|
gr.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gr.Close()
|
gr.Close()
|
||||||
@@ -357,20 +411,19 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, objInfo Object
|
|||||||
opts.TransitionStatus = lifecycle.TransitionComplete
|
opts.TransitionStatus = lifecycle.TransitionComplete
|
||||||
eventName := event.ObjectTransitionComplete
|
eventName := event.ObjectTransitionComplete
|
||||||
|
|
||||||
_, err = objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts)
|
objInfo, err = objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
eventName = event.ObjectTransitionFailed
|
eventName = event.ObjectTransitionFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify object deleted event.
|
// Notify object deleted event.
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: eventName,
|
EventName: eventName,
|
||||||
BucketName: oi.Bucket,
|
BucketName: objInfo.Bucket,
|
||||||
Object: ObjectInfo{
|
Object: objInfo,
|
||||||
Name: oi.Name,
|
Host: "Internal: [ILM-Transition]",
|
||||||
VersionID: opts.VersionID,
|
|
||||||
},
|
|
||||||
Host: "Internal: [ILM-Transition]",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +473,7 @@ func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, _, _, err := tgt.GetObject(ctx, arn.Bucket, object, gopts)
|
reader, err := tgt.GetObject(ctx, arn.Bucket, object, gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -639,11 +692,11 @@ func restoreTransitionedObject(ctx context.Context, bucket, object string, objAP
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer gr.Close()
|
defer gr.Close()
|
||||||
hashReader, err := hash.NewReader(gr, objInfo.Size, "", "", objInfo.Size, globalCLIContext.StrictS3Compat)
|
hashReader, err := hash.NewReader(gr, objInfo.Size, "", "", objInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pReader := NewPutObjReader(hashReader, nil, nil)
|
pReader := NewPutObjReader(hashReader)
|
||||||
opts := putRestoreOpts(bucket, object, rreq, objInfo)
|
opts := putRestoreOpts(bucket, object, rreq, objInfo)
|
||||||
opts.UserDefined[xhttp.AmzRestore] = fmt.Sprintf("ongoing-request=%t, expiry-date=%s", false, restoreExpiry.Format(http.TimeFormat))
|
opts.UserDefined[xhttp.AmzRestore] = fmt.Sprintf("ongoing-request=%t, expiry-date=%s", false, restoreExpiry.Format(http.TimeFormat))
|
||||||
if _, err := objAPI.PutObject(ctx, bucket, object, pReader, opts); err != nil {
|
if _, err := objAPI.PutObject(ctx, bucket, object, pReader, opts); err != nil {
|
||||||
|
|||||||
@@ -26,32 +26,25 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"github.com/minio/minio/pkg/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func concurrentDecryptETag(ctx context.Context, objects []ObjectInfo) {
|
func concurrentDecryptETag(ctx context.Context, objects []ObjectInfo) {
|
||||||
inParallel := func(objects []ObjectInfo) {
|
g := errgroup.WithNErrs(len(objects)).WithConcurrency(500)
|
||||||
g := errgroup.WithNErrs(len(objects))
|
_, cancel := g.WithCancelOnError(ctx)
|
||||||
for index := range objects {
|
defer cancel()
|
||||||
index := index
|
for index := range objects {
|
||||||
g.Go(func() error {
|
index := index
|
||||||
objects[index].ETag = objects[index].GetActualETag(nil)
|
g.Go(func() error {
|
||||||
objects[index].Size, _ = objects[index].GetActualSize()
|
size, err := objects[index].GetActualSize()
|
||||||
return nil
|
if err == nil {
|
||||||
}, index)
|
objects[index].Size = size
|
||||||
}
|
}
|
||||||
g.Wait()
|
objects[index].ETag = objects[index].GetActualETag(nil)
|
||||||
}
|
return nil
|
||||||
const maxConcurrent = 500
|
}, index)
|
||||||
for {
|
|
||||||
if len(objects) < maxConcurrent {
|
|
||||||
inParallel(objects)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
inParallel(objects[:maxConcurrent])
|
|
||||||
objects = objects[maxConcurrent:]
|
|
||||||
}
|
}
|
||||||
|
g.WaitErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
// Validate all the ListObjects query arguments, returns an APIErrorCode
|
||||||
@@ -82,7 +75,7 @@ func validateListObjectsArgs(marker, delimiter, encodingType string, maxKeys int
|
|||||||
func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListObjectVersions")
|
ctx := newContext(r, w, "ListObjectVersions")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListObjectVersions", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -143,7 +136,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListObjectsV2M")
|
ctx := newContext(r, w, "ListObjectsV2M")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListObjectsV2M", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -210,7 +203,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
|
|||||||
func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListObjectsV2")
|
ctx := newContext(r, w, "ListObjectsV2")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListObjectsV2", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -301,10 +294,6 @@ func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http
|
|||||||
return proxyRequest(ctx, w, r, ep)
|
return proxyRequest(ctx, w, r, ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
func proxyRequestByStringHash(ctx context.Context, w http.ResponseWriter, r *http.Request, str string) (success bool) {
|
|
||||||
return proxyRequestByNodeIndex(ctx, w, r, crcHashMod(str, len(globalProxyEndpoints)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListObjectsV1Handler - GET Bucket (List Objects) Version 1.
|
// ListObjectsV1Handler - GET Bucket (List Objects) Version 1.
|
||||||
// --------------------------
|
// --------------------------
|
||||||
// This implementation of the GET operation returns some or all (up to 10000)
|
// This implementation of the GET operation returns some or all (up to 10000)
|
||||||
@@ -314,7 +303,7 @@ func proxyRequestByStringHash(ctx context.Context, w http.ResponseWriter, r *htt
|
|||||||
func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListObjectsV1")
|
ctx := newContext(r, w, "ListObjectsV1")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "ListObjectsV1", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -343,15 +332,6 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward the request using Source IP or bucket
|
|
||||||
forwardStr := handlers.GetSourceIPFromHeaders(r)
|
|
||||||
if forwardStr == "" {
|
|
||||||
forwardStr = bucket
|
|
||||||
}
|
|
||||||
if proxyRequestByStringHash(ctx, w, r, forwardStr) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
listObjects := objectAPI.ListObjects
|
listObjects := objectAPI.ListObjects
|
||||||
|
|
||||||
// Inititate a list objects operation based on the input params.
|
// Inititate a list objects operation based on the input params.
|
||||||
|
|||||||
@@ -477,6 +477,15 @@ func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, ob
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the state of the BucketMetadataSys.
|
||||||
|
func (sys *BucketMetadataSys) Reset() {
|
||||||
|
sys.Lock()
|
||||||
|
for k := range sys.metadataMap {
|
||||||
|
delete(sys.metadataMap, k)
|
||||||
|
}
|
||||||
|
sys.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// NewBucketMetadataSys - creates new policy system.
|
// NewBucketMetadataSys - creates new policy system.
|
||||||
func NewBucketMetadataSys() *BucketMetadataSys {
|
func NewBucketMetadataSys() *BucketMetadataSys {
|
||||||
return &BucketMetadataSys{
|
return &BucketMetadataSys{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const (
|
|||||||
func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketNotification")
|
ctx := newContext(r, w, "GetBucketNotification")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketNotification", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucketName := vars["bucket"]
|
bucketName := vars["bucket"]
|
||||||
@@ -111,7 +111,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
|||||||
func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketNotification")
|
ctx := newContext(r, w, "PutBucketNotification")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketNotification", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI := api.ObjectAPI()
|
objectAPI := api.ObjectAPI()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const (
|
|||||||
func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketPolicy")
|
ctx := newContext(r, w, "PutBucketPolicy")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketPolicy", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -106,7 +106,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
|||||||
func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteBucketPolicy")
|
ctx := newContext(r, w, "DeleteBucketPolicy")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "DeleteBucketPolicy", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -141,7 +141,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketPolicy")
|
ctx := newContext(r, w, "GetBucketPolicy")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketPolicy", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objAPI := api.ObjectAPI()
|
objAPI := api.ObjectAPI()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
|
|||||||
+527
-154
@@ -20,7 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -88,35 +88,37 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
|
|||||||
return false, BucketRemoteTargetNotFound{Bucket: bucket}
|
return false, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustReplicateWeb(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string, permErr APIErrorCode) bool {
|
func mustReplicateWeb(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string, permErr APIErrorCode) (replicate bool, sync bool) {
|
||||||
if permErr != ErrNone {
|
if permErr != ErrNone {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
return mustReplicater(ctx, bucket, object, meta, replStatus)
|
return mustReplicater(ctx, bucket, object, meta, replStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustReplicate returns true if object meets replication criteria.
|
// mustReplicate returns 2 booleans - true if object meets replication criteria and true if replication is to be done in
|
||||||
func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool {
|
// a synchronous manner.
|
||||||
|
func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) (replicate bool, sync bool) {
|
||||||
if s3Err := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, "", r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone {
|
if s3Err := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, "", r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
return mustReplicater(ctx, bucket, object, meta, replStatus)
|
return mustReplicater(ctx, bucket, object, meta, replStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustReplicater returns true if object meets replication criteria.
|
// mustReplicater returns 2 booleans - true if object meets replication criteria and true if replication is to be done in
|
||||||
func mustReplicater(ctx context.Context, bucket, object string, meta map[string]string, replStatus string) bool {
|
// a synchronous manner.
|
||||||
|
func mustReplicater(ctx context.Context, bucket, object string, meta map[string]string, replStatus string) (replicate bool, sync bool) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
return false
|
return replicate, sync
|
||||||
}
|
}
|
||||||
if rs, ok := meta[xhttp.AmzBucketReplicationStatus]; ok {
|
if rs, ok := meta[xhttp.AmzBucketReplicationStatus]; ok {
|
||||||
replStatus = rs
|
replStatus = rs
|
||||||
}
|
}
|
||||||
if replication.StatusType(replStatus) == replication.Replica {
|
if replication.StatusType(replStatus) == replication.Replica {
|
||||||
return false
|
return replicate, sync
|
||||||
}
|
}
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return replicate, sync
|
||||||
}
|
}
|
||||||
opts := replication.ObjectOpts{
|
opts := replication.ObjectOpts{
|
||||||
Name: object,
|
Name: object,
|
||||||
@@ -126,7 +128,30 @@ func mustReplicater(ctx context.Context, bucket, object string, meta map[string]
|
|||||||
if ok {
|
if ok {
|
||||||
opts.UserTags = tagStr
|
opts.UserTags = tagStr
|
||||||
}
|
}
|
||||||
return cfg.Replicate(opts)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, cfg.RoleArn)
|
||||||
|
// the target online status should not be used here while deciding
|
||||||
|
// whether to replicate as the target could be temporarily down
|
||||||
|
if tgt != nil {
|
||||||
|
return cfg.Replicate(opts), tgt.replicateSync
|
||||||
|
}
|
||||||
|
return cfg.Replicate(opts), false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard headers that needs to be extracted from User metadata.
|
||||||
|
var standardHeaders = []string{
|
||||||
|
xhttp.ContentType,
|
||||||
|
xhttp.CacheControl,
|
||||||
|
xhttp.ContentEncoding,
|
||||||
|
xhttp.ContentLanguage,
|
||||||
|
xhttp.ContentDisposition,
|
||||||
|
xhttp.AmzStorageClass,
|
||||||
|
xhttp.AmzObjectTagging,
|
||||||
|
xhttp.AmzBucketReplicationStatus,
|
||||||
|
xhttp.AmzObjectLockMode,
|
||||||
|
xhttp.AmzObjectLockRetainUntilDate,
|
||||||
|
xhttp.AmzObjectLockLegalHold,
|
||||||
|
xhttp.AmzTagCount,
|
||||||
|
xhttp.AmzServerSideEncryption,
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if any of the objects being deleted qualifies for replication.
|
// returns true if any of the objects being deleted qualifies for replication.
|
||||||
@@ -143,33 +168,47 @@ func hasReplicationRules(ctx context.Context, bucket string, objects []ObjectToD
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isStandardHeader returns true if header is a supported header and not a custom header
|
||||||
|
func isStandardHeader(matchHeaderKey string) bool {
|
||||||
|
return equals(matchHeaderKey, standardHeaders...)
|
||||||
|
}
|
||||||
|
|
||||||
// returns whether object version is a deletemarker and if object qualifies for replication
|
// returns whether object version is a deletemarker and if object qualifies for replication
|
||||||
func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelete, oi ObjectInfo, gerr error) (dm, replicate bool) {
|
func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelete, oi ObjectInfo, gerr error) (dm, replicate, sync bool) {
|
||||||
rcfg, err := getReplicationConfig(ctx, bucket)
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil || rcfg == nil {
|
if err != nil || rcfg == nil {
|
||||||
return false, false
|
return false, false, sync
|
||||||
}
|
|
||||||
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
|
||||||
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
|
||||||
if gerr != nil {
|
|
||||||
validReplStatus := false
|
|
||||||
switch oi.ReplicationStatus {
|
|
||||||
case replication.Pending, replication.Complete, replication.Failed:
|
|
||||||
validReplStatus = true
|
|
||||||
}
|
|
||||||
if oi.DeleteMarker && validReplStatus {
|
|
||||||
return oi.DeleteMarker, true
|
|
||||||
}
|
|
||||||
return oi.DeleteMarker, false
|
|
||||||
}
|
}
|
||||||
opts := replication.ObjectOpts{
|
opts := replication.ObjectOpts{
|
||||||
Name: dobj.ObjectName,
|
Name: dobj.ObjectName,
|
||||||
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
UserTags: oi.UserTags,
|
UserTags: oi.UserTags,
|
||||||
DeleteMarker: true,
|
DeleteMarker: oi.DeleteMarker,
|
||||||
VersionID: dobj.VersionID,
|
VersionID: dobj.VersionID,
|
||||||
}
|
}
|
||||||
return oi.DeleteMarker, rcfg.Replicate(opts)
|
replicate = rcfg.Replicate(opts)
|
||||||
|
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
||||||
|
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
||||||
|
if gerr != nil {
|
||||||
|
validReplStatus := false
|
||||||
|
switch oi.ReplicationStatus {
|
||||||
|
case replication.Pending, replication.Completed, replication.Failed:
|
||||||
|
validReplStatus = true
|
||||||
|
}
|
||||||
|
if oi.DeleteMarker && (validReplStatus || replicate) {
|
||||||
|
return oi.DeleteMarker, true, sync
|
||||||
|
}
|
||||||
|
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
||||||
|
// is issued - this still needs to be replicated back to the other target
|
||||||
|
return oi.DeleteMarker, oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed, sync
|
||||||
|
}
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rcfg.RoleArn)
|
||||||
|
// the target online status should not be used here while deciding
|
||||||
|
// whether to replicate deletes as the target could be temporarily down
|
||||||
|
if tgt == nil {
|
||||||
|
return oi.DeleteMarker, false, false
|
||||||
|
}
|
||||||
|
return oi.DeleteMarker, replicate, tgt.replicateSync
|
||||||
}
|
}
|
||||||
|
|
||||||
// replicate deletes to the designated replication target if replication configuration
|
// replicate deletes to the designated replication target if replication configuration
|
||||||
@@ -184,18 +223,45 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
|||||||
// on target.
|
// on target.
|
||||||
func replicateDelete(ctx context.Context, dobj DeletedObjectVersionInfo, objectAPI ObjectLayer) {
|
func replicateDelete(ctx context.Context, dobj DeletedObjectVersionInfo, objectAPI ObjectLayer) {
|
||||||
bucket := dobj.Bucket
|
bucket := dobj.Bucket
|
||||||
rcfg, err := getReplicationConfig(ctx, bucket)
|
|
||||||
if err != nil || rcfg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rcfg.RoleArn)
|
|
||||||
if tgt == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
versionID := dobj.DeleteMarkerVersionID
|
versionID := dobj.DeleteMarkerVersionID
|
||||||
if versionID == "" {
|
if versionID == "" {
|
||||||
versionID = dobj.VersionID
|
versionID = dobj.VersionID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil || rcfg == nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
VersionID: versionID,
|
||||||
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
|
},
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rcfg.RoleArn)
|
||||||
|
if tgt == nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, rcfg.RoleArn))
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
VersionID: versionID,
|
||||||
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
|
},
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
rmErr := tgt.RemoveObject(ctx, rcfg.GetDestination().Bucket, dobj.ObjectName, miniogo.RemoveObjectOptions{
|
rmErr := tgt.RemoveObject(ctx, rcfg.GetDestination().Bucket, dobj.ObjectName, miniogo.RemoveObjectOptions{
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
Internal: miniogo.AdvancedRemoveOptions{
|
Internal: miniogo.AdvancedRemoveOptions{
|
||||||
@@ -214,107 +280,134 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectVersionInfo, objectA
|
|||||||
} else {
|
} else {
|
||||||
versionPurgeStatus = Failed
|
versionPurgeStatus = Failed
|
||||||
}
|
}
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to replicate delete marker to %s/%s(%s): %s", rcfg.GetDestination().Bucket, dobj.ObjectName, versionID, rmErr))
|
||||||
} else {
|
} else {
|
||||||
if dobj.VersionID == "" {
|
if dobj.VersionID == "" {
|
||||||
replicationStatus = string(replication.Complete)
|
replicationStatus = string(replication.Completed)
|
||||||
} else {
|
} else {
|
||||||
versionPurgeStatus = Complete
|
versionPurgeStatus = Complete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventName = event.ObjectReplicationComplete
|
var eventName = event.ObjectReplicationComplete
|
||||||
if replicationStatus == string(replication.Failed) || versionPurgeStatus == Failed {
|
if replicationStatus == string(replication.Failed) || versionPurgeStatus == Failed {
|
||||||
eventName = event.ObjectReplicationFailed
|
eventName = event.ObjectReplicationFailed
|
||||||
}
|
}
|
||||||
objInfo := ObjectInfo{
|
|
||||||
Name: dobj.ObjectName,
|
|
||||||
DeleteMarker: dobj.DeleteMarker,
|
|
||||||
VersionID: versionID,
|
|
||||||
ReplicationStatus: replication.StatusType(dobj.DeleteMarkerReplicationStatus),
|
|
||||||
VersionPurgeStatus: versionPurgeStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
eventArg := &eventArgs{
|
|
||||||
BucketName: bucket,
|
|
||||||
Object: objInfo,
|
|
||||||
Host: "Internal: [Replication]",
|
|
||||||
EventName: eventName,
|
|
||||||
}
|
|
||||||
sendEvent(*eventArg)
|
|
||||||
|
|
||||||
// Update metadata on the delete marker or purge permanent delete if replication success.
|
// Update metadata on the delete marker or purge permanent delete if replication success.
|
||||||
if _, err = objectAPI.DeleteObject(ctx, bucket, dobj.ObjectName, ObjectOptions{
|
dobjInfo, err := objectAPI.DeleteObject(ctx, bucket, dobj.ObjectName, ObjectOptions{
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
DeleteMarker: dobj.DeleteMarker,
|
|
||||||
DeleteMarkerReplicationStatus: replicationStatus,
|
DeleteMarkerReplicationStatus: replicationStatus,
|
||||||
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
|
||||||
VersionPurgeStatus: versionPurgeStatus,
|
VersionPurgeStatus: versionPurgeStatus,
|
||||||
|
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
||||||
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
VersionSuspended: globalBucketVersioningSys.Suspended(bucket),
|
||||||
}); err != nil {
|
})
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s %s: %w", bucket, dobj.ObjectName, dobj.VersionID, err))
|
if err != nil && !isErrVersionNotFound(err) { // VersionNotFound would be reported by pool that object version is missing on.
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s(%s): %s", bucket, dobj.ObjectName, versionID, err))
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
VersionID: versionID,
|
||||||
|
DeleteMarker: dobj.DeleteMarker,
|
||||||
|
},
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
EventName: eventName,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: dobjInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
EventName: eventName,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCopyObjMetadata(oi ObjectInfo, dest replication.Destination) map[string]string {
|
func getCopyObjMetadata(oi ObjectInfo, dest replication.Destination) map[string]string {
|
||||||
meta := make(map[string]string, len(oi.UserDefined))
|
meta := make(map[string]string, len(oi.UserDefined))
|
||||||
for k, v := range oi.UserDefined {
|
for k, v := range oi.UserDefined {
|
||||||
if k == xhttp.AmzBucketReplicationStatus {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if equals(k, xhttp.AmzBucketReplicationStatus) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
||||||
|
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
meta[k] = v
|
meta[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if oi.ContentEncoding != "" {
|
if oi.ContentEncoding != "" {
|
||||||
meta[xhttp.ContentEncoding] = oi.ContentEncoding
|
meta[xhttp.ContentEncoding] = oi.ContentEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
if oi.ContentType != "" {
|
if oi.ContentType != "" {
|
||||||
meta[xhttp.ContentType] = oi.ContentType
|
meta[xhttp.ContentType] = oi.ContentType
|
||||||
}
|
}
|
||||||
tag, err := tags.ParseObjectTags(oi.UserTags)
|
|
||||||
if err != nil {
|
if oi.UserTags != "" {
|
||||||
return nil
|
meta[xhttp.AmzObjectTagging] = oi.UserTags
|
||||||
}
|
|
||||||
if tag != nil {
|
|
||||||
meta[xhttp.AmzObjectTagging] = tag.String()
|
|
||||||
meta[xhttp.AmzTagDirective] = "REPLACE"
|
meta[xhttp.AmzTagDirective] = "REPLACE"
|
||||||
}
|
}
|
||||||
|
|
||||||
sc := dest.StorageClass
|
sc := dest.StorageClass
|
||||||
if sc == "" {
|
if sc == "" {
|
||||||
sc = oi.StorageClass
|
sc = oi.StorageClass
|
||||||
}
|
}
|
||||||
meta[xhttp.AmzStorageClass] = sc
|
if sc != "" {
|
||||||
if oi.UserTags != "" {
|
meta[xhttp.AmzStorageClass] = sc
|
||||||
meta[xhttp.AmzObjectTagging] = oi.UserTags
|
|
||||||
}
|
}
|
||||||
meta[xhttp.MinIOSourceMTime] = oi.ModTime.Format(time.RFC3339Nano)
|
|
||||||
meta[xhttp.MinIOSourceETag] = oi.ETag
|
meta[xhttp.MinIOSourceETag] = oi.ETag
|
||||||
|
meta[xhttp.MinIOSourceMTime] = oi.ModTime.Format(time.RFC3339Nano)
|
||||||
meta[xhttp.AmzBucketReplicationStatus] = replication.Replica.String()
|
meta[xhttp.AmzBucketReplicationStatus] = replication.Replica.String()
|
||||||
return meta
|
return meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func putReplicationOpts(ctx context.Context, dest replication.Destination, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
|
type caseInsensitiveMap map[string]string
|
||||||
|
|
||||||
|
// Lookup map entry case insensitively.
|
||||||
|
func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for _, k := range []string{
|
||||||
|
key,
|
||||||
|
strings.ToLower(key),
|
||||||
|
http.CanonicalHeaderKey(key),
|
||||||
|
} {
|
||||||
|
v, ok := m[k]
|
||||||
|
if ok {
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func putReplicationOpts(ctx context.Context, dest replication.Destination, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, err error) {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
if k == xhttp.AmzBucketReplicationStatus {
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if isStandardHeader(k) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
meta[k] = v
|
meta[k] = v
|
||||||
}
|
}
|
||||||
tag, err := tags.ParseObjectTags(objInfo.UserTags)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sc := dest.StorageClass
|
sc := dest.StorageClass
|
||||||
if sc == "" {
|
if sc == "" {
|
||||||
sc = objInfo.StorageClass
|
sc = objInfo.StorageClass
|
||||||
}
|
}
|
||||||
putOpts = miniogo.PutObjectOptions{
|
putOpts = miniogo.PutObjectOptions{
|
||||||
UserMetadata: meta,
|
UserMetadata: meta,
|
||||||
UserTags: tag.ToMap(),
|
|
||||||
ContentType: objInfo.ContentType,
|
ContentType: objInfo.ContentType,
|
||||||
ContentEncoding: objInfo.ContentEncoding,
|
ContentEncoding: objInfo.ContentEncoding,
|
||||||
StorageClass: sc,
|
StorageClass: sc,
|
||||||
@@ -325,24 +418,40 @@ func putReplicationOpts(ctx context.Context, dest replication.Destination, objIn
|
|||||||
SourceETag: objInfo.ETag,
|
SourceETag: objInfo.ETag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok {
|
if objInfo.UserTags != "" {
|
||||||
|
tag, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
|
if tag != nil {
|
||||||
|
putOpts.UserTags = tag.ToMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lkMap := caseInsensitiveMap(objInfo.UserDefined)
|
||||||
|
if lang, ok := lkMap.Lookup(xhttp.ContentLanguage); ok {
|
||||||
|
putOpts.ContentLanguage = lang
|
||||||
|
}
|
||||||
|
if disp, ok := lkMap.Lookup(xhttp.ContentDisposition); ok {
|
||||||
|
putOpts.ContentDisposition = disp
|
||||||
|
}
|
||||||
|
if cc, ok := lkMap.Lookup(xhttp.CacheControl); ok {
|
||||||
|
putOpts.CacheControl = cc
|
||||||
|
}
|
||||||
|
if mode, ok := lkMap.Lookup(xhttp.AmzObjectLockMode); ok {
|
||||||
rmode := miniogo.RetentionMode(mode)
|
rmode := miniogo.RetentionMode(mode)
|
||||||
putOpts.Mode = rmode
|
putOpts.Mode = rmode
|
||||||
}
|
}
|
||||||
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
if retainDateStr, ok := lkMap.Lookup(xhttp.AmzObjectLockRetainUntilDate); ok {
|
||||||
rdate, err := time.Parse(time.RFC3339Nano, retainDateStr)
|
rdate, err := time.Parse(time.RFC3339, retainDateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return putOpts, err
|
||||||
}
|
}
|
||||||
putOpts.RetainUntilDate = rdate
|
putOpts.RetainUntilDate = rdate
|
||||||
}
|
}
|
||||||
if lhold, ok := objInfo.UserDefined[xhttp.AmzObjectLockLegalHold]; ok {
|
if lhold, ok := lkMap.Lookup(xhttp.AmzObjectLockLegalHold); ok {
|
||||||
putOpts.LegalHold = miniogo.LegalHoldStatus(lhold)
|
putOpts.LegalHold = miniogo.LegalHoldStatus(lhold)
|
||||||
}
|
}
|
||||||
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
|
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
|
||||||
putOpts.ServerSideEncryption = encrypt.NewSSE()
|
putOpts.ServerSideEncryption = encrypt.NewSSE()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,152 +463,277 @@ const (
|
|||||||
replicateAll replicationAction = "all"
|
replicateAll replicationAction = "all"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// matches k1 with all keys, returns 'true' if one of them matches
|
||||||
|
func equals(k1 string, keys ...string) bool {
|
||||||
|
for _, k2 := range keys {
|
||||||
|
if strings.ToLower(k1) == strings.ToLower(k2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// returns replicationAction by comparing metadata between source and target
|
// returns replicationAction by comparing metadata between source and target
|
||||||
func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo) replicationAction {
|
func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo) replicationAction {
|
||||||
// needs full replication
|
// needs full replication
|
||||||
if oi1.ETag != oi2.ETag ||
|
if oi1.ETag != oi2.ETag ||
|
||||||
oi1.VersionID != oi2.VersionID ||
|
oi1.VersionID != oi2.VersionID ||
|
||||||
oi1.Size != oi2.Size ||
|
oi1.Size != oi2.Size ||
|
||||||
oi1.DeleteMarker != oi2.IsDeleteMarker {
|
oi1.DeleteMarker != oi2.IsDeleteMarker ||
|
||||||
|
oi1.ModTime.Unix() != oi2.LastModified.Unix() {
|
||||||
return replicateAll
|
return replicateAll
|
||||||
}
|
}
|
||||||
|
|
||||||
if !oi1.ModTime.Equal(oi2.LastModified) ||
|
if oi1.ContentType != oi2.ContentType {
|
||||||
oi1.ContentType != oi2.ContentType ||
|
|
||||||
oi1.StorageClass != oi2.StorageClass {
|
|
||||||
return replicateMetadata
|
return replicateMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
if oi1.ContentEncoding != "" {
|
if oi1.ContentEncoding != "" {
|
||||||
enc, ok := oi2.UserMetadata[xhttp.ContentEncoding]
|
enc, ok := oi2.Metadata[xhttp.ContentEncoding]
|
||||||
if !ok || enc != oi1.ContentEncoding {
|
if !ok {
|
||||||
|
enc, ok = oi2.Metadata[strings.ToLower(xhttp.ContentEncoding)]
|
||||||
|
if !ok {
|
||||||
|
return replicateMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Join(enc, ",") != oi1.ContentEncoding {
|
||||||
return replicateMetadata
|
return replicateMetadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, v := range oi2.UserMetadata {
|
|
||||||
oi2.Metadata[k] = []string{v}
|
t, _ := tags.ParseObjectTags(oi1.UserTags)
|
||||||
}
|
if !reflect.DeepEqual(oi2.UserTags, t.ToMap()) {
|
||||||
if len(oi2.Metadata) != len(oi1.UserDefined) {
|
|
||||||
return replicateMetadata
|
return replicateMetadata
|
||||||
}
|
}
|
||||||
for k1, v1 := range oi1.UserDefined {
|
|
||||||
if v2, ok := oi2.Metadata[k1]; !ok || v1 != strings.Join(v2, "") {
|
// Compare only necessary headers
|
||||||
return replicateMetadata
|
compareKeys := []string{
|
||||||
|
"Expires",
|
||||||
|
"Cache-Control",
|
||||||
|
"Content-Language",
|
||||||
|
"Content-Disposition",
|
||||||
|
"X-Amz-Object-Lock-Mode",
|
||||||
|
"X-Amz-Object-Lock-Retain-Until-Date",
|
||||||
|
"X-Amz-Object-Lock-Legal-Hold",
|
||||||
|
"X-Amz-Website-Redirect-Location",
|
||||||
|
"X-Amz-Meta-",
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare metadata on both maps to see if meta is identical
|
||||||
|
compareMeta1 := make(map[string]string)
|
||||||
|
for k, v := range oi1.UserDefined {
|
||||||
|
var found bool
|
||||||
|
for _, prefix := range compareKeys {
|
||||||
|
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
compareMeta1[strings.ToLower(k)] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t, _ := tags.MapToObjectTags(oi2.UserTags)
|
|
||||||
if t.String() != oi1.UserTags {
|
compareMeta2 := make(map[string]string)
|
||||||
|
for k, v := range oi2.Metadata {
|
||||||
|
var found bool
|
||||||
|
for _, prefix := range compareKeys {
|
||||||
|
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
compareMeta2[strings.ToLower(k)] = strings.Join(v, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(compareMeta1, compareMeta2) {
|
||||||
return replicateMetadata
|
return replicateMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
return replicateNone
|
return replicateNone
|
||||||
}
|
}
|
||||||
|
|
||||||
// replicateObject replicates the specified version of the object to destination bucket
|
// replicateObject replicates the specified version of the object to destination bucket
|
||||||
// The source object is then updated to reflect the replication status.
|
// The source object is then updated to reflect the replication status.
|
||||||
func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLayer) {
|
func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLayer) {
|
||||||
|
z, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
bucket := objInfo.Bucket
|
bucket := objInfo.Bucket
|
||||||
object := objInfo.Name
|
object := objInfo.Name
|
||||||
|
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, cfg.RoleArn)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, cfg.RoleArn)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, cfg.RoleArn))
|
logger.LogIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, cfg.RoleArn))
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{
|
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{
|
||||||
VersionID: objInfo.VersionID,
|
VersionID: objInfo.VersionID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer gr.Close() // hold read lock for entire transaction
|
||||||
|
|
||||||
objInfo = gr.ObjInfo
|
objInfo = gr.ObjInfo
|
||||||
size, err := objInfo.GetActualSize()
|
size, err := objInfo.GetActualSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
gr.Close()
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dest := cfg.GetDestination()
|
dest := cfg.GetDestination()
|
||||||
if dest.Bucket == "" {
|
if dest.Bucket == "" {
|
||||||
gr.Close()
|
logger.LogIf(ctx, fmt.Errorf("Unable to replicate object %s(%s), bucket is empty", objInfo.Name, objInfo.VersionID))
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rtype := replicateAll
|
rtype := replicateAll
|
||||||
oi, err := tgt.StatObject(ctx, dest.Bucket, object, miniogo.StatObjectOptions{VersionID: objInfo.VersionID})
|
oi, err := tgt.StatObject(ctx, dest.Bucket, object, miniogo.StatObjectOptions{
|
||||||
|
VersionID: objInfo.VersionID,
|
||||||
|
Internal: miniogo.AdvancedGetOptions{
|
||||||
|
ReplicationProxyRequest: "false",
|
||||||
|
}})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rtype = getReplicationAction(objInfo, oi)
|
rtype = getReplicationAction(objInfo, oi)
|
||||||
if rtype == replicateNone {
|
if rtype == replicateNone {
|
||||||
gr.Close()
|
|
||||||
// object with same VersionID already exists, replication kicked off by
|
// object with same VersionID already exists, replication kicked off by
|
||||||
// PutObject might have completed.
|
// PutObject might have completed.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
replicationStatus := replication.Completed
|
||||||
target, err := globalBucketMetadataSys.GetBucketTarget(bucket, cfg.RoleArn)
|
// use core client to avoid doing multipart on PUT
|
||||||
if err != nil {
|
c := &miniogo.Core{Client: tgt.Client}
|
||||||
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%s", bucket, cfg.RoleArn, err))
|
if rtype != replicateAll {
|
||||||
return
|
|
||||||
}
|
|
||||||
putOpts := putReplicationOpts(ctx, dest, objInfo)
|
|
||||||
replicationStatus := replication.Complete
|
|
||||||
|
|
||||||
// Setup bandwidth throttling
|
|
||||||
peers, _ := globalEndpoints.peers()
|
|
||||||
totalNodesCount := len(peers)
|
|
||||||
if totalNodesCount == 0 {
|
|
||||||
totalNodesCount = 1 // For standalone erasure coding
|
|
||||||
}
|
|
||||||
b := target.BandwidthLimit / int64(totalNodesCount)
|
|
||||||
var headerSize int
|
|
||||||
for k, v := range putOpts.Header() {
|
|
||||||
headerSize += len(k) + len(v)
|
|
||||||
}
|
|
||||||
r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b, target.BandwidthLimit)
|
|
||||||
if rtype == replicateAll {
|
|
||||||
_, err = tgt.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts)
|
|
||||||
} else {
|
|
||||||
// replicate metadata for object tagging/copy with metadata replacement
|
// replicate metadata for object tagging/copy with metadata replacement
|
||||||
|
srcOpts := miniogo.CopySrcOptions{
|
||||||
|
Bucket: dest.Bucket,
|
||||||
|
Object: object,
|
||||||
|
VersionID: objInfo.VersionID}
|
||||||
dstOpts := miniogo.PutObjectOptions{Internal: miniogo.AdvancedPutOptions{SourceVersionID: objInfo.VersionID}}
|
dstOpts := miniogo.PutObjectOptions{Internal: miniogo.AdvancedPutOptions{SourceVersionID: objInfo.VersionID}}
|
||||||
_, err = tgt.CopyObject(ctx, dest.Bucket, object, dest.Bucket, object, getCopyObjMetadata(objInfo, dest), dstOpts)
|
if _, err = c.CopyObject(ctx, dest.Bucket, object, dest.Bucket, object, getCopyObjMetadata(objInfo, dest), srcOpts, dstOpts); err != nil {
|
||||||
|
replicationStatus = replication.Failed
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to replicate metadata for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target, err := globalBucketMetadataSys.GetBucketTarget(bucket, cfg.RoleArn)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%s", bucket, cfg.RoleArn, err))
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
putOpts, err := putReplicationOpts(ctx, dest, objInfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%w", bucket, cfg.RoleArn, err))
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
|
BucketName: bucket,
|
||||||
|
Object: objInfo,
|
||||||
|
Host: "Internal: [Replication]",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup bandwidth throttling
|
||||||
|
peers, _ := globalEndpoints.peers()
|
||||||
|
totalNodesCount := len(peers)
|
||||||
|
if totalNodesCount == 0 {
|
||||||
|
totalNodesCount = 1 // For standalone erasure coding
|
||||||
|
}
|
||||||
|
b := target.BandwidthLimit / int64(totalNodesCount)
|
||||||
|
var headerSize int
|
||||||
|
for k, v := range putOpts.Header() {
|
||||||
|
headerSize += len(k) + len(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// r takes over closing gr.
|
||||||
|
r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b, target.BandwidthLimit)
|
||||||
|
if _, err = c.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts); err != nil {
|
||||||
|
replicationStatus = replication.Failed
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %w", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Close()
|
|
||||||
if err != nil {
|
|
||||||
replicationStatus = replication.Failed
|
|
||||||
}
|
|
||||||
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
||||||
if objInfo.UserTags != "" {
|
if objInfo.UserTags != "" {
|
||||||
objInfo.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
|
objInfo.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: add support for missing replication events
|
// FIXME: add support for missing replication events
|
||||||
// - event.ObjectReplicationNotTracked
|
|
||||||
// - event.ObjectReplicationMissedThreshold
|
// - event.ObjectReplicationMissedThreshold
|
||||||
// - event.ObjectReplicationReplicatedAfterThreshold
|
// - event.ObjectReplicationReplicatedAfterThreshold
|
||||||
var eventName = event.ObjectReplicationComplete
|
var eventName = event.ObjectReplicationComplete
|
||||||
if replicationStatus == replication.Failed {
|
if replicationStatus == replication.Failed {
|
||||||
eventName = event.ObjectReplicationFailed
|
eventName = event.ObjectReplicationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This lower level implementation is necessary to avoid write locks from CopyObject.
|
||||||
|
poolIdx, err := z.getPoolIdx(ctx, bucket, object, objInfo.Size)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s(%s): %w", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||||
|
} else {
|
||||||
|
if err = z.serverPools[poolIdx].getHashedSet(object).updateObjectMeta(ctx, bucket, object, objInfo.UserDefined, ObjectOptions{
|
||||||
|
VersionID: objInfo.VersionID,
|
||||||
|
}); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s(%s): %w", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: eventName,
|
EventName: eventName,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
Object: objInfo,
|
Object: objInfo,
|
||||||
Host: "Internal: [Replication]",
|
Host: "Internal: [Replication]",
|
||||||
})
|
})
|
||||||
objInfo.metadataOnly = true // Perform only metadata updates.
|
|
||||||
if _, err = objectAPI.CopyObject(ctx, bucket, object, bucket, object, objInfo, ObjectOptions{
|
|
||||||
VersionID: objInfo.VersionID,
|
|
||||||
}, ObjectOptions{
|
|
||||||
VersionID: objInfo.VersionID,
|
|
||||||
}); err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s: %s", objInfo.VersionID, err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterReplicationStatusMetadata filters replication status metadata for COPY
|
// filterReplicationStatusMetadata filters replication status metadata for COPY
|
||||||
@@ -558,19 +792,9 @@ func (r *replicationState) queueReplicaDeleteTask(doi DeletedObjectVersionInfo)
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
globalReplicationState *replicationState
|
globalReplicationState *replicationState
|
||||||
// TODO: currently keeping it conservative
|
|
||||||
// but eventually can be tuned in future,
|
|
||||||
// take only half the CPUs for replication
|
|
||||||
// conservatively.
|
|
||||||
globalReplicationConcurrent = runtime.GOMAXPROCS(0) / 2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newReplicationState() *replicationState {
|
func newReplicationState() *replicationState {
|
||||||
|
|
||||||
// fix minimum concurrent replication to 1 for single CPU setup
|
|
||||||
if globalReplicationConcurrent == 0 {
|
|
||||||
globalReplicationConcurrent = 1
|
|
||||||
}
|
|
||||||
rs := &replicationState{
|
rs := &replicationState{
|
||||||
replicaCh: make(chan ObjectInfo, 10000),
|
replicaCh: make(chan ObjectInfo, 10000),
|
||||||
replicaDeleteCh: make(chan DeletedObjectVersionInfo, 10000),
|
replicaDeleteCh: make(chan DeletedObjectVersionInfo, 10000),
|
||||||
@@ -611,8 +835,157 @@ func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start with globalReplicationConcurrent.
|
// Start replication workers per count set in api config or MINIO_API_REPLICATION_WORKERS.
|
||||||
for i := 0; i < globalReplicationConcurrent; i++ {
|
for i := 0; i < globalAPIConfig.getReplicationWorkers(); i++ {
|
||||||
globalReplicationState.addWorker(ctx, objectAPI)
|
globalReplicationState.addWorker(ctx, objectAPI)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get Reader from replication target if active-active replication is in place and
|
||||||
|
// this node returns a 404
|
||||||
|
func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, proxy bool) {
|
||||||
|
tgt, oi, proxy, err := proxyHeadToRepTarget(ctx, bucket, object, opts)
|
||||||
|
if !proxy || err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
fn, off, length, err := NewGetObjectReader(rs, oi, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
gopts := miniogo.GetObjectOptions{
|
||||||
|
VersionID: opts.VersionID,
|
||||||
|
ServerSideEncryption: opts.ServerSideEncryption,
|
||||||
|
Internal: miniogo.AdvancedGetOptions{
|
||||||
|
ReplicationProxyRequest: "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// get correct offsets for encrypted object
|
||||||
|
if off >= 0 && length >= 0 {
|
||||||
|
if err := gopts.SetRange(off, off+length-1); err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make sure to match ETag when proxying.
|
||||||
|
if err = gopts.SetMatchETag(oi.ETag); err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
c := miniogo.Core{Client: tgt.Client}
|
||||||
|
obj, _, _, err := c.GetObject(ctx, bucket, object, gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
closeReader := func() { obj.Close() }
|
||||||
|
|
||||||
|
reader, err := fn(obj, h, opts.CheckPrecondFn, closeReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
reader.ObjInfo = oi.Clone()
|
||||||
|
return reader, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isProxyable returns true if replication config found for this bucket
|
||||||
|
func isProxyable(ctx context.Context, bucket string) bool {
|
||||||
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dest := cfg.GetDestination()
|
||||||
|
return dest.Bucket == bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxyHeadToRepTarget(ctx context.Context, bucket, object string, opts ObjectOptions) (tgt *TargetClient, oi ObjectInfo, proxy bool, err error) {
|
||||||
|
// this option is set when active-active replication is in place between site A -> B,
|
||||||
|
// and site B does not have the object yet.
|
||||||
|
if opts.ProxyRequest || (opts.ProxyHeaderSet && !opts.ProxyRequest) { // true only when site B sets MinIOSourceProxyRequest header
|
||||||
|
return nil, oi, false, nil
|
||||||
|
}
|
||||||
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return nil, oi, false, err
|
||||||
|
}
|
||||||
|
dest := cfg.GetDestination()
|
||||||
|
if dest.Bucket != bucket { // not active-active
|
||||||
|
return nil, oi, false, err
|
||||||
|
}
|
||||||
|
ssec := false
|
||||||
|
if opts.ServerSideEncryption != nil {
|
||||||
|
ssec = opts.ServerSideEncryption.Type() == encrypt.SSEC
|
||||||
|
}
|
||||||
|
ropts := replication.ObjectOpts{
|
||||||
|
Name: object,
|
||||||
|
SSEC: ssec,
|
||||||
|
}
|
||||||
|
if !cfg.Replicate(ropts) { // no matching rule for object prefix
|
||||||
|
return nil, oi, false, nil
|
||||||
|
}
|
||||||
|
tgt = globalBucketTargetSys.GetRemoteTargetClient(ctx, cfg.RoleArn)
|
||||||
|
if tgt == nil || tgt.isOffline() {
|
||||||
|
return nil, oi, false, fmt.Errorf("target is offline or not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
gopts := miniogo.GetObjectOptions{
|
||||||
|
VersionID: opts.VersionID,
|
||||||
|
ServerSideEncryption: opts.ServerSideEncryption,
|
||||||
|
Internal: miniogo.AdvancedGetOptions{
|
||||||
|
ReplicationProxyRequest: "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := tgt.StatObject(ctx, dest.Bucket, object, gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, oi, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, _ := tags.MapToObjectTags(objInfo.UserTags)
|
||||||
|
oi = ObjectInfo{
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: object,
|
||||||
|
ModTime: objInfo.LastModified,
|
||||||
|
Size: objInfo.Size,
|
||||||
|
ETag: objInfo.ETag,
|
||||||
|
VersionID: objInfo.VersionID,
|
||||||
|
IsLatest: objInfo.IsLatest,
|
||||||
|
DeleteMarker: objInfo.IsDeleteMarker,
|
||||||
|
ContentType: objInfo.ContentType,
|
||||||
|
Expires: objInfo.Expires,
|
||||||
|
StorageClass: objInfo.StorageClass,
|
||||||
|
ReplicationStatus: replication.StatusType(objInfo.ReplicationStatus),
|
||||||
|
UserTags: tags.String(),
|
||||||
|
}
|
||||||
|
oi.UserDefined = make(map[string]string, len(objInfo.Metadata))
|
||||||
|
for k, v := range objInfo.Metadata {
|
||||||
|
oi.UserDefined[k] = v[0]
|
||||||
|
}
|
||||||
|
ce, ok := oi.UserDefined[xhttp.ContentEncoding]
|
||||||
|
if !ok {
|
||||||
|
ce, ok = oi.UserDefined[strings.ToLower(xhttp.ContentEncoding)]
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
oi.ContentEncoding = ce
|
||||||
|
}
|
||||||
|
return tgt, oi, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get object info from replication target if active-active replication is in place and
|
||||||
|
// this node returns a 404
|
||||||
|
func proxyHeadToReplicationTarget(ctx context.Context, bucket, object string, opts ObjectOptions) (oi ObjectInfo, proxy bool, err error) {
|
||||||
|
_, oi, proxy, err = proxyHeadToRepTarget(ctx, bucket, object, opts)
|
||||||
|
return oi, proxy, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheduleReplication(ctx context.Context, objInfo ObjectInfo, o ObjectLayer, sync bool) {
|
||||||
|
if sync {
|
||||||
|
replicateObject(ctx, objInfo, o)
|
||||||
|
} else {
|
||||||
|
globalReplicationState.queueReplicaTask(objInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheduleReplicationDelete(ctx context.Context, dv DeletedObjectVersionInfo, o ObjectLayer, sync bool) {
|
||||||
|
if sync {
|
||||||
|
replicateDelete(ctx, dv, o)
|
||||||
|
} else {
|
||||||
|
globalReplicationState.queueReplicaDeleteTask(dv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+59
-12
@@ -18,11 +18,13 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
@@ -31,13 +33,16 @@ import (
|
|||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/pkg/bucket/versioning"
|
"github.com/minio/minio/pkg/bucket/versioning"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
sha256 "github.com/minio/sha256-simd"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultHealthCheckDuration = 100 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketTargetSys represents bucket targets subsystem
|
// BucketTargetSys represents bucket targets subsystem
|
||||||
type BucketTargetSys struct {
|
type BucketTargetSys struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
arnRemotesMap map[string]*miniogo.Core
|
arnRemotesMap map[string]*TargetClient
|
||||||
targetsMap map[string][]madmin.BucketTarget
|
targetsMap map[string][]madmin.BucketTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +73,6 @@ func (sys *BucketTargetSys) ListTargets(ctx context.Context, bucket, arnType str
|
|||||||
|
|
||||||
// ListBucketTargets - gets list of bucket targets for this bucket.
|
// ListBucketTargets - gets list of bucket targets for this bucket.
|
||||||
func (sys *BucketTargetSys) ListBucketTargets(ctx context.Context, bucket string) (*madmin.BucketTargets, error) {
|
func (sys *BucketTargetSys) ListBucketTargets(ctx context.Context, bucket string) (*madmin.BucketTargets, error) {
|
||||||
|
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
defer sys.RUnlock()
|
defer sys.RUnlock()
|
||||||
|
|
||||||
@@ -132,7 +136,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
|
|
||||||
tgts := sys.targetsMap[bucket]
|
tgts := sys.targetsMap[bucket]
|
||||||
newtgts := make([]madmin.BucketTarget, len(tgts))
|
newtgts := make([]madmin.BucketTarget, len(tgts))
|
||||||
labels := make(map[string]struct{})
|
labels := make(map[string]struct{}, len(tgts))
|
||||||
found := false
|
found := false
|
||||||
for idx, t := range tgts {
|
for idx, t := range tgts {
|
||||||
labels[t.Label] = struct{}{}
|
labels[t.Label] = struct{}{}
|
||||||
@@ -198,9 +202,12 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
// delete ARN type from list of matching targets
|
// delete ARN type from list of matching targets
|
||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
targets := make([]madmin.BucketTarget, 0)
|
|
||||||
found := false
|
found := false
|
||||||
tgts := sys.targetsMap[bucket]
|
tgts, ok := sys.targetsMap[bucket]
|
||||||
|
if !ok {
|
||||||
|
return BucketRemoteTargetNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
targets := make([]madmin.BucketTarget, 0, len(tgts))
|
||||||
for _, tgt := range tgts {
|
for _, tgt := range tgts {
|
||||||
if tgt.Arn != arnStr {
|
if tgt.Arn != arnStr {
|
||||||
targets = append(targets, tgt)
|
targets = append(targets, tgt)
|
||||||
@@ -217,7 +224,7 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRemoteTargetClient returns minio-go client for replication target instance
|
// GetRemoteTargetClient returns minio-go client for replication target instance
|
||||||
func (sys *BucketTargetSys) GetRemoteTargetClient(ctx context.Context, arn string) *miniogo.Core {
|
func (sys *BucketTargetSys) GetRemoteTargetClient(ctx context.Context, arn string) *TargetClient {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
defer sys.RUnlock()
|
defer sys.RUnlock()
|
||||||
return sys.arnRemotesMap[arn]
|
return sys.arnRemotesMap[arn]
|
||||||
@@ -264,7 +271,7 @@ func (sys *BucketTargetSys) GetRemoteLabelWithArn(ctx context.Context, bucket, a
|
|||||||
// NewBucketTargetSys - creates new replication system.
|
// NewBucketTargetSys - creates new replication system.
|
||||||
func NewBucketTargetSys() *BucketTargetSys {
|
func NewBucketTargetSys() *BucketTargetSys {
|
||||||
return &BucketTargetSys{
|
return &BucketTargetSys{
|
||||||
arnRemotesMap: make(map[string]*miniogo.Core),
|
arnRemotesMap: make(map[string]*TargetClient),
|
||||||
targetsMap: make(map[string][]madmin.BucketTarget),
|
targetsMap: make(map[string][]madmin.BucketTarget),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,20 +352,34 @@ var getRemoteTargetInstanceTransport http.RoundTripper
|
|||||||
var getRemoteTargetInstanceTransportOnce sync.Once
|
var getRemoteTargetInstanceTransportOnce sync.Once
|
||||||
|
|
||||||
// Returns a minio-go Client configured to access remote host described in replication target config.
|
// Returns a minio-go Client configured to access remote host described in replication target config.
|
||||||
func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*miniogo.Core, error) {
|
func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*TargetClient, error) {
|
||||||
config := tcfg.Credentials
|
config := tcfg.Credentials
|
||||||
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
||||||
|
|
||||||
getRemoteTargetInstanceTransportOnce.Do(func() {
|
getRemoteTargetInstanceTransportOnce.Do(func() {
|
||||||
getRemoteTargetInstanceTransport = newGatewayHTTPTransport(10 * time.Minute)
|
getRemoteTargetInstanceTransport = newGatewayHTTPTransport(10 * time.Minute)
|
||||||
})
|
})
|
||||||
|
api, err := minio.New(tcfg.Endpoint, &miniogo.Options{
|
||||||
core, err := miniogo.NewCore(tcfg.URL().Host, &miniogo.Options{
|
|
||||||
Creds: creds,
|
Creds: creds,
|
||||||
Secure: tcfg.Secure,
|
Secure: tcfg.Secure,
|
||||||
|
Region: tcfg.Region,
|
||||||
Transport: getRemoteTargetInstanceTransport,
|
Transport: getRemoteTargetInstanceTransport,
|
||||||
})
|
})
|
||||||
return core, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hcDuration := defaultHealthCheckDuration
|
||||||
|
if tcfg.HealthCheckDuration >= 1 { // require minimum health check duration of 1 sec.
|
||||||
|
hcDuration = tcfg.HealthCheckDuration
|
||||||
|
}
|
||||||
|
tc := &TargetClient{
|
||||||
|
Client: api,
|
||||||
|
healthCheckDuration: hcDuration,
|
||||||
|
bucket: tcfg.TargetBucket,
|
||||||
|
replicateSync: tcfg.ReplicationSync,
|
||||||
|
}
|
||||||
|
go tc.healthCheck()
|
||||||
|
return tc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRemoteARN gets existing ARN for an endpoint or generates a new one.
|
// getRemoteARN gets existing ARN for an endpoint or generates a new one.
|
||||||
@@ -422,3 +443,29 @@ func parseBucketTargetConfig(bucket string, cdata, cmetadata []byte) (*madmin.Bu
|
|||||||
}
|
}
|
||||||
return &t, nil
|
return &t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TargetClient is the struct for remote target client.
|
||||||
|
type TargetClient struct {
|
||||||
|
*miniogo.Client
|
||||||
|
up int32
|
||||||
|
healthCheckDuration time.Duration
|
||||||
|
bucket string // remote bucket target
|
||||||
|
replicateSync bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TargetClient) isOffline() bool {
|
||||||
|
return atomic.LoadInt32(&tc.up) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TargetClient) healthCheck() {
|
||||||
|
for {
|
||||||
|
_, err := tc.BucketExists(GlobalContext, tc.bucket)
|
||||||
|
if err != nil {
|
||||||
|
atomic.StoreInt32(&tc.up, 0)
|
||||||
|
time.Sleep(tc.healthCheckDuration)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
atomic.StoreInt32(&tc.up, 1)
|
||||||
|
time.Sleep(tc.healthCheckDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const (
|
|||||||
func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketVersioning")
|
ctx := newContext(r, w, "PutBucketVersioning")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "PutBucketVersioning", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -98,7 +98,7 @@ func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketVersioning")
|
ctx := newContext(r, w, "GetBucketVersioning")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketVersioning", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ func (sys *BucketVersioningSys) Get(bucket string) (*versioning.Versioning, erro
|
|||||||
return globalBucketMetadataSys.GetVersioningConfig(bucket)
|
return globalBucketMetadataSys.GetVersioningConfig(bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset BucketVersioningSys to initial state.
|
||||||
|
func (sys *BucketVersioningSys) Reset() {
|
||||||
|
// There is currently no internal state.
|
||||||
|
}
|
||||||
|
|
||||||
// NewBucketVersioningSys - creates new versioning system.
|
// NewBucketVersioningSys - creates new versioning system.
|
||||||
func NewBucketVersioningSys() *BucketVersioningSys {
|
func NewBucketVersioningSys() *BucketVersioningSys {
|
||||||
return &BucketVersioningSys{}
|
return &BucketVersioningSys{}
|
||||||
|
|||||||
+49
-2
@@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -27,6 +28,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -41,20 +43,36 @@ import (
|
|||||||
"github.com/minio/minio/pkg/certs"
|
"github.com/minio/minio/pkg/certs"
|
||||||
"github.com/minio/minio/pkg/console"
|
"github.com/minio/minio/pkg/console"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
|
"github.com/minio/minio/pkg/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serverDebugLog will enable debug printing
|
// serverDebugLog will enable debug printing
|
||||||
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
|
||||||
logger.Init(GOPATH, GOROOT)
|
logger.Init(GOPATH, GOROOT)
|
||||||
logger.RegisterError(config.FmtError)
|
logger.RegisterError(config.FmtError)
|
||||||
|
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
// Inject into config package.
|
||||||
globalDNSCache = xhttp.NewDNSCache(10*time.Second, 10*time.Second)
|
config.Logger.Info = logger.Info
|
||||||
|
config.Logger.LogIf = logger.LogIf
|
||||||
|
|
||||||
|
globalDNSCache = xhttp.NewDNSCache(10*time.Second, 10*time.Second, logger.LogOnceIf)
|
||||||
|
|
||||||
initGlobalContext()
|
initGlobalContext()
|
||||||
|
|
||||||
|
globalForwarder = handlers.NewForwarder(&handlers.Forwarder{
|
||||||
|
PassHost: true,
|
||||||
|
RoundTripper: newGatewayHTTPTransport(1 * time.Hour),
|
||||||
|
Logger: func(err error) {
|
||||||
|
if err != nil && !errors.Is(err, context.Canceled) {
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
globalReplicationState = newReplicationState()
|
globalReplicationState = newReplicationState()
|
||||||
globalTransitionState = newTransitionState()
|
globalTransitionState = newTransitionState()
|
||||||
|
|
||||||
@@ -241,6 +259,14 @@ func handleCommonEnvVars() {
|
|||||||
}
|
}
|
||||||
globalDomainNames = append(globalDomainNames, domainName)
|
globalDomainNames = append(globalDomainNames, domainName)
|
||||||
}
|
}
|
||||||
|
sort.Strings(globalDomainNames)
|
||||||
|
lcpSuf := lcpSuffix(globalDomainNames)
|
||||||
|
for _, domainName := range globalDomainNames {
|
||||||
|
if domainName == lcpSuf && len(globalDomainNames) > 1 {
|
||||||
|
logger.Fatal(config.ErrOverlappingDomainValue(nil).Msg("Overlapping domains `%s` not allowed", globalDomainNames),
|
||||||
|
"Invalid MINIO_DOMAIN value in environment variable")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publicIPs := env.Get(config.EnvPublicIPs, "")
|
publicIPs := env.Get(config.EnvPublicIPs, "")
|
||||||
@@ -286,6 +312,16 @@ func handleCommonEnvVars() {
|
|||||||
globalConfigEncrypted = true
|
globalConfigEncrypted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if env.IsSet(config.EnvRootUser) || env.IsSet(config.EnvRootPassword) {
|
||||||
|
cred, err := auth.CreateCredentials(env.Get(config.EnvRootUser, ""), env.Get(config.EnvRootPassword, ""))
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(config.ErrInvalidCredentials(err),
|
||||||
|
"Unable to validate credentials inherited from the shell environment")
|
||||||
|
}
|
||||||
|
globalActiveCred = cred
|
||||||
|
globalConfigEncrypted = true
|
||||||
|
}
|
||||||
|
|
||||||
if env.IsSet(config.EnvAccessKeyOld) && env.IsSet(config.EnvSecretKeyOld) {
|
if env.IsSet(config.EnvAccessKeyOld) && env.IsSet(config.EnvSecretKeyOld) {
|
||||||
oldCred, err := auth.CreateCredentials(env.Get(config.EnvAccessKeyOld, ""), env.Get(config.EnvSecretKeyOld, ""))
|
oldCred, err := auth.CreateCredentials(env.Get(config.EnvAccessKeyOld, ""), env.Get(config.EnvSecretKeyOld, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -296,6 +332,17 @@ func handleCommonEnvVars() {
|
|||||||
os.Unsetenv(config.EnvAccessKeyOld)
|
os.Unsetenv(config.EnvAccessKeyOld)
|
||||||
os.Unsetenv(config.EnvSecretKeyOld)
|
os.Unsetenv(config.EnvSecretKeyOld)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if env.IsSet(config.EnvRootUserOld) && env.IsSet(config.EnvRootPasswordOld) {
|
||||||
|
oldCred, err := auth.CreateCredentials(env.Get(config.EnvRootUserOld, ""), env.Get(config.EnvRootPasswordOld, ""))
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(config.ErrInvalidCredentials(err),
|
||||||
|
"Unable to validate the old credentials inherited from the shell environment")
|
||||||
|
}
|
||||||
|
globalOldCred = oldCred
|
||||||
|
os.Unsetenv(config.EnvRootUserOld)
|
||||||
|
os.Unsetenv(config.EnvRootPasswordOld)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logStartupMessage(msg string) {
|
func logStartupMessage(msg string) {
|
||||||
|
|||||||
+13
-8
@@ -20,6 +20,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
)
|
)
|
||||||
@@ -27,9 +29,9 @@ import (
|
|||||||
var errConfigNotFound = errors.New("config file not found")
|
var errConfigNotFound = errors.New("config file not found")
|
||||||
|
|
||||||
func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) ([]byte, error) {
|
func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) ([]byte, error) {
|
||||||
var buffer bytes.Buffer
|
|
||||||
// Read entire content by setting size to -1
|
// Read entire content by setting size to -1
|
||||||
if err := objAPI.GetObject(ctx, minioMetaBucket, configFile, 0, -1, &buffer, "", ObjectOptions{}); err != nil {
|
r, err := objAPI.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, readLock, ObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
// Treat object not found as config not found.
|
// Treat object not found as config not found.
|
||||||
if isErrObjectNotFound(err) {
|
if isErrObjectNotFound(err) {
|
||||||
return nil, errConfigNotFound
|
return nil, errConfigNotFound
|
||||||
@@ -37,13 +39,16 @@ func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) ([]b
|
|||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
// Return config not found on empty content.
|
buf, err := ioutil.ReadAll(r)
|
||||||
if buffer.Len() == 0 {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(buf) == 0 {
|
||||||
return nil, errConfigNotFound
|
return nil, errConfigNotFound
|
||||||
}
|
}
|
||||||
|
return buf, nil
|
||||||
return buffer.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectDeleter interface {
|
type objectDeleter interface {
|
||||||
@@ -59,12 +64,12 @@ func deleteConfig(ctx context.Context, objAPI objectDeleter, configFile string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveConfig(ctx context.Context, objAPI ObjectLayer, configFile string, data []byte) error {
|
func saveConfig(ctx context.Context, objAPI ObjectLayer, configFile string, data []byte) error {
|
||||||
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)), globalCLIContext.StrictS3Compat)
|
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 = objAPI.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader, nil, nil), ObjectOptions{})
|
_, err = objAPI.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader), ObjectOptions{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-35
@@ -18,6 +18,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -26,7 +27,6 @@ import (
|
|||||||
"github.com/minio/minio/cmd/config/api"
|
"github.com/minio/minio/cmd/config/api"
|
||||||
"github.com/minio/minio/cmd/config/cache"
|
"github.com/minio/minio/cmd/config/cache"
|
||||||
"github.com/minio/minio/cmd/config/compress"
|
"github.com/minio/minio/cmd/config/compress"
|
||||||
"github.com/minio/minio/cmd/config/crawler"
|
|
||||||
"github.com/minio/minio/cmd/config/dns"
|
"github.com/minio/minio/cmd/config/dns"
|
||||||
"github.com/minio/minio/cmd/config/etcd"
|
"github.com/minio/minio/cmd/config/etcd"
|
||||||
"github.com/minio/minio/cmd/config/heal"
|
"github.com/minio/minio/cmd/config/heal"
|
||||||
@@ -34,6 +34,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/config/identity/openid"
|
"github.com/minio/minio/cmd/config/identity/openid"
|
||||||
"github.com/minio/minio/cmd/config/notify"
|
"github.com/minio/minio/cmd/config/notify"
|
||||||
"github.com/minio/minio/cmd/config/policy/opa"
|
"github.com/minio/minio/cmd/config/policy/opa"
|
||||||
|
"github.com/minio/minio/cmd/config/scanner"
|
||||||
"github.com/minio/minio/cmd/config/storageclass"
|
"github.com/minio/minio/cmd/config/storageclass"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
@@ -59,7 +60,7 @@ func initHelp() {
|
|||||||
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
||||||
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
||||||
config.HealSubSys: heal.DefaultKVS,
|
config.HealSubSys: heal.DefaultKVS,
|
||||||
config.CrawlerSubSys: crawler.DefaultKVS,
|
config.ScannerSubSys: scanner.DefaultKVS,
|
||||||
}
|
}
|
||||||
for k, v := range notify.DefaultNotificationKVS {
|
for k, v := range notify.DefaultNotificationKVS {
|
||||||
kvs[k] = v
|
kvs[k] = v
|
||||||
@@ -116,8 +117,8 @@ func initHelp() {
|
|||||||
Description: "manage object healing frequency and bitrot verification checks",
|
Description: "manage object healing frequency and bitrot verification checks",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.CrawlerSubSys,
|
Key: config.ScannerSubSys,
|
||||||
Description: "manage crawling for usage calculation, lifecycle, healing and more",
|
Description: "manage scanner for usage calculation, lifecycle, healing and more",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.LoggerWebhookSubSys,
|
Key: config.LoggerWebhookSubSys,
|
||||||
@@ -199,7 +200,7 @@ func initHelp() {
|
|||||||
config.CacheSubSys: cache.Help,
|
config.CacheSubSys: cache.Help,
|
||||||
config.CompressionSubSys: compress.Help,
|
config.CompressionSubSys: compress.Help,
|
||||||
config.HealSubSys: heal.Help,
|
config.HealSubSys: heal.Help,
|
||||||
config.CrawlerSubSys: crawler.Help,
|
config.ScannerSubSys: scanner.Help,
|
||||||
config.IdentityOpenIDSubSys: openid.Help,
|
config.IdentityOpenIDSubSys: openid.Help,
|
||||||
config.IdentityLDAPSubSys: xldap.Help,
|
config.IdentityLDAPSubSys: xldap.Help,
|
||||||
config.PolicyOPASubSys: opa.Help,
|
config.PolicyOPASubSys: opa.Help,
|
||||||
@@ -228,7 +229,7 @@ var (
|
|||||||
globalServerConfigMu sync.RWMutex
|
globalServerConfigMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateConfig(s config.Config, setDriveCount int) error {
|
func validateConfig(s config.Config, setDriveCounts []int) error {
|
||||||
// We must have a global lock for this so nobody else modifies env while we do.
|
// We must have a global lock for this so nobody else modifies env while we do.
|
||||||
defer env.LockSetEnv()()
|
defer env.LockSetEnv()()
|
||||||
|
|
||||||
@@ -251,8 +252,10 @@ func validateConfig(s config.Config, setDriveCount int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if globalIsErasure {
|
if globalIsErasure {
|
||||||
if _, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount); err != nil {
|
for _, setDriveCount := range setDriveCounts {
|
||||||
return err
|
if _, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,11 +274,11 @@ func validateConfig(s config.Config, setDriveCount int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default]); err != nil {
|
if _, err = scanner.LookupConfig(s[config.ScannerSubSys][config.Default]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +296,10 @@ func validateConfig(s config.Config, setDriveCount int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
|
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), newCustomHTTPTransportWithHTTP2(
|
||||||
|
&tls.Config{
|
||||||
|
RootCAs: globalRootCAs,
|
||||||
|
}, defaultDialTimeout)())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -342,7 +348,7 @@ func validateConfig(s config.Config, setDriveCount int) error {
|
|||||||
return notify.TestNotificationTargets(GlobalContext, s, NewGatewayHTTPTransport(), globalNotificationSys.ConfiguredTargetIDs())
|
return notify.TestNotificationTargets(GlobalContext, s, NewGatewayHTTPTransport(), globalNotificationSys.ConfiguredTargetIDs())
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupConfigs(s config.Config, setDriveCount int) {
|
func lookupConfigs(s config.Config, setDriveCounts []int) {
|
||||||
ctx := GlobalContext
|
ctx := GlobalContext
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -429,7 +435,7 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
globalAPIConfig.init(apiConfig, setDriveCount)
|
globalAPIConfig.init(apiConfig, setDriveCounts)
|
||||||
|
|
||||||
// Initialize remote instance transport once.
|
// Initialize remote instance transport once.
|
||||||
getRemoteInstanceTransportOnce.Do(func() {
|
getRemoteInstanceTransportOnce.Do(func() {
|
||||||
@@ -437,9 +443,17 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if globalIsErasure {
|
if globalIsErasure {
|
||||||
globalStorageClass, err = storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount)
|
for i, setDriveCount := range setDriveCounts {
|
||||||
if err != nil {
|
sc, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount)
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize storage class config: %w", err))
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize storage class config: %w", err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if we validated all setDriveCounts and it was successful
|
||||||
|
// proceed to store the correct storage class globally.
|
||||||
|
if i == len(setDriveCounts)-1 {
|
||||||
|
globalStorageClass = sc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +475,10 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
|
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), newCustomHTTPTransportWithHTTP2(
|
||||||
|
&tls.Config{
|
||||||
|
RootCAs: globalRootCAs,
|
||||||
|
}, defaultDialTimeout)())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS config: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS config: %w", err))
|
||||||
}
|
}
|
||||||
@@ -470,11 +487,13 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS with current KMS config: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS with current KMS config: %w", err))
|
||||||
}
|
}
|
||||||
|
globalAutoEncryption = kmsCfg.AutoEncryption // Enable auto-encryption if enabled
|
||||||
|
|
||||||
// Enable auto-encryption if enabled
|
if kmsCfg.Vault.Enabled {
|
||||||
globalAutoEncryption = kmsCfg.AutoEncryption
|
const deprecationWarning = `Native Hashicorp Vault support is deprecated and will be removed on 2021-10-01. Please migrate to KES + Hashicorp Vault: https://github.com/minio/kes/wiki/Hashicorp-Vault-Keystore
|
||||||
if globalAutoEncryption && !globalIsGateway {
|
Note that native Hashicorp Vault and KES + Hashicorp Vault are not compatible.
|
||||||
logger.LogIf(ctx, fmt.Errorf("%s env is deprecated please migrate to using `mc encrypt` at bucket level", crypto.EnvKMSAutoEncryption))
|
If you need help to migrate smoothly visit: https://min.io/pricing`
|
||||||
|
logger.LogIf(ctx, fmt.Errorf(deprecationWarning))
|
||||||
}
|
}
|
||||||
|
|
||||||
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
||||||
@@ -553,12 +572,16 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply dynamic config values
|
// Apply dynamic config values
|
||||||
logger.LogIf(ctx, applyDynamicConfig(ctx, s))
|
logger.LogIf(ctx, applyDynamicConfig(ctx, newObjectLayerFn(), s))
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyDynamicConfig will apply dynamic config values.
|
// applyDynamicConfig will apply dynamic config values.
|
||||||
// Dynamic systems should be in config.SubSystemsDynamic as well.
|
// Dynamic systems should be in config.SubSystemsDynamic as well.
|
||||||
func applyDynamicConfig(ctx context.Context, s config.Config) error {
|
func applyDynamicConfig(ctx context.Context, objAPI ObjectLayer, s config.Config) error {
|
||||||
|
if objAPI == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Read all dynamic configs.
|
// Read all dynamic configs.
|
||||||
// API
|
// API
|
||||||
apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default])
|
apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default])
|
||||||
@@ -571,28 +594,27 @@ func applyDynamicConfig(ctx context.Context, s config.Config) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to setup Compression: %w", err)
|
return fmt.Errorf("Unable to setup Compression: %w", err)
|
||||||
}
|
}
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI != nil {
|
// Validate if the object layer supports compression.
|
||||||
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||||
return fmt.Errorf("Backend does not support compression")
|
return fmt.Errorf("Backend does not support compression")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal
|
// Heal
|
||||||
healCfg, err := heal.LookupConfig(s[config.HealSubSys][config.Default])
|
healCfg, err := heal.LookupConfig(s[config.HealSubSys][config.Default])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to apply heal config: %w", err))
|
return fmt.Errorf("Unable to apply heal config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crawler
|
// Scanner
|
||||||
crawlerCfg, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default])
|
scannerCfg, err := scanner.LookupConfig(s[config.ScannerSubSys][config.Default])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to apply crawler config: %w", err)
|
return fmt.Errorf("Unable to apply scanner config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply configurations.
|
// Apply configurations.
|
||||||
// We should not fail after this.
|
// We should not fail after this.
|
||||||
globalAPIConfig.init(apiConfig, globalAPIConfig.setDriveCount)
|
globalAPIConfig.init(apiConfig, objAPI.SetDriveCounts())
|
||||||
|
|
||||||
globalCompressConfigMu.Lock()
|
globalCompressConfigMu.Lock()
|
||||||
globalCompressConfig = cmpCfg
|
globalCompressConfig = cmpCfg
|
||||||
@@ -602,7 +624,7 @@ func applyDynamicConfig(ctx context.Context, s config.Config) error {
|
|||||||
globalHealConfig = healCfg
|
globalHealConfig = healCfg
|
||||||
globalHealConfigMu.Unlock()
|
globalHealConfigMu.Unlock()
|
||||||
|
|
||||||
logger.LogIf(ctx, crawlerSleeper.Update(crawlerCfg.Delay, crawlerCfg.MaxWait))
|
logger.LogIf(ctx, scannerSleeper.Update(scannerCfg.Delay, scannerCfg.MaxWait))
|
||||||
|
|
||||||
// Update all dynamic config values in memory.
|
// Update all dynamic config values in memory.
|
||||||
globalServerConfigMu.Lock()
|
globalServerConfigMu.Lock()
|
||||||
@@ -723,7 +745,7 @@ func loadConfig(objAPI ObjectLayer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override any values from ENVs.
|
// Override any values from ENVs.
|
||||||
lookupConfigs(srvCfg, objAPI.SetDriveCount())
|
lookupConfigs(srvCfg, objAPI.SetDriveCounts())
|
||||||
|
|
||||||
// hold the mutex lock before a new config is assigned.
|
// hold the mutex lock before a new config is assigned.
|
||||||
globalServerConfigMu.Lock()
|
globalServerConfigMu.Lock()
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if encrypted && globalActiveCred.IsValid() && globalOldCred.IsValid() {
|
if encrypted && globalActiveCred.IsValid() && globalOldCred.IsValid() {
|
||||||
logger.Info("Rotation complete, please make sure to unset MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD envs")
|
logger.Info("Rotation complete, please make sure to unset MINIO_ROOT_USER_OLD and MINIO_ROOT_PASSWORD_OLD envs")
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationComplete)
|
return saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||||
@@ -294,7 +294,7 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede
|
|||||||
}
|
}
|
||||||
|
|
||||||
if encrypted && globalActiveCred.IsValid() && activeCredOld.IsValid() {
|
if encrypted && globalActiveCred.IsValid() && activeCredOld.IsValid() {
|
||||||
logger.Info("Rotation complete, please make sure to unset MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD envs")
|
logger.Info("Rotation complete, please make sure to unset MINIO_ROOT_USER_OLD and MINIO_ROOT_PASSWORD_OLD envs")
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveConfig(GlobalContext, objAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
return saveConfig(GlobalContext, objAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||||
|
|||||||
+25
-9
@@ -29,14 +29,14 @@ import (
|
|||||||
|
|
||||||
// API sub-system constants
|
// API sub-system constants
|
||||||
const (
|
const (
|
||||||
apiRequestsMax = "requests_max"
|
apiRequestsMax = "requests_max"
|
||||||
apiRequestsDeadline = "requests_deadline"
|
apiRequestsDeadline = "requests_deadline"
|
||||||
apiClusterDeadline = "cluster_deadline"
|
apiClusterDeadline = "cluster_deadline"
|
||||||
apiCorsAllowOrigin = "cors_allow_origin"
|
apiCorsAllowOrigin = "cors_allow_origin"
|
||||||
apiRemoteTransportDeadline = "remote_transport_deadline"
|
apiRemoteTransportDeadline = "remote_transport_deadline"
|
||||||
apiListQuorum = "list_quorum"
|
apiListQuorum = "list_quorum"
|
||||||
apiExtendListCacheLife = "extend_list_cache_life"
|
apiExtendListCacheLife = "extend_list_cache_life"
|
||||||
|
apiReplicationWorkers = "replication_workers"
|
||||||
EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX"
|
EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX"
|
||||||
EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE"
|
EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE"
|
||||||
EnvAPIClusterDeadline = "MINIO_API_CLUSTER_DEADLINE"
|
EnvAPIClusterDeadline = "MINIO_API_CLUSTER_DEADLINE"
|
||||||
@@ -45,6 +45,7 @@ const (
|
|||||||
EnvAPIListQuorum = "MINIO_API_LIST_QUORUM"
|
EnvAPIListQuorum = "MINIO_API_LIST_QUORUM"
|
||||||
EnvAPIExtendListCacheLife = "MINIO_API_EXTEND_LIST_CACHE_LIFE"
|
EnvAPIExtendListCacheLife = "MINIO_API_EXTEND_LIST_CACHE_LIFE"
|
||||||
EnvAPISecureCiphers = "MINIO_API_SECURE_CIPHERS"
|
EnvAPISecureCiphers = "MINIO_API_SECURE_CIPHERS"
|
||||||
|
EnvAPIReplicationWorkers = "MINIO_API_REPLICATION_WORKERS"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deprecated key and ENVs
|
// Deprecated key and ENVs
|
||||||
@@ -78,12 +79,16 @@ var (
|
|||||||
},
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: apiListQuorum,
|
Key: apiListQuorum,
|
||||||
Value: "optimal",
|
Value: "strict",
|
||||||
},
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: apiExtendListCacheLife,
|
Key: apiExtendListCacheLife,
|
||||||
Value: "0s",
|
Value: "0s",
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: apiReplicationWorkers,
|
||||||
|
Value: "100",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,6 +101,7 @@ type Config struct {
|
|||||||
RemoteTransportDeadline time.Duration `json:"remote_transport_deadline"`
|
RemoteTransportDeadline time.Duration `json:"remote_transport_deadline"`
|
||||||
ListQuorum string `json:"list_strict_quorum"`
|
ListQuorum string `json:"list_strict_quorum"`
|
||||||
ExtendListLife time.Duration `json:"extend_list_cache_life"`
|
ExtendListLife time.Duration `json:"extend_list_cache_life"`
|
||||||
|
ReplicationWorkers int `json:"replication_workers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
||||||
@@ -173,6 +179,15 @@ func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
|||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replicationWorkers, err := strconv.Atoi(env.Get(EnvAPIReplicationWorkers, kvs.Get(apiReplicationWorkers)))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if replicationWorkers <= 0 {
|
||||||
|
return cfg, config.ErrInvalidReplicationWorkersValue(nil).Msg("Minimum number of replication workers should be 1")
|
||||||
|
}
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
RequestsMax: requestsMax,
|
RequestsMax: requestsMax,
|
||||||
RequestsDeadline: requestsDeadline,
|
RequestsDeadline: requestsDeadline,
|
||||||
@@ -181,5 +196,6 @@ func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
|||||||
RemoteTransportDeadline: remoteTransportDeadline,
|
RemoteTransportDeadline: remoteTransportDeadline,
|
||||||
ListQuorum: listQuorum,
|
ListQuorum: listQuorum,
|
||||||
ExtendListLife: listLife,
|
ExtendListLife: listLife,
|
||||||
|
ReplicationWorkers: replicationWorkers,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,5 +45,11 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "duration",
|
Type: "duration",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: apiReplicationWorkers,
|
||||||
|
Description: `set the number of replication workers, defaults to 100`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "number",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,23 +26,26 @@ import (
|
|||||||
|
|
||||||
// Config represents the compression settings.
|
// Config represents the compression settings.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Extensions []string `json:"extensions"`
|
AllowEncrypted bool `json:"allow_encryption"`
|
||||||
MimeTypes []string `json:"mime-types"`
|
Extensions []string `json:"extensions"`
|
||||||
|
MimeTypes []string `json:"mime-types"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compression environment variables
|
// Compression environment variables
|
||||||
const (
|
const (
|
||||||
Extensions = "extensions"
|
Extensions = "extensions"
|
||||||
MimeTypes = "mime_types"
|
AllowEncrypted = "allow_encryption"
|
||||||
|
MimeTypes = "mime_types"
|
||||||
|
|
||||||
EnvCompressState = "MINIO_COMPRESS_ENABLE"
|
EnvCompressState = "MINIO_COMPRESS_ENABLE"
|
||||||
EnvCompressExtensions = "MINIO_COMPRESS_EXTENSIONS"
|
EnvCompressAllowEncryption = "MINIO_COMPRESS_ALLOW_ENCRYPTION"
|
||||||
EnvCompressMimeTypes = "MINIO_COMPRESS_MIME_TYPES"
|
EnvCompressExtensions = "MINIO_COMPRESS_EXTENSIONS"
|
||||||
|
EnvCompressMimeTypes = "MINIO_COMPRESS_MIME_TYPES"
|
||||||
|
|
||||||
// Include-list for compression.
|
// Include-list for compression.
|
||||||
DefaultExtensions = ".txt,.log,.csv,.json,.tar,.xml,.bin"
|
DefaultExtensions = ".txt,.log,.csv,.json,.tar,.xml,.bin"
|
||||||
DefaultMimeTypes = "text/*,application/json,application/xml"
|
DefaultMimeTypes = "text/*,application/json,application/xml,binary/octet-stream"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultKVS - default KV config for compression settings
|
// DefaultKVS - default KV config for compression settings
|
||||||
@@ -52,6 +55,10 @@ var (
|
|||||||
Key: config.Enable,
|
Key: config.Enable,
|
||||||
Value: config.EnableOff,
|
Value: config.EnableOff,
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: AllowEncrypted,
|
||||||
|
Value: config.EnableOff,
|
||||||
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: Extensions,
|
Key: Extensions,
|
||||||
Value: DefaultExtensions,
|
Value: DefaultExtensions,
|
||||||
@@ -101,6 +108,12 @@ func LookupConfig(kvs config.KVS) (Config, error) {
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowEnc := env.Get(EnvCompressAllowEncryption, kvs.Get(AllowEncrypted))
|
||||||
|
cfg.AllowEncrypted, err = config.ParseBool(allowEnc)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
compressExtensions := env.Get(EnvCompressExtensions, kvs.Get(Extensions))
|
compressExtensions := env.Get(EnvCompressExtensions, kvs.Get(Extensions))
|
||||||
compressMimeTypes := env.Get(EnvCompressMimeTypes, kvs.Get(MimeTypes))
|
compressMimeTypes := env.Get(EnvCompressMimeTypes, kvs.Get(MimeTypes))
|
||||||
compressMimeTypesLegacy := env.Get(EnvCompressMimeTypesLegacy, kvs.Get(MimeTypes))
|
compressMimeTypesLegacy := env.Get(EnvCompressMimeTypesLegacy, kvs.Get(MimeTypes))
|
||||||
|
|||||||
+26
-6
@@ -77,6 +77,7 @@ const (
|
|||||||
LoggerWebhookSubSys = "logger_webhook"
|
LoggerWebhookSubSys = "logger_webhook"
|
||||||
AuditWebhookSubSys = "audit_webhook"
|
AuditWebhookSubSys = "audit_webhook"
|
||||||
HealSubSys = "heal"
|
HealSubSys = "heal"
|
||||||
|
ScannerSubSys = "scanner"
|
||||||
CrawlerSubSys = "crawler"
|
CrawlerSubSys = "crawler"
|
||||||
|
|
||||||
// Add new constants here if you add new fields to config.
|
// Add new constants here if you add new fields to config.
|
||||||
@@ -114,7 +115,7 @@ var SubSystems = set.CreateStringSet(
|
|||||||
PolicyOPASubSys,
|
PolicyOPASubSys,
|
||||||
IdentityLDAPSubSys,
|
IdentityLDAPSubSys,
|
||||||
IdentityOpenIDSubSys,
|
IdentityOpenIDSubSys,
|
||||||
CrawlerSubSys,
|
ScannerSubSys,
|
||||||
HealSubSys,
|
HealSubSys,
|
||||||
NotifyAMQPSubSys,
|
NotifyAMQPSubSys,
|
||||||
NotifyESSubSys,
|
NotifyESSubSys,
|
||||||
@@ -132,7 +133,7 @@ var SubSystems = set.CreateStringSet(
|
|||||||
var SubSystemsDynamic = set.CreateStringSet(
|
var SubSystemsDynamic = set.CreateStringSet(
|
||||||
APISubSys,
|
APISubSys,
|
||||||
CompressionSubSys,
|
CompressionSubSys,
|
||||||
CrawlerSubSys,
|
ScannerSubSys,
|
||||||
HealSubSys,
|
HealSubSys,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -151,7 +152,7 @@ var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
|||||||
IdentityLDAPSubSys,
|
IdentityLDAPSubSys,
|
||||||
IdentityOpenIDSubSys,
|
IdentityOpenIDSubSys,
|
||||||
HealSubSys,
|
HealSubSys,
|
||||||
CrawlerSubSys,
|
ScannerSubSys,
|
||||||
}...)
|
}...)
|
||||||
|
|
||||||
// Constant separators
|
// Constant separators
|
||||||
@@ -462,6 +463,13 @@ func LookupWorm() (bool, error) {
|
|||||||
return ParseBool(env.Get(EnvWorm, EnableOff))
|
return ParseBool(env.Get(EnvWorm, EnableOff))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Carries all the renamed sub-systems from their
|
||||||
|
// previously known names
|
||||||
|
var renamedSubsys = map[string]string{
|
||||||
|
CrawlerSubSys: ScannerSubSys,
|
||||||
|
// Add future sub-system renames
|
||||||
|
}
|
||||||
|
|
||||||
// Merge - merges a new config with all the
|
// Merge - merges a new config with all the
|
||||||
// missing values for default configs,
|
// missing values for default configs,
|
||||||
// returns a config.
|
// returns a config.
|
||||||
@@ -477,9 +485,21 @@ func (c Config) Merge() Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := cp[subSys]; !ok {
|
if _, ok := cp[subSys]; !ok {
|
||||||
// A config subsystem was removed or server was downgraded.
|
rnSubSys, ok := renamedSubsys[subSys]
|
||||||
Logger.Info("config: ignoring unknown subsystem config %q\n", subSys)
|
if !ok {
|
||||||
continue
|
// A config subsystem was removed or server was downgraded.
|
||||||
|
Logger.Info("config: ignoring unknown subsystem config %q\n", subSys)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Copy over settings from previous sub-system
|
||||||
|
// to newly renamed sub-system
|
||||||
|
for _, kv := range cp[rnSubSys][Default] {
|
||||||
|
_, ok := c[subSys][tgt].Lookup(kv.Key)
|
||||||
|
if !ok {
|
||||||
|
ckvs.Set(kv.Key, kv.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subSys = rnSubSys
|
||||||
}
|
}
|
||||||
cp[subSys][tgt] = ckvs
|
cp[subSys][tgt] = ckvs
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-11
@@ -23,17 +23,21 @@ const (
|
|||||||
|
|
||||||
// Top level common ENVs
|
// Top level common ENVs
|
||||||
const (
|
const (
|
||||||
EnvAccessKey = "MINIO_ACCESS_KEY"
|
EnvAccessKey = "MINIO_ACCESS_KEY"
|
||||||
EnvSecretKey = "MINIO_SECRET_KEY"
|
EnvSecretKey = "MINIO_SECRET_KEY"
|
||||||
EnvAccessKeyOld = "MINIO_ACCESS_KEY_OLD"
|
EnvRootUser = "MINIO_ROOT_USER"
|
||||||
EnvSecretKeyOld = "MINIO_SECRET_KEY_OLD"
|
EnvRootPassword = "MINIO_ROOT_PASSWORD"
|
||||||
EnvBrowser = "MINIO_BROWSER"
|
EnvAccessKeyOld = "MINIO_ACCESS_KEY_OLD"
|
||||||
EnvDomain = "MINIO_DOMAIN"
|
EnvSecretKeyOld = "MINIO_SECRET_KEY_OLD"
|
||||||
EnvRegionName = "MINIO_REGION_NAME"
|
EnvRootUserOld = "MINIO_ROOT_USER_OLD"
|
||||||
EnvPublicIPs = "MINIO_PUBLIC_IPS"
|
EnvRootPasswordOld = "MINIO_ROOT_PASSWORD_OLD"
|
||||||
EnvFSOSync = "MINIO_FS_OSYNC"
|
EnvBrowser = "MINIO_BROWSER"
|
||||||
EnvArgs = "MINIO_ARGS"
|
EnvDomain = "MINIO_DOMAIN"
|
||||||
EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT"
|
EnvRegionName = "MINIO_REGION_NAME"
|
||||||
|
EnvPublicIPs = "MINIO_PUBLIC_IPS"
|
||||||
|
EnvFSOSync = "MINIO_FS_OSYNC"
|
||||||
|
EnvArgs = "MINIO_ARGS"
|
||||||
|
EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT"
|
||||||
|
|
||||||
EnvUpdate = "MINIO_UPDATE"
|
EnvUpdate = "MINIO_UPDATE"
|
||||||
|
|
||||||
|
|||||||
+22
-19
@@ -34,6 +34,9 @@ import (
|
|||||||
// ErrNoEntriesFound - Indicates no entries were found for the given key (directory)
|
// ErrNoEntriesFound - Indicates no entries were found for the given key (directory)
|
||||||
var ErrNoEntriesFound = errors.New("No entries found for this key")
|
var ErrNoEntriesFound = errors.New("No entries found for this key")
|
||||||
|
|
||||||
|
// ErrDomainMissing - Indicates domain is missing
|
||||||
|
var ErrDomainMissing = errors.New("domain is missing")
|
||||||
|
|
||||||
const etcdPathSeparator = "/"
|
const etcdPathSeparator = "/"
|
||||||
|
|
||||||
// create a new coredns service record for the bucket.
|
// create a new coredns service record for the bucket.
|
||||||
@@ -57,9 +60,9 @@ func (c *CoreDNS) List() (map[string][]SrvRecord, error) {
|
|||||||
var srvRecords = map[string][]SrvRecord{}
|
var srvRecords = map[string][]SrvRecord{}
|
||||||
for _, domainName := range c.domainNames {
|
for _, domainName := range c.domainNames {
|
||||||
key := msg.Path(fmt.Sprintf("%s.", domainName), c.prefixPath)
|
key := msg.Path(fmt.Sprintf("%s.", domainName), c.prefixPath)
|
||||||
records, err := c.list(key)
|
records, err := c.list(key+etcdPathSeparator, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return srvRecords, err
|
||||||
}
|
}
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
if record.Key == "" {
|
if record.Key == "" {
|
||||||
@@ -76,7 +79,7 @@ func (c *CoreDNS) Get(bucket string) ([]SrvRecord, error) {
|
|||||||
var srvRecords []SrvRecord
|
var srvRecords []SrvRecord
|
||||||
for _, domainName := range c.domainNames {
|
for _, domainName := range c.domainNames {
|
||||||
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, domainName), c.prefixPath)
|
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, domainName), c.prefixPath)
|
||||||
records, err := c.list(key)
|
records, err := c.list(key, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -109,19 +112,25 @@ func msgUnPath(s string) string {
|
|||||||
|
|
||||||
// Retrieves list of entries under the key passed.
|
// Retrieves list of entries under the key passed.
|
||||||
// Note that this method fetches entries upto only two levels deep.
|
// Note that this method fetches entries upto only two levels deep.
|
||||||
func (c *CoreDNS) list(key string) ([]SrvRecord, error) {
|
func (c *CoreDNS) list(key string, domain bool) ([]SrvRecord, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
r, err := c.etcdClient.Get(ctx, key, clientv3.WithPrefix())
|
r, err := c.etcdClient.Get(ctx, key, clientv3.WithPrefix())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Count == 0 {
|
if r.Count == 0 {
|
||||||
key = strings.TrimSuffix(key, etcdPathSeparator)
|
key = strings.TrimSuffix(key, etcdPathSeparator)
|
||||||
r, err = c.etcdClient.Get(ctx, key)
|
r, err = c.etcdClient.Get(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// only if we are looking at `domain` as true
|
||||||
|
// we should return error here.
|
||||||
|
if domain && r.Count == 0 {
|
||||||
|
return nil, ErrDomainMissing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var srvRecords []SrvRecord
|
var srvRecords []SrvRecord
|
||||||
@@ -166,11 +175,11 @@ func (c *CoreDNS) Put(bucket string) error {
|
|||||||
key = key + etcdPathSeparator + ip
|
key = key + etcdPathSeparator + ip
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
_, err = c.etcdClient.Put(ctx, key, string(bucketMsg))
|
_, err = c.etcdClient.Put(ctx, key, string(bucketMsg))
|
||||||
defer cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx, cancel = context.WithTimeout(context.Background(), defaultContextTimeout)
|
ctx, cancel = context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
c.etcdClient.Delete(ctx, key)
|
c.etcdClient.Delete(ctx, key)
|
||||||
defer cancel()
|
cancel()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,18 +191,12 @@ func (c *CoreDNS) Put(bucket string) error {
|
|||||||
func (c *CoreDNS) Delete(bucket string) error {
|
func (c *CoreDNS) Delete(bucket string) error {
|
||||||
for _, domainName := range c.domainNames {
|
for _, domainName := range c.domainNames {
|
||||||
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, domainName), c.prefixPath)
|
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, domainName), c.prefixPath)
|
||||||
srvRecords, err := c.list(key)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
|
_, err := c.etcdClient.Delete(ctx, key+etcdPathSeparator, clientv3.WithPrefix())
|
||||||
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, record := range srvRecords {
|
|
||||||
dctx, dcancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
|
||||||
if _, err = c.etcdClient.Delete(dctx, key+etcdPathSeparator+record.Host); err != nil {
|
|
||||||
dcancel()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dcancel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -203,12 +206,12 @@ func (c *CoreDNS) DeleteRecord(record SrvRecord) error {
|
|||||||
for _, domainName := range c.domainNames {
|
for _, domainName := range c.domainNames {
|
||||||
key := msg.Path(fmt.Sprintf("%s.%s.", record.Key, domainName), c.prefixPath)
|
key := msg.Path(fmt.Sprintf("%s.%s.", record.Key, domainName), c.prefixPath)
|
||||||
|
|
||||||
dctx, dcancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
if _, err := c.etcdClient.Delete(dctx, key+etcdPathSeparator+record.Host); err != nil {
|
_, err := c.etcdClient.Delete(ctx, key+etcdPathSeparator+record.Host)
|
||||||
dcancel()
|
cancel()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dcancel()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-5
@@ -30,6 +30,12 @@ var (
|
|||||||
"Can only accept `on` and `off` values. To enable O_SYNC for fs backend, set this value to `on`",
|
"Can only accept `on` and `off` values. To enable O_SYNC for fs backend, set this value to `on`",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ErrOverlappingDomainValue = newErrFn(
|
||||||
|
"Overlapping domain values",
|
||||||
|
"Please check the passed value",
|
||||||
|
"MINIO_DOMAIN only accepts non-overlapping domain values",
|
||||||
|
)
|
||||||
|
|
||||||
ErrInvalidDomainValue = newErrFn(
|
ErrInvalidDomainValue = newErrFn(
|
||||||
"Invalid domain value",
|
"Invalid domain value",
|
||||||
"Please check the passed value",
|
"Please check the passed value",
|
||||||
@@ -116,19 +122,19 @@ var (
|
|||||||
ErrInvalidRotatingCredentialsBackendEncrypted = newErrFn(
|
ErrInvalidRotatingCredentialsBackendEncrypted = newErrFn(
|
||||||
"Invalid rotating credentials",
|
"Invalid rotating credentials",
|
||||||
"Please set correct rotating credentials in the environment for decryption",
|
"Please set correct rotating credentials in the environment for decryption",
|
||||||
`Detected encrypted config backend, correct old access and secret keys should be specified via environment variables MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD to be able to re-encrypt the MinIO config, user IAM and policies with new credentials`,
|
`Detected encrypted config backend, correct old access and secret keys should be specified via environment variables MINIO_ROOT_USER_OLD and MINIO_ROOT_PASSWORD_OLD to be able to re-encrypt the MinIO config, user IAM and policies with new credentials`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrInvalidCredentialsBackendEncrypted = newErrFn(
|
ErrInvalidCredentialsBackendEncrypted = newErrFn(
|
||||||
"Invalid credentials",
|
"Invalid credentials",
|
||||||
"Please set correct credentials in the environment for decryption",
|
"Please set correct credentials in the environment for decryption",
|
||||||
`Detected encrypted config backend, correct access and secret keys should be specified via environment variables MINIO_ACCESS_KEY and MINIO_SECRET_KEY to be able to decrypt the MinIO config, user IAM and policies`,
|
`Detected encrypted config backend, correct access and secret keys should be specified via environment variables MINIO_ROOT_USER and MINIO_ROOT_PASSWORD to be able to decrypt the MinIO config, user IAM and policies`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrMissingCredentialsBackendEncrypted = newErrFn(
|
ErrMissingCredentialsBackendEncrypted = newErrFn(
|
||||||
"Credentials missing",
|
"Credentials missing",
|
||||||
"Please set your credentials in the environment",
|
"Please set your credentials in the environment",
|
||||||
`Detected encrypted config backend, access and secret keys should be specified via environment variables MINIO_ACCESS_KEY and MINIO_SECRET_KEY to be able to decrypt the MinIO config, user IAM and policies`,
|
`Detected encrypted config backend, access and secret keys should be specified via environment variables MINIO_ROOT_USER and MINIO_ROOT_PASSWORD to be able to decrypt the MinIO config, user IAM and policies`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrInvalidCredentials = newErrFn(
|
ErrInvalidCredentials = newErrFn(
|
||||||
@@ -140,13 +146,13 @@ var (
|
|||||||
ErrEnvCredentialsMissingGateway = newErrFn(
|
ErrEnvCredentialsMissingGateway = newErrFn(
|
||||||
"Credentials missing",
|
"Credentials missing",
|
||||||
"Please set your credentials in the environment",
|
"Please set your credentials in the environment",
|
||||||
`In Gateway mode, access and secret keys should be specified via environment variables MINIO_ACCESS_KEY and MINIO_SECRET_KEY respectively`,
|
`In Gateway mode, access and secret keys should be specified via environment variables MINIO_ROOT_USER and MINIO_ROOT_PASSWORD respectively`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrEnvCredentialsMissingDistributed = newErrFn(
|
ErrEnvCredentialsMissingDistributed = newErrFn(
|
||||||
"Credentials missing",
|
"Credentials missing",
|
||||||
"Please set your credentials in the environment",
|
"Please set your credentials in the environment",
|
||||||
`In distributed server mode, access and secret keys should be specified via environment variables MINIO_ACCESS_KEY and MINIO_SECRET_KEY respectively`,
|
`In distributed server mode, access and secret keys should be specified via environment variables MINIO_ROOT_USER and MINIO_ROOT_PASSWORD respectively`,
|
||||||
)
|
)
|
||||||
|
|
||||||
ErrInvalidErasureEndpoints = newErrFn(
|
ErrInvalidErasureEndpoints = newErrFn(
|
||||||
@@ -275,4 +281,10 @@ Example 1:
|
|||||||
"",
|
"",
|
||||||
"Refer to https://docs.min.io/docs/minio-kms-quickstart-guide.html for setting up SSE",
|
"Refer to https://docs.min.io/docs/minio-kms-quickstart-guide.html for setting up SSE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ErrInvalidReplicationWorkersValue = newErrFn(
|
||||||
|
"Invalid value for replication workers",
|
||||||
|
"",
|
||||||
|
"MINIO_API_REPLICATION_WORKERS: should be > 0",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"go.etcd.io/etcd/clientv3"
|
"go.etcd.io/etcd/clientv3"
|
||||||
"go.etcd.io/etcd/clientv3/namespace"
|
"go.etcd.io/etcd/clientv3/namespace"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -144,6 +145,13 @@ func LookupConfig(kvs config.KVS, rootCAs *x509.CertPool) (Config, error) {
|
|||||||
cfg.Enabled = true
|
cfg.Enabled = true
|
||||||
cfg.DialTimeout = defaultDialTimeout
|
cfg.DialTimeout = defaultDialTimeout
|
||||||
cfg.DialKeepAliveTime = defaultDialKeepAlive
|
cfg.DialKeepAliveTime = defaultDialKeepAlive
|
||||||
|
// Disable etcd client SDK logging, etcd client
|
||||||
|
// incorrectly starts logging in unexpected data
|
||||||
|
// format.
|
||||||
|
cfg.LogConfig = &zap.Config{
|
||||||
|
Level: zap.NewAtomicLevelAt(zap.FatalLevel),
|
||||||
|
Encoding: "console",
|
||||||
|
}
|
||||||
cfg.Endpoints = etcdEndpoints
|
cfg.Endpoints = etcdEndpoints
|
||||||
cfg.CoreDNSPath = env.Get(EnvEtcdCoreDNSPath, kvs.Get(CoreDNSPath))
|
cfg.CoreDNSPath = env.Get(EnvEtcdCoreDNSPath, kvs.Get(CoreDNSPath))
|
||||||
// Default path prefix for all keys on etcd, other than CoreDNSPath.
|
// Default path prefix for all keys on etcd, other than CoreDNSPath.
|
||||||
|
|||||||
+223
-125
@@ -25,13 +25,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ldap "github.com/go-ldap/ldap/v3"
|
||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
ldap "gopkg.in/ldap.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultLDAPExpiry = time.Hour * 1
|
defaultLDAPExpiry = time.Hour * 1
|
||||||
|
|
||||||
|
dnDelimiter = ";"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains AD/LDAP server connectivity information.
|
// Config contains AD/LDAP server connectivity information.
|
||||||
@@ -45,50 +47,65 @@ type Config struct {
|
|||||||
STSExpiryDuration string `json:"stsExpiryDuration"`
|
STSExpiryDuration string `json:"stsExpiryDuration"`
|
||||||
|
|
||||||
// Format string for usernames
|
// Format string for usernames
|
||||||
UsernameFormat string `json:"usernameFormat"`
|
UsernameFormat string `json:"usernameFormat"`
|
||||||
UsernameFormats []string `json:"-"`
|
UsernameFormats []string `json:"-"`
|
||||||
UsernameSearchFilter string `json:"-"`
|
|
||||||
UsernameSearchBaseDNS []string `json:"-"`
|
|
||||||
|
|
||||||
GroupSearchBaseDN string `json:"groupSearchBaseDN"`
|
// User DN search parameters
|
||||||
GroupSearchBaseDNS []string `json:"-"`
|
UserDNSearchBaseDN string `json:"userDNSearchBaseDN"`
|
||||||
GroupSearchFilter string `json:"groupSearchFilter"`
|
UserDNSearchFilter string `json:"userDNSearchFilter"`
|
||||||
GroupNameAttribute string `json:"groupNameAttribute"`
|
|
||||||
|
// Group search parameters
|
||||||
|
GroupSearchBaseDistName string `json:"groupSearchBaseDN"`
|
||||||
|
GroupSearchBaseDistNames []string `json:"-"`
|
||||||
|
GroupSearchFilter string `json:"groupSearchFilter"`
|
||||||
|
|
||||||
|
// Lookup bind LDAP service account
|
||||||
|
LookupBindDN string `json:"lookupBindDN"`
|
||||||
|
LookupBindPassword string `json:"lookupBindPassword"`
|
||||||
|
|
||||||
stsExpiryDuration time.Duration // contains converted value
|
stsExpiryDuration time.Duration // contains converted value
|
||||||
tlsSkipVerify bool // allows skipping TLS verification
|
tlsSkipVerify bool // allows skipping TLS verification
|
||||||
serverInsecure bool // allows plain text connection to LDAP Server
|
serverInsecure bool // allows plain text connection to LDAP server
|
||||||
serverStartTLS bool // allows plain text connection to LDAP Server
|
serverStartTLS bool // allows using StartTLS connection to LDAP server
|
||||||
|
isUsingLookupBind bool
|
||||||
rootCAs *x509.CertPool
|
rootCAs *x509.CertPool
|
||||||
}
|
}
|
||||||
|
|
||||||
// LDAP keys and envs.
|
// LDAP keys and envs.
|
||||||
const (
|
const (
|
||||||
ServerAddr = "server_addr"
|
ServerAddr = "server_addr"
|
||||||
STSExpiry = "sts_expiry"
|
STSExpiry = "sts_expiry"
|
||||||
UsernameFormat = "username_format"
|
LookupBindDN = "lookup_bind_dn"
|
||||||
UsernameSearchFilter = "username_search_filter"
|
LookupBindPassword = "lookup_bind_password"
|
||||||
UsernameSearchBaseDN = "username_search_base_dn"
|
UserDNSearchBaseDN = "user_dn_search_base_dn"
|
||||||
GroupSearchFilter = "group_search_filter"
|
UserDNSearchFilter = "user_dn_search_filter"
|
||||||
GroupNameAttribute = "group_name_attribute"
|
UsernameFormat = "username_format"
|
||||||
GroupSearchBaseDN = "group_search_base_dn"
|
GroupSearchFilter = "group_search_filter"
|
||||||
TLSSkipVerify = "tls_skip_verify"
|
GroupSearchBaseDN = "group_search_base_dn"
|
||||||
ServerInsecure = "server_insecure"
|
TLSSkipVerify = "tls_skip_verify"
|
||||||
ServerStartTLS = "server_starttls"
|
ServerInsecure = "server_insecure"
|
||||||
|
ServerStartTLS = "server_starttls"
|
||||||
|
|
||||||
EnvServerAddr = "MINIO_IDENTITY_LDAP_SERVER_ADDR"
|
EnvServerAddr = "MINIO_IDENTITY_LDAP_SERVER_ADDR"
|
||||||
EnvSTSExpiry = "MINIO_IDENTITY_LDAP_STS_EXPIRY"
|
EnvSTSExpiry = "MINIO_IDENTITY_LDAP_STS_EXPIRY"
|
||||||
EnvTLSSkipVerify = "MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"
|
EnvTLSSkipVerify = "MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"
|
||||||
EnvServerInsecure = "MINIO_IDENTITY_LDAP_SERVER_INSECURE"
|
EnvServerInsecure = "MINIO_IDENTITY_LDAP_SERVER_INSECURE"
|
||||||
EnvServerStartTLS = "MINIO_IDENTITY_LDAP_SERVER_STARTTLS"
|
EnvServerStartTLS = "MINIO_IDENTITY_LDAP_SERVER_STARTTLS"
|
||||||
EnvUsernameFormat = "MINIO_IDENTITY_LDAP_USERNAME_FORMAT"
|
EnvUsernameFormat = "MINIO_IDENTITY_LDAP_USERNAME_FORMAT"
|
||||||
EnvUsernameSearchFilter = "MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER"
|
EnvUserDNSearchBaseDN = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"
|
||||||
EnvUsernameSearchBaseDN = "MINIO_IDENTITY_LDAP_USERNAME_SEARCH_BASE_DN"
|
EnvUserDNSearchFilter = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER"
|
||||||
EnvGroupSearchFilter = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"
|
EnvGroupSearchFilter = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"
|
||||||
EnvGroupNameAttribute = "MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE"
|
EnvGroupSearchBaseDN = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"
|
||||||
EnvGroupSearchBaseDN = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"
|
EnvLookupBindDN = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"
|
||||||
|
EnvLookupBindPassword = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var removedKeys = []string{
|
||||||
|
"username_search_filter",
|
||||||
|
"username_search_base_dn",
|
||||||
|
"group_name_attribute",
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultKVS - default config for LDAP config
|
// DefaultKVS - default config for LDAP config
|
||||||
var (
|
var (
|
||||||
DefaultKVS = config.KVS{
|
DefaultKVS = config.KVS{
|
||||||
@@ -101,21 +118,17 @@ var (
|
|||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: UsernameSearchFilter,
|
Key: UserDNSearchBaseDN,
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: UsernameSearchBaseDN,
|
Key: UserDNSearchFilter,
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: GroupSearchFilter,
|
Key: GroupSearchFilter,
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
config.KV{
|
|
||||||
Key: GroupNameAttribute,
|
|
||||||
Value: "",
|
|
||||||
},
|
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: GroupSearchBaseDN,
|
Key: GroupSearchBaseDN,
|
||||||
Value: "",
|
Value: "",
|
||||||
@@ -136,117 +149,180 @@ var (
|
|||||||
Key: ServerStartTLS,
|
Key: ServerStartTLS,
|
||||||
Value: config.EnableOff,
|
Value: config.EnableOff,
|
||||||
},
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: LookupBindDN,
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: LookupBindPassword,
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
dnDelimiter = ";"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getGroups(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
|
func getGroups(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
|
||||||
var groups []string
|
var groups []string
|
||||||
sres, err := conn.Search(sreq)
|
sres, err := conn.Search(sreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Check if there is no matching result and return empty slice.
|
||||||
|
// Ref: https://ldap.com/ldap-result-code-reference/
|
||||||
|
if ldap.IsErrorWithCode(err, 32) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, entry := range sres.Entries {
|
for _, entry := range sres.Entries {
|
||||||
// We only queried one attribute,
|
// We only queried one attribute,
|
||||||
// so we only look up the first one.
|
// so we only look up the first one.
|
||||||
groups = append(groups, entry.Attributes[0].Values...)
|
groups = append(groups, entry.DN)
|
||||||
}
|
}
|
||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Config) bind(conn *ldap.Conn, username, password string) ([]string, error) {
|
func (l *Config) lookupBind(conn *ldap.Conn) error {
|
||||||
var bindDNS = make([]string, len(l.UsernameFormats))
|
return conn.Bind(l.LookupBindDN, l.LookupBindPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// usernameFormatsBind - Iterates over all given username formats and expects
|
||||||
|
// that only one will succeed if the credentials are valid. The succeeding
|
||||||
|
// bindDN is returned or an error.
|
||||||
|
//
|
||||||
|
// In the rare case that multiple username formats succeed, implying that two
|
||||||
|
// (or more) distinct users in the LDAP directory have the same username and
|
||||||
|
// password, we return an error as we cannot identify the account intended by
|
||||||
|
// the user.
|
||||||
|
func (l *Config) usernameFormatsBind(conn *ldap.Conn, username, password string) (string, error) {
|
||||||
|
var bindDistNames []string
|
||||||
|
var errs = make([]error, len(l.UsernameFormats))
|
||||||
|
var successCount = 0
|
||||||
for i, usernameFormat := range l.UsernameFormats {
|
for i, usernameFormat := range l.UsernameFormats {
|
||||||
bindDN := fmt.Sprintf(usernameFormat, username)
|
bindDN := fmt.Sprintf(usernameFormat, username)
|
||||||
// Bind with user credentials to validate the password
|
// Bind with user credentials to validate the password
|
||||||
if err := conn.Bind(bindDN, password); err != nil {
|
errs[i] = conn.Bind(bindDN, password)
|
||||||
return nil, err
|
if errs[i] == nil {
|
||||||
|
bindDistNames = append(bindDistNames, bindDN)
|
||||||
|
successCount++
|
||||||
}
|
}
|
||||||
bindDNS[i] = bindDN
|
|
||||||
}
|
}
|
||||||
return bindDNS, nil
|
if successCount == 0 {
|
||||||
|
var errStrings []string
|
||||||
|
for _, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
errStrings = append(errStrings, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outErr := fmt.Sprintf("All username formats failed with: %s", strings.Join(errStrings, "; "))
|
||||||
|
return "", errors.New(outErr)
|
||||||
|
}
|
||||||
|
if successCount > 1 {
|
||||||
|
successDistNames := strings.Join(bindDistNames, ", ")
|
||||||
|
errMsg := fmt.Sprintf("Multiple username formats succeeded - ambiguous user login (succeeded for: %s)", successDistNames)
|
||||||
|
return "", errors.New(errMsg)
|
||||||
|
}
|
||||||
|
return bindDistNames[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var standardAttributes = []string{
|
// lookupUserDN searches for the DN of the user given their username. conn is
|
||||||
"givenName",
|
// assumed to be using the lookup bind service account. It is required that the
|
||||||
"sn",
|
// search result in at most one result.
|
||||||
"cn",
|
func (l *Config) lookupUserDN(conn *ldap.Conn, username string) (string, error) {
|
||||||
"memberOf",
|
filter := strings.Replace(l.UserDNSearchFilter, "%s", ldap.EscapeFilter(username), -1)
|
||||||
"email",
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
l.UserDNSearchBaseDN,
|
||||||
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
filter,
|
||||||
|
[]string{}, // only need DN, so no pass no attributes here
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
searchResult, err := conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(searchResult.Entries) == 0 {
|
||||||
|
return "", fmt.Errorf("User DN for %s not found", username)
|
||||||
|
}
|
||||||
|
if len(searchResult.Entries) != 1 {
|
||||||
|
return "", fmt.Errorf("Multiple DNs for %s found - please fix the search filter", username)
|
||||||
|
}
|
||||||
|
return searchResult.Entries[0].DN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind - binds to ldap, searches LDAP and returns list of groups.
|
// Bind - binds to ldap, searches LDAP and returns the distinguished name of the
|
||||||
func (l *Config) Bind(username, password string) ([]string, error) {
|
// user and the list of groups.
|
||||||
|
func (l *Config) Bind(username, password string) (string, []string, error) {
|
||||||
conn, err := l.Connect()
|
conn, err := l.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
bindDNS, err := l.bind(conn, username, password)
|
var bindDN string
|
||||||
if err != nil {
|
if l.isUsingLookupBind {
|
||||||
return nil, err
|
// Bind to the lookup user account
|
||||||
|
if err = l.lookupBind(conn); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup user DN
|
||||||
|
bindDN, err = l.lookupUserDN(conn, username)
|
||||||
|
if err != nil {
|
||||||
|
errRet := fmt.Errorf("Unable to find user DN: %s", err)
|
||||||
|
return "", nil, errRet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate the user credentials.
|
||||||
|
err = conn.Bind(bindDN, password)
|
||||||
|
if err != nil {
|
||||||
|
errRet := fmt.Errorf("LDAP auth failed for DN %s: %v", bindDN, err)
|
||||||
|
return "", nil, errRet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to the lookup user account again to perform group search.
|
||||||
|
if err = l.lookupBind(conn); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Verify login credentials by checking the username formats.
|
||||||
|
bindDN, err = l.usernameFormatsBind(conn, username, password)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to the successful bindDN again.
|
||||||
|
err = conn.Bind(bindDN, password)
|
||||||
|
if err != nil {
|
||||||
|
errRet := fmt.Errorf("LDAP conn failed though auth for DN %s succeeded: %v", bindDN, err)
|
||||||
|
return "", nil, errRet
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User groups lookup.
|
||||||
var groups []string
|
var groups []string
|
||||||
if l.UsernameSearchFilter != "" {
|
if l.GroupSearchFilter != "" {
|
||||||
for _, userSearchBase := range l.UsernameSearchBaseDNS {
|
for _, groupSearchBase := range l.GroupSearchBaseDistNames {
|
||||||
filter := strings.Replace(l.UsernameSearchFilter, "%s",
|
filter := strings.Replace(l.GroupSearchFilter, "%s", ldap.EscapeFilter(username), -1)
|
||||||
ldap.EscapeFilter(username), -1)
|
filter = strings.Replace(filter, "%d", ldap.EscapeFilter(bindDN), -1)
|
||||||
|
|
||||||
searchRequest := ldap.NewSearchRequest(
|
searchRequest := ldap.NewSearchRequest(
|
||||||
userSearchBase,
|
groupSearchBase,
|
||||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
filter,
|
filter,
|
||||||
standardAttributes,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
groups, err = getGroups(conn, searchRequest)
|
var newGroups []string
|
||||||
|
newGroups, err = getGroups(conn, searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
errRet := fmt.Errorf("Error finding groups of %s: %v", bindDN, err)
|
||||||
|
return "", nil, errRet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groups = append(groups, newGroups...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.GroupSearchFilter != "" {
|
return bindDN, groups, nil
|
||||||
for _, groupSearchBase := range l.GroupSearchBaseDNS {
|
|
||||||
var filters []string
|
|
||||||
if l.GroupNameAttribute == "" {
|
|
||||||
filters = []string{strings.Replace(l.GroupSearchFilter, "%s",
|
|
||||||
ldap.EscapeFilter(username), -1)}
|
|
||||||
} else {
|
|
||||||
// With group name attribute specified, make sure to
|
|
||||||
// include search queries for CN distinguished name
|
|
||||||
for _, bindDN := range bindDNS {
|
|
||||||
filters = append(filters, strings.Replace(l.GroupSearchFilter, "%s",
|
|
||||||
ldap.EscapeFilter(bindDN), -1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, filter := range filters {
|
|
||||||
searchRequest := ldap.NewSearchRequest(
|
|
||||||
groupSearchBase,
|
|
||||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
|
||||||
filter,
|
|
||||||
standardAttributes,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
var newGroups []string
|
|
||||||
newGroups, err = getGroups(conn, searchRequest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
groups = append(groups, newGroups...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect connect to ldap server.
|
// Connect connect to ldap server.
|
||||||
@@ -295,6 +371,12 @@ func Enabled(kvs config.KVS) bool {
|
|||||||
// Lookup - initializes LDAP config, overrides config, if any ENV values are set.
|
// Lookup - initializes LDAP config, overrides config, if any ENV values are set.
|
||||||
func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
|
func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
|
||||||
l = Config{}
|
l = Config{}
|
||||||
|
|
||||||
|
// Purge all removed keys first
|
||||||
|
for _, k := range removedKeys {
|
||||||
|
kvs.Delete(k)
|
||||||
|
}
|
||||||
|
|
||||||
if err = config.CheckValidKeys(config.IdentityLDAPSubSys, kvs, DefaultKVS); err != nil {
|
if err = config.CheckValidKeys(config.IdentityLDAPSubSys, kvs, DefaultKVS); err != nil {
|
||||||
return l, err
|
return l, err
|
||||||
}
|
}
|
||||||
@@ -316,6 +398,8 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
|
|||||||
l.STSExpiryDuration = v
|
l.STSExpiryDuration = v
|
||||||
l.stsExpiryDuration = expDur
|
l.stsExpiryDuration = expDur
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LDAP connection configuration
|
||||||
if v := env.Get(EnvServerInsecure, kvs.Get(ServerInsecure)); v != "" {
|
if v := env.Get(EnvServerInsecure, kvs.Get(ServerInsecure)); v != "" {
|
||||||
l.serverInsecure, err = config.ParseBool(v)
|
l.serverInsecure, err = config.ParseBool(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -334,43 +418,57 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
|
|||||||
return l, err
|
return l, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lookup bind user configuration
|
||||||
|
lookupBindDN := env.Get(EnvLookupBindDN, kvs.Get(LookupBindDN))
|
||||||
|
lookupBindPassword := env.Get(EnvLookupBindPassword, kvs.Get(LookupBindPassword))
|
||||||
|
if lookupBindDN != "" && lookupBindPassword != "" {
|
||||||
|
l.LookupBindDN = lookupBindDN
|
||||||
|
l.LookupBindPassword = lookupBindPassword
|
||||||
|
l.isUsingLookupBind = true
|
||||||
|
|
||||||
|
// User DN search configuration
|
||||||
|
userDNSearchBaseDN := env.Get(EnvUserDNSearchBaseDN, kvs.Get(UserDNSearchBaseDN))
|
||||||
|
userDNSearchFilter := env.Get(EnvUserDNSearchFilter, kvs.Get(UserDNSearchFilter))
|
||||||
|
if userDNSearchFilter == "" || userDNSearchBaseDN == "" {
|
||||||
|
return l, errors.New("In lookup bind mode, userDN search base DN and userDN search filter are both required")
|
||||||
|
}
|
||||||
|
l.UserDNSearchBaseDN = userDNSearchBaseDN
|
||||||
|
l.UserDNSearchFilter = userDNSearchFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Username format configuration.
|
||||||
if v := env.Get(EnvUsernameFormat, kvs.Get(UsernameFormat)); v != "" {
|
if v := env.Get(EnvUsernameFormat, kvs.Get(UsernameFormat)); v != "" {
|
||||||
if !strings.Contains(v, "%s") {
|
if !strings.Contains(v, "%s") {
|
||||||
return l, errors.New("LDAP username format doesn't have '%s' substitution")
|
return l, errors.New("LDAP username format doesn't have '%s' substitution")
|
||||||
}
|
}
|
||||||
l.UsernameFormats = strings.Split(v, dnDelimiter)
|
l.UsernameFormats = strings.Split(v, dnDelimiter)
|
||||||
} else {
|
|
||||||
return l, fmt.Errorf("'%s' cannot be empty and must have a value", UsernameFormat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := env.Get(EnvUsernameSearchFilter, kvs.Get(UsernameSearchFilter)); v != "" {
|
// Either lookup bind mode or username format is supported, but not
|
||||||
if !strings.Contains(v, "%s") {
|
// both.
|
||||||
return l, errors.New("LDAP username search filter doesn't have '%s' substitution")
|
if l.isUsingLookupBind && len(l.UsernameFormats) > 0 {
|
||||||
}
|
return l, errors.New("Lookup Bind mode and Username Format mode are not supported at the same time")
|
||||||
l.UsernameSearchFilter = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := env.Get(EnvUsernameSearchBaseDN, kvs.Get(UsernameSearchBaseDN)); v != "" {
|
// At least one of bind mode or username format must be used.
|
||||||
l.UsernameSearchBaseDNS = strings.Split(v, dnDelimiter)
|
if !l.isUsingLookupBind && len(l.UsernameFormats) == 0 {
|
||||||
|
return l, errors.New("Either Lookup Bind mode or Username Format mode is required.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group search params configuration
|
||||||
grpSearchFilter := env.Get(EnvGroupSearchFilter, kvs.Get(GroupSearchFilter))
|
grpSearchFilter := env.Get(EnvGroupSearchFilter, kvs.Get(GroupSearchFilter))
|
||||||
grpSearchNameAttr := env.Get(EnvGroupNameAttribute, kvs.Get(GroupNameAttribute))
|
|
||||||
grpSearchBaseDN := env.Get(EnvGroupSearchBaseDN, kvs.Get(GroupSearchBaseDN))
|
grpSearchBaseDN := env.Get(EnvGroupSearchBaseDN, kvs.Get(GroupSearchBaseDN))
|
||||||
|
|
||||||
// Either all group params must be set or none must be set.
|
// Either all group params must be set or none must be set.
|
||||||
var allSet bool
|
if (grpSearchFilter != "" && grpSearchBaseDN == "") || (grpSearchFilter == "" && grpSearchBaseDN != "") {
|
||||||
if grpSearchFilter != "" {
|
return l, errors.New("All group related parameters must be set")
|
||||||
if grpSearchNameAttr == "" || grpSearchBaseDN == "" {
|
|
||||||
return l, errors.New("All group related parameters must be set")
|
|
||||||
}
|
|
||||||
allSet = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if allSet {
|
if grpSearchFilter != "" {
|
||||||
l.GroupSearchFilter = grpSearchFilter
|
l.GroupSearchFilter = grpSearchFilter
|
||||||
l.GroupNameAttribute = grpSearchNameAttr
|
l.GroupSearchBaseDistName = grpSearchBaseDN
|
||||||
l.GroupSearchBaseDNS = strings.Split(grpSearchBaseDN, dnDelimiter)
|
l.GroupSearchBaseDistNames = strings.Split(l.GroupSearchBaseDistName, dnDelimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.rootCAs = rootCAs
|
l.rootCAs = rootCAs
|
||||||
|
|||||||
@@ -27,43 +27,53 @@ var (
|
|||||||
Type: "address",
|
Type: "address",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: UsernameFormat,
|
Key: STSExpiry,
|
||||||
Description: `";" separated list of username bind DNs e.g. "uid=%s,cn=accounts,dc=myldapserver,dc=com"`,
|
Description: `temporary credentials validity duration in s,m,h,d. Default is "1h"`,
|
||||||
Type: "list",
|
Optional: true,
|
||||||
|
Type: "duration",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: UsernameSearchFilter,
|
Key: LookupBindDN,
|
||||||
Description: `user search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"`,
|
Description: `DN for LDAP read-only service account used to perform DN and group lookups`,
|
||||||
|
Optional: true,
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: LookupBindPassword,
|
||||||
|
Description: `Password for LDAP read-only service account used to perform DN and group lookups`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: UserDNSearchBaseDN,
|
||||||
|
Description: `Base LDAP DN to search for user DN`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: UserDNSearchFilter,
|
||||||
|
Description: `Search filter to lookup user DN`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: UsernameFormat,
|
||||||
|
Description: `";" separated list of username bind DNs e.g. "uid=%s,cn=accounts,dc=myldapserver,dc=com"`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "list",
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: GroupSearchFilter,
|
Key: GroupSearchFilter,
|
||||||
Description: `search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"`,
|
Description: `search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"`,
|
||||||
|
Optional: true,
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: GroupSearchBaseDN,
|
Key: GroupSearchBaseDN,
|
||||||
Description: `";" separated list of group search base DNs e.g. "dc=myldapserver,dc=com"`,
|
Description: `";" separated list of group search base DNs e.g. "dc=myldapserver,dc=com"`,
|
||||||
|
Optional: true,
|
||||||
Type: "list",
|
Type: "list",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
|
||||||
Key: UsernameSearchBaseDN,
|
|
||||||
Description: `";" separated list of username search DNs`,
|
|
||||||
Type: "list",
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
config.HelpKV{
|
|
||||||
Key: GroupNameAttribute,
|
|
||||||
Description: `search attribute for group name e.g. "cn"`,
|
|
||||||
Optional: true,
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
config.HelpKV{
|
|
||||||
Key: STSExpiry,
|
|
||||||
Description: `temporary credentials validity duration in s,m,h,d. Default is "1h"`,
|
|
||||||
Optional: true,
|
|
||||||
Type: "duration",
|
|
||||||
},
|
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: TLSSkipVerify,
|
Key: TLSSkipVerify,
|
||||||
Description: `trust server TLS without verification, defaults to "off" (verify)`,
|
Description: `trust server TLS without verification, defaults to "off" (verify)`,
|
||||||
@@ -76,6 +86,12 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "on|off",
|
Type: "on|off",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: ServerStartTLS,
|
||||||
|
Description: `use StartTLS connection to AD/LDAP server, defaults to "off"`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "on|off",
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.Comment,
|
Key: config.Comment,
|
||||||
Description: config.DefaultComment,
|
Description: config.DefaultComment,
|
||||||
|
|||||||
@@ -41,13 +41,9 @@ func SetIdentityLDAP(s config.Config, ldapArgs Config) {
|
|||||||
Key: GroupSearchFilter,
|
Key: GroupSearchFilter,
|
||||||
Value: ldapArgs.GroupSearchFilter,
|
Value: ldapArgs.GroupSearchFilter,
|
||||||
},
|
},
|
||||||
config.KV{
|
|
||||||
Key: GroupNameAttribute,
|
|
||||||
Value: ldapArgs.GroupNameAttribute,
|
|
||||||
},
|
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: GroupSearchBaseDN,
|
Key: GroupSearchBaseDN,
|
||||||
Value: ldapArgs.GroupSearchBaseDN,
|
Value: ldapArgs.GroupSearchBaseDistName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package crawler
|
package scanner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -29,8 +29,10 @@ const (
|
|||||||
Delay = "delay"
|
Delay = "delay"
|
||||||
MaxWait = "max_wait"
|
MaxWait = "max_wait"
|
||||||
|
|
||||||
EnvDelay = "MINIO_CRAWLER_DELAY"
|
EnvDelay = "MINIO_SCANNER_DELAY"
|
||||||
EnvMaxWait = "MINIO_CRAWLER_MAX_WAIT"
|
EnvDelayLegacy = "MINIO_CRAWLER_DELAY"
|
||||||
|
EnvMaxWait = "MINIO_SCANNER_MAX_WAIT"
|
||||||
|
EnvMaxWaitLegacy = "MINIO_CRAWLER_MAX_WAIT"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config represents the heal settings.
|
// Config represents the heal settings.
|
||||||
@@ -58,7 +60,7 @@ var (
|
|||||||
Help = config.HelpKVS{
|
Help = config.HelpKVS{
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: Delay,
|
Key: Delay,
|
||||||
Description: `crawler delay multiplier, defaults to '10.0'`,
|
Description: `scanner delay multiplier, defaults to '10.0'`,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "float",
|
Type: "float",
|
||||||
},
|
},
|
||||||
@@ -73,14 +75,22 @@ var (
|
|||||||
|
|
||||||
// LookupConfig - lookup config and override with valid environment settings if any.
|
// LookupConfig - lookup config and override with valid environment settings if any.
|
||||||
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
||||||
if err = config.CheckValidKeys(config.CrawlerSubSys, kvs, DefaultKVS); err != nil {
|
if err = config.CheckValidKeys(config.ScannerSubSys, kvs, DefaultKVS); err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
cfg.Delay, err = strconv.ParseFloat(env.Get(EnvDelay, kvs.Get(Delay)), 64)
|
delay := env.Get(EnvDelayLegacy, "")
|
||||||
|
if delay == "" {
|
||||||
|
delay = env.Get(EnvDelay, kvs.Get(Delay))
|
||||||
|
}
|
||||||
|
cfg.Delay, err = strconv.ParseFloat(delay, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
cfg.MaxWait, err = time.ParseDuration(env.Get(EnvMaxWait, kvs.Get(MaxWait)))
|
maxWait := env.Get(EnvMaxWaitLegacy, "")
|
||||||
|
if maxWait == "" {
|
||||||
|
maxWait = env.Get(EnvMaxWait, kvs.Get(MaxWait))
|
||||||
|
}
|
||||||
|
cfg.MaxWait, err = time.ParseDuration(maxWait)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ const (
|
|||||||
defaultRRSParity = minParityDisks
|
defaultRRSParity = minParityDisks
|
||||||
|
|
||||||
// Default DMA value
|
// Default DMA value
|
||||||
defaultDMA = DMAWrite
|
defaultDMA = DMAReadWrite
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultKVS - default storage class config
|
// DefaultKVS - default storage class config
|
||||||
@@ -88,14 +88,13 @@ var (
|
|||||||
// StorageClass - holds storage class information
|
// StorageClass - holds storage class information
|
||||||
type StorageClass struct {
|
type StorageClass struct {
|
||||||
Parity int
|
Parity int
|
||||||
DMA string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config storage class configuration
|
// Config storage class configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Standard StorageClass `json:"standard"`
|
Standard StorageClass `json:"standard"`
|
||||||
RRS StorageClass `json:"rrs"`
|
RRS StorageClass `json:"rrs"`
|
||||||
DMA StorageClass `json:"dma"`
|
DMA string `json:"dma"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
// UnmarshalJSON - Validate SS and RRS parity when unmarshalling JSON.
|
||||||
@@ -112,7 +111,7 @@ func (sCfg *Config) UnmarshalJSON(data []byte) error {
|
|||||||
// IsValid - returns true if input string is a valid
|
// IsValid - returns true if input string is a valid
|
||||||
// storage class kind supported.
|
// storage class kind supported.
|
||||||
func IsValid(sc string) bool {
|
func IsValid(sc string) bool {
|
||||||
return sc == RRS || sc == STANDARD || sc == DMA
|
return sc == RRS || sc == STANDARD
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText unmarshals storage class from its textual form into
|
// UnmarshalText unmarshals storage class from its textual form into
|
||||||
@@ -122,14 +121,6 @@ func (sc *StorageClass) UnmarshalText(b []byte) error {
|
|||||||
if scStr == "" {
|
if scStr == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if scStr == DMAWrite {
|
|
||||||
sc.DMA = DMAWrite
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if scStr == DMAReadWrite {
|
|
||||||
sc.DMA = DMAReadWrite
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s, err := parseStorageClass(scStr)
|
s, err := parseStorageClass(scStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -143,14 +134,14 @@ func (sc *StorageClass) MarshalText() ([]byte, error) {
|
|||||||
if sc.Parity != 0 {
|
if sc.Parity != 0 {
|
||||||
return []byte(fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)), nil
|
return []byte(fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)), nil
|
||||||
}
|
}
|
||||||
return []byte(sc.DMA), nil
|
return []byte{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *StorageClass) String() string {
|
func (sc *StorageClass) String() string {
|
||||||
if sc.Parity != 0 {
|
if sc.Parity != 0 {
|
||||||
return fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)
|
return fmt.Sprintf("%s:%d", schemePrefix, sc.Parity)
|
||||||
}
|
}
|
||||||
return sc.DMA
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses given storageClassEnv and returns a storageClass structure.
|
// Parses given storageClassEnv and returns a storageClass structure.
|
||||||
@@ -182,22 +173,34 @@ func parseStorageClass(storageClassEnv string) (sc StorageClass, err error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates the parity disks.
|
// ValidateParity validate standard storage class parity.
|
||||||
func validateParity(ssParity, rrsParity, setDriveCount int) (err error) {
|
func ValidateParity(ssParity, setDriveCount int) error {
|
||||||
if ssParity == 0 && rrsParity == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SS parity disks should be greater than or equal to minParityDisks.
|
// SS parity disks should be greater than or equal to minParityDisks.
|
||||||
// Parity below minParityDisks is not supported.
|
// Parity below minParityDisks is not supported.
|
||||||
if ssParity < minParityDisks {
|
if ssParity > 0 && ssParity < minParityDisks {
|
||||||
|
return fmt.Errorf("Standard storage class parity %d should be greater than or equal to %d",
|
||||||
|
ssParity, minParityDisks)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssParity > setDriveCount/2 {
|
||||||
|
return fmt.Errorf("Standard storage class parity %d should be less than or equal to %d", ssParity, setDriveCount/2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates the parity disks.
|
||||||
|
func validateParity(ssParity, rrsParity, setDriveCount int) (err error) {
|
||||||
|
// SS parity disks should be greater than or equal to minParityDisks.
|
||||||
|
// Parity below minParityDisks is not supported.
|
||||||
|
if ssParity > 0 && ssParity < minParityDisks {
|
||||||
return fmt.Errorf("Standard storage class parity %d should be greater than or equal to %d",
|
return fmt.Errorf("Standard storage class parity %d should be greater than or equal to %d",
|
||||||
ssParity, minParityDisks)
|
ssParity, minParityDisks)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RRS parity disks should be greater than or equal to minParityDisks.
|
// RRS parity disks should be greater than or equal to minParityDisks.
|
||||||
// Parity below minParityDisks is not supported.
|
// Parity below minParityDisks is not supported.
|
||||||
if rrsParity < minParityDisks {
|
if rrsParity > 0 && rrsParity < minParityDisks {
|
||||||
return fmt.Errorf("Reduced redundancy storage class parity %d should be greater than or equal to %d", rrsParity, minParityDisks)
|
return fmt.Errorf("Reduced redundancy storage class parity %d should be greater than or equal to %d", rrsParity, minParityDisks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +213,7 @@ func validateParity(ssParity, rrsParity, setDriveCount int) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ssParity > 0 && rrsParity > 0 {
|
if ssParity > 0 && rrsParity > 0 {
|
||||||
if ssParity < rrsParity {
|
if ssParity > 0 && ssParity < rrsParity {
|
||||||
return fmt.Errorf("Standard storage class parity disks %d should be greater than or equal to Reduced redundancy storage class parity disks %d", ssParity, rrsParity)
|
return fmt.Errorf("Standard storage class parity disks %d should be greater than or equal to Reduced redundancy storage class parity disks %d", ssParity, rrsParity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,14 +221,16 @@ func validateParity(ssParity, rrsParity, setDriveCount int) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetParityForSC - Returns the data and parity drive count based on storage class
|
// GetParityForSC - Returns the data and parity drive count based on storage class
|
||||||
// If storage class is set using the env vars MINIO_STORAGE_CLASS_RRS and MINIO_STORAGE_CLASS_STANDARD
|
// If storage class is set using the env vars MINIO_STORAGE_CLASS_RRS and
|
||||||
// or config.json fields
|
// MINIO_STORAGE_CLASS_STANDARD or server config fields corresponding values are
|
||||||
// -- corresponding values are returned
|
// returned.
|
||||||
// If storage class is not set during startup, default values are returned
|
//
|
||||||
// -- Default for Reduced Redundancy Storage class is, parity = 2 and data = N-Parity
|
// -- if input storage class is empty then standard is assumed
|
||||||
// -- Default for Standard Storage class is, parity = N/2, data = N/2
|
// -- if input is RRS but RRS is not configured default '2' parity
|
||||||
// If storage class is empty
|
// for RRS is assumed
|
||||||
// -- standard storage class is assumed and corresponding data and parity is returned
|
// -- if input is STANDARD but STANDARD is not configured '0' parity
|
||||||
|
// is returned, the caller is expected to choose the right parity
|
||||||
|
// at that point.
|
||||||
func (sCfg Config) GetParityForSC(sc string) (parity int) {
|
func (sCfg Config) GetParityForSC(sc string) (parity int) {
|
||||||
switch strings.TrimSpace(sc) {
|
switch strings.TrimSpace(sc) {
|
||||||
case RRS:
|
case RRS:
|
||||||
@@ -241,7 +246,7 @@ func (sCfg Config) GetParityForSC(sc string) (parity int) {
|
|||||||
|
|
||||||
// GetDMA - returns DMA configuration.
|
// GetDMA - returns DMA configuration.
|
||||||
func (sCfg Config) GetDMA() string {
|
func (sCfg Config) GetDMA() string {
|
||||||
return sCfg.DMA.DMA
|
return sCfg.DMA
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enabled returns if etcd is enabled.
|
// Enabled returns if etcd is enabled.
|
||||||
@@ -254,8 +259,6 @@ func Enabled(kvs config.KVS) bool {
|
|||||||
// LookupConfig - lookup storage class config and override with valid environment settings if any.
|
// LookupConfig - lookup storage class config and override with valid environment settings if any.
|
||||||
func LookupConfig(kvs config.KVS, setDriveCount int) (cfg Config, err error) {
|
func LookupConfig(kvs config.KVS, setDriveCount int) (cfg Config, err error) {
|
||||||
cfg = Config{}
|
cfg = Config{}
|
||||||
cfg.Standard.Parity = setDriveCount / 2
|
|
||||||
cfg.RRS.Parity = defaultRRSParity
|
|
||||||
|
|
||||||
if err = config.CheckValidKeys(config.StorageClassSubSys, kvs, DefaultKVS); err != nil {
|
if err = config.CheckValidKeys(config.StorageClassSubSys, kvs, DefaultKVS); err != nil {
|
||||||
return Config{}, err
|
return Config{}, err
|
||||||
@@ -271,9 +274,6 @@ func LookupConfig(kvs config.KVS, setDriveCount int) (cfg Config, err error) {
|
|||||||
return Config{}, err
|
return Config{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.Standard.Parity == 0 {
|
|
||||||
cfg.Standard.Parity = setDriveCount / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if rrsc != "" {
|
if rrsc != "" {
|
||||||
cfg.RRS, err = parseStorageClass(rrsc)
|
cfg.RRS, err = parseStorageClass(rrsc)
|
||||||
@@ -291,7 +291,7 @@ func LookupConfig(kvs config.KVS, setDriveCount int) (cfg Config, err error) {
|
|||||||
if dma != DMAReadWrite && dma != DMAWrite {
|
if dma != DMAReadWrite && dma != DMAWrite {
|
||||||
return Config{}, errors.New(`valid dma values are "read-write" and "write"`)
|
return Config{}, errors.New(`valid dma values are "read-write" and "write"`)
|
||||||
}
|
}
|
||||||
cfg.DMA.DMA = dma
|
cfg.DMA = dma
|
||||||
|
|
||||||
// Validation is done after parsing both the storage classes. This is needed because we need one
|
// Validation is done after parsing both the storage classes. This is needed because we need one
|
||||||
// storage class value to deduce the correct value of the other storage class.
|
// storage class value to deduce the correct value of the other storage class.
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
// MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Adapted from Go stdlib.
|
||||||
|
|
||||||
|
var hexTable = "0123456789abcdef"
|
||||||
|
|
||||||
|
// EscapeStringJSON will escape a string for JSON and write it to dst.
|
||||||
|
func EscapeStringJSON(dst *bytes.Buffer, s string) {
|
||||||
|
start := 0
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
|
if htmlSafeSet[b] {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
dst.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteByte('\\')
|
||||||
|
switch b {
|
||||||
|
case '\\', '"':
|
||||||
|
dst.WriteByte(b)
|
||||||
|
case '\n':
|
||||||
|
dst.WriteByte('n')
|
||||||
|
case '\r':
|
||||||
|
dst.WriteByte('r')
|
||||||
|
case '\t':
|
||||||
|
dst.WriteByte('t')
|
||||||
|
default:
|
||||||
|
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||||
|
// If escapeHTML is set, it also escapes <, >, and &
|
||||||
|
// because they can lead to security holes when
|
||||||
|
// user-controlled strings are rendered into JSON
|
||||||
|
// and served to some browsers.
|
||||||
|
dst.WriteString(`u00`)
|
||||||
|
dst.WriteByte(hexTable[b>>4])
|
||||||
|
dst.WriteByte(hexTable[b&0xF])
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, size := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if c == utf8.RuneError && size == 1 {
|
||||||
|
if start < i {
|
||||||
|
dst.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\ufffd`)
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// U+2028 is LINE SEPARATOR.
|
||||||
|
// U+2029 is PARAGRAPH SEPARATOR.
|
||||||
|
// They are both technically valid characters in JSON strings,
|
||||||
|
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
||||||
|
// and can lead to security holes there. It is valid JSON to
|
||||||
|
// escape them, so we do so unconditionally.
|
||||||
|
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
||||||
|
if c == '\u2028' || c == '\u2029' {
|
||||||
|
if start < i {
|
||||||
|
dst.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\u202`)
|
||||||
|
dst.WriteByte(hexTable[c&0xF])
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
if start < len(s) {
|
||||||
|
dst.WriteString(s[start:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// htmlSafeSet holds the value true if the ASCII character with the given
|
||||||
|
// array position can be safely represented inside a JSON string, embedded
|
||||||
|
// inside of HTML <script> tags, without any additional escaping.
|
||||||
|
//
|
||||||
|
// All values are true except for the ASCII control characters (0-31), the
|
||||||
|
// double quote ("), the backslash character ("\"), HTML opening and closing
|
||||||
|
// tags ("<" and ">"), and the ampersand ("&").
|
||||||
|
var htmlSafeSet = [utf8.RuneSelf]bool{
|
||||||
|
' ': true,
|
||||||
|
'!': true,
|
||||||
|
'"': false,
|
||||||
|
'#': true,
|
||||||
|
'$': true,
|
||||||
|
'%': true,
|
||||||
|
'&': false,
|
||||||
|
'\'': true,
|
||||||
|
'(': true,
|
||||||
|
')': true,
|
||||||
|
'*': true,
|
||||||
|
'+': true,
|
||||||
|
',': true,
|
||||||
|
'-': true,
|
||||||
|
'.': true,
|
||||||
|
'/': true,
|
||||||
|
'0': true,
|
||||||
|
'1': true,
|
||||||
|
'2': true,
|
||||||
|
'3': true,
|
||||||
|
'4': true,
|
||||||
|
'5': true,
|
||||||
|
'6': true,
|
||||||
|
'7': true,
|
||||||
|
'8': true,
|
||||||
|
'9': true,
|
||||||
|
':': true,
|
||||||
|
';': true,
|
||||||
|
'<': false,
|
||||||
|
'=': true,
|
||||||
|
'>': false,
|
||||||
|
'?': true,
|
||||||
|
'@': true,
|
||||||
|
'A': true,
|
||||||
|
'B': true,
|
||||||
|
'C': true,
|
||||||
|
'D': true,
|
||||||
|
'E': true,
|
||||||
|
'F': true,
|
||||||
|
'G': true,
|
||||||
|
'H': true,
|
||||||
|
'I': true,
|
||||||
|
'J': true,
|
||||||
|
'K': true,
|
||||||
|
'L': true,
|
||||||
|
'M': true,
|
||||||
|
'N': true,
|
||||||
|
'O': true,
|
||||||
|
'P': true,
|
||||||
|
'Q': true,
|
||||||
|
'R': true,
|
||||||
|
'S': true,
|
||||||
|
'T': true,
|
||||||
|
'U': true,
|
||||||
|
'V': true,
|
||||||
|
'W': true,
|
||||||
|
'X': true,
|
||||||
|
'Y': true,
|
||||||
|
'Z': true,
|
||||||
|
'[': true,
|
||||||
|
'\\': false,
|
||||||
|
']': true,
|
||||||
|
'^': true,
|
||||||
|
'_': true,
|
||||||
|
'`': true,
|
||||||
|
'a': true,
|
||||||
|
'b': true,
|
||||||
|
'c': true,
|
||||||
|
'd': true,
|
||||||
|
'e': true,
|
||||||
|
'f': true,
|
||||||
|
'g': true,
|
||||||
|
'h': true,
|
||||||
|
'i': true,
|
||||||
|
'j': true,
|
||||||
|
'k': true,
|
||||||
|
'l': true,
|
||||||
|
'm': true,
|
||||||
|
'n': true,
|
||||||
|
'o': true,
|
||||||
|
'p': true,
|
||||||
|
'q': true,
|
||||||
|
'r': true,
|
||||||
|
's': true,
|
||||||
|
't': true,
|
||||||
|
'u': true,
|
||||||
|
'v': true,
|
||||||
|
'w': true,
|
||||||
|
'x': true,
|
||||||
|
'y': true,
|
||||||
|
'z': true,
|
||||||
|
'{': true,
|
||||||
|
'|': true,
|
||||||
|
'}': true,
|
||||||
|
'~': true,
|
||||||
|
'\u007f': true,
|
||||||
|
}
|
||||||
+9
-5
@@ -116,9 +116,9 @@ func NewKes(cfg KesConfig) (KMS, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if cfg.Transport.TLSClientConfig != nil {
|
|
||||||
if err = loadCACertificates(cfg.CAPath,
|
if cfg.Transport.TLSClientConfig != nil && cfg.Transport.TLSClientConfig.RootCAs != nil {
|
||||||
cfg.Transport.TLSClientConfig.RootCAs); err != nil {
|
if err = loadCACertificates(cfg.CAPath, cfg.Transport.TLSClientConfig.RootCAs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -132,8 +132,12 @@ func NewKes(cfg KesConfig) (KMS, error) {
|
|||||||
if err = loadCACertificates(cfg.CAPath, rootCAs); err != nil {
|
if err = loadCACertificates(cfg.CAPath, rootCAs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cfg.Transport.TLSClientConfig = &tls.Config{
|
if cfg.Transport.TLSClientConfig == nil {
|
||||||
RootCAs: rootCAs,
|
cfg.Transport.TLSClientConfig = &tls.Config{
|
||||||
|
RootCAs: rootCAs,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.Transport.TLSClientConfig.RootCAs = rootCAs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cfg.Transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
|
cfg.Transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
|||||||
+1
-1
@@ -19,13 +19,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
sha256 "github.com/minio/sha256-simd"
|
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+14
-6
@@ -19,13 +19,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
sha256 "github.com/minio/sha256-simd"
|
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ type Context map[string]string
|
|||||||
//
|
//
|
||||||
// WriteTo sorts the context keys and writes the sorted
|
// WriteTo sorts the context keys and writes the sorted
|
||||||
// key-value pairs as canonical JSON object to w.
|
// key-value pairs as canonical JSON object to w.
|
||||||
|
// Sort order is based on the un-escaped keys.
|
||||||
//
|
//
|
||||||
// Note that neither keys nor values are escaped for JSON.
|
// Note that neither keys nor values are escaped for JSON.
|
||||||
func (c Context) WriteTo(w io.Writer) (n int64, err error) {
|
func (c Context) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
@@ -48,13 +49,19 @@ func (c Context) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
}
|
}
|
||||||
sort.Sort(sortedKeys)
|
sort.Sort(sortedKeys)
|
||||||
|
|
||||||
|
escape := func(s string) string {
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, len(s)))
|
||||||
|
EscapeStringJSON(buf, s)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
nn, err := io.WriteString(w, "{")
|
nn, err := io.WriteString(w, "{")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n + int64(nn), err
|
return n + int64(nn), err
|
||||||
}
|
}
|
||||||
n += int64(nn)
|
n += int64(nn)
|
||||||
for i, k := range sortedKeys {
|
for i, k := range sortedKeys {
|
||||||
s := fmt.Sprintf("\"%s\":\"%s\",", k, c[k])
|
s := fmt.Sprintf("\"%s\":\"%s\",", escape(k), escape(c[k]))
|
||||||
if i == len(sortedKeys)-1 {
|
if i == len(sortedKeys)-1 {
|
||||||
s = s[:len(s)-1] // remove last ','
|
s = s[:len(s)-1] // remove last ','
|
||||||
}
|
}
|
||||||
@@ -73,6 +80,7 @@ func (c Context) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
//
|
//
|
||||||
// AppendTo sorts the context keys and writes the sorted
|
// AppendTo sorts the context keys and writes the sorted
|
||||||
// key-value pairs as canonical JSON object to w.
|
// key-value pairs as canonical JSON object to w.
|
||||||
|
// Sort order is based on the un-escaped keys.
|
||||||
//
|
//
|
||||||
// Note that neither keys nor values are escaped for JSON.
|
// Note that neither keys nor values are escaped for JSON.
|
||||||
func (c Context) AppendTo(dst []byte) (output []byte) {
|
func (c Context) AppendTo(dst []byte) (output []byte) {
|
||||||
@@ -87,9 +95,9 @@ func (c Context) AppendTo(dst []byte) (output []byte) {
|
|||||||
if len(c) == 1 {
|
if len(c) == 1 {
|
||||||
for k, v := range c {
|
for k, v := range c {
|
||||||
out.WriteString(`{"`)
|
out.WriteString(`{"`)
|
||||||
out.WriteString(k)
|
EscapeStringJSON(out, k)
|
||||||
out.WriteString(`":"`)
|
out.WriteString(`":"`)
|
||||||
out.WriteString(v)
|
EscapeStringJSON(out, v)
|
||||||
out.WriteString(`"}`)
|
out.WriteString(`"}`)
|
||||||
}
|
}
|
||||||
return out.Bytes()
|
return out.Bytes()
|
||||||
@@ -104,9 +112,9 @@ func (c Context) AppendTo(dst []byte) (output []byte) {
|
|||||||
out.WriteByte('{')
|
out.WriteByte('{')
|
||||||
for i, k := range sortedKeys {
|
for i, k := range sortedKeys {
|
||||||
out.WriteByte('"')
|
out.WriteByte('"')
|
||||||
out.WriteString(k)
|
EscapeStringJSON(out, k)
|
||||||
out.WriteString(`":"`)
|
out.WriteString(`":"`)
|
||||||
out.WriteString(c[k])
|
EscapeStringJSON(out, c[k])
|
||||||
out.WriteByte('"')
|
out.WriteByte('"')
|
||||||
if i < len(sortedKeys)-1 {
|
if i < len(sortedKeys)-1 {
|
||||||
out.WriteByte(',')
|
out.WriteByte(',')
|
||||||
|
|||||||
+10
-6
@@ -65,11 +65,15 @@ var contextWriteToTests = []struct {
|
|||||||
Context Context
|
Context Context
|
||||||
ExpectedJSON string
|
ExpectedJSON string
|
||||||
}{
|
}{
|
||||||
{Context: Context{}, ExpectedJSON: "{}"}, // 0
|
0: {Context: Context{}, ExpectedJSON: "{}"},
|
||||||
{Context: Context{"a": "b"}, ExpectedJSON: `{"a":"b"}`}, // 1
|
1: {Context: Context{"a": "b"}, ExpectedJSON: `{"a":"b"}`},
|
||||||
{Context: Context{"a": "b", "c": "d"}, ExpectedJSON: `{"a":"b","c":"d"}`}, // 2
|
2: {Context: Context{"a": "b", "c": "d"}, ExpectedJSON: `{"a":"b","c":"d"}`},
|
||||||
{Context: Context{"c": "d", "a": "b"}, ExpectedJSON: `{"a":"b","c":"d"}`}, // 3
|
3: {Context: Context{"c": "d", "a": "b"}, ExpectedJSON: `{"a":"b","c":"d"}`},
|
||||||
{Context: Context{"0": "1", "-": "2", ".": "#"}, ExpectedJSON: `{"-":"2",".":"#","0":"1"}`}, // 4
|
4: {Context: Context{"0": "1", "-": "2", ".": "#"}, ExpectedJSON: `{"-":"2",".":"#","0":"1"}`},
|
||||||
|
// rfc 8259 escapes
|
||||||
|
5: {Context: Context{"0": "1", "key\\": "val\tue\r\n", "\"": "\""}, ExpectedJSON: `{"\"":"\"","0":"1","key\\":"val\tue\r\n"}`},
|
||||||
|
// html sensitive escapes
|
||||||
|
6: {Context: Context{"a": "<>&"}, ExpectedJSON: `{"a":"\u003c\u003e\u0026"}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextWriteTo(t *testing.T) {
|
func TestContextWriteTo(t *testing.T) {
|
||||||
@@ -101,7 +105,7 @@ func TestContextAppendTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkContext_AppendTo(b *testing.B) {
|
func BenchmarkContext_AppendTo(b *testing.B) {
|
||||||
tests := []Context{{}, {"bucket": "warp-benchmark-bucket"}, {"0": "1", "-": "2", ".": "#"}, {"34trg": "dfioutr89", "ikjfdghkjf": "jkedfhgfjkhg", "sdfhsdjkh": "if88889", "asddsirfh804": "kjfdshgdfuhgfg78-45604586#$%"}}
|
tests := []Context{{}, {"bucket": "warp-benchmark-bucket"}, {"0": "1", "-": "2", ".": "#"}, {"34trg": "dfioutr89", "ikjfdghkjf": "jkedfhgfjkhg", "sdfhsdjkh": "if88889", "asddsirfh804": "kjfdshgdfuhgfg78-45604586#$%<>&"}}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
b.Run(fmt.Sprintf("%d-elems", len(test)), func(b *testing.B) {
|
b.Run(fmt.Sprintf("%d-elems", len(test)), func(b *testing.B) {
|
||||||
dst := make([]byte, 0, 1024)
|
dst := make([]byte, 0, 1024)
|
||||||
|
|||||||
+32
-14
@@ -44,6 +44,15 @@ const (
|
|||||||
// MetaDataEncryptionKey is the sealed data encryption key (DEK) received from
|
// MetaDataEncryptionKey is the sealed data encryption key (DEK) received from
|
||||||
// the KMS.
|
// the KMS.
|
||||||
MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
|
MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
|
||||||
|
|
||||||
|
// MetaContext is the KMS context provided by a client when encrypting an
|
||||||
|
// object with SSE-KMS. A client may not send a context in which case the
|
||||||
|
// MetaContext will not be present.
|
||||||
|
// MetaContext only contains the bucket/object name if the client explicitly
|
||||||
|
// added it. However, when decrypting an object the bucket/object name must
|
||||||
|
// be part of the object. Therefore, the bucket/object name must be added
|
||||||
|
// to the context, if not present, whenever a decryption is performed.
|
||||||
|
MetaContext = "X-Minio-Internal-Server-Side-Encryption-Context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsMultiPart returns true if the object metadata indicates
|
// IsMultiPart returns true if the object metadata indicates
|
||||||
@@ -109,26 +118,35 @@ func IsSourceEncrypted(metadata map[string]string) bool {
|
|||||||
//
|
//
|
||||||
// IsEncrypted only checks whether the metadata contains at least
|
// IsEncrypted only checks whether the metadata contains at least
|
||||||
// one entry indicating SSE-C or SSE-S3.
|
// one entry indicating SSE-C or SSE-S3.
|
||||||
func IsEncrypted(metadata map[string]string) bool {
|
func IsEncrypted(metadata map[string]string) (Type, bool) {
|
||||||
if _, ok := metadata[MetaIV]; ok {
|
if S3KMS.IsEncrypted(metadata) {
|
||||||
return true
|
return S3KMS, true
|
||||||
}
|
|
||||||
if _, ok := metadata[MetaAlgorithm]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if IsMultiPart(metadata) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
if S3.IsEncrypted(metadata) {
|
if S3.IsEncrypted(metadata) {
|
||||||
return true
|
return S3, true
|
||||||
}
|
}
|
||||||
if SSEC.IsEncrypted(metadata) {
|
if SSEC.IsEncrypted(metadata) {
|
||||||
return true
|
return SSEC, true
|
||||||
}
|
}
|
||||||
if S3KMS.IsEncrypted(metadata) {
|
if IsMultiPart(metadata) {
|
||||||
return true
|
return nil, true
|
||||||
}
|
}
|
||||||
return false
|
if _, ok := metadata[MetaIV]; ok {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaAlgorithm]; ok {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaKeyID]; ok {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaDataEncryptionKey]; ok {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaContext]; ok {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMultipartMetadata adds the multipart flag entry to metadata
|
// CreateMultipartMetadata adds the multipart flag entry to metadata
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ var isEncryptedTests = []struct {
|
|||||||
|
|
||||||
func TestIsEncrypted(t *testing.T) {
|
func TestIsEncrypted(t *testing.T) {
|
||||||
for i, test := range isEncryptedTests {
|
for i, test := range isEncryptedTests {
|
||||||
if isEncrypted := IsEncrypted(test.Metadata); isEncrypted != test.Encrypted {
|
if _, isEncrypted := IsEncrypted(test.Metadata); isEncrypted != test.Encrypted {
|
||||||
t.Errorf("Test %d: got '%v' - want '%v'", i, isEncrypted, test.Encrypted)
|
t.Errorf("Test %d: got '%v' - want '%v'", i, isEncrypted, test.Encrypted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,8 +74,8 @@ var s3IsEncryptedTests = []struct {
|
|||||||
{Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
|
{Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
|
||||||
{Encrypted: false, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
|
{Encrypted: false, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
|
||||||
{Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
|
{Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
|
||||||
{Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5
|
{Encrypted: false, Metadata: map[string]string{MetaKeyID: ""}}, // 5
|
||||||
{Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
|
{Encrypted: false, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
|
||||||
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
||||||
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-19
@@ -82,12 +82,6 @@ func (ssekms) IsEncrypted(metadata map[string]string) bool {
|
|||||||
if _, ok := metadata[MetaSealedKeyKMS]; ok {
|
if _, ok := metadata[MetaSealedKeyKMS]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := metadata[MetaKeyID]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := metadata[MetaDataEncryptionKey]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,11 +89,14 @@ func (ssekms) IsEncrypted(metadata map[string]string) bool {
|
|||||||
// from the metadata using KMS and returns the decrypted object
|
// from the metadata using KMS and returns the decrypted object
|
||||||
// key.
|
// key.
|
||||||
func (s3 ssekms) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
|
func (s3 ssekms) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
|
||||||
keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata)
|
keyID, kmsKey, sealedKey, ctx, err := s3.ParseMetadata(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return key, err
|
return key, err
|
||||||
}
|
}
|
||||||
unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
|
if _, ok := ctx[bucket]; !ok {
|
||||||
|
ctx[bucket] = path.Join(bucket, object)
|
||||||
|
}
|
||||||
|
unsealKey, err := kms.UnsealKey(keyID, kmsKey, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return key, err
|
return key, err
|
||||||
}
|
}
|
||||||
@@ -147,19 +144,19 @@ func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []
|
|||||||
// KMS data key it returns both. If the metadata does not contain neither a
|
// KMS data key it returns both. If the metadata does not contain neither a
|
||||||
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
|
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
|
||||||
// KMS data key. Otherwise, it returns an error.
|
// KMS data key. Otherwise, it returns an error.
|
||||||
func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
|
func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, ctx Context, err error) {
|
||||||
// Extract all required values from object metadata
|
// Extract all required values from object metadata
|
||||||
b64IV, ok := metadata[MetaIV]
|
b64IV, ok := metadata[MetaIV]
|
||||||
if !ok {
|
if !ok {
|
||||||
return keyID, kmsKey, sealedKey, errMissingInternalIV
|
return keyID, kmsKey, sealedKey, ctx, errMissingInternalIV
|
||||||
}
|
}
|
||||||
algorithm, ok := metadata[MetaAlgorithm]
|
algorithm, ok := metadata[MetaAlgorithm]
|
||||||
if !ok {
|
if !ok {
|
||||||
return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
|
return keyID, kmsKey, sealedKey, ctx, errMissingInternalSealAlgorithm
|
||||||
}
|
}
|
||||||
b64SealedKey, ok := metadata[MetaSealedKeyKMS]
|
b64SealedKey, ok := metadata[MetaSealedKeyKMS]
|
||||||
if !ok {
|
if !ok {
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
|
return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are two possibilites:
|
// There are two possibilites:
|
||||||
@@ -169,33 +166,44 @@ func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []
|
|||||||
keyID, idPresent := metadata[MetaKeyID]
|
keyID, idPresent := metadata[MetaKeyID]
|
||||||
b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey]
|
b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey]
|
||||||
if !idPresent && kmsKeyPresent {
|
if !idPresent && kmsKeyPresent {
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
|
return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
|
||||||
}
|
}
|
||||||
if idPresent && !kmsKeyPresent {
|
if idPresent && !kmsKeyPresent {
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
|
return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether all extracted values are well-formed
|
// Check whether all extracted values are well-formed
|
||||||
iv, err := base64.StdEncoding.DecodeString(b64IV)
|
iv, err := base64.StdEncoding.DecodeString(b64IV)
|
||||||
if err != nil || len(iv) != 32 {
|
if err != nil || len(iv) != 32 {
|
||||||
return keyID, kmsKey, sealedKey, errInvalidInternalIV
|
return keyID, kmsKey, sealedKey, ctx, errInvalidInternalIV
|
||||||
}
|
}
|
||||||
if algorithm != SealAlgorithm {
|
if algorithm != SealAlgorithm {
|
||||||
return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
|
return keyID, kmsKey, sealedKey, ctx, errInvalidInternalSealAlgorithm
|
||||||
}
|
}
|
||||||
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
|
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
|
||||||
if err != nil || len(encryptedKey) != 64 {
|
if err != nil || len(encryptedKey) != 64 {
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
|
return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed key for SSE-KMS is invalid")
|
||||||
}
|
}
|
||||||
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
|
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
|
||||||
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
|
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
|
return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed KMS data key for SSE-KMS is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b64Ctx, ok := metadata[MetaContext]
|
||||||
|
if ok {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(b64Ctx)
|
||||||
|
if err != nil {
|
||||||
|
return keyID, kmsKey, sealedKey, ctx, Errorf("The internal KMS context is not base64-encoded")
|
||||||
|
}
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
if err = json.Unmarshal(b, ctx); err != nil {
|
||||||
|
return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed KMS context is invalid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealedKey.Algorithm = algorithm
|
sealedKey.Algorithm = algorithm
|
||||||
copy(sealedKey.IV[:], iv)
|
copy(sealedKey.IV[:], iv)
|
||||||
copy(sealedKey.Key[:], encryptedKey)
|
copy(sealedKey.Key[:], encryptedKey)
|
||||||
return keyID, kmsKey, sealedKey, nil
|
return keyID, kmsKey, sealedKey, ctx, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,12 +62,6 @@ func (sses3) IsEncrypted(metadata map[string]string) bool {
|
|||||||
if _, ok := metadata[MetaSealedKeyS3]; ok {
|
if _, ok := metadata[MetaSealedKeyS3]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := metadata[MetaKeyID]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := metadata[MetaDataEncryptionKey]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -55,25 +56,25 @@ var (
|
|||||||
globalHealConfig heal.Config
|
globalHealConfig heal.Config
|
||||||
globalHealConfigMu sync.Mutex
|
globalHealConfigMu sync.Mutex
|
||||||
|
|
||||||
dataCrawlerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
dataScannerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||||
// Sleeper values are updated when config is loaded.
|
// Sleeper values are updated when config is loaded.
|
||||||
crawlerSleeper = newDynamicSleeper(10, 10*time.Second)
|
scannerSleeper = newDynamicSleeper(10, 10*time.Second)
|
||||||
)
|
)
|
||||||
|
|
||||||
// initDataCrawler will start the crawler in the background.
|
// initDataScanner will start the scanner in the background.
|
||||||
func initDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
func initDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||||
go runDataCrawler(ctx, objAPI)
|
go runDataScanner(ctx, objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runDataCrawler will start a data crawler.
|
// 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 crawler running per cluster.
|
// There should only ever be one scanner running per cluster.
|
||||||
func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||||
// Make sure only 1 crawler is running on the cluster.
|
// Make sure only 1 scanner is running on the cluster.
|
||||||
locker := objAPI.NewNSLock(minioMetaBucket, "runDataCrawler.lock")
|
locker := objAPI.NewNSLock(minioMetaBucket, "runDataScanner.lock")
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
for {
|
for {
|
||||||
err := locker.GetLock(ctx, dataCrawlerLeaderLockTimeout)
|
err := locker.GetLock(ctx, dataScannerLeaderLockTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
time.Sleep(time.Duration(r.Float64() * float64(dataCrawlStartDelay)))
|
time.Sleep(time.Duration(r.Float64() * float64(dataCrawlStartDelay)))
|
||||||
continue
|
continue
|
||||||
@@ -84,16 +85,19 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
// Load current bloom cycle
|
// Load current bloom cycle
|
||||||
nextBloomCycle := intDataUpdateTracker.current() + 1
|
nextBloomCycle := intDataUpdateTracker.current() + 1
|
||||||
var buf bytes.Buffer
|
|
||||||
err := objAPI.GetObject(ctx, dataUsageBucket, dataUsageBloomName, 0, -1, &buf, "", ObjectOptions{})
|
br, err := objAPI.GetObjectNInfo(ctx, dataUsageBucket, dataUsageBloomName, nil, http.Header{}, readLock, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrObjectNotFound(err) && !isErrBucketNotFound(err) {
|
if !isErrObjectNotFound(err) && !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if buf.Len() == 8 {
|
if br.ObjInfo.Size == 8 {
|
||||||
nextBloomCycle = binary.LittleEndian.Uint64(buf.Bytes())
|
if err = binary.Read(br, binary.LittleEndian, &nextBloomCycle); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
br.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
crawlTimer := time.NewTimer(dataCrawlStartDelay)
|
crawlTimer := time.NewTimer(dataCrawlStartDelay)
|
||||||
@@ -108,7 +112,7 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
crawlTimer.Reset(dataCrawlStartDelay)
|
crawlTimer.Reset(dataCrawlStartDelay)
|
||||||
|
|
||||||
if intDataUpdateTracker.debug {
|
if intDataUpdateTracker.debug {
|
||||||
console.Debugln("starting crawler cycle")
|
console.Debugln("starting scanner cycle")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait before starting next cycle and wait on startup.
|
// Wait before starting next cycle and wait on startup.
|
||||||
@@ -124,13 +128,13 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
nextBloomCycle++
|
nextBloomCycle++
|
||||||
var tmp [8]byte
|
var tmp [8]byte
|
||||||
binary.LittleEndian.PutUint64(tmp[:], nextBloomCycle)
|
binary.LittleEndian.PutUint64(tmp[:], nextBloomCycle)
|
||||||
r, err := hash.NewReader(bytes.NewReader(tmp[:]), int64(len(tmp)), "", "", int64(len(tmp)), false)
|
r, err := hash.NewReader(bytes.NewReader(tmp[:]), int64(len(tmp)), "", "", int64(len(tmp)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageBloomName, NewPutObjReader(r, nil, nil), ObjectOptions{})
|
_, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageBloomName, NewPutObjReader(r), ObjectOptions{})
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -163,7 +167,7 @@ type folderScanner struct {
|
|||||||
|
|
||||||
// crawlDataFolder will crawl the basepath+cache.Info.Name and return an updated cache.
|
// crawlDataFolder will crawl the basepath+cache.Info.Name and return an updated cache.
|
||||||
// The returned cache will always be valid, but may not be updated from the existing.
|
// The returned cache will always be valid, but may not be updated from the existing.
|
||||||
// Before each operation sleepDuration is called which can be used to temporarily halt the crawler.
|
// Before each operation sleepDuration is called which can be used to temporarily halt the scanner.
|
||||||
// If the supplied context is canceled the function will return at the first chance.
|
// If the supplied context is canceled the function will return at the first chance.
|
||||||
func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache, getSize getSizeFn) (dataUsageCache, error) {
|
func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache, getSize getSizeFn) (dataUsageCache, error) {
|
||||||
t := UTCNow()
|
t := UTCNow()
|
||||||
@@ -182,6 +186,8 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
return cache, errors.New("internal error: root scan attempted")
|
return cache, errors.New("internal error: root scan attempted")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipHeal := cache.Info.SkipHealing
|
||||||
|
|
||||||
s := folderScanner{
|
s := folderScanner{
|
||||||
root: basePath,
|
root: basePath,
|
||||||
getSize: getSize,
|
getSize: getSize,
|
||||||
@@ -244,7 +250,7 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
todo, err = s.scanQueuedLevels(ctx, todo, i == flattenLevels-1)
|
todo, err = s.scanQueuedLevels(ctx, todo, i == flattenLevels-1, skipHeal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// No useful information...
|
// No useful information...
|
||||||
return cache, err
|
return cache, err
|
||||||
@@ -262,7 +268,7 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
return s.newCache, ctx.Err()
|
return s.newCache, ctx.Err()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
du, err := s.deepScanFolder(ctx, folder)
|
du, err := s.deepScanFolder(ctx, folder, skipHeal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
@@ -324,7 +330,7 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update on this cycle...
|
// Update on this cycle...
|
||||||
du, err := s.deepScanFolder(ctx, folder)
|
du, err := s.deepScanFolder(ctx, folder, skipHeal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
@@ -347,7 +353,7 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
// Files found in the folders will be added to f.newCache.
|
// Files found in the folders will be added to f.newCache.
|
||||||
// If final is provided folders will be put into f.newFolders or f.existingFolders.
|
// If final is provided folders will be put into f.newFolders or f.existingFolders.
|
||||||
// If final is not provided the folders found are returned from the function.
|
// If final is not provided the folders found are returned from the function.
|
||||||
func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFolder, final bool) ([]cachedFolder, error) {
|
func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFolder, final bool, skipHeal bool) ([]cachedFolder, error) {
|
||||||
var nextFolders []cachedFolder
|
var nextFolders []cachedFolder
|
||||||
done := ctx.Done()
|
done := ctx.Done()
|
||||||
scannerLogPrefix := color.Green("folder-scanner:")
|
scannerLogPrefix := color.Green("folder-scanner:")
|
||||||
@@ -384,12 +390,12 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageCrawlDebug {
|
||||||
console.Debugf(scannerLogPrefix+" Adding non-updated folder to heal check: %v\n", folder.name)
|
console.Debugf(scannerLogPrefix+" Adding non-updated folder to heal check: %v\n", folder.name)
|
||||||
}
|
}
|
||||||
// If probability was already crawlerHealFolderInclude, keep it.
|
// If probability was already scannerHealFolderInclude, keep it.
|
||||||
folder.objectHealProbDiv = f.healFolderInclude
|
folder.objectHealProbDiv = f.healFolderInclude
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
scannerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||||
|
|
||||||
cache := dataUsageEntry{}
|
cache := dataUsageEntry{}
|
||||||
|
|
||||||
@@ -401,19 +407,19 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageCrawlDebug {
|
||||||
console.Debugf(scannerLogPrefix+" no bucket (%s,%s)\n", f.root, entName)
|
console.Debugf(scannerLogPrefix+" no bucket (%s,%s)\n", f.root, entName)
|
||||||
}
|
}
|
||||||
return nil
|
return errDoneForNow
|
||||||
}
|
}
|
||||||
|
|
||||||
if isReservedOrInvalidBucket(bucket, false) {
|
if isReservedOrInvalidBucket(bucket, false) {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageCrawlDebug {
|
||||||
console.Debugf(scannerLogPrefix+" invalid bucket: %v, entry: %v\n", bucket, entName)
|
console.Debugf(scannerLogPrefix+" invalid bucket: %v, entry: %v\n", bucket, entName)
|
||||||
}
|
}
|
||||||
return nil
|
return errDoneForNow
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
return ctx.Err()
|
return errDoneForNow
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +447,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
// Dynamic time delay.
|
||||||
wait := crawlerSleeper.Timer(ctx)
|
wait := scannerSleeper.Timer(ctx)
|
||||||
|
|
||||||
// Get file size, ignore errors.
|
// Get file size, ignore errors.
|
||||||
item := crawlItem{
|
item := crawlItem{
|
||||||
@@ -455,6 +461,11 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the drive belongs to an erasure set
|
||||||
|
// that is already being healed, skip the
|
||||||
|
// healing attempt on this drive.
|
||||||
|
item.heal = item.heal && !skipHeal
|
||||||
|
|
||||||
sizeSummary, err := f.getSize(item)
|
sizeSummary, err := f.getSize(item)
|
||||||
if err == errSkipFile {
|
if err == errSkipFile {
|
||||||
wait() // wait to proceed to next entry.
|
wait() // wait to proceed to next entry.
|
||||||
@@ -526,7 +537,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
// Dynamic time delay.
|
||||||
wait := crawlerSleeper.Timer(ctx)
|
wait := scannerSleeper.Timer(ctx)
|
||||||
resolver.bucket = bucket
|
resolver.bucket = bucket
|
||||||
|
|
||||||
foundObjs := false
|
foundObjs := false
|
||||||
@@ -556,7 +567,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
|
|
||||||
// Sleep and reset.
|
// Sleep and reset.
|
||||||
wait()
|
wait()
|
||||||
wait = crawlerSleeper.Timer(ctx)
|
wait = scannerSleeper.Timer(ctx)
|
||||||
entry, ok := entries.resolve(&resolver)
|
entry, ok := entries.resolve(&resolver)
|
||||||
if !ok {
|
if !ok {
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
@@ -593,7 +604,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
for _, ver := range fiv.Versions {
|
for _, ver := range fiv.Versions {
|
||||||
// Sleep and reset.
|
// Sleep and reset.
|
||||||
wait()
|
wait()
|
||||||
wait = crawlerSleeper.Timer(ctx)
|
wait = scannerSleeper.Timer(ctx)
|
||||||
err := bgSeq.queueHealTask(healSource{
|
err := bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: fiv.Name,
|
object: fiv.Name,
|
||||||
@@ -626,12 +637,12 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
|
|
||||||
objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{
|
objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
Remove: true,
|
Remove: healDeleteDangling,
|
||||||
},
|
},
|
||||||
func(bucket, object, versionID string) error {
|
func(bucket, object, versionID string) error {
|
||||||
// Wait for each heal as per crawler frequency.
|
// Wait for each heal as per scanner frequency.
|
||||||
wait()
|
wait()
|
||||||
wait = crawlerSleeper.Timer(ctx)
|
wait = scannerSleeper.Timer(ctx)
|
||||||
return bgSeq.queueHealTask(healSource{
|
return bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: object,
|
object: object,
|
||||||
@@ -659,7 +670,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deepScanFolder will deep scan a folder and return the size if no error occurs.
|
// deepScanFolder will deep scan a folder and return the size if no error occurs.
|
||||||
func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder) (*dataUsageEntry, error) {
|
func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder, skipHeal bool) (*dataUsageEntry, error) {
|
||||||
var cache dataUsageEntry
|
var cache dataUsageEntry
|
||||||
|
|
||||||
done := ctx.Done()
|
done := ctx.Done()
|
||||||
@@ -671,7 +682,7 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
|
|||||||
addDir = func(entName string, typ os.FileMode) error {
|
addDir = func(entName string, typ os.FileMode) error {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
return ctx.Err()
|
return errDoneForNow
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,12 +690,12 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
|
|||||||
dirStack = append(dirStack, entName)
|
dirStack = append(dirStack, entName)
|
||||||
err := readDirFn(path.Join(dirStack...), addDir)
|
err := readDirFn(path.Join(dirStack...), addDir)
|
||||||
dirStack = dirStack[:len(dirStack)-1]
|
dirStack = dirStack[:len(dirStack)-1]
|
||||||
crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
scannerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
// Dynamic time delay.
|
||||||
wait := crawlerSleeper.Timer(ctx)
|
wait := scannerSleeper.Timer(ctx)
|
||||||
|
|
||||||
// Get file size, ignore errors.
|
// Get file size, ignore errors.
|
||||||
dirStack = append(dirStack, entName)
|
dirStack = append(dirStack, entName)
|
||||||
@@ -700,18 +711,23 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
|
|||||||
activeLifeCycle = f.oldCache.Info.lifeCycle
|
activeLifeCycle = f.oldCache.Info.lifeCycle
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeSummary, err := f.getSize(
|
item := crawlItem{
|
||||||
crawlItem{
|
Path: fileName,
|
||||||
Path: fileName,
|
Typ: typ,
|
||||||
Typ: typ,
|
bucket: bucket,
|
||||||
bucket: bucket,
|
prefix: path.Dir(prefix),
|
||||||
prefix: path.Dir(prefix),
|
objectName: path.Base(entName),
|
||||||
objectName: path.Base(entName),
|
debug: f.dataUsageCrawlDebug,
|
||||||
debug: f.dataUsageCrawlDebug,
|
lifeCycle: activeLifeCycle,
|
||||||
lifeCycle: activeLifeCycle,
|
heal: hashPath(path.Join(prefix, entName)).mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
||||||
heal: hashPath(path.Join(prefix, entName)).mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
}
|
||||||
})
|
|
||||||
|
|
||||||
|
// if the drive belongs to an erasure set
|
||||||
|
// that is already being healed, skip the
|
||||||
|
// healing attempt on this drive.
|
||||||
|
item.heal = item.heal && !skipHeal
|
||||||
|
|
||||||
|
sizeSummary, err := f.getSize(item)
|
||||||
if err == errSkipFile {
|
if err == errSkipFile {
|
||||||
// Wait to throttle IO
|
// Wait to throttle IO
|
||||||
wait()
|
wait()
|
||||||
@@ -773,12 +789,12 @@ func (i *crawlItem) transformMetaDir() {
|
|||||||
|
|
||||||
// actionMeta contains information used to apply actions.
|
// actionMeta contains information used to apply actions.
|
||||||
type actionMeta struct {
|
type actionMeta struct {
|
||||||
oi ObjectInfo
|
oi ObjectInfo
|
||||||
successorModTime time.Time // The modtime of the successor version
|
bitRotScan bool // indicates if bitrot check was requested.
|
||||||
numVersions int // The number of versions of this object
|
|
||||||
bitRotScan bool // indicates if bitrot check was requested.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var applyActionsLogPrefix = color.Green("applyActions:")
|
||||||
|
|
||||||
// applyActions will apply lifecycle checks on to a scanned item.
|
// applyActions will apply lifecycle checks on to a scanned item.
|
||||||
// The resulting size on disk will always be returned.
|
// The resulting size on disk will always be returned.
|
||||||
// The metadata will be compared to consensus on the object layer before any changes are applied.
|
// The metadata will be compared to consensus on the object layer before any changes are applied.
|
||||||
@@ -788,7 +804,6 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
if i.debug {
|
if i.debug {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
applyActionsLogPrefix := color.Green("applyActions:")
|
|
||||||
if i.heal {
|
if i.heal {
|
||||||
if i.debug {
|
if i.debug {
|
||||||
if meta.oi.VersionID != "" {
|
if meta.oi.VersionID != "" {
|
||||||
@@ -827,8 +842,8 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
VersionID: meta.oi.VersionID,
|
VersionID: meta.oi.VersionID,
|
||||||
DeleteMarker: meta.oi.DeleteMarker,
|
DeleteMarker: meta.oi.DeleteMarker,
|
||||||
IsLatest: meta.oi.IsLatest,
|
IsLatest: meta.oi.IsLatest,
|
||||||
NumVersions: meta.numVersions,
|
NumVersions: meta.oi.NumVersions,
|
||||||
SuccessorModTime: meta.successorModTime,
|
SuccessorModTime: meta.oi.SuccessorModTime,
|
||||||
RestoreOngoing: meta.oi.RestoreOngoing,
|
RestoreOngoing: meta.oi.RestoreOngoing,
|
||||||
RestoreExpires: meta.oi.RestoreExpires,
|
RestoreExpires: meta.oi.RestoreExpires,
|
||||||
TransitionStatus: meta.oi.TransitionStatus,
|
TransitionStatus: meta.oi.TransitionStatus,
|
||||||
@@ -863,8 +878,8 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
case ObjectNotFound:
|
case ObjectNotFound, VersionNotFound:
|
||||||
// object not found return 0
|
// object not found or version not found return 0
|
||||||
return 0
|
return 0
|
||||||
default:
|
default:
|
||||||
// All other errors proceed.
|
// All other errors proceed.
|
||||||
@@ -872,87 +887,131 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size = obj.Size
|
|
||||||
|
|
||||||
// Recalculate action.
|
var applied bool
|
||||||
|
action = evalActionFromLifecycle(ctx, *i.lifeCycle, obj, i.debug)
|
||||||
|
if action != lifecycle.NoneAction {
|
||||||
|
applied = applyLifecycleAction(ctx, action, o, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if applied {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, obj ObjectInfo, debug bool) (action lifecycle.Action) {
|
||||||
lcOpts := lifecycle.ObjectOpts{
|
lcOpts := lifecycle.ObjectOpts{
|
||||||
Name: i.objectPath(),
|
Name: obj.Name,
|
||||||
UserTags: obj.UserTags,
|
UserTags: obj.UserTags,
|
||||||
ModTime: obj.ModTime,
|
ModTime: obj.ModTime,
|
||||||
VersionID: obj.VersionID,
|
VersionID: obj.VersionID,
|
||||||
DeleteMarker: obj.DeleteMarker,
|
DeleteMarker: obj.DeleteMarker,
|
||||||
IsLatest: obj.IsLatest,
|
IsLatest: obj.IsLatest,
|
||||||
NumVersions: meta.numVersions,
|
NumVersions: obj.NumVersions,
|
||||||
SuccessorModTime: meta.successorModTime,
|
SuccessorModTime: obj.SuccessorModTime,
|
||||||
RestoreOngoing: obj.RestoreOngoing,
|
RestoreOngoing: obj.RestoreOngoing,
|
||||||
RestoreExpires: obj.RestoreExpires,
|
RestoreExpires: obj.RestoreExpires,
|
||||||
TransitionStatus: obj.TransitionStatus,
|
TransitionStatus: obj.TransitionStatus,
|
||||||
}
|
}
|
||||||
action = i.lifeCycle.ComputeAction(lcOpts)
|
|
||||||
if i.debug {
|
action = lc.ComputeAction(lcOpts)
|
||||||
|
if debug {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action)
|
||||||
}
|
}
|
||||||
switch action {
|
|
||||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
if action == lifecycle.NoneAction {
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
return action
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
|
||||||
default:
|
|
||||||
// No action.
|
|
||||||
return size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := ObjectOptions{}
|
|
||||||
switch action {
|
switch 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 size
|
return lifecycle.NoneAction
|
||||||
}
|
}
|
||||||
if rcfg, _ := globalBucketObjectLockSys.Get(i.bucket); rcfg.LockEnabled {
|
if rcfg, _ := globalBucketObjectLockSys.Get(obj.Bucket); rcfg.LockEnabled {
|
||||||
locked := enforceRetentionForDeletion(ctx, obj)
|
locked := enforceRetentionForDeletion(ctx, obj)
|
||||||
if locked {
|
if locked {
|
||||||
if i.debug {
|
if debug {
|
||||||
if obj.VersionID != "" {
|
if obj.VersionID != "" {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %s v(%s) is locked, not deleting\n", i.objectPath(), obj.VersionID)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %s v(%s) is locked, not deleting\n", obj.Name, obj.VersionID)
|
||||||
} else {
|
} else {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", i.objectPath())
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", obj.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return size
|
return lifecycle.NoneAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return action
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyTransitionAction(ctx context.Context, action lifecycle.Action, objLayer ObjectLayer, obj ObjectInfo) bool {
|
||||||
|
opts := ObjectOptions{}
|
||||||
|
if obj.TransitionStatus == "" {
|
||||||
|
opts.Versioned = globalBucketVersioningSys.Enabled(obj.Bucket)
|
||||||
opts.VersionID = obj.VersionID
|
opts.VersionID = obj.VersionID
|
||||||
case lifecycle.DeleteAction, lifecycle.DeleteRestoredAction:
|
opts.TransitionStatus = lifecycle.TransitionPending
|
||||||
opts.Versioned = globalBucketVersioningSys.Enabled(i.bucket)
|
if _, err := objLayer.DeleteObject(ctx, obj.Bucket, obj.Name, opts); err != nil {
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
if obj.TransitionStatus == "" {
|
return false
|
||||||
opts.Versioned = globalBucketVersioningSys.Enabled(obj.Bucket)
|
|
||||||
opts.VersionID = obj.VersionID
|
|
||||||
opts.TransitionStatus = lifecycle.TransitionPending
|
|
||||||
if _, err = o.DeleteObject(ctx, obj.Bucket, obj.Name, opts); err != nil {
|
|
||||||
// Assume it is still there.
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
return size
|
|
||||||
}
|
}
|
||||||
}
|
// Assume it is still there.
|
||||||
globalTransitionState.queueTransitionTask(obj)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.TransitionStatus != "" {
|
|
||||||
if err := deleteTransitionedObject(ctx, o, i.bucket, i.objectPath(), lcOpts, action, false); err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return size
|
return false
|
||||||
}
|
}
|
||||||
// Notification already sent at *deleteTransitionedObject*, return '0' here.
|
}
|
||||||
return 0
|
globalTransitionState.queueTransitionTask(obj)
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, restoredObject bool) bool {
|
||||||
|
lcOpts := lifecycle.ObjectOpts{
|
||||||
|
Name: obj.Name,
|
||||||
|
UserTags: obj.UserTags,
|
||||||
|
ModTime: obj.ModTime,
|
||||||
|
VersionID: obj.VersionID,
|
||||||
|
DeleteMarker: obj.DeleteMarker,
|
||||||
|
IsLatest: obj.IsLatest,
|
||||||
|
NumVersions: obj.NumVersions,
|
||||||
|
SuccessorModTime: obj.SuccessorModTime,
|
||||||
|
RestoreOngoing: obj.RestoreOngoing,
|
||||||
|
RestoreExpires: obj.RestoreExpires,
|
||||||
|
TransitionStatus: obj.TransitionStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
obj, err = o.DeleteObject(ctx, i.bucket, i.objectPath(), opts)
|
if err := deleteTransitionedObject(ctx, objLayer, obj.Bucket, obj.Name, lcOpts, restoredObject, false); err != nil {
|
||||||
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Notification already sent at *deleteTransitionedObject*, just return 'true' here.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, applyOnVersion bool) bool {
|
||||||
|
opts := ObjectOptions{}
|
||||||
|
|
||||||
|
if applyOnVersion {
|
||||||
|
opts.VersionID = obj.VersionID
|
||||||
|
}
|
||||||
|
if opts.VersionID == "" {
|
||||||
|
opts.Versioned = globalBucketVersioningSys.Enabled(obj.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := objLayer.DeleteObject(ctx, obj.Bucket, obj.Name, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
// Assume it is still there.
|
// Assume it is still there.
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return size
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
eventName := event.ObjectRemovedDelete
|
eventName := event.ObjectRemovedDelete
|
||||||
@@ -963,11 +1022,33 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
// Notify object deleted event.
|
// Notify object deleted event.
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: eventName,
|
EventName: eventName,
|
||||||
BucketName: i.bucket,
|
BucketName: obj.Bucket,
|
||||||
Object: obj,
|
Object: obj,
|
||||||
Host: "Internal: [ILM-EXPIRY]",
|
Host: "Internal: [ILM-EXPIRY]",
|
||||||
})
|
})
|
||||||
return 0
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply object, object version, restored object or restored object version action on the given object
|
||||||
|
func applyExpiryRule(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, restoredObject, applyOnVersion bool) bool {
|
||||||
|
if obj.TransitionStatus != "" {
|
||||||
|
return applyExpiryOnTransitionedObject(ctx, objLayer, obj, restoredObject)
|
||||||
|
}
|
||||||
|
return applyExpiryOnNonTransitionedObjects(ctx, objLayer, obj, applyOnVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform actions (removal of transitioning of objects), return true the action is successfully performed
|
||||||
|
func applyLifecycleAction(ctx context.Context, action lifecycle.Action, objLayer ObjectLayer, obj ObjectInfo) (success bool) {
|
||||||
|
switch action {
|
||||||
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
|
success = applyExpiryRule(ctx, objLayer, obj, false, action == lifecycle.DeleteVersionAction)
|
||||||
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
|
success = applyExpiryRule(ctx, objLayer, obj, true, action == lifecycle.DeleteRestoredVersionAction)
|
||||||
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
|
success = applyTransitionAction(ctx, action, objLayer, obj)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectPath returns the prefix and object name.
|
// objectPath returns the prefix and object name.
|
||||||
@@ -993,7 +1074,7 @@ func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, oi Objec
|
|||||||
case replication.Failed:
|
case replication.Failed:
|
||||||
sizeS.failedSize += oi.Size
|
sizeS.failedSize += oi.Size
|
||||||
globalReplicationState.queueReplicaTask(oi)
|
globalReplicationState.queueReplicaTask(oi)
|
||||||
case replication.Complete:
|
case replication.Completed, "COMPLETE":
|
||||||
sizeS.replicatedSize += oi.Size
|
sizeS.replicatedSize += oi.Size
|
||||||
case replication.Replica:
|
case replication.Replica:
|
||||||
sizeS.replicaSize += oi.Size
|
sizeS.replicaSize += oi.Size
|
||||||
+68
-22
@@ -56,13 +56,29 @@ type dataUsageEntry struct {
|
|||||||
Children dataUsageHashMap
|
Children dataUsageHashMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// dataUsageCache contains a cache of data usage entries.
|
//msgp:tuple dataUsageEntryV2
|
||||||
|
type dataUsageEntryV2 struct {
|
||||||
|
// These fields do no include any children.
|
||||||
|
Size int64
|
||||||
|
Objects uint64
|
||||||
|
ObjSizes sizeHistogram
|
||||||
|
Children dataUsageHashMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataUsageCache contains a cache of data usage entries latest version 3.
|
||||||
type dataUsageCache struct {
|
type dataUsageCache struct {
|
||||||
Info dataUsageCacheInfo
|
Info dataUsageCacheInfo
|
||||||
Disks []string
|
Disks []string
|
||||||
Cache map[string]dataUsageEntry
|
Cache map[string]dataUsageEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dataUsageCache contains a cache of data usage entries version 2.
|
||||||
|
type dataUsageCacheV2 struct {
|
||||||
|
Info dataUsageCacheInfo
|
||||||
|
Disks []string
|
||||||
|
Cache map[string]dataUsageEntryV2
|
||||||
|
}
|
||||||
|
|
||||||
//msgp:ignore dataUsageEntryInfo
|
//msgp:ignore dataUsageEntryInfo
|
||||||
type dataUsageEntryInfo struct {
|
type dataUsageEntryInfo struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -72,9 +88,12 @@ type dataUsageEntryInfo struct {
|
|||||||
|
|
||||||
type dataUsageCacheInfo struct {
|
type dataUsageCacheInfo struct {
|
||||||
// Name of the bucket. Also root element.
|
// Name of the bucket. Also root element.
|
||||||
Name string
|
Name string
|
||||||
LastUpdate time.Time
|
LastUpdate time.Time
|
||||||
NextCycle uint32
|
NextCycle uint32
|
||||||
|
// indicates if the disk is being healed and scanner
|
||||||
|
// should skip healing the disk
|
||||||
|
SkipHealing bool
|
||||||
BloomFilter []byte `msg:"BloomFilter,omitempty"`
|
BloomFilter []byte `msg:"BloomFilter,omitempty"`
|
||||||
lifeCycle *lifecycle.Lifecycle `msg:"-"`
|
lifeCycle *lifecycle.Lifecycle `msg:"-"`
|
||||||
}
|
}
|
||||||
@@ -466,7 +485,7 @@ type objectIO interface {
|
|||||||
// Only backend errors are returned as errors.
|
// Only backend errors are returned as errors.
|
||||||
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
|
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
|
||||||
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
||||||
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, readLock, ObjectOptions{})
|
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, noLock, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ObjectNotFound:
|
case ObjectNotFound:
|
||||||
@@ -478,6 +497,7 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
|
|||||||
*d = dataUsageCache{}
|
*d = dataUsageCache{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
defer r.Close()
|
||||||
if err := d.deserialize(r); err != nil {
|
if err := d.deserialize(r); err != nil {
|
||||||
*d = dataUsageCache{}
|
*d = dataUsageCache{}
|
||||||
logger.LogOnceIf(ctx, err, err.Error())
|
logger.LogOnceIf(ctx, err, err.Error())
|
||||||
@@ -493,7 +513,7 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
}()
|
}()
|
||||||
defer pr.Close()
|
defer pr.Close()
|
||||||
|
|
||||||
r, err := hash.NewReader(pr, -1, "", "", -1, false)
|
r, err := hash.NewReader(pr, -1, "", "", -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -501,8 +521,8 @@ 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, nil, nil),
|
NewPutObjReader(r),
|
||||||
ObjectOptions{})
|
ObjectOptions{NoLock: true})
|
||||||
if isErrBucketNotFound(err) {
|
if isErrBucketNotFound(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -512,12 +532,16 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
// dataUsageCacheVer indicates the cache version.
|
// dataUsageCacheVer indicates the cache version.
|
||||||
// Bumping the cache version will drop data from previous versions
|
// Bumping the cache version will drop data from previous versions
|
||||||
// and write new data with the new version.
|
// and write new data with the new version.
|
||||||
const dataUsageCacheVer = 3
|
const (
|
||||||
|
dataUsageCacheVerV3 = 3
|
||||||
|
dataUsageCacheVerV2 = 2
|
||||||
|
dataUsageCacheVerV1 = 1
|
||||||
|
)
|
||||||
|
|
||||||
// serialize the contents of the cache.
|
// serialize the contents of the cache.
|
||||||
func (d *dataUsageCache) serializeTo(dst io.Writer) error {
|
func (d *dataUsageCache) serializeTo(dst io.Writer) error {
|
||||||
// Add version and compress.
|
// Add version and compress.
|
||||||
_, err := dst.Write([]byte{dataUsageCacheVer})
|
_, err := dst.Write([]byte{dataUsageCacheVerV3})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -552,21 +576,43 @@ func (d *dataUsageCache) deserialize(r io.Reader) error {
|
|||||||
return io.ErrUnexpectedEOF
|
return io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
switch b[0] {
|
switch b[0] {
|
||||||
case 1, 2:
|
case dataUsageCacheVerV1:
|
||||||
return errors.New("cache version deprecated (will autoupdate)")
|
return errors.New("cache version deprecated (will autoupdate)")
|
||||||
case dataUsageCacheVer:
|
case dataUsageCacheVerV2:
|
||||||
default:
|
// Zstd compressed.
|
||||||
return fmt.Errorf("dataUsageCache: unknown version: %d", int(b[0]))
|
dec, err := zstd.NewReader(r, zstd.WithDecoderConcurrency(2))
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dec.Close()
|
||||||
|
|
||||||
// Zstd compressed.
|
dold := &dataUsageCacheV2{}
|
||||||
dec, err := zstd.NewReader(r, zstd.WithDecoderConcurrency(2))
|
if err = dold.DecodeMsg(msgp.NewReader(dec)); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
d.Info = dold.Info
|
||||||
defer dec.Close()
|
d.Disks = dold.Disks
|
||||||
|
d.Cache = make(map[string]dataUsageEntry, len(dold.Cache))
|
||||||
|
for k, v := range dold.Cache {
|
||||||
|
d.Cache[k] = dataUsageEntry{
|
||||||
|
Size: v.Size,
|
||||||
|
Objects: v.Objects,
|
||||||
|
ObjSizes: v.ObjSizes,
|
||||||
|
Children: v.Children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case dataUsageCacheVerV3:
|
||||||
|
// Zstd compressed.
|
||||||
|
dec, err := zstd.NewReader(r, zstd.WithDecoderConcurrency(2))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dec.Close()
|
||||||
|
|
||||||
return d.DecodeMsg(msgp.NewReader(dec))
|
return d.DecodeMsg(msgp.NewReader(dec))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("dataUsageCache: unknown version: %d", int(b[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim this from start+end of hashes.
|
// Trim this from start+end of hashes.
|
||||||
|
|||||||
+462
-9
@@ -313,6 +313,12 @@ func (z *dataUsageCacheInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "NextCycle")
|
err = msgp.WrapError(err, "NextCycle")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "SkipHealing":
|
||||||
|
z.SkipHealing, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SkipHealing")
|
||||||
|
return
|
||||||
|
}
|
||||||
case "BloomFilter":
|
case "BloomFilter":
|
||||||
z.BloomFilter, err = dc.ReadBytes(z.BloomFilter)
|
z.BloomFilter, err = dc.ReadBytes(z.BloomFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -333,11 +339,11 @@ func (z *dataUsageCacheInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *dataUsageCacheInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *dataUsageCacheInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// omitempty: check for empty values
|
// omitempty: check for empty values
|
||||||
zb0001Len := uint32(4)
|
zb0001Len := uint32(5)
|
||||||
var zb0001Mask uint8 /* 4 bits */
|
var zb0001Mask uint8 /* 5 bits */
|
||||||
if z.BloomFilter == nil {
|
if z.BloomFilter == nil {
|
||||||
zb0001Len--
|
zb0001Len--
|
||||||
zb0001Mask |= 0x8
|
zb0001Mask |= 0x10
|
||||||
}
|
}
|
||||||
// variable map header, size zb0001Len
|
// variable map header, size zb0001Len
|
||||||
err = en.Append(0x80 | uint8(zb0001Len))
|
err = en.Append(0x80 | uint8(zb0001Len))
|
||||||
@@ -377,7 +383,17 @@ func (z *dataUsageCacheInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "NextCycle")
|
err = msgp.WrapError(err, "NextCycle")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (zb0001Mask & 0x8) == 0 { // if not empty
|
// write "SkipHealing"
|
||||||
|
err = en.Append(0xab, 0x53, 0x6b, 0x69, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.SkipHealing)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SkipHealing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x10) == 0 { // if not empty
|
||||||
// write "BloomFilter"
|
// write "BloomFilter"
|
||||||
err = en.Append(0xab, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72)
|
err = en.Append(0xab, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -396,11 +412,11 @@ func (z *dataUsageCacheInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
func (z *dataUsageCacheInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *dataUsageCacheInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// omitempty: check for empty values
|
// omitempty: check for empty values
|
||||||
zb0001Len := uint32(4)
|
zb0001Len := uint32(5)
|
||||||
var zb0001Mask uint8 /* 4 bits */
|
var zb0001Mask uint8 /* 5 bits */
|
||||||
if z.BloomFilter == nil {
|
if z.BloomFilter == nil {
|
||||||
zb0001Len--
|
zb0001Len--
|
||||||
zb0001Mask |= 0x8
|
zb0001Mask |= 0x10
|
||||||
}
|
}
|
||||||
// variable map header, size zb0001Len
|
// variable map header, size zb0001Len
|
||||||
o = append(o, 0x80|uint8(zb0001Len))
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
@@ -416,7 +432,10 @@ func (z *dataUsageCacheInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "NextCycle"
|
// string "NextCycle"
|
||||||
o = append(o, 0xa9, 0x4e, 0x65, 0x78, 0x74, 0x43, 0x79, 0x63, 0x6c, 0x65)
|
o = append(o, 0xa9, 0x4e, 0x65, 0x78, 0x74, 0x43, 0x79, 0x63, 0x6c, 0x65)
|
||||||
o = msgp.AppendUint32(o, z.NextCycle)
|
o = msgp.AppendUint32(o, z.NextCycle)
|
||||||
if (zb0001Mask & 0x8) == 0 { // if not empty
|
// string "SkipHealing"
|
||||||
|
o = append(o, 0xab, 0x53, 0x6b, 0x69, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67)
|
||||||
|
o = msgp.AppendBool(o, z.SkipHealing)
|
||||||
|
if (zb0001Mask & 0x10) == 0 { // if not empty
|
||||||
// string "BloomFilter"
|
// string "BloomFilter"
|
||||||
o = append(o, 0xab, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72)
|
o = append(o, 0xab, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72)
|
||||||
o = msgp.AppendBytes(o, z.BloomFilter)
|
o = msgp.AppendBytes(o, z.BloomFilter)
|
||||||
@@ -460,6 +479,12 @@ func (z *dataUsageCacheInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "NextCycle")
|
err = msgp.WrapError(err, "NextCycle")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "SkipHealing":
|
||||||
|
z.SkipHealing, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SkipHealing")
|
||||||
|
return
|
||||||
|
}
|
||||||
case "BloomFilter":
|
case "BloomFilter":
|
||||||
z.BloomFilter, bts, err = msgp.ReadBytesBytes(bts, z.BloomFilter)
|
z.BloomFilter, bts, err = msgp.ReadBytesBytes(bts, z.BloomFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -480,7 +505,278 @@ func (z *dataUsageCacheInfo) 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 *dataUsageCacheInfo) Msgsize() (s int) {
|
func (z *dataUsageCacheInfo) Msgsize() (s int) {
|
||||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 11 + msgp.TimeSize + 10 + msgp.Uint32Size + 12 + msgp.BytesPrefixSize + len(z.BloomFilter)
|
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 11 + msgp.TimeSize + 10 + msgp.Uint32Size + 12 + msgp.BoolSize + 12 + msgp.BytesPrefixSize + len(z.BloomFilter)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *dataUsageCacheV2) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Info":
|
||||||
|
err = z.Info.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Disks":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disks")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Disks) >= int(zb0002) {
|
||||||
|
z.Disks = (z.Disks)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Disks = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Disks {
|
||||||
|
z.Disks[za0001], err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disks", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Cache":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Cache == nil {
|
||||||
|
z.Cache = make(map[string]dataUsageEntryV2, zb0003)
|
||||||
|
} else if len(z.Cache) > 0 {
|
||||||
|
for key := range z.Cache {
|
||||||
|
delete(z.Cache, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0003 > 0 {
|
||||||
|
zb0003--
|
||||||
|
var za0002 string
|
||||||
|
var za0003 dataUsageEntryV2
|
||||||
|
za0002, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = za0003.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Cache[za0002] = za0003
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *dataUsageCacheV2) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 3
|
||||||
|
// write "Info"
|
||||||
|
err = en.Append(0x83, 0xa4, 0x49, 0x6e, 0x66, 0x6f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = z.Info.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Disks"
|
||||||
|
err = en.Append(0xa5, 0x44, 0x69, 0x73, 0x6b, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.Disks)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disks")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.Disks {
|
||||||
|
err = en.WriteString(z.Disks[za0001])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disks", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "Cache"
|
||||||
|
err = en.Append(0xa5, 0x43, 0x61, 0x63, 0x68, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteMapHeader(uint32(len(z.Cache)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0002, za0003 := range z.Cache {
|
||||||
|
err = en.WriteString(za0002)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = za0003.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *dataUsageCacheV2) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 3
|
||||||
|
// string "Info"
|
||||||
|
o = append(o, 0x83, 0xa4, 0x49, 0x6e, 0x66, 0x6f)
|
||||||
|
o, err = z.Info.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "Disks"
|
||||||
|
o = append(o, 0xa5, 0x44, 0x69, 0x73, 0x6b, 0x73)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Disks)))
|
||||||
|
for za0001 := range z.Disks {
|
||||||
|
o = msgp.AppendString(o, z.Disks[za0001])
|
||||||
|
}
|
||||||
|
// string "Cache"
|
||||||
|
o = append(o, 0xa5, 0x43, 0x61, 0x63, 0x68, 0x65)
|
||||||
|
o = msgp.AppendMapHeader(o, uint32(len(z.Cache)))
|
||||||
|
for za0002, za0003 := range z.Cache {
|
||||||
|
o = msgp.AppendString(o, za0002)
|
||||||
|
o, err = za0003.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *dataUsageCacheV2) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Info":
|
||||||
|
bts, err = z.Info.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Disks":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disks")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Disks) >= int(zb0002) {
|
||||||
|
z.Disks = (z.Disks)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Disks = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Disks {
|
||||||
|
z.Disks[za0001], bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Disks", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Cache":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Cache == nil {
|
||||||
|
z.Cache = make(map[string]dataUsageEntryV2, zb0003)
|
||||||
|
} else if len(z.Cache) > 0 {
|
||||||
|
for key := range z.Cache {
|
||||||
|
delete(z.Cache, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0003 > 0 {
|
||||||
|
var za0002 string
|
||||||
|
var za0003 dataUsageEntryV2
|
||||||
|
zb0003--
|
||||||
|
za0002, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bts, err = za0003.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Cache", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Cache[za0002] = za0003
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *dataUsageCacheV2) Msgsize() (s int) {
|
||||||
|
s = 1 + 5 + z.Info.Msgsize() + 6 + msgp.ArrayHeaderSize
|
||||||
|
for za0001 := range z.Disks {
|
||||||
|
s += msgp.StringPrefixSize + len(z.Disks[za0001])
|
||||||
|
}
|
||||||
|
s += 6 + msgp.MapHeaderSize
|
||||||
|
if z.Cache != nil {
|
||||||
|
for za0002, za0003 := range z.Cache {
|
||||||
|
_ = za0003
|
||||||
|
s += msgp.StringPrefixSize + len(za0002) + za0003.Msgsize()
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,6 +1001,163 @@ func (z *dataUsageEntry) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *dataUsageEntryV2) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0001 != 4 {
|
||||||
|
err = msgp.ArrayError{Wanted: 4, Got: zb0001}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Size, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Objects, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0002 != uint32(dataUsageBucketLen) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLen), Got: zb0002}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.ObjSizes {
|
||||||
|
z.ObjSizes[za0001], err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = z.Children.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Children")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *dataUsageEntryV2) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// array header, size 4
|
||||||
|
err = en.Append(0x94)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.Size)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.Objects)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(dataUsageBucketLen))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.ObjSizes {
|
||||||
|
err = en.WriteUint64(z.ObjSizes[za0001])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = z.Children.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Children")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *dataUsageEntryV2) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// array header, size 4
|
||||||
|
o = append(o, 0x94)
|
||||||
|
o = msgp.AppendInt64(o, z.Size)
|
||||||
|
o = msgp.AppendUint64(o, z.Objects)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
|
||||||
|
for za0001 := range z.ObjSizes {
|
||||||
|
o = msgp.AppendUint64(o, z.ObjSizes[za0001])
|
||||||
|
}
|
||||||
|
o, err = z.Children.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Children")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *dataUsageEntryV2) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0001 != 4 {
|
||||||
|
err = msgp.ArrayError{Wanted: 4, Got: zb0001}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Size, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Objects, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0002 != uint32(dataUsageBucketLen) {
|
||||||
|
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLen), Got: zb0002}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.ObjSizes {
|
||||||
|
z.ObjSizes[za0001], bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bts, err = z.Children.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Children")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *dataUsageEntryV2) Msgsize() (s int) {
|
||||||
|
s = 1 + msgp.Int64Size + msgp.Uint64Size + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + z.Children.Msgsize()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *dataUsageHash) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *dataUsageHash) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -235,6 +235,119 @@ func BenchmarkDecodedataUsageCacheInfo(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshaldataUsageCacheV2(t *testing.T) {
|
||||||
|
v := dataUsageCacheV2{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgdataUsageCacheV2(b *testing.B) {
|
||||||
|
v := dataUsageCacheV2{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgdataUsageCacheV2(b *testing.B) {
|
||||||
|
v := dataUsageCacheV2{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshaldataUsageCacheV2(b *testing.B) {
|
||||||
|
v := dataUsageCacheV2{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodedataUsageCacheV2(t *testing.T) {
|
||||||
|
v := dataUsageCacheV2{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodedataUsageCacheV2 Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := dataUsageCacheV2{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodedataUsageCacheV2(b *testing.B) {
|
||||||
|
v := dataUsageCacheV2{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodedataUsageCacheV2(b *testing.B) {
|
||||||
|
v := dataUsageCacheV2{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshaldataUsageEntry(t *testing.T) {
|
func TestMarshalUnmarshaldataUsageEntry(t *testing.T) {
|
||||||
v := dataUsageEntry{}
|
v := dataUsageEntry{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
@@ -348,6 +461,119 @@ func BenchmarkDecodedataUsageEntry(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshaldataUsageEntryV2(t *testing.T) {
|
||||||
|
v := dataUsageEntryV2{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgdataUsageEntryV2(b *testing.B) {
|
||||||
|
v := dataUsageEntryV2{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgdataUsageEntryV2(b *testing.B) {
|
||||||
|
v := dataUsageEntryV2{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshaldataUsageEntryV2(b *testing.B) {
|
||||||
|
v := dataUsageEntryV2{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodedataUsageEntryV2(t *testing.T) {
|
||||||
|
v := dataUsageEntryV2{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodedataUsageEntryV2 Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := dataUsageEntryV2{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodedataUsageEntryV2(b *testing.B) {
|
||||||
|
v := dataUsageEntryV2{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodedataUsageEntryV2(b *testing.B) {
|
||||||
|
v := dataUsageEntryV2{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalsizeHistogram(t *testing.T) {
|
func TestMarshalUnmarshalsizeHistogram(t *testing.T) {
|
||||||
v := sizeHistogram{}
|
v := sizeHistogram{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
|||||||
+6
-7
@@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -46,12 +47,12 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
size := int64(len(dataUsageJSON))
|
size := int64(len(dataUsageJSON))
|
||||||
r, err := hash.NewReader(bytes.NewReader(dataUsageJSON), size, "", "", size, false)
|
r, err := hash.NewReader(bytes.NewReader(dataUsageJSON), size, "", "", size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageObjName, NewPutObjReader(r, nil, nil), ObjectOptions{})
|
_, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageObjName, NewPutObjReader(r), ObjectOptions{})
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -59,20 +60,18 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
|
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) {
|
||||||
var dataUsageInfoJSON bytes.Buffer
|
r, err := objAPI.GetObjectNInfo(ctx, dataUsageBucket, dataUsageObjName, nil, http.Header{}, readLock, ObjectOptions{})
|
||||||
|
|
||||||
err := objAPI.GetObject(ctx, dataUsageBucket, dataUsageObjName, 0, -1, &dataUsageInfoJSON, "", ObjectOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isErrObjectNotFound(err) || isErrBucketNotFound(err) {
|
if isErrObjectNotFound(err) || isErrBucketNotFound(err) {
|
||||||
return DataUsageInfo{}, nil
|
return DataUsageInfo{}, nil
|
||||||
}
|
}
|
||||||
return DataUsageInfo{}, toObjectErr(err, dataUsageBucket, dataUsageObjName)
|
return DataUsageInfo{}, toObjectErr(err, dataUsageBucket, dataUsageObjName)
|
||||||
}
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
var dataUsageInfo DataUsageInfo
|
var dataUsageInfo DataUsageInfo
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
err = json.Unmarshal(dataUsageInfoJSON.Bytes(), &dataUsageInfo)
|
if err = json.NewDecoder(r).Decode(&dataUsageInfo); err != nil {
|
||||||
if err != nil {
|
|
||||||
return DataUsageInfo{}, err
|
return DataUsageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -103,7 +102,6 @@ func (m *cacheMeta) ToObjectInfo(bucket, object string) (o ObjectInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We set file info only if its valid.
|
// We set file info only if its valid.
|
||||||
o.ModTime = m.Stat.ModTime
|
|
||||||
o.Size = m.Stat.Size
|
o.Size = m.Stat.Size
|
||||||
o.ETag = extractETag(m.Meta)
|
o.ETag = extractETag(m.Meta)
|
||||||
o.ContentType = m.Meta["content-type"]
|
o.ContentType = m.Meta["content-type"]
|
||||||
@@ -122,6 +120,12 @@ func (m *cacheMeta) ToObjectInfo(bucket, object string) (o ObjectInfo) {
|
|||||||
o.Expires = t.UTC()
|
o.Expires = t.UTC()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if mtime, ok := m.Meta["last-modified"]; ok {
|
||||||
|
if t, e = time.Parse(http.TimeFormat, mtime); e == nil {
|
||||||
|
o.ModTime = t.UTC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// etag/md5Sum has already been extracted. We need to
|
// etag/md5Sum has already been extracted. We need to
|
||||||
// remove to avoid it from appearing as part of user-defined metadata
|
// remove to avoid it from appearing as part of user-defined metadata
|
||||||
o.UserDefined = cleanMetadata(m.Meta)
|
o.UserDefined = cleanMetadata(m.Meta)
|
||||||
@@ -264,10 +268,6 @@ func (c *diskCache) toClear() uint64 {
|
|||||||
return bytesToClear(int64(di.Total), int64(di.Free), uint64(c.quotaPct), uint64(c.lowWatermark), uint64(c.highWatermark))
|
return bytesToClear(int64(di.Total), int64(di.Free), uint64(c.quotaPct), uint64(c.lowWatermark), uint64(c.highWatermark))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
errDoneForNow = errors.New("done for now")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *diskCache) purgeWait(ctx context.Context) {
|
func (c *diskCache) purgeWait(ctx context.Context) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -377,7 +377,7 @@ func (c *diskCache) purge(ctx context.Context) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := readDirFilterFn(c.dir, filterFn); err != nil {
|
if err := readDirFn(c.dir, filterFn); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -506,9 +506,7 @@ func (c *diskCache) statCache(ctx context.Context, cacheObjPath string) (meta *c
|
|||||||
}
|
}
|
||||||
// get metadata of part.1 if full file has been cached.
|
// get metadata of part.1 if full file has been cached.
|
||||||
partial = true
|
partial = true
|
||||||
fi, err := os.Stat(pathJoin(cacheObjPath, cacheDataFile))
|
if _, err := os.Stat(pathJoin(cacheObjPath, cacheDataFile)); err == nil {
|
||||||
if err == nil {
|
|
||||||
meta.Stat.ModTime = atime.Get(fi)
|
|
||||||
partial = false
|
partial = false
|
||||||
}
|
}
|
||||||
return meta, partial, meta.Hits, nil
|
return meta, partial, meta.Hits, nil
|
||||||
@@ -570,7 +568,6 @@ func (c *diskCache) saveMetadata(ctx context.Context, bucket, object string, met
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.Stat.Size = actualSize
|
m.Stat.Size = actualSize
|
||||||
m.Stat.ModTime = UTCNow()
|
|
||||||
if !incHitsOnly {
|
if !incHitsOnly {
|
||||||
// reset meta
|
// reset meta
|
||||||
m.Meta = meta
|
m.Meta = meta
|
||||||
@@ -1023,7 +1020,7 @@ func (c *diskCache) scanCacheWritebackFailures(ctx context.Context) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := readDirFilterFn(c.dir, filterFn); err != nil {
|
if err := readDirFn(c.dir, filterFn); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ type CacheDiskStats struct {
|
|||||||
Dir string
|
Dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUsageLevelString gets the string representation for the usage level.
|
||||||
|
func (c *CacheDiskStats) GetUsageLevelString() (u string) {
|
||||||
|
if atomic.LoadInt32(&c.UsageState) == 0 {
|
||||||
|
return "low"
|
||||||
|
}
|
||||||
|
return "high"
|
||||||
|
}
|
||||||
|
|
||||||
// CacheStats - represents bytes served from cache,
|
// CacheStats - represents bytes served from cache,
|
||||||
// cache hits and cache misses.
|
// cache hits and cache misses.
|
||||||
type CacheStats struct {
|
type CacheStats struct {
|
||||||
|
|||||||
@@ -173,7 +173,11 @@ func backendDownError(err error) bool {
|
|||||||
|
|
||||||
// IsCacheable returns if the object should be saved in the cache.
|
// IsCacheable returns if the object should be saved in the cache.
|
||||||
func (o ObjectInfo) IsCacheable() bool {
|
func (o ObjectInfo) IsCacheable() bool {
|
||||||
return !crypto.IsEncrypted(o.UserDefined) || globalCacheKMS != nil
|
if globalCacheKMS != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, ok := crypto.IsEncrypted(o.UserDefined)
|
||||||
|
return !ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// reads file cached on disk from offset upto length
|
// reads file cached on disk from offset upto length
|
||||||
|
|||||||
+3
-2
@@ -200,6 +200,7 @@ func getMetadata(objInfo ObjectInfo) map[string]string {
|
|||||||
if !objInfo.Expires.Equal(timeSentinel) {
|
if !objInfo.Expires.Equal(timeSentinel) {
|
||||||
metadata["expires"] = objInfo.Expires.Format(http.TimeFormat)
|
metadata["expires"] = objInfo.Expires.Format(http.TimeFormat)
|
||||||
}
|
}
|
||||||
|
metadata["last-modified"] = objInfo.ModTime.Format(http.TimeFormat)
|
||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
metadata[k] = v
|
metadata[k] = v
|
||||||
}
|
}
|
||||||
@@ -704,14 +705,14 @@ func (c *cacheObjects) uploadObject(ctx context.Context, oi ObjectInfo) {
|
|||||||
if st == CommitComplete || st.String() == "" {
|
if st == CommitComplete || st.String() == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size, globalCLIContext.StrictS3Compat)
|
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var opts ObjectOptions
|
var opts ObjectOptions
|
||||||
opts.UserDefined = make(map[string]string)
|
opts.UserDefined = make(map[string]string)
|
||||||
opts.UserDefined[xhttp.ContentMD5] = oi.UserDefined["content-md5"]
|
opts.UserDefined[xhttp.ContentMD5] = oi.UserDefined["content-md5"]
|
||||||
objInfo, err := c.InnerPutObjectFn(ctx, oi.Bucket, oi.Name, NewPutObjReader(hashReader, nil, nil), opts)
|
objInfo, err := c.InnerPutObjectFn(ctx, oi.Bucket, oi.Name, NewPutObjReader(hashReader), opts)
|
||||||
wbCommitStatus := CommitComplete
|
wbCommitStatus := CommitComplete
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wbCommitStatus = CommitFailed
|
wbCommitStatus = CommitFailed
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests ToObjectInfo function.
|
// Tests ToObjectInfo function.
|
||||||
@@ -27,7 +28,7 @@ func TestCacheMetadataObjInfo(t *testing.T) {
|
|||||||
if objInfo.Size != 0 {
|
if objInfo.Size != 0 {
|
||||||
t.Fatal("Unexpected object info value for Size", objInfo.Size)
|
t.Fatal("Unexpected object info value for Size", objInfo.Size)
|
||||||
}
|
}
|
||||||
if !objInfo.ModTime.Equal(timeSentinel) {
|
if !objInfo.ModTime.Equal(time.Time{}) {
|
||||||
t.Fatal("Unexpected object info value for ModTime ", objInfo.ModTime)
|
t.Fatal("Unexpected object info value for ModTime ", objInfo.ModTime)
|
||||||
}
|
}
|
||||||
if objInfo.IsDir {
|
if objInfo.IsDir {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
func (api objectAPIHandlers) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketWebsite")
|
ctx := newContext(r, w, "GetBucketWebsite")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketWebsite", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -64,7 +64,7 @@ func (api objectAPIHandlers) GetBucketWebsiteHandler(w http.ResponseWriter, r *h
|
|||||||
func (api objectAPIHandlers) GetBucketAccelerateHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketAccelerateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketAccelerate")
|
ctx := newContext(r, w, "GetBucketAccelerate")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketAccelerate", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -97,7 +97,7 @@ func (api objectAPIHandlers) GetBucketAccelerateHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketRequestPayment")
|
ctx := newContext(r, w, "GetBucketRequestPayment")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketRequestPayment", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -131,7 +131,7 @@ func (api objectAPIHandlers) GetBucketRequestPaymentHandler(w http.ResponseWrite
|
|||||||
func (api objectAPIHandlers) GetBucketLoggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketLoggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketLogging")
|
ctx := newContext(r, w, "GetBucketLogging")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketLogging", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
@@ -170,7 +170,7 @@ func (api objectAPIHandlers) DeleteBucketWebsiteHandler(w http.ResponseWriter, r
|
|||||||
func (api objectAPIHandlers) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketCors")
|
ctx := newContext(r, w, "GetBucketCors")
|
||||||
|
|
||||||
defer logger.AuditLog(w, r, "GetBucketCors", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|||||||
+24
-37
@@ -20,6 +20,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -33,7 +34,6 @@ import (
|
|||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
sha256 "github.com/minio/sha256-simd"
|
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -247,56 +247,45 @@ func EncryptRequest(content io.Reader, r *http.Request, bucket, object string, m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
|
func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
|
||||||
switch {
|
switch kind, _ := crypto.IsEncrypted(metadata); kind {
|
||||||
default:
|
case crypto.S3:
|
||||||
return nil, errObjectTampered
|
var KMS crypto.KMS = GlobalKMS
|
||||||
case crypto.S3.IsEncrypted(metadata) && isCacheEncrypted(metadata):
|
if isCacheEncrypted(metadata) {
|
||||||
if globalCacheKMS == nil {
|
KMS = globalCacheKMS
|
||||||
|
}
|
||||||
|
if KMS == nil {
|
||||||
return nil, errKMSNotConfigured
|
return nil, errKMSNotConfigured
|
||||||
}
|
}
|
||||||
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata)
|
objectKey, err := crypto.S3.UnsealObjectKey(KMS, metadata, bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var objectKey crypto.ObjectKey
|
|
||||||
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return objectKey[:], nil
|
return objectKey[:], nil
|
||||||
case crypto.S3.IsEncrypted(metadata):
|
case crypto.S3KMS:
|
||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
return nil, errKMSNotConfigured
|
return nil, errKMSNotConfigured
|
||||||
}
|
}
|
||||||
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata)
|
objectKey, err := crypto.S3KMS.UnsealObjectKey(GlobalKMS, metadata, bucket, object)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extKey, err := GlobalKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var objectKey crypto.ObjectKey
|
|
||||||
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return objectKey[:], nil
|
return objectKey[:], nil
|
||||||
case crypto.SSEC.IsEncrypted(metadata):
|
case crypto.SSEC:
|
||||||
var extKey [32]byte
|
|
||||||
copy(extKey[:], key)
|
|
||||||
sealedKey, err := crypto.SSEC.ParseMetadata(metadata)
|
sealedKey, err := crypto.SSEC.ParseMetadata(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var objectKey crypto.ObjectKey
|
var (
|
||||||
|
objectKey crypto.ObjectKey
|
||||||
|
extKey [32]byte
|
||||||
|
)
|
||||||
|
copy(extKey[:], key)
|
||||||
if err = objectKey.Unseal(extKey, sealedKey, crypto.SSEC.String(), bucket, object); err != nil {
|
if err = objectKey.Unseal(extKey, sealedKey, crypto.SSEC.String(), bucket, object); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return objectKey[:], nil
|
return objectKey[:], nil
|
||||||
|
default:
|
||||||
|
return nil, errObjectTampered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,9 +342,7 @@ func newDecryptReaderWithObjectKey(client io.Reader, objectEncryptionKey []byte,
|
|||||||
|
|
||||||
// DecryptBlocksRequestR - same as DecryptBlocksRequest but with a
|
// DecryptBlocksRequestR - same as DecryptBlocksRequest but with a
|
||||||
// reader
|
// reader
|
||||||
func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, offset,
|
func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, seqNumber uint32, partStart int, oi ObjectInfo, copySource bool) (io.Reader, error) {
|
||||||
length int64, seqNumber uint32, partStart int, oi ObjectInfo, copySource bool) (
|
|
||||||
io.Reader, error) {
|
|
||||||
|
|
||||||
bucket, object := oi.Bucket, oi.Name
|
bucket, object := oi.Bucket, oi.Name
|
||||||
// Single part case
|
// Single part case
|
||||||
@@ -518,7 +505,7 @@ func (d *DecryptBlocksReader) Read(p []byte) (int, error) {
|
|||||||
// It returns an error if the object is not encrypted or marked as encrypted
|
// It returns an error if the object is not encrypted or marked as encrypted
|
||||||
// but has an invalid size.
|
// but has an invalid size.
|
||||||
func (o *ObjectInfo) DecryptedSize() (int64, error) {
|
func (o *ObjectInfo) DecryptedSize() (int64, error) {
|
||||||
if !crypto.IsEncrypted(o.UserDefined) {
|
if _, ok := crypto.IsEncrypted(o.UserDefined); !ok {
|
||||||
return 0, errors.New("Cannot compute decrypted size of an unencrypted object")
|
return 0, errors.New("Cannot compute decrypted size of an unencrypted object")
|
||||||
}
|
}
|
||||||
if !isEncryptedMultipart(*o) {
|
if !isEncryptedMultipart(*o) {
|
||||||
@@ -651,7 +638,7 @@ func tryDecryptETag(key []byte, encryptedETag string, ssec bool) string {
|
|||||||
// requested range starts, along with the DARE sequence number within
|
// requested range starts, along with the DARE sequence number within
|
||||||
// that part. For single part objects, the partStart will be 0.
|
// that part. For single part objects, the partStart will be 0.
|
||||||
func (o *ObjectInfo) GetDecryptedRange(rs *HTTPRangeSpec) (encOff, encLength, skipLen int64, seqNumber uint32, partStart int, err error) {
|
func (o *ObjectInfo) GetDecryptedRange(rs *HTTPRangeSpec) (encOff, encLength, skipLen int64, seqNumber uint32, partStart int, err error) {
|
||||||
if !crypto.IsEncrypted(o.UserDefined) {
|
if _, ok := crypto.IsEncrypted(o.UserDefined); !ok {
|
||||||
err = errors.New("Object is not encrypted")
|
err = errors.New("Object is not encrypted")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -798,7 +785,7 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted = crypto.IsEncrypted(info.UserDefined)
|
_, encrypted = crypto.IsEncrypted(info.UserDefined)
|
||||||
if !encrypted && crypto.SSEC.IsRequested(headers) && r.Header.Get(xhttp.AmzCopySource) == "" {
|
if !encrypted && crypto.SSEC.IsRequested(headers) && r.Header.Get(xhttp.AmzCopySource) == "" {
|
||||||
return false, errInvalidEncryptionParameters
|
return false, errInvalidEncryptionParameters
|
||||||
}
|
}
|
||||||
@@ -820,7 +807,7 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e
|
|||||||
return encrypted, err
|
return encrypted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.IsEncrypted(info.UserDefined) && !crypto.IsMultiPart(info.UserDefined) {
|
if _, ok := crypto.IsEncrypted(info.UserDefined); ok && !crypto.IsMultiPart(info.UserDefined) {
|
||||||
info.ETag = getDecryptedETag(headers, *info, false)
|
info.ETag = getDecryptedETag(headers, *info, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func TestDecryptObjectInfo(t *testing.T) {
|
|||||||
for i, test := range decryptObjectInfoTests {
|
for i, test := range decryptObjectInfoTests {
|
||||||
if encrypted, err := DecryptObjectInfo(&test.info, test.request); err != test.expErr {
|
if encrypted, err := DecryptObjectInfo(&test.info, test.request); err != test.expErr {
|
||||||
t.Errorf("Test %d: Decryption returned wrong error code: got %d , want %d", i, err, test.expErr)
|
t.Errorf("Test %d: Decryption returned wrong error code: got %d , want %d", i, err, test.expErr)
|
||||||
} else if enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted {
|
} else if _, enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted {
|
||||||
t.Errorf("Test %d: Decryption thinks object is encrypted but it is not", i)
|
t.Errorf("Test %d: Decryption thinks object is encrypted but it is not", i)
|
||||||
} else if !encrypted && enc != encrypted {
|
} else if !encrypted && enc != encrypted {
|
||||||
t.Errorf("Test %d: Decryption thinks object is not encrypted but it is", i)
|
t.Errorf("Test %d: Decryption thinks object is not encrypted but it is", i)
|
||||||
|
|||||||
+15
-19
@@ -276,7 +276,16 @@ func parseEndpointSet(customSetDriveCount uint64, args ...string) (ep endpointSe
|
|||||||
// specific set size.
|
// specific set size.
|
||||||
// For example: {1...64} is divided into 4 sets each of size 16.
|
// For example: {1...64} is divided into 4 sets each of size 16.
|
||||||
// This applies to even distributed setup syntax as well.
|
// This applies to even distributed setup syntax as well.
|
||||||
func GetAllSets(customSetDriveCount uint64, args ...string) ([][]string, error) {
|
func GetAllSets(args ...string) ([][]string, error) {
|
||||||
|
var customSetDriveCount uint64
|
||||||
|
if v := env.Get(EnvErasureSetDriveCount, ""); v != "" {
|
||||||
|
driveCount, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, config.ErrInvalidErasureSetSize(err)
|
||||||
|
}
|
||||||
|
customSetDriveCount = uint64(driveCount)
|
||||||
|
}
|
||||||
|
|
||||||
var setArgs [][]string
|
var setArgs [][]string
|
||||||
if !ellipses.HasEllipses(args...) {
|
if !ellipses.HasEllipses(args...) {
|
||||||
var setIndexes [][]uint64
|
var setIndexes [][]uint64
|
||||||
@@ -335,16 +344,8 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
return nil, -1, errInvalidArgument
|
return nil, -1, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
var setDriveCount int
|
|
||||||
if v := env.Get(EnvErasureSetDriveCount, ""); v != "" {
|
|
||||||
setDriveCount, err = strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, config.ErrInvalidErasureSetSize(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ellipses.HasEllipses(args...) {
|
if !ellipses.HasEllipses(args...) {
|
||||||
setArgs, err := GetAllSets(uint64(setDriveCount), args...)
|
setArgs, err := GetAllSets(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
@@ -352,7 +353,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
endpointServerPools = append(endpointServerPools, ZoneEndpoints{
|
endpointServerPools = append(endpointServerPools, PoolEndpoints{
|
||||||
SetCount: len(setArgs),
|
SetCount: len(setArgs),
|
||||||
DrivesPerSet: len(setArgs[0]),
|
DrivesPerSet: len(setArgs[0]),
|
||||||
Endpoints: endpointList,
|
Endpoints: endpointList,
|
||||||
@@ -363,18 +364,16 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
|
|
||||||
var foundPrevLocal bool
|
var foundPrevLocal bool
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
setArgs, err := GetAllSets(uint64(setDriveCount), arg)
|
setArgs, err := GetAllSets(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointList, gotSetupType, err := CreateEndpoints(serverAddr, foundPrevLocal, setArgs...)
|
endpointList, gotSetupType, err := CreateEndpoints(serverAddr, foundPrevLocal, setArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
if setDriveCount != 0 && setDriveCount != len(setArgs[0]) {
|
if err = endpointServerPools.Add(PoolEndpoints{
|
||||||
return nil, -1, fmt.Errorf("All serverPools should have same drive per set ratio - expected %d, got %d", setDriveCount, len(setArgs[0]))
|
|
||||||
}
|
|
||||||
if err = endpointServerPools.Add(ZoneEndpoints{
|
|
||||||
SetCount: len(setArgs),
|
SetCount: len(setArgs),
|
||||||
DrivesPerSet: len(setArgs[0]),
|
DrivesPerSet: len(setArgs[0]),
|
||||||
Endpoints: endpointList,
|
Endpoints: endpointList,
|
||||||
@@ -382,9 +381,6 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
|||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
foundPrevLocal = endpointList.atleastOneEndpointLocal()
|
foundPrevLocal = endpointList.atleastOneEndpointLocal()
|
||||||
if setDriveCount == 0 {
|
|
||||||
setDriveCount = len(setArgs[0])
|
|
||||||
}
|
|
||||||
if setupType == UnknownSetupType {
|
if setupType == UnknownSetupType {
|
||||||
setupType = gotSetupType
|
setupType = gotSetupType
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -196,20 +196,20 @@ func NewEndpoint(arg string) (ep Endpoint, e error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZoneEndpoints represent endpoints in a given zone
|
// PoolEndpoints represent endpoints in a given pool
|
||||||
// along with its setCount and setDriveCount.
|
// along with its setCount and setDriveCount.
|
||||||
type ZoneEndpoints struct {
|
type PoolEndpoints struct {
|
||||||
SetCount int
|
SetCount int
|
||||||
DrivesPerSet int
|
DrivesPerSet int
|
||||||
Endpoints Endpoints
|
Endpoints Endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndpointServerPools - list of list of endpoints
|
// EndpointServerPools - list of list of endpoints
|
||||||
type EndpointServerPools []ZoneEndpoints
|
type EndpointServerPools []PoolEndpoints
|
||||||
|
|
||||||
// GetLocalZoneIdx returns the zone which endpoint belongs to locally.
|
// GetLocalPoolIdx returns the pool which endpoint belongs to locally.
|
||||||
// if ep is remote this code will return -1 zoneIndex
|
// if ep is remote this code will return -1 poolIndex
|
||||||
func (l EndpointServerPools) GetLocalZoneIdx(ep Endpoint) int {
|
func (l EndpointServerPools) GetLocalPoolIdx(ep Endpoint) int {
|
||||||
for i, zep := range l {
|
for i, zep := range l {
|
||||||
for _, cep := range zep.Endpoints {
|
for _, cep := range zep.Endpoints {
|
||||||
if cep.IsLocal && ep.IsLocal {
|
if cep.IsLocal && ep.IsLocal {
|
||||||
@@ -222,8 +222,8 @@ func (l EndpointServerPools) GetLocalZoneIdx(ep Endpoint) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add add zone endpoints
|
// Add add pool endpoints
|
||||||
func (l *EndpointServerPools) Add(zeps ZoneEndpoints) error {
|
func (l *EndpointServerPools) Add(zeps PoolEndpoints) error {
|
||||||
existSet := set.NewStringSet()
|
existSet := set.NewStringSet()
|
||||||
for _, zep := range *l {
|
for _, zep := range *l {
|
||||||
for _, ep := range zep.Endpoints {
|
for _, ep := range zep.Endpoints {
|
||||||
@@ -478,8 +478,8 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
|
|||||||
// participate atleast one disk and be local.
|
// participate atleast one disk and be local.
|
||||||
//
|
//
|
||||||
// In special cases for replica set with expanded
|
// In special cases for replica set with expanded
|
||||||
// zone setups we need to make sure to provide
|
// pool setups we need to make sure to provide
|
||||||
// value of foundPrevLocal from zone1 if we already
|
// value of foundPrevLocal from pool1 if we already
|
||||||
// found a local setup. Only if we haven't found
|
// found a local setup. Only if we haven't found
|
||||||
// previous local we continue to wait to look for
|
// previous local we continue to wait to look for
|
||||||
// atleast one local.
|
// atleast one local.
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ func TestGetLocalPeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
zendpoints := mustGetZoneEndpoints(testCase.endpointArgs...)
|
zendpoints := mustGetPoolEndpoints(testCase.endpointArgs...)
|
||||||
if !zendpoints[0].Endpoints[0].IsLocal {
|
if !zendpoints[0].Endpoints[0].IsLocal {
|
||||||
if err := zendpoints[0].Endpoints.UpdateIsLocal(false); err != nil {
|
if err := zendpoints[0].Endpoints.UpdateIsLocal(false); err != nil {
|
||||||
t.Fatalf("error: expected = <nil>, got = %v", err)
|
t.Fatalf("error: expected = <nil>, got = %v", err)
|
||||||
@@ -390,7 +390,7 @@ func TestGetRemotePeers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
zendpoints := mustGetZoneEndpoints(testCase.endpointArgs...)
|
zendpoints := mustGetPoolEndpoints(testCase.endpointArgs...)
|
||||||
if !zendpoints[0].Endpoints[0].IsLocal {
|
if !zendpoints[0].Endpoints[0].IsLocal {
|
||||||
if err := zendpoints[0].Endpoints.UpdateIsLocal(false); err != nil {
|
if err := zendpoints[0].Endpoints.UpdateIsLocal(false); err != nil {
|
||||||
t.Errorf("error: expected = <nil>, got = %v", err)
|
t.Errorf("error: expected = <nil>, got = %v", err)
|
||||||
|
|||||||
+7
-89
@@ -18,8 +18,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"github.com/minio/minio/pkg/sync/errgroup"
|
||||||
@@ -38,65 +36,6 @@ func (er erasureObjects) getLoadBalancedLocalDisks() (newDisks []StorageAPI) {
|
|||||||
return newDisks
|
return newDisks
|
||||||
}
|
}
|
||||||
|
|
||||||
type sortSlices struct {
|
|
||||||
disks []StorageAPI
|
|
||||||
infos []DiskInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortByOther sortSlices
|
|
||||||
|
|
||||||
func (sbo sortByOther) Len() int {
|
|
||||||
return len(sbo.disks)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sbo sortByOther) Swap(i, j int) {
|
|
||||||
sbo.disks[i], sbo.disks[j] = sbo.disks[j], sbo.disks[i]
|
|
||||||
sbo.infos[i], sbo.infos[j] = sbo.infos[j], sbo.infos[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sbo sortByOther) Less(i, j int) bool {
|
|
||||||
return sbo.infos[i].UsedInodes < sbo.infos[j].UsedInodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (er erasureObjects) getOnlineDisksSortedByUsedInodes() (newDisks []StorageAPI) {
|
|
||||||
disks := er.getDisks()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var mu sync.Mutex
|
|
||||||
var infos []DiskInfo
|
|
||||||
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
|
|
||||||
i := i
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if disks[i-1] == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
di, err := disks[i-1].DiskInfo(context.Background())
|
|
||||||
if err != nil || di.Healing {
|
|
||||||
|
|
||||||
// - Do not consume disks which are not reachable
|
|
||||||
// unformatted or simply not accessible for some reason.
|
|
||||||
//
|
|
||||||
// - Do not consume disks which are being healed
|
|
||||||
//
|
|
||||||
// - Future: skip busy disks
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mu.Lock()
|
|
||||||
newDisks = append(newDisks, disks[i-1])
|
|
||||||
infos = append(infos, di)
|
|
||||||
mu.Unlock()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
slices := sortSlices{newDisks, infos}
|
|
||||||
sort.Sort(sortByOther(slices))
|
|
||||||
|
|
||||||
return slices.disks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
|
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
|
||||||
disks := er.getDisks()
|
disks := er.getDisks()
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -204,32 +143,12 @@ func (er erasureObjects) getLoadBalancedDisks(optimized bool) []StorageAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This function does the following check, suppose
|
// This function does the following check, suppose
|
||||||
// object is "a/b/c/d", stat makes sure that objects ""a/b/c""
|
// object is "a/b/c/d", stat makes sure that objects
|
||||||
// "a/b" and "a" do not exist.
|
// - "a/b/c"
|
||||||
|
// - "a/b"
|
||||||
|
// - "a"
|
||||||
|
// do not exist on the namespace.
|
||||||
func (er erasureObjects) parentDirIsObject(ctx context.Context, bucket, parent string) bool {
|
func (er erasureObjects) parentDirIsObject(ctx context.Context, bucket, parent string) bool {
|
||||||
path := ""
|
|
||||||
segments := strings.Split(parent, slashSeparator)
|
|
||||||
for _, s := range segments {
|
|
||||||
if s == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
path += s
|
|
||||||
isObject, pathNotExist := er.isObject(ctx, bucket, path)
|
|
||||||
if pathNotExist {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if isObject {
|
|
||||||
// If there is already a file at prefix "p", return true.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
path += slashSeparator
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// isObject - returns `true` if the prefix is an object i.e if
|
|
||||||
// `xl.meta` exists at the leaf, false otherwise.
|
|
||||||
func (er erasureObjects) isObject(ctx context.Context, bucket, prefix string) (ok, pathDoesNotExist bool) {
|
|
||||||
storageDisks := er.getDisks()
|
storageDisks := er.getDisks()
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
g := errgroup.WithNErrs(len(storageDisks))
|
||||||
@@ -241,7 +160,7 @@ func (er erasureObjects) isObject(ctx context.Context, bucket, prefix string) (o
|
|||||||
return errDiskNotFound
|
return errDiskNotFound
|
||||||
}
|
}
|
||||||
// Check if 'prefix' is an object on this 'disk', else continue the check the next disk
|
// Check if 'prefix' is an object on this 'disk', else continue the check the next disk
|
||||||
return storageDisks[index].CheckFile(ctx, bucket, prefix)
|
return storageDisks[index].CheckFile(ctx, bucket, parent)
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +170,5 @@ func (er erasureObjects) isObject(ctx context.Context, bucket, prefix string) (o
|
|||||||
// ignored if necessary.
|
// ignored if necessary.
|
||||||
readQuorum := getReadQuorum(len(storageDisks))
|
readQuorum := getReadQuorum(len(storageDisks))
|
||||||
|
|
||||||
err := reduceReadQuorumErrs(ctx, g.Wait(), objectOpIgnoredErrs, readQuorum)
|
return reduceReadQuorumErrs(ctx, g.Wait(), objectOpIgnoredErrs, readQuorum) == nil
|
||||||
return err == nil, err == errPathNotFound
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-41
@@ -28,7 +28,7 @@ func TestErasureParentDirIsObject(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
obj, fsDisks, err := prepareErasure16(ctx)
|
obj, fsDisks, err := prepareErasureSets32(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to initialize 'Erasure' object layer.")
|
t.Fatalf("Unable to initialize 'Erasure' object layer.")
|
||||||
}
|
}
|
||||||
@@ -45,59 +45,38 @@ func TestErasureParentDirIsObject(t *testing.T) {
|
|||||||
if err = obj.MakeBucketWithLocation(GlobalContext, bucketName, BucketOptions{}); err != nil {
|
if err = obj.MakeBucketWithLocation(GlobalContext, bucketName, BucketOptions{}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectContent := "12345"
|
objectContent := "12345"
|
||||||
objInfo, err := obj.PutObject(GlobalContext, bucketName, objectName,
|
_, err = obj.PutObject(GlobalContext, bucketName, objectName,
|
||||||
mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), ObjectOptions{})
|
mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if objInfo.Name != objectName {
|
|
||||||
t.Fatalf("Unexpected object name returned got %s, expected %s", objInfo.Name, objectName)
|
|
||||||
}
|
|
||||||
|
|
||||||
z := obj.(*erasureServerPools)
|
|
||||||
xl := z.serverPools[0].sets[0]
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
parentIsObject bool
|
expectedErr bool
|
||||||
objectName string
|
objectName string
|
||||||
}{
|
}{
|
||||||
// parentIsObject is true if object is available.
|
|
||||||
{
|
{
|
||||||
parentIsObject: true,
|
expectedErr: true,
|
||||||
objectName: objectName,
|
objectName: pathJoin(objectName, "parent-is-object"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parentIsObject: false,
|
expectedErr: false,
|
||||||
objectName: "new-dir",
|
objectName: pathJoin("no-parent", "object"),
|
||||||
},
|
|
||||||
{
|
|
||||||
parentIsObject: false,
|
|
||||||
objectName: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parentIsObject: false,
|
|
||||||
objectName: ".",
|
|
||||||
},
|
|
||||||
// Should not cause infinite loop.
|
|
||||||
{
|
|
||||||
parentIsObject: false,
|
|
||||||
objectName: SlashSeparator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parentIsObject: false,
|
|
||||||
objectName: "\\",
|
|
||||||
},
|
|
||||||
// Should not cause infinite loop with double forward slash.
|
|
||||||
{
|
|
||||||
parentIsObject: false,
|
|
||||||
objectName: "//",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
gotValue := xl.parentDirIsObject(GlobalContext, bucketName, testCase.objectName)
|
t.Run("", func(t *testing.T) {
|
||||||
if testCase.parentIsObject != gotValue {
|
_, err = obj.PutObject(GlobalContext, bucketName, testCase.objectName,
|
||||||
t.Errorf("Test %d: Unexpected value returned got %t, expected %t", i+1, gotValue, testCase.parentIsObject)
|
mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), ObjectOptions{})
|
||||||
}
|
if testCase.expectedErr && err == nil {
|
||||||
|
t.Error("Expected error but got nil")
|
||||||
|
}
|
||||||
|
if !testCase.expectedErr && err != nil {
|
||||||
|
t.Errorf("Expected nil but got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-44
@@ -26,8 +26,6 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errHealRequired = errors.New("heal required")
|
|
||||||
|
|
||||||
// Reads in parallel from readers.
|
// Reads in parallel from readers.
|
||||||
type parallelReader struct {
|
type parallelReader struct {
|
||||||
readers []io.ReaderAt
|
readers []io.ReaderAt
|
||||||
@@ -125,7 +123,8 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
|
|||||||
readTriggerCh <- true
|
readTriggerCh <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
healRequired := int32(0) // Atomic bool flag.
|
bitrotHeal := int32(0) // Atomic bool flag.
|
||||||
|
missingPartsHeal := int32(0) // Atomic bool flag.
|
||||||
readerIndex := 0
|
readerIndex := 0
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
// if readTrigger is true, it implies next disk.ReadAt() should be tried
|
// if readTrigger is true, it implies next disk.ReadAt() should be tried
|
||||||
@@ -164,8 +163,10 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
|
|||||||
p.buf[bufIdx] = p.buf[bufIdx][:p.shardSize]
|
p.buf[bufIdx] = p.buf[bufIdx][:p.shardSize]
|
||||||
_, err := rr.ReadAt(p.buf[bufIdx], p.offset)
|
_, err := rr.ReadAt(p.buf[bufIdx], p.offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*errHashMismatch); ok {
|
if errors.Is(err, errFileNotFound) {
|
||||||
atomic.StoreInt32(&healRequired, 1)
|
atomic.StoreInt32(&missingPartsHeal, 1)
|
||||||
|
} else if errors.Is(err, errFileCorrupt) {
|
||||||
|
atomic.StoreInt32(&bitrotHeal, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will be communicated upstream.
|
// This will be communicated upstream.
|
||||||
@@ -188,8 +189,10 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
if p.canDecode(newBuf) {
|
if p.canDecode(newBuf) {
|
||||||
p.offset += p.shardSize
|
p.offset += p.shardSize
|
||||||
if healRequired != 0 {
|
if atomic.LoadInt32(&missingPartsHeal) == 1 {
|
||||||
return newBuf, errHealRequired
|
return newBuf, errFileNotFound
|
||||||
|
} else if atomic.LoadInt32(&bitrotHeal) == 1 {
|
||||||
|
return newBuf, errFileCorrupt
|
||||||
}
|
}
|
||||||
return newBuf, nil
|
return newBuf, nil
|
||||||
}
|
}
|
||||||
@@ -197,41 +200,20 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
|
|||||||
return nil, reduceReadQuorumErrs(context.Background(), p.errs, objectOpIgnoredErrs, p.dataBlocks)
|
return nil, reduceReadQuorumErrs(context.Background(), p.errs, objectOpIgnoredErrs, p.dataBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
type errDecodeHealRequired struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *errDecodeHealRequired) Error() string {
|
|
||||||
return err.err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *errDecodeHealRequired) Unwrap() error {
|
|
||||||
return err.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode reads from readers, reconstructs data if needed and writes the data to the writer.
|
// Decode reads from readers, reconstructs data if needed and writes the data to the writer.
|
||||||
// A set of preferred drives can be supplied. In that case they will be used and the data reconstructed.
|
// A set of preferred drives can be supplied. In that case they will be used and the data reconstructed.
|
||||||
func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.ReaderAt, offset, length, totalLength int64, prefer []bool) error {
|
func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.ReaderAt, offset, length, totalLength int64, prefer []bool) (written int64, derr error) {
|
||||||
healRequired, err := e.decode(ctx, writer, readers, offset, length, totalLength, prefer)
|
|
||||||
if healRequired {
|
|
||||||
return &errDecodeHealRequired{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode reads from readers, reconstructs data if needed and writes the data to the writer.
|
|
||||||
func (e Erasure) decode(ctx context.Context, writer io.Writer, readers []io.ReaderAt, offset, length, totalLength int64, prefer []bool) (bool, error) {
|
|
||||||
if offset < 0 || length < 0 {
|
if offset < 0 || length < 0 {
|
||||||
logger.LogIf(ctx, errInvalidArgument)
|
logger.LogIf(ctx, errInvalidArgument)
|
||||||
return false, errInvalidArgument
|
return -1, errInvalidArgument
|
||||||
}
|
}
|
||||||
if offset+length > totalLength {
|
if offset+length > totalLength {
|
||||||
logger.LogIf(ctx, errInvalidArgument)
|
logger.LogIf(ctx, errInvalidArgument)
|
||||||
return false, errInvalidArgument
|
return -1, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return false, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := newParallelReader(readers, e, offset, totalLength)
|
reader := newParallelReader(readers, e, offset, totalLength)
|
||||||
@@ -242,7 +224,6 @@ func (e Erasure) decode(ctx context.Context, writer io.Writer, readers []io.Read
|
|||||||
startBlock := offset / e.blockSize
|
startBlock := offset / e.blockSize
|
||||||
endBlock := (offset + length) / e.blockSize
|
endBlock := (offset + length) / e.blockSize
|
||||||
|
|
||||||
var healRequired bool
|
|
||||||
var bytesWritten int64
|
var bytesWritten int64
|
||||||
var bufs [][]byte
|
var bufs [][]byte
|
||||||
for block := startBlock; block <= endBlock; block++ {
|
for block := startBlock; block <= endBlock; block++ {
|
||||||
@@ -264,32 +245,39 @@ func (e Erasure) decode(ctx context.Context, writer io.Writer, readers []io.Read
|
|||||||
if blockLength == 0 {
|
if blockLength == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
bufs, err = reader.Read(bufs)
|
bufs, err = reader.Read(bufs)
|
||||||
if err != nil {
|
if len(bufs) > 0 {
|
||||||
if errors.Is(err, errHealRequired) {
|
// Set only if there are be enough data for reconstruction.
|
||||||
// errHealRequired is only returned if there are be enough data for reconstruction.
|
// and only for expected errors, also set once.
|
||||||
healRequired = true
|
if errors.Is(err, errFileNotFound) || errors.Is(err, errFileCorrupt) {
|
||||||
} else {
|
if derr == nil {
|
||||||
return healRequired, err
|
derr = err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
// For all errors that cannot be reconstructed fail the read operation.
|
||||||
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = e.DecodeDataBlocks(bufs); err != nil {
|
if err = e.DecodeDataBlocks(bufs); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return healRequired, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := writeDataBlocks(ctx, writer, bufs, e.dataBlocks, blockOffset, blockLength)
|
n, err := writeDataBlocks(ctx, writer, bufs, e.dataBlocks, blockOffset, blockLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return healRequired, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesWritten += n
|
bytesWritten += n
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytesWritten != length {
|
if bytesWritten != length {
|
||||||
logger.LogIf(ctx, errLessData)
|
logger.LogIf(ctx, errLessData)
|
||||||
return healRequired, errLessData
|
return bytesWritten, errLessData
|
||||||
}
|
}
|
||||||
|
|
||||||
return healRequired, nil
|
return bytesWritten, derr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,11 +134,11 @@ func TestErasureDecode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
tillOffset := erasure.ShardFileOffset(test.offset, test.length, test.data)
|
tillOffset := erasure.ShardFileOffset(test.offset, test.length, test.data)
|
||||||
|
|
||||||
bitrotReaders[index] = newBitrotReader(disk, "testbucket", "object", tillOffset, writeAlgorithm, bitrotWriterSum(writers[index]), erasure.ShardSize())
|
bitrotReaders[index] = newBitrotReader(disk, nil, "testbucket", "object", tillOffset, writeAlgorithm, bitrotWriterSum(writers[index]), erasure.ShardSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
writer := bytes.NewBuffer(nil)
|
writer := bytes.NewBuffer(nil)
|
||||||
err = erasure.Decode(context.Background(), writer, bitrotReaders, test.offset, test.length, test.data, nil)
|
_, err = erasure.Decode(context.Background(), writer, bitrotReaders, test.offset, test.length, test.data, nil)
|
||||||
closeBitrotReaders(bitrotReaders)
|
closeBitrotReaders(bitrotReaders)
|
||||||
if err != nil && !test.shouldFail {
|
if err != nil && !test.shouldFail {
|
||||||
t.Errorf("Test %d: should pass but failed with: %v", i, err)
|
t.Errorf("Test %d: should pass but failed with: %v", i, err)
|
||||||
@@ -164,7 +164,7 @@ func TestErasureDecode(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tillOffset := erasure.ShardFileOffset(test.offset, test.length, test.data)
|
tillOffset := erasure.ShardFileOffset(test.offset, test.length, test.data)
|
||||||
bitrotReaders[index] = newBitrotReader(disk, "testbucket", "object", tillOffset, writeAlgorithm, bitrotWriterSum(writers[index]), erasure.ShardSize())
|
bitrotReaders[index] = newBitrotReader(disk, nil, "testbucket", "object", tillOffset, writeAlgorithm, bitrotWriterSum(writers[index]), erasure.ShardSize())
|
||||||
}
|
}
|
||||||
for j := range disks[:test.offDisks] {
|
for j := range disks[:test.offDisks] {
|
||||||
if bitrotReaders[j] == nil {
|
if bitrotReaders[j] == nil {
|
||||||
@@ -181,7 +181,7 @@ func TestErasureDecode(t *testing.T) {
|
|||||||
bitrotReaders[0] = nil
|
bitrotReaders[0] = nil
|
||||||
}
|
}
|
||||||
writer.Reset()
|
writer.Reset()
|
||||||
err = erasure.Decode(context.Background(), writer, bitrotReaders, test.offset, test.length, test.data, nil)
|
_, err = erasure.Decode(context.Background(), writer, bitrotReaders, test.offset, test.length, test.data, nil)
|
||||||
closeBitrotReaders(bitrotReaders)
|
closeBitrotReaders(bitrotReaders)
|
||||||
if err != nil && !test.shouldFailQuorum {
|
if err != nil && !test.shouldFailQuorum {
|
||||||
t.Errorf("Test %d: should pass but failed with: %v", i, err)
|
t.Errorf("Test %d: should pass but failed with: %v", i, err)
|
||||||
@@ -270,9 +270,9 @@ func TestErasureDecodeRandomOffsetLength(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tillOffset := erasure.ShardFileOffset(offset, readLen, length)
|
tillOffset := erasure.ShardFileOffset(offset, readLen, length)
|
||||||
bitrotReaders[index] = newStreamingBitrotReader(disk, "testbucket", "object", tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
|
bitrotReaders[index] = newStreamingBitrotReader(disk, nil, "testbucket", "object", tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||||
}
|
}
|
||||||
err = erasure.Decode(context.Background(), buf, bitrotReaders, offset, readLen, length, nil)
|
_, err = erasure.Decode(context.Background(), buf, bitrotReaders, offset, readLen, length, nil)
|
||||||
closeBitrotReaders(bitrotReaders)
|
closeBitrotReaders(bitrotReaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err, offset, readLen)
|
t.Fatal(err, offset, readLen)
|
||||||
@@ -332,9 +332,9 @@ func benchmarkErasureDecode(data, parity, dataDown, parityDown int, size int64,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tillOffset := erasure.ShardFileOffset(0, size, size)
|
tillOffset := erasure.ShardFileOffset(0, size, size)
|
||||||
bitrotReaders[index] = newStreamingBitrotReader(disk, "testbucket", "object", tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
|
bitrotReaders[index] = newStreamingBitrotReader(disk, nil, "testbucket", "object", tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||||
}
|
}
|
||||||
if err = erasure.Decode(context.Background(), bytes.NewBuffer(content[:0]), bitrotReaders, 0, size, size, nil); err != nil {
|
if _, err = erasure.Decode(context.Background(), bytes.NewBuffer(content[:0]), bitrotReaders, 0, size, size, nil); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
closeBitrotReaders(bitrotReaders)
|
closeBitrotReaders(bitrotReaders)
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func TestErasureHeal(t *testing.T) {
|
|||||||
readers := make([]io.ReaderAt, len(disks))
|
readers := make([]io.ReaderAt, len(disks))
|
||||||
for i, disk := range disks {
|
for i, disk := range disks {
|
||||||
shardFilesize := erasure.ShardFileSize(test.size)
|
shardFilesize := erasure.ShardFileSize(test.size)
|
||||||
readers[i] = newBitrotReader(disk, "testbucket", "testobject", shardFilesize, test.algorithm, bitrotWriterSum(writers[i]), erasure.ShardSize())
|
readers[i] = newBitrotReader(disk, nil, "testbucket", "testobject", shardFilesize, test.algorithm, bitrotWriterSum(writers[i]), erasure.ShardSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup stale disks for the test case
|
// setup stale disks for the test case
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ func TestListOnlineDisks(t *testing.T) {
|
|||||||
t.Fatalf("Failed to putObject %v", err)
|
t.Fatalf("Failed to putObject %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "")
|
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false)
|
||||||
fi, err := getLatestFileInfo(ctx, partsMetadata, errs)
|
fi, err := getLatestFileInfo(ctx, partsMetadata, errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to getLatestFileInfo %v", err)
|
t.Fatalf("Failed to getLatestFileInfo %v", err)
|
||||||
@@ -287,7 +287,7 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
t.Fatalf("Failed to putObject %v", err)
|
t.Fatalf("Failed to putObject %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "")
|
_, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false)
|
||||||
readQuorum := len(erasureDisks) / 2
|
readQuorum := len(erasureDisks) / 2
|
||||||
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
|
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
|
||||||
t.Fatalf("Failed to read xl meta data %v", reducedErr)
|
t.Fatalf("Failed to read xl meta data %v", reducedErr)
|
||||||
@@ -295,7 +295,7 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
|
|
||||||
// Test that all disks are returned without any failures with
|
// Test that all disks are returned without any failures with
|
||||||
// unmodified meta data
|
// unmodified meta data
|
||||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "")
|
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to read xl meta data %v", err)
|
t.Fatalf("Failed to read xl meta data %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-39
@@ -25,6 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"github.com/minio/minio/pkg/sync/errgroup"
|
||||||
)
|
)
|
||||||
@@ -42,15 +43,18 @@ func (er erasureObjects) HealBucket(ctx context.Context, bucket string, opts mad
|
|||||||
storageEndpoints := er.getEndpoints()
|
storageEndpoints := er.getEndpoints()
|
||||||
|
|
||||||
// get write quorum for an object
|
// get write quorum for an object
|
||||||
writeQuorum := getWriteQuorum(len(storageDisks))
|
writeQuorum := len(storageDisks) - er.defaultParityCount
|
||||||
|
if writeQuorum == er.defaultParityCount {
|
||||||
|
writeQuorum++
|
||||||
|
}
|
||||||
|
|
||||||
// Heal bucket.
|
// Heal bucket.
|
||||||
return healBucket(ctx, storageDisks, storageEndpoints, bucket, writeQuorum, opts.DryRun)
|
return healBucket(ctx, storageDisks, storageEndpoints, bucket, writeQuorum, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal bucket - create buckets on disks where it does not exist.
|
// Heal bucket - create buckets on disks where it does not exist.
|
||||||
func healBucket(ctx context.Context, storageDisks []StorageAPI, storageEndpoints []string, bucket string, writeQuorum int,
|
func healBucket(ctx context.Context, storageDisks []StorageAPI, storageEndpoints []string, bucket string, writeQuorum int,
|
||||||
dryRun bool) (res madmin.HealResultItem, err error) {
|
opts madmin.HealOpts) (res madmin.HealResultItem, err error) {
|
||||||
|
|
||||||
// Initialize sync waitgroup.
|
// Initialize sync waitgroup.
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
g := errgroup.WithNErrs(len(storageDisks))
|
||||||
@@ -84,7 +88,7 @@ func healBucket(ctx context.Context, storageDisks []StorageAPI, storageEndpoints
|
|||||||
afterState[index] = madmin.DriveStateMissing
|
afterState[index] = madmin.DriveStateMissing
|
||||||
|
|
||||||
// mutate only if not a dry-run
|
// mutate only if not a dry-run
|
||||||
if dryRun {
|
if opts.DryRun {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,16 +102,13 @@ func healBucket(ctx context.Context, storageDisks []StorageAPI, storageEndpoints
|
|||||||
|
|
||||||
errs := g.Wait()
|
errs := g.Wait()
|
||||||
|
|
||||||
reducedErr := reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, writeQuorum-1)
|
|
||||||
if reducedErr == errVolumeNotFound {
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize heal result info
|
// Initialize heal result info
|
||||||
res = madmin.HealResultItem{
|
res = madmin.HealResultItem{
|
||||||
Type: madmin.HealItemBucket,
|
Type: madmin.HealItemBucket,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
DiskCount: len(storageDisks),
|
DiskCount: len(storageDisks),
|
||||||
|
ParityBlocks: len(storageDisks) / 2,
|
||||||
|
DataBlocks: len(storageDisks) / 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range beforeState {
|
for i := range beforeState {
|
||||||
@@ -118,6 +119,18 @@ func healBucket(ctx context.Context, storageDisks []StorageAPI, storageEndpoints
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reducedErr := reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, writeQuorum-1)
|
||||||
|
if errors.Is(reducedErr, errVolumeNotFound) && !opts.Recreate {
|
||||||
|
for i := range beforeState {
|
||||||
|
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
|
||||||
|
UUID: "",
|
||||||
|
Endpoint: storageEndpoints[i],
|
||||||
|
State: madmin.DriveStateOk,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize sync waitgroup.
|
// Initialize sync waitgroup.
|
||||||
g = errgroup.WithNErrs(len(storageDisks))
|
g = errgroup.WithNErrs(len(storageDisks))
|
||||||
|
|
||||||
@@ -223,8 +236,6 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
dryRun := opts.DryRun
|
dryRun := opts.DryRun
|
||||||
scanMode := opts.ScanMode
|
scanMode := opts.ScanMode
|
||||||
|
|
||||||
dataBlocks := lfi.Erasure.DataBlocks
|
|
||||||
|
|
||||||
storageDisks := er.getDisks()
|
storageDisks := er.getDisks()
|
||||||
storageEndpoints := er.getEndpoints()
|
storageEndpoints := er.getEndpoints()
|
||||||
|
|
||||||
@@ -241,8 +252,8 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: object,
|
Object: object,
|
||||||
DiskCount: len(storageDisks),
|
DiskCount: len(storageDisks),
|
||||||
ParityBlocks: len(storageDisks) / 2,
|
ParityBlocks: er.defaultParityCount,
|
||||||
DataBlocks: len(storageDisks) / 2,
|
DataBlocks: len(storageDisks) - er.defaultParityCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop to find number of disks with valid data, per-drive
|
// Loop to find number of disks with valid data, per-drive
|
||||||
@@ -308,12 +319,12 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
// File is fully gone, fileInfo is empty.
|
// File is fully gone, fileInfo is empty.
|
||||||
return defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs, bucket, object, versionID), err
|
return defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If less than read quorum number of disks have all the parts
|
// If less than read quorum number of disks have all the parts
|
||||||
// of the data, we can't reconstruct the erasure-coded data.
|
// of the data, we can't reconstruct the erasure-coded data.
|
||||||
if numAvailableDisks < dataBlocks {
|
if numAvailableDisks < result.DataBlocks {
|
||||||
return er.purgeObjectDangling(ctx, bucket, object, versionID, partsMetadata, errs, dataErrs, opts)
|
return er.purgeObjectDangling(ctx, bucket, object, versionID, partsMetadata, errs, dataErrs, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +341,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
|
|
||||||
// Latest FileInfo for reference. If a valid metadata is not
|
// Latest FileInfo for reference. If a valid metadata is not
|
||||||
// present, it is as good as object not found.
|
// present, it is as good as object not found.
|
||||||
latestMeta, err := pickValidFileInfo(ctx, partsMetadata, modTime, dataBlocks)
|
latestMeta, err := pickValidFileInfo(ctx, partsMetadata, modTime, result.DataBlocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, toObjectErr(err, bucket, object, versionID)
|
return result, toObjectErr(err, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
@@ -360,7 +371,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
dataDir = migrateDataDir
|
dataDir = migrateDataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
if !latestMeta.Deleted {
|
if !latestMeta.Deleted || latestMeta.TransitionStatus != lifecycle.TransitionComplete {
|
||||||
result.DataBlocks = latestMeta.Erasure.DataBlocks
|
result.DataBlocks = latestMeta.Erasure.DataBlocks
|
||||||
result.ParityBlocks = latestMeta.Erasure.ParityBlocks
|
result.ParityBlocks = latestMeta.Erasure.ParityBlocks
|
||||||
|
|
||||||
@@ -395,7 +406,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
if latestMeta.XLV1 {
|
if latestMeta.XLV1 {
|
||||||
partPath = pathJoin(object, fmt.Sprintf("part.%d", partNumber))
|
partPath = pathJoin(object, fmt.Sprintf("part.%d", partNumber))
|
||||||
}
|
}
|
||||||
readers[i] = newBitrotReader(disk, bucket, partPath, tillOffset, checksumAlgo, checksumInfo.Hash, erasure.ShardSize())
|
readers[i] = newBitrotReader(disk, nil, bucket, partPath, tillOffset, checksumAlgo, checksumInfo.Hash, erasure.ShardSize())
|
||||||
}
|
}
|
||||||
writers := make([]io.Writer, len(outDatedDisks))
|
writers := make([]io.Writer, len(outDatedDisks))
|
||||||
for i, disk := range outDatedDisks {
|
for i, disk := range outDatedDisks {
|
||||||
@@ -490,8 +501,8 @@ func (er erasureObjects) healObjectDir(ctx context.Context, bucket, object strin
|
|||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: object,
|
Object: object,
|
||||||
DiskCount: len(storageDisks),
|
DiskCount: len(storageDisks),
|
||||||
ParityBlocks: getDefaultParityBlocks(len(storageDisks)),
|
ParityBlocks: er.defaultParityCount,
|
||||||
DataBlocks: getDefaultDataBlocks(len(storageDisks)),
|
DataBlocks: len(storageDisks) - er.defaultParityCount,
|
||||||
ObjectSize: 0,
|
ObjectSize: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +573,7 @@ func (er erasureObjects) healObjectDir(ctx context.Context, bucket, object strin
|
|||||||
|
|
||||||
// Populates default heal result item entries with possible values when we are returning prematurely.
|
// Populates default heal result item entries with possible values when we are returning prematurely.
|
||||||
// This is to ensure that in any circumstance we are not returning empty arrays with wrong values.
|
// This is to ensure that in any circumstance we are not returning empty arrays with wrong values.
|
||||||
func defaultHealResult(lfi FileInfo, storageDisks []StorageAPI, storageEndpoints []string, errs []error, bucket, object, versionID string) madmin.HealResultItem {
|
func defaultHealResult(lfi FileInfo, storageDisks []StorageAPI, storageEndpoints []string, errs []error, bucket, object, versionID string, defaultParityCount int) madmin.HealResultItem {
|
||||||
// Initialize heal result object
|
// Initialize heal result object
|
||||||
result := madmin.HealResultItem{
|
result := madmin.HealResultItem{
|
||||||
Type: madmin.HealItemObject,
|
Type: madmin.HealItemObject,
|
||||||
@@ -608,8 +619,8 @@ func defaultHealResult(lfi FileInfo, storageDisks []StorageAPI, storageEndpoints
|
|||||||
|
|
||||||
if !lfi.IsValid() {
|
if !lfi.IsValid() {
|
||||||
// Default to most common configuration for erasure blocks.
|
// Default to most common configuration for erasure blocks.
|
||||||
result.ParityBlocks = getDefaultParityBlocks(len(storageDisks))
|
result.ParityBlocks = defaultParityCount
|
||||||
result.DataBlocks = getDefaultDataBlocks(len(storageDisks))
|
result.DataBlocks = len(storageDisks) - defaultParityCount
|
||||||
} else {
|
} else {
|
||||||
result.ParityBlocks = lfi.Erasure.ParityBlocks
|
result.ParityBlocks = lfi.Erasure.ParityBlocks
|
||||||
result.DataBlocks = lfi.Erasure.DataBlocks
|
result.DataBlocks = lfi.Erasure.DataBlocks
|
||||||
@@ -690,7 +701,7 @@ func (er erasureObjects) purgeObjectDangling(ctx context.Context, bucket, object
|
|||||||
if ok {
|
if ok {
|
||||||
writeQuorum := m.Erasure.DataBlocks
|
writeQuorum := m.Erasure.DataBlocks
|
||||||
if m.Erasure.DataBlocks == 0 || m.Erasure.DataBlocks == m.Erasure.ParityBlocks {
|
if m.Erasure.DataBlocks == 0 || m.Erasure.DataBlocks == m.Erasure.ParityBlocks {
|
||||||
writeQuorum = getWriteQuorum(len(storageDisks))
|
writeQuorum++
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
var returnNotFound bool
|
var returnNotFound bool
|
||||||
@@ -698,7 +709,7 @@ func (er erasureObjects) purgeObjectDangling(ctx context.Context, bucket, object
|
|||||||
if versionID == "" {
|
if versionID == "" {
|
||||||
err = er.deleteObject(ctx, bucket, object, writeQuorum)
|
err = er.deleteObject(ctx, bucket, object, writeQuorum)
|
||||||
} else {
|
} else {
|
||||||
err = er.deleteObjectVersion(ctx, bucket, object, writeQuorum, FileInfo{VersionID: versionID})
|
err = er.deleteObjectVersion(ctx, bucket, object, writeQuorum, FileInfo{VersionID: versionID}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Delete was successful, make sure to return the appropriate error
|
// If Delete was successful, make sure to return the appropriate error
|
||||||
@@ -722,18 +733,15 @@ func (er erasureObjects) purgeObjectDangling(ctx context.Context, bucket, object
|
|||||||
if versionID != "" {
|
if versionID != "" {
|
||||||
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID), err
|
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
||||||
}
|
}
|
||||||
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID), toObjectErr(err, bucket, object, versionID)
|
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), toObjectErr(err, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum := m.Erasure.DataBlocks
|
readQuorum := len(storageDisks) - er.defaultParityCount
|
||||||
if m.Erasure.DataBlocks == 0 || m.Erasure.DataBlocks == m.Erasure.ParityBlocks {
|
|
||||||
readQuorum = getReadQuorum(len(storageDisks))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := toObjectErr(reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum), bucket, object, versionID)
|
err := toObjectErr(reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum), bucket, object, versionID)
|
||||||
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID), err
|
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object is considered dangling/corrupted if any only
|
// Object is considered dangling/corrupted if any only
|
||||||
@@ -775,8 +783,10 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if validMeta.Deleted {
|
if validMeta.Deleted || validMeta.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
// notFoundParts is ignored since a delete marker does not have any parts
|
// notFoundParts is ignored since a
|
||||||
|
// - delete marker does not have any parts
|
||||||
|
// - transition status of complete has no parts
|
||||||
return validMeta, corruptedErasureMeta+notFoundErasureMeta > len(errs)/2
|
return validMeta, corruptedErasureMeta+notFoundErasureMeta > len(errs)/2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,7 +821,7 @@ func (er erasureObjects) HealObject(ctx context.Context, bucket, object, version
|
|||||||
storageEndpoints := er.getEndpoints()
|
storageEndpoints := er.getEndpoints()
|
||||||
|
|
||||||
// Read metadata files from all the disks
|
// Read metadata files from all the disks
|
||||||
partsMetadata, errs := readAllFileInfo(healCtx, storageDisks, bucket, object, versionID)
|
partsMetadata, errs := readAllFileInfo(healCtx, storageDisks, bucket, object, versionID, false)
|
||||||
|
|
||||||
if isAllNotFound(errs) {
|
if isAllNotFound(errs) {
|
||||||
err = toObjectErr(errFileNotFound, bucket, object)
|
err = toObjectErr(errFileNotFound, bucket, object)
|
||||||
@@ -819,7 +829,7 @@ func (er erasureObjects) HealObject(ctx context.Context, bucket, object, version
|
|||||||
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
// Nothing to do, file is already gone.
|
// Nothing to do, file is already gone.
|
||||||
return defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs, bucket, object, versionID), err
|
return defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := getLatestFileInfo(healCtx, partsMetadata, errs)
|
fi, err := getLatestFileInfo(healCtx, partsMetadata, errs)
|
||||||
|
|||||||
+11
-11
@@ -67,7 +67,7 @@ func TestHealing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disk := er.getDisks()[0]
|
disk := er.getDisks()[0]
|
||||||
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), bucket, object, "")
|
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ func TestHealing(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), bucket, object, "")
|
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func TestHealing(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "")
|
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ func TestHealObjectCorrupted(t *testing.T) {
|
|||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
// Everything is fine, should return nil
|
// Everything is fine, should return nil
|
||||||
objLayer, _, err := initObjectLayer(ctx, mustGetZoneEndpoints(fsDirs...))
|
objLayer, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ func TestHealObjectCorrupted(t *testing.T) {
|
|||||||
t.Fatalf("Failed to heal object - %v", err)
|
t.Fatalf("Failed to heal object - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "")
|
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false)
|
||||||
fi, err := getLatestFileInfo(ctx, fileInfos, errs)
|
fi, err := getLatestFileInfo(ctx, fileInfos, errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||||
@@ -239,7 +239,7 @@ func TestHealObjectCorrupted(t *testing.T) {
|
|||||||
t.Errorf("Expected nil but received %v", err)
|
t.Errorf("Expected nil but received %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "")
|
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false)
|
||||||
nfi, err := getLatestFileInfo(ctx, fileInfos, errs)
|
nfi, err := getLatestFileInfo(ctx, fileInfos, errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||||
@@ -265,7 +265,7 @@ func TestHealObjectCorrupted(t *testing.T) {
|
|||||||
t.Errorf("Expected nil but received %v", err)
|
t.Errorf("Expected nil but received %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "")
|
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false)
|
||||||
nfi, err = getLatestFileInfo(ctx, fileInfos, errs)
|
nfi, err = getLatestFileInfo(ctx, fileInfos, errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||||
@@ -277,8 +277,8 @@ func TestHealObjectCorrupted(t *testing.T) {
|
|||||||
|
|
||||||
// Test 4: checks if HealObject returns an error when xl.meta is not found
|
// Test 4: checks if HealObject returns an error when xl.meta is not found
|
||||||
// in more than read quorum number of disks, to create a corrupted situation.
|
// in more than read quorum number of disks, to create a corrupted situation.
|
||||||
for i := 0; i <= len(er.getDisks())/2; i++ {
|
for i := 0; i <= nfi.Erasure.DataBlocks; i++ {
|
||||||
er.getDisks()[i].Delete(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), false)
|
erasureDisks[i].Delete(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try healing now, expect to receive errFileNotFound.
|
// Try healing now, expect to receive errFileNotFound.
|
||||||
@@ -310,7 +310,7 @@ func TestHealObjectErasure(t *testing.T) {
|
|||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
// Everything is fine, should return nil
|
// Everything is fine, should return nil
|
||||||
obj, _, err := initObjectLayer(ctx, mustGetZoneEndpoints(fsDirs...))
|
obj, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -400,7 +400,7 @@ func TestHealEmptyDirectoryErasure(t *testing.T) {
|
|||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
// Everything is fine, should return nil
|
// Everything is fine, should return nil
|
||||||
obj, _, err := initObjectLayer(ctx, mustGetZoneEndpoints(fsDirs...))
|
obj, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
func (e Erasure) Heal(ctx context.Context, readers []io.ReaderAt, writers []io.Writer, size int64) error {
|
func (e Erasure) Heal(ctx context.Context, readers []io.ReaderAt, writers []io.Writer, size int64) error {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
if err := e.Decode(ctx, w, readers, 0, size, size, nil); err != nil {
|
if _, err := e.Decode(ctx, w, readers, 0, size, size, nil); err != nil {
|
||||||
w.CloseWithError(err)
|
w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func hashOrder(key string, cardinality int) []int {
|
|||||||
|
|
||||||
// Reads all `xl.meta` metadata as a FileInfo slice.
|
// Reads all `xl.meta` metadata as a FileInfo slice.
|
||||||
// Returns error slice indicating the failed metadata reads.
|
// Returns error slice indicating the failed metadata reads.
|
||||||
func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, versionID string) ([]FileInfo, []error) {
|
func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, versionID string, readData bool) ([]FileInfo, []error) {
|
||||||
metadataArray := make([]FileInfo, len(disks))
|
metadataArray := make([]FileInfo, len(disks))
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(disks))
|
g := errgroup.WithNErrs(len(disks))
|
||||||
@@ -126,9 +126,14 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
|
|||||||
if disks[index] == nil {
|
if disks[index] == nil {
|
||||||
return errDiskNotFound
|
return errDiskNotFound
|
||||||
}
|
}
|
||||||
metadataArray[index], err = disks[index].ReadVersion(ctx, bucket, object, versionID)
|
metadataArray[index], err = disks[index].ReadVersion(ctx, bucket, object, versionID, readData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !IsErr(err, errFileNotFound, errVolumeNotFound, errFileVersionNotFound, errDiskNotFound) {
|
if !IsErr(err, []error{
|
||||||
|
errFileNotFound,
|
||||||
|
errVolumeNotFound,
|
||||||
|
errFileVersionNotFound,
|
||||||
|
errDiskNotFound,
|
||||||
|
}...) {
|
||||||
logger.LogOnceIf(ctx, err, disks[index].String())
|
logger.LogOnceIf(ctx, err, disks[index].String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user