Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 878bc6c72b | |||
| fdc2f69218 | |||
| 0d124095ea | |||
| 691035832a | |||
| eac66e67ec | |||
| 57f3ed22d4 | |||
| 2f29719e6b | |||
| 209fe61dcc | |||
| 8ecffdb7a7 | |||
| 806df164b2 | |||
| 4ac9ed4248 | |||
| 3d8c512bba | |||
| 3ff5f55dcb | |||
| 097e5eba9f | |||
| ba6930bb13 | |||
| 64662a49ff | |||
| 78e867e145 | |||
| 9ccc483df6 | |||
| abce040088 | |||
| 558762bdf6 | |||
| d971061305 | |||
| 509bcc01ad | |||
| 7ea95fcec8 | |||
| 79b0d056a2 | |||
| ec547c0fa8 | |||
| bcf9825082 | |||
| 651487507a | |||
| 124816f6a6 | |||
| fa9cf1251b | |||
| 97e7a902d0 | |||
| d73d756a80 | |||
| 7488c77e7c | |||
| 786585009e | |||
| 7be7109471 | |||
| 464fa08f2e | |||
| c3217bd6eb | |||
| f14cc6c943 | |||
| 2c198ae7b6 | |||
| 690434514d | |||
| 039f59b552 | |||
| c6a120df0e | |||
| cd9e30c0f4 | |||
| f96d4cf7d3 | |||
| 879599b0cf | |||
| b1bb3f7016 | |||
| e8d8dfa3ae | |||
| bbd1244a88 | |||
| 10bdb78699 | |||
| 289b22d911 | |||
| 0b9c17443e | |||
| 23f7ab40b3 | |||
| e3f8830ab7 | |||
| 2f4af09c01 | |||
| 37960cbc2f | |||
| c67d1bf120 | |||
| c5b3a675fa | |||
| b690304eed | |||
| 9171d6ef65 | |||
| 6386b45c08 | |||
| 1f659204a2 | |||
| f9f6fd0421 | |||
| 85620dfe93 | |||
| a8e4f64ff3 | |||
| ca5c6e3160 | |||
| b23659927c | |||
| b912e9ab41 | |||
| 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 |
@@ -1,2 +1,9 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
|
docs
|
||||||
|
default.etcd
|
||||||
|
browser
|
||||||
|
*.gz
|
||||||
|
*.tar.gz
|
||||||
|
*.bzip2
|
||||||
|
*.zip
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.14.x, 1.15.x]
|
go-version: [1.16.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
@@ -21,6 +21,14 @@ jobs:
|
|||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
- name: Build on ${{ matrix.os }}
|
||||||
|
if: matrix.os == 'macos-latest'
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
GO111MODULE: on
|
||||||
|
run: |
|
||||||
|
make
|
||||||
|
make test-race
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
env:
|
env:
|
||||||
@@ -42,8 +50,6 @@ jobs:
|
|||||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && 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 pkg) <(printf "")
|
|
||||||
make test-race
|
make test-race
|
||||||
make crosscompile
|
make crosscompile
|
||||||
make verify
|
make verify
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ linters:
|
|||||||
- gosimple
|
- gosimple
|
||||||
- deadcode
|
- deadcode
|
||||||
- structcheck
|
- structcheck
|
||||||
|
- gomodguard
|
||||||
|
- gofmt
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.15-alpine as builder
|
FROM golang:1.16-alpine as builder
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.15-alpine as builder
|
FROM golang:1.16-alpine as builder
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ 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 \
|
||||||
@@ -17,10 +15,9 @@ ENV MINIO_UPDATE=off \
|
|||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ 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.2021-01-16T02-19-44Z" \
|
version="RELEASE.2021-03-04T00-53-13Z" \
|
||||||
release="RELEASE.2021-01-16T02-19-44Z" \
|
release="RELEASE.2021-03-04T00-53-13Z" \
|
||||||
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."
|
||||||
|
|
||||||
|
|||||||
@@ -17,32 +17,22 @@ 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" && go get github.com/quasilyte/go-ruleguard/cmd/ruleguard@v0.2.1)
|
|
||||||
@which msgp 1>/dev/null || (echo "Installing msgp" && go get github.com/tinylib/msgp@v1.1.3)
|
@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" && 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)
|
||||||
|
|
||||||
verifiers: getdeps fmt lint ruleguard check-gen
|
verifiers: getdeps lint check-gen
|
||||||
|
|
||||||
check-gen:
|
check-gen:
|
||||||
@go generate ./... >/dev/null
|
@go generate ./... >/dev/null
|
||||||
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
|
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
|
||||||
|
|
||||||
fmt:
|
|
||||||
@echo "Running $@ check"
|
|
||||||
@GO111MODULE=on gofmt -d cmd/
|
|
||||||
@GO111MODULE=on gofmt -d pkg/
|
|
||||||
|
|
||||||
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=10m --config ./.golangci.yml
|
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
||||||
|
|
||||||
ruleguard:
|
|
||||||
@echo "Running $@ check"
|
|
||||||
@${GOPATH}/bin/ruleguard -rules ruleguard.rules.go github.com/minio/minio/...
|
|
||||||
|
|
||||||
# Builds minio, runs the verifiers then runs the tests.
|
# Builds minio, runs the verifiers then runs the tests.
|
||||||
check: test
|
check: test
|
||||||
@@ -71,18 +61,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.$(shell git rev-parse --short HEAD)" 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 | \
|
||||||
TAG := "minio/minio:$(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD)"
|
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#')))
|
||||||
hotfix: install
|
$(eval TAG := "minio/minio:$(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD)")
|
||||||
|
hotfix: hotfix-vars install
|
||||||
|
|
||||||
docker-hotfix: hotfix checks
|
docker-hotfix: hotfix checks
|
||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||||
|
|
||||||
docker: checks
|
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.
|
||||||
|
|||||||
@@ -8,21 +8,6 @@ MinIO is a High Performance Object Storage released under Apache License v2.0. I
|
|||||||
This README provides quickstart instructions on running MinIO on baremetal hardware, including Docker-based installations. For Kubernetes environments,
|
This README provides quickstart instructions on running MinIO on baremetal hardware, including Docker-based installations. For Kubernetes environments,
|
||||||
use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
|
use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
|
||||||
|
|
||||||
**Table of Contents**
|
|
||||||
|
|
||||||
- [Docker Installation](#docker-installation)
|
|
||||||
- [macOS Installation](#macos)
|
|
||||||
- [GNU/Linux Installation](#gnulinux)
|
|
||||||
- [Windows Installation](#microsoft-windows)
|
|
||||||
- [FreeBSD Installation](#freebsd)
|
|
||||||
- [Source Installation](#install-from-source)
|
|
||||||
- [Deployment Recommendations](#deployment-recommendations)
|
|
||||||
- [Test Connectivity to MinIO](#test-minio-connectivity)
|
|
||||||
- [Upgrade MinIO](#upgrading-minio)
|
|
||||||
- [Explore Further](#explore-further)
|
|
||||||
- [Contribute to MinIO](#contribute-to-minio-project)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
# Docker Installation
|
# Docker Installation
|
||||||
|
|
||||||
Use the following commands to run a standalone MinIO server on a Docker container.
|
Use the following commands to run a standalone MinIO server on a Docker container.
|
||||||
@@ -199,7 +184,7 @@ service minio start
|
|||||||
|
|
||||||
# Install from Source
|
# Install from Source
|
||||||
|
|
||||||
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.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.16](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
GO111MODULE=on go get github.com/minio/minio
|
GO111MODULE=on go get github.com/minio/minio
|
||||||
|
|||||||
@@ -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.16](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) 查看许可。
|
||||||
|
|||||||
@@ -17,4 +17,3 @@ release
|
|||||||
*.syso
|
*.syso
|
||||||
coverage.txt
|
coverage.txt
|
||||||
node_modules
|
node_modules
|
||||||
production
|
|
||||||
|
|||||||
@@ -17,24 +17,13 @@ nvm install stable
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install `go-bindata` and `go-bindata-assetfs`
|
|
||||||
|
|
||||||
If you do not have a working Golang environment, please follow [Install Golang](https://golang.org/doc/install)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get github.com/go-bindata/go-bindata/go-bindata
|
|
||||||
go get github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generating Assets
|
## Generating Assets
|
||||||
|
|
||||||
### Generate ui-assets.go
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run release
|
npm run release
|
||||||
```
|
```
|
||||||
|
|
||||||
This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
|
This generates `production` in the current directory.
|
||||||
|
|
||||||
|
|
||||||
## Run MinIO Browser with live reload
|
## Run MinIO Browser with live reload
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ jest.mock("jwt-decode")
|
|||||||
jwtDecode.mockImplementation(() => ({ sub: "minio" }))
|
jwtDecode.mockImplementation(() => ({ sub: "minio" }))
|
||||||
|
|
||||||
jest.mock("../../web", () => ({
|
jest.mock("../../web", () => ({
|
||||||
GenerateAuth: jest.fn(() => {
|
|
||||||
return Promise.resolve({ accessKey: "gen1", secretKey: "gen2" })
|
|
||||||
}),
|
|
||||||
SetAuth: jest.fn(
|
SetAuth: jest.fn(
|
||||||
({ currentAccessKey, currentSecretKey, newAccessKey, newSecretKey }) => {
|
({ currentAccessKey, currentSecretKey, newAccessKey, newSecretKey }) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed production/*
|
||||||
|
var fs embed.FS
|
||||||
|
|
||||||
|
// GetStaticAssets returns assets
|
||||||
|
func GetStaticAssets() embed.FS {
|
||||||
|
return fs
|
||||||
|
}
|
||||||
@@ -14,19 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var moment = require('moment')
|
|
||||||
var async = require('async')
|
var async = require('async')
|
||||||
var exec = require('child_process').exec
|
var exec = require('child_process').exec
|
||||||
var fs = require('fs')
|
var fs = require('fs')
|
||||||
|
|
||||||
var isProduction = process.env.NODE_ENV == 'production' ? true : false
|
var isProduction = process.env.NODE_ENV == 'production' ? true : false
|
||||||
var assetsFileName = ''
|
|
||||||
var commitId = ''
|
|
||||||
var date = moment.utc()
|
|
||||||
var version = date.format('YYYY-MM-DDTHH:mm:ss') + 'Z'
|
|
||||||
var releaseTag = date.format('YYYY-MM-DDTHH-mm-ss') + 'Z'
|
|
||||||
var buildType = 'DEVELOPMENT'
|
|
||||||
if (process.env.MINIO_UI_BUILD) buildType = process.env.MINIO_UI_BUILD
|
|
||||||
|
|
||||||
rmDir = function(dirPath) {
|
rmDir = function(dirPath) {
|
||||||
try { var files = fs.readdirSync(dirPath); }
|
try { var files = fs.readdirSync(dirPath); }
|
||||||
@@ -53,74 +45,6 @@ async.waterfall([
|
|||||||
console.log('Running', cmd)
|
console.log('Running', cmd)
|
||||||
exec(cmd, cb)
|
exec(cmd, cb)
|
||||||
},
|
},
|
||||||
function(stdout, stderr, cb) {
|
|
||||||
if (isProduction) {
|
|
||||||
fs.renameSync('production/index_bundle.js',
|
|
||||||
'production/index_bundle-' + releaseTag + '.js')
|
|
||||||
} else {
|
|
||||||
fs.renameSync('dev/index_bundle.js',
|
|
||||||
'dev/index_bundle-' + releaseTag + '.js')
|
|
||||||
}
|
|
||||||
var cmd = 'git log --format="%H" -n1'
|
|
||||||
console.log('Running', cmd)
|
|
||||||
exec(cmd, cb)
|
|
||||||
},
|
|
||||||
function(stdout, stderr, cb) {
|
|
||||||
if (!stdout) throw new Error('commitId is empty')
|
|
||||||
commitId = stdout.replace('\n', '')
|
|
||||||
if (commitId.length !== 40) throw new Error('commitId invalid : ' + commitId)
|
|
||||||
assetsFileName = 'ui-assets.go';
|
|
||||||
var cmd = 'go-bindata-assetfs -o bindata_assetfs.go -pkg browser -nocompress=true production/...'
|
|
||||||
if (!isProduction) {
|
|
||||||
cmd = 'go-bindata-assetfs -o bindata_assetfs.go -pkg browser -nocompress=true dev/...'
|
|
||||||
}
|
|
||||||
console.log('Running', cmd)
|
|
||||||
exec(cmd, cb)
|
|
||||||
},
|
|
||||||
function(stdout, stderr, cb) {
|
|
||||||
var cmd = 'gofmt -s -w -l bindata_assetfs.go'
|
|
||||||
console.log('Running', cmd)
|
|
||||||
exec(cmd, cb)
|
|
||||||
},
|
|
||||||
function(stdout, stderr, cb) {
|
|
||||||
fs.renameSync('bindata_assetfs.go', assetsFileName)
|
|
||||||
fs.appendFileSync(assetsFileName, '\n')
|
|
||||||
fs.appendFileSync(assetsFileName, 'var UIReleaseTag = "' + buildType + '.' +
|
|
||||||
releaseTag + '"\n')
|
|
||||||
fs.appendFileSync(assetsFileName, 'var UICommitID = "' + commitId + '"\n')
|
|
||||||
fs.appendFileSync(assetsFileName, 'var UIVersion = "' + version + '"')
|
|
||||||
fs.appendFileSync(assetsFileName, '\n')
|
|
||||||
var contents;
|
|
||||||
if (isProduction) {
|
|
||||||
contents = fs.readFileSync(assetsFileName, 'utf8')
|
|
||||||
.replace(/_productionIndexHtml/g, '_productionIndexHTML')
|
|
||||||
.replace(/productionIndexHtmlBytes/g, 'productionIndexHTMLBytes')
|
|
||||||
.replace(/productionIndexHtml/g, 'productionIndexHTML')
|
|
||||||
.replace(/_productionIndex_bundleJs/g, '_productionIndexBundleJs')
|
|
||||||
.replace(/productionIndex_bundleJsBytes/g, 'productionIndexBundleJsBytes')
|
|
||||||
.replace(/productionIndex_bundleJs/g, 'productionIndexBundleJs')
|
|
||||||
.replace(/_productionJqueryUiMinJs/g, '_productionJqueryUIMinJs')
|
|
||||||
.replace(/productionJqueryUiMinJsBytes/g, 'productionJqueryUIMinJsBytes')
|
|
||||||
.replace(/productionJqueryUiMinJs/g, 'productionJqueryUIMinJs');
|
|
||||||
} else {
|
|
||||||
contents = fs.readFileSync(assetsFileName, 'utf8')
|
|
||||||
.replace(/_devIndexHtml/g, '_devIndexHTML')
|
|
||||||
.replace(/devIndexHtmlBytes/g, 'devIndexHTMLBytes')
|
|
||||||
.replace(/devIndexHtml/g, 'devIndexHTML')
|
|
||||||
.replace(/_devIndex_bundleJs/g, '_devIndexBundleJs')
|
|
||||||
.replace(/devIndex_bundleJsBytes/g, 'devIndexBundleJsBytes')
|
|
||||||
.replace(/devIndex_bundleJs/g, 'devIndexBundleJs')
|
|
||||||
.replace(/_devJqueryUiMinJs/g, '_devJqueryUIMinJs')
|
|
||||||
.replace(/devJqueryUiMinJsBytes/g, 'devJqueryUIMinJsBytes')
|
|
||||||
.replace(/devJqueryUiMinJs/g, 'devJqueryUIMinJs');
|
|
||||||
}
|
|
||||||
contents = contents.replace(/MINIO_UI_VERSION/g, version)
|
|
||||||
contents = contents.replace(/index_bundle.js/g, 'index_bundle-' + releaseTag + '.js')
|
|
||||||
|
|
||||||
fs.writeFileSync(assetsFileName, contents, 'utf8')
|
|
||||||
console.log('UI assets file :', assetsFileName)
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
], function(err) {
|
], function(err) {
|
||||||
if (err) return console.log(err)
|
if (err) return console.log(err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"dev": "NODE_ENV=dev webpack-dev-server --devtool cheap-module-eval-source-map --progress --colors --hot --content-base dev",
|
"dev": "NODE_ENV=dev webpack-dev-server --devtool cheap-module-eval-source-map --progress --colors --hot --content-base dev",
|
||||||
"build": "NODE_ENV=dev node build.js",
|
"build": "NODE_ENV=dev node build.js",
|
||||||
"release": "NODE_ENV=production MINIO_UI_BUILD=RELEASE node build.js",
|
"release": "NODE_ENV=production node build.js",
|
||||||
"format": "esformatter -i 'app/**/*.js'"
|
"format": "esformatter -i 'app/**/*.js'"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>MinIO Browser</title>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/minio/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="/minio/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/minio/favicon-16x16.png">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/minio/loader.css" type="text/css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="page-load">
|
||||||
|
<div class="pl-inner">
|
||||||
|
<img src="/minio/logo.svg" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<!--[if lt IE 11]>
|
||||||
|
<div class="ie-warning">
|
||||||
|
<div class="iw-inner">
|
||||||
|
<i class="iwi-icon fas fa-exclamation-triangle"></i>
|
||||||
|
|
||||||
|
You are using Internet Explorer version 12.0 or lower. Due to security issues and lack of support for Web Standards it is highly recommended that you upgrade to a modern browser
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.google.com/chrome/">
|
||||||
|
<img src="chrome.png" alt="">
|
||||||
|
<div>Chrome</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.mozilla.org/en-US/firefox/new/">
|
||||||
|
<img src="firefox.png" alt="">
|
||||||
|
<div>Firefox</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.apple.com/safari/">
|
||||||
|
<img src="safari.png" alt="">
|
||||||
|
<div>Safari</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="iwi-skip">Skip & Continue</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<script>currentUiVersion = 'MINIO_UI_VERSION'</script>
|
||||||
|
<script src="/minio/index_bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
.page-load {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #002a37;
|
||||||
|
z-index: 100;
|
||||||
|
transition: opacity 200ms;
|
||||||
|
-webkit-transition: opacity 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-0{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-1 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-inner {
|
||||||
|
position: absolute;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -50px;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -50px;
|
||||||
|
text-align: center;
|
||||||
|
-webkit-animation: fade-in 500ms;
|
||||||
|
animation: fade-in 500ms;
|
||||||
|
-webkit-animation-fill-mode: both;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
animation-delay: 350ms;
|
||||||
|
-webkit-animation-delay: 350ms;
|
||||||
|
-webkit-backface-visibility: visible;
|
||||||
|
backface-visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-inner:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
display: block;
|
||||||
|
-webkit-animation: spin 1000ms infinite linear;
|
||||||
|
animation: spin 1000ms infinite linear;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);;
|
||||||
|
border-left-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-inner > img {
|
||||||
|
width: 30px;
|
||||||
|
margin-top: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: rotate(0deg);
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: rotate(0deg);
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="93px" height="187px" viewBox="0 0 93 187" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>logo</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="logo" transform="translate(0.187500, -0.683594)" fill="#FFFFFF" fill-rule="nonzero">
|
||||||
|
<path d="M91.49,46.551 C86.7827023,38.7699609 82.062696,30.9966172 77.33,23.231 C74.87,19.231 72.33,15.231 69.88,11.231 C69.57,10.731 69.18,10.291 68.88,9.831 C64.35,2.931 55.44,-1.679 46.73,2.701 C42.9729806,4.51194908 40.0995718,7.75449451 38.7536428,11.7020516 C37.4077139,15.6496086 37.701799,19.9721186 39.57,23.701 C41.08,26.641 43.57,29.121 45.91,31.581 C53.03,39.141 60.38,46.491 67.45,54.111 C72.4175495,59.4492221 74.4526451,66.8835066 72.8965704,74.0075359 C71.3404956,81.1315653 66.390952,87.0402215 59.65,89.821 C59.4938176,89.83842 59.3361824,89.83842 59.18,89.821 L59.18,54.591 C46.6388051,61.0478363 35.3944735,69.759905 26.01,80.291 C11.32,96.671 2.64,117.141 0.01,132.071 L23.96,119.821 C31.96,115.771 39.86,111.821 48.14,107.581 L48.14,175.921 L59.14,187.131 L59.14,101.831 C59.14,101.831 59.39,101.711 60.22,101.261 C63.5480598,99.6738911 66.7772674,97.8873078 69.89,95.911 C77.7130888,90.4306687 82.7479457,81.8029342 83.6709542,72.295947 C84.5939627,62.7889599 81.3127806,53.3538429 74.69,46.471 C66.49,37.891 58.24,29.351 50.05,20.761 C47.67,18.261 47.72,15.101 50.05,12.881 C52.38,10.661 55.56,10.881 57.96,13.331 L61.38,16.781 C64.1,19.681 66.79,22.611 69.53,25.481 C76.4547149,32.7389629 83.3947303,39.9823123 90.35,47.211 C90.7,47.571 91.12,47.871 91.5,48.211 L91.93,47.951 C91.8351945,47.4695902 91.6876376,47.0000911 91.49,46.551 Z M48.11,94.931 C47.9883217,95.5022568 47.6230065,95.9917791 47.11,96.271 C42.72,98.601 38.29,100.871 33.87,103.141 L17.76,111.401 C24.771203,96.7435071 35.1132853,83.9289138 47.96,73.981 C48.08,74.221 48.16,74.301 48.16,74.381 C48.15,81.231 48.17,88.081 48.11,94.931 Z" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
@@ -21,7 +21,7 @@ _init() {
|
|||||||
|
|
||||||
## Minimum required versions for build dependencies
|
## Minimum required versions for build dependencies
|
||||||
GIT_VERSION="1.0"
|
GIT_VERSION="1.0"
|
||||||
GO_VERSION="1.13"
|
GO_VERSION="1.16"
|
||||||
OSX_VERSION="10.8"
|
OSX_VERSION="10.8"
|
||||||
KNAME=$(uname -s)
|
KNAME=$(uname -s)
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function _init() {
|
|||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
## List of architectures and OS to test coss compilation.
|
## List of architectures and OS to test coss compilation.
|
||||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64"
|
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64"
|
||||||
}
|
}
|
||||||
|
|
||||||
function _build() {
|
function _build() {
|
||||||
|
|||||||
@@ -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 100m "$d"
|
CGO_ENABLED=1 go test -v -tags kqueue -race --timeout 100m "$d"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
@@ -180,7 +179,7 @@ func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object, err := url.PathUnescape(vars["object"])
|
object, err := unescapePath(vars["object"])
|
||||||
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
|
||||||
@@ -244,7 +243,7 @@ func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object, err := url.PathUnescape(vars["object"])
|
object, err := unescapePath(vars["object"])
|
||||||
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
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -66,7 +68,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
accessKey := vars["accessKey"]
|
accessKey := vars["accessKey"]
|
||||||
|
|
||||||
ok, err := globalIAMSys.IsTempUser(accessKey)
|
ok, _, err := globalIAMSys.IsTempUser(accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -405,6 +407,18 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if implicitPerm && !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: accessKey,
|
||||||
|
Action: iampolicy.CreateUserAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", accessKey, claims),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: claims,
|
||||||
|
DenyOnly: true, // check if changing password is explicitly denied.
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
// More than maxConfigSize bytes were available
|
// More than maxConfigSize bytes were available
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
@@ -483,7 +497,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
parentUser = cred.ParentUser
|
parentUser = cred.ParentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
newCred, err := globalIAMSys.NewServiceAccount(ctx, parentUser, createReq.Policy)
|
newCred, err := globalIAMSys.NewServiceAccount(ctx, parentUser, cred.Groups, createReq.Policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -688,12 +702,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 +709,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)
|
||||||
@@ -963,7 +994,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
isGroup := vars["isGroup"] == "true"
|
isGroup := vars["isGroup"] == "true"
|
||||||
|
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
ok, err := globalIAMSys.IsTempUser(entityName)
|
ok, _, err := globalIAMSys.IsTempUser(entityName)
|
||||||
if err != nil && err != errNoSuchUser {
|
if err != nil && err != errNoSuchUser {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -259,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"`
|
||||||
@@ -296,7 +297,7 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
storageInfo, _ := objectAPI.StorageInfo(ctx)
|
storageInfo, _ := objectAPI.StorageInfo(ctx)
|
||||||
|
|
||||||
// Collect any disk healing.
|
// Collect any disk healing.
|
||||||
healing, _ := getAggregatedBackgroundHealState(ctx)
|
healing, _ := getAggregatedBackgroundHealState(ctx, nil)
|
||||||
healDisks := make(map[string]struct{}, len(healing.HealDisks))
|
healDisks := make(map[string]struct{}, len(healing.HealDisks))
|
||||||
for _, disk := range healing.HealDisks {
|
for _, disk := range healing.HealDisks {
|
||||||
healDisks[disk] = struct{}{}
|
healDisks[disk] = struct{}{}
|
||||||
@@ -860,16 +861,14 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
keepConnLive(w, r, respCh)
|
keepConnLive(w, r, respCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAggregatedBackgroundHealState(ctx context.Context) (madmin.BgHealState, error) {
|
// getAggregatedBackgroundHealState returns the heal state of disks.
|
||||||
var bgHealStates []madmin.BgHealState
|
// If no ObjectLayer is provided no set status is returned.
|
||||||
|
func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmin.BgHealState, error) {
|
||||||
localHealState, ok := getLocalBackgroundHealStatus()
|
|
||||||
if !ok {
|
|
||||||
return madmin.BgHealState{}, errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get local heal status first
|
// Get local heal status first
|
||||||
bgHealStates = append(bgHealStates, localHealState)
|
bgHealStates, ok := getBackgroundHealStatus(ctx, o)
|
||||||
|
if !ok {
|
||||||
|
return bgHealStates, errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Get heal status from other peers
|
// Get heal status from other peers
|
||||||
@@ -884,33 +883,10 @@ func getAggregatedBackgroundHealState(ctx context.Context) (madmin.BgHealState,
|
|||||||
if errCount == len(nerrs) {
|
if errCount == len(nerrs) {
|
||||||
return madmin.BgHealState{}, fmt.Errorf("all remote servers failed to report heal status, cluster is unhealthy")
|
return madmin.BgHealState{}, fmt.Errorf("all remote servers failed to report heal status, cluster is unhealthy")
|
||||||
}
|
}
|
||||||
bgHealStates = append(bgHealStates, peersHealStates...)
|
bgHealStates.Merge(peersHealStates...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggregate healing result
|
return bgHealStates, nil
|
||||||
var aggregatedHealStateResult = madmin.BgHealState{
|
|
||||||
ScannedItemsCount: bgHealStates[0].ScannedItemsCount,
|
|
||||||
LastHealActivity: bgHealStates[0].LastHealActivity,
|
|
||||||
NextHealRound: bgHealStates[0].NextHealRound,
|
|
||||||
HealDisks: bgHealStates[0].HealDisks,
|
|
||||||
}
|
|
||||||
|
|
||||||
bgHealStates = bgHealStates[1:]
|
|
||||||
|
|
||||||
for _, state := range bgHealStates {
|
|
||||||
aggregatedHealStateResult.ScannedItemsCount += state.ScannedItemsCount
|
|
||||||
aggregatedHealStateResult.HealDisks = append(aggregatedHealStateResult.HealDisks, state.HealDisks...)
|
|
||||||
if !state.LastHealActivity.IsZero() && aggregatedHealStateResult.LastHealActivity.Before(state.LastHealActivity) {
|
|
||||||
aggregatedHealStateResult.LastHealActivity = state.LastHealActivity
|
|
||||||
// The node which has the last heal activity means its
|
|
||||||
// is the node that is orchestrating self healing operations,
|
|
||||||
// which also means it is the same node which decides when
|
|
||||||
// the next self healing operation will be done.
|
|
||||||
aggregatedHealStateResult.NextHealRound = state.NextHealRound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return aggregatedHealStateResult, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -929,7 +905,7 @@ func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregateHealStateResult, err := getAggregatedBackgroundHealState(r.Context())
|
aggregateHealStateResult, err := getAggregatedBackgroundHealState(r.Context(), objectAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1347,8 +1323,10 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
deadlinedCtx, cancel := context.WithTimeout(ctx, deadline)
|
deadlinedCtx, cancel := context.WithTimeout(ctx, deadline)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
var err error
|
||||||
nsLock := objectAPI.NewNSLock(minioMetaBucket, "health-check-in-progress")
|
nsLock := objectAPI.NewNSLock(minioMetaBucket, "health-check-in-progress")
|
||||||
if err := nsLock.GetLock(ctx, newDynamicTimeout(deadline, deadline)); err != nil { // returns a locked lock
|
ctx, err = nsLock.GetLock(ctx, newDynamicTimeout(deadline, deadline))
|
||||||
|
if err != nil { // returns a locked lock
|
||||||
errResp(err)
|
errResp(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1544,19 +1522,19 @@ 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 {
|
||||||
ldapConn, err := globalLDAPConfig.Connect()
|
ldapConn, err := globalLDAPConfig.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ldap.Status = "offline"
|
ldap.Status = string(madmin.ItemOffline)
|
||||||
} else if ldapConn == nil {
|
} else if ldapConn == nil {
|
||||||
ldap.Status = "Not Configured"
|
ldap.Status = "Not Configured"
|
||||||
} else {
|
} else {
|
||||||
// Close ldap connection to avoid leaks.
|
// Close ldap connection to avoid leaks.
|
||||||
ldapConn.Close()
|
ldapConn.Close()
|
||||||
ldap.Status = "online"
|
ldap.Status = string(madmin.ItemOnline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1565,12 +1543,14 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
// Get the notification target info
|
// Get the notification target info
|
||||||
notifyTarget := fetchLambdaInfo()
|
notifyTarget := fetchLambdaInfo()
|
||||||
|
|
||||||
server := getLocalServerProperty(globalEndpoints, r)
|
local := getLocalServerProperty(globalEndpoints, r)
|
||||||
servers := globalNotificationSys.ServerInfo()
|
servers := globalNotificationSys.ServerInfo()
|
||||||
servers = append(servers, server)
|
servers = append(servers, local)
|
||||||
|
|
||||||
|
assignPoolNumbers(servers)
|
||||||
|
|
||||||
var backend interface{}
|
var backend interface{}
|
||||||
mode := madmin.ObjectLayerInitializing
|
mode := madmin.ItemInitializing
|
||||||
|
|
||||||
buckets := madmin.Buckets{}
|
buckets := madmin.Buckets{}
|
||||||
objects := madmin.Objects{}
|
objects := madmin.Objects{}
|
||||||
@@ -1578,18 +1558,22 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI != nil {
|
if objectAPI != nil {
|
||||||
mode = madmin.ObjectLayerOnline
|
mode = madmin.ItemOnline
|
||||||
// Load data usage
|
// Load data usage
|
||||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
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
|
||||||
backendInfo := objectAPI.BackendInfo()
|
backendInfo := objectAPI.BackendInfo()
|
||||||
if backendInfo.Type == BackendType(madmin.Erasure) {
|
if backendInfo.Type == madmin.Erasure {
|
||||||
// Calculate the number of online/offline disks of all nodes
|
// Calculate the number of online/offline disks of all nodes
|
||||||
var allDisks []madmin.Disk
|
var allDisks []madmin.Disk
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
@@ -1613,7 +1597,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,
|
||||||
@@ -1621,7 +1605,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
infoMsg := madmin.InfoMessage{
|
infoMsg := madmin.InfoMessage{
|
||||||
Mode: mode,
|
Mode: string(mode),
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
Region: globalServerRegion,
|
Region: globalServerRegion,
|
||||||
SQSARN: globalNotificationSys.GetARNList(false),
|
SQSARN: globalNotificationSys.GetARNList(false),
|
||||||
@@ -1646,6 +1630,22 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
writeSuccessResponseJSON(w, jsonBytes)
|
writeSuccessResponseJSON(w, jsonBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assignPoolNumbers(servers []madmin.ServerProperties) {
|
||||||
|
for i := range servers {
|
||||||
|
for idx, ge := range globalEndpoints {
|
||||||
|
for _, endpoint := range ge.Endpoints {
|
||||||
|
if servers[i].Endpoint == endpoint.Host {
|
||||||
|
servers[i].PoolNumber = idx + 1
|
||||||
|
} else if host, err := xnet.ParseHost(servers[i].Endpoint); err == nil {
|
||||||
|
if host.Name == endpoint.Hostname() {
|
||||||
|
servers[i].PoolNumber = idx + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
||||||
|
|
||||||
lambdaMap := make(map[string][]madmin.TargetIDStatus)
|
lambdaMap := make(map[string][]madmin.TargetIDStatus)
|
||||||
@@ -1655,9 +1655,9 @@ func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
|||||||
active, _ := tgt.IsActive()
|
active, _ := tgt.IsActive()
|
||||||
targetID := tgt.ID()
|
targetID := tgt.ID()
|
||||||
if active {
|
if active {
|
||||||
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"}
|
targetIDStatus[targetID.ID] = madmin.Status{Status: string(madmin.ItemOnline)}
|
||||||
} else {
|
} else {
|
||||||
targetIDStatus[targetID.ID] = madmin.Status{Status: "Offline"}
|
targetIDStatus[targetID.ID] = madmin.Status{Status: string(madmin.ItemOffline)}
|
||||||
}
|
}
|
||||||
list := lambdaMap[targetID.Name]
|
list := lambdaMap[targetID.Name]
|
||||||
list = append(list, targetIDStatus)
|
list = append(list, targetIDStatus)
|
||||||
@@ -1669,9 +1669,9 @@ func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
|
|||||||
active, _ := tgt.IsActive()
|
active, _ := tgt.IsActive()
|
||||||
targetID := tgt.ID()
|
targetID := tgt.ID()
|
||||||
if active {
|
if active {
|
||||||
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"}
|
targetIDStatus[targetID.ID] = madmin.Status{Status: string(madmin.ItemOnline)}
|
||||||
} else {
|
} else {
|
||||||
targetIDStatus[targetID.ID] = madmin.Status{Status: "Offline"}
|
targetIDStatus[targetID.ID] = madmin.Status{Status: string(madmin.ItemOffline)}
|
||||||
}
|
}
|
||||||
list := lambdaMap[targetID.Name]
|
list := lambdaMap[targetID.Name]
|
||||||
list = append(list, targetIDStatus)
|
list = append(list, targetIDStatus)
|
||||||
@@ -1689,47 +1689,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 = string(madmin.ItemOffline)
|
||||||
} else {
|
} else {
|
||||||
vault.Status = "online"
|
kmsStat.Status = string(madmin.ItemOnline)
|
||||||
|
|
||||||
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 = "success"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = "success"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vault
|
return kmsStat
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchLoggerDetails return log info
|
// fetchLoggerDetails return log info
|
||||||
@@ -1742,11 +1741,11 @@ func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
|||||||
err := checkConnection(target.Endpoint(), 15*time.Second)
|
err := checkConnection(target.Endpoint(), 15*time.Second)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mapLog := make(map[string]madmin.Status)
|
mapLog := make(map[string]madmin.Status)
|
||||||
mapLog[tgt] = madmin.Status{Status: "Online"}
|
mapLog[tgt] = madmin.Status{Status: string(madmin.ItemOnline)}
|
||||||
loggerInfo = append(loggerInfo, mapLog)
|
loggerInfo = append(loggerInfo, mapLog)
|
||||||
} else {
|
} else {
|
||||||
mapLog := make(map[string]madmin.Status)
|
mapLog := make(map[string]madmin.Status)
|
||||||
mapLog[tgt] = madmin.Status{Status: "offline"}
|
mapLog[tgt] = madmin.Status{Status: string(madmin.ItemOffline)}
|
||||||
loggerInfo = append(loggerInfo, mapLog)
|
loggerInfo = append(loggerInfo, mapLog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1758,11 +1757,11 @@ func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
|||||||
err := checkConnection(target.Endpoint(), 15*time.Second)
|
err := checkConnection(target.Endpoint(), 15*time.Second)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mapAudit := make(map[string]madmin.Status)
|
mapAudit := make(map[string]madmin.Status)
|
||||||
mapAudit[tgt] = madmin.Status{Status: "Online"}
|
mapAudit[tgt] = madmin.Status{Status: string(madmin.ItemOnline)}
|
||||||
auditloggerInfo = append(auditloggerInfo, mapAudit)
|
auditloggerInfo = append(auditloggerInfo, mapAudit)
|
||||||
} else {
|
} else {
|
||||||
mapAudit := make(map[string]madmin.Status)
|
mapAudit := make(map[string]madmin.Status)
|
||||||
mapAudit[tgt] = madmin.Status{Status: "Offline"}
|
mapAudit[tgt] = madmin.Status{Status: string(madmin.ItemOffline)}
|
||||||
auditloggerInfo = append(auditloggerInfo, mapAudit)
|
auditloggerInfo = append(auditloggerInfo, mapAudit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -90,8 +89,9 @@ type allHealState struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
// map of heal path to heal sequence
|
// map of heal path to heal sequence
|
||||||
healSeqMap map[string]*healSequence
|
healSeqMap map[string]*healSequence // Indexed by endpoint
|
||||||
healLocalDisks map[Endpoint]struct{}
|
healLocalDisks map[Endpoint]struct{}
|
||||||
|
healStatus map[string]healingTracker // Indexed by disk ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHealState - initialize global heal state management
|
// newHealState - initialize global heal state management
|
||||||
@@ -99,6 +99,7 @@ func newHealState(cleanup bool) *allHealState {
|
|||||||
hstate := &allHealState{
|
hstate := &allHealState{
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
healLocalDisks: map[Endpoint]struct{}{},
|
healLocalDisks: map[Endpoint]struct{}{},
|
||||||
|
healStatus: make(map[string]healingTracker),
|
||||||
}
|
}
|
||||||
if cleanup {
|
if cleanup {
|
||||||
go hstate.periodicHealSeqsClean(GlobalContext)
|
go hstate.periodicHealSeqsClean(GlobalContext)
|
||||||
@@ -113,7 +114,56 @@ func (ahs *allHealState) healDriveCount() int {
|
|||||||
return len(ahs.healLocalDisks)
|
return len(ahs.healLocalDisks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) getHealLocalDisks() Endpoints {
|
func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||||
|
ahs.Lock()
|
||||||
|
defer ahs.Unlock()
|
||||||
|
|
||||||
|
for _, ep := range healLocalDisks {
|
||||||
|
delete(ahs.healLocalDisks, ep)
|
||||||
|
}
|
||||||
|
for id, disk := range ahs.healStatus {
|
||||||
|
for _, ep := range healLocalDisks {
|
||||||
|
if disk.Endpoint == ep.String() {
|
||||||
|
delete(ahs.healStatus, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateHealStatus will update the heal status.
|
||||||
|
func (ahs *allHealState) updateHealStatus(tracker *healingTracker) {
|
||||||
|
ahs.Lock()
|
||||||
|
defer ahs.Unlock()
|
||||||
|
ahs.healStatus[tracker.ID] = *tracker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by zone, set and disk index
|
||||||
|
func sortDisks(disks []madmin.Disk) {
|
||||||
|
sort.Slice(disks, func(i, j int) bool {
|
||||||
|
a, b := &disks[i], &disks[j]
|
||||||
|
if a.PoolIndex != b.PoolIndex {
|
||||||
|
return a.PoolIndex < b.PoolIndex
|
||||||
|
}
|
||||||
|
if a.SetIndex != b.SetIndex {
|
||||||
|
return a.SetIndex < b.SetIndex
|
||||||
|
}
|
||||||
|
return a.DiskIndex < b.DiskIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLocalHealingDisks returns local healing disks indexed by endpoint.
|
||||||
|
func (ahs *allHealState) getLocalHealingDisks() map[string]madmin.HealingDisk {
|
||||||
|
ahs.RLock()
|
||||||
|
defer ahs.RUnlock()
|
||||||
|
dst := make(map[string]madmin.HealingDisk, len(ahs.healStatus))
|
||||||
|
for _, v := range ahs.healStatus {
|
||||||
|
dst[v.Endpoint] = v.toHealingDisk()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ahs *allHealState) getHealLocalDiskEndpoints() Endpoints {
|
||||||
ahs.RLock()
|
ahs.RLock()
|
||||||
defer ahs.RUnlock()
|
defer ahs.RUnlock()
|
||||||
|
|
||||||
@@ -124,15 +174,6 @@ func (ahs *allHealState) getHealLocalDisks() Endpoints {
|
|||||||
return endpoints
|
return endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
|
|
||||||
ahs.Lock()
|
|
||||||
defer ahs.Unlock()
|
|
||||||
|
|
||||||
for _, ep := range healLocalDisks {
|
|
||||||
delete(ahs.healLocalDisks, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
defer ahs.Unlock()
|
defer ahs.Unlock()
|
||||||
@@ -662,6 +703,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
|
||||||
@@ -834,11 +882,6 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
|
|||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip metacache entries healing
|
|
||||||
if strings.HasPrefix(object, "buckets/.minio.sys/.metacache/") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := h.queueHealTask(healSource{
|
err := h.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: object,
|
object: object,
|
||||||
|
|||||||
@@ -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:.*}")
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,65 +42,38 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
}
|
}
|
||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal {
|
||||||
// Only proceed for local endpoints
|
// Only proceed for local endpoints
|
||||||
network[nodeName] = "online"
|
network[nodeName] = string(madmin.ItemOnline)
|
||||||
localEndpoints = append(localEndpoints, endpoint)
|
localEndpoints = append(localEndpoints, endpoint)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, present := network[nodeName]
|
_, present := network[nodeName]
|
||||||
if !present {
|
if !present {
|
||||||
if err := isServerResolvable(endpoint, time.Second); err == nil {
|
if err := isServerResolvable(endpoint, 2*time.Second); err == nil {
|
||||||
network[nodeName] = "online"
|
network[nodeName] = string(madmin.ItemOnline)
|
||||||
} else {
|
} else {
|
||||||
network[nodeName] = "offline"
|
network[nodeName] = string(madmin.ItemOffline)
|
||||||
|
// log once the error
|
||||||
|
logger.LogOnceIf(context.Background(), err, nodeName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localDisks, _ := initStorageDisksWithErrors(localEndpoints)
|
props := madmin.ServerProperties{
|
||||||
defer closeStorageDisks(localDisks)
|
State: string(madmin.ItemInitializing),
|
||||||
|
|
||||||
storageInfo, _ := getStorageInfo(localDisks, localEndpoints.GetAllStrings())
|
|
||||||
|
|
||||||
return madmin.ServerProperties{
|
|
||||||
State: "ok",
|
|
||||||
Endpoint: addr,
|
Endpoint: addr,
|
||||||
Uptime: UTCNow().Unix() - globalBootTime.Unix(),
|
Uptime: UTCNow().Unix() - globalBootTime.Unix(),
|
||||||
Version: Version,
|
Version: Version,
|
||||||
CommitID: CommitID,
|
CommitID: CommitID,
|
||||||
Network: network,
|
Network: network,
|
||||||
Disks: storageInfo.Disks,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func getLocalDisks(endpointServerPools EndpointServerPools) []madmin.Disk {
|
objLayer := newObjectLayerFn()
|
||||||
var localEndpoints Endpoints
|
if objLayer != nil {
|
||||||
network := make(map[string]string)
|
storageInfo, _ := objLayer.LocalStorageInfo(GlobalContext)
|
||||||
|
props.State = string(madmin.ItemOnline)
|
||||||
for _, ep := range endpointServerPools {
|
props.Disks = storageInfo.Disks
|
||||||
for _, endpoint := range ep.Endpoints {
|
|
||||||
nodeName := endpoint.Host
|
|
||||||
if nodeName == "" {
|
|
||||||
nodeName = "localhost"
|
|
||||||
}
|
|
||||||
if endpoint.IsLocal {
|
|
||||||
// Only proceed for local endpoints
|
|
||||||
network[nodeName] = "online"
|
|
||||||
localEndpoints = append(localEndpoints, endpoint)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, present := network[nodeName]
|
|
||||||
if !present {
|
|
||||||
if err := isServerResolvable(endpoint, time.Second); err == nil {
|
|
||||||
network[nodeName] = "online"
|
|
||||||
} else {
|
|
||||||
network[nodeName] = "offline"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
localDisks, _ := initStorageDisksWithErrors(localEndpoints)
|
|
||||||
defer closeStorageDisks(localDisks)
|
return props
|
||||||
storageInfo, _ := getStorageInfo(localDisks, localEndpoints.GetAllStrings())
|
|
||||||
return storageInfo.Disks
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -177,21 +179,25 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
if objInfo.VersionID != "" {
|
if objInfo.VersionID != "" {
|
||||||
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
||||||
}
|
}
|
||||||
|
|
||||||
if objInfo.ReplicationStatus.String() != "" {
|
if objInfo.ReplicationStatus.String() != "" {
|
||||||
w.Header()[xhttp.AmzBucketReplicationStatus] = []string{objInfo.ReplicationStatus.String()}
|
w.Header()[xhttp.AmzBucketReplicationStatus] = []string{objInfo.ReplicationStatus.String()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
||||||
ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
|
if opts.VersionID == "" {
|
||||||
Name: objInfo.Name,
|
if ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
|
||||||
UserTags: objInfo.UserTags,
|
Name: objInfo.Name,
|
||||||
VersionID: objInfo.VersionID,
|
UserTags: objInfo.UserTags,
|
||||||
ModTime: objInfo.ModTime,
|
VersionID: objInfo.VersionID,
|
||||||
IsLatest: objInfo.IsLatest,
|
ModTime: objInfo.ModTime,
|
||||||
DeleteMarker: objInfo.DeleteMarker,
|
IsLatest: objInfo.IsLatest,
|
||||||
})
|
DeleteMarker: objInfo.DeleteMarker,
|
||||||
if !expiryTime.IsZero() {
|
SuccessorModTime: objInfo.SuccessorModTime,
|
||||||
w.Header()[xhttp.AmzExpiration] = []string{
|
}); !expiryTime.IsZero() {
|
||||||
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
|
w.Header()[xhttp.AmzExpiration] = []string{
|
||||||
|
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if objInfo.TransitionStatus == lifecycle.TransitionComplete {
|
if objInfo.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
|
|||||||
@@ -36,8 +36,19 @@ func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string,
|
|||||||
maxkeys = maxObjectList
|
maxkeys = maxObjectList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = values.Get("prefix")
|
var err error
|
||||||
marker = values.Get("marker")
|
prefix, err = unescapePath(values.Get("prefix"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
marker, err = unescapePath(values.Get("marker"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
return
|
return
|
||||||
@@ -56,8 +67,19 @@ func getListBucketObjectVersionsArgs(values url.Values) (prefix, marker, delimit
|
|||||||
maxkeys = maxObjectList
|
maxkeys = maxObjectList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = values.Get("prefix")
|
var err error
|
||||||
marker = values.Get("key-marker")
|
prefix, err = unescapePath(values.Get("prefix"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
marker, err = unescapePath(values.Get("key-marker"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
versionIDMarker = values.Get("version-id-marker")
|
versionIDMarker = values.Get("version-id-marker")
|
||||||
@@ -86,8 +108,19 @@ func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimit
|
|||||||
maxkeys = maxObjectList
|
maxkeys = maxObjectList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = values.Get("prefix")
|
var err error
|
||||||
startAfter = values.Get("start-after")
|
prefix, err = unescapePath(values.Get("prefix"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startAfter, err = unescapePath(values.Get("start-after"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
fetchOwner = values.Get("fetch-owner") == "true"
|
fetchOwner = values.Get("fetch-owner") == "true"
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
@@ -117,8 +150,19 @@ func getBucketMultipartResources(values url.Values) (prefix, keyMarker, uploadID
|
|||||||
maxUploads = maxUploadsList
|
maxUploads = maxUploadsList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = values.Get("prefix")
|
var err error
|
||||||
keyMarker = values.Get("key-marker")
|
prefix, err = unescapePath(values.Get("prefix"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyMarker, err = unescapePath(values.Get("key-marker"))
|
||||||
|
if err != nil {
|
||||||
|
errCode = ErrInvalidRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
uploadIDMarker = values.Get("upload-id-marker")
|
uploadIDMarker = values.Get("upload-id-marker")
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ type LocationResponse struct {
|
|||||||
Location string `xml:",chardata"`
|
Location string `xml:",chardata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PolicyStatus captures information returned by GetBucketPolicyStatusHandler
|
||||||
|
type PolicyStatus struct {
|
||||||
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ PolicyStatus" json:"-"`
|
||||||
|
IsPublic string
|
||||||
|
}
|
||||||
|
|
||||||
// ListVersionsResponse - format for list bucket versions response.
|
// ListVersionsResponse - format for list bucket versions response.
|
||||||
type ListVersionsResponse struct {
|
type ListVersionsResponse struct {
|
||||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListVersionsResult" json:"-"`
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListVersionsResult" json:"-"`
|
||||||
@@ -410,9 +416,11 @@ func getObjectLocation(r *http.Request, domains []string, bucket, object string)
|
|||||||
func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
||||||
listbuckets := make([]Bucket, 0, len(buckets))
|
listbuckets := make([]Bucket, 0, len(buckets))
|
||||||
var data = ListBucketsResponse{}
|
var data = ListBucketsResponse{}
|
||||||
var owner = Owner{}
|
var owner = Owner{
|
||||||
|
ID: globalMinioDefaultOwnerID,
|
||||||
|
DisplayName: "minio",
|
||||||
|
}
|
||||||
|
|
||||||
owner.ID = globalMinioDefaultOwnerID
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
var listbucket = Bucket{}
|
var listbucket = Bucket{}
|
||||||
listbucket.Name = bucket.Name
|
listbucket.Name = bucket.Name
|
||||||
@@ -429,10 +437,12 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
var owner = Owner{}
|
var owner = Owner{
|
||||||
|
ID: globalMinioDefaultOwnerID,
|
||||||
|
DisplayName: "minio",
|
||||||
|
}
|
||||||
var data = ListVersionsResponse{}
|
var data = ListVersionsResponse{}
|
||||||
|
|
||||||
owner.ID = globalMinioDefaultOwnerID
|
|
||||||
for _, object := range resp.Objects {
|
for _, object := range resp.Objects {
|
||||||
var content = ObjectVersion{}
|
var content = ObjectVersion{}
|
||||||
if object.Name == "" {
|
if object.Name == "" {
|
||||||
@@ -485,10 +495,12 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
||||||
contents := make([]Object, 0, len(resp.Objects))
|
contents := make([]Object, 0, len(resp.Objects))
|
||||||
var owner = Owner{}
|
var owner = Owner{
|
||||||
|
ID: globalMinioDefaultOwnerID,
|
||||||
|
DisplayName: "minio",
|
||||||
|
}
|
||||||
var data = ListObjectsResponse{}
|
var data = ListObjectsResponse{}
|
||||||
|
|
||||||
owner.ID = globalMinioDefaultOwnerID
|
|
||||||
for _, object := range resp.Objects {
|
for _, object := range resp.Objects {
|
||||||
var content = Object{}
|
var content = Object{}
|
||||||
if object.Name == "" {
|
if object.Name == "" {
|
||||||
@@ -532,12 +544,11 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata bool) ListObjectsV2Response {
|
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata bool) ListObjectsV2Response {
|
||||||
contents := make([]Object, 0, len(objects))
|
contents := make([]Object, 0, len(objects))
|
||||||
var owner = Owner{}
|
var owner = Owner{
|
||||||
var data = ListObjectsV2Response{}
|
ID: globalMinioDefaultOwnerID,
|
||||||
|
DisplayName: "minio",
|
||||||
if fetchOwner {
|
|
||||||
owner.ID = globalMinioDefaultOwnerID
|
|
||||||
}
|
}
|
||||||
|
var data = ListObjectsV2Response{}
|
||||||
|
|
||||||
for _, object := range objects {
|
for _, object := range objects {
|
||||||
var content = Object{}
|
var content = Object{}
|
||||||
@@ -565,7 +576,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 +650,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
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("deleteobject", maxClients(httpTraceAll(api.DeleteObjectHandler))))
|
collectAPIStats("deleteobject", maxClients(httpTraceAll(api.DeleteObjectHandler))))
|
||||||
|
|
||||||
|
// PostRestoreObject
|
||||||
|
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
||||||
|
collectAPIStats("restoreobject", maxClients(httpTraceAll(api.PostRestoreObjectHandler)))).Queries("restore", "")
|
||||||
|
|
||||||
/// Bucket operations
|
/// Bucket operations
|
||||||
// GetBucketLocation
|
// GetBucketLocation
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
@@ -262,9 +266,9 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// ListObjectVersions
|
// ListObjectVersions
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectversions", maxClients(httpTraceAll(api.ListObjectVersionsHandler)))).Queries("versions", "")
|
collectAPIStats("listobjectversions", maxClients(httpTraceAll(api.ListObjectVersionsHandler)))).Queries("versions", "")
|
||||||
// ListObjectsV1 (Legacy)
|
// GetBucketPolicyStatus
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectsv1", maxClients(httpTraceAll(api.ListObjectsV1Handler))))
|
collectAPIStats("getpolicystatus", maxClients(httpTraceAll(api.GetBucketPolicyStatusHandler)))).Queries("policyStatus", "")
|
||||||
// PutBucketLifecycle
|
// PutBucketLifecycle
|
||||||
bucket.Methods(http.MethodPut).HandlerFunc(
|
bucket.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketlifecycle", maxClients(httpTraceAll(api.PutBucketLifecycleHandler)))).Queries("lifecycle", "")
|
collectAPIStats("putbucketlifecycle", maxClients(httpTraceAll(api.PutBucketLifecycleHandler)))).Queries("lifecycle", "")
|
||||||
@@ -320,9 +324,9 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// DeleteBucket
|
// DeleteBucket
|
||||||
bucket.Methods(http.MethodDelete).HandlerFunc(
|
bucket.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebucket", maxClients(httpTraceAll(api.DeleteBucketHandler))))
|
collectAPIStats("deletebucket", maxClients(httpTraceAll(api.DeleteBucketHandler))))
|
||||||
// PostRestoreObject
|
// ListObjectsV1 (Legacy)
|
||||||
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("restoreobject", maxClients(httpTraceAll(api.PostRestoreObjectHandler)))).Queries("restore", "")
|
collectAPIStats("listobjectsv1", maxClients(httpTraceAll(api.ListObjectsV1Handler))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root operation
|
/// Root operation
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
|
"github.com/minio/minio/pkg/etag"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
@@ -185,12 +186,12 @@ func getSessionToken(r *http.Request) (token string) {
|
|||||||
// Fetch claims in the security token returned by the client, doesn't return
|
// Fetch claims in the security token returned by the client, doesn't return
|
||||||
// errors - upon errors the returned claims map will be empty.
|
// errors - upon errors the returned claims map will be empty.
|
||||||
func mustGetClaimsFromToken(r *http.Request) map[string]interface{} {
|
func mustGetClaimsFromToken(r *http.Request) map[string]interface{} {
|
||||||
claims, _ := getClaimsFromToken(r, getSessionToken(r))
|
claims, _ := getClaimsFromToken(getSessionToken(r))
|
||||||
return claims
|
return claims
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch claims in the security token returned by the client.
|
// Fetch claims in the security token returned by the client.
|
||||||
func getClaimsFromToken(r *http.Request, token string) (map[string]interface{}, error) {
|
func getClaimsFromToken(token string) (map[string]interface{}, error) {
|
||||||
claims := xjwt.NewMapClaims()
|
claims := xjwt.NewMapClaims()
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return claims.Map(), nil
|
return claims.Map(), nil
|
||||||
@@ -237,7 +238,7 @@ func getClaimsFromToken(r *http.Request, token string) (map[string]interface{},
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Base64 decoding fails, we should log to indicate
|
// Base64 decoding fails, we should log to indicate
|
||||||
// something is malforming the request sent by client.
|
// something is malforming the request sent by client.
|
||||||
logger.LogIf(r.Context(), err, logger.Application)
|
logger.LogIf(GlobalContext, err, logger.Application)
|
||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
|
claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
|
||||||
@@ -258,7 +259,7 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
|||||||
if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
|
if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
|
||||||
return nil, ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
claims, err := getClaimsFromToken(r, token)
|
claims, err := getClaimsFromToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, toAPIErrorCode(r.Context(), err)
|
return nil, toAPIErrorCode(r.Context(), err)
|
||||||
}
|
}
|
||||||
@@ -430,19 +431,14 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
return errCode
|
return errCode
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
clientETag, err := etag.FromContentMD5(r.Header)
|
||||||
err error
|
|
||||||
contentMD5, contentSHA256 []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
// Extract 'Content-Md5' if present.
|
|
||||||
contentMD5, err = checkValidMD5(r.Header)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrInvalidDigest
|
return ErrInvalidDigest
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract either 'X-Amz-Content-Sha256' header or 'X-Amz-Content-Sha256' query parameter (if V4 presigned)
|
// Extract either 'X-Amz-Content-Sha256' header or 'X-Amz-Content-Sha256' query parameter (if V4 presigned)
|
||||||
// Do not verify 'X-Amz-Content-Sha256' if skipSHA256.
|
// Do not verify 'X-Amz-Content-Sha256' if skipSHA256.
|
||||||
|
var contentSHA256 []byte
|
||||||
if skipSHA256 := skipContentSha256Cksum(r); !skipSHA256 && isRequestPresignedSignatureV4(r) {
|
if skipSHA256 := skipContentSha256Cksum(r); !skipSHA256 && isRequestPresignedSignatureV4(r) {
|
||||||
if sha256Sum, ok := r.URL.Query()[xhttp.AmzContentSha256]; ok && len(sha256Sum) > 0 {
|
if sha256Sum, ok := r.URL.Query()[xhttp.AmzContentSha256]; ok && len(sha256Sum) > 0 {
|
||||||
contentSHA256, err = hex.DecodeString(sha256Sum[0])
|
contentSHA256, err = hex.DecodeString(sha256Sum[0])
|
||||||
@@ -459,8 +455,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, clientETag.String(), 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 +493,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)
|
||||||
|
|||||||
@@ -17,16 +17,23 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/color"
|
"github.com/minio/minio/pkg/color"
|
||||||
"github.com/minio/minio/pkg/console"
|
"github.com/minio/minio/pkg/console"
|
||||||
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -35,10 +42,200 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE -unexported
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
type healingTracker struct {
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// future add more tracking capabilities
|
// healingTracker is used to persist healing information during a heal.
|
||||||
|
type healingTracker struct {
|
||||||
|
disk StorageAPI `msg:"-"`
|
||||||
|
|
||||||
|
ID string
|
||||||
|
PoolIndex int
|
||||||
|
SetIndex int
|
||||||
|
DiskIndex int
|
||||||
|
Path string
|
||||||
|
Endpoint string
|
||||||
|
Started time.Time
|
||||||
|
LastUpdate time.Time
|
||||||
|
ObjectsHealed uint64
|
||||||
|
ObjectsFailed uint64
|
||||||
|
BytesDone uint64
|
||||||
|
BytesFailed uint64
|
||||||
|
|
||||||
|
// Last object scanned.
|
||||||
|
Bucket string `json:"-"`
|
||||||
|
Object string `json:"-"`
|
||||||
|
|
||||||
|
// Numbers when current bucket started healing,
|
||||||
|
// for resuming with correct numbers.
|
||||||
|
ResumeObjectsHealed uint64 `json:"-"`
|
||||||
|
ResumeObjectsFailed uint64 `json:"-"`
|
||||||
|
ResumeBytesDone uint64 `json:"-"`
|
||||||
|
ResumeBytesFailed uint64 `json:"-"`
|
||||||
|
|
||||||
|
// Filled on startup/restarts.
|
||||||
|
QueuedBuckets []string
|
||||||
|
|
||||||
|
// Filled during heal.
|
||||||
|
HealedBuckets []string
|
||||||
|
// Add future tracking capabilities
|
||||||
|
// Be sure that they are included in toHealingDisk
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadHealingTracker will load the healing tracker from the supplied disk.
|
||||||
|
// The disk ID will be validated against the loaded one.
|
||||||
|
func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker, error) {
|
||||||
|
if disk == nil {
|
||||||
|
return nil, errors.New("loadHealingTracker: nil disk given")
|
||||||
|
}
|
||||||
|
diskID, err := disk.GetDiskID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b, err := disk.ReadAll(ctx, minioMetaBucket,
|
||||||
|
pathJoin(bucketMetaPrefix, slashSeparator, healingTrackerFilename))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var h healingTracker
|
||||||
|
_, err = h.UnmarshalMsg(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if h.ID != diskID && h.ID != "" {
|
||||||
|
return nil, fmt.Errorf("loadHealingTracker: disk id mismatch expected %s, got %s", h.ID, diskID)
|
||||||
|
}
|
||||||
|
h.disk = disk
|
||||||
|
h.ID = diskID
|
||||||
|
return &h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHealingTracker will create a new healing tracker for the disk.
|
||||||
|
func newHealingTracker(disk StorageAPI) *healingTracker {
|
||||||
|
diskID, _ := disk.GetDiskID()
|
||||||
|
h := healingTracker{
|
||||||
|
disk: disk,
|
||||||
|
ID: diskID,
|
||||||
|
Path: disk.String(),
|
||||||
|
Endpoint: disk.Endpoint().String(),
|
||||||
|
Started: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
h.PoolIndex, h.SetIndex, h.DiskIndex = disk.GetDiskLoc()
|
||||||
|
return &h
|
||||||
|
}
|
||||||
|
|
||||||
|
// update will update the tracker on the disk.
|
||||||
|
// If the tracker has been deleted an error is returned.
|
||||||
|
func (h *healingTracker) update(ctx context.Context) error {
|
||||||
|
if h.disk.Healing() == nil {
|
||||||
|
return fmt.Errorf("healingTracker: disk %q is not marked as healing", h.ID)
|
||||||
|
}
|
||||||
|
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
||||||
|
h.ID, _ = h.disk.GetDiskID()
|
||||||
|
h.PoolIndex, h.SetIndex, h.DiskIndex = h.disk.GetDiskLoc()
|
||||||
|
}
|
||||||
|
return h.save(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save will unconditionally save the tracker and will be created if not existing.
|
||||||
|
func (h *healingTracker) save(ctx context.Context) error {
|
||||||
|
if h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
||||||
|
// Attempt to get location.
|
||||||
|
if api := newObjectLayerFn(); api != nil {
|
||||||
|
if ep, ok := api.(*erasureServerPools); ok {
|
||||||
|
h.PoolIndex, h.SetIndex, h.DiskIndex, _ = ep.getPoolAndSet(h.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.LastUpdate = time.Now().UTC()
|
||||||
|
htrackerBytes, err := h.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
globalBackgroundHealState.updateHealStatus(h)
|
||||||
|
return h.disk.WriteAll(ctx, minioMetaBucket,
|
||||||
|
pathJoin(bucketMetaPrefix, slashSeparator, healingTrackerFilename),
|
||||||
|
htrackerBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the tracker on disk.
|
||||||
|
func (h *healingTracker) delete(ctx context.Context) error {
|
||||||
|
return h.disk.Delete(ctx, minioMetaBucket,
|
||||||
|
pathJoin(bucketMetaPrefix, slashSeparator, healingTrackerFilename),
|
||||||
|
false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healingTracker) isHealed(bucket string) bool {
|
||||||
|
for _, v := range h.HealedBuckets {
|
||||||
|
if v == bucket {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// resume will reset progress to the numbers at the start of the bucket.
|
||||||
|
func (h *healingTracker) resume() {
|
||||||
|
h.ObjectsHealed = h.ResumeObjectsHealed
|
||||||
|
h.ObjectsFailed = h.ResumeObjectsFailed
|
||||||
|
h.BytesDone = h.ResumeBytesDone
|
||||||
|
h.BytesFailed = h.ResumeBytesFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// bucketDone should be called when a bucket is done healing.
|
||||||
|
// Adds the bucket to the list of healed buckets and updates resume numbers.
|
||||||
|
func (h *healingTracker) bucketDone(bucket string) {
|
||||||
|
h.ResumeObjectsHealed = h.ObjectsHealed
|
||||||
|
h.ResumeObjectsFailed = h.ObjectsFailed
|
||||||
|
h.ResumeBytesDone = h.BytesDone
|
||||||
|
h.ResumeBytesFailed = h.BytesFailed
|
||||||
|
h.HealedBuckets = append(h.HealedBuckets, bucket)
|
||||||
|
for i, b := range h.QueuedBuckets {
|
||||||
|
if b == bucket {
|
||||||
|
// Delete...
|
||||||
|
h.QueuedBuckets = append(h.QueuedBuckets[:i], h.QueuedBuckets[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setQueuedBuckets will add buckets, but exclude any that is already in h.HealedBuckets.
|
||||||
|
// Order is preserved.
|
||||||
|
func (h *healingTracker) setQueuedBuckets(buckets []BucketInfo) {
|
||||||
|
s := set.CreateStringSet(h.HealedBuckets...)
|
||||||
|
h.QueuedBuckets = make([]string, 0, len(buckets))
|
||||||
|
for _, b := range buckets {
|
||||||
|
if !s.Contains(b.Name) {
|
||||||
|
h.QueuedBuckets = append(h.QueuedBuckets, b.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healingTracker) printTo(writer io.Writer) {
|
||||||
|
b, err := json.MarshalIndent(h, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
writer.Write([]byte(err.Error()))
|
||||||
|
}
|
||||||
|
writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// toHealingDisk converts the information to madmin.HealingDisk
|
||||||
|
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
||||||
|
return madmin.HealingDisk{
|
||||||
|
ID: h.ID,
|
||||||
|
Endpoint: h.Endpoint,
|
||||||
|
PoolIndex: h.PoolIndex,
|
||||||
|
SetIndex: h.SetIndex,
|
||||||
|
DiskIndex: h.DiskIndex,
|
||||||
|
Path: h.Path,
|
||||||
|
Started: h.Started.UTC(),
|
||||||
|
LastUpdate: h.LastUpdate.UTC(),
|
||||||
|
ObjectsHealed: h.ObjectsHealed,
|
||||||
|
ObjectsFailed: h.ObjectsFailed,
|
||||||
|
BytesDone: h.BytesDone,
|
||||||
|
BytesFailed: h.BytesFailed,
|
||||||
|
Bucket: h.Bucket,
|
||||||
|
Object: h.Object,
|
||||||
|
QueuedBuckets: h.QueuedBuckets,
|
||||||
|
HealedBuckets: h.HealedBuckets,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
||||||
@@ -90,7 +287,7 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
|||||||
disk, _, err := connectEndpoint(endpoint)
|
disk, _, err := connectEndpoint(endpoint)
|
||||||
if errors.Is(err, errUnformattedDisk) {
|
if errors.Is(err, errUnformattedDisk) {
|
||||||
disksToHeal = append(disksToHeal, endpoint)
|
disksToHeal = append(disksToHeal, endpoint)
|
||||||
} else if err == nil && disk != nil && disk.Healing() {
|
} else if err == nil && disk != nil && disk.Healing() != nil {
|
||||||
disksToHeal = append(disksToHeal, disk.Endpoint())
|
disksToHeal = append(disksToHeal, disk.Endpoint())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +311,7 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq
|
|||||||
// Perform automatic disk healing when a disk is replaced locally.
|
// Perform automatic disk healing when a disk is replaced locally.
|
||||||
diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval)
|
diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval)
|
||||||
defer diskCheckTimer.Stop()
|
defer diskCheckTimer.Stop()
|
||||||
wait:
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -125,7 +322,7 @@ wait:
|
|||||||
|
|
||||||
var erasureSetInPoolDisksToHeal []map[int][]StorageAPI
|
var erasureSetInPoolDisksToHeal []map[int][]StorageAPI
|
||||||
|
|
||||||
healDisks := globalBackgroundHealState.getHealLocalDisks()
|
healDisks := globalBackgroundHealState.getHealLocalDiskEndpoints()
|
||||||
if len(healDisks) > 0 {
|
if len(healDisks) > 0 {
|
||||||
// Reformat disks
|
// Reformat disks
|
||||||
bgSeq.sourceCh <- healSource{bucket: SlashSeparator}
|
bgSeq.sourceCh <- healSource{bucket: SlashSeparator}
|
||||||
@@ -174,55 +371,76 @@ wait:
|
|||||||
|
|
||||||
buckets, _ := z.ListBuckets(ctx)
|
buckets, _ := z.ListBuckets(ctx)
|
||||||
|
|
||||||
|
buckets = append(buckets, BucketInfo{
|
||||||
|
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Buckets data are dispersed in multiple zones/sets, make
|
||||||
|
// sure to heal all bucket metadata configuration.
|
||||||
|
buckets = append(buckets, []BucketInfo{
|
||||||
|
{Name: pathJoin(minioMetaBucket, bucketMetaPrefix)},
|
||||||
|
}...)
|
||||||
|
|
||||||
// Heal latest buckets first.
|
// Heal latest buckets first.
|
||||||
sort.Slice(buckets, func(i, j int) bool {
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
a, b := strings.HasPrefix(buckets[i].Name, minioMetaBucket), strings.HasPrefix(buckets[j].Name, minioMetaBucket)
|
||||||
|
if a != b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
return buckets[i].Created.After(buckets[j].Created)
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO(klauspost): This will block until all heals are done,
|
||||||
|
// in the future this should be able to start healing other sets at once.
|
||||||
|
var wg sync.WaitGroup
|
||||||
for i, setMap := range erasureSetInPoolDisksToHeal {
|
for i, setMap := range erasureSetInPoolDisksToHeal {
|
||||||
|
i := i
|
||||||
for setIndex, disks := range setMap {
|
for setIndex, disks := range setMap {
|
||||||
for _, disk := range disks {
|
if len(disks) == 0 {
|
||||||
logger.Info("Healing disk '%s' on %s pool", disk, humanize.Ordinal(i+1))
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func(setIndex int, disks []StorageAPI) {
|
||||||
|
defer wg.Done()
|
||||||
|
for _, disk := range disks {
|
||||||
|
logger.Info("Healing disk '%v' 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() {
|
tracker, err := loadHealingTracker(ctx, disk)
|
||||||
logger.Info("Healing tracker missing on '%s', disk was swapped again on %s pool", disk, humanize.Ordinal(i+1))
|
|
||||||
diskID, err := disk.GetDiskID()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.Info("Healing tracker missing on '%s', disk was swapped again on %s pool", disk, humanize.Ordinal(i+1))
|
||||||
// reading format.json failed or not found, proceed to look
|
tracker = newHealingTracker(disk)
|
||||||
// for new disks to be healed again, we cannot proceed further.
|
|
||||||
goto wait
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := saveHealingTracker(disk, diskID); err != nil {
|
tracker.PoolIndex, tracker.SetIndex, tracker.DiskIndex = disk.GetDiskLoc()
|
||||||
|
tracker.setQueuedBuckets(buckets)
|
||||||
|
if err := tracker.save(ctx); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
// Unable to write healing tracker, permission denied or some
|
// Unable to write healing tracker, permission denied or some
|
||||||
// other unexpected error occurred. Proceed to look for new
|
// other unexpected error occurred. Proceed to look for new
|
||||||
// disks to be healed again, we cannot proceed further.
|
// disks to be healed again, we cannot proceed further.
|
||||||
goto wait
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = z.serverPools[i].sets[setIndex].healErasureSet(ctx, buckets, tracker)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Healing disk '%s' on %s pool complete", disk, humanize.Ordinal(i+1))
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tracker.printTo(&buf)
|
||||||
|
logger.Info("Summary:\n%s", buf.String())
|
||||||
|
logger.LogIf(ctx, tracker.delete(ctx))
|
||||||
|
|
||||||
|
// Only upon success pop the healed disk.
|
||||||
|
globalBackgroundHealState.popHealLocalDisks(disk.Endpoint())
|
||||||
}
|
}
|
||||||
|
}(setIndex, disks)
|
||||||
lbDisks := z.serverPools[i].sets[setIndex].getOnlineDisks()
|
|
||||||
if err := healErasureSet(ctx, setIndex, buckets, lbDisks); err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Healing disk '%s' on %s pool complete", disk, humanize.Ordinal(i+1))
|
|
||||||
|
|
||||||
if err := disk.Delete(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix),
|
|
||||||
healingTrackerFilename, false); err != nil && !errors.Is(err, errFileNotFound) {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only upon success pop the healed disk.
|
|
||||||
globalBackgroundHealState.popHealLocalDisks(disk.Endpoint())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,146 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "ID")
|
err = msgp.WrapError(err, "ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "PoolIndex":
|
||||||
|
z.PoolIndex, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PoolIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "SetIndex":
|
||||||
|
z.SetIndex, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SetIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "DiskIndex":
|
||||||
|
z.DiskIndex, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DiskIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Path":
|
||||||
|
z.Path, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Endpoint":
|
||||||
|
z.Endpoint, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Started":
|
||||||
|
z.Started, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Started")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "LastUpdate":
|
||||||
|
z.LastUpdate, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ObjectsHealed":
|
||||||
|
z.ObjectsHealed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ObjectsFailed":
|
||||||
|
z.ObjectsFailed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "BytesDone":
|
||||||
|
z.BytesDone, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesDone")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "BytesFailed":
|
||||||
|
z.BytesFailed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Bucket":
|
||||||
|
z.Bucket, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Object":
|
||||||
|
z.Object, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeObjectsHealed":
|
||||||
|
z.ResumeObjectsHealed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeObjectsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeObjectsFailed":
|
||||||
|
z.ResumeObjectsFailed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeBytesDone":
|
||||||
|
z.ResumeBytesDone, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeBytesDone")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeBytesFailed":
|
||||||
|
z.ResumeBytesFailed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeBytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "QueuedBuckets":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QueuedBuckets")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.QueuedBuckets) >= int(zb0002) {
|
||||||
|
z.QueuedBuckets = (z.QueuedBuckets)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.QueuedBuckets = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.QueuedBuckets {
|
||||||
|
z.QueuedBuckets[za0001], err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QueuedBuckets", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "HealedBuckets":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealedBuckets")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.HealedBuckets) >= int(zb0003) {
|
||||||
|
z.HealedBuckets = (z.HealedBuckets)[:zb0003]
|
||||||
|
} else {
|
||||||
|
z.HealedBuckets = make([]string, zb0003)
|
||||||
|
}
|
||||||
|
for za0002 := range z.HealedBuckets {
|
||||||
|
z.HealedBuckets[za0002], err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealedBuckets", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,10 +182,10 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 1
|
// map header, size 20
|
||||||
// write "ID"
|
// write "ID"
|
||||||
err = en.Append(0x81, 0xa2, 0x49, 0x44)
|
err = en.Append(0xde, 0x0, 0x14, 0xa2, 0x49, 0x44)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -54,16 +194,283 @@ func (z healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "ID")
|
err = msgp.WrapError(err, "ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "PoolIndex"
|
||||||
|
err = en.Append(0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.PoolIndex)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PoolIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "SetIndex"
|
||||||
|
err = en.Append(0xa8, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.SetIndex)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SetIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "DiskIndex"
|
||||||
|
err = en.Append(0xa9, 0x44, 0x69, 0x73, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.DiskIndex)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DiskIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Path"
|
||||||
|
err = en.Append(0xa4, 0x50, 0x61, 0x74, 0x68)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Path)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Endpoint"
|
||||||
|
err = en.Append(0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Started"
|
||||||
|
err = en.Append(0xa7, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.Started)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Started")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "LastUpdate"
|
||||||
|
err = en.Append(0xaa, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.LastUpdate)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ObjectsHealed"
|
||||||
|
err = en.Append(0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ObjectsHealed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ObjectsFailed"
|
||||||
|
err = en.Append(0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ObjectsFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "BytesDone"
|
||||||
|
err = en.Append(0xa9, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.BytesDone)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesDone")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "BytesFailed"
|
||||||
|
err = en.Append(0xab, 0x42, 0x79, 0x74, 0x65, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.BytesFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Bucket"
|
||||||
|
err = en.Append(0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Object"
|
||||||
|
err = en.Append(0xa6, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Object)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ResumeObjectsHealed"
|
||||||
|
err = en.Append(0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ResumeObjectsHealed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeObjectsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ResumeObjectsFailed"
|
||||||
|
err = en.Append(0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ResumeObjectsFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ResumeBytesDone"
|
||||||
|
err = en.Append(0xaf, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ResumeBytesDone)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeBytesDone")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ResumeBytesFailed"
|
||||||
|
err = en.Append(0xb1, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ResumeBytesFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeBytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "QueuedBuckets"
|
||||||
|
err = en.Append(0xad, 0x51, 0x75, 0x65, 0x75, 0x65, 0x64, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.QueuedBuckets)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QueuedBuckets")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.QueuedBuckets {
|
||||||
|
err = en.WriteString(z.QueuedBuckets[za0001])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QueuedBuckets", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "HealedBuckets"
|
||||||
|
err = en.Append(0xad, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.HealedBuckets)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealedBuckets")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0002 := range z.HealedBuckets {
|
||||||
|
err = en.WriteString(z.HealedBuckets[za0002])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealedBuckets", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 1
|
// map header, size 20
|
||||||
// string "ID"
|
// string "ID"
|
||||||
o = append(o, 0x81, 0xa2, 0x49, 0x44)
|
o = append(o, 0xde, 0x0, 0x14, 0xa2, 0x49, 0x44)
|
||||||
o = msgp.AppendString(o, z.ID)
|
o = msgp.AppendString(o, z.ID)
|
||||||
|
// string "PoolIndex"
|
||||||
|
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
|
o = msgp.AppendInt(o, z.PoolIndex)
|
||||||
|
// string "SetIndex"
|
||||||
|
o = append(o, 0xa8, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
|
o = msgp.AppendInt(o, z.SetIndex)
|
||||||
|
// string "DiskIndex"
|
||||||
|
o = append(o, 0xa9, 0x44, 0x69, 0x73, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
|
o = msgp.AppendInt(o, z.DiskIndex)
|
||||||
|
// string "Path"
|
||||||
|
o = append(o, 0xa4, 0x50, 0x61, 0x74, 0x68)
|
||||||
|
o = msgp.AppendString(o, z.Path)
|
||||||
|
// string "Endpoint"
|
||||||
|
o = append(o, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.Endpoint)
|
||||||
|
// string "Started"
|
||||||
|
o = append(o, 0xa7, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64)
|
||||||
|
o = msgp.AppendTime(o, z.Started)
|
||||||
|
// string "LastUpdate"
|
||||||
|
o = append(o, 0xaa, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65)
|
||||||
|
o = msgp.AppendTime(o, z.LastUpdate)
|
||||||
|
// string "ObjectsHealed"
|
||||||
|
o = append(o, 0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ObjectsHealed)
|
||||||
|
// string "ObjectsFailed"
|
||||||
|
o = append(o, 0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ObjectsFailed)
|
||||||
|
// string "BytesDone"
|
||||||
|
o = append(o, 0xa9, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
||||||
|
o = msgp.AppendUint64(o, z.BytesDone)
|
||||||
|
// string "BytesFailed"
|
||||||
|
o = append(o, 0xab, 0x42, 0x79, 0x74, 0x65, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.BytesFailed)
|
||||||
|
// string "Bucket"
|
||||||
|
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
|
// string "Object"
|
||||||
|
o = append(o, 0xa6, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.Object)
|
||||||
|
// string "ResumeObjectsHealed"
|
||||||
|
o = append(o, 0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ResumeObjectsHealed)
|
||||||
|
// string "ResumeObjectsFailed"
|
||||||
|
o = append(o, 0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ResumeObjectsFailed)
|
||||||
|
// string "ResumeBytesDone"
|
||||||
|
o = append(o, 0xaf, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
||||||
|
o = msgp.AppendUint64(o, z.ResumeBytesDone)
|
||||||
|
// string "ResumeBytesFailed"
|
||||||
|
o = append(o, 0xb1, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ResumeBytesFailed)
|
||||||
|
// string "QueuedBuckets"
|
||||||
|
o = append(o, 0xad, 0x51, 0x75, 0x65, 0x75, 0x65, 0x64, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.QueuedBuckets)))
|
||||||
|
for za0001 := range z.QueuedBuckets {
|
||||||
|
o = msgp.AppendString(o, z.QueuedBuckets[za0001])
|
||||||
|
}
|
||||||
|
// string "HealedBuckets"
|
||||||
|
o = append(o, 0xad, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.HealedBuckets)))
|
||||||
|
for za0002 := range z.HealedBuckets {
|
||||||
|
o = msgp.AppendString(o, z.HealedBuckets[za0002])
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +498,146 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "ID")
|
err = msgp.WrapError(err, "ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "PoolIndex":
|
||||||
|
z.PoolIndex, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "PoolIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "SetIndex":
|
||||||
|
z.SetIndex, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "SetIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "DiskIndex":
|
||||||
|
z.DiskIndex, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DiskIndex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Path":
|
||||||
|
z.Path, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Endpoint":
|
||||||
|
z.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Started":
|
||||||
|
z.Started, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Started")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "LastUpdate":
|
||||||
|
z.LastUpdate, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ObjectsHealed":
|
||||||
|
z.ObjectsHealed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ObjectsFailed":
|
||||||
|
z.ObjectsFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "BytesDone":
|
||||||
|
z.BytesDone, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesDone")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "BytesFailed":
|
||||||
|
z.BytesFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Bucket":
|
||||||
|
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Object":
|
||||||
|
z.Object, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeObjectsHealed":
|
||||||
|
z.ResumeObjectsHealed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeObjectsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeObjectsFailed":
|
||||||
|
z.ResumeObjectsFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeBytesDone":
|
||||||
|
z.ResumeBytesDone, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeBytesDone")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ResumeBytesFailed":
|
||||||
|
z.ResumeBytesFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ResumeBytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "QueuedBuckets":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QueuedBuckets")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.QueuedBuckets) >= int(zb0002) {
|
||||||
|
z.QueuedBuckets = (z.QueuedBuckets)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.QueuedBuckets = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.QueuedBuckets {
|
||||||
|
z.QueuedBuckets[za0001], bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "QueuedBuckets", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "HealedBuckets":
|
||||||
|
var zb0003 uint32
|
||||||
|
zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealedBuckets")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.HealedBuckets) >= int(zb0003) {
|
||||||
|
z.HealedBuckets = (z.HealedBuckets)[:zb0003]
|
||||||
|
} else {
|
||||||
|
z.HealedBuckets = make([]string, zb0003)
|
||||||
|
}
|
||||||
|
for za0002 := range z.HealedBuckets {
|
||||||
|
z.HealedBuckets[za0002], bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealedBuckets", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -104,7 +651,14 @@ func (z *healingTracker) 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 healingTracker) Msgsize() (s int) {
|
func (z *healingTracker) Msgsize() (s int) {
|
||||||
s = 1 + 3 + msgp.StringPrefixSize + len(z.ID)
|
s = 3 + 3 + msgp.StringPrefixSize + len(z.ID) + 10 + msgp.IntSize + 9 + msgp.IntSize + 10 + msgp.IntSize + 5 + msgp.StringPrefixSize + len(z.Path) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 8 + msgp.TimeSize + 11 + msgp.TimeSize + 14 + msgp.Uint64Size + 14 + msgp.Uint64Size + 10 + msgp.Uint64Size + 12 + msgp.Uint64Size + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Object) + 20 + msgp.Uint64Size + 20 + msgp.Uint64Size + 16 + msgp.Uint64Size + 18 + msgp.Uint64Size + 14 + msgp.ArrayHeaderSize
|
||||||
|
for za0001 := range z.QueuedBuckets {
|
||||||
|
s += msgp.StringPrefixSize + len(z.QueuedBuckets[za0001])
|
||||||
|
}
|
||||||
|
s += 14 + msgp.ArrayHeaderSize
|
||||||
|
for za0002 := range z.HealedBuckets {
|
||||||
|
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -175,56 +174,6 @@ func benchmarkPutObjectParallel(b *testing.B, instanceType string, objSize int)
|
|||||||
runPutObjectBenchmarkParallel(b, objLayer, objSize)
|
runPutObjectBenchmarkParallel(b, objLayer, objSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark utility functions for ObjectLayer.GetObject().
|
|
||||||
// Creates Object layer setup ( MakeBucket, PutObject) and then runs the benchmark.
|
|
||||||
func runGetObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
|
|
||||||
// obtains random bucket name.
|
|
||||||
bucket := getRandomBucketName()
|
|
||||||
// create bucket.
|
|
||||||
err := obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
textData := generateBytesData(objSize)
|
|
||||||
|
|
||||||
// generate etag for the generated data.
|
|
||||||
// etag of the data to written is required as input for PutObject.
|
|
||||||
// PutObject is the functions which writes the data onto the FS/Erasure backend.
|
|
||||||
|
|
||||||
// get text data generated for number of bytes equal to object size.
|
|
||||||
md5hex := getMD5Hash(textData)
|
|
||||||
sha256hex := ""
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
// insert the object.
|
|
||||||
var objInfo ObjectInfo
|
|
||||||
objInfo, err = obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i),
|
|
||||||
mustGetPutObjReader(b, bytes.NewReader(textData), int64(len(textData)), md5hex, sha256hex), ObjectOptions{})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
if objInfo.ETag != md5hex {
|
|
||||||
b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.ETag, md5hex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// benchmark utility which helps obtain number of allocations and bytes allocated per ops.
|
|
||||||
b.ReportAllocs()
|
|
||||||
// the actual benchmark for GetObject starts here. Reset the benchmark timer.
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
var buffer = new(bytes.Buffer)
|
|
||||||
err = obj.GetObject(context.Background(), bucket, "object"+strconv.Itoa(i%10), 0, int64(objSize), buffer, "", ObjectOptions{})
|
|
||||||
if err != nil {
|
|
||||||
b.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Benchmark ends here. Stop timer.
|
|
||||||
b.StopTimer()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// randomly picks a character and returns its equivalent byte array.
|
// randomly picks a character and returns its equivalent byte array.
|
||||||
func getRandomByte() []byte {
|
func getRandomByte() []byte {
|
||||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
@@ -240,38 +189,6 @@ func generateBytesData(size int) []byte {
|
|||||||
return bytes.Repeat(getRandomByte(), size)
|
return bytes.Repeat(getRandomByte(), size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates Erasure/FS backend setup, obtains the object layer and calls the runGetObjectBenchmark function.
|
|
||||||
func benchmarkGetObject(b *testing.B, instanceType string, objSize int) {
|
|
||||||
// create a temp Erasure/FS backend.
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
objLayer, disks, err := prepareTestBackend(ctx, instanceType)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("Failed obtaining Temp Backend: <ERROR> %s", err)
|
|
||||||
}
|
|
||||||
// cleaning up the backend by removing all the directories and files created.
|
|
||||||
defer removeRoots(disks)
|
|
||||||
|
|
||||||
// uses *testing.B and the object Layer to run the benchmark.
|
|
||||||
runGetObjectBenchmark(b, objLayer, objSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates Erasure/FS backend setup, obtains the object layer and runs parallel benchmark for ObjectLayer.GetObject() .
|
|
||||||
func benchmarkGetObjectParallel(b *testing.B, instanceType string, objSize int) {
|
|
||||||
// create a temp Erasure/FS backend.
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
objLayer, disks, err := prepareTestBackend(ctx, instanceType)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("Failed obtaining Temp Backend: <ERROR> %s", err)
|
|
||||||
}
|
|
||||||
// cleaning up the backend by removing all the directories and files created.
|
|
||||||
defer removeRoots(disks)
|
|
||||||
|
|
||||||
// uses *testing.B and the object Layer to run the benchmark.
|
|
||||||
runGetObjectBenchmarkParallel(b, objLayer, objSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parallel benchmark utility functions for ObjectLayer.PutObject().
|
// Parallel benchmark utility functions for ObjectLayer.PutObject().
|
||||||
// Creates Object layer setup ( MakeBucket ) and then runs the PutObject benchmark.
|
// Creates Object layer setup ( MakeBucket ) and then runs the PutObject benchmark.
|
||||||
func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
|
func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
|
||||||
@@ -315,58 +232,3 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
|
|||||||
// Benchmark ends here. Stop timer.
|
// Benchmark ends here. Stop timer.
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parallel benchmark utility functions for ObjectLayer.GetObject().
|
|
||||||
// Creates Object layer setup ( MakeBucket, PutObject) and then runs the benchmark.
|
|
||||||
func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
|
|
||||||
// obtains random bucket name.
|
|
||||||
bucket := getRandomBucketName()
|
|
||||||
// create bucket.
|
|
||||||
err := obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get text data generated for number of bytes equal to object size.
|
|
||||||
textData := generateBytesData(objSize)
|
|
||||||
// generate md5sum for the generated data.
|
|
||||||
// md5sum of the data to written is required as input for PutObject.
|
|
||||||
// PutObject is the functions which writes the data onto the FS/Erasure backend.
|
|
||||||
|
|
||||||
md5hex := getMD5Hash([]byte(textData))
|
|
||||||
sha256hex := ""
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
// insert the object.
|
|
||||||
var objInfo ObjectInfo
|
|
||||||
objInfo, err = obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i),
|
|
||||||
mustGetPutObjReader(b, bytes.NewReader(textData), int64(len(textData)), md5hex, sha256hex), ObjectOptions{})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
if objInfo.ETag != md5hex {
|
|
||||||
b.Fatalf("Write no: %d: Md5Sum mismatch during object write into the bucket: Expected %s, got %s", i+1, objInfo.ETag, md5hex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// benchmark utility which helps obtain number of allocations and bytes allocated per ops.
|
|
||||||
b.ReportAllocs()
|
|
||||||
// the actual benchmark for GetObject starts here. Reset the benchmark timer.
|
|
||||||
b.ResetTimer()
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
i := 0
|
|
||||||
for pb.Next() {
|
|
||||||
err = obj.GetObject(context.Background(), bucket, "object"+strconv.Itoa(i), 0, int64(objSize), ioutil.Discard, "", ObjectOptions{})
|
|
||||||
if err != nil {
|
|
||||||
b.Error(err)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
if i == 10 {
|
|
||||||
i = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Benchmark ends here. Stop timer.
|
|
||||||
b.StopTimer()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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, nil, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,15 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -125,7 +126,10 @@ 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
|
||||||
bucketsToBeUpdatedSlice := bucketsToBeUpdated.ToSlice()
|
bucketsToBeUpdatedSlice := bucketsToBeUpdated.ToSlice()
|
||||||
g := errgroup.WithNErrs(len(bucketsToBeUpdatedSlice))
|
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 {
|
||||||
@@ -133,14 +137,13 @@ 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 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()))
|
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
|
var wg sync.WaitGroup
|
||||||
@@ -334,7 +337,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
// err will be nil here as we already called this function
|
// err will be nil here as we already called this function
|
||||||
// earlier in this request.
|
// earlier in this request.
|
||||||
claims, _ := getClaimsFromToken(r, getSessionToken(r))
|
claims, _ := getClaimsFromToken(getSessionToken(r))
|
||||||
n := 0
|
n := 0
|
||||||
// Use the following trick to filter in place
|
// Use the following trick to filter in place
|
||||||
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
||||||
@@ -424,6 +427,12 @@ 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 {
|
||||||
@@ -544,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,
|
||||||
@@ -580,6 +588,10 @@ 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 {
|
||||||
dv := DeletedObjectVersionInfo{
|
dv := DeletedObjectVersionInfo{
|
||||||
@@ -591,22 +603,14 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@@ -793,13 +797,15 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resource, err := getResource(r.URL.Path, r.Host, globalDomainNames)
|
resource, err := getResource(r.URL.Path, r.Host, globalDomainNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Make sure that the URL does not contain object name.
|
|
||||||
if bucket != filepath.Clean(resource[1:]) {
|
// Make sure that the URL does not contain object name.
|
||||||
|
if bucket != path.Clean(resource[1:]) {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -842,7 +848,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
defer fileBody.Close()
|
defer fileBody.Close()
|
||||||
|
|
||||||
formValues.Set("Bucket", bucket)
|
formValues.Set("Bucket", bucket)
|
||||||
|
|
||||||
if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") {
|
if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") {
|
||||||
// S3 feature to replace ${filename} found in Key form field
|
// S3 feature to replace ${filename} found in Key form field
|
||||||
// by the filename attribute passed in multipart
|
// by the filename attribute passed in multipart
|
||||||
@@ -850,6 +855,12 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
object := formValues.Get("Key")
|
object := formValues.Get("Key")
|
||||||
|
|
||||||
|
object, err = unescapePath(object)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
successRedirect := formValues.Get("success_action_redirect")
|
successRedirect := formValues.Get("success_action_redirect")
|
||||||
successStatus := formValues.Get("success_action_status")
|
successStatus := formValues.Get("success_action_status")
|
||||||
var redirectURL *url.URL
|
var redirectURL *url.URL
|
||||||
@@ -862,12 +873,51 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify policy signature.
|
// Verify policy signature.
|
||||||
errCode := doesPolicySignatureMatch(formValues)
|
cred, errCode := doesPolicySignatureMatch(formValues)
|
||||||
if errCode != ErrNone {
|
if errCode != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Once signature is validated, check if the user has
|
||||||
|
// explicit permissions for the user.
|
||||||
|
{
|
||||||
|
token := formValues.Get(xhttp.AmzSecurityToken)
|
||||||
|
if token != "" && cred.AccessKey == "" {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoAccessKey), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cred.IsServiceAccount() && token == "" {
|
||||||
|
token = cred.SessionToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidToken), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract claims if any.
|
||||||
|
claims, err := getClaimsFromToken(token)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Action: iampolicy.PutObjectAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
||||||
|
BucketName: bucket,
|
||||||
|
ObjectName: object,
|
||||||
|
IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
|
||||||
|
Claims: claims,
|
||||||
|
}) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy"))
|
policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
|
||||||
@@ -907,20 +957,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
|
||||||
@@ -960,12 +1010,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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1022,6 +1076,64 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBucketPolicyStatusHandler - Retrieves the policy status
|
||||||
|
// for an MinIO bucket, indicating whether the bucket is public.
|
||||||
|
func (api objectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetBucketPolicyStatus")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objectAPI := api.ObjectAPI()
|
||||||
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrServerNotInitialized))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyStatusAction, bucket, ""); s3Error != ErrNone {
|
||||||
|
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if anonymous (non-owner) has access to list objects.
|
||||||
|
readable := globalPolicySys.IsAllowed(policy.Args{
|
||||||
|
Action: policy.ListBucketAction,
|
||||||
|
BucketName: bucket,
|
||||||
|
ConditionValues: getConditionValues(r, "", "", nil),
|
||||||
|
IsOwner: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if anonymous (non-owner) has access to upload objects.
|
||||||
|
writable := globalPolicySys.IsAllowed(policy.Args{
|
||||||
|
Action: policy.PutObjectAction,
|
||||||
|
BucketName: bucket,
|
||||||
|
ConditionValues: getConditionValues(r, "", "", nil),
|
||||||
|
IsOwner: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
encodedSuccessResponse := encodeResponse(PolicyStatus{
|
||||||
|
IsPublic: func() string {
|
||||||
|
// Silly to have special 'boolean' values yes
|
||||||
|
// but complying with silly implementation
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicyStatus.html
|
||||||
|
if readable && writable {
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
|
return "FALSE"
|
||||||
|
}(),
|
||||||
|
})
|
||||||
|
|
||||||
|
writeSuccessResponseXML(w, encodedSuccessResponse)
|
||||||
|
}
|
||||||
|
|
||||||
// HeadBucketHandler - HEAD Bucket
|
// HeadBucketHandler - HEAD Bucket
|
||||||
// ----------
|
// ----------
|
||||||
// This operation is useful to determine if a bucket exists.
|
// This operation is useful to determine if a bucket exists.
|
||||||
|
|||||||
@@ -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,44 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
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]",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
@@ -343,7 +393,12 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, objInfo Object
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
putOpts := putTransitionOpts(oi)
|
putOpts, err := putTransitionOpts(oi)
|
||||||
|
if err != nil {
|
||||||
|
gr.Close()
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
if _, err = tgt.PutObject(ctx, arn.Bucket, oi.Name, gr, oi.Size, putOpts); err != nil {
|
if _, err = tgt.PutObject(ctx, arn.Bucket, oi.Name, gr, oi.Size, putOpts); err != nil {
|
||||||
gr.Close()
|
gr.Close()
|
||||||
return err
|
return err
|
||||||
@@ -368,6 +423,7 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, objInfo Object
|
|||||||
Object: objInfo,
|
Object: objInfo,
|
||||||
Host: "Internal: [ILM-Transition]",
|
Host: "Internal: [ILM-Transition]",
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,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
|
||||||
@@ -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)
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
@@ -169,14 +170,8 @@ func hasReplicationRules(ctx context.Context, bucket string, objects []ObjectToD
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isStandardHeader returns true if header is a supported header and not a custom header
|
// isStandardHeader returns true if header is a supported header and not a custom header
|
||||||
func isStandardHeader(headerKey string) bool {
|
func isStandardHeader(matchHeaderKey string) bool {
|
||||||
key := strings.ToLower(headerKey)
|
return equals(matchHeaderKey, standardHeaders...)
|
||||||
for _, header := range standardHeaders {
|
|
||||||
if strings.ToLower(header) == key {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -185,6 +180,14 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
|||||||
if err != nil || rcfg == nil {
|
if err != nil || rcfg == nil {
|
||||||
return false, false, sync
|
return false, false, sync
|
||||||
}
|
}
|
||||||
|
opts := replication.ObjectOpts{
|
||||||
|
Name: dobj.ObjectName,
|
||||||
|
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||||
|
UserTags: oi.UserTags,
|
||||||
|
DeleteMarker: oi.DeleteMarker,
|
||||||
|
VersionID: dobj.VersionID,
|
||||||
|
}
|
||||||
|
replicate = rcfg.Replicate(opts)
|
||||||
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
||||||
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
||||||
if gerr != nil {
|
if gerr != nil {
|
||||||
@@ -193,10 +196,12 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
|||||||
case replication.Pending, replication.Completed, replication.Failed:
|
case replication.Pending, replication.Completed, replication.Failed:
|
||||||
validReplStatus = true
|
validReplStatus = true
|
||||||
}
|
}
|
||||||
if oi.DeleteMarker && validReplStatus {
|
if oi.DeleteMarker && (validReplStatus || replicate) {
|
||||||
return oi.DeleteMarker, true, sync
|
return oi.DeleteMarker, true, sync
|
||||||
}
|
}
|
||||||
return oi.DeleteMarker, false, 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)
|
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rcfg.RoleArn)
|
||||||
// the target online status should not be used here while deciding
|
// the target online status should not be used here while deciding
|
||||||
@@ -204,14 +209,7 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
|||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
return oi.DeleteMarker, false, false
|
return oi.DeleteMarker, false, false
|
||||||
}
|
}
|
||||||
opts := replication.ObjectOpts{
|
return oi.DeleteMarker, replicate, tgt.replicateSync
|
||||||
Name: dobj.ObjectName,
|
|
||||||
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
|
||||||
UserTags: oi.UserTags,
|
|
||||||
DeleteMarker: oi.DeleteMarker,
|
|
||||||
VersionID: dobj.VersionID,
|
|
||||||
}
|
|
||||||
return oi.DeleteMarker, rcfg.Replicate(opts), tgt.replicateSync
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// replicate deletes to the designated replication target if replication configuration
|
// replicate deletes to the designated replication target if replication configuration
|
||||||
@@ -226,26 +224,52 @@ 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 {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
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))
|
|
||||||
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{
|
||||||
ReplicationDeleteMarker: dobj.DeleteMarkerVersionID != "",
|
ReplicationDeleteMarker: dobj.DeleteMarkerVersionID != "",
|
||||||
ReplicationMTime: dobj.DeleteMarkerMTime.Time,
|
ReplicationMTime: dobj.DeleteMarkerMTime.Time,
|
||||||
ReplicationStatus: miniogo.ReplicationStatusReplica,
|
ReplicationStatus: miniogo.ReplicationStatusReplica,
|
||||||
|
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -258,6 +282,7 @@ 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.Completed)
|
replicationStatus = string(replication.Completed)
|
||||||
@@ -272,66 +297,102 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectVersionInfo, objectA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
objInfo, 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),
|
||||||
})
|
})
|
||||||
if err != nil {
|
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: %w", bucket, dobj.ObjectName, dobj.VersionID, err))
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(eventArgs{
|
|
||||||
BucketName: bucket,
|
|
||||||
Object: objInfo,
|
|
||||||
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 strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||||
@@ -342,54 +403,58 @@ func putReplicationOpts(ctx context.Context, dest replication.Destination, objIn
|
|||||||
}
|
}
|
||||||
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,
|
||||||
Internal: miniogo.AdvancedPutOptions{
|
Internal: miniogo.AdvancedPutOptions{
|
||||||
SourceVersionID: objInfo.VersionID,
|
SourceVersionID: objInfo.VersionID,
|
||||||
ReplicationStatus: miniogo.ReplicationStatusReplica,
|
ReplicationStatus: miniogo.ReplicationStatusReplica,
|
||||||
SourceMTime: objInfo.ModTime,
|
SourceMTime: objInfo.ModTime,
|
||||||
SourceETag: objInfo.ETag,
|
SourceETag: objInfo.ETag,
|
||||||
|
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if lang, ok := objInfo.UserDefined[xhttp.ContentLanguage]; 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
|
putOpts.ContentLanguage = lang
|
||||||
}
|
}
|
||||||
if disp, ok := objInfo.UserDefined[xhttp.ContentDisposition]; ok {
|
if disp, ok := lkMap.Lookup(xhttp.ContentDisposition); ok {
|
||||||
putOpts.ContentDisposition = disp
|
putOpts.ContentDisposition = disp
|
||||||
}
|
}
|
||||||
if cc, ok := objInfo.UserDefined[xhttp.CacheControl]; ok {
|
if cc, ok := lkMap.Lookup(xhttp.CacheControl); ok {
|
||||||
putOpts.CacheControl = cc
|
putOpts.CacheControl = cc
|
||||||
}
|
}
|
||||||
if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +466,16 @@ 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
|
||||||
@@ -408,83 +483,153 @@ func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo) replicationActio
|
|||||||
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.Equal(oi2.LastModified) {
|
oi1.ModTime.Unix() != oi2.LastModified.Unix() {
|
||||||
return replicateAll
|
return replicateAll
|
||||||
}
|
}
|
||||||
|
|
||||||
if oi1.ContentType != oi2.ContentType {
|
if oi1.ContentType != oi2.ContentType {
|
||||||
return replicateMetadata
|
return replicateMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
if oi1.ContentEncoding != "" {
|
if oi1.ContentEncoding != "" {
|
||||||
enc, ok := oi2.Metadata[xhttp.ContentEncoding]
|
enc, ok := oi2.Metadata[xhttp.ContentEncoding]
|
||||||
if !ok || strings.Join(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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t, _ := tags.ParseObjectTags(oi1.UserTags)
|
||||||
|
if !reflect.DeepEqual(oi2.UserTags, t.ToMap()) {
|
||||||
|
return replicateMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare only necessary headers
|
||||||
|
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
|
// compare metadata on both maps to see if meta is identical
|
||||||
for k1, v1 := range oi1.UserDefined {
|
compareMeta1 := make(map[string]string)
|
||||||
if v2, ok := oi2.UserMetadata[k1]; ok && v1 == v2 {
|
for k, v := range oi1.UserDefined {
|
||||||
continue
|
var found bool
|
||||||
|
for _, prefix := range compareKeys {
|
||||||
|
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if v2, ok := oi2.Metadata[k1]; ok && v1 == strings.Join(v2, "") {
|
if found {
|
||||||
continue
|
compareMeta1[strings.ToLower(k)] = v
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
for k1, v1 := range oi2.UserMetadata {
|
|
||||||
if v2, ok := oi1.UserDefined[k1]; !ok || v1 != v2 {
|
|
||||||
return replicateMetadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k1, v1slc := range oi2.Metadata {
|
|
||||||
v1 := strings.Join(v1slc, "")
|
|
||||||
if k1 == xhttp.ContentEncoding { //already compared
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v2, ok := oi1.UserDefined[k1]; !ok || v1 != v2 {
|
|
||||||
return replicateMetadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t, _ := tags.MapToObjectTags(oi2.UserTags)
|
|
||||||
if t.String() != oi1.UserTags {
|
|
||||||
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)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,31 +642,54 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
|
|||||||
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
|
replicationStatus := replication.Completed
|
||||||
|
// use core client to avoid doing multipart on PUT
|
||||||
|
c := &miniogo.Core{Client: tgt.Client}
|
||||||
if rtype != replicateAll {
|
if rtype != replicateAll {
|
||||||
gr.Close()
|
|
||||||
// replicate metadata for object tagging/copy with metadata replacement
|
// replicate metadata for object tagging/copy with metadata replacement
|
||||||
dstOpts := miniogo.PutObjectOptions{Internal: miniogo.AdvancedPutOptions{SourceVersionID: objInfo.VersionID}}
|
srcOpts := miniogo.CopySrcOptions{
|
||||||
c := &miniogo.Core{Client: tgt.Client}
|
Bucket: dest.Bucket,
|
||||||
_, err = c.CopyObject(ctx, dest.Bucket, object, dest.Bucket, object, getCopyObjMetadata(objInfo, dest), dstOpts)
|
Object: object,
|
||||||
if err != nil {
|
VersionID: objInfo.VersionID}
|
||||||
|
dstOpts := miniogo.PutObjectOptions{
|
||||||
|
Internal: miniogo.AdvancedPutOptions{
|
||||||
|
SourceVersionID: objInfo.VersionID,
|
||||||
|
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||||
|
}}
|
||||||
|
if _, err = c.CopyObject(ctx, dest.Bucket, object, dest.Bucket, object, getCopyObjMetadata(objInfo, dest), srcOpts, dstOpts); err != nil {
|
||||||
replicationStatus = replication.Failed
|
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 {
|
} else {
|
||||||
target, err := globalBucketMetadataSys.GetBucketTarget(bucket, cfg.RoleArn)
|
target, err := globalBucketMetadataSys.GetBucketTarget(bucket, cfg.RoleArn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%s", bucket, cfg.RoleArn, err))
|
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%s", bucket, cfg.RoleArn, err))
|
||||||
gr.Close()
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putOpts := putReplicationOpts(ctx, dest, objInfo)
|
|
||||||
// Setup bandwidth throttling
|
// Setup bandwidth throttling
|
||||||
peers, _ := globalEndpoints.peers()
|
peers, _ := globalEndpoints.peers()
|
||||||
totalNodesCount := len(peers)
|
totalNodesCount := len(peers)
|
||||||
@@ -536,11 +704,11 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
|
|||||||
|
|
||||||
// r takes over closing gr.
|
// r takes over closing gr.
|
||||||
r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b, target.BandwidthLimit)
|
r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b, target.BandwidthLimit)
|
||||||
_, err = tgt.PutObject(ctx, dest.Bucket, object, r, size, putOpts)
|
if _, err = c.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts); err != nil {
|
||||||
if err != nil {
|
|
||||||
replicationStatus = replication.Failed
|
replicationStatus = replication.Failed
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %w", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||||
}
|
}
|
||||||
r.Close()
|
defer r.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
||||||
@@ -549,7 +717,6 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -557,16 +724,17 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
|
|||||||
eventName = event.ObjectReplicationFailed
|
eventName = event.ObjectReplicationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo.metadataOnly = true // Perform only metadata updates.
|
// This lower level implementation is necessary to avoid write locks from CopyObject.
|
||||||
objInfo, err = objectAPI.CopyObject(ctx, bucket, object, bucket, object, objInfo, ObjectOptions{
|
poolIdx, err := z.getPoolIdx(ctx, bucket, object, objInfo.Size)
|
||||||
VersionID: objInfo.VersionID,
|
|
||||||
}, ObjectOptions{
|
|
||||||
VersionID: objInfo.VersionID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s: %s", objInfo.VersionID, err))
|
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,
|
||||||
@@ -603,91 +771,102 @@ type DeletedObjectVersionInfo struct {
|
|||||||
DeletedObject
|
DeletedObject
|
||||||
Bucket string
|
Bucket string
|
||||||
}
|
}
|
||||||
type replicationState struct {
|
|
||||||
// add future metrics here
|
|
||||||
replicaCh chan ObjectInfo
|
|
||||||
replicaDeleteCh chan DeletedObjectVersionInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *replicationState) queueReplicaTask(oi ObjectInfo) {
|
|
||||||
if r == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case r.replicaCh <- oi:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *replicationState) queueReplicaDeleteTask(doi DeletedObjectVersionInfo) {
|
|
||||||
if r == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case r.replicaDeleteCh <- doi:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalReplicationState *replicationState
|
globalReplicationPool *ReplicationPool
|
||||||
// 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 {
|
// ReplicationPool describes replication pool
|
||||||
|
type ReplicationPool struct {
|
||||||
// fix minimum concurrent replication to 1 for single CPU setup
|
mu sync.Mutex
|
||||||
if globalReplicationConcurrent == 0 {
|
size int
|
||||||
globalReplicationConcurrent = 1
|
replicaCh chan ObjectInfo
|
||||||
}
|
replicaDeleteCh chan DeletedObjectVersionInfo
|
||||||
rs := &replicationState{
|
killCh chan struct{}
|
||||||
replicaCh: make(chan ObjectInfo, 10000),
|
wg sync.WaitGroup
|
||||||
replicaDeleteCh: make(chan DeletedObjectVersionInfo, 10000),
|
ctx context.Context
|
||||||
}
|
objLayer ObjectLayer
|
||||||
go func() {
|
|
||||||
<-GlobalContext.Done()
|
|
||||||
close(rs.replicaCh)
|
|
||||||
close(rs.replicaDeleteCh)
|
|
||||||
}()
|
|
||||||
return rs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addWorker creates a new worker to process tasks
|
// NewReplicationPool creates a pool of replication workers of specified size
|
||||||
func (r *replicationState) addWorker(ctx context.Context, objectAPI ObjectLayer) {
|
func NewReplicationPool(ctx context.Context, o ObjectLayer, sz int) *ReplicationPool {
|
||||||
// Add a new worker.
|
pool := &ReplicationPool{
|
||||||
|
replicaCh: make(chan ObjectInfo, 10000),
|
||||||
|
replicaDeleteCh: make(chan DeletedObjectVersionInfo, 10000),
|
||||||
|
ctx: ctx,
|
||||||
|
objLayer: o,
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
<-ctx.Done()
|
||||||
select {
|
close(pool.replicaCh)
|
||||||
case <-ctx.Done():
|
close(pool.replicaDeleteCh)
|
||||||
return
|
|
||||||
case oi, ok := <-r.replicaCh:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
replicateObject(ctx, oi, objectAPI)
|
|
||||||
case doi, ok := <-r.replicaDeleteCh:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
replicateDelete(ctx, doi, objectAPI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
pool.Resize(sz)
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddWorker adds a replication worker to the pool
|
||||||
|
func (p *ReplicationPool) AddWorker() {
|
||||||
|
defer p.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
return
|
||||||
|
case oi, ok := <-p.replicaCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replicateObject(p.ctx, oi, p.objLayer)
|
||||||
|
case doi, ok := <-p.replicaDeleteCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replicateDelete(p.ctx, doi, p.objLayer)
|
||||||
|
case <-p.killCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Resize replication pool to new size
|
||||||
|
func (p *ReplicationPool) Resize(n int) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
for p.size < n {
|
||||||
|
p.size++
|
||||||
|
p.wg.Add(1)
|
||||||
|
go p.AddWorker()
|
||||||
|
}
|
||||||
|
for p.size > n {
|
||||||
|
p.size--
|
||||||
|
go func() { p.killCh <- struct{}{} }()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReplicationPool) queueReplicaTask(oi ObjectInfo) {
|
||||||
|
if p == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case p.replicaCh <- oi:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectVersionInfo) {
|
||||||
|
if p == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case p.replicaDeleteCh <- doi:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
if globalReplicationState == nil {
|
globalReplicationPool = NewReplicationPool(ctx, objectAPI, globalAPIConfig.getReplicationWorkers())
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with globalReplicationConcurrent.
|
|
||||||
for i := 0; i < globalReplicationConcurrent; i++ {
|
|
||||||
globalReplicationState.addWorker(ctx, objectAPI)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get Reader from replication target if active-active replication is in place and
|
// get Reader from replication target if active-active replication is in place and
|
||||||
@@ -714,8 +893,11 @@ func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs
|
|||||||
return nil, false
|
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}
|
c := miniogo.Core{Client: tgt.Client}
|
||||||
|
|
||||||
obj, _, _, err := c.GetObject(ctx, bucket, object, gopts)
|
obj, _, _, err := c.GetObject(ctx, bucket, object, gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -726,6 +908,7 @@ func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
reader.ObjInfo = oi.Clone()
|
||||||
return reader, true
|
return reader, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,6 +964,7 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, opts Objec
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, oi, false, err
|
return nil, oi, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, _ := tags.MapToObjectTags(objInfo.UserTags)
|
tags, _ := tags.MapToObjectTags(objInfo.UserTags)
|
||||||
oi = ObjectInfo{
|
oi = ObjectInfo{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
@@ -795,12 +979,18 @@ func proxyHeadToRepTarget(ctx context.Context, bucket, object string, opts Objec
|
|||||||
Expires: objInfo.Expires,
|
Expires: objInfo.Expires,
|
||||||
StorageClass: objInfo.StorageClass,
|
StorageClass: objInfo.StorageClass,
|
||||||
ReplicationStatus: replication.StatusType(objInfo.ReplicationStatus),
|
ReplicationStatus: replication.StatusType(objInfo.ReplicationStatus),
|
||||||
UserDefined: cloneMSS(objInfo.UserMetadata),
|
|
||||||
UserTags: tags.String(),
|
UserTags: tags.String(),
|
||||||
}
|
}
|
||||||
if ce, ok := oi.UserDefined[xhttp.ContentEncoding]; ok {
|
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
|
oi.ContentEncoding = ce
|
||||||
delete(oi.UserDefined, xhttp.ContentEncoding)
|
|
||||||
}
|
}
|
||||||
return tgt, oi, true, nil
|
return tgt, oi, true, nil
|
||||||
}
|
}
|
||||||
@@ -816,7 +1006,7 @@ func scheduleReplication(ctx context.Context, objInfo ObjectInfo, o ObjectLayer,
|
|||||||
if sync {
|
if sync {
|
||||||
replicateObject(ctx, objInfo, o)
|
replicateObject(ctx, objInfo, o)
|
||||||
} else {
|
} else {
|
||||||
globalReplicationState.queueReplicaTask(objInfo)
|
globalReplicationPool.queueReplicaTask(objInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,6 +1014,6 @@ func scheduleReplicationDelete(ctx context.Context, dv DeletedObjectVersionInfo,
|
|||||||
if sync {
|
if sync {
|
||||||
replicateDelete(ctx, dv, o)
|
replicateDelete(ctx, dv, o)
|
||||||
} else {
|
} else {
|
||||||
globalReplicationState.queueReplicaDeleteTask(dv)
|
globalReplicationPool.queueReplicaDeleteTask(dv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -32,7 +33,6 @@ 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 (
|
const (
|
||||||
@@ -362,6 +362,7 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
|
|||||||
api, err := minio.New(tcfg.Endpoint, &miniogo.Options{
|
api, err := minio.New(tcfg.Endpoint, &miniogo.Options{
|
||||||
Creds: creds,
|
Creds: creds,
|
||||||
Secure: tcfg.Secure,
|
Secure: tcfg.Secure,
|
||||||
|
Region: tcfg.Region,
|
||||||
Transport: getRemoteTargetInstanceTransport,
|
Transport: getRemoteTargetInstanceTransport,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -50,10 +50,14 @@ import (
|
|||||||
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.
|
||||||
|
config.Logger.Info = logger.Info
|
||||||
|
config.Logger.LogIf = logger.LogIf
|
||||||
|
|
||||||
globalDNSCache = xhttp.NewDNSCache(10*time.Second, 10*time.Second, logger.LogOnceIf)
|
globalDNSCache = xhttp.NewDNSCache(10*time.Second, 10*time.Second, logger.LogOnceIf)
|
||||||
|
|
||||||
@@ -69,7 +73,6 @@ func init() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
globalReplicationState = newReplicationState()
|
|
||||||
globalTransitionState = newTransitionState()
|
globalTransitionState = newTransitionState()
|
||||||
|
|
||||||
console.SetColor("Debug", color.New())
|
console.SetColor("Debug", color.New())
|
||||||
|
|||||||
@@ -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{MaxParity: true})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 namespace scanning 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,
|
||||||
@@ -273,11 +274,11 @@ func validateConfig(s config.Config, setDriveCounts []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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +296,10 @@ func validateConfig(s config.Config, setDriveCounts []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
|
||||||
}
|
}
|
||||||
@@ -471,7 +475,10 @@ func lookupConfigs(s config.Config, setDriveCounts []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))
|
||||||
}
|
}
|
||||||
@@ -480,11 +487,13 @@ func lookupConfigs(s config.Config, setDriveCounts []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],
|
||||||
@@ -544,7 +553,7 @@ func lookupConfigs(s config.Config, setDriveCounts []int) {
|
|||||||
http.WithAuthToken(l.AuthToken),
|
http.WithAuthToken(l.AuthToken),
|
||||||
http.WithUserAgent(loggerUserAgent),
|
http.WithUserAgent(loggerUserAgent),
|
||||||
http.WithLogKind(string(logger.All)),
|
http.WithLogKind(string(logger.All)),
|
||||||
http.WithTransport(NewGatewayHTTPTransport()),
|
http.WithTransport(NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)),
|
||||||
),
|
),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize audit HTTP target: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize audit HTTP target: %w", err))
|
||||||
@@ -597,10 +606,10 @@ func applyDynamicConfig(ctx context.Context, objAPI ObjectLayer, s config.Config
|
|||||||
return 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.
|
||||||
@@ -615,7 +624,7 @@ func applyDynamicConfig(ctx context.Context, objAPI ObjectLayer, s config.Config
|
|||||||
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()
|
||||||
|
|||||||
@@ -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",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -40,19 +40,13 @@ var (
|
|||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: Exclude,
|
Key: Exclude,
|
||||||
Description: `comma separated wildcard exclusion patterns e.g. "bucket/*.tmp,*.exe"`,
|
Description: `exclude cache for following patterns e.g. "bucket/*.tmp,*.exe"`,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "csv",
|
Type: "csv",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
|
||||||
Key: config.Comment,
|
|
||||||
Description: config.DefaultComment,
|
|
||||||
Optional: true,
|
|
||||||
Type: "sentence",
|
|
||||||
},
|
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: After,
|
Key: After,
|
||||||
Description: `minimum accesses before caching an object`,
|
Description: `minimum number of access before caching an object`,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "number",
|
Type: "number",
|
||||||
},
|
},
|
||||||
@@ -80,5 +74,11 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.Comment,
|
||||||
|
Description: config.DefaultComment,
|
||||||
|
Optional: true,
|
||||||
|
Type: "sentence",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
@@ -113,3 +114,12 @@ func LoadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) {
|
|||||||
}
|
}
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnsureCertAndKey checks if both client certificate and key paths are provided
|
||||||
|
func EnsureCertAndKey(ClientCert, ClientKey string) error {
|
||||||
|
if (ClientCert != "" && ClientKey == "") ||
|
||||||
|
(ClientCert == "" && ClientKey != "") {
|
||||||
|
return errors.New("cert and key must be specified as a pair")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const (
|
|||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,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+etcdPathSeparator, false)
|
records, err := c.list(key, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,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",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ var (
|
|||||||
Help = config.HelpKVS{
|
Help = config.HelpKVS{
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: Bitrot,
|
Key: Bitrot,
|
||||||
Description: `perform bitrot scan on disks when checking objects during crawl`,
|
Description: `perform bitrot scan on disks when checking objects during scanner`,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "on|off",
|
Type: "on|off",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"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 (
|
||||||
@@ -58,7 +59,6 @@ type Config struct {
|
|||||||
GroupSearchBaseDistName string `json:"groupSearchBaseDN"`
|
GroupSearchBaseDistName string `json:"groupSearchBaseDN"`
|
||||||
GroupSearchBaseDistNames []string `json:"-"`
|
GroupSearchBaseDistNames []string `json:"-"`
|
||||||
GroupSearchFilter string `json:"groupSearchFilter"`
|
GroupSearchFilter string `json:"groupSearchFilter"`
|
||||||
GroupNameAttribute string `json:"groupNameAttribute"`
|
|
||||||
|
|
||||||
// Lookup bind LDAP service account
|
// Lookup bind LDAP service account
|
||||||
LookupBindDN string `json:"lookupBindDN"`
|
LookupBindDN string `json:"lookupBindDN"`
|
||||||
@@ -82,7 +82,6 @@ const (
|
|||||||
UserDNSearchFilter = "user_dn_search_filter"
|
UserDNSearchFilter = "user_dn_search_filter"
|
||||||
UsernameFormat = "username_format"
|
UsernameFormat = "username_format"
|
||||||
GroupSearchFilter = "group_search_filter"
|
GroupSearchFilter = "group_search_filter"
|
||||||
GroupNameAttribute = "group_name_attribute"
|
|
||||||
GroupSearchBaseDN = "group_search_base_dn"
|
GroupSearchBaseDN = "group_search_base_dn"
|
||||||
TLSSkipVerify = "tls_skip_verify"
|
TLSSkipVerify = "tls_skip_verify"
|
||||||
ServerInsecure = "server_insecure"
|
ServerInsecure = "server_insecure"
|
||||||
@@ -97,7 +96,6 @@ const (
|
|||||||
EnvUserDNSearchBaseDN = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"
|
EnvUserDNSearchBaseDN = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN"
|
||||||
EnvUserDNSearchFilter = "MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER"
|
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"
|
EnvLookupBindDN = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"
|
||||||
EnvLookupBindPassword = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"
|
EnvLookupBindPassword = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"
|
||||||
@@ -106,6 +104,7 @@ const (
|
|||||||
var removedKeys = []string{
|
var removedKeys = []string{
|
||||||
"username_search_filter",
|
"username_search_filter",
|
||||||
"username_search_base_dn",
|
"username_search_base_dn",
|
||||||
|
"group_name_attribute",
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultKVS - default config for LDAP config
|
// DefaultKVS - default config for LDAP config
|
||||||
@@ -131,10 +130,6 @@ var (
|
|||||||
Key: GroupSearchFilter,
|
Key: GroupSearchFilter,
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
config.KV{
|
|
||||||
Key: GroupNameAttribute,
|
|
||||||
Value: "",
|
|
||||||
},
|
|
||||||
config.KV{
|
config.KV{
|
||||||
Key: GroupSearchBaseDN,
|
Key: GroupSearchBaseDN,
|
||||||
Value: "",
|
Value: "",
|
||||||
@@ -180,13 +175,22 @@ func getGroups(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
|
|||||||
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) lookupBind(conn *ldap.Conn) error {
|
func (l *Config) lookupBind(conn *ldap.Conn) error {
|
||||||
return conn.Bind(l.LookupBindDN, l.LookupBindPassword)
|
var err error
|
||||||
|
if l.LookupBindPassword == "" {
|
||||||
|
err = conn.UnauthenticatedBind(l.LookupBindDN)
|
||||||
|
} else {
|
||||||
|
err = conn.Bind(l.LookupBindDN, l.LookupBindPassword)
|
||||||
|
}
|
||||||
|
if ldap.IsErrorWithCode(err, 49) {
|
||||||
|
return fmt.Errorf("LDAP Lookup Bind user invalid credentials error: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// usernameFormatsBind - Iterates over all given username formats and expects
|
// usernameFormatsBind - Iterates over all given username formats and expects
|
||||||
@@ -208,6 +212,8 @@ func (l *Config) usernameFormatsBind(conn *ldap.Conn, username, password string)
|
|||||||
if errs[i] == nil {
|
if errs[i] == nil {
|
||||||
bindDistNames = append(bindDistNames, bindDN)
|
bindDistNames = append(bindDistNames, bindDN)
|
||||||
successCount++
|
successCount++
|
||||||
|
} else if !ldap.IsErrorWithCode(errs[i], 49) {
|
||||||
|
return "", fmt.Errorf("LDAP Bind request failed with unexpected error: %v", errs[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if successCount == 0 {
|
if successCount == 0 {
|
||||||
@@ -217,7 +223,7 @@ func (l *Config) usernameFormatsBind(conn *ldap.Conn, username, password string)
|
|||||||
errStrings = append(errStrings, err.Error())
|
errStrings = append(errStrings, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outErr := fmt.Sprintf("All username formats failed with: %s", strings.Join(errStrings, "; "))
|
outErr := fmt.Sprintf("All username formats failed due to invalid credentials: %s", strings.Join(errStrings, "; "))
|
||||||
return "", errors.New(outErr)
|
return "", errors.New(outErr)
|
||||||
}
|
}
|
||||||
if successCount > 1 {
|
if successCount > 1 {
|
||||||
@@ -307,12 +313,13 @@ func (l *Config) Bind(username, password string) (string, []string, error) {
|
|||||||
var groups []string
|
var groups []string
|
||||||
if l.GroupSearchFilter != "" {
|
if l.GroupSearchFilter != "" {
|
||||||
for _, groupSearchBase := range l.GroupSearchBaseDistNames {
|
for _, groupSearchBase := range l.GroupSearchBaseDistNames {
|
||||||
filter := strings.Replace(l.GroupSearchFilter, "%s", ldap.EscapeFilter(bindDN), -1)
|
filter := strings.Replace(l.GroupSearchFilter, "%s", ldap.EscapeFilter(username), -1)
|
||||||
|
filter = strings.Replace(filter, "%d", ldap.EscapeFilter(bindDN), -1)
|
||||||
searchRequest := ldap.NewSearchRequest(
|
searchRequest := ldap.NewSearchRequest(
|
||||||
groupSearchBase,
|
groupSearchBase,
|
||||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
filter,
|
filter,
|
||||||
[]string{l.GroupNameAttribute},
|
nil,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -368,6 +375,38 @@ func (l Config) GetExpiryDuration() time.Duration {
|
|||||||
return l.stsExpiryDuration
|
return l.stsExpiryDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l Config) testConnection() error {
|
||||||
|
conn, err := l.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating connection to LDAP server: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if l.isUsingLookupBind {
|
||||||
|
if err = l.lookupBind(conn); err != nil {
|
||||||
|
return fmt.Errorf("Error connecting as LDAP Lookup Bind user: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate some random user credentials for username formats mode test.
|
||||||
|
username := fmt.Sprintf("sometestuser%09d", rand.Int31n(1000000000))
|
||||||
|
charset := []byte("abcdefghijklmnopqrstuvwxyz" +
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
rand.Shuffle(len(charset), func(i, j int) {
|
||||||
|
charset[i], charset[j] = charset[j], charset[i]
|
||||||
|
})
|
||||||
|
password := string(charset[:20])
|
||||||
|
_, err = l.usernameFormatsBind(conn, username, password)
|
||||||
|
if err == nil {
|
||||||
|
// We don't expect to successfully guess a credential in this
|
||||||
|
// way.
|
||||||
|
return fmt.Errorf("Unexpected random credentials success for user=%s password=%s", username, password)
|
||||||
|
} else if strings.HasPrefix(err.Error(), "All username formats failed due to invalid credentials: ") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("LDAP connection test error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Enabled returns if jwks is enabled.
|
// Enabled returns if jwks is enabled.
|
||||||
func Enabled(kvs config.KVS) bool {
|
func Enabled(kvs config.KVS) bool {
|
||||||
return kvs.Get(ServerAddr) != ""
|
return kvs.Get(ServerAddr) != ""
|
||||||
@@ -427,14 +466,14 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
|
|||||||
// Lookup bind user configuration
|
// Lookup bind user configuration
|
||||||
lookupBindDN := env.Get(EnvLookupBindDN, kvs.Get(LookupBindDN))
|
lookupBindDN := env.Get(EnvLookupBindDN, kvs.Get(LookupBindDN))
|
||||||
lookupBindPassword := env.Get(EnvLookupBindPassword, kvs.Get(LookupBindPassword))
|
lookupBindPassword := env.Get(EnvLookupBindPassword, kvs.Get(LookupBindPassword))
|
||||||
if lookupBindDN != "" && lookupBindPassword != "" {
|
if lookupBindDN != "" {
|
||||||
l.LookupBindDN = lookupBindDN
|
l.LookupBindDN = lookupBindDN
|
||||||
l.LookupBindPassword = lookupBindPassword
|
l.LookupBindPassword = lookupBindPassword
|
||||||
l.isUsingLookupBind = true
|
l.isUsingLookupBind = true
|
||||||
|
|
||||||
// User DN search configuration
|
// User DN search configuration
|
||||||
userDNSearchBaseDN := env.Get(EnvUserDNSearchBaseDN, kvs.Get(UserDNSearchBaseDN))
|
userDNSearchBaseDN := env.Get(EnvUserDNSearchBaseDN, kvs.Get(UserDNSearchBaseDN))
|
||||||
userDNSearchFilter := env.Get(EnvUserDNSearchFilter, kvs.Get(EnvUserDNSearchFilter))
|
userDNSearchFilter := env.Get(EnvUserDNSearchFilter, kvs.Get(UserDNSearchFilter))
|
||||||
if userDNSearchFilter == "" || userDNSearchBaseDN == "" {
|
if userDNSearchFilter == "" || userDNSearchBaseDN == "" {
|
||||||
return l, errors.New("In lookup bind mode, userDN search base DN and userDN search filter are both required")
|
return l, errors.New("In lookup bind mode, userDN search base DN and userDN search filter are both required")
|
||||||
}
|
}
|
||||||
@@ -461,23 +500,22 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
|
|||||||
return l, errors.New("Either Lookup Bind mode or Username Format mode is required.")
|
return l, errors.New("Either Lookup Bind mode or Username Format mode is required.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test connection to LDAP server.
|
||||||
|
if err := l.testConnection(); err != nil {
|
||||||
|
return l, fmt.Errorf("Connection test for LDAP server failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Group search params configuration
|
// 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.GroupSearchBaseDistName = grpSearchBaseDN
|
||||||
l.GroupSearchBaseDistNames = strings.Split(l.GroupSearchBaseDistName, dnDelimiter)
|
l.GroupSearchBaseDistNames = strings.Split(l.GroupSearchBaseDistName, dnDelimiter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,12 +68,6 @@ var (
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
|
||||||
Key: GroupNameAttribute,
|
|
||||||
Description: `search attribute for group name e.g. "cn"`,
|
|
||||||
Optional: true,
|
|
||||||
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"`,
|
||||||
|
|||||||
@@ -41,10 +41,6 @@ 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.GroupSearchBaseDistName,
|
Value: ldapArgs.GroupSearchBaseDistName,
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
/*
|
// MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
//
|
||||||
*
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
// you may not use this file except in compliance with the License.
|
||||||
* you may not use this file except in compliance with the License.
|
// You may obtain a copy of the License at
|
||||||
* You may obtain a copy of the License at
|
//
|
||||||
*
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
*
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// See the License for the specific language governing permissions and
|
||||||
* See the License for the specific language governing permissions and
|
// limitations under the License.
|
||||||
* limitations under the License.
|
|
||||||
*/
|
// +build !fips
|
||||||
|
|
||||||
package openid
|
package openid
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
|
||||||
// Needed for SHA3 to work - See: https://golang.org/src/crypto/crypto.go?s=1034:1288
|
// Needed for SHA3 to work - See: https://golang.org/src/crypto/crypto.go?s=1034:1288
|
||||||
_ "golang.org/x/crypto/sha3"
|
_ "golang.org/x/crypto/sha3" // There is no SHA-3 FIPS-140 2 compliant implementation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Specific instances for EC256 and company
|
// Specific instances for EC256 and company
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
/*
|
// MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
//
|
||||||
*
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
// you may not use this file except in compliance with the License.
|
||||||
* you may not use this file except in compliance with the License.
|
// You may obtain a copy of the License at
|
||||||
* You may obtain a copy of the License at
|
//
|
||||||
*
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
*
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// See the License for the specific language governing permissions and
|
||||||
* See the License for the specific language governing permissions and
|
// limitations under the License.
|
||||||
* limitations under the License.
|
|
||||||
*/
|
// +build !fips
|
||||||
|
|
||||||
package openid
|
package openid
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
|
||||||
// Needed for SHA3 to work - See: https://golang.org/src/crypto/crypto.go?s=1034:1288
|
// Needed for SHA3 to work - See: https://golang.org/src/crypto/crypto.go?s=1034:1288
|
||||||
_ "golang.org/x/crypto/sha3"
|
_ "golang.org/x/crypto/sha3" // There is no SHA-3 FIPS-140 2 compliant implementation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Specific instances for RS256 and company
|
// Specific instances for RS256 and company
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -410,6 +411,9 @@ func NewKMS(cfg KMSConfig) (kms KMS, err error) {
|
|||||||
} else if cfg.Vault.Enabled && cfg.Kes.Enabled {
|
} else if cfg.Vault.Enabled && cfg.Kes.Enabled {
|
||||||
return kms, errors.New("Ambiguous KMS configuration: vault configuration and kes configuration are provided at the same time")
|
return kms, errors.New("Ambiguous KMS configuration: vault configuration and kes configuration are provided at the same time")
|
||||||
} else if cfg.Vault.Enabled {
|
} else if cfg.Vault.Enabled {
|
||||||
|
if v, ok := os.LookupEnv("MINIO_KMS_VAULT_DEPRECATION"); !ok || v != "off" { // TODO(aead): Remove once Vault support has been removed
|
||||||
|
return kms, errors.New("Hashicorp Vault is deprecated and will be removed Oct. 2021. To temporarily enable Hashicorp Vault support, set MINIO_KMS_VAULT_DEPRECATION=off")
|
||||||
|
}
|
||||||
kms, err = NewVault(cfg.Vault)
|
kms, err = NewVault(cfg.Vault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return kms, err
|
return kms, err
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -42,9 +43,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dataCrawlSleepPerFolder = time.Millisecond // Time to wait between folders.
|
dataScannerSleepPerFolder = time.Millisecond // Time to wait between folders.
|
||||||
dataCrawlStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
|
dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
|
||||||
dataUsageUpdateDirCycles = 16 // Visit all folders every n cycles.
|
dataUsageUpdateDirCycles = 16 // Visit all folders every n cycles.
|
||||||
|
|
||||||
healDeleteDangling = true
|
healDeleteDangling = true
|
||||||
healFolderIncludeProb = 32 // Include a clean folder one in n cycles.
|
healFolderIncludeProb = 32 // Include a clean folder one in n cycles.
|
||||||
@@ -55,27 +56,28 @@ 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.
|
var err error
|
||||||
locker := objAPI.NewNSLock(minioMetaBucket, "runDataCrawler.lock")
|
// Make sure only 1 scanner is running on the cluster.
|
||||||
|
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)
|
ctx, 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(dataScannerStartDelay)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -84,31 +86,34 @@ 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)
|
scannerTimer := time.NewTimer(dataScannerStartDelay)
|
||||||
defer crawlTimer.Stop()
|
defer scannerTimer.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-crawlTimer.C:
|
case <-scannerTimer.C:
|
||||||
// Reset the timer for next cycle.
|
// Reset the timer for next cycle.
|
||||||
crawlTimer.Reset(dataCrawlStartDelay)
|
scannerTimer.Reset(dataScannerStartDelay)
|
||||||
|
|
||||||
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.
|
||||||
@@ -116,7 +121,7 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
go storeDataUsageInBackend(ctx, objAPI, results)
|
go storeDataUsageInBackend(ctx, objAPI, results)
|
||||||
bf, err := globalNotificationSys.updateBloomFilter(ctx, nextBloomCycle)
|
bf, err := globalNotificationSys.updateBloomFilter(ctx, nextBloomCycle)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
err = objAPI.CrawlAndGetDataUsage(ctx, bf, results)
|
err = objAPI.NSScanner(ctx, bf, results)
|
||||||
close(results)
|
close(results)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -124,13 +129,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)
|
||||||
}
|
}
|
||||||
@@ -152,27 +157,27 @@ type folderScanner struct {
|
|||||||
newCache dataUsageCache
|
newCache dataUsageCache
|
||||||
withFilter *bloomFilter
|
withFilter *bloomFilter
|
||||||
|
|
||||||
dataUsageCrawlDebug bool
|
dataUsageScannerDebug bool
|
||||||
healFolderInclude uint32 // Include a clean folder one in n cycles.
|
healFolderInclude uint32 // Include a clean folder one in n cycles.
|
||||||
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
||||||
|
|
||||||
newFolders []cachedFolder
|
newFolders []cachedFolder
|
||||||
existingFolders []cachedFolder
|
existingFolders []cachedFolder
|
||||||
disks []StorageAPI
|
disks []StorageAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
// crawlDataFolder will crawl the basepath+cache.Info.Name and return an updated cache.
|
// scanDataFolder will scanner 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 scanDataFolder(ctx context.Context, basePath string, cache dataUsageCache, getSize getSizeFn) (dataUsageCache, error) {
|
||||||
t := UTCNow()
|
t := UTCNow()
|
||||||
|
|
||||||
logPrefix := color.Green("data-usage: ")
|
logPrefix := color.Green("data-usage: ")
|
||||||
logSuffix := color.Blue("- %v + %v", basePath, cache.Info.Name)
|
logSuffix := color.Blue("- %v + %v", basePath, cache.Info.Name)
|
||||||
if intDataUpdateTracker.debug {
|
if intDataUpdateTracker.debug {
|
||||||
defer func() {
|
defer func() {
|
||||||
console.Debugf(logPrefix+" Crawl time: %v %s\n", time.Since(t), logSuffix)
|
console.Debugf(logPrefix+" Scanner time: %v %s\n", time.Since(t), logSuffix)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -185,15 +190,15 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
skipHeal := cache.Info.SkipHealing
|
skipHeal := cache.Info.SkipHealing
|
||||||
|
|
||||||
s := folderScanner{
|
s := folderScanner{
|
||||||
root: basePath,
|
root: basePath,
|
||||||
getSize: getSize,
|
getSize: getSize,
|
||||||
oldCache: cache,
|
oldCache: cache,
|
||||||
newCache: dataUsageCache{Info: cache.Info},
|
newCache: dataUsageCache{Info: cache.Info},
|
||||||
newFolders: nil,
|
newFolders: nil,
|
||||||
existingFolders: nil,
|
existingFolders: nil,
|
||||||
dataUsageCrawlDebug: intDataUpdateTracker.debug,
|
dataUsageScannerDebug: intDataUpdateTracker.debug,
|
||||||
healFolderInclude: 0,
|
healFolderInclude: 0,
|
||||||
healObjectSelect: 0,
|
healObjectSelect: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add disks for set healing.
|
// Add disks for set healing.
|
||||||
@@ -223,21 +228,21 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
s.withFilter = nil
|
s.withFilter = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"Start crawling. Bloom filter: %v %s\n", s.withFilter != nil, logSuffix)
|
console.Debugf(logPrefix+"Start scanning. Bloom filter: %v %s\n", s.withFilter != nil, logSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
done := ctx.Done()
|
done := ctx.Done()
|
||||||
var flattenLevels = 2
|
var flattenLevels = 2
|
||||||
|
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"Cycle: %v, Entries: %v %s\n", cache.Info.NextCycle, len(cache.Cache), logSuffix)
|
console.Debugf(logPrefix+"Cycle: %v, Entries: %v %s\n", cache.Info.NextCycle, len(cache.Cache), logSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always scan flattenLevels deep. Cache root is level 0.
|
// Always scan flattenLevels deep. Cache root is level 0.
|
||||||
todo := []cachedFolder{{name: cache.Info.Name, objectHealProbDiv: 1}}
|
todo := []cachedFolder{{name: cache.Info.Name, objectHealProbDiv: 1}}
|
||||||
for i := 0; i < flattenLevels; i++ {
|
for i := 0; i < flattenLevels; i++ {
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"Level %v, scanning %v directories. %s\n", i, len(todo), logSuffix)
|
console.Debugf(logPrefix+"Level %v, scanning %v directories. %s\n", i, len(todo), logSuffix)
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
@@ -253,7 +258,7 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"New folders: %v %s\n", s.newFolders, logSuffix)
|
console.Debugf(logPrefix+"New folders: %v %s\n", s.newFolders, logSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +287,7 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"Existing folders: %v %s\n", len(s.existingFolders), logSuffix)
|
console.Debugf(logPrefix+"Existing folders: %v %s\n", len(s.existingFolders), logSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,13 +314,13 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
// If folder isn't in filter, skip it completely.
|
// If folder isn't in filter, skip it completely.
|
||||||
if !s.withFilter.containsDir(folder.name) {
|
if !s.withFilter.containsDir(folder.name) {
|
||||||
if !h.mod(s.oldCache.Info.NextCycle, s.healFolderInclude/folder.objectHealProbDiv) {
|
if !h.mod(s.oldCache.Info.NextCycle, s.healFolderInclude/folder.objectHealProbDiv) {
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"Skipping non-updated folder: %v %s\n", folder, logSuffix)
|
console.Debugf(logPrefix+"Skipping non-updated folder: %v %s\n", folder, logSuffix)
|
||||||
}
|
}
|
||||||
s.newCache.replaceHashed(h, folder.parent, s.oldCache.Cache[h.Key()])
|
s.newCache.replaceHashed(h, folder.parent, s.oldCache.Cache[h.Key()])
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"Adding non-updated folder to heal check: %v %s\n", folder.name, logSuffix)
|
console.Debugf(logPrefix+"Adding non-updated folder to heal check: %v %s\n", folder.name, logSuffix)
|
||||||
}
|
}
|
||||||
// Update probability of including objects
|
// Update probability of including objects
|
||||||
@@ -337,8 +342,8 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
}
|
}
|
||||||
s.newCache.replaceHashed(h, folder.parent, *du)
|
s.newCache.replaceHashed(h, folder.parent, *du)
|
||||||
}
|
}
|
||||||
if s.dataUsageCrawlDebug {
|
if s.dataUsageScannerDebug {
|
||||||
console.Debugf(logPrefix+"Finished crawl, %v entries %s\n", len(s.newCache.Cache), logSuffix)
|
console.Debugf(logPrefix+"Finished scanner, %v entries %s\n", len(s.newCache.Cache), logSuffix)
|
||||||
}
|
}
|
||||||
s.newCache.Info.LastUpdate = UTCNow()
|
s.newCache.Info.LastUpdate = UTCNow()
|
||||||
s.newCache.Info.NextCycle++
|
s.newCache.Info.NextCycle++
|
||||||
@@ -367,7 +372,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
|
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
|
||||||
var activeLifeCycle *lifecycle.Lifecycle
|
var activeLifeCycle *lifecycle.Lifecycle
|
||||||
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) {
|
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(scannerLogPrefix+" Prefix %q has active rules\n", prefix)
|
console.Debugf(scannerLogPrefix+" Prefix %q has active rules\n", prefix)
|
||||||
}
|
}
|
||||||
activeLifeCycle = f.oldCache.Info.lifeCycle
|
activeLifeCycle = f.oldCache.Info.lifeCycle
|
||||||
@@ -378,20 +383,20 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
if folder.name != dataUsageRoot && !filter.containsDir(folder.name) {
|
if folder.name != dataUsageRoot && !filter.containsDir(folder.name) {
|
||||||
if !thisHash.mod(f.oldCache.Info.NextCycle, f.healFolderInclude/folder.objectHealProbDiv) {
|
if !thisHash.mod(f.oldCache.Info.NextCycle, f.healFolderInclude/folder.objectHealProbDiv) {
|
||||||
f.newCache.copyWithChildren(&f.oldCache, thisHash, folder.parent)
|
f.newCache.copyWithChildren(&f.oldCache, thisHash, folder.parent)
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(scannerLogPrefix+" Skipping non-updated folder: %v\n", folder.name)
|
console.Debugf(scannerLogPrefix+" Skipping non-updated folder: %v\n", folder.name)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
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, dataScannerSleepPerFolder)
|
||||||
|
|
||||||
cache := dataUsageEntry{}
|
cache := dataUsageEntry{}
|
||||||
|
|
||||||
@@ -400,22 +405,22 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
entName = path.Clean(path.Join(folder.name, entName))
|
entName = path.Clean(path.Join(folder.name, entName))
|
||||||
bucket, prefix := path2BucketObjectWithBasePath(f.root, entName)
|
bucket, prefix := path2BucketObjectWithBasePath(f.root, entName)
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
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.dataUsageScannerDebug {
|
||||||
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:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,16 +448,16 @@ 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 := scannerItem{
|
||||||
Path: path.Join(f.root, entName),
|
Path: path.Join(f.root, entName),
|
||||||
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.dataUsageScannerDebug,
|
||||||
lifeCycle: activeLifeCycle,
|
lifeCycle: activeLifeCycle,
|
||||||
heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
||||||
}
|
}
|
||||||
@@ -528,17 +533,18 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
healObjectsPrefix := color.Green("healObjects:")
|
healObjectsPrefix := color.Green("healObjects:")
|
||||||
for k := range existing {
|
for k := range existing {
|
||||||
bucket, prefix := path2BucketObject(k)
|
bucket, prefix := path2BucketObject(k)
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(scannerLogPrefix+" checking disappeared folder: %v/%v\n", bucket, prefix)
|
console.Debugf(scannerLogPrefix+" checking disappeared folder: %v/%v\n", bucket, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
// Dynamic time delay.
|
||||||
wait := crawlerSleeper.Timer(ctx)
|
wait := scannerSleeper.Timer(ctx)
|
||||||
resolver.bucket = bucket
|
resolver.bucket = bucket
|
||||||
|
|
||||||
foundObjs := false
|
foundObjs := false
|
||||||
dangling := false
|
dangling := false
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
err := listPathRaw(ctx, listPathRawOptions{
|
err := listPathRaw(ctx, listPathRawOptions{
|
||||||
disks: f.disks,
|
disks: f.disks,
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
@@ -548,13 +554,13 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
minDisks: len(f.disks), // We want full consistency.
|
minDisks: len(f.disks), // We want full consistency.
|
||||||
// Weird, maybe transient error.
|
// Weird, maybe transient error.
|
||||||
agreed: func(entry metaCacheEntry) {
|
agreed: func(entry metaCacheEntry) {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(healObjectsPrefix+" got agreement: %v\n", entry.name)
|
console.Debugf(healObjectsPrefix+" got agreement: %v\n", entry.name)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Some disks have data for this.
|
// Some disks have data for this.
|
||||||
partial: func(entries metaCacheEntries, nAgreed int, errs []error) {
|
partial: func(entries metaCacheEntries, nAgreed int, errs []error) {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(healObjectsPrefix+" got partial, %d agreed, errs: %v\n", nAgreed, errs)
|
console.Debugf(healObjectsPrefix+" got partial, %d agreed, errs: %v\n", nAgreed, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,7 +569,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 {
|
||||||
@@ -576,7 +582,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
entry, _ = entries.firstFound()
|
entry, _ = entries.firstFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(healObjectsPrefix+" resolved to: %v, dir: %v\n", entry.name, entry.isDir())
|
console.Debugf(healObjectsPrefix+" resolved to: %v, dir: %v\n", entry.name, entry.isDir())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,7 +606,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,
|
||||||
@@ -614,31 +620,31 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
},
|
},
|
||||||
// Too many disks failed.
|
// Too many disks failed.
|
||||||
finished: func(errs []error) {
|
finished: func(errs []error) {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(healObjectsPrefix+" too many errors: %v\n", errs)
|
console.Debugf(healObjectsPrefix+" too many errors: %v\n", errs)
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if f.dataUsageCrawlDebug && err != nil && err != errFileNotFound {
|
if f.dataUsageScannerDebug && err != nil && err != errFileNotFound {
|
||||||
console.Debugf(healObjectsPrefix+" checking returned value %v (%T)\n", err, err)
|
console.Debugf(healObjectsPrefix+" checking returned value %v (%T)\n", err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found one or more disks with this folder, delete it.
|
// If we found one or more disks with this folder, delete it.
|
||||||
if err == nil && dangling {
|
if err == nil && dangling {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(healObjectsPrefix+" deleting dangling directory %s\n", prefix)
|
console.Debugf(healObjectsPrefix+" deleting dangling directory %s\n", prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@@ -678,7 +684,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:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,12 +692,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, dataScannerSleepPerFolder)
|
||||||
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)
|
||||||
@@ -701,19 +707,19 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder,
|
|||||||
bucket, prefix := path2BucketObjectWithBasePath(f.root, fileName)
|
bucket, prefix := path2BucketObjectWithBasePath(f.root, fileName)
|
||||||
var activeLifeCycle *lifecycle.Lifecycle
|
var activeLifeCycle *lifecycle.Lifecycle
|
||||||
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, false) {
|
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, false) {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageScannerDebug {
|
||||||
console.Debugf(deepScannerLogPrefix+" Prefix %q has active rules\n", prefix)
|
console.Debugf(deepScannerLogPrefix+" Prefix %q has active rules\n", prefix)
|
||||||
}
|
}
|
||||||
activeLifeCycle = f.oldCache.Info.lifeCycle
|
activeLifeCycle = f.oldCache.Info.lifeCycle
|
||||||
}
|
}
|
||||||
|
|
||||||
item := crawlItem{
|
item := scannerItem{
|
||||||
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.dataUsageScannerDebug,
|
||||||
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,
|
||||||
}
|
}
|
||||||
@@ -748,8 +754,8 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder,
|
|||||||
return &cache, nil
|
return &cache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// crawlItem represents each file while walking.
|
// scannerItem represents each file while walking.
|
||||||
type crawlItem struct {
|
type scannerItem struct {
|
||||||
Path string
|
Path string
|
||||||
Typ os.FileMode
|
Typ os.FileMode
|
||||||
|
|
||||||
@@ -769,10 +775,10 @@ type sizeSummary struct {
|
|||||||
replicaSize int64
|
replicaSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type getSizeFn func(item crawlItem) (sizeSummary, error)
|
type getSizeFn func(item scannerItem) (sizeSummary, error)
|
||||||
|
|
||||||
// transformMetaDir will transform a directory to prefix/file.ext
|
// transformMetaDir will transform a directory to prefix/file.ext
|
||||||
func (i *crawlItem) transformMetaDir() {
|
func (i *scannerItem) transformMetaDir() {
|
||||||
split := strings.Split(i.prefix, SlashSeparator)
|
split := strings.Split(i.prefix, SlashSeparator)
|
||||||
if len(split) > 1 {
|
if len(split) > 1 {
|
||||||
i.prefix = path.Join(split[:len(split)-1]...)
|
i.prefix = path.Join(split[:len(split)-1]...)
|
||||||
@@ -785,22 +791,21 @@ 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.
|
||||||
// If no metadata is supplied, -1 is returned if no action is taken.
|
// If no metadata is supplied, -1 is returned if no action is taken.
|
||||||
func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta actionMeta) (size int64) {
|
func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, meta actionMeta) (size int64) {
|
||||||
size, err := meta.oi.GetActualSize()
|
size, err := meta.oi.GetActualSize()
|
||||||
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 != "" {
|
||||||
@@ -839,8 +844,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,
|
||||||
@@ -884,96 +889,135 @@ 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 {
|
||||||
|
switch action {
|
||||||
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
|
default: // for all lifecycle actions that remove data
|
||||||
|
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 obj.TransitionStatus == "" {
|
|
||||||
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 {
|
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
// Assume it is still there.
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
globalTransitionState.queueTransitionTask(obj)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.TransitionStatus != "" {
|
|
||||||
if err := deleteTransitionedObject(ctx, o, i.bucket, i.objectPath(), lcOpts, action, false); err != nil {
|
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
return 0
|
return false
|
||||||
}
|
}
|
||||||
|
// Assume it is still there.
|
||||||
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) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
return 0
|
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
|
||||||
@@ -984,20 +1028,42 @@ 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 or 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.
|
||||||
func (i *crawlItem) objectPath() string {
|
func (i *scannerItem) objectPath() string {
|
||||||
return path.Join(i.prefix, i.objectName)
|
return path.Join(i.prefix, i.objectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// healReplication will heal a scanned item that has failed replication.
|
// healReplication will heal a scanned item that has failed replication.
|
||||||
func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, oi ObjectInfo, sizeS *sizeSummary) {
|
func (i *scannerItem) healReplication(ctx context.Context, o ObjectLayer, oi ObjectInfo, sizeS *sizeSummary) {
|
||||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||||
// heal delete marker replication failure or versioned delete replication failure
|
// heal delete marker replication failure or versioned delete replication failure
|
||||||
if oi.ReplicationStatus == replication.Pending ||
|
if oi.ReplicationStatus == replication.Pending ||
|
||||||
@@ -1010,10 +1076,10 @@ func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, oi Objec
|
|||||||
switch oi.ReplicationStatus {
|
switch oi.ReplicationStatus {
|
||||||
case replication.Pending:
|
case replication.Pending:
|
||||||
sizeS.pendingSize += oi.Size
|
sizeS.pendingSize += oi.Size
|
||||||
globalReplicationState.queueReplicaTask(oi)
|
globalReplicationPool.queueReplicaTask(oi)
|
||||||
case replication.Failed:
|
case replication.Failed:
|
||||||
sizeS.failedSize += oi.Size
|
sizeS.failedSize += oi.Size
|
||||||
globalReplicationState.queueReplicaTask(oi)
|
globalReplicationPool.queueReplicaTask(oi)
|
||||||
case replication.Completed, "COMPLETE":
|
case replication.Completed, "COMPLETE":
|
||||||
sizeS.replicatedSize += oi.Size
|
sizeS.replicatedSize += oi.Size
|
||||||
case replication.Replica:
|
case replication.Replica:
|
||||||
@@ -1022,7 +1088,7 @@ func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, oi Objec
|
|||||||
}
|
}
|
||||||
|
|
||||||
// healReplicationDeletes will heal a scanned deleted item that failed to replicate deletes.
|
// healReplicationDeletes will heal a scanned deleted item that failed to replicate deletes.
|
||||||
func (i *crawlItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, oi ObjectInfo) {
|
func (i *scannerItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, oi ObjectInfo) {
|
||||||
// handle soft delete and permanent delete failures here.
|
// handle soft delete and permanent delete failures here.
|
||||||
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
|
||||||
versionID := ""
|
versionID := ""
|
||||||
@@ -1032,7 +1098,7 @@ func (i *crawlItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, o
|
|||||||
} else {
|
} else {
|
||||||
versionID = oi.VersionID
|
versionID = oi.VersionID
|
||||||
}
|
}
|
||||||
globalReplicationState.queueReplicaDeleteTask(DeletedObjectVersionInfo{
|
globalReplicationPool.queueReplicaDeleteTask(DeletedObjectVersionInfo{
|
||||||
DeletedObject: DeletedObject{
|
DeletedObject: DeletedObject{
|
||||||
ObjectName: oi.Name,
|
ObjectName: oi.Name,
|
||||||
DeleteMarkerVersionID: dmVersionID,
|
DeleteMarkerVersionID: dmVersionID,
|
||||||
@@ -80,7 +80,7 @@ func newDataUpdateTracker() *dataUpdateTracker {
|
|||||||
Current: dataUpdateFilter{
|
Current: dataUpdateFilter{
|
||||||
idx: 1,
|
idx: 1,
|
||||||
},
|
},
|
||||||
debug: env.Get(envDataUsageCrawlDebug, config.EnableOff) == config.EnableOn || serverDebugLog,
|
debug: env.Get(envDataUsageScannerDebug, config.EnableOff) == config.EnableOn || serverDebugLog,
|
||||||
input: make(chan string, dataUpdateTrackerQueueSize),
|
input: make(chan string, dataUpdateTrackerQueueSize),
|
||||||
save: make(chan struct{}, 1),
|
save: make(chan struct{}, 1),
|
||||||
saveExited: make(chan struct{}),
|
saveExited: make(chan struct{}),
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ type dataUsageCacheInfo struct {
|
|||||||
Name string
|
Name string
|
||||||
LastUpdate time.Time
|
LastUpdate time.Time
|
||||||
NextCycle uint32
|
NextCycle uint32
|
||||||
// indicates if the disk is being healed and crawler
|
// indicates if the disk is being healed and scanner
|
||||||
// should skip healing the disk
|
// should skip healing the disk
|
||||||
SkipHealing bool
|
SkipHealing bool
|
||||||
BloomFilter []byte `msg:"BloomFilter,omitempty"`
|
BloomFilter []byte `msg:"BloomFilter,omitempty"`
|
||||||
@@ -485,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:
|
||||||
@@ -513,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
|
||||||
}
|
}
|
||||||
@@ -521,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -27,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
envDataUsageCrawlDebug = "MINIO_DISK_USAGE_CRAWL_DEBUG"
|
envDataUsageScannerDebug = "MINIO_DISK_USAGE_SCANNER_DEBUG"
|
||||||
|
|
||||||
dataUsageRoot = SlashSeparator
|
dataUsageRoot = SlashSeparator
|
||||||
dataUsageBucket = minioMetaBucket + SlashSeparator + bucketMetaPrefix
|
dataUsageBucket = minioMetaBucket + SlashSeparator + bucketMetaPrefix
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, bucket, files)
|
createUsageTestFiles(t, base, bucket, files)
|
||||||
|
|
||||||
getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
|
getSize := func(item scannerItem) (sizeS sizeSummary, err error) {
|
||||||
if item.Typ&os.ModeDir == 0 {
|
if item.Typ&os.ModeDir == 0 {
|
||||||
var s os.FileInfo
|
var s os.FileInfo
|
||||||
s, err = os.Stat(item.Path)
|
s, err = os.Stat(item.Path)
|
||||||
@@ -64,7 +64,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
got, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, bucket, files)
|
createUsageTestFiles(t, base, bucket, files)
|
||||||
got, err = crawlDataFolder(context.Background(), base, got, getSize)
|
got, err = scanDataFolder(context.Background(), base, got, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -270,7 +270,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Changed dir must be picked up in this many cycles.
|
// Changed dir must be picked up in this many cycles.
|
||||||
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
||||||
got, err = crawlDataFolder(context.Background(), base, got, getSize)
|
got, err = scanDataFolder(context.Background(), base, got, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -347,7 +347,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, "", files)
|
createUsageTestFiles(t, base, "", files)
|
||||||
|
|
||||||
getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
|
getSize := func(item scannerItem) (sizeS sizeSummary, err error) {
|
||||||
if item.Typ&os.ModeDir == 0 {
|
if item.Typ&os.ModeDir == 0 {
|
||||||
var s os.FileInfo
|
var s os.FileInfo
|
||||||
s, err = os.Stat(item.Path)
|
s, err = os.Stat(item.Path)
|
||||||
@@ -359,7 +359,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize)
|
got, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -469,7 +469,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, "", files)
|
createUsageTestFiles(t, base, "", files)
|
||||||
got, err = crawlDataFolder(context.Background(), base, got, getSize)
|
got, err = scanDataFolder(context.Background(), base, got, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -552,7 +552,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Changed dir must be picked up in this many cycles.
|
// Changed dir must be picked up in this many cycles.
|
||||||
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
||||||
got, err = crawlDataFolder(context.Background(), base, got, getSize)
|
got, err = scanDataFolder(context.Background(), base, got, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -646,7 +646,7 @@ func TestDataUsageCacheSerialize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, bucket, files)
|
createUsageTestFiles(t, base, bucket, files)
|
||||||
|
|
||||||
getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
|
getSize := func(item scannerItem) (sizeS sizeSummary, err error) {
|
||||||
if item.Typ&os.ModeDir == 0 {
|
if item.Typ&os.ModeDir == 0 {
|
||||||
var s os.FileInfo
|
var s os.FileInfo
|
||||||
s, err = os.Stat(item.Path)
|
s, err = os.Stat(item.Path)
|
||||||
@@ -658,7 +658,7 @@ func TestDataUsageCacheSerialize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
want, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
want, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(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
|
||||||
}
|
}
|
||||||
@@ -437,7 +437,7 @@ func (c *diskCache) Stat(ctx context.Context, bucket, object string) (oi ObjectI
|
|||||||
func (c *diskCache) statCachedMeta(ctx context.Context, cacheObjPath string) (meta *cacheMeta, partial bool, numHits int, err error) {
|
func (c *diskCache) statCachedMeta(ctx context.Context, cacheObjPath string) (meta *cacheMeta, partial bool, numHits int, err error) {
|
||||||
|
|
||||||
cLock := c.NewNSLockFn(cacheObjPath)
|
cLock := c.NewNSLockFn(cacheObjPath)
|
||||||
if err = cLock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
if ctx, err = cLock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
||||||
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
|
||||||
@@ -517,9 +515,11 @@ func (c *diskCache) statCache(ctx context.Context, cacheObjPath string) (meta *c
|
|||||||
// saves object metadata to disk cache
|
// saves object metadata to disk cache
|
||||||
// incHitsOnly is true if metadata update is incrementing only the hit counter
|
// incHitsOnly is true if metadata update is incrementing only the hit counter
|
||||||
func (c *diskCache) SaveMetadata(ctx context.Context, bucket, object string, meta map[string]string, actualSize int64, rs *HTTPRangeSpec, rsFileName string, incHitsOnly bool) error {
|
func (c *diskCache) SaveMetadata(ctx context.Context, bucket, object string, meta map[string]string, actualSize int64, rs *HTTPRangeSpec, rsFileName string, incHitsOnly bool) error {
|
||||||
|
var err error
|
||||||
cachedPath := getCacheSHADir(c.dir, bucket, object)
|
cachedPath := getCacheSHADir(c.dir, bucket, object)
|
||||||
cLock := c.NewNSLockFn(cachedPath)
|
cLock := c.NewNSLockFn(cachedPath)
|
||||||
if err := cLock.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = cLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cLock.Unlock()
|
defer cLock.Unlock()
|
||||||
@@ -570,7 +570,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
|
||||||
@@ -697,7 +696,8 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
|
|||||||
}
|
}
|
||||||
cachePath := getCacheSHADir(c.dir, bucket, object)
|
cachePath := getCacheSHADir(c.dir, bucket, object)
|
||||||
cLock := c.NewNSLockFn(cachePath)
|
cLock := c.NewNSLockFn(cachePath)
|
||||||
if err := cLock.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = cLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
defer cLock.Unlock()
|
defer cLock.Unlock()
|
||||||
@@ -911,7 +911,8 @@ func (c *diskCache) bitrotReadFromCache(ctx context.Context, filePath string, of
|
|||||||
func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, numHits int, err error) {
|
func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, numHits int, err error) {
|
||||||
cacheObjPath := getCacheSHADir(c.dir, bucket, object)
|
cacheObjPath := getCacheSHADir(c.dir, bucket, object)
|
||||||
cLock := c.NewNSLockFn(cacheObjPath)
|
cLock := c.NewNSLockFn(cacheObjPath)
|
||||||
if err := cLock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = cLock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return nil, numHits, err
|
return nil, numHits, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,7 +976,8 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
|
|||||||
// Deletes the cached object
|
// Deletes the cached object
|
||||||
func (c *diskCache) delete(ctx context.Context, cacheObjPath string) (err error) {
|
func (c *diskCache) delete(ctx context.Context, cacheObjPath string) (err error) {
|
||||||
cLock := c.NewNSLockFn(cacheObjPath)
|
cLock := c.NewNSLockFn(cacheObjPath)
|
||||||
if err := cLock.GetLock(ctx, globalOperationTimeout); err != nil {
|
_, err = cLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cLock.Unlock()
|
defer cLock.Unlock()
|
||||||
@@ -1023,7 +1025,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,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) {
|
||||||
@@ -649,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
|
||||||
}
|
}
|
||||||
@@ -796,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
|
||||||
}
|
}
|
||||||
@@ -818,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)
|
||||||
|
|||||||
@@ -23,12 +23,22 @@ import (
|
|||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"github.com/minio/minio/pkg/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (er erasureObjects) getLocalDisks() (localDisks []StorageAPI) {
|
||||||
|
disks := er.getDisks()
|
||||||
|
for _, disk := range disks {
|
||||||
|
if disk != nil && disk.IsLocal() {
|
||||||
|
localDisks = append(localDisks, disk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localDisks
|
||||||
|
}
|
||||||
|
|
||||||
func (er erasureObjects) getLoadBalancedLocalDisks() (newDisks []StorageAPI) {
|
func (er erasureObjects) getLoadBalancedLocalDisks() (newDisks []StorageAPI) {
|
||||||
disks := er.getDisks()
|
disks := er.getDisks()
|
||||||
// Based on the random shuffling return back randomized disks.
|
// Based on the random shuffling return back randomized disks.
|
||||||
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
|
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
|
||||||
if disks[i-1] != nil && disks[i-1].IsLocal() {
|
if disks[i-1] != nil && disks[i-1].IsLocal() {
|
||||||
if !disks[i-1].Healing() && disks[i-1].IsOnline() {
|
if disks[i-1].Healing() == nil && disks[i-1].IsOnline() {
|
||||||
newDisks = append(newDisks, disks[i-1])
|
newDisks = append(newDisks, disks[i-1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,46 +41,46 @@ var erasureDecodeTests = []struct {
|
|||||||
algorithm BitrotAlgorithm
|
algorithm BitrotAlgorithm
|
||||||
shouldFail, shouldFailQuorum bool
|
shouldFail, shouldFailQuorum bool
|
||||||
}{
|
}{
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 0
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 0
|
||||||
{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false}, // 1
|
{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false}, // 1
|
||||||
{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 2
|
{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 2
|
||||||
{dataBlocks: 5, onDisks: 10, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 1, length: oneMiByte - 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 3
|
{dataBlocks: 5, onDisks: 10, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 1, length: oneMiByte - 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 3
|
||||||
{dataBlocks: 6, onDisks: 12, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: oneMiByte, length: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},
|
{dataBlocks: 6, onDisks: 12, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: oneMiByte, length: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},
|
||||||
// 4
|
// 4
|
||||||
{dataBlocks: 7, onDisks: 14, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: 3, length: 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 5
|
{dataBlocks: 7, onDisks: 14, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: 3, length: 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 5
|
||||||
{dataBlocks: 8, onDisks: 16, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: 4, length: 8 * 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 6
|
{dataBlocks: 8, onDisks: 16, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: 4, length: 8 * 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 6
|
||||||
{dataBlocks: 7, onDisks: 14, offDisks: 7, blocksize: int64(blockSizeV1), data: oneMiByte, offset: oneMiByte, length: 1, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false}, // 7
|
{dataBlocks: 7, onDisks: 14, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte, length: 1, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false}, // 7
|
||||||
{dataBlocks: 6, onDisks: 12, offDisks: 6, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 8
|
{dataBlocks: 6, onDisks: 12, offDisks: 6, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 8
|
||||||
{dataBlocks: 5, onDisks: 10, offDisks: 5, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 9
|
{dataBlocks: 5, onDisks: 10, offDisks: 5, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 9
|
||||||
{dataBlocks: 4, onDisks: 8, offDisks: 4, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false}, // 10
|
{dataBlocks: 4, onDisks: 8, offDisks: 4, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false}, // 10
|
||||||
{dataBlocks: 3, onDisks: 6, offDisks: 3, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 11
|
{dataBlocks: 3, onDisks: 6, offDisks: 3, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 11
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 2, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 12
|
{dataBlocks: 2, onDisks: 4, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 12
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 1, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 13
|
{dataBlocks: 2, onDisks: 4, offDisks: 1, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 13
|
||||||
{dataBlocks: 3, onDisks: 6, offDisks: 2, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 14
|
{dataBlocks: 3, onDisks: 6, offDisks: 2, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 14
|
||||||
{dataBlocks: 4, onDisks: 8, offDisks: 3, blocksize: int64(2 * oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 15
|
{dataBlocks: 4, onDisks: 8, offDisks: 3, blocksize: int64(2 * oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 15
|
||||||
{dataBlocks: 5, onDisks: 10, offDisks: 6, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 16
|
{dataBlocks: 5, onDisks: 10, offDisks: 6, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 16
|
||||||
{dataBlocks: 5, onDisks: 10, offDisks: 2, blocksize: int64(blockSizeV1), data: 2 * oneMiByte, offset: oneMiByte, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 17
|
{dataBlocks: 5, onDisks: 10, offDisks: 2, blocksize: int64(blockSizeV2), data: 2 * oneMiByte, offset: oneMiByte, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 17
|
||||||
{dataBlocks: 5, onDisks: 10, offDisks: 1, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 18
|
{dataBlocks: 5, onDisks: 10, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 18
|
||||||
{dataBlocks: 6, onDisks: 12, offDisks: 3, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false},
|
{dataBlocks: 6, onDisks: 12, offDisks: 3, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false},
|
||||||
// 19
|
// 19
|
||||||
{dataBlocks: 6, onDisks: 12, offDisks: 7, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 20
|
{dataBlocks: 6, onDisks: 12, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 20
|
||||||
{dataBlocks: 8, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 21
|
{dataBlocks: 8, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 21
|
||||||
{dataBlocks: 8, onDisks: 16, offDisks: 9, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 22
|
{dataBlocks: 8, onDisks: 16, offDisks: 9, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 22
|
||||||
{dataBlocks: 8, onDisks: 16, offDisks: 7, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 23
|
{dataBlocks: 8, onDisks: 16, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 23
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 1, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 24
|
{dataBlocks: 2, onDisks: 4, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 24
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 25
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 25
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(blockSizeV1) + 1, offset: 0, length: int64(blockSizeV1) + 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 26
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(blockSizeV2) + 1, offset: 0, length: int64(blockSizeV2) + 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 26
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: 12, length: int64(blockSizeV1) + 17, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 27
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 12, length: int64(blockSizeV2) + 17, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 27
|
||||||
{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: 1023, length: int64(blockSizeV1) + 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 28
|
{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 1023, length: int64(blockSizeV2) + 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 28
|
||||||
{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: 11, length: int64(blockSizeV1) + 2*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 29
|
{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 11, length: int64(blockSizeV2) + 2*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 29
|
||||||
{dataBlocks: 6, onDisks: 12, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: 512, length: int64(blockSizeV1) + 8*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 30
|
{dataBlocks: 6, onDisks: 12, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 512, length: int64(blockSizeV2) + 8*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 30
|
||||||
{dataBlocks: 8, onDisks: 16, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: int64(blockSizeV1), length: int64(blockSizeV1) - 1, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 31
|
{dataBlocks: 8, onDisks: 16, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: int64(blockSizeV2), length: int64(blockSizeV2) - 1, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 31
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(oneMiByte), offset: -1, length: 3, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false}, // 32
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(oneMiByte), offset: -1, length: 3, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false}, // 32
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(oneMiByte), offset: 1024, length: -1, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false}, // 33
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(oneMiByte), offset: 1024, length: -1, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false}, // 33
|
||||||
{dataBlocks: 4, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV1), data: int64(blockSizeV1), offset: 0, length: int64(blockSizeV1), algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 34
|
{dataBlocks: 4, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(blockSizeV2), offset: 0, length: int64(blockSizeV2), algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 34
|
||||||
{dataBlocks: 4, onDisks: 6, offDisks: 1, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: 12, length: int64(blockSizeV1) + 17, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 35
|
{dataBlocks: 4, onDisks: 6, offDisks: 1, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 12, length: int64(blockSizeV2) + 17, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 35
|
||||||
{dataBlocks: 4, onDisks: 6, offDisks: 3, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: 1023, length: int64(blockSizeV1) + 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 36
|
{dataBlocks: 4, onDisks: 6, offDisks: 3, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 1023, length: int64(blockSizeV2) + 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 36
|
||||||
{dataBlocks: 8, onDisks: 12, offDisks: 4, blocksize: int64(blockSizeV1), data: int64(2 * blockSizeV1), offset: 11, length: int64(blockSizeV1) + 2*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 37
|
{dataBlocks: 8, onDisks: 12, offDisks: 4, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 11, length: int64(blockSizeV2) + 2*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 37
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErasureDecode(t *testing.T) {
|
func TestErasureDecode(t *testing.T) {
|
||||||
@@ -288,13 +288,13 @@ func TestErasureDecodeRandomOffsetLength(t *testing.T) {
|
|||||||
// Benchmarks
|
// Benchmarks
|
||||||
|
|
||||||
func benchmarkErasureDecode(data, parity, dataDown, parityDown int, size int64, b *testing.B) {
|
func benchmarkErasureDecode(data, parity, dataDown, parityDown int, size int64, b *testing.B) {
|
||||||
setup, err := newErasureTestSetup(data, parity, blockSizeV1)
|
setup, err := newErasureTestSetup(data, parity, blockSizeV2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed to create test setup: %v", err)
|
b.Fatalf("failed to create test setup: %v", err)
|
||||||
}
|
}
|
||||||
defer setup.Remove()
|
defer setup.Remove()
|
||||||
disks := setup.disks
|
disks := setup.disks
|
||||||
erasure, err := NewErasure(context.Background(), data, parity, blockSizeV1)
|
erasure, err := NewErasure(context.Background(), data, parity, blockSizeV2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed to create ErasureStorage: %v", err)
|
b.Fatalf("failed to create ErasureStorage: %v", err)
|
||||||
}
|
}
|
||||||
@@ -308,7 +308,7 @@ func benchmarkErasureDecode(data, parity, dataDown, parityDown int, size int64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
content := make([]byte, size)
|
content := make([]byte, size)
|
||||||
buffer := make([]byte, blockSizeV1, 2*blockSizeV1)
|
buffer := make([]byte, blockSizeV2, 2*blockSizeV2)
|
||||||
_, err = erasure.Encode(context.Background(), bytes.NewReader(content), writers, buffer, erasure.dataBlocks+1)
|
_, err = erasure.Encode(context.Background(), bytes.NewReader(content), writers, buffer, erasure.dataBlocks+1)
|
||||||
closeBitrotWriters(writers)
|
closeBitrotWriters(writers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -62,26 +62,26 @@ var erasureEncodeTests = []struct {
|
|||||||
algorithm BitrotAlgorithm
|
algorithm BitrotAlgorithm
|
||||||
shouldFail, shouldFailQuorum bool
|
shouldFail, shouldFailQuorum bool
|
||||||
}{
|
}{
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 0
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 0
|
||||||
{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 1, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false}, // 1
|
{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 1, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false}, // 1
|
||||||
{dataBlocks: 4, onDisks: 8, offDisks: 2, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 2
|
{dataBlocks: 4, onDisks: 8, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 2
|
||||||
{dataBlocks: 5, onDisks: 10, offDisks: 3, blocksize: int64(blockSizeV1), data: oneMiByte, offset: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 3
|
{dataBlocks: 5, onDisks: 10, offDisks: 3, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 3
|
||||||
{dataBlocks: 6, onDisks: 12, offDisks: 4, blocksize: int64(blockSizeV1), data: oneMiByte, offset: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 4
|
{dataBlocks: 6, onDisks: 12, offDisks: 4, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 4
|
||||||
{dataBlocks: 7, onDisks: 14, offDisks: 5, blocksize: int64(blockSizeV1), data: 0, offset: 0, shouldFail: false, algorithm: SHA256, shouldFailQuorum: false}, // 5
|
{dataBlocks: 7, onDisks: 14, offDisks: 5, blocksize: int64(blockSizeV2), data: 0, offset: 0, shouldFail: false, algorithm: SHA256, shouldFailQuorum: false}, // 5
|
||||||
{dataBlocks: 8, onDisks: 16, offDisks: 7, blocksize: int64(blockSizeV1), data: 0, offset: 0, shouldFail: false, algorithm: DefaultBitrotAlgorithm, shouldFailQuorum: false}, // 6
|
{dataBlocks: 8, onDisks: 16, offDisks: 7, blocksize: int64(blockSizeV2), data: 0, offset: 0, shouldFail: false, algorithm: DefaultBitrotAlgorithm, shouldFailQuorum: false}, // 6
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 2, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: true}, // 7
|
{dataBlocks: 2, onDisks: 4, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: true}, // 7
|
||||||
{dataBlocks: 4, onDisks: 8, offDisks: 4, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, algorithm: SHA256, shouldFail: false, shouldFailQuorum: true}, // 8
|
{dataBlocks: 4, onDisks: 8, offDisks: 4, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: SHA256, shouldFail: false, shouldFailQuorum: true}, // 8
|
||||||
{dataBlocks: 7, onDisks: 14, offDisks: 7, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 9
|
{dataBlocks: 7, onDisks: 14, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 9
|
||||||
{dataBlocks: 8, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 10
|
{dataBlocks: 8, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 10
|
||||||
{dataBlocks: 5, onDisks: 10, offDisks: 3, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 11
|
{dataBlocks: 5, onDisks: 10, offDisks: 3, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 11
|
||||||
{dataBlocks: 3, onDisks: 6, offDisks: 1, blocksize: int64(blockSizeV1), data: oneMiByte, offset: oneMiByte / 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 12
|
{dataBlocks: 3, onDisks: 6, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte / 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 12
|
||||||
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(oneMiByte / 2), data: oneMiByte, offset: oneMiByte/2 + 1, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 13
|
{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(oneMiByte / 2), data: oneMiByte, offset: oneMiByte/2 + 1, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 13
|
||||||
{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(oneMiByte - 1), data: oneMiByte, offset: oneMiByte - 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 14
|
{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(oneMiByte - 1), data: oneMiByte, offset: oneMiByte - 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 14
|
||||||
{dataBlocks: 8, onDisks: 12, offDisks: 2, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 15
|
{dataBlocks: 8, onDisks: 12, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 15
|
||||||
{dataBlocks: 8, onDisks: 10, offDisks: 1, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 16
|
{dataBlocks: 8, onDisks: 10, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 16
|
||||||
{dataBlocks: 10, onDisks: 14, offDisks: 0, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 17, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 17
|
{dataBlocks: 10, onDisks: 14, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 17, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 17
|
||||||
{dataBlocks: 2, onDisks: 6, offDisks: 2, blocksize: int64(oneMiByte), data: oneMiByte, offset: oneMiByte / 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 18
|
{dataBlocks: 2, onDisks: 6, offDisks: 2, blocksize: int64(oneMiByte), data: oneMiByte, offset: oneMiByte / 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 18
|
||||||
{dataBlocks: 10, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV1), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 19
|
{dataBlocks: 10, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 19
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErasureEncode(t *testing.T) {
|
func TestErasureEncode(t *testing.T) {
|
||||||
@@ -166,17 +166,17 @@ func TestErasureEncode(t *testing.T) {
|
|||||||
// Benchmarks
|
// Benchmarks
|
||||||
|
|
||||||
func benchmarkErasureEncode(data, parity, dataDown, parityDown int, size int64, b *testing.B) {
|
func benchmarkErasureEncode(data, parity, dataDown, parityDown int, size int64, b *testing.B) {
|
||||||
setup, err := newErasureTestSetup(data, parity, blockSizeV1)
|
setup, err := newErasureTestSetup(data, parity, blockSizeV2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed to create test setup: %v", err)
|
b.Fatalf("failed to create test setup: %v", err)
|
||||||
}
|
}
|
||||||
defer setup.Remove()
|
defer setup.Remove()
|
||||||
erasure, err := NewErasure(context.Background(), data, parity, blockSizeV1)
|
erasure, err := NewErasure(context.Background(), data, parity, blockSizeV2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed to create ErasureStorage: %v", err)
|
b.Fatalf("failed to create ErasureStorage: %v", err)
|
||||||
}
|
}
|
||||||
disks := setup.disks
|
disks := setup.disks
|
||||||
buffer := make([]byte, blockSizeV1, 2*blockSizeV1)
|
buffer := make([]byte, blockSizeV2, 2*blockSizeV2)
|
||||||
content := make([]byte, size)
|
content := make([]byte, size)
|
||||||
|
|
||||||
for i := 0; i < dataDown; i++ {
|
for i := 0; i < dataDown; i++ {
|
||||||
|
|||||||
@@ -39,26 +39,26 @@ var erasureHealTests = []struct {
|
|||||||
algorithm BitrotAlgorithm
|
algorithm BitrotAlgorithm
|
||||||
shouldFail bool
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: SHA256, shouldFail: false}, // 0
|
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: SHA256, shouldFail: false}, // 0
|
||||||
{dataBlocks: 3, disks: 6, offDisks: 2, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 1
|
{dataBlocks: 3, disks: 6, offDisks: 2, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 1
|
||||||
{dataBlocks: 4, disks: 8, offDisks: 2, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 2
|
{dataBlocks: 4, disks: 8, offDisks: 2, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 2
|
||||||
{dataBlocks: 5, disks: 10, offDisks: 3, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 3
|
{dataBlocks: 5, disks: 10, offDisks: 3, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 3
|
||||||
{dataBlocks: 6, disks: 12, offDisks: 2, badDisks: 3, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: SHA256, shouldFail: false}, // 4
|
{dataBlocks: 6, disks: 12, offDisks: 2, badDisks: 3, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: SHA256, shouldFail: false}, // 4
|
||||||
{dataBlocks: 7, disks: 14, offDisks: 4, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 5
|
{dataBlocks: 7, disks: 14, offDisks: 4, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 5
|
||||||
{dataBlocks: 8, disks: 16, offDisks: 6, badDisks: 1, badStaleDisks: 1, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 6
|
{dataBlocks: 8, disks: 16, offDisks: 6, badDisks: 1, badStaleDisks: 1, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 6
|
||||||
{dataBlocks: 7, disks: 14, offDisks: 2, badDisks: 3, badStaleDisks: 0, blocksize: int64(oneMiByte / 2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 7
|
{dataBlocks: 7, disks: 14, offDisks: 2, badDisks: 3, badStaleDisks: 0, blocksize: int64(oneMiByte / 2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 7
|
||||||
{dataBlocks: 6, disks: 12, offDisks: 1, badDisks: 0, badStaleDisks: 1, blocksize: int64(oneMiByte - 1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 8
|
{dataBlocks: 6, disks: 12, offDisks: 1, badDisks: 0, badStaleDisks: 1, blocksize: int64(oneMiByte - 1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 8
|
||||||
{dataBlocks: 5, disks: 10, offDisks: 3, badDisks: 0, badStaleDisks: 3, blocksize: int64(oneMiByte / 2), size: oneMiByte, algorithm: SHA256, shouldFail: true}, // 9
|
{dataBlocks: 5, disks: 10, offDisks: 3, badDisks: 0, badStaleDisks: 3, blocksize: int64(oneMiByte / 2), size: oneMiByte, algorithm: SHA256, shouldFail: true}, // 9
|
||||||
{dataBlocks: 4, disks: 8, offDisks: 1, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 10
|
{dataBlocks: 4, disks: 8, offDisks: 1, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 10
|
||||||
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 1, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 11
|
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 1, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 11
|
||||||
{dataBlocks: 6, disks: 12, offDisks: 8, badDisks: 3, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 12
|
{dataBlocks: 6, disks: 12, offDisks: 8, badDisks: 3, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 12
|
||||||
{dataBlocks: 7, disks: 14, offDisks: 3, badDisks: 4, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 13
|
{dataBlocks: 7, disks: 14, offDisks: 3, badDisks: 4, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 13
|
||||||
{dataBlocks: 7, disks: 14, offDisks: 6, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 14
|
{dataBlocks: 7, disks: 14, offDisks: 6, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 14
|
||||||
{dataBlocks: 8, disks: 16, offDisks: 4, badDisks: 5, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 15
|
{dataBlocks: 8, disks: 16, offDisks: 4, badDisks: 5, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 15
|
||||||
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 16
|
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 16
|
||||||
{dataBlocks: 12, disks: 16, offDisks: 2, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 17
|
{dataBlocks: 12, disks: 16, offDisks: 2, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 17
|
||||||
{dataBlocks: 6, disks: 8, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 18
|
{dataBlocks: 6, disks: 8, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 18
|
||||||
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV1), size: oneMiByte * 64, algorithm: SHA256, shouldFail: false}, // 19
|
{dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte * 64, algorithm: SHA256, shouldFail: false}, // 19
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErasureHeal(t *testing.T) {
|
func TestErasureHeal(t *testing.T) {
|
||||||
|
|||||||
@@ -239,13 +239,6 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
storageDisks := er.getDisks()
|
storageDisks := er.getDisks()
|
||||||
storageEndpoints := er.getEndpoints()
|
storageEndpoints := er.getEndpoints()
|
||||||
|
|
||||||
// List of disks having latest version of the object er.meta
|
|
||||||
// (by modtime).
|
|
||||||
latestDisks, modTime := listOnlineDisks(storageDisks, partsMetadata, errs)
|
|
||||||
|
|
||||||
// List of disks having all parts as per latest er.meta.
|
|
||||||
availableDisks, dataErrs := disksWithAllParts(ctx, latestDisks, partsMetadata, errs, bucket, object, scanMode)
|
|
||||||
|
|
||||||
// Initialize heal result object
|
// Initialize heal result object
|
||||||
result = madmin.HealResultItem{
|
result = madmin.HealResultItem{
|
||||||
Type: madmin.HealItemObject,
|
Type: madmin.HealItemObject,
|
||||||
@@ -256,6 +249,21 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
DataBlocks: len(storageDisks) - er.defaultParityCount,
|
DataBlocks: len(storageDisks) - er.defaultParityCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !opts.NoLock {
|
||||||
|
lk := er.NewNSLock(bucket, object)
|
||||||
|
if ctx, err = lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
defer lk.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of disks having latest version of the object er.meta
|
||||||
|
// (by modtime).
|
||||||
|
latestDisks, modTime := listOnlineDisks(storageDisks, partsMetadata, errs)
|
||||||
|
|
||||||
|
// List of disks having all parts as per latest er.meta.
|
||||||
|
availableDisks, dataErrs := disksWithAllParts(ctx, latestDisks, partsMetadata, errs, bucket, object, scanMode)
|
||||||
|
|
||||||
// Loop to find number of disks with valid data, per-drive
|
// Loop to find number of disks with valid data, per-drive
|
||||||
// data state and a list of outdated disks on which data needs
|
// data state and a list of outdated disks on which data needs
|
||||||
// to be healed.
|
// to be healed.
|
||||||
@@ -709,7 +717,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
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -199,3 +202,22 @@ func TestEvalDisks(t *testing.T) {
|
|||||||
z := objLayer.(*erasureServerPools)
|
z := objLayer.(*erasureServerPools)
|
||||||
testShuffleDisks(t, z)
|
testShuffleDisks(t, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_hashOrder(t *testing.T) {
|
||||||
|
for x := 1; x < 17; x++ {
|
||||||
|
t.Run(fmt.Sprintf("%d", x), func(t *testing.T) {
|
||||||
|
var first [17]int
|
||||||
|
rng := rand.New(rand.NewSource(0))
|
||||||
|
var tmp [16]byte
|
||||||
|
rng.Read(tmp[:])
|
||||||
|
prefix := hex.EncodeToString(tmp[:])
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
rng.Read(tmp[:])
|
||||||
|
|
||||||
|
y := hashOrder(fmt.Sprintf("%s/%x", prefix, hex.EncodeToString(tmp[:3])), x)
|
||||||
|
first[y[0]]++
|
||||||
|
}
|
||||||
|
t.Log("first:", first[:x])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -28,7 +29,6 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/bucket/replication"
|
"github.com/minio/minio/pkg/bucket/replication"
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"github.com/minio/minio/pkg/sync/errgroup"
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const erasureAlgorithm = "rs-vandermonde"
|
const erasureAlgorithm = "rs-vandermonde"
|
||||||
@@ -102,23 +102,26 @@ func (fi FileInfo) IsValid() bool {
|
|||||||
func (fi FileInfo) ToObjectInfo(bucket, object string) ObjectInfo {
|
func (fi FileInfo) ToObjectInfo(bucket, object string) ObjectInfo {
|
||||||
object = decodeDirObject(object)
|
object = decodeDirObject(object)
|
||||||
versionID := fi.VersionID
|
versionID := fi.VersionID
|
||||||
if globalBucketVersioningSys.Enabled(bucket) && versionID == "" {
|
if (globalBucketVersioningSys.Enabled(bucket) || globalBucketVersioningSys.Suspended(bucket)) && versionID == "" {
|
||||||
versionID = nullVersionID
|
versionID = nullVersionID
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo := ObjectInfo{
|
objInfo := ObjectInfo{
|
||||||
IsDir: HasSuffix(object, SlashSeparator),
|
IsDir: HasSuffix(object, SlashSeparator),
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Name: object,
|
Name: object,
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
IsLatest: fi.IsLatest,
|
IsLatest: fi.IsLatest,
|
||||||
DeleteMarker: fi.Deleted,
|
DeleteMarker: fi.Deleted,
|
||||||
Size: fi.Size,
|
Size: fi.Size,
|
||||||
ModTime: fi.ModTime,
|
ModTime: fi.ModTime,
|
||||||
Legacy: fi.XLV1,
|
Legacy: fi.XLV1,
|
||||||
ContentType: fi.Metadata["content-type"],
|
ContentType: fi.Metadata["content-type"],
|
||||||
ContentEncoding: fi.Metadata["content-encoding"],
|
ContentEncoding: fi.Metadata["content-encoding"],
|
||||||
|
NumVersions: fi.NumVersions,
|
||||||
|
SuccessorModTime: fi.SuccessorModTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update expires
|
// Update expires
|
||||||
var (
|
var (
|
||||||
t time.Time
|
t time.Time
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -91,50 +93,85 @@ func (er erasureObjects) removeObjectPart(bucket, object, uploadID, dataDir stri
|
|||||||
// Clean-up the old multipart uploads. Should be run in a Go routine.
|
// Clean-up the old multipart uploads. Should be run in a Go routine.
|
||||||
func (er erasureObjects) cleanupStaleUploads(ctx context.Context, expiry time.Duration) {
|
func (er erasureObjects) cleanupStaleUploads(ctx context.Context, expiry time.Duration) {
|
||||||
// run multiple cleanup's local to this server.
|
// run multiple cleanup's local to this server.
|
||||||
|
var wg sync.WaitGroup
|
||||||
for _, disk := range er.getLoadBalancedLocalDisks() {
|
for _, disk := range er.getLoadBalancedLocalDisks() {
|
||||||
if disk != nil {
|
if disk != nil {
|
||||||
er.cleanupStaleUploadsOnDisk(ctx, disk, expiry)
|
wg.Add(1)
|
||||||
return
|
go func(disk StorageAPI) {
|
||||||
|
defer wg.Done()
|
||||||
|
er.cleanupStaleUploadsOnDisk(ctx, disk, expiry)
|
||||||
|
}(disk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er erasureObjects) renameAll(ctx context.Context, bucket, prefix string) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, disk := range er.getDisks() {
|
||||||
|
if disk == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func(disk StorageAPI) {
|
||||||
|
defer wg.Done()
|
||||||
|
disk.RenameFile(ctx, bucket, prefix, minioMetaTmpBucket, mustGetUUID())
|
||||||
|
}(disk)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er erasureObjects) deleteAll(ctx context.Context, bucket, prefix string) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, disk := range er.getDisks() {
|
||||||
|
if disk == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func(disk StorageAPI) {
|
||||||
|
defer wg.Done()
|
||||||
|
disk.Delete(ctx, bucket, prefix, true)
|
||||||
|
}(disk)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the old multipart uploads on the given disk.
|
// Remove the old multipart uploads on the given disk.
|
||||||
func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk StorageAPI, expiry time.Duration) {
|
func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk StorageAPI, expiry time.Duration) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
shaDirs, err := disk.ListDir(ctx, minioMetaMultipartBucket, "", -1)
|
diskPath := disk.Endpoint().Path
|
||||||
if err != nil {
|
|
||||||
return
|
readDirFn(pathJoin(diskPath, minioMetaMultipartBucket), func(shaDir string, typ os.FileMode) error {
|
||||||
}
|
return readDirFn(pathJoin(diskPath, minioMetaMultipartBucket, shaDir), func(uploadIDDir string, typ os.FileMode) error {
|
||||||
for _, shaDir := range shaDirs {
|
|
||||||
uploadIDDirs, err := disk.ListDir(ctx, minioMetaMultipartBucket, shaDir, -1)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, uploadIDDir := range uploadIDDirs {
|
|
||||||
uploadIDPath := pathJoin(shaDir, uploadIDDir)
|
uploadIDPath := pathJoin(shaDir, uploadIDDir)
|
||||||
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, uploadIDPath, "", false)
|
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, uploadIDPath, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
|
wait := er.deletedCleanupSleeper.Timer(ctx)
|
||||||
if now.Sub(fi.ModTime) > expiry {
|
if now.Sub(fi.ModTime) > expiry {
|
||||||
er.deleteObject(ctx, minioMetaMultipartBucket, uploadIDPath, fi.Erasure.DataBlocks+1)
|
er.renameAll(ctx, minioMetaMultipartBucket, uploadIDPath)
|
||||||
}
|
}
|
||||||
|
wait()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
readDirFn(pathJoin(diskPath, minioMetaTmpBucket), func(tmpDir string, typ os.FileMode) error {
|
||||||
|
if tmpDir == ".trash/" { // do not remove .trash/ here, it has its own routines
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
vi, err := disk.StatVol(ctx, pathJoin(minioMetaTmpBucket, tmpDir))
|
||||||
tmpDirs, err := disk.ListDir(ctx, minioMetaTmpBucket, "", -1)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, tmpDir := range tmpDirs {
|
|
||||||
fi, err := disk.ReadVersion(ctx, minioMetaTmpBucket, tmpDir, "", false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
if now.Sub(fi.ModTime) > expiry {
|
wait := er.deletedCleanupSleeper.Timer(ctx)
|
||||||
er.deleteObject(ctx, minioMetaTmpBucket, tmpDir, fi.Erasure.DataBlocks+1)
|
if now.Sub(vi.Created) > expiry {
|
||||||
|
er.deleteAll(ctx, minioMetaTmpBucket, tmpDir)
|
||||||
}
|
}
|
||||||
}
|
wait()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMultipartUploads - lists all the pending multipart
|
// ListMultipartUploads - lists all the pending multipart
|
||||||
@@ -247,7 +284,7 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
dataBlocks := len(onlineDisks) - parityBlocks
|
dataBlocks := len(onlineDisks) - parityBlocks
|
||||||
fi := newFileInfo(object, dataBlocks, parityBlocks)
|
fi := newFileInfo(pathJoin(bucket, object), dataBlocks, parityBlocks)
|
||||||
|
|
||||||
// we now know the number of blocks this object needs for data and parity.
|
// we now know the number of blocks this object needs for data and parity.
|
||||||
// establish the writeQuorum using this data
|
// establish the writeQuorum using this data
|
||||||
@@ -325,7 +362,7 @@ func (er erasureObjects) NewMultipartUpload(ctx context.Context, bucket, object
|
|||||||
//
|
//
|
||||||
// Implements S3 compatible Upload Part Copy API.
|
// Implements S3 compatible Upload Part Copy API.
|
||||||
func (er erasureObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int, startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (pi PartInfo, e error) {
|
func (er erasureObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int, startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (pi PartInfo, e error) {
|
||||||
partInfo, err := er.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, NewPutObjReader(srcInfo.Reader, nil, nil), dstOpts)
|
partInfo, err := er.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, NewPutObjReader(srcInfo.Reader), dstOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pi, toObjectErr(err, dstBucket, dstObject)
|
return pi, toObjectErr(err, dstBucket, dstObject)
|
||||||
}
|
}
|
||||||
@@ -341,7 +378,8 @@ func (er erasureObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObjec
|
|||||||
// Implements S3 compatible Upload Part API.
|
// Implements S3 compatible Upload Part API.
|
||||||
func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, r *PutObjReader, opts ObjectOptions) (pi PartInfo, err error) {
|
func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, r *PutObjReader, opts ObjectOptions) (pi PartInfo, err error) {
|
||||||
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||||
if err = uploadIDLock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = uploadIDLock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return PartInfo{}, err
|
return PartInfo{}, err
|
||||||
}
|
}
|
||||||
readLocked := true
|
readLocked := true
|
||||||
@@ -415,7 +453,14 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
|||||||
switch size := data.Size(); {
|
switch size := data.Size(); {
|
||||||
case size == 0:
|
case size == 0:
|
||||||
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
||||||
case size == -1 || size >= fi.Erasure.BlockSize:
|
case size == -1:
|
||||||
|
if size := data.ActualSize(); size > 0 && size < fi.Erasure.BlockSize {
|
||||||
|
buffer = make([]byte, data.ActualSize()+256, data.ActualSize()*2+512)
|
||||||
|
} else {
|
||||||
|
buffer = er.bp.Get()
|
||||||
|
defer er.bp.Put(buffer)
|
||||||
|
}
|
||||||
|
case size >= fi.Erasure.BlockSize:
|
||||||
buffer = er.bp.Get()
|
buffer = er.bp.Get()
|
||||||
defer er.bp.Put(buffer)
|
defer er.bp.Put(buffer)
|
||||||
case size < fi.Erasure.BlockSize:
|
case size < fi.Erasure.BlockSize:
|
||||||
@@ -456,7 +501,8 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
|||||||
// PutObjectParts would serialize here updating `xl.meta`
|
// PutObjectParts would serialize here updating `xl.meta`
|
||||||
uploadIDLock.RUnlock()
|
uploadIDLock.RUnlock()
|
||||||
readLocked = false
|
readLocked = false
|
||||||
if err = uploadIDLock.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = uploadIDLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return PartInfo{}, err
|
return PartInfo{}, err
|
||||||
}
|
}
|
||||||
defer uploadIDLock.Unlock()
|
defer uploadIDLock.Unlock()
|
||||||
@@ -537,8 +583,10 @@ func (er erasureObjects) GetMultipartInfo(ctx context.Context, bucket, object, u
|
|||||||
UploadID: uploadID,
|
UploadID: uploadID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||||
if err := uploadIDLock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = uploadIDLock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return MultipartInfo{}, err
|
return MultipartInfo{}, err
|
||||||
}
|
}
|
||||||
defer uploadIDLock.RUnlock()
|
defer uploadIDLock.RUnlock()
|
||||||
@@ -584,9 +632,10 @@ func (er erasureObjects) GetMultipartInfo(ctx context.Context, bucket, object, u
|
|||||||
// Implements S3 compatible ListObjectParts API. The resulting
|
// Implements S3 compatible ListObjectParts API. The resulting
|
||||||
// ListPartsInfo structure is marshaled directly into XML and
|
// ListPartsInfo structure is marshaled directly into XML and
|
||||||
// replied back to the client.
|
// replied back to the client.
|
||||||
func (er erasureObjects) ListObjectParts(ctx context.Context, bucket, object, uploadID string, partNumberMarker, maxParts int, opts ObjectOptions) (result ListPartsInfo, e error) {
|
func (er erasureObjects) ListObjectParts(ctx context.Context, bucket, object, uploadID string, partNumberMarker, maxParts int, opts ObjectOptions) (result ListPartsInfo, err error) {
|
||||||
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||||
if err := uploadIDLock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = uploadIDLock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return ListPartsInfo{}, err
|
return ListPartsInfo{}, err
|
||||||
}
|
}
|
||||||
defer uploadIDLock.RUnlock()
|
defer uploadIDLock.RUnlock()
|
||||||
@@ -679,7 +728,8 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
// Hold read-locks to verify uploaded parts, also disallows
|
// Hold read-locks to verify uploaded parts, also disallows
|
||||||
// parallel part uploads as well.
|
// parallel part uploads as well.
|
||||||
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
uploadIDLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||||
if err = uploadIDLock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = uploadIDLock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
defer uploadIDLock.RUnlock()
|
defer uploadIDLock.RUnlock()
|
||||||
@@ -834,7 +884,8 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
|
|
||||||
// Hold namespace to complete the transaction
|
// Hold namespace to complete the transaction
|
||||||
lk := er.NewNSLock(bucket, object)
|
lk := er.NewNSLock(bucket, object)
|
||||||
if err = lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
defer lk.Unlock()
|
defer lk.Unlock()
|
||||||
@@ -874,9 +925,10 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
// All parts are purged from all disks and reference to the uploadID
|
// All parts are purged from all disks and reference to the uploadID
|
||||||
// would be removed from the system, rollback is not possible on this
|
// would be removed from the system, rollback is not possible on this
|
||||||
// operation.
|
// operation.
|
||||||
func (er erasureObjects) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) error {
|
func (er erasureObjects) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) (err error) {
|
||||||
lk := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
lk := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||||
if err := lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer lk.Unlock()
|
defer lk.Unlock()
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ var objectOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errUnform
|
|||||||
// CopyObject - copy object source object to destination object.
|
// CopyObject - copy object source object to destination object.
|
||||||
// if source object and destination object are same we only
|
// if source object and destination object are same we only
|
||||||
// update metadata.
|
// update metadata.
|
||||||
func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, e error) {
|
func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, err error) {
|
||||||
// This call shouldn't be used for anything other than metadata updates or adding self referential versions.
|
// This call shouldn't be used for anything other than metadata updates or adding self referential versions.
|
||||||
if !srcInfo.metadataOnly {
|
if !srcInfo.metadataOnly {
|
||||||
return oi, NotImplemented{}
|
return oi, NotImplemented{}
|
||||||
@@ -54,7 +54,8 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
|
|||||||
defer ObjectPathUpdated(pathJoin(dstBucket, dstObject))
|
defer ObjectPathUpdated(pathJoin(dstBucket, dstObject))
|
||||||
|
|
||||||
lk := er.NewNSLock(dstBucket, dstObject)
|
lk := er.NewNSLock(dstBucket, dstObject)
|
||||||
if err := lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
defer lk.Unlock()
|
defer lk.Unlock()
|
||||||
@@ -147,12 +148,14 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
|||||||
lock := er.NewNSLock(bucket, object)
|
lock := er.NewNSLock(bucket, object)
|
||||||
switch lockType {
|
switch lockType {
|
||||||
case writeLock:
|
case writeLock:
|
||||||
if err = lock.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = lock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nsUnlocker = lock.Unlock
|
nsUnlocker = lock.Unlock
|
||||||
case readLock:
|
case readLock:
|
||||||
if err = lock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = lock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nsUnlocker = lock.RUnlock
|
nsUnlocker = lock.RUnlock
|
||||||
@@ -162,14 +165,6 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
|||||||
|
|
||||||
fi, metaArr, onlineDisks, err := er.getObjectFileInfo(ctx, bucket, object, opts, true)
|
fi, metaArr, onlineDisks, err := er.getObjectFileInfo(ctx, bucket, object, opts, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isProxyable(ctx, bucket) && (errors.Is(err, errFileNotFound) || errors.Is(err, errFileVersionNotFound)) {
|
|
||||||
// proxy to replication target if active-active replication is in place.
|
|
||||||
reader, proxy := proxyGetToReplicationTarget(ctx, bucket, object, rs, h, opts)
|
|
||||||
if reader == nil || !proxy {
|
|
||||||
return nil, toObjectErr(err, bucket, object)
|
|
||||||
}
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
return nil, toObjectErr(err, bucket, object)
|
return nil, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +182,7 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
|||||||
}
|
}
|
||||||
if objInfo.TransitionStatus == lifecycle.TransitionComplete {
|
if objInfo.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
// If transitioned, stream from transition tier unless object is restored locally or restore date is past.
|
// If transitioned, stream from transition tier unless object is restored locally or restore date is past.
|
||||||
restoreHdr, ok := objInfo.UserDefined[xhttp.AmzRestore]
|
restoreHdr, ok := caseInsensitiveMap(objInfo.UserDefined).Lookup(xhttp.AmzRestore)
|
||||||
if !ok || !strings.HasPrefix(restoreHdr, "ongoing-request=false") || (!objInfo.RestoreExpires.IsZero() && time.Now().After(objInfo.RestoreExpires)) {
|
if !ok || !strings.HasPrefix(restoreHdr, "ongoing-request=false") || (!objInfo.RestoreExpires.IsZero() && time.Now().After(objInfo.RestoreExpires)) {
|
||||||
return getTransitionedObjectReader(ctx, bucket, object, rs, h, objInfo, opts)
|
return getTransitionedObjectReader(ctx, bucket, object, rs, h, objInfo, opts)
|
||||||
}
|
}
|
||||||
@@ -216,10 +211,11 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
|||||||
//
|
//
|
||||||
// startOffset indicates the starting read location of the object.
|
// startOffset indicates the starting read location of the object.
|
||||||
// length indicates the total length of the object.
|
// length indicates the total length of the object.
|
||||||
func (er erasureObjects) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) error {
|
func (er erasureObjects) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) {
|
||||||
// Lock the object before reading.
|
// Lock the object before reading.
|
||||||
lk := er.NewNSLock(bucket, object)
|
lk := er.NewNSLock(bucket, object)
|
||||||
if err := lk.GetRLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = lk.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer lk.RUnlock()
|
defer lk.RUnlock()
|
||||||
@@ -336,7 +332,9 @@ func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, obje
|
|||||||
}
|
}
|
||||||
if scan != madmin.HealUnknownScan {
|
if scan != madmin.HealUnknownScan {
|
||||||
healOnce.Do(func() {
|
healOnce.Do(func() {
|
||||||
go healObject(bucket, object, fi.VersionID, scan)
|
if _, healing := er.getOnlineDisksWithHealing(); !healing {
|
||||||
|
go healObject(bucket, object, fi.VersionID, scan)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,12 +376,15 @@ func (er erasureObjects) getObject(ctx context.Context, bucket, object string, s
|
|||||||
|
|
||||||
// GetObjectInfo - reads object metadata and replies back ObjectInfo.
|
// GetObjectInfo - reads object metadata and replies back ObjectInfo.
|
||||||
func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (info ObjectInfo, err error) {
|
func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (info ObjectInfo, err error) {
|
||||||
// Lock the object before reading.
|
if !opts.NoLock {
|
||||||
lk := er.NewNSLock(bucket, object)
|
// Lock the object before reading.
|
||||||
if err := lk.GetRLock(ctx, globalOperationTimeout); err != nil {
|
lk := er.NewNSLock(bucket, object)
|
||||||
return ObjectInfo{}, err
|
ctx, err = lk.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectInfo{}, err
|
||||||
|
}
|
||||||
|
defer lk.RUnlock()
|
||||||
}
|
}
|
||||||
defer lk.RUnlock()
|
|
||||||
|
|
||||||
return er.getObjectInfo(ctx, bucket, object, opts)
|
return er.getObjectInfo(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
@@ -414,7 +415,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
er.deleteObjectVersion(ctx, bucket, object, 1, FileInfo{
|
er.deleteObjectVersion(ctx, bucket, object, 1, FileInfo{
|
||||||
Name: object,
|
Name: object,
|
||||||
VersionID: opts.VersionID,
|
VersionID: opts.VersionID,
|
||||||
})
|
}, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,7 +445,9 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
|
|
||||||
// if missing metadata can be reconstructed, attempt to reconstruct.
|
// if missing metadata can be reconstructed, attempt to reconstruct.
|
||||||
if missingBlocks > 0 && missingBlocks < readQuorum {
|
if missingBlocks > 0 && missingBlocks < readQuorum {
|
||||||
go healObject(bucket, object, fi.VersionID, madmin.HealNormalScan)
|
if _, healing := er.getOnlineDisksWithHealing(); !healing {
|
||||||
|
go healObject(bucket, object, fi.VersionID, madmin.HealNormalScan)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fi, metaArr, onlineDisks, nil
|
return fi, metaArr, onlineDisks, nil
|
||||||
@@ -454,13 +457,6 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
func (er erasureObjects) getObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
func (er erasureObjects) getObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
||||||
fi, _, _, err := er.getObjectFileInfo(ctx, bucket, object, opts, false)
|
fi, _, _, err := er.getObjectFileInfo(ctx, bucket, object, opts, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// proxy HEAD to replication target if active-active replication configured on bucket
|
|
||||||
if isProxyable(ctx, bucket) && (errors.Is(err, errFileNotFound) || errors.Is(err, errFileVersionNotFound)) {
|
|
||||||
oi, proxy, err := proxyHeadToReplicationTarget(ctx, bucket, object, opts)
|
|
||||||
if proxy {
|
|
||||||
return oi, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return objInfo, toObjectErr(err, bucket, object)
|
return objInfo, toObjectErr(err, bucket, object)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -471,10 +467,11 @@ func (er erasureObjects) getObjectInfo(ctx context.Context, bucket, object strin
|
|||||||
objInfo.StorageClass = sc
|
objInfo.StorageClass = sc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !fi.VersionPurgeStatus.Empty() {
|
if !fi.VersionPurgeStatus.Empty() && opts.VersionID != "" {
|
||||||
// Make sure to return object info to provide extra information.
|
// Make sure to return object info to provide extra information.
|
||||||
return objInfo, toObjectErr(errMethodNotAllowed, bucket, object)
|
return objInfo, toObjectErr(errMethodNotAllowed, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.Deleted {
|
if fi.Deleted {
|
||||||
if opts.VersionID == "" || opts.DeleteMarker {
|
if opts.VersionID == "" || opts.DeleteMarker {
|
||||||
return objInfo, toObjectErr(errFileNotFound, bucket, object)
|
return objInfo, toObjectErr(errFileNotFound, bucket, object)
|
||||||
@@ -627,10 +624,13 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
|
|
||||||
storageDisks := er.getDisks()
|
storageDisks := er.getDisks()
|
||||||
|
|
||||||
// Get parity and data drive count based on storage class metadata
|
parityDrives := len(storageDisks) / 2
|
||||||
parityDrives := globalStorageClass.GetParityForSC(opts.UserDefined[xhttp.AmzStorageClass])
|
if !opts.MaxParity {
|
||||||
if parityDrives <= 0 {
|
// Get parity and data drive count based on storage class metadata
|
||||||
parityDrives = er.defaultParityCount
|
parityDrives = globalStorageClass.GetParityForSC(opts.UserDefined[xhttp.AmzStorageClass])
|
||||||
|
if parityDrives <= 0 {
|
||||||
|
parityDrives = er.defaultParityCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dataDrives := len(storageDisks) - parityDrives
|
dataDrives := len(storageDisks) - parityDrives
|
||||||
|
|
||||||
@@ -662,7 +662,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
// Initialize parts metadata
|
// Initialize parts metadata
|
||||||
partsMetadata := make([]FileInfo, len(storageDisks))
|
partsMetadata := make([]FileInfo, len(storageDisks))
|
||||||
|
|
||||||
fi := newFileInfo(object, dataDrives, parityDrives)
|
fi := newFileInfo(pathJoin(bucket, object), dataDrives, parityDrives)
|
||||||
|
|
||||||
if opts.Versioned {
|
if opts.Versioned {
|
||||||
fi.VersionID = opts.VersionID
|
fi.VersionID = opts.VersionID
|
||||||
@@ -691,7 +691,14 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
switch size := data.Size(); {
|
switch size := data.Size(); {
|
||||||
case size == 0:
|
case size == 0:
|
||||||
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
||||||
case size == -1 || size >= fi.Erasure.BlockSize:
|
case size == -1:
|
||||||
|
if size := data.ActualSize(); size > 0 && size < fi.Erasure.BlockSize {
|
||||||
|
buffer = make([]byte, data.ActualSize()+256, data.ActualSize()*2+512)
|
||||||
|
} else {
|
||||||
|
buffer = er.bp.Get()
|
||||||
|
defer er.bp.Put(buffer)
|
||||||
|
}
|
||||||
|
case size >= fi.Erasure.BlockSize:
|
||||||
buffer = er.bp.Get()
|
buffer = er.bp.Get()
|
||||||
defer er.bp.Put(buffer)
|
defer er.bp.Put(buffer)
|
||||||
case size < fi.Erasure.BlockSize:
|
case size < fi.Erasure.BlockSize:
|
||||||
@@ -727,8 +734,10 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !opts.NoLock {
|
if !opts.NoLock {
|
||||||
|
var err error
|
||||||
lk := er.NewNSLock(bucket, object)
|
lk := er.NewNSLock(bucket, object)
|
||||||
if err := lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return ObjectInfo{}, err
|
return ObjectInfo{}, err
|
||||||
}
|
}
|
||||||
defer lk.Unlock()
|
defer lk.Unlock()
|
||||||
@@ -800,7 +809,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
return fi.ToObjectInfo(bucket, object), nil
|
return fi.ToObjectInfo(bucket, object), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (er erasureObjects) deleteObjectVersion(ctx context.Context, bucket, object string, writeQuorum int, fi FileInfo) error {
|
func (er erasureObjects) deleteObjectVersion(ctx context.Context, bucket, object string, writeQuorum int, fi FileInfo, forceDelMarker bool) error {
|
||||||
defer ObjectPathUpdated(pathJoin(bucket, object))
|
defer ObjectPathUpdated(pathJoin(bucket, object))
|
||||||
disks := er.getDisks()
|
disks := er.getDisks()
|
||||||
g := errgroup.WithNErrs(len(disks))
|
g := errgroup.WithNErrs(len(disks))
|
||||||
@@ -810,7 +819,7 @@ func (er erasureObjects) deleteObjectVersion(ctx context.Context, bucket, object
|
|||||||
if disks[index] == nil {
|
if disks[index] == nil {
|
||||||
return errDiskNotFound
|
return errDiskNotFound
|
||||||
}
|
}
|
||||||
return disks[index].DeleteVersion(ctx, bucket, object, fi)
|
return disks[index].DeleteVersion(ctx, bucket, object, fi, forceDelMarker)
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
// return errors if any during deletion
|
// return errors if any during deletion
|
||||||
@@ -1037,7 +1046,8 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
}
|
}
|
||||||
// Acquire a write lock before deleting the object.
|
// Acquire a write lock before deleting the object.
|
||||||
lk := er.NewNSLock(bucket, object)
|
lk := er.NewNSLock(bucket, object)
|
||||||
if err = lk.GetLock(ctx, globalDeleteOperationTimeout); err != nil {
|
ctx, err = lk.GetLock(ctx, globalDeleteOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return ObjectInfo{}, err
|
return ObjectInfo{}, err
|
||||||
}
|
}
|
||||||
defer lk.Unlock()
|
defer lk.Unlock()
|
||||||
@@ -1049,11 +1059,10 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
if goi.VersionID != "" {
|
if goi.VersionID != "" {
|
||||||
markDelete = true
|
markDelete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default deleteMarker to true if object is under versioning
|
// Default deleteMarker to true if object is under versioning
|
||||||
deleteMarker := true
|
deleteMarker := opts.Versioned
|
||||||
if gerr == nil {
|
|
||||||
deleteMarker = goi.VersionID != ""
|
|
||||||
}
|
|
||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
// case where replica version needs to be deleted on target cluster
|
// case where replica version needs to be deleted on target cluster
|
||||||
if versionFound && opts.DeleteMarkerReplicationStatus == replication.Replica.String() {
|
if versionFound && opts.DeleteMarkerReplicationStatus == replication.Replica.String() {
|
||||||
@@ -1062,7 +1071,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
if opts.VersionPurgeStatus.Empty() && opts.DeleteMarkerReplicationStatus == "" {
|
if opts.VersionPurgeStatus.Empty() && opts.DeleteMarkerReplicationStatus == "" {
|
||||||
markDelete = false
|
markDelete = false
|
||||||
}
|
}
|
||||||
if opts.DeleteMarker && opts.VersionPurgeStatus == Complete {
|
if opts.VersionPurgeStatus == Complete {
|
||||||
markDelete = false
|
markDelete = false
|
||||||
}
|
}
|
||||||
// determine if the version represents an object delete
|
// determine if the version represents an object delete
|
||||||
@@ -1098,7 +1107,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
// version as delete marker
|
// version as delete marker
|
||||||
// Add delete marker, since we don't have any version specified explicitly.
|
// Add delete marker, since we don't have any version specified explicitly.
|
||||||
// Or if a particular version id needs to be replicated.
|
// Or if a particular version id needs to be replicated.
|
||||||
if err = er.deleteObjectVersion(ctx, bucket, object, writeQuorum, fi); err != nil {
|
if err = er.deleteObjectVersion(ctx, bucket, object, writeQuorum, fi, opts.DeleteMarker); err != nil {
|
||||||
return objInfo, toObjectErr(err, bucket, object)
|
return objInfo, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
return fi.ToObjectInfo(bucket, object), nil
|
return fi.ToObjectInfo(bucket, object), nil
|
||||||
@@ -1115,7 +1124,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
DeleteMarkerReplicationStatus: opts.DeleteMarkerReplicationStatus,
|
DeleteMarkerReplicationStatus: opts.DeleteMarkerReplicationStatus,
|
||||||
VersionPurgeStatus: opts.VersionPurgeStatus,
|
VersionPurgeStatus: opts.VersionPurgeStatus,
|
||||||
TransitionStatus: opts.TransitionStatus,
|
TransitionStatus: opts.TransitionStatus,
|
||||||
}); err != nil {
|
}, opts.DeleteMarker); err != nil {
|
||||||
return objInfo, toObjectErr(err, bucket, object)
|
return objInfo, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1146,7 +1155,16 @@ func (er erasureObjects) addPartial(bucket, object, versionID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectTags - replace or add tags to an existing object
|
// PutObjectTags - replace or add tags to an existing object
|
||||||
func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) error {
|
func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) (ObjectInfo, error) {
|
||||||
|
var err error
|
||||||
|
// Lock the object before updating tags.
|
||||||
|
lk := er.NewNSLock(bucket, object)
|
||||||
|
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectInfo{}, err
|
||||||
|
}
|
||||||
|
defer lk.Unlock()
|
||||||
|
|
||||||
disks := er.getDisks()
|
disks := er.getDisks()
|
||||||
|
|
||||||
// Read metadata associated with the object from all disks.
|
// Read metadata associated with the object from all disks.
|
||||||
@@ -1154,7 +1172,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
|
|||||||
|
|
||||||
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List all online disks.
|
// List all online disks.
|
||||||
@@ -1163,13 +1181,13 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
|
|||||||
// Pick latest valid metadata.
|
// Pick latest valid metadata.
|
||||||
fi, err := pickValidFileInfo(ctx, metaArr, modTime, readQuorum)
|
fi, err := pickValidFileInfo(ctx, metaArr, modTime, readQuorum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
if fi.Deleted {
|
if fi.Deleted {
|
||||||
if opts.VersionID == "" {
|
if opts.VersionID == "" {
|
||||||
return toObjectErr(errFileNotFound, bucket, object)
|
return ObjectInfo{}, toObjectErr(errFileNotFound, bucket, object)
|
||||||
}
|
}
|
||||||
return toObjectErr(errMethodNotAllowed, bucket, object)
|
return ObjectInfo{}, toObjectErr(errMethodNotAllowed, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
onlineDisks, metaArr = shuffleDisksAndPartsMetadataByIndex(onlineDisks, metaArr, fi.Erasure.Distribution)
|
onlineDisks, metaArr = shuffleDisksAndPartsMetadataByIndex(onlineDisks, metaArr, fi.Erasure.Distribution)
|
||||||
@@ -1192,15 +1210,18 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
|
|||||||
|
|
||||||
// Write unique `xl.meta` for each disk.
|
// Write unique `xl.meta` for each disk.
|
||||||
if onlineDisks, err = writeUniqueFileInfo(ctx, onlineDisks, minioMetaTmpBucket, tempObj, metaArr, writeQuorum); err != nil {
|
if onlineDisks, err = writeUniqueFileInfo(ctx, onlineDisks, minioMetaTmpBucket, tempObj, metaArr, writeQuorum); err != nil {
|
||||||
return toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atomically rename metadata from tmp location to destination for each disk.
|
// Atomically rename metadata from tmp location to destination for each disk.
|
||||||
if _, err = renameFileInfo(ctx, onlineDisks, minioMetaTmpBucket, tempObj, bucket, object, writeQuorum); err != nil {
|
if _, err = renameFileInfo(ctx, onlineDisks, minioMetaTmpBucket, tempObj, bucket, object, writeQuorum); err != nil {
|
||||||
return toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
objInfo := fi.ToObjectInfo(bucket, object)
|
||||||
|
objInfo.UserTags = tags
|
||||||
|
|
||||||
|
return objInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateObjectMeta will update the metadata of a file.
|
// updateObjectMeta will update the metadata of a file.
|
||||||
@@ -1264,7 +1285,7 @@ func (er erasureObjects) updateObjectMeta(ctx context.Context, bucket, object st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObjectTags - delete object tags from an existing object
|
// DeleteObjectTags - delete object tags from an existing object
|
||||||
func (er erasureObjects) DeleteObjectTags(ctx context.Context, bucket, object string, opts ObjectOptions) error {
|
func (er erasureObjects) DeleteObjectTags(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
||||||
return er.PutObjectTags(ctx, bucket, object, "", opts)
|
return er.PutObjectTags(ctx, bucket, object, "", opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/cmd/config/storageclass"
|
"github.com/minio/minio/cmd/config/storageclass"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/color"
|
|
||||||
"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"
|
||||||
|
"github.com/minio/minio/pkg/wildcard"
|
||||||
)
|
)
|
||||||
|
|
||||||
type erasureServerPools struct {
|
type erasureServerPools struct {
|
||||||
@@ -107,16 +107,10 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
|||||||
return nil, fmt.Errorf("All serverPools should have same deployment ID expected %s, got %s", deploymentID, formats[i].ID)
|
return nil, fmt.Errorf("All serverPools should have same deployment ID expected %s, got %s", deploymentID, formats[i].ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if users brought different different distribution algo pools.
|
z.serverPools[i], err = newErasureSets(ctx, ep.Endpoints, storageDisks[i], formats[i], commonParityDrives, i)
|
||||||
if distributionAlgo != formats[i].Erasure.DistributionAlgo {
|
|
||||||
return nil, fmt.Errorf("All serverPools should have same distributionAlgo expected %s, got %s", distributionAlgo, formats[i].Erasure.DistributionAlgo)
|
|
||||||
}
|
|
||||||
|
|
||||||
z.serverPools[i], err = newErasureSets(ctx, ep.Endpoints, storageDisks[i], formats[i], commonParityDrives)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
z.serverPools[i].poolNumber = i
|
|
||||||
}
|
}
|
||||||
ctx, z.shutdown = context.WithCancel(ctx)
|
ctx, z.shutdown = context.WithCancel(ctx)
|
||||||
go intDataUpdateTracker.start(ctx, localDrives...)
|
go intDataUpdateTracker.start(ctx, localDrives...)
|
||||||
@@ -252,29 +246,39 @@ func (z *erasureServerPools) getServerPoolsAvailableSpace(ctx context.Context, s
|
|||||||
|
|
||||||
// getPoolIdx returns the found previous object and its corresponding pool idx,
|
// getPoolIdx returns the found previous object and its corresponding pool idx,
|
||||||
// if none are found falls back to most available space pool.
|
// if none are found falls back to most available space pool.
|
||||||
func (z *erasureServerPools) getPoolIdx(ctx context.Context, bucket, object string, opts ObjectOptions, size int64) (idx int, err error) {
|
func (z *erasureServerPools) getPoolIdx(ctx context.Context, bucket, object string, size int64) (idx int, err error) {
|
||||||
if z.SinglePool() {
|
if z.SinglePool() {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errs := make([]error, len(z.serverPools))
|
||||||
|
objInfos := make([]ObjectInfo, len(z.serverPools))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
for i, pool := range z.serverPools {
|
for i, pool := range z.serverPools {
|
||||||
objInfo, err := pool.GetObjectInfo(ctx, bucket, object, opts)
|
wg.Add(1)
|
||||||
switch err.(type) {
|
go func(i int, pool *erasureSets) {
|
||||||
case ObjectNotFound:
|
defer wg.Done()
|
||||||
// VersionId was not specified but found delete marker or no versions exist.
|
objInfos[i], errs[i] = pool.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
||||||
case MethodNotAllowed:
|
}(i, pool)
|
||||||
// VersionId was specified but found delete marker
|
}
|
||||||
default:
|
wg.Wait()
|
||||||
if err != nil {
|
|
||||||
// any other un-handled errors return right here.
|
for i, err := range errs {
|
||||||
return -1, err
|
if err != nil && !isErrObjectNotFound(err) {
|
||||||
}
|
return -1, err
|
||||||
}
|
}
|
||||||
// delete marker not specified means no versions
|
if isErrObjectNotFound(err) {
|
||||||
// exist continue to next pool.
|
// No object exists or its a delete marker,
|
||||||
if !objInfo.DeleteMarker && err != nil {
|
// check objInfo to confirm.
|
||||||
|
if objInfos[i].DeleteMarker && objInfos[i].Name != "" {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
// objInfo is not valid, truly the object doesn't
|
||||||
|
// exist proceed to next pool.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Success case and when DeleteMarker is true return.
|
// object exists at this pool.
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,8 +311,8 @@ func (z *erasureServerPools) Shutdown(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) BackendInfo() (b BackendInfo) {
|
func (z *erasureServerPools) BackendInfo() (b madmin.BackendInfo) {
|
||||||
b.Type = BackendErasure
|
b.Type = madmin.Erasure
|
||||||
|
|
||||||
scParity := globalStorageClass.GetParityForSC(storageclass.STANDARD)
|
scParity := globalStorageClass.GetParityForSC(storageclass.STANDARD)
|
||||||
if scParity <= 0 {
|
if scParity <= 0 {
|
||||||
@@ -327,6 +331,35 @@ func (z *erasureServerPools) BackendInfo() (b BackendInfo) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (z *erasureServerPools) LocalStorageInfo(ctx context.Context) (StorageInfo, []error) {
|
||||||
|
var storageInfo StorageInfo
|
||||||
|
|
||||||
|
storageInfos := make([]StorageInfo, len(z.serverPools))
|
||||||
|
storageInfosErrs := make([][]error, len(z.serverPools))
|
||||||
|
g := errgroup.WithNErrs(len(z.serverPools))
|
||||||
|
for index := range z.serverPools {
|
||||||
|
index := index
|
||||||
|
g.Go(func() error {
|
||||||
|
storageInfos[index], storageInfosErrs[index] = z.serverPools[index].LocalStorageInfo(ctx)
|
||||||
|
return nil
|
||||||
|
}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the go routines.
|
||||||
|
g.Wait()
|
||||||
|
|
||||||
|
storageInfo.Backend = z.BackendInfo()
|
||||||
|
for _, lstorageInfo := range storageInfos {
|
||||||
|
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for i := range z.serverPools {
|
||||||
|
errs = append(errs, storageInfosErrs[i]...)
|
||||||
|
}
|
||||||
|
return storageInfo, errs
|
||||||
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) StorageInfo(ctx context.Context) (StorageInfo, []error) {
|
func (z *erasureServerPools) StorageInfo(ctx context.Context) (StorageInfo, []error) {
|
||||||
var storageInfo StorageInfo
|
var storageInfo StorageInfo
|
||||||
|
|
||||||
@@ -356,7 +389,7 @@ func (z *erasureServerPools) StorageInfo(ctx context.Context) (StorageInfo, []er
|
|||||||
return storageInfo, errs
|
return storageInfo, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter, updates chan<- DataUsageInfo) error {
|
func (z *erasureServerPools) NSScanner(ctx context.Context, bf *bloomFilter, updates chan<- DataUsageInfo) error {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -371,12 +404,11 @@ func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloom
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(allBuckets) == 0 {
|
if len(allBuckets) == 0 {
|
||||||
logger.Info(color.Green("data-crawl:") + " No buckets found, skipping crawl")
|
|
||||||
updates <- DataUsageInfo{} // no buckets found update data usage to reflect latest state
|
updates <- DataUsageInfo{} // no buckets found update data usage to reflect latest state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crawl latest allBuckets first.
|
// Scanner latest allBuckets first.
|
||||||
sort.Slice(allBuckets, func(i, j int) bool {
|
sort.Slice(allBuckets, func(i, j int) bool {
|
||||||
return allBuckets[i].Created.After(allBuckets[j].Created)
|
return allBuckets[i].Created.After(allBuckets[j].Created)
|
||||||
})
|
})
|
||||||
@@ -398,8 +430,8 @@ func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloom
|
|||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Start crawler. Blocks until done.
|
// Start scanner. Blocks until done.
|
||||||
err := erObj.crawlAndGetDataUsage(ctx, allBuckets, bf, updates)
|
err := erObj.nsScanner(ctx, allBuckets, bf, updates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -521,60 +553,129 @@ func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object
|
|||||||
|
|
||||||
object = encodeDirObject(object)
|
object = encodeDirObject(object)
|
||||||
|
|
||||||
for _, pool := range z.serverPools {
|
if z.SinglePool() {
|
||||||
gr, err = pool.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
return z.serverPools[0].GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
||||||
if err != nil {
|
}
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
|
||||||
continue
|
var unlockOnDefer bool
|
||||||
|
var nsUnlocker = func() {}
|
||||||
|
defer func() {
|
||||||
|
if unlockOnDefer {
|
||||||
|
nsUnlocker()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Acquire lock
|
||||||
|
if lockType != noLock {
|
||||||
|
lock := z.NewNSLock(bucket, object)
|
||||||
|
switch lockType {
|
||||||
|
case writeLock:
|
||||||
|
ctx, err = lock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nsUnlocker = lock.Unlock
|
||||||
|
case readLock:
|
||||||
|
ctx, err = lock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nsUnlocker = lock.RUnlock
|
||||||
|
}
|
||||||
|
unlockOnDefer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, len(z.serverPools))
|
||||||
|
grs := make([]*GetObjectReader, len(z.serverPools))
|
||||||
|
|
||||||
|
lockType = noLock // do not take locks at lower levels
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i, pool := range z.serverPools {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int, pool *erasureSets) {
|
||||||
|
defer wg.Done()
|
||||||
|
grs[i], errs[i] = pool.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
||||||
|
}(i, pool)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
var found int = -1
|
||||||
|
for i, err := range errs {
|
||||||
|
if err == nil {
|
||||||
|
found = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
for _, grr := range grs {
|
||||||
|
if grr != nil {
|
||||||
|
grr.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return gr, err
|
return gr, err
|
||||||
}
|
}
|
||||||
return gr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if found >= 0 {
|
||||||
|
return grs[found], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
object = decodeDirObject(object)
|
||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
return gr, VersionNotFound{Bucket: bucket, Object: object, VersionID: opts.VersionID}
|
return gr, VersionNotFound{Bucket: bucket, Object: object, VersionID: opts.VersionID}
|
||||||
}
|
}
|
||||||
return gr, ObjectNotFound{Bucket: bucket, Object: object}
|
return gr, ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) error {
|
|
||||||
if err := checkGetObjArgs(ctx, bucket, object); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
object = encodeDirObject(object)
|
|
||||||
|
|
||||||
for _, pool := range z.serverPools {
|
|
||||||
if err := pool.GetObject(ctx, bucket, object, startOffset, length, writer, etag, opts); err != nil {
|
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if opts.VersionID != "" {
|
|
||||||
return VersionNotFound{Bucket: bucket, Object: object, VersionID: opts.VersionID}
|
|
||||||
}
|
|
||||||
return ObjectNotFound{Bucket: bucket, Object: object}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *erasureServerPools) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
func (z *erasureServerPools) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
||||||
if err = checkGetObjArgs(ctx, bucket, object); err != nil {
|
if err = checkGetObjArgs(ctx, bucket, object); err != nil {
|
||||||
return objInfo, err
|
return objInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
object = encodeDirObject(object)
|
object = encodeDirObject(object)
|
||||||
for _, pool := range z.serverPools {
|
|
||||||
objInfo, err = pool.GetObjectInfo(ctx, bucket, object, opts)
|
if z.SinglePool() {
|
||||||
if err != nil {
|
return z.serverPools[0].GetObjectInfo(ctx, bucket, object, opts)
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return objInfo, err
|
|
||||||
}
|
|
||||||
return objInfo, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock the object before reading.
|
||||||
|
lk := z.NewNSLock(bucket, object)
|
||||||
|
ctx, err = lk.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectInfo{}, err
|
||||||
|
}
|
||||||
|
defer lk.RUnlock()
|
||||||
|
|
||||||
|
errs := make([]error, len(z.serverPools))
|
||||||
|
objInfos := make([]ObjectInfo, len(z.serverPools))
|
||||||
|
|
||||||
|
opts.NoLock = true // avoid taking locks at lower levels for multi-pool setups.
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i, pool := range z.serverPools {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int, pool *erasureSets) {
|
||||||
|
defer wg.Done()
|
||||||
|
objInfos[i], errs[i] = pool.GetObjectInfo(ctx, bucket, object, opts)
|
||||||
|
}(i, pool)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
var found int = -1
|
||||||
|
for i, err := range errs {
|
||||||
|
if err == nil {
|
||||||
|
found = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
// some errors such as MethodNotAllowed for delete marker
|
||||||
|
// should be returned upwards.
|
||||||
|
return objInfos[i], err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found >= 0 {
|
||||||
|
return objInfos[found], nil
|
||||||
|
}
|
||||||
|
|
||||||
object = decodeDirObject(object)
|
object = decodeDirObject(object)
|
||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
return objInfo, VersionNotFound{Bucket: bucket, Object: object, VersionID: opts.VersionID}
|
return objInfo, VersionNotFound{Bucket: bucket, Object: object, VersionID: opts.VersionID}
|
||||||
@@ -595,7 +696,7 @@ func (z *erasureServerPools) PutObject(ctx context.Context, bucket string, objec
|
|||||||
return z.serverPools[0].PutObject(ctx, bucket, object, data, opts)
|
return z.serverPools[0].PutObject(ctx, bucket, object, data, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
idx, err := z.getPoolIdx(ctx, bucket, object, opts, data.Size())
|
idx, err := z.getPoolIdx(ctx, bucket, object, data.Size())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ObjectInfo{}, err
|
return ObjectInfo{}, err
|
||||||
}
|
}
|
||||||
@@ -610,21 +711,17 @@ func (z *erasureServerPools) DeleteObject(ctx context.Context, bucket string, ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
object = encodeDirObject(object)
|
object = encodeDirObject(object)
|
||||||
|
|
||||||
if z.SinglePool() {
|
if z.SinglePool() {
|
||||||
return z.serverPools[0].DeleteObject(ctx, bucket, object, opts)
|
return z.serverPools[0].DeleteObject(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
for _, pool := range z.serverPools {
|
|
||||||
objInfo, err = pool.DeleteObject(ctx, bucket, object, opts)
|
// We don't know the size here set 1GiB atleast.
|
||||||
if err == nil {
|
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||||
return objInfo, nil
|
if err != nil {
|
||||||
}
|
return objInfo, err
|
||||||
if err != nil && !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return objInfo, err
|
return z.serverPools[idx].DeleteObject(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, objects []ObjectToDelete, opts ObjectOptions) ([]DeletedObject, []error) {
|
func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, objects []ObjectToDelete, opts ObjectOptions) ([]DeletedObject, []error) {
|
||||||
@@ -638,9 +735,28 @@ func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, o
|
|||||||
objSets.Add(objects[i].ObjectName)
|
objSets.Add(objects[i].ObjectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
poolObjIdxMap := map[int][]ObjectToDelete{}
|
||||||
|
origIndexMap := map[int][]int{}
|
||||||
|
if !z.SinglePool() {
|
||||||
|
for j, obj := range objects {
|
||||||
|
idx, err := z.getPoolIdx(ctx, bucket, obj.ObjectName, 1<<30)
|
||||||
|
if err != nil {
|
||||||
|
// Unhandled errors return right here.
|
||||||
|
for i := range derrs {
|
||||||
|
derrs[i] = err
|
||||||
|
}
|
||||||
|
return dobjects, derrs
|
||||||
|
}
|
||||||
|
poolObjIdxMap[idx] = append(poolObjIdxMap[idx], obj)
|
||||||
|
origIndexMap[idx] = append(origIndexMap[idx], j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
// Acquire a bulk write lock across 'objects'
|
// Acquire a bulk write lock across 'objects'
|
||||||
multiDeleteLock := z.NewNSLock(bucket, objSets.ToSlice()...)
|
multiDeleteLock := z.NewNSLock(bucket, objSets.ToSlice()...)
|
||||||
if err := multiDeleteLock.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = multiDeleteLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
for i := range derrs {
|
for i := range derrs {
|
||||||
derrs[i] = err
|
derrs[i] = err
|
||||||
}
|
}
|
||||||
@@ -652,13 +768,15 @@ func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, o
|
|||||||
return z.serverPools[0].DeleteObjects(ctx, bucket, objects, opts)
|
return z.serverPools[0].DeleteObjects(ctx, bucket, objects, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pool := range z.serverPools {
|
for idx, pool := range z.serverPools {
|
||||||
deletedObjects, errs := pool.DeleteObjects(ctx, bucket, objects, opts)
|
objs := poolObjIdxMap[idx]
|
||||||
|
orgIndexes := origIndexMap[idx]
|
||||||
|
deletedObjects, errs := pool.DeleteObjects(ctx, bucket, objs, opts)
|
||||||
for i, derr := range errs {
|
for i, derr := range errs {
|
||||||
if derr != nil {
|
if derr != nil {
|
||||||
derrs[i] = derr
|
derrs[orgIndexes[i]] = derr
|
||||||
}
|
}
|
||||||
dobjects[i] = deletedObjects[i]
|
dobjects[orgIndexes[i]] = deletedObjects[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dobjects, derrs
|
return dobjects, derrs
|
||||||
@@ -670,7 +788,7 @@ func (z *erasureServerPools) CopyObject(ctx context.Context, srcBucket, srcObjec
|
|||||||
|
|
||||||
cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject))
|
cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject))
|
||||||
|
|
||||||
poolIdx, err := z.getPoolIdx(ctx, dstBucket, dstObject, dstOpts, srcInfo.Size)
|
poolIdx, err := z.getPoolIdx(ctx, dstBucket, dstObject, srcInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return objInfo, err
|
return objInfo, err
|
||||||
}
|
}
|
||||||
@@ -739,7 +857,7 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
|||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
Separator: delimiter,
|
Separator: delimiter,
|
||||||
Limit: maxKeys,
|
Limit: maxKeysPlusOne(maxKeys, marker != ""),
|
||||||
Marker: marker,
|
Marker: marker,
|
||||||
InclDeleted: true,
|
InclDeleted: true,
|
||||||
AskDisks: globalAPIConfig.getListQuorum(),
|
AskDisks: globalAPIConfig.getListQuorum(),
|
||||||
@@ -759,6 +877,11 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
|||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return loi, err
|
return loi, err
|
||||||
}
|
}
|
||||||
|
if versionMarker == "" {
|
||||||
|
// If we are not looking for a specific version skip it.
|
||||||
|
marker, _ = parseMarker(marker)
|
||||||
|
merged.forwardPast(marker)
|
||||||
|
}
|
||||||
objects := merged.fileInfoVersions(bucket, prefix, delimiter, versionMarker)
|
objects := merged.fileInfoVersions(bucket, prefix, delimiter, versionMarker)
|
||||||
loi.IsTruncated = err == nil && len(objects) > 0
|
loi.IsTruncated = err == nil && len(objects) > 0
|
||||||
if maxKeys > 0 && len(objects) > maxKeys {
|
if maxKeys > 0 && len(objects) > maxKeys {
|
||||||
@@ -766,7 +889,7 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
|||||||
loi.IsTruncated = true
|
loi.IsTruncated = true
|
||||||
}
|
}
|
||||||
for _, obj := range objects {
|
for _, obj := range objects {
|
||||||
if obj.IsDir && delimiter != "" {
|
if obj.IsDir && obj.ModTime.IsZero() && delimiter != "" {
|
||||||
loi.Prefixes = append(loi.Prefixes, obj.Name)
|
loi.Prefixes = append(loi.Prefixes, obj.Name)
|
||||||
} else {
|
} else {
|
||||||
loi.Objects = append(loi.Objects, obj)
|
loi.Objects = append(loi.Objects, obj)
|
||||||
@@ -780,6 +903,16 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
|||||||
return loi, nil
|
return loi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maxKeysPlusOne(maxKeys int, addOne bool) int {
|
||||||
|
if maxKeys < 0 || maxKeys > maxObjectList {
|
||||||
|
maxKeys = maxObjectList
|
||||||
|
}
|
||||||
|
if addOne {
|
||||||
|
maxKeys++
|
||||||
|
}
|
||||||
|
return maxKeys
|
||||||
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||||
var loi ListObjectsInfo
|
var loi ListObjectsInfo
|
||||||
|
|
||||||
@@ -787,7 +920,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
|||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
Separator: delimiter,
|
Separator: delimiter,
|
||||||
Limit: maxKeys,
|
Limit: maxKeysPlusOne(maxKeys, marker != ""),
|
||||||
Marker: marker,
|
Marker: marker,
|
||||||
InclDeleted: false,
|
InclDeleted: false,
|
||||||
AskDisks: globalAPIConfig.getListQuorum(),
|
AskDisks: globalAPIConfig.getListQuorum(),
|
||||||
@@ -796,6 +929,8 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return loi, err
|
return loi, err
|
||||||
}
|
}
|
||||||
|
marker, _ = parseMarker(marker)
|
||||||
|
merged.forwardPast(marker)
|
||||||
|
|
||||||
// Default is recursive, if delimiter is set then list non recursive.
|
// Default is recursive, if delimiter is set then list non recursive.
|
||||||
objects := merged.fileInfos(bucket, prefix, delimiter)
|
objects := merged.fileInfos(bucket, prefix, delimiter)
|
||||||
@@ -805,7 +940,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
|||||||
loi.IsTruncated = true
|
loi.IsTruncated = true
|
||||||
}
|
}
|
||||||
for _, obj := range objects {
|
for _, obj := range objects {
|
||||||
if obj.IsDir && delimiter != "" {
|
if obj.IsDir && obj.ModTime.IsZero() && delimiter != "" {
|
||||||
loi.Prefixes = append(loi.Prefixes, obj.Name)
|
loi.Prefixes = append(loi.Prefixes, obj.Name)
|
||||||
} else {
|
} else {
|
||||||
loi.Objects = append(loi.Objects, obj)
|
loi.Objects = append(loi.Objects, obj)
|
||||||
@@ -854,7 +989,7 @@ func (z *erasureServerPools) NewMultipartUpload(ctx context.Context, bucket, obj
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We don't know the exact size, so we ask for at least 1GiB file.
|
// We don't know the exact size, so we ask for at least 1GiB file.
|
||||||
idx, err := z.getPoolIdx(ctx, bucket, object, opts, 1<<30)
|
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -869,7 +1004,7 @@ func (z *erasureServerPools) CopyObjectPart(ctx context.Context, srcBucket, srcO
|
|||||||
}
|
}
|
||||||
|
|
||||||
return z.PutObjectPart(ctx, destBucket, destObject, uploadID, partID,
|
return z.PutObjectPart(ctx, destBucket, destObject, uploadID, partID,
|
||||||
NewPutObjReader(srcInfo.Reader, nil, nil), dstOpts)
|
NewPutObjReader(srcInfo.Reader), dstOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectPart - writes part of an object to hashedSet based on the object name.
|
// PutObjectPart - writes part of an object to hashedSet based on the object name.
|
||||||
@@ -1115,22 +1250,24 @@ func (z *erasureServerPools) DeleteBucket(ctx context.Context, bucket string, fo
|
|||||||
// data is not distributed across sets.
|
// data is not distributed across sets.
|
||||||
// Errors are logged but individual disk failures are not returned.
|
// Errors are logged but individual disk failures are not returned.
|
||||||
func (z *erasureServerPools) deleteAll(ctx context.Context, bucket, prefix string) {
|
func (z *erasureServerPools) deleteAll(ctx context.Context, bucket, prefix string) {
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, servers := range z.serverPools {
|
for _, servers := range z.serverPools {
|
||||||
for _, set := range servers.sets {
|
for _, set := range servers.sets {
|
||||||
for _, disk := range set.getDisks() {
|
set.deleteAll(ctx, bucket, prefix)
|
||||||
if disk == nil {
|
}
|
||||||
continue
|
}
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
|
||||||
go func(disk StorageAPI) {
|
// renameAll will rename bucket+prefix unconditionally across all disks to
|
||||||
defer wg.Done()
|
// minioMetaTmpBucket + unique uuid,
|
||||||
disk.Delete(ctx, bucket, prefix, true)
|
// Note that set distribution is ignored so it should only be used in cases where
|
||||||
}(disk)
|
// data is not distributed across sets. Errors are logged but individual
|
||||||
}
|
// disk failures are not returned.
|
||||||
|
func (z *erasureServerPools) renameAll(ctx context.Context, bucket, prefix string) {
|
||||||
|
for _, servers := range z.serverPools {
|
||||||
|
for _, set := range servers.sets {
|
||||||
|
set.renameAll(ctx, bucket, prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is used to undo a successful DeleteBucket operation.
|
// This function is used to undo a successful DeleteBucket operation.
|
||||||
@@ -1180,9 +1317,11 @@ func (z *erasureServerPools) ListBuckets(ctx context.Context) (buckets []BucketI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error) {
|
func (z *erasureServerPools) HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error) {
|
||||||
|
var err error
|
||||||
// Acquire lock on format.json
|
// Acquire lock on format.json
|
||||||
formatLock := z.NewNSLock(minioMetaBucket, formatConfigFile)
|
formatLock := z.NewNSLock(minioMetaBucket, formatConfigFile)
|
||||||
if err := formatLock.GetLock(ctx, globalOperationTimeout); err != nil {
|
ctx, err = formatLock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
return madmin.HealResultItem{}, err
|
return madmin.HealResultItem{}, err
|
||||||
}
|
}
|
||||||
defer formatLock.Unlock()
|
defer formatLock.Unlock()
|
||||||
@@ -1312,90 +1451,99 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
|||||||
type HealObjectFn func(bucket, object, versionID string) error
|
type HealObjectFn func(bucket, object, versionID string) error
|
||||||
|
|
||||||
func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObject HealObjectFn) error {
|
func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObject HealObjectFn) error {
|
||||||
// If listing did not return any entries upon first attempt, we
|
errCh := make(chan error)
|
||||||
// return `ObjectNotFound`, to indicate the caller for any
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
// actions they may want to take as if `prefix` is missing.
|
go func() {
|
||||||
err := toObjectErr(errFileNotFound, bucket, prefix)
|
defer close(errCh)
|
||||||
for _, erasureSet := range z.serverPools {
|
defer cancel()
|
||||||
for _, set := range erasureSet.sets {
|
|
||||||
var entryChs []FileInfoVersionsCh
|
for _, erasureSet := range z.serverPools {
|
||||||
var mu sync.Mutex
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for _, disk := range set.getOnlineDisks() {
|
for _, set := range erasureSet.sets {
|
||||||
disk := disk
|
set := set
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
entryCh, err := disk.WalkVersions(ctx, bucket, prefix, "", true, ctx.Done())
|
|
||||||
if err != nil {
|
disks, _ := set.getOnlineDisksWithHealing()
|
||||||
// Disk walk returned error, ignore it.
|
if len(disks) == 0 {
|
||||||
|
errCh <- errors.New("HealObjects: No non-healing disks found")
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
healEntry := func(entry metaCacheEntry) {
|
||||||
|
if entry.isDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// We might land at .metacache, .trash, .multipart
|
||||||
|
// no need to heal them skip, only when bucket
|
||||||
|
// is '.minio.sys'
|
||||||
|
if bucket == minioMetaBucket {
|
||||||
|
if wildcard.Match("buckets/*/.metacache/*", entry.name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if wildcard.Match("tmp/.trash/*", entry.name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if wildcard.Match("multipart/*", entry.name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fivs, err := entry.fileInfoVersions(bucket)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
waitForLowHTTPReq(globalHealConfig.IOCount, globalHealConfig.Sleep)
|
||||||
|
for _, version := range fivs.Versions {
|
||||||
|
if err := healObject(bucket, version.Name, version.VersionID); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// How to resolve partial results.
|
||||||
|
resolver := metadataResolutionParams{
|
||||||
|
dirQuorum: 1,
|
||||||
|
objQuorum: 1,
|
||||||
|
bucket: bucket,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := listPathRaw(ctx, listPathRawOptions{
|
||||||
|
disks: disks,
|
||||||
|
bucket: bucket,
|
||||||
|
path: baseDirFromPrefix(prefix),
|
||||||
|
recursive: true,
|
||||||
|
forwardTo: "",
|
||||||
|
minDisks: 1,
|
||||||
|
reportNotFound: false,
|
||||||
|
agreed: healEntry,
|
||||||
|
partial: func(entries metaCacheEntries, nAgreed int, errs []error) {
|
||||||
|
entry, ok := entries.resolve(&resolver)
|
||||||
|
if ok {
|
||||||
|
healEntry(*entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
finished: nil,
|
||||||
|
}); err != nil {
|
||||||
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mu.Lock()
|
|
||||||
entryChs = append(entryChs, FileInfoVersionsCh{
|
|
||||||
Ch: entryCh,
|
|
||||||
})
|
|
||||||
mu.Unlock()
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
entriesValid := make([]bool, len(entryChs))
|
|
||||||
entries := make([]FileInfoVersions, len(entryChs))
|
|
||||||
|
|
||||||
for {
|
|
||||||
entry, quorumCount, ok := lexicallySortedEntryVersions(entryChs, entries, entriesValid)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove empty directories if found - they have no meaning.
|
|
||||||
// Can be left over from highly concurrent put/remove.
|
|
||||||
if quorumCount > set.setDriveCount/2 && entry.IsEmptyDir {
|
|
||||||
if !opts.DryRun && opts.Remove {
|
|
||||||
set.deleteEmptyDir(ctx, bucket, entry.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indicate that first attempt was a success and subsequent loop
|
|
||||||
// knows that its not our first attempt at 'prefix'
|
|
||||||
err = nil
|
|
||||||
|
|
||||||
if quorumCount == set.setDriveCount && opts.ScanMode == madmin.HealNormalScan {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, version := range entry.Versions {
|
|
||||||
if err := healObject(bucket, version.Name, version.VersionID); err != nil {
|
|
||||||
return toObjectErr(err, bucket, version.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
|
return <-errCh
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
func (z *erasureServerPools) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||||
object = encodeDirObject(object)
|
object = encodeDirObject(object)
|
||||||
|
|
||||||
lk := z.NewNSLock(bucket, object)
|
|
||||||
if bucket == minioMetaBucket {
|
|
||||||
// For .minio.sys bucket heals we should hold write locks.
|
|
||||||
if err := lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
|
||||||
return madmin.HealResultItem{}, err
|
|
||||||
}
|
|
||||||
defer lk.Unlock()
|
|
||||||
} else {
|
|
||||||
// Lock the object before healing. Use read lock since healing
|
|
||||||
// will only regenerate parts & xl.meta of outdated disks.
|
|
||||||
if err := lk.GetRLock(ctx, globalOperationTimeout); err != nil {
|
|
||||||
return madmin.HealResultItem{}, err
|
|
||||||
}
|
|
||||||
defer lk.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pool := range z.serverPools {
|
for _, pool := range z.serverPools {
|
||||||
result, err := pool.HealObject(ctx, bucket, object, versionID, opts)
|
result, err := pool.HealObject(ctx, bucket, object, versionID, opts)
|
||||||
result.Object = decodeDirObject(result.Object)
|
result.Object = decodeDirObject(result.Object)
|
||||||
@@ -1426,18 +1574,18 @@ func (z *erasureServerPools) GetMetrics(ctx context.Context) (*BackendMetrics, e
|
|||||||
return &BackendMetrics{}, NotImplemented{}
|
return &BackendMetrics{}, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) getPoolAndSet(id string) (int, int, error) {
|
func (z *erasureServerPools) getPoolAndSet(id string) (poolIdx, setIdx, diskIdx int, err error) {
|
||||||
for poolIdx := range z.serverPools {
|
for poolIdx := range z.serverPools {
|
||||||
format := z.serverPools[poolIdx].format
|
format := z.serverPools[poolIdx].format
|
||||||
for setIdx, set := range format.Erasure.Sets {
|
for setIdx, set := range format.Erasure.Sets {
|
||||||
for _, diskID := range set {
|
for i, diskID := range set {
|
||||||
if diskID == id {
|
if diskID == id {
|
||||||
return poolIdx, setIdx, nil
|
return poolIdx, setIdx, i, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, 0, fmt.Errorf("DiskID(%s) %w", id, errDiskNotFound)
|
return -1, -1, -1, fmt.Errorf("DiskID(%s) %w", id, errDiskNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthOptions takes input options to return sepcific information
|
// HealthOptions takes input options to return sepcific information
|
||||||
@@ -1455,6 +1603,40 @@ type HealthResult struct {
|
|||||||
WriteQuorum int
|
WriteQuorum int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadHealth returns if the cluster can serve read requests
|
||||||
|
func (z *erasureServerPools) ReadHealth(ctx context.Context) bool {
|
||||||
|
erasureSetUpCount := make([][]int, len(z.serverPools))
|
||||||
|
for i := range z.serverPools {
|
||||||
|
erasureSetUpCount[i] = make([]int, len(z.serverPools[i].sets))
|
||||||
|
}
|
||||||
|
|
||||||
|
diskIDs := globalNotificationSys.GetLocalDiskIDs(ctx)
|
||||||
|
diskIDs = append(diskIDs, getLocalDiskIDs(z))
|
||||||
|
|
||||||
|
for _, localDiskIDs := range diskIDs {
|
||||||
|
for _, id := range localDiskIDs {
|
||||||
|
poolIdx, setIdx, _, err := z.getPoolAndSet(id)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
erasureSetUpCount[poolIdx][setIdx]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b := z.BackendInfo()
|
||||||
|
readQuorum := b.StandardSCData[0]
|
||||||
|
|
||||||
|
for poolIdx := range erasureSetUpCount {
|
||||||
|
for setIdx := range erasureSetUpCount[poolIdx] {
|
||||||
|
if erasureSetUpCount[poolIdx][setIdx] < readQuorum {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Health - returns current status of the object layer health,
|
// Health - returns current status of the object layer health,
|
||||||
// provides if write access exists across sets, additionally
|
// provides if write access exists across sets, additionally
|
||||||
// can be used to query scenarios if health may be lost
|
// can be used to query scenarios if health may be lost
|
||||||
@@ -1472,7 +1654,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
|||||||
|
|
||||||
for _, localDiskIDs := range diskIDs {
|
for _, localDiskIDs := range diskIDs {
|
||||||
for _, id := range localDiskIDs {
|
for _, id := range localDiskIDs {
|
||||||
poolIdx, setIdx, err := z.getPoolAndSet(id)
|
poolIdx, setIdx, _, err := z.getPoolAndSet(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
@@ -1495,7 +1677,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
|||||||
// we need to tell healthy status as 'false' so that this server
|
// we need to tell healthy status as 'false' so that this server
|
||||||
// is not taken down for maintenance
|
// is not taken down for maintenance
|
||||||
var err error
|
var err error
|
||||||
aggHealStateResult, err = getAggregatedBackgroundHealState(ctx)
|
aggHealStateResult, err = getAggregatedBackgroundHealState(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(logger.SetReqInfo(ctx, reqInfo), fmt.Errorf("Unable to verify global heal status: %w", err))
|
logger.LogIf(logger.SetReqInfo(ctx, reqInfo), fmt.Errorf("Unable to verify global heal status: %w", err))
|
||||||
return HealthResult{
|
return HealthResult{
|
||||||
@@ -1542,62 +1724,35 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectTags - replace or add tags to an existing object
|
// PutObjectTags - replace or add tags to an existing object
|
||||||
func (z *erasureServerPools) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) error {
|
func (z *erasureServerPools) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) (ObjectInfo, error) {
|
||||||
object = encodeDirObject(object)
|
object = encodeDirObject(object)
|
||||||
if z.SinglePool() {
|
if z.SinglePool() {
|
||||||
return z.serverPools[0].PutObjectTags(ctx, bucket, object, tags, opts)
|
return z.serverPools[0].PutObjectTags(ctx, bucket, object, tags, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pool := range z.serverPools {
|
// We don't know the size here set 1GiB atleast.
|
||||||
err := pool.PutObjectTags(ctx, bucket, object, tags, opts)
|
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
return ObjectInfo{}, err
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if opts.VersionID != "" {
|
|
||||||
return VersionNotFound{
|
|
||||||
Bucket: bucket,
|
|
||||||
Object: object,
|
|
||||||
VersionID: opts.VersionID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ObjectNotFound{
|
|
||||||
Bucket: bucket,
|
|
||||||
Object: object,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return z.serverPools[idx].PutObjectTags(ctx, bucket, object, tags, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObjectTags - delete object tags from an existing object
|
// DeleteObjectTags - delete object tags from an existing object
|
||||||
func (z *erasureServerPools) DeleteObjectTags(ctx context.Context, bucket, object string, opts ObjectOptions) error {
|
func (z *erasureServerPools) DeleteObjectTags(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
||||||
object = encodeDirObject(object)
|
object = encodeDirObject(object)
|
||||||
if z.SinglePool() {
|
if z.SinglePool() {
|
||||||
return z.serverPools[0].DeleteObjectTags(ctx, bucket, object, opts)
|
return z.serverPools[0].DeleteObjectTags(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
for _, pool := range z.serverPools {
|
|
||||||
err := pool.DeleteObjectTags(ctx, bucket, object, opts)
|
// We don't know the size here set 1GiB atleast.
|
||||||
if err != nil {
|
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if err != nil {
|
||||||
continue
|
return ObjectInfo{}, err
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if opts.VersionID != "" {
|
|
||||||
return VersionNotFound{
|
|
||||||
Bucket: bucket,
|
|
||||||
Object: object,
|
|
||||||
VersionID: opts.VersionID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ObjectNotFound{
|
|
||||||
Bucket: bucket,
|
|
||||||
Object: object,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return z.serverPools[idx].DeleteObjectTags(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectTags - get object tags from an existing object
|
// GetObjectTags - get object tags from an existing object
|
||||||
@@ -1606,25 +1761,12 @@ func (z *erasureServerPools) GetObjectTags(ctx context.Context, bucket, object s
|
|||||||
if z.SinglePool() {
|
if z.SinglePool() {
|
||||||
return z.serverPools[0].GetObjectTags(ctx, bucket, object, opts)
|
return z.serverPools[0].GetObjectTags(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
for _, pool := range z.serverPools {
|
|
||||||
tags, err := pool.GetObjectTags(ctx, bucket, object, opts)
|
// We don't know the size here set 1GiB atleast.
|
||||||
if err != nil {
|
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if err != nil {
|
||||||
continue
|
return nil, err
|
||||||
}
|
|
||||||
return tags, err
|
|
||||||
}
|
|
||||||
return tags, nil
|
|
||||||
}
|
|
||||||
if opts.VersionID != "" {
|
|
||||||
return nil, VersionNotFound{
|
|
||||||
Bucket: bucket,
|
|
||||||
Object: object,
|
|
||||||
VersionID: opts.VersionID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, ObjectNotFound{
|
|
||||||
Bucket: bucket,
|
|
||||||
Object: object,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return z.serverPools[idx].GetObjectTags(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dchest/siphash"
|
"github.com/dchest/siphash"
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
@@ -83,7 +84,7 @@ type erasureSets struct {
|
|||||||
setCount, setDriveCount int
|
setCount, setDriveCount int
|
||||||
defaultParityCount int
|
defaultParityCount int
|
||||||
|
|
||||||
poolNumber int
|
poolIndex int
|
||||||
|
|
||||||
disksConnectEvent chan diskConnectInfo
|
disksConnectEvent chan diskConnectInfo
|
||||||
|
|
||||||
@@ -217,7 +218,7 @@ func (s *erasureSets) connectDisks() {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if disk.IsLocal() && disk.Healing() {
|
if disk.IsLocal() && disk.Healing() != nil {
|
||||||
globalBackgroundHealState.pushHealLocalDisks(disk.Endpoint())
|
globalBackgroundHealState.pushHealLocalDisks(disk.Endpoint())
|
||||||
logger.Info(fmt.Sprintf("Found the drive %s that needs healing, attempting to heal...", disk))
|
logger.Info(fmt.Sprintf("Found the drive %s that needs healing, attempting to heal...", disk))
|
||||||
}
|
}
|
||||||
@@ -246,6 +247,7 @@ func (s *erasureSets) connectDisks() {
|
|||||||
disk.SetDiskID(format.Erasure.This)
|
disk.SetDiskID(format.Erasure.This)
|
||||||
s.erasureDisks[setIndex][diskIndex] = disk
|
s.erasureDisks[setIndex][diskIndex] = disk
|
||||||
}
|
}
|
||||||
|
disk.SetDiskLoc(s.poolIndex, setIndex, diskIndex)
|
||||||
s.endpointStrings[setIndex*s.setDriveCount+diskIndex] = disk.String()
|
s.endpointStrings[setIndex*s.setDriveCount+diskIndex] = disk.String()
|
||||||
s.erasureDisksMu.Unlock()
|
s.erasureDisksMu.Unlock()
|
||||||
go func(setIndex int) {
|
go func(setIndex int) {
|
||||||
@@ -331,7 +333,7 @@ func (s *erasureSets) GetDisks(setIndex int) func() []StorageAPI {
|
|||||||
const defaultMonitorConnectEndpointInterval = defaultMonitorNewDiskInterval + time.Second*5
|
const defaultMonitorConnectEndpointInterval = defaultMonitorNewDiskInterval + time.Second*5
|
||||||
|
|
||||||
// Initialize new set of erasure coded sets.
|
// Initialize new set of erasure coded sets.
|
||||||
func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []StorageAPI, format *formatErasureV3, defaultParityCount int) (*erasureSets, error) {
|
func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []StorageAPI, format *formatErasureV3, defaultParityCount, poolIdx int) (*erasureSets, error) {
|
||||||
setCount := len(format.Erasure.Sets)
|
setCount := len(format.Erasure.Sets)
|
||||||
setDriveCount := len(format.Erasure.Sets[0])
|
setDriveCount := len(format.Erasure.Sets[0])
|
||||||
|
|
||||||
@@ -353,13 +355,17 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
distributionAlgo: format.Erasure.DistributionAlgo,
|
distributionAlgo: format.Erasure.DistributionAlgo,
|
||||||
deploymentID: uuid.MustParse(format.ID),
|
deploymentID: uuid.MustParse(format.ID),
|
||||||
mrfOperations: make(map[healSource]int),
|
mrfOperations: make(map[healSource]int),
|
||||||
|
poolIndex: poolIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex := newNSLock(globalIsDistErasure)
|
mutex := newNSLock(globalIsDistErasure)
|
||||||
|
|
||||||
|
// Number of buffers, max 2GB
|
||||||
|
n := (2 * humanize.GiByte) / (blockSizeV2 * 2)
|
||||||
|
|
||||||
// Initialize byte pool once for all sets, bpool size is set to
|
// Initialize byte pool once for all sets, bpool size is set to
|
||||||
// setCount * setDriveCount with each memory upto blockSizeV1.
|
// setCount * setDriveCount with each memory upto blockSizeV2.
|
||||||
bp := bpool.NewBytePoolCap(setCount*setDriveCount, blockSizeV1, blockSizeV1*2)
|
bp := bpool.NewBytePoolCap(n, blockSizeV2, blockSizeV2*2)
|
||||||
|
|
||||||
for i := 0; i < setCount; i++ {
|
for i := 0; i < setCount; i++ {
|
||||||
s.erasureDisks[i] = make([]StorageAPI, setDriveCount)
|
s.erasureDisks[i] = make([]StorageAPI, setDriveCount)
|
||||||
@@ -393,27 +399,36 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
disk.SetDiskLoc(s.poolIndex, m, n)
|
||||||
s.endpointStrings[m*setDriveCount+n] = disk.String()
|
s.endpointStrings[m*setDriveCount+n] = disk.String()
|
||||||
s.erasureDisks[m][n] = disk
|
s.erasureDisks[m][n] = disk
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize erasure objects for a given set.
|
// Initialize erasure objects for a given set.
|
||||||
s.sets[i] = &erasureObjects{
|
s.sets[i] = &erasureObjects{
|
||||||
setNumber: i,
|
setIndex: i,
|
||||||
setDriveCount: setDriveCount,
|
poolIndex: poolIdx,
|
||||||
defaultParityCount: defaultParityCount,
|
setDriveCount: setDriveCount,
|
||||||
getDisks: s.GetDisks(i),
|
defaultParityCount: defaultParityCount,
|
||||||
getLockers: s.GetLockers(i),
|
getDisks: s.GetDisks(i),
|
||||||
getEndpoints: s.GetEndpoints(i),
|
getLockers: s.GetLockers(i),
|
||||||
nsMutex: mutex,
|
getEndpoints: s.GetEndpoints(i),
|
||||||
bp: bp,
|
deletedCleanupSleeper: newDynamicSleeper(10, 10*time.Second),
|
||||||
mrfOpCh: make(chan partialOperation, 10000),
|
nsMutex: mutex,
|
||||||
|
bp: bp,
|
||||||
|
mrfOpCh: make(chan partialOperation, 10000),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanup ".trash/" folder every 30 minutes with sufficient sleep cycles.
|
||||||
|
const deletedObjectsCleanupInterval = 10 * time.Minute
|
||||||
|
|
||||||
// start cleanup stale uploads go-routine.
|
// start cleanup stale uploads go-routine.
|
||||||
go s.cleanupStaleUploads(ctx, GlobalStaleUploadsCleanupInterval, GlobalStaleUploadsExpiry)
|
go s.cleanupStaleUploads(ctx, GlobalStaleUploadsCleanupInterval, GlobalStaleUploadsExpiry)
|
||||||
|
|
||||||
|
// start cleanup of deleted objects.
|
||||||
|
go s.cleanupDeletedObjects(ctx, deletedObjectsCleanupInterval)
|
||||||
|
|
||||||
// Start the disk monitoring and connect routine.
|
// Start the disk monitoring and connect routine.
|
||||||
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
|
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
|
||||||
go s.maintainMRFList()
|
go s.maintainMRFList()
|
||||||
@@ -422,15 +437,37 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *erasureSets) cleanupStaleUploads(ctx context.Context, cleanupInterval, expiry time.Duration) {
|
func (s *erasureSets) cleanupDeletedObjects(ctx context.Context, cleanupInterval time.Duration) {
|
||||||
ticker := time.NewTicker(cleanupInterval)
|
timer := time.NewTimer(cleanupInterval)
|
||||||
defer ticker.Stop()
|
defer timer.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-timer.C:
|
||||||
|
// Reset for the next interval
|
||||||
|
timer.Reset(cleanupInterval)
|
||||||
|
|
||||||
|
for _, set := range s.sets {
|
||||||
|
set.cleanupDeletedObjects(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *erasureSets) cleanupStaleUploads(ctx context.Context, cleanupInterval, expiry time.Duration) {
|
||||||
|
timer := time.NewTimer(cleanupInterval)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-timer.C:
|
||||||
|
// Reset for the next interval
|
||||||
|
timer.Reset(cleanupInterval)
|
||||||
|
|
||||||
for _, set := range s.sets {
|
for _, set := range s.sets {
|
||||||
set.cleanupStaleUploads(ctx, expiry)
|
set.cleanupStaleUploads(ctx, expiry)
|
||||||
}
|
}
|
||||||
@@ -446,7 +483,7 @@ type auditObjectOp struct {
|
|||||||
Disks []string `json:"disks"`
|
Disks []string `json:"disks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func auditObjectErasureSet(ctx context.Context, object string, set *erasureObjects, poolNum int) {
|
func auditObjectErasureSet(ctx context.Context, object string, set *erasureObjects) {
|
||||||
if len(logger.AuditTargets) == 0 {
|
if len(logger.AuditTargets) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -454,8 +491,8 @@ func auditObjectErasureSet(ctx context.Context, object string, set *erasureObjec
|
|||||||
object = decodeDirObject(object)
|
object = decodeDirObject(object)
|
||||||
|
|
||||||
op := auditObjectOp{
|
op := auditObjectOp{
|
||||||
Pool: poolNum + 1,
|
Pool: set.poolIndex + 1,
|
||||||
Set: set.setNumber + 1,
|
Set: set.setIndex + 1,
|
||||||
Disks: set.getEndpoints(),
|
Disks: set.getEndpoints(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,7 +540,7 @@ func (s *erasureSets) StorageUsageInfo(ctx context.Context) StorageInfo {
|
|||||||
storageUsageInfo := func() StorageInfo {
|
storageUsageInfo := func() StorageInfo {
|
||||||
var storageInfo StorageInfo
|
var storageInfo StorageInfo
|
||||||
storageInfos := make([]StorageInfo, len(s.sets))
|
storageInfos := make([]StorageInfo, len(s.sets))
|
||||||
storageInfo.Backend.Type = BackendErasure
|
storageInfo.Backend.Type = madmin.Erasure
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(s.sets))
|
g := errgroup.WithNErrs(len(s.sets))
|
||||||
for index := range s.sets {
|
for index := range s.sets {
|
||||||
@@ -538,6 +575,37 @@ func (s *erasureSets) StorageUsageInfo(ctx context.Context) StorageInfo {
|
|||||||
|
|
||||||
// StorageInfo - combines output of StorageInfo across all erasure coded object sets.
|
// StorageInfo - combines output of StorageInfo across all erasure coded object sets.
|
||||||
func (s *erasureSets) StorageInfo(ctx context.Context) (StorageInfo, []error) {
|
func (s *erasureSets) StorageInfo(ctx context.Context) (StorageInfo, []error) {
|
||||||
|
var storageInfo madmin.StorageInfo
|
||||||
|
|
||||||
|
storageInfos := make([]madmin.StorageInfo, len(s.sets))
|
||||||
|
storageInfoErrs := make([][]error, len(s.sets))
|
||||||
|
|
||||||
|
g := errgroup.WithNErrs(len(s.sets))
|
||||||
|
for index := range s.sets {
|
||||||
|
index := index
|
||||||
|
g.Go(func() error {
|
||||||
|
storageInfos[index], storageInfoErrs[index] = s.sets[index].StorageInfo(ctx)
|
||||||
|
return nil
|
||||||
|
}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the go routines.
|
||||||
|
g.Wait()
|
||||||
|
|
||||||
|
for _, lstorageInfo := range storageInfos {
|
||||||
|
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for i := range s.sets {
|
||||||
|
errs = append(errs, storageInfoErrs[i]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageInfo, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageInfo - combines output of StorageInfo across all erasure coded object sets.
|
||||||
|
func (s *erasureSets) LocalStorageInfo(ctx context.Context) (StorageInfo, []error) {
|
||||||
var storageInfo StorageInfo
|
var storageInfo StorageInfo
|
||||||
|
|
||||||
storageInfos := make([]StorageInfo, len(s.sets))
|
storageInfos := make([]StorageInfo, len(s.sets))
|
||||||
@@ -547,7 +615,7 @@ func (s *erasureSets) StorageInfo(ctx context.Context) (StorageInfo, []error) {
|
|||||||
for index := range s.sets {
|
for index := range s.sets {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
storageInfos[index], storageInfoErrs[index] = s.sets[index].StorageInfo(ctx)
|
storageInfos[index], storageInfoErrs[index] = s.sets[index].LocalStorageInfo(ctx)
|
||||||
return nil
|
return nil
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
@@ -630,9 +698,11 @@ func sipHashMod(key string, cardinality int, id [16]byte) int {
|
|||||||
if cardinality <= 0 {
|
if cardinality <= 0 {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
sip := siphash.New(id[:])
|
// use the faster version as per siphash docs
|
||||||
sip.Write([]byte(key))
|
// https://github.com/dchest/siphash#usage
|
||||||
return int(sip.Sum64() % uint64(cardinality))
|
k0, k1 := binary.LittleEndian.Uint64(id[0:8]), binary.LittleEndian.Uint64(id[8:16])
|
||||||
|
sum64 := siphash.Hash(k0, k1, []byte(key))
|
||||||
|
return int(sum64 % uint64(cardinality))
|
||||||
}
|
}
|
||||||
|
|
||||||
func crcHashMod(key string, cardinality int) int {
|
func crcHashMod(key string, cardinality int) int {
|
||||||
@@ -772,17 +842,10 @@ func (s *erasureSets) ListBuckets(ctx context.Context) (buckets []BucketInfo, er
|
|||||||
// GetObjectNInfo - returns object info and locked object ReadCloser
|
// GetObjectNInfo - returns object info and locked object ReadCloser
|
||||||
func (s *erasureSets) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
func (s *erasureSets) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
return set.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObject - reads an object from the hashedSet based on the object name.
|
|
||||||
func (s *erasureSets) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) error {
|
|
||||||
set := s.getHashedSet(object)
|
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
|
||||||
return set.GetObject(ctx, bucket, object, startOffset, length, writer, etag, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *erasureSets) parentDirIsObject(ctx context.Context, bucket, parent string) bool {
|
func (s *erasureSets) parentDirIsObject(ctx context.Context, bucket, parent string) bool {
|
||||||
if parent == "." {
|
if parent == "." {
|
||||||
return false
|
return false
|
||||||
@@ -793,7 +856,7 @@ func (s *erasureSets) parentDirIsObject(ctx context.Context, bucket, parent stri
|
|||||||
// PutObject - writes an object to hashedSet based on the object name.
|
// PutObject - writes an object to hashedSet based on the object name.
|
||||||
func (s *erasureSets) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
func (s *erasureSets) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
opts.ParentIsObject = s.parentDirIsObject
|
opts.ParentIsObject = s.parentDirIsObject
|
||||||
return set.PutObject(ctx, bucket, object, data, opts)
|
return set.PutObject(ctx, bucket, object, data, opts)
|
||||||
}
|
}
|
||||||
@@ -801,14 +864,14 @@ func (s *erasureSets) PutObject(ctx context.Context, bucket string, object strin
|
|||||||
// GetObjectInfo - reads object metadata from the hashedSet based on the object name.
|
// GetObjectInfo - reads object metadata from the hashedSet based on the object name.
|
||||||
func (s *erasureSets) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
func (s *erasureSets) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.GetObjectInfo(ctx, bucket, object, opts)
|
return set.GetObjectInfo(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObject - deletes an object from the hashedSet based on the object name.
|
// DeleteObject - deletes an object from the hashedSet based on the object name.
|
||||||
func (s *erasureSets) DeleteObject(ctx context.Context, bucket string, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
func (s *erasureSets) DeleteObject(ctx context.Context, bucket string, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.DeleteObject(ctx, bucket, object, opts)
|
return set.DeleteObject(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,7 +923,7 @@ func (s *erasureSets) DeleteObjects(ctx context.Context, bucket string, objects
|
|||||||
delErrs[obj.origIndex] = errs[i]
|
delErrs[obj.origIndex] = errs[i]
|
||||||
delObjects[obj.origIndex] = dobjects[i]
|
delObjects[obj.origIndex] = dobjects[i]
|
||||||
if errs[i] == nil {
|
if errs[i] == nil {
|
||||||
auditObjectErasureSet(ctx, obj.object.ObjectName, set, s.poolNumber)
|
auditObjectErasureSet(ctx, obj.object.ObjectName, set)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -873,7 +936,7 @@ func (s *erasureSets) CopyObject(ctx context.Context, srcBucket, srcObject, dstB
|
|||||||
srcSet := s.getHashedSet(srcObject)
|
srcSet := s.getHashedSet(srcObject)
|
||||||
dstSet := s.getHashedSet(dstObject)
|
dstSet := s.getHashedSet(dstObject)
|
||||||
|
|
||||||
auditObjectErasureSet(ctx, dstObject, dstSet, s.poolNumber)
|
auditObjectErasureSet(ctx, dstObject, dstSet)
|
||||||
|
|
||||||
cpSrcDstSame := srcSet == dstSet
|
cpSrcDstSame := srcSet == dstSet
|
||||||
// Check if this request is only metadata update.
|
// Check if this request is only metadata update.
|
||||||
@@ -909,162 +972,18 @@ func (s *erasureSets) CopyObject(ctx context.Context, srcBucket, srcObject, dstB
|
|||||||
return dstSet.putObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, putOpts)
|
return dstSet.putObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, putOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileInfoVersionsCh - file info versions channel
|
|
||||||
type FileInfoVersionsCh struct {
|
|
||||||
Ch chan FileInfoVersions
|
|
||||||
Prev FileInfoVersions
|
|
||||||
Valid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop - pops a cached entry if any, or from the cached channel.
|
|
||||||
func (f *FileInfoVersionsCh) Pop() (fi FileInfoVersions, ok bool) {
|
|
||||||
if f.Valid {
|
|
||||||
f.Valid = false
|
|
||||||
return f.Prev, true
|
|
||||||
} // No cached entries found, read from channel
|
|
||||||
f.Prev, ok = <-f.Ch
|
|
||||||
return f.Prev, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push - cache an entry, for Pop() later.
|
|
||||||
func (f *FileInfoVersionsCh) Push(fi FileInfoVersions) {
|
|
||||||
f.Prev = fi
|
|
||||||
f.Valid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileInfoCh - file info channel
|
|
||||||
type FileInfoCh struct {
|
|
||||||
Ch chan FileInfo
|
|
||||||
Prev FileInfo
|
|
||||||
Valid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop - pops a cached entry if any, or from the cached channel.
|
|
||||||
func (f *FileInfoCh) Pop() (fi FileInfo, ok bool) {
|
|
||||||
if f.Valid {
|
|
||||||
f.Valid = false
|
|
||||||
return f.Prev, true
|
|
||||||
} // No cached entries found, read from channel
|
|
||||||
f.Prev, ok = <-f.Ch
|
|
||||||
return f.Prev, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push - cache an entry, for Pop() later.
|
|
||||||
func (f *FileInfoCh) Push(fi FileInfo) {
|
|
||||||
f.Prev = fi
|
|
||||||
f.Valid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate lexically least entry across multiple FileInfo channels,
|
|
||||||
// returns the lexically common entry and the total number of times
|
|
||||||
// we found this entry. Additionally also returns a boolean
|
|
||||||
// to indicate if the caller needs to call this function
|
|
||||||
// again to list the next entry. It is callers responsibility
|
|
||||||
// if the caller wishes to list N entries to call lexicallySortedEntry
|
|
||||||
// N times until this boolean is 'false'.
|
|
||||||
func lexicallySortedEntryVersions(entryChs []FileInfoVersionsCh, entries []FileInfoVersions, entriesValid []bool) (FileInfoVersions, int, bool) {
|
|
||||||
for j := range entryChs {
|
|
||||||
entries[j], entriesValid[j] = entryChs[j].Pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
var isTruncated = false
|
|
||||||
for _, valid := range entriesValid {
|
|
||||||
if !valid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
isTruncated = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var lentry FileInfoVersions
|
|
||||||
var found bool
|
|
||||||
for i, valid := range entriesValid {
|
|
||||||
if !valid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
lentry = entries[i]
|
|
||||||
found = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if entries[i].Name < lentry.Name {
|
|
||||||
lentry = entries[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We haven't been able to find any lexically least entry,
|
|
||||||
// this would mean that we don't have valid entry.
|
|
||||||
if !found {
|
|
||||||
return lentry, 0, isTruncated
|
|
||||||
}
|
|
||||||
|
|
||||||
lexicallySortedEntryCount := 0
|
|
||||||
for i, valid := range entriesValid {
|
|
||||||
if !valid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entries are duplicated across disks,
|
|
||||||
// we should simply skip such entries.
|
|
||||||
if lentry.Name == entries[i].Name && lentry.LatestModTime.Equal(entries[i].LatestModTime) {
|
|
||||||
lexicallySortedEntryCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push all entries which are lexically higher
|
|
||||||
// and will be returned later in Pop()
|
|
||||||
entryChs[i].Push(entries[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return lentry, lexicallySortedEntryCount, isTruncated
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *erasureSets) startMergeWalksVersions(ctx context.Context, bucket, prefix, marker string, recursive bool, endWalkCh <-chan struct{}) []FileInfoVersionsCh {
|
|
||||||
return s.startMergeWalksVersionsN(ctx, bucket, prefix, marker, recursive, endWalkCh, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts a walk versions channel across N number of disks and returns a slice.
|
|
||||||
// FileInfoCh which can be read from.
|
|
||||||
func (s *erasureSets) startMergeWalksVersionsN(ctx context.Context, bucket, prefix, marker string, recursive bool, endWalkCh <-chan struct{}, ndisks int) []FileInfoVersionsCh {
|
|
||||||
var entryChs []FileInfoVersionsCh
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var mutex sync.Mutex
|
|
||||||
for _, set := range s.sets {
|
|
||||||
// Reset for the next erasure set.
|
|
||||||
for _, disk := range set.getLoadBalancedNDisks(ndisks) {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(disk StorageAPI) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
entryCh, err := disk.WalkVersions(GlobalContext, bucket, prefix, marker, recursive, endWalkCh)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex.Lock()
|
|
||||||
entryChs = append(entryChs, FileInfoVersionsCh{
|
|
||||||
Ch: entryCh,
|
|
||||||
})
|
|
||||||
mutex.Unlock()
|
|
||||||
}(disk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return entryChs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *erasureSets) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error) {
|
func (s *erasureSets) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error) {
|
||||||
// In list multipart uploads we are going to treat input prefix as the object,
|
// In list multipart uploads we are going to treat input prefix as the object,
|
||||||
// this means that we are not supporting directory navigation.
|
// this means that we are not supporting directory navigation.
|
||||||
set := s.getHashedSet(prefix)
|
set := s.getHashedSet(prefix)
|
||||||
auditObjectErasureSet(ctx, prefix, set, s.poolNumber)
|
auditObjectErasureSet(ctx, prefix, set)
|
||||||
return set.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
|
return set.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiate a new multipart upload on a hashedSet based on object name.
|
// Initiate a new multipart upload on a hashedSet based on object name.
|
||||||
func (s *erasureSets) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error) {
|
func (s *erasureSets) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.NewMultipartUpload(ctx, bucket, object, opts)
|
return set.NewMultipartUpload(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1072,42 +991,42 @@ func (s *erasureSets) NewMultipartUpload(ctx context.Context, bucket, object str
|
|||||||
func (s *erasureSets) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int,
|
func (s *erasureSets) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int,
|
||||||
startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (partInfo PartInfo, err error) {
|
startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (partInfo PartInfo, err error) {
|
||||||
destSet := s.getHashedSet(destObject)
|
destSet := s.getHashedSet(destObject)
|
||||||
auditObjectErasureSet(ctx, destObject, destSet, s.poolNumber)
|
auditObjectErasureSet(ctx, destObject, destSet)
|
||||||
return destSet.PutObjectPart(ctx, destBucket, destObject, uploadID, partID, NewPutObjReader(srcInfo.Reader, nil, nil), dstOpts)
|
return destSet.PutObjectPart(ctx, destBucket, destObject, uploadID, partID, NewPutObjReader(srcInfo.Reader), dstOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectPart - writes part of an object to hashedSet based on the object name.
|
// PutObjectPart - writes part of an object to hashedSet based on the object name.
|
||||||
func (s *erasureSets) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error) {
|
func (s *erasureSets) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts)
|
return set.PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMultipartInfo - return multipart metadata info uploaded at hashedSet.
|
// GetMultipartInfo - return multipart metadata info uploaded at hashedSet.
|
||||||
func (s *erasureSets) GetMultipartInfo(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) (result MultipartInfo, err error) {
|
func (s *erasureSets) GetMultipartInfo(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) (result MultipartInfo, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.GetMultipartInfo(ctx, bucket, object, uploadID, opts)
|
return set.GetMultipartInfo(ctx, bucket, object, uploadID, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListObjectParts - lists all uploaded parts to an object in hashedSet.
|
// ListObjectParts - lists all uploaded parts to an object in hashedSet.
|
||||||
func (s *erasureSets) ListObjectParts(ctx context.Context, bucket, object, uploadID string, partNumberMarker int, maxParts int, opts ObjectOptions) (result ListPartsInfo, err error) {
|
func (s *erasureSets) ListObjectParts(ctx context.Context, bucket, object, uploadID string, partNumberMarker int, maxParts int, opts ObjectOptions) (result ListPartsInfo, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts, opts)
|
return set.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aborts an in-progress multipart operation on hashedSet based on the object name.
|
// Aborts an in-progress multipart operation on hashedSet based on the object name.
|
||||||
func (s *erasureSets) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) error {
|
func (s *erasureSets) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) error {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
return set.AbortMultipartUpload(ctx, bucket, object, uploadID, opts)
|
return set.AbortMultipartUpload(ctx, bucket, object, uploadID, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompleteMultipartUpload - completes a pending multipart transaction, on hashedSet based on object name.
|
// CompleteMultipartUpload - completes a pending multipart transaction, on hashedSet based on object name.
|
||||||
func (s *erasureSets) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
func (s *erasureSets) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
||||||
set := s.getHashedSet(object)
|
set := s.getHashedSet(object)
|
||||||
auditObjectErasureSet(ctx, object, set, s.poolNumber)
|
auditObjectErasureSet(ctx, object, set)
|
||||||
opts.ParentIsObject = s.parentDirIsObject
|
opts.ParentIsObject = s.parentDirIsObject
|
||||||
return set.CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, opts)
|
return set.CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, opts)
|
||||||
}
|
}
|
||||||
@@ -1291,8 +1210,8 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
|||||||
res.Before.Drives = make([]madmin.HealDriveInfo, len(beforeDrives))
|
res.Before.Drives = make([]madmin.HealDriveInfo, len(beforeDrives))
|
||||||
// Copy "after" drive state too from before.
|
// Copy "after" drive state too from before.
|
||||||
for k, v := range beforeDrives {
|
for k, v := range beforeDrives {
|
||||||
res.Before.Drives[k] = madmin.HealDriveInfo(v)
|
res.Before.Drives[k] = v
|
||||||
res.After.Drives[k] = madmin.HealDriveInfo(v)
|
res.After.Drives[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if countErrs(sErrs, errUnformattedDisk) == 0 {
|
if countErrs(sErrs, errUnformattedDisk) == 0 {
|
||||||
@@ -1335,7 +1254,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
|||||||
if s.erasureDisks[m][n] != nil {
|
if s.erasureDisks[m][n] != nil {
|
||||||
s.erasureDisks[m][n].Close()
|
s.erasureDisks[m][n].Close()
|
||||||
}
|
}
|
||||||
|
storageDisks[index].SetDiskLoc(s.poolIndex, m, n)
|
||||||
s.erasureDisks[m][n] = storageDisks[index]
|
s.erasureDisks[m][n] = storageDisks[index]
|
||||||
s.endpointStrings[m*s.setDriveCount+n] = storageDisks[index].String()
|
s.endpointStrings[m*s.setDriveCount+n] = storageDisks[index].String()
|
||||||
}
|
}
|
||||||
@@ -1359,11 +1278,11 @@ func (s *erasureSets) HealBucket(ctx context.Context, bucket string, opts madmin
|
|||||||
SetCount: s.setCount,
|
SetCount: s.setCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range s.sets {
|
for _, set := range s.sets {
|
||||||
var healResult madmin.HealResultItem
|
var healResult madmin.HealResultItem
|
||||||
healResult, err = s.HealBucket(ctx, bucket, opts)
|
healResult, err = set.HealBucket(ctx, bucket, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, toObjectErr(err, bucket)
|
||||||
}
|
}
|
||||||
result.Before.Drives = append(result.Before.Drives, healResult.Before.Drives...)
|
result.Before.Drives = append(result.Before.Drives, healResult.Before.Drives...)
|
||||||
result.After.Drives = append(result.After.Drives, healResult.After.Drives...)
|
result.After.Drives = append(result.After.Drives, healResult.After.Drives...)
|
||||||
@@ -1384,13 +1303,13 @@ func (s *erasureSets) HealObject(ctx context.Context, bucket, object, versionID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectTags - replace or add tags to an existing object
|
// PutObjectTags - replace or add tags to an existing object
|
||||||
func (s *erasureSets) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) error {
|
func (s *erasureSets) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) (ObjectInfo, error) {
|
||||||
er := s.getHashedSet(object)
|
er := s.getHashedSet(object)
|
||||||
return er.PutObjectTags(ctx, bucket, object, tags, opts)
|
return er.PutObjectTags(ctx, bucket, object, tags, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObjectTags - delete object tags from an existing object
|
// DeleteObjectTags - delete object tags from an existing object
|
||||||
func (s *erasureSets) DeleteObjectTags(ctx context.Context, bucket, object string, opts ObjectOptions) error {
|
func (s *erasureSets) DeleteObjectTags(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
||||||
er := s.getHashedSet(object)
|
er := s.getHashedSet(object)
|
||||||
return er.DeleteObjectTags(ctx, bucket, object, opts)
|
return er.DeleteObjectTags(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|||||||