mirror of
https://github.com/pgsty/minio.git
synced 2026-08-15 11:13:16 +03:00
Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b6eadbd80 | |||
| 6f47414b23 | |||
| 224a27992a | |||
| 232544e1d8 | |||
| dbcb71828d | |||
| b9196757fd | |||
| b4ac53d157 | |||
| 4952bdb770 | |||
| 00b2ef2932 | |||
| 4536ecfaa4 | |||
| 43a7402968 | |||
| 330dca9a35 | |||
| ddd137d317 | |||
| 06ddd8770e | |||
| 16f8cf1c52 | |||
| 01e520eb23 | |||
| 02f770a0c0 | |||
| 2f4c79bc0f | |||
| 969ee7dfbe | |||
| 5f0b086b05 | |||
| 68b004a48f | |||
| 54ecce66f0 | |||
| 2b008c598b | |||
| 86d02b17cf | |||
| c1a95a70ac | |||
| f246c9053f | |||
| 9cdd204ae4 | |||
| 7b3eb9f7f8 | |||
| d56ef8dbe1 | |||
| a248ed5ff5 | |||
| 5bb31e4883 | |||
| aff2a76d80 | |||
| eddbe6bca2 | |||
| 734d1e320a | |||
| b8dab7b1a9 | |||
| abd6bf060d | |||
| f0d4ef604c | |||
| 2712f75762 | |||
| 4c46668da8 | |||
| 02e93fd6ba | |||
| 366876e98b | |||
| d202fdd022 | |||
| c07e5b49d4 | |||
| 9a39f8ad4d | |||
| 7e0c1c9413 | |||
| 485d833cd7 | |||
| e8a476ef5a | |||
| 267f0ecea2 | |||
| 4ee3434854 | |||
| 0e9854372e | |||
| b5177993b3 | |||
| 55f5c18fd9 | |||
| 8ce101c174 | |||
| 4972735507 | |||
| e6ca6de194 | |||
| cefc43e4da | |||
| 25e34fda5f | |||
| 4208d7af5a | |||
| 8d42f37e4b | |||
| 7cb4b5c636 | |||
| 1615920f48 | |||
| 7ee42b3ff5 | |||
| a6f1e727fb | |||
| c1fc7779ca | |||
| b3ab7546ee | |||
| ad88a81e3d | |||
| c4239ced22 | |||
| f85c28e960 | |||
| f7e176d4ca | |||
| 72a0d14195 | |||
| 6abe4128d7 | |||
| ed5ed7e490 | |||
| 51410c9023 | |||
| 96ca402dcd | |||
| 3da7c9cce3 | |||
| a14e19ec54 | |||
| e091dde041 |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Resiliency Functional Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
|
# updated.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [1.23.x]
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
check-latest: true
|
||||||
|
- name: Build on ${{ matrix.os }}
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
GO111MODULE: on
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
make test-resiliency
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -39,3 +39,4 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make verify
|
make verify
|
||||||
|
make test-timeout
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
||||||
# the tests.
|
# the tests.
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
ldap: ["", "localhost:389"]
|
ldap: ["", "localhost:389"]
|
||||||
etcd: ["", "http://localhost:2379"]
|
etcd: ["", "http://localhost:2379"]
|
||||||
openid: ["", "http://127.0.0.1:5556/dex"]
|
openid: ["", "http://127.0.0.1:5556/dex"]
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: setup-go-step
|
- name: setup-go-step
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.x
|
go-version: 1.23.x
|
||||||
|
|
||||||
- name: github sha short
|
- name: github sha short
|
||||||
id: vars
|
id: vars
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -40,6 +40,7 @@ jobs:
|
|||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-ilm
|
make test-ilm
|
||||||
|
make test-ilm-transition
|
||||||
|
|
||||||
- name: Test PBAC
|
- name: Test PBAC
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export ACCESS_KEY="$2"
|
|||||||
export SECRET_KEY="$3"
|
export SECRET_KEY="$3"
|
||||||
export JOB_NAME="$4"
|
export JOB_NAME="$4"
|
||||||
export MINT_MODE="full"
|
export MINT_MODE="full"
|
||||||
|
export MINT_NO_FULL_OBJECT="true"
|
||||||
|
|
||||||
docker system prune -f || true
|
docker system prune -f || true
|
||||||
docker volume prune -f || true
|
docker volume prune -f || true
|
||||||
@@ -15,6 +16,9 @@ docker volume rm $(docker volume ls -f dangling=true) || true
|
|||||||
## change working directory
|
## change working directory
|
||||||
cd .github/workflows/mint
|
cd .github/workflows/mint
|
||||||
|
|
||||||
|
## always pull latest
|
||||||
|
docker pull docker.io/minio/mint:edge
|
||||||
|
|
||||||
docker-compose -f minio-${MODE}.yaml up -d
|
docker-compose -f minio-${MODE}.yaml up -d
|
||||||
sleep 1m
|
sleep 1m
|
||||||
|
|
||||||
@@ -35,6 +39,7 @@ docker run --rm --net=mint_default \
|
|||||||
-e ACCESS_KEY="${ACCESS_KEY}" \
|
-e ACCESS_KEY="${ACCESS_KEY}" \
|
||||||
-e SECRET_KEY="${SECRET_KEY}" \
|
-e SECRET_KEY="${SECRET_KEY}" \
|
||||||
-e ENABLE_HTTPS=0 \
|
-e ENABLE_HTTPS=0 \
|
||||||
|
-e MINT_NO_FULL_OBJECT="${MINT_NO_FULL_OBJECT}" \
|
||||||
-e MINT_MODE="${MINT_MODE}" \
|
-e MINT_MODE="${MINT_MODE}" \
|
||||||
docker.io/minio/mint:edge
|
docker.io/minio/mint:edge
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.22.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.7
|
go-version: 1.23.2
|
||||||
- name: Get official govulncheck
|
- name: Get official govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22-alpine as build
|
FROM golang:1.23-alpine as build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22-alpine AS build
|
FROM golang:1.23-alpine AS build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22-alpine AS build
|
FROM golang:1.23-alpine AS build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22-alpine AS build
|
FROM golang:1.23-alpine AS build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ help: ## print this help
|
|||||||
getdeps: ## fetch necessary dependencies
|
getdeps: ## fetch necessary dependencies
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR)
|
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR)
|
||||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.10-0.20240227114326-6d6f813fff1b
|
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.2.5
|
||||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||||
|
|
||||||
crosscompile: ## cross compile minio
|
crosscompile: ## cross compile minio
|
||||||
@@ -60,6 +60,10 @@ test-ilm: install-race
|
|||||||
@echo "Running ILM tests"
|
@echo "Running ILM tests"
|
||||||
@env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh
|
@env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh
|
||||||
|
|
||||||
|
test-ilm-transition: install-race
|
||||||
|
@echo "Running ILM tiering tests with healing"
|
||||||
|
@env bash $(PWD)/docs/bucket/lifecycle/setup_ilm_transition.sh
|
||||||
|
|
||||||
test-pbac: install-race
|
test-pbac: install-race
|
||||||
@echo "Running bucket policies tests"
|
@echo "Running bucket policies tests"
|
||||||
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh
|
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh
|
||||||
@@ -145,6 +149,10 @@ test-multipart: install-race ## test multipart
|
|||||||
@echo "Test multipart behavior when part files are missing"
|
@echo "Test multipart behavior when part files are missing"
|
||||||
@(env bash $(PWD)/buildscripts/multipart-quorum-test.sh)
|
@(env bash $(PWD)/buildscripts/multipart-quorum-test.sh)
|
||||||
|
|
||||||
|
test-timeout: install-race ## test multipart
|
||||||
|
@echo "Test server timeout"
|
||||||
|
@(env bash $(PWD)/buildscripts/test-timeout.sh)
|
||||||
|
|
||||||
verify: install-race ## verify minio various setups
|
verify: install-race ## verify minio various setups
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||||
@@ -209,6 +217,10 @@ docker: build ## builds minio docker container
|
|||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
||||||
|
|
||||||
|
test-resiliency: build
|
||||||
|
@echo "Running resiliency tests"
|
||||||
|
@(DOCKER_COMPOSE_FILE=$(PWD)/docs/resiliency/docker-compose.yaml env bash $(PWD)/docs/resiliency/resiliency-tests.sh)
|
||||||
|
|
||||||
install-race: checks build-debugging ## builds minio to $(PWD)
|
install-race: checks build-debugging ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary with -race to './minio'"
|
@echo "Building minio binary with -race to './minio'"
|
||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue,dev -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue,dev -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
[](https://min.io)
|
[](https://min.io)
|
||||||
|
|
||||||
MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.
|
MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads. To learn more about what MinIO is doing for AI storage, go to [AI storage documentation](https://min.io/solutions/object-storage-for-ai).
|
||||||
|
|
||||||
This README provides quickstart instructions on running MinIO on bare metal hardware, including container-based installations. For Kubernetes environments, use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
|
This README provides quickstart instructions on running MinIO on bare metal hardware, including container-based installations. For Kubernetes environments, use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#
|
#
|
||||||
# This script assumes that LDAP server is at:
|
# This script assumes that LDAP server is at:
|
||||||
#
|
#
|
||||||
# `localhost:1389`
|
# `localhost:389`
|
||||||
#
|
#
|
||||||
# if this is not the case, set the environment variable
|
# if this is not the case, set the environment variable
|
||||||
# `_MINIO_LDAP_TEST_SERVER`.
|
# `_MINIO_LDAP_TEST_SERVER`.
|
||||||
@@ -41,7 +41,7 @@ __init__() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$_MINIO_LDAP_TEST_SERVER" ]; then
|
if [ -z "$_MINIO_LDAP_TEST_SERVER" ]; then
|
||||||
export _MINIO_LDAP_TEST_SERVER=localhost:1389
|
export _MINIO_LDAP_TEST_SERVER=localhost:389
|
||||||
echo "Using default LDAP endpoint: $_MINIO_LDAP_TEST_SERVER"
|
echo "Using default LDAP endpoint: $_MINIO_LDAP_TEST_SERVER"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ create_iam_content_in_old_minio() {
|
|||||||
mc alias set old-minio http://localhost:9000 minioadmin minioadmin
|
mc alias set old-minio http://localhost:9000 minioadmin minioadmin
|
||||||
mc ready old-minio
|
mc ready old-minio
|
||||||
mc idp ldap add old-minio \
|
mc idp ldap add old-minio \
|
||||||
server_addr=localhost:1389 \
|
server_addr=localhost:389 \
|
||||||
server_insecure=on \
|
server_insecure=on \
|
||||||
lookup_bind_dn=cn=admin,dc=min,dc=io \
|
lookup_bind_dn=cn=admin,dc=min,dc=io \
|
||||||
lookup_bind_password=admin \
|
lookup_bind_password=admin \
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -n "$TEST_DEBUG" ]; then
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
|
echo "minio executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap 'catch $LINENO' ERR
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
catch() {
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
echo "error on line $1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Cleaning up instances of MinIO"
|
||||||
|
pkill minio || true
|
||||||
|
pkill -9 minio || true
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
exit $#
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
catch
|
||||||
|
|
||||||
|
function gen_put_request() {
|
||||||
|
hdr_sleep=$1
|
||||||
|
body_sleep=$2
|
||||||
|
|
||||||
|
echo "PUT /testbucket/testobject HTTP/1.1"
|
||||||
|
sleep $hdr_sleep
|
||||||
|
echo "Host: foo-header"
|
||||||
|
echo "User-Agent: curl/8.2.1"
|
||||||
|
echo "Accept: */*"
|
||||||
|
echo "Content-Length: 30"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
sleep $body_sleep
|
||||||
|
echo "random line 0"
|
||||||
|
echo "random line 1"
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function send_put_object_request() {
|
||||||
|
hdr_timeout=$1
|
||||||
|
body_timeout=$2
|
||||||
|
|
||||||
|
start=$(date +%s)
|
||||||
|
timeout 5m bash -c "gen_put_request $hdr_timeout $body_timeout | netcat 127.0.0.1 $start_port | read" || return -1
|
||||||
|
[ $(($(date +%s) - start)) -gt $((srv_hdr_timeout + srv_idle_timeout + 1)) ] && return -1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_minio_with_timeout() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=minio
|
||||||
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
mkdir ${WORK_DIR}
|
||||||
|
C_PWD=${PWD}
|
||||||
|
if [ ! -x "$PWD/mc" ]; then
|
||||||
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
|
echo "failed to download https://github.com/minio/mc"
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
"${PWD}/mc" mb minio/testbucket
|
||||||
|
"${PWD}/mc" anonymous set public minio/testbucket
|
||||||
|
|
||||||
|
# slow header writing
|
||||||
|
send_put_object_request 20 0 && exit -1
|
||||||
|
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
|
||||||
|
|
||||||
|
# quick header write and slow bodywrite
|
||||||
|
send_put_object_request 0 40 && exit -1
|
||||||
|
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
|
||||||
|
|
||||||
|
# quick header and body write
|
||||||
|
send_put_object_request 1 1 || exit -1
|
||||||
|
"${PWD}/mc" stat minio/testbucket/testobject || exit -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
export start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
export srv_hdr_timeout=5
|
||||||
|
export srv_idle_timeout=5
|
||||||
|
export -f gen_put_request
|
||||||
|
|
||||||
|
test_minio_with_timeout ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
+35
-45
@@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
xldap "github.com/minio/pkg/v3/ldap"
|
xldap "github.com/minio/pkg/v3/ldap"
|
||||||
"github.com/minio/pkg/v3/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
@@ -65,6 +66,17 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This API only supports removal of internal users not service accounts.
|
||||||
|
ok, _, err = globalIAMSys.IsServiceAccount(accessKey)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// When the user is root credential you are not allowed to
|
// When the user is root credential you are not allowed to
|
||||||
// remove the root user. Also you cannot delete yourself.
|
// remove the root user. Also you cannot delete yourself.
|
||||||
if accessKey == globalActiveCred.AccessKey || accessKey == cred.AccessKey {
|
if accessKey == globalActiveCred.AccessKey || accessKey == cred.AccessKey {
|
||||||
@@ -1568,6 +1580,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTooManyPolicies), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTooManyPolicies), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setReqInfoPolicyName(ctx, name)
|
||||||
|
|
||||||
policyDoc, err := globalIAMSys.InfoPolicy(name)
|
policyDoc, err := globalIAMSys.InfoPolicy(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1671,6 +1684,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
policyName := vars["name"]
|
policyName := vars["name"]
|
||||||
|
setReqInfoPolicyName(ctx, policyName)
|
||||||
|
|
||||||
if err := globalIAMSys.DeletePolicy(ctx, policyName, true); err != nil {
|
if err := globalIAMSys.DeletePolicy(ctx, policyName, true); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -1703,6 +1717,13 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setReqInfoPolicyName(ctx, policyName)
|
||||||
|
|
||||||
|
// Reject policy names with commas.
|
||||||
|
if strings.Contains(policyName, ",") {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyInvalidName), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Error out if Content-Length is missing.
|
// Error out if Content-Length is missing.
|
||||||
if r.ContentLength <= 0 {
|
if r.ContentLength <= 0 {
|
||||||
@@ -1768,6 +1789,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
policyName := vars["policyName"]
|
policyName := vars["policyName"]
|
||||||
entityName := vars["userOrGroup"]
|
entityName := vars["userOrGroup"]
|
||||||
isGroup := vars["isGroup"] == "true"
|
isGroup := vars["isGroup"] == "true"
|
||||||
|
setReqInfoPolicyName(ctx, policyName)
|
||||||
|
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
ok, _, err := globalIAMSys.IsTempUser(entityName)
|
ok, _, err := globalIAMSys.IsTempUser(entityName)
|
||||||
@@ -1853,7 +1875,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx
|
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/policy-entities?policy=xxx&user=xxx&group=xxx
|
||||||
func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
@@ -1955,6 +1977,7 @@ func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *ht
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setReqInfoPolicyName(ctx, strings.Join(addedOrRemoved, ","))
|
||||||
|
|
||||||
respBody := madmin.PolicyAssociationResp{
|
respBody := madmin.PolicyAssociationResp{
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
@@ -2009,6 +2032,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Initialize a zip writer which will provide a zipped content
|
// Initialize a zip writer which will provide a zipped content
|
||||||
@@ -2231,17 +2255,13 @@ func (a adminAPIHandlers) ImportIAMV2(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiVer string) {
|
func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiVer string) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Validate signature, permissions and get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportIAMAction)
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
|
||||||
if s3Err != ErrNone {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data, err := io.ReadAll(r.Body)
|
data, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
@@ -2331,38 +2351,12 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
|
|
||||||
// Incoming access key matches parent user then we should
|
|
||||||
// reject password change requests.
|
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if accessKey has beginning and end space characters, this only applies to new users.
|
// Check if accessKey has beginning and end space characters, this only applies to new users.
|
||||||
if !exists && hasSpaceBE(accessKey) {
|
if !exists && hasSpaceBE(accessKey) {
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allUsersFile, accessKey), r.URL)
|
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allUsersFile, accessKey), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDenyOnly := false
|
|
||||||
if accessKey == cred.AccessKey {
|
|
||||||
// Check that there is no explicit deny - otherwise it's allowed
|
|
||||||
// to change one's own password.
|
|
||||||
checkDenyOnly = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
|
||||||
AccountName: cred.AccessKey,
|
|
||||||
Groups: cred.Groups,
|
|
||||||
Action: policy.CreateUserAdminAction,
|
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
|
||||||
IsOwner: owner,
|
|
||||||
Claims: cred.Claims,
|
|
||||||
DenyOnly: checkDenyOnly,
|
|
||||||
}) {
|
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allUsersFile, accessKey), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil {
|
if _, err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil {
|
||||||
failed.Users = append(failed.Users, madmin.IAMErrEntity{Name: accessKey, Error: err})
|
failed.Users = append(failed.Users, madmin.IAMErrEntity{Name: accessKey, Error: err})
|
||||||
} else {
|
} else {
|
||||||
@@ -2462,17 +2456,6 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
|
||||||
AccountName: cred.AccessKey,
|
|
||||||
Groups: cred.Groups,
|
|
||||||
Action: policy.CreateServiceAccountAdminAction,
|
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
|
||||||
IsOwner: owner,
|
|
||||||
Claims: cred.Claims,
|
|
||||||
}) {
|
|
||||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allSvcAcctsFile, user), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updateReq := true
|
updateReq := true
|
||||||
_, _, err = globalIAMSys.GetServiceAccount(ctx, svcAcctReq.AccessKey)
|
_, _, err = globalIAMSys.GetServiceAccount(ctx, svcAcctReq.AccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2801,3 +2784,10 @@ func commonAddServiceAccount(r *http.Request, ldap bool) (context.Context, auth.
|
|||||||
|
|
||||||
return ctx, cred, opts, createReq, targetUser, APIError{}
|
return ctx, cred, opts, createReq, targetUser, APIError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setReqInfoPolicyName will set the given policyName as a tag on the context's request info,
|
||||||
|
// so that it appears in audit logs.
|
||||||
|
func setReqInfoPolicyName(ctx context.Context, policyName string) {
|
||||||
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
|
reqInfo.SetTags("policyName", policyName)
|
||||||
|
}
|
||||||
|
|||||||
@@ -716,6 +716,12 @@ func (s *TestSuiteIAM) TestCannedPolicies(c *check) {
|
|||||||
c.Fatalf("policy info err: %v", err)
|
c.Fatalf("policy info err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that policy with comma is rejected.
|
||||||
|
err = s.adm.AddCannedPolicy(ctx, "invalid,policy", policyBytes)
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("invalid policy created successfully")
|
||||||
|
}
|
||||||
|
|
||||||
infoStr := string(info)
|
infoStr := string(info)
|
||||||
if !strings.Contains(infoStr, `"s3:PutObject"`) || !strings.Contains(infoStr, ":"+bucket+"/") {
|
if !strings.Contains(infoStr, `"s3:PutObject"`) || !strings.Contains(infoStr, ":"+bucket+"/") {
|
||||||
c.Fatalf("policy contains unexpected content!")
|
c.Fatalf("policy contains unexpected content!")
|
||||||
|
|||||||
+11
-19
@@ -806,18 +806,20 @@ func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
|
|||||||
return h.healMinioSysMeta(objAPI, minioConfigPrefix)()
|
return h.healMinioSysMeta(objAPI, minioConfigPrefix)()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
func (h *healSequence) healItems(objAPI ObjectLayer) error {
|
||||||
if h.clientToken == bgHealingUUID {
|
if h.clientToken == bgHealingUUID {
|
||||||
// For background heal do nothing.
|
// For background heal do nothing.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.healDiskMeta(objAPI); err != nil {
|
if h.bucket == "" { // heal internal meta only during a site-wide heal
|
||||||
return err
|
if err := h.healDiskMeta(objAPI); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal buckets and objects
|
// Heal buckets and objects
|
||||||
return h.healBuckets(objAPI, bucketsOnly)
|
return h.healBuckets(objAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// traverseAndHeal - traverses on-disk data and performs healing
|
// traverseAndHeal - traverses on-disk data and performs healing
|
||||||
@@ -828,8 +830,7 @@ func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
|||||||
// has to wait until a safe point is reached, such as between scanning
|
// has to wait until a safe point is reached, such as between scanning
|
||||||
// two objects.
|
// two objects.
|
||||||
func (h *healSequence) traverseAndHeal(objAPI ObjectLayer) {
|
func (h *healSequence) traverseAndHeal(objAPI ObjectLayer) {
|
||||||
bucketsOnly := false // Heals buckets and objects also.
|
h.traverseAndHealDoneCh <- h.healItems(objAPI)
|
||||||
h.traverseAndHealDoneCh <- h.healItems(objAPI, bucketsOnly)
|
|
||||||
xioutil.SafeClose(h.traverseAndHealDoneCh)
|
xioutil.SafeClose(h.traverseAndHealDoneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,6 +841,7 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
|
|||||||
// NOTE: Healing on meta is run regardless
|
// NOTE: Healing on meta is run regardless
|
||||||
// of any bucket being selected, this is to ensure that
|
// of any bucket being selected, this is to ensure that
|
||||||
// meta are always upto date and correct.
|
// meta are always upto date and correct.
|
||||||
|
h.settings.Recursive = true
|
||||||
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string, scanMode madmin.HealScanMode) error {
|
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string, scanMode madmin.HealScanMode) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
@@ -856,14 +858,14 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// healBuckets - check for all buckets heal or just particular bucket.
|
// healBuckets - check for all buckets heal or just particular bucket.
|
||||||
func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
|
func (h *healSequence) healBuckets(objAPI ObjectLayer) error {
|
||||||
if h.isQuitting() {
|
if h.isQuitting() {
|
||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. If a bucket was specified, heal only the bucket.
|
// 1. If a bucket was specified, heal only the bucket.
|
||||||
if h.bucket != "" {
|
if h.bucket != "" {
|
||||||
return h.healBucket(objAPI, h.bucket, bucketsOnly)
|
return h.healBucket(objAPI, h.bucket, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, err := objAPI.ListBuckets(h.ctx, BucketOptions{})
|
buckets, err := objAPI.ListBuckets(h.ctx, BucketOptions{})
|
||||||
@@ -877,7 +879,7 @@ func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
if err = h.healBucket(objAPI, bucket.Name, bucketsOnly); err != nil {
|
if err = h.healBucket(objAPI, bucket.Name, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -895,16 +897,6 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.settings.Recursive {
|
|
||||||
if h.object != "" {
|
|
||||||
if err := h.healObject(bucket, h.object, "", h.settings.ScanMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := objAPI.HealObjects(h.ctx, bucket, h.object, h.settings, h.healObject); err != nil {
|
if err := objAPI.HealObjects(h.ctx, bucket, h.object, h.settings, h.healObject); err != nil {
|
||||||
return errFnHealFromAPIErr(h.ctx, err)
|
return errFnHealFromAPIErr(h.ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ const (
|
|||||||
ErrPolicyAlreadyAttached
|
ErrPolicyAlreadyAttached
|
||||||
ErrPolicyNotAttached
|
ErrPolicyNotAttached
|
||||||
ErrExcessData
|
ErrExcessData
|
||||||
|
ErrPolicyInvalidName
|
||||||
// Add new error codes here.
|
// Add new error codes here.
|
||||||
|
|
||||||
// SSE-S3/SSE-KMS related API errors
|
// SSE-S3/SSE-KMS related API errors
|
||||||
@@ -561,6 +562,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "More data provided than indicated content length",
|
Description: "More data provided than indicated content length",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrPolicyInvalidName: {
|
||||||
|
Code: "PolicyInvalidName",
|
||||||
|
Description: "Policy name may not contain comma",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrPolicyTooLarge: {
|
ErrPolicyTooLarge: {
|
||||||
Code: "PolicyTooLarge",
|
Code: "PolicyTooLarge",
|
||||||
Description: "Policy exceeds the maximum allowed document size.",
|
Description: "Policy exceeds the maximum allowed document size.",
|
||||||
|
|||||||
@@ -593,8 +593,6 @@ func generateListVersionsResponse(ctx context.Context, bucket, prefix, marker, v
|
|||||||
for k, v := range cleanReservedKeys(object.UserDefined) {
|
for k, v := range cleanReservedKeys(object.UserDefined) {
|
||||||
content.UserMetadata.Set(k, v)
|
content.UserMetadata.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
|
|
||||||
content.Internal = &ObjectInternalInfo{
|
content.Internal = &ObjectInternalInfo{
|
||||||
K: object.DataBlocks,
|
K: object.DataBlocks,
|
||||||
M: object.ParityBlocks,
|
M: object.ParityBlocks,
|
||||||
@@ -729,7 +727,6 @@ func generateListObjectsV2Response(ctx context.Context, bucket, prefix, token, n
|
|||||||
for k, v := range cleanReservedKeys(object.UserDefined) {
|
for k, v := range cleanReservedKeys(object.UserDefined) {
|
||||||
content.UserMetadata.Set(k, v)
|
content.UserMetadata.Set(k, v)
|
||||||
}
|
}
|
||||||
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
|
|
||||||
content.Internal = &ObjectInternalInfo{
|
content.Internal = &ObjectInternalInfo{
|
||||||
K: object.DataBlocks,
|
K: object.DataBlocks,
|
||||||
M: object.ParityBlocks,
|
M: object.ParityBlocks,
|
||||||
|
|||||||
+200
-199
File diff suppressed because one or more lines are too long
@@ -223,9 +223,6 @@ func (h *healingTracker) updateProgress(success, skipped bool, bytes uint64) {
|
|||||||
// update will update the tracker on the disk.
|
// update will update the tracker on the disk.
|
||||||
// If the tracker has been deleted an error is returned.
|
// If the tracker has been deleted an error is returned.
|
||||||
func (h *healingTracker) update(ctx context.Context) error {
|
func (h *healingTracker) update(ctx context.Context) error {
|
||||||
if h.disk.Healing() == nil {
|
|
||||||
return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID)
|
|
||||||
}
|
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
||||||
h.ID, _ = h.disk.GetDiskID()
|
h.ID, _ = h.disk.GetDiskID()
|
||||||
|
|||||||
@@ -301,12 +301,12 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.Before(oi.ModTime) {
|
if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.After(oi.ModTime) {
|
||||||
// skip all objects that are created before the specified time.
|
// skip all objects that are created before the specified time.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.After(oi.ModTime) {
|
if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.Before(oi.ModTime) {
|
||||||
// skip all objects that are created after the specified time.
|
// skip all objects that are created after the specified time.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -1047,12 +1047,12 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.Before(info.ModTime) {
|
if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.After(info.ModTime) {
|
||||||
// skip all objects that are created before the specified time.
|
// skip all objects that are created before the specified time.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.After(info.ModTime) {
|
if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.Before(info.ModTime) {
|
||||||
// skip all objects that are created after the specified time.
|
// skip all objects that are created after the specified time.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -37,12 +38,22 @@ type streamingBitrotWriter struct {
|
|||||||
shardSize int64
|
shardSize int64
|
||||||
canClose *sync.WaitGroup
|
canClose *sync.WaitGroup
|
||||||
byteBuf []byte
|
byteBuf []byte
|
||||||
|
finished bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
|
||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
if b.finished {
|
||||||
|
return 0, errors.New("bitrot write not allowed")
|
||||||
|
}
|
||||||
|
if int64(len(p)) > b.shardSize {
|
||||||
|
return 0, errors.New("unexpected bitrot buffer size")
|
||||||
|
}
|
||||||
|
if int64(len(p)) < b.shardSize {
|
||||||
|
b.finished = true
|
||||||
|
}
|
||||||
b.h.Reset()
|
b.h.Reset()
|
||||||
b.h.Write(p)
|
b.h.Write(p)
|
||||||
hashBytes := b.h.Sum(nil)
|
hashBytes := b.h.Sum(nil)
|
||||||
|
|||||||
@@ -1338,6 +1338,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
||||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -1809,6 +1810,10 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Audit log tags.
|
||||||
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
|
reqInfo.SetTags("retention", config.String())
|
||||||
|
|
||||||
configData, err := xml.Marshal(config)
|
configData, err := xml.Marshal(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
|||||||
+12
-7
@@ -71,7 +71,7 @@ func NewLifecycleSys() *LifecycleSys {
|
|||||||
return &LifecycleSys{}
|
return &LifecycleSys{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string, metadata map[string]string) madmin.TraceInfo {
|
func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string, metadata map[string]string, err string) madmin.TraceInfo {
|
||||||
sz, _ := oi.GetActualSize()
|
sz, _ := oi.GetActualSize()
|
||||||
return madmin.TraceInfo{
|
return madmin.TraceInfo{
|
||||||
TraceType: madmin.TraceILM,
|
TraceType: madmin.TraceILM,
|
||||||
@@ -81,18 +81,22 @@ func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event
|
|||||||
Duration: duration,
|
Duration: duration,
|
||||||
Path: pathJoin(oi.Bucket, oi.Name),
|
Path: pathJoin(oi.Bucket, oi.Name),
|
||||||
Bytes: sz,
|
Bytes: sz,
|
||||||
Error: "",
|
Error: err,
|
||||||
Message: getSource(4),
|
Message: getSource(4),
|
||||||
Custom: metadata,
|
Custom: metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *LifecycleSys) trace(oi ObjectInfo) func(event string, metadata map[string]string) {
|
func (sys *LifecycleSys) trace(oi ObjectInfo) func(event string, metadata map[string]string, err error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
return func(event string, metadata map[string]string) {
|
return func(event string, metadata map[string]string, err error) {
|
||||||
duration := time.Since(startTime)
|
duration := time.Since(startTime)
|
||||||
if globalTrace.NumSubscribers(madmin.TraceILM) > 0 {
|
if globalTrace.NumSubscribers(madmin.TraceILM) > 0 {
|
||||||
globalTrace.Publish(ilmTrace(startTime, duration, oi, event, metadata))
|
e := ""
|
||||||
|
if err != nil {
|
||||||
|
e = err.Error()
|
||||||
|
}
|
||||||
|
globalTrace.Publish(ilmTrace(startTime, duration, oi, event, metadata, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,7 +352,7 @@ func (es *expiryState) Worker(input <-chan expiryOp) {
|
|||||||
traceFn := globalLifecycleSys.trace(oi)
|
traceFn := globalLifecycleSys.trace(oi)
|
||||||
if !oi.TransitionedObject.FreeVersion {
|
if !oi.TransitionedObject.FreeVersion {
|
||||||
// nothing to be done
|
// nothing to be done
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoreNotFoundErr := func(err error) error {
|
ignoreNotFoundErr := func(err error) error {
|
||||||
@@ -362,7 +366,8 @@ func (es *expiryState) Worker(input <-chan expiryOp) {
|
|||||||
err := deleteObjectFromRemoteTier(es.ctx, oi.TransitionedObject.Name, oi.TransitionedObject.VersionID, oi.TransitionedObject.Tier)
|
err := deleteObjectFromRemoteTier(es.ctx, oi.TransitionedObject.Name, oi.TransitionedObject.VersionID, oi.TransitionedObject.Tier)
|
||||||
if ignoreNotFoundErr(err) != nil {
|
if ignoreNotFoundErr(err) != nil {
|
||||||
transitionLogIf(es.ctx, err)
|
transitionLogIf(es.ctx, err)
|
||||||
return
|
traceFn(ILMFreeVersionDelete, nil, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove this free version
|
// Remove this free version
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
|||||||
|
|
||||||
func proxyRequestByToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token string) (string, bool) {
|
func proxyRequestByToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token string) (string, bool) {
|
||||||
subToken, nodeIndex := parseRequestToken(token)
|
subToken, nodeIndex := parseRequestToken(token)
|
||||||
if nodeIndex > 0 {
|
if nodeIndex >= 0 {
|
||||||
return subToken, proxyRequestByNodeIndex(ctx, w, r, nodeIndex)
|
return subToken, proxyRequestByNodeIndex(ctx, w, r, nodeIndex)
|
||||||
}
|
}
|
||||||
return subToken, false
|
return subToken, false
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ import (
|
|||||||
"github.com/minio/minio/internal/once"
|
"github.com/minio/minio/internal/once"
|
||||||
"github.com/tinylib/msgp/msgp"
|
"github.com/tinylib/msgp/msgp"
|
||||||
"github.com/zeebo/xxh3"
|
"github.com/zeebo/xxh3"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -780,8 +778,9 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
|
|||||||
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
|
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
|
||||||
|
|
||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
|
_, isValidSSEHeader := validSSEReplicationHeaders[k]
|
||||||
// In case of SSE-C objects copy the allowed internal headers as well
|
// In case of SSE-C objects copy the allowed internal headers as well
|
||||||
if !isSSEC || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
if !isSSEC || !isValidSSEHeader {
|
||||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -789,7 +788,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
if isValidSSEHeader {
|
||||||
meta[validSSEReplicationHeaders[k]] = v
|
meta[validSSEReplicationHeaders[k]] = v
|
||||||
} else {
|
} else {
|
||||||
meta[k] = v
|
meta[k] = v
|
||||||
|
|||||||
+7
-7
@@ -108,18 +108,18 @@ func (l ReplicationLastHour) merge(o ReplicationLastHour) (merged ReplicationLas
|
|||||||
|
|
||||||
// Add a new duration data
|
// Add a new duration data
|
||||||
func (l *ReplicationLastHour) addsize(sz int64) {
|
func (l *ReplicationLastHour) addsize(sz int64) {
|
||||||
min := time.Now().Unix() / 60
|
minutes := time.Now().Unix() / 60
|
||||||
l.forwardTo(min)
|
l.forwardTo(minutes)
|
||||||
winIdx := min % 60
|
winIdx := minutes % 60
|
||||||
l.Totals[winIdx].merge(AccElem{Total: min, Size: sz, N: 1})
|
l.Totals[winIdx].merge(AccElem{Total: minutes, Size: sz, N: 1})
|
||||||
l.LastMin = min
|
l.LastMin = minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge all recorded counts of last hour into one
|
// Merge all recorded counts of last hour into one
|
||||||
func (l *ReplicationLastHour) getTotal() AccElem {
|
func (l *ReplicationLastHour) getTotal() AccElem {
|
||||||
var res AccElem
|
var res AccElem
|
||||||
min := time.Now().Unix() / 60
|
minutes := time.Now().Unix() / 60
|
||||||
l.forwardTo(min)
|
l.forwardTo(minutes)
|
||||||
for _, elem := range l.Totals[:] {
|
for _, elem := range l.Totals[:] {
|
||||||
res.merge(elem)
|
res.merge(elem)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -1325,10 +1325,13 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
|||||||
dobj, err = objLayer.DeleteObject(ctx, obj.Bucket, encodeDirObject(obj.Name), opts)
|
dobj, err = objLayer.DeleteObject(ctx, obj.Bucket, encodeDirObject(obj.Name), opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
|
traceFn(ILMExpiry, nil, nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Assume it is still there.
|
// Assume it is still there.
|
||||||
ilmLogOnceIf(ctx, fmt.Errorf("DeleteObject(%s, %s): %w", obj.Bucket, obj.Name, err), "non-transition-expiry"+obj.Name)
|
err := fmt.Errorf("DeleteObject(%s, %s): %w", obj.Bucket, obj.Name, err)
|
||||||
|
ilmLogOnceIf(ctx, err, "non-transition-expiry"+obj.Name)
|
||||||
|
traceFn(ILMExpiry, nil, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if dobj.Name == "" {
|
if dobj.Name == "" {
|
||||||
@@ -1549,7 +1552,7 @@ const (
|
|||||||
ILMTransition = " ilm:transition"
|
ILMTransition = " ilm:transition"
|
||||||
)
|
)
|
||||||
|
|
||||||
func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string, tags map[string]string, traceFn func(event string, metadata map[string]string)) {
|
func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string, tags map[string]string, traceFn func(event string, metadata map[string]string, err error)) {
|
||||||
var apiName string
|
var apiName string
|
||||||
switch event {
|
switch event {
|
||||||
case ILMExpiry:
|
case ILMExpiry:
|
||||||
@@ -1567,5 +1570,5 @@ func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string, tags ma
|
|||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
})
|
})
|
||||||
traceFn(event, tags)
|
traceFn(event, tags, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import (
|
|||||||
"github.com/valyala/bytebufferpool"
|
"github.com/valyala/bytebufferpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//msgp:clearomitted
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE -unexported
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
|
|
||||||
// dataUsageHash is the hash type used.
|
// dataUsageHash is the hash type used.
|
||||||
|
|||||||
+591
-161
@@ -1633,6 +1633,8 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, err = dc.ReadMapKeyPtr()
|
field, err = dc.ReadMapKeyPtr()
|
||||||
@@ -1719,12 +1721,95 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
if z.AllTierStats == nil {
|
if z.AllTierStats == nil {
|
||||||
z.AllTierStats = new(allTierStats)
|
z.AllTierStats = new(allTierStats)
|
||||||
}
|
}
|
||||||
err = z.AllTierStats.DecodeMsg(dc)
|
var zb0004 uint32
|
||||||
|
zb0004, err = dc.ReadMapHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "AllTierStats")
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for zb0004 > 0 {
|
||||||
|
zb0004--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
var zb0005 uint32
|
||||||
|
zb0005, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.AllTierStats.Tiers == nil {
|
||||||
|
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
|
||||||
|
} else if len(z.AllTierStats.Tiers) > 0 {
|
||||||
|
for key := range z.AllTierStats.Tiers {
|
||||||
|
delete(z.AllTierStats.Tiers, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0005 > 0 {
|
||||||
|
zb0005--
|
||||||
|
var za0003 string
|
||||||
|
var za0004 tierStats
|
||||||
|
za0003, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0006 uint32
|
||||||
|
zb0006, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0006 > 0 {
|
||||||
|
zb0006--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
za0004.TotalSize, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "nv":
|
||||||
|
za0004.NumVersions, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "no":
|
||||||
|
za0004.NumObjects, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.AllTierStats.Tiers[za0003] = za0004
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "c":
|
case "c":
|
||||||
z.Compacted, err = dc.ReadBool()
|
z.Compacted, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1739,6 +1824,11 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.AllTierStats = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1757,121 +1847,166 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// write "ch"
|
// write "ch"
|
||||||
err = en.Append(0xa2, 0x63, 0x68)
|
err = en.Append(0xa2, 0x63, 0x68)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = z.Children.EncodeMsg(en)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Children")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "sz"
|
|
||||||
err = en.Append(0xa2, 0x73, 0x7a)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteInt64(z.Size)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Size")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "os"
|
|
||||||
err = en.Append(0xa2, 0x6f, 0x73)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteUint64(z.Objects)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Objects")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "vs"
|
|
||||||
err = en.Append(0xa2, 0x76, 0x73)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteUint64(z.Versions)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Versions")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "dms"
|
|
||||||
err = en.Append(0xa3, 0x64, 0x6d, 0x73)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteUint64(z.DeleteMarkers)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "DeleteMarkers")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "szs"
|
|
||||||
err = en.Append(0xa3, 0x73, 0x7a, 0x73)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteArrayHeader(uint32(dataUsageBucketLen))
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "ObjSizes")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for za0001 := range z.ObjSizes {
|
|
||||||
err = en.WriteUint64(z.ObjSizes[za0001])
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "ObjSizes", za0001)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// write "vh"
|
|
||||||
err = en.Append(0xa2, 0x76, 0x68)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteArrayHeader(uint32(dataUsageVersionLen))
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "ObjVersions")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for za0002 := range z.ObjVersions {
|
|
||||||
err = en.WriteUint64(z.ObjVersions[za0002])
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "ObjVersions", za0002)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
|
||||||
// write "ats"
|
|
||||||
err = en.Append(0xa3, 0x61, 0x74, 0x73)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if z.AllTierStats == nil {
|
err = z.Children.EncodeMsg(en)
|
||||||
err = en.WriteNil()
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Children")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "sz"
|
||||||
|
err = en.Append(0xa2, 0x73, 0x7a)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.Size)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "os"
|
||||||
|
err = en.Append(0xa2, 0x6f, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.Objects)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "vs"
|
||||||
|
err = en.Append(0xa2, 0x76, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.Versions)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Versions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "dms"
|
||||||
|
err = en.Append(0xa3, 0x64, 0x6d, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.DeleteMarkers)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DeleteMarkers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "szs"
|
||||||
|
err = en.Append(0xa3, 0x73, 0x7a, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(dataUsageBucketLen))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjSizes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.ObjSizes {
|
||||||
|
err = en.WriteUint64(z.ObjSizes[za0001])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "ObjSizes", za0001)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = z.AllTierStats.EncodeMsg(en)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "AllTierStats")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// write "vh"
|
||||||
// write "c"
|
err = en.Append(0xa2, 0x76, 0x68)
|
||||||
err = en.Append(0xa1, 0x63)
|
if err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return
|
}
|
||||||
}
|
err = en.WriteArrayHeader(uint32(dataUsageVersionLen))
|
||||||
err = en.WriteBool(z.Compacted)
|
if err != nil {
|
||||||
if err != nil {
|
err = msgp.WrapError(err, "ObjVersions")
|
||||||
err = msgp.WrapError(err, "Compacted")
|
return
|
||||||
return
|
}
|
||||||
|
for za0002 := range z.ObjVersions {
|
||||||
|
err = en.WriteUint64(z.ObjVersions[za0002])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjVersions", za0002)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
||||||
|
// write "ats"
|
||||||
|
err = en.Append(0xa3, 0x61, 0x74, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.AllTierStats == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// map header, size 1
|
||||||
|
// write "ts"
|
||||||
|
err = en.Append(0x81, 0xa2, 0x74, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteMapHeader(uint32(len(z.AllTierStats.Tiers)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0003, za0004 := range z.AllTierStats.Tiers {
|
||||||
|
err = en.WriteString(za0003)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// map header, size 3
|
||||||
|
// write "ts"
|
||||||
|
err = en.Append(0x83, 0xa2, 0x74, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(za0004.TotalSize)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "nv"
|
||||||
|
err = en.Append(0xa2, 0x6e, 0x76)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(za0004.NumVersions)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "no"
|
||||||
|
err = en.Append(0xa2, 0x6e, 0x6f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(za0004.NumObjects)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "c"
|
||||||
|
err = en.Append(0xa1, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.Compacted)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Compacted")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1889,56 +2024,69 @@ func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
// variable map header, size zb0001Len
|
// variable map header, size zb0001Len
|
||||||
o = append(o, 0x80|uint8(zb0001Len))
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// string "ch"
|
// string "ch"
|
||||||
o = append(o, 0xa2, 0x63, 0x68)
|
o = append(o, 0xa2, 0x63, 0x68)
|
||||||
o, err = z.Children.MarshalMsg(o)
|
o, err = z.Children.MarshalMsg(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Children")
|
err = msgp.WrapError(err, "Children")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// string "sz"
|
// string "sz"
|
||||||
o = append(o, 0xa2, 0x73, 0x7a)
|
o = append(o, 0xa2, 0x73, 0x7a)
|
||||||
o = msgp.AppendInt64(o, z.Size)
|
o = msgp.AppendInt64(o, z.Size)
|
||||||
// string "os"
|
// string "os"
|
||||||
o = append(o, 0xa2, 0x6f, 0x73)
|
o = append(o, 0xa2, 0x6f, 0x73)
|
||||||
o = msgp.AppendUint64(o, z.Objects)
|
o = msgp.AppendUint64(o, z.Objects)
|
||||||
// string "vs"
|
// string "vs"
|
||||||
o = append(o, 0xa2, 0x76, 0x73)
|
o = append(o, 0xa2, 0x76, 0x73)
|
||||||
o = msgp.AppendUint64(o, z.Versions)
|
o = msgp.AppendUint64(o, z.Versions)
|
||||||
// string "dms"
|
// string "dms"
|
||||||
o = append(o, 0xa3, 0x64, 0x6d, 0x73)
|
o = append(o, 0xa3, 0x64, 0x6d, 0x73)
|
||||||
o = msgp.AppendUint64(o, z.DeleteMarkers)
|
o = msgp.AppendUint64(o, z.DeleteMarkers)
|
||||||
// string "szs"
|
// string "szs"
|
||||||
o = append(o, 0xa3, 0x73, 0x7a, 0x73)
|
o = append(o, 0xa3, 0x73, 0x7a, 0x73)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
|
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
|
||||||
for za0001 := range z.ObjSizes {
|
for za0001 := range z.ObjSizes {
|
||||||
o = msgp.AppendUint64(o, z.ObjSizes[za0001])
|
o = msgp.AppendUint64(o, z.ObjSizes[za0001])
|
||||||
}
|
}
|
||||||
// string "vh"
|
// string "vh"
|
||||||
o = append(o, 0xa2, 0x76, 0x68)
|
o = append(o, 0xa2, 0x76, 0x68)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(dataUsageVersionLen))
|
o = msgp.AppendArrayHeader(o, uint32(dataUsageVersionLen))
|
||||||
for za0002 := range z.ObjVersions {
|
for za0002 := range z.ObjVersions {
|
||||||
o = msgp.AppendUint64(o, z.ObjVersions[za0002])
|
o = msgp.AppendUint64(o, z.ObjVersions[za0002])
|
||||||
}
|
}
|
||||||
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
||||||
// string "ats"
|
// string "ats"
|
||||||
o = append(o, 0xa3, 0x61, 0x74, 0x73)
|
o = append(o, 0xa3, 0x61, 0x74, 0x73)
|
||||||
if z.AllTierStats == nil {
|
if z.AllTierStats == nil {
|
||||||
o = msgp.AppendNil(o)
|
o = msgp.AppendNil(o)
|
||||||
} else {
|
} else {
|
||||||
o, err = z.AllTierStats.MarshalMsg(o)
|
// map header, size 1
|
||||||
if err != nil {
|
// string "ts"
|
||||||
err = msgp.WrapError(err, "AllTierStats")
|
o = append(o, 0x81, 0xa2, 0x74, 0x73)
|
||||||
return
|
o = msgp.AppendMapHeader(o, uint32(len(z.AllTierStats.Tiers)))
|
||||||
|
for za0003, za0004 := range z.AllTierStats.Tiers {
|
||||||
|
o = msgp.AppendString(o, za0003)
|
||||||
|
// map header, size 3
|
||||||
|
// string "ts"
|
||||||
|
o = append(o, 0x83, 0xa2, 0x74, 0x73)
|
||||||
|
o = msgp.AppendUint64(o, za0004.TotalSize)
|
||||||
|
// string "nv"
|
||||||
|
o = append(o, 0xa2, 0x6e, 0x76)
|
||||||
|
o = msgp.AppendInt(o, za0004.NumVersions)
|
||||||
|
// string "no"
|
||||||
|
o = append(o, 0xa2, 0x6e, 0x6f)
|
||||||
|
o = msgp.AppendInt(o, za0004.NumObjects)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// string "c"
|
||||||
|
o = append(o, 0xa1, 0x63)
|
||||||
|
o = msgp.AppendBool(o, z.Compacted)
|
||||||
}
|
}
|
||||||
// string "c"
|
|
||||||
o = append(o, 0xa1, 0x63)
|
|
||||||
o = msgp.AppendBool(o, z.Compacted)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1952,6 +2100,8 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
@@ -2037,12 +2187,95 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
if z.AllTierStats == nil {
|
if z.AllTierStats == nil {
|
||||||
z.AllTierStats = new(allTierStats)
|
z.AllTierStats = new(allTierStats)
|
||||||
}
|
}
|
||||||
bts, err = z.AllTierStats.UnmarshalMsg(bts)
|
var zb0004 uint32
|
||||||
|
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "AllTierStats")
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for zb0004 > 0 {
|
||||||
|
zb0004--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
var zb0005 uint32
|
||||||
|
zb0005, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.AllTierStats.Tiers == nil {
|
||||||
|
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
|
||||||
|
} else if len(z.AllTierStats.Tiers) > 0 {
|
||||||
|
for key := range z.AllTierStats.Tiers {
|
||||||
|
delete(z.AllTierStats.Tiers, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0005 > 0 {
|
||||||
|
var za0003 string
|
||||||
|
var za0004 tierStats
|
||||||
|
zb0005--
|
||||||
|
za0003, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0006 uint32
|
||||||
|
zb0006, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0006 > 0 {
|
||||||
|
zb0006--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
za0004.TotalSize, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "nv":
|
||||||
|
za0004.NumVersions, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "no":
|
||||||
|
za0004.NumObjects, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.AllTierStats.Tiers[za0003] = za0004
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "c":
|
case "c":
|
||||||
z.Compacted, bts, err = msgp.ReadBoolBytes(bts)
|
z.Compacted, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2057,6 +2290,11 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.AllTierStats = nil
|
||||||
|
}
|
||||||
|
|
||||||
o = bts
|
o = bts
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2067,7 +2305,13 @@ func (z *dataUsageEntry) Msgsize() (s int) {
|
|||||||
if z.AllTierStats == nil {
|
if z.AllTierStats == nil {
|
||||||
s += msgp.NilSize
|
s += msgp.NilSize
|
||||||
} else {
|
} else {
|
||||||
s += z.AllTierStats.Msgsize()
|
s += 1 + 3 + msgp.MapHeaderSize
|
||||||
|
if z.AllTierStats.Tiers != nil {
|
||||||
|
for za0003, za0004 := range z.AllTierStats.Tiers {
|
||||||
|
_ = za0004
|
||||||
|
s += msgp.StringPrefixSize + len(za0003) + 1 + 3 + msgp.Uint64Size + 3 + msgp.IntSize + 3 + msgp.IntSize
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s += 2 + msgp.BoolSize
|
s += 2 + msgp.BoolSize
|
||||||
return
|
return
|
||||||
@@ -2628,6 +2872,8 @@ func (z *dataUsageEntryV7) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, err = dc.ReadMapKeyPtr()
|
field, err = dc.ReadMapKeyPtr()
|
||||||
@@ -2714,12 +2960,95 @@ func (z *dataUsageEntryV7) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
if z.AllTierStats == nil {
|
if z.AllTierStats == nil {
|
||||||
z.AllTierStats = new(allTierStats)
|
z.AllTierStats = new(allTierStats)
|
||||||
}
|
}
|
||||||
err = z.AllTierStats.DecodeMsg(dc)
|
var zb0004 uint32
|
||||||
|
zb0004, err = dc.ReadMapHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "AllTierStats")
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for zb0004 > 0 {
|
||||||
|
zb0004--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
var zb0005 uint32
|
||||||
|
zb0005, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.AllTierStats.Tiers == nil {
|
||||||
|
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
|
||||||
|
} else if len(z.AllTierStats.Tiers) > 0 {
|
||||||
|
for key := range z.AllTierStats.Tiers {
|
||||||
|
delete(z.AllTierStats.Tiers, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0005 > 0 {
|
||||||
|
zb0005--
|
||||||
|
var za0003 string
|
||||||
|
var za0004 tierStats
|
||||||
|
za0003, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0006 uint32
|
||||||
|
zb0006, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0006 > 0 {
|
||||||
|
zb0006--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
za0004.TotalSize, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "nv":
|
||||||
|
za0004.NumVersions, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "no":
|
||||||
|
za0004.NumObjects, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.AllTierStats.Tiers[za0003] = za0004
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "c":
|
case "c":
|
||||||
z.Compacted, err = dc.ReadBool()
|
z.Compacted, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2734,6 +3063,11 @@ func (z *dataUsageEntryV7) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.AllTierStats = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2747,6 +3081,8 @@ func (z *dataUsageEntryV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
@@ -2832,12 +3168,95 @@ func (z *dataUsageEntryV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
if z.AllTierStats == nil {
|
if z.AllTierStats == nil {
|
||||||
z.AllTierStats = new(allTierStats)
|
z.AllTierStats = new(allTierStats)
|
||||||
}
|
}
|
||||||
bts, err = z.AllTierStats.UnmarshalMsg(bts)
|
var zb0004 uint32
|
||||||
|
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "AllTierStats")
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for zb0004 > 0 {
|
||||||
|
zb0004--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
var zb0005 uint32
|
||||||
|
zb0005, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.AllTierStats.Tiers == nil {
|
||||||
|
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
|
||||||
|
} else if len(z.AllTierStats.Tiers) > 0 {
|
||||||
|
for key := range z.AllTierStats.Tiers {
|
||||||
|
delete(z.AllTierStats.Tiers, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for zb0005 > 0 {
|
||||||
|
var za0003 string
|
||||||
|
var za0004 tierStats
|
||||||
|
zb0005--
|
||||||
|
za0003, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var zb0006 uint32
|
||||||
|
zb0006, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0006 > 0 {
|
||||||
|
zb0006--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ts":
|
||||||
|
za0004.TotalSize, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "nv":
|
||||||
|
za0004.NumVersions, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "no":
|
||||||
|
za0004.NumObjects, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z.AllTierStats.Tiers[za0003] = za0004
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "AllTierStats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "c":
|
case "c":
|
||||||
z.Compacted, bts, err = msgp.ReadBoolBytes(bts)
|
z.Compacted, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2852,6 +3271,11 @@ func (z *dataUsageEntryV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.AllTierStats = nil
|
||||||
|
}
|
||||||
|
|
||||||
o = bts
|
o = bts
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2862,7 +3286,13 @@ func (z *dataUsageEntryV7) Msgsize() (s int) {
|
|||||||
if z.AllTierStats == nil {
|
if z.AllTierStats == nil {
|
||||||
s += msgp.NilSize
|
s += msgp.NilSize
|
||||||
} else {
|
} else {
|
||||||
s += z.AllTierStats.Msgsize()
|
s += 1 + 3 + msgp.MapHeaderSize
|
||||||
|
if z.AllTierStats.Tiers != nil {
|
||||||
|
for za0003, za0004 := range z.AllTierStats.Tiers {
|
||||||
|
_ = za0004
|
||||||
|
s += msgp.StringPrefixSize + len(za0003) + 1 + 3 + msgp.Uint64Size + 3 + msgp.IntSize + 3 + msgp.IntSize
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s += 2 + msgp.BoolSize
|
s += 2 + msgp.BoolSize
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -117,12 +117,12 @@ func (dt *dynamicTimeout) logEntry(duration time.Duration) {
|
|||||||
// adjust changes the value of the dynamic timeout based on the
|
// adjust changes the value of the dynamic timeout based on the
|
||||||
// previous results
|
// previous results
|
||||||
func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) {
|
func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) {
|
||||||
failures, max := 0, time.Duration(0)
|
failures, maxDur := 0, time.Duration(0)
|
||||||
for _, dur := range entries[:] {
|
for _, dur := range entries[:] {
|
||||||
if dur == maxDuration {
|
if dur == maxDuration {
|
||||||
failures++
|
failures++
|
||||||
} else if dur > max {
|
} else if dur > maxDur {
|
||||||
max = dur
|
maxDur = dur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,12 +144,12 @@ func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) {
|
|||||||
} else if failPct < dynamicTimeoutDecreaseThresholdPct {
|
} else if failPct < dynamicTimeoutDecreaseThresholdPct {
|
||||||
// We are hitting the timeout relatively few times,
|
// We are hitting the timeout relatively few times,
|
||||||
// so decrease the timeout towards 25 % of maximum time spent.
|
// so decrease the timeout towards 25 % of maximum time spent.
|
||||||
max = max * 125 / 100
|
maxDur = maxDur * 125 / 100
|
||||||
|
|
||||||
timeout := atomic.LoadInt64(&dt.timeout)
|
timeout := atomic.LoadInt64(&dt.timeout)
|
||||||
if max < time.Duration(timeout) {
|
if maxDur < time.Duration(timeout) {
|
||||||
// Move 50% toward the max.
|
// Move 50% toward the max.
|
||||||
timeout = (int64(max) + timeout) / 2
|
timeout = (int64(maxDur) + timeout) / 2
|
||||||
}
|
}
|
||||||
if timeout < dt.minimum {
|
if timeout < dt.minimum {
|
||||||
timeout = dt.minimum
|
timeout = dt.minimum
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func kmsKeyIDFromMetadata(metadata map[string]string) string {
|
|||||||
// be AWS S3 compliant.
|
// be AWS S3 compliant.
|
||||||
//
|
//
|
||||||
// DecryptETags uses a KMS bulk decryption API, if available, which
|
// DecryptETags uses a KMS bulk decryption API, if available, which
|
||||||
// is more efficient than decrypting ETags sequentually.
|
// is more efficient than decrypting ETags sequentially.
|
||||||
func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
|
func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
|
||||||
const BatchSize = 250 // We process the objects in batches - 250 is a reasonable default.
|
const BatchSize = 250 // We process the objects in batches - 250 is a reasonable default.
|
||||||
var (
|
var (
|
||||||
|
|||||||
+1
-1
@@ -26,6 +26,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -38,7 +39,6 @@ import (
|
|||||||
"github.com/minio/minio/internal/mountinfo"
|
"github.com/minio/minio/internal/mountinfo"
|
||||||
"github.com/minio/pkg/v3/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
xnet "github.com/minio/pkg/v3/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EndpointType - enum for endpoint type.
|
// EndpointType - enum for endpoint type.
|
||||||
|
|||||||
@@ -277,23 +277,21 @@ func partNeedsHealing(partErrs []int) bool {
|
|||||||
return slices.IndexFunc(partErrs, func(i int) bool { return i != checkPartSuccess && i != checkPartUnknown }) > -1
|
return slices.IndexFunc(partErrs, func(i int) bool { return i != checkPartSuccess && i != checkPartUnknown }) > -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPartErr(partErrs []int) bool {
|
func countPartNotSuccess(partErrs []int) (c int) {
|
||||||
return slices.IndexFunc(partErrs, func(i int) bool { return i != checkPartSuccess }) > -1
|
for _, pe := range partErrs {
|
||||||
|
if pe != checkPartSuccess {
|
||||||
|
c++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// disksWithAllParts - This function needs to be called with
|
// checkObjectWithAllParts sets partsMetadata and onlineDisks when xl.meta is inexistant/corrupted or outdated
|
||||||
// []StorageAPI returned by listOnlineDisks. Returns,
|
// it also checks if the status of each part (corrupted, missing, ok) in each drive
|
||||||
//
|
func checkObjectWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetadata []FileInfo,
|
||||||
// - disks which have all parts specified in the latest xl.meta.
|
|
||||||
//
|
|
||||||
// - slice of errors about the state of data files on disk - can have
|
|
||||||
// a not-found error or a hash-mismatch error.
|
|
||||||
func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetadata []FileInfo,
|
|
||||||
errs []error, latestMeta FileInfo, filterByETag bool, bucket, object string,
|
errs []error, latestMeta FileInfo, filterByETag bool, bucket, object string,
|
||||||
scanMode madmin.HealScanMode,
|
scanMode madmin.HealScanMode,
|
||||||
) (availableDisks []StorageAPI, dataErrsByDisk map[int][]int, dataErrsByPart map[int][]int) {
|
) (dataErrsByDisk map[int][]int, dataErrsByPart map[int][]int) {
|
||||||
availableDisks = make([]StorageAPI, len(onlineDisks))
|
|
||||||
|
|
||||||
dataErrsByDisk = make(map[int][]int, len(onlineDisks))
|
dataErrsByDisk = make(map[int][]int, len(onlineDisks))
|
||||||
for i := range onlineDisks {
|
for i := range onlineDisks {
|
||||||
dataErrsByDisk[i] = make([]int, len(latestMeta.Parts))
|
dataErrsByDisk[i] = make([]int, len(latestMeta.Parts))
|
||||||
@@ -334,12 +332,12 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
|
|
||||||
metaErrs := make([]error, len(errs))
|
metaErrs := make([]error, len(errs))
|
||||||
|
|
||||||
for i, onlineDisk := range onlineDisks {
|
for i := range onlineDisks {
|
||||||
if errs[i] != nil {
|
if errs[i] != nil {
|
||||||
metaErrs[i] = errs[i]
|
metaErrs[i] = errs[i]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if onlineDisk == OfflineDisk {
|
if onlineDisks[i] == OfflineDisk {
|
||||||
metaErrs[i] = errDiskNotFound
|
metaErrs[i] = errDiskNotFound
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -355,6 +353,7 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
if corrupted {
|
if corrupted {
|
||||||
metaErrs[i] = errFileCorrupt
|
metaErrs[i] = errFileCorrupt
|
||||||
partsMetadata[i] = FileInfo{}
|
partsMetadata[i] = FileInfo{}
|
||||||
|
onlineDisks[i] = nil
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,6 +361,7 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
if !meta.IsValid() {
|
if !meta.IsValid() {
|
||||||
partsMetadata[i] = FileInfo{}
|
partsMetadata[i] = FileInfo{}
|
||||||
metaErrs[i] = errFileCorrupt
|
metaErrs[i] = errFileCorrupt
|
||||||
|
onlineDisks[i] = nil
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,6 +372,7 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
// might have the right erasure distribution.
|
// might have the right erasure distribution.
|
||||||
partsMetadata[i] = FileInfo{}
|
partsMetadata[i] = FileInfo{}
|
||||||
metaErrs[i] = errFileCorrupt
|
metaErrs[i] = errFileCorrupt
|
||||||
|
onlineDisks[i] = nil
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,8 +393,8 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
if metaErrs[i] != nil {
|
if metaErrs[i] != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
meta := partsMetadata[i]
|
|
||||||
|
|
||||||
|
meta := partsMetadata[i]
|
||||||
if meta.Deleted || meta.IsRemote() {
|
if meta.Deleted || meta.IsRemote() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -440,16 +441,5 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
dataErrsByDisk[disk][part] = dataErrsByPart[part][disk]
|
dataErrsByDisk[disk][part] = dataErrsByPart[part][disk]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, onlineDisk := range onlineDisks {
|
|
||||||
if metaErrs[i] == nil && !hasPartErr(dataErrsByDisk[i]) {
|
|
||||||
// All parts verified, mark it as all data available.
|
|
||||||
availableDisks[i] = onlineDisk
|
|
||||||
} else {
|
|
||||||
// upon errors just make that disk's fileinfo invalid
|
|
||||||
partsMetadata[i] = FileInfo{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,11 +313,11 @@ func TestListOnlineDisks(t *testing.T) {
|
|||||||
t.Fatalf("Expected modTime to be equal to %v but was found to be %v",
|
t.Fatalf("Expected modTime to be equal to %v but was found to be %v",
|
||||||
test.expectedTime, modTime)
|
test.expectedTime, modTime)
|
||||||
}
|
}
|
||||||
availableDisks, _, _ := disksWithAllParts(ctx, onlineDisks, partsMetadata,
|
_, _ = checkObjectWithAllParts(ctx, onlineDisks, partsMetadata,
|
||||||
test.errs, fi, false, bucket, object, madmin.HealDeepScan)
|
test.errs, fi, false, bucket, object, madmin.HealDeepScan)
|
||||||
|
|
||||||
if test._tamperBackend != noTamper {
|
if test._tamperBackend != noTamper {
|
||||||
if tamperedIndex != -1 && availableDisks[tamperedIndex] != nil {
|
if tamperedIndex != -1 && onlineDisks[tamperedIndex] != nil {
|
||||||
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
|
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
|
||||||
erasureDisks[tamperedIndex])
|
erasureDisks[tamperedIndex])
|
||||||
}
|
}
|
||||||
@@ -495,11 +495,11 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
|
|||||||
test.expectedTime, modTime)
|
test.expectedTime, modTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
availableDisks, _, _ := disksWithAllParts(ctx, onlineDisks, partsMetadata,
|
_, _ = checkObjectWithAllParts(ctx, onlineDisks, partsMetadata,
|
||||||
test.errs, fi, false, bucket, object, madmin.HealDeepScan)
|
test.errs, fi, false, bucket, object, madmin.HealDeepScan)
|
||||||
|
|
||||||
if test._tamperBackend != noTamper {
|
if test._tamperBackend != noTamper {
|
||||||
if tamperedIndex != -1 && availableDisks[tamperedIndex] != nil {
|
if tamperedIndex != -1 && onlineDisks[tamperedIndex] != nil {
|
||||||
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
|
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
|
||||||
erasureDisks[tamperedIndex])
|
erasureDisks[tamperedIndex])
|
||||||
}
|
}
|
||||||
@@ -545,6 +545,7 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
|
|
||||||
// Test 1: Test that all disks are returned without any failures with
|
// Test 1: Test that all disks are returned without any failures with
|
||||||
// unmodified meta data
|
// unmodified meta data
|
||||||
|
erasureDisks = s.getDisks()
|
||||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to read xl meta data %v", err)
|
t.Fatalf("Failed to read xl meta data %v", err)
|
||||||
@@ -557,14 +558,10 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
|
|
||||||
erasureDisks, _, _ = listOnlineDisks(erasureDisks, partsMetadata, errs, readQuorum)
|
erasureDisks, _, _ = listOnlineDisks(erasureDisks, partsMetadata, errs, readQuorum)
|
||||||
|
|
||||||
filteredDisks, _, dataErrsPerDisk := disksWithAllParts(ctx, erasureDisks, partsMetadata,
|
dataErrsPerDisk, _ := checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
|
||||||
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
||||||
|
|
||||||
if len(filteredDisks) != len(erasureDisks) {
|
for diskIndex, disk := range erasureDisks {
|
||||||
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
|
|
||||||
}
|
|
||||||
|
|
||||||
for diskIndex, disk := range filteredDisks {
|
|
||||||
if partNeedsHealing(dataErrsPerDisk[diskIndex]) {
|
if partNeedsHealing(dataErrsPerDisk[diskIndex]) {
|
||||||
t.Errorf("Unexpected error: %v", dataErrsPerDisk[diskIndex])
|
t.Errorf("Unexpected error: %v", dataErrsPerDisk[diskIndex])
|
||||||
}
|
}
|
||||||
@@ -575,17 +572,15 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test 2: Not synchronized modtime
|
// Test 2: Not synchronized modtime
|
||||||
|
erasureDisks = s.getDisks()
|
||||||
partsMetadataBackup := partsMetadata[0]
|
partsMetadataBackup := partsMetadata[0]
|
||||||
partsMetadata[0].ModTime = partsMetadata[0].ModTime.Add(-1 * time.Hour)
|
partsMetadata[0].ModTime = partsMetadata[0].ModTime.Add(-1 * time.Hour)
|
||||||
|
|
||||||
errs = make([]error, len(erasureDisks))
|
errs = make([]error, len(erasureDisks))
|
||||||
filteredDisks, _, _ = disksWithAllParts(ctx, erasureDisks, partsMetadata,
|
_, _ = checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
|
||||||
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
||||||
|
|
||||||
if len(filteredDisks) != len(erasureDisks) {
|
for diskIndex, disk := range erasureDisks {
|
||||||
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
|
|
||||||
}
|
|
||||||
for diskIndex, disk := range filteredDisks {
|
|
||||||
if diskIndex == 0 && disk != nil {
|
if diskIndex == 0 && disk != nil {
|
||||||
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
|
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
|
||||||
}
|
}
|
||||||
@@ -596,17 +591,15 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
partsMetadata[0] = partsMetadataBackup // Revert before going to the next test
|
partsMetadata[0] = partsMetadataBackup // Revert before going to the next test
|
||||||
|
|
||||||
// Test 3: Not synchronized DataDir
|
// Test 3: Not synchronized DataDir
|
||||||
|
erasureDisks = s.getDisks()
|
||||||
partsMetadataBackup = partsMetadata[1]
|
partsMetadataBackup = partsMetadata[1]
|
||||||
partsMetadata[1].DataDir = "foo-random"
|
partsMetadata[1].DataDir = "foo-random"
|
||||||
|
|
||||||
errs = make([]error, len(erasureDisks))
|
errs = make([]error, len(erasureDisks))
|
||||||
filteredDisks, _, _ = disksWithAllParts(ctx, erasureDisks, partsMetadata,
|
_, _ = checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
|
||||||
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
||||||
|
|
||||||
if len(filteredDisks) != len(erasureDisks) {
|
for diskIndex, disk := range erasureDisks {
|
||||||
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
|
|
||||||
}
|
|
||||||
for diskIndex, disk := range filteredDisks {
|
|
||||||
if diskIndex == 1 && disk != nil {
|
if diskIndex == 1 && disk != nil {
|
||||||
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
|
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
|
||||||
}
|
}
|
||||||
@@ -617,6 +610,7 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
partsMetadata[1] = partsMetadataBackup // Revert before going to the next test
|
partsMetadata[1] = partsMetadataBackup // Revert before going to the next test
|
||||||
|
|
||||||
// Test 4: key = disk index, value = part name with hash mismatch
|
// Test 4: key = disk index, value = part name with hash mismatch
|
||||||
|
erasureDisks = s.getDisks()
|
||||||
diskFailures := make(map[int]string)
|
diskFailures := make(map[int]string)
|
||||||
diskFailures[0] = "part.1"
|
diskFailures[0] = "part.1"
|
||||||
diskFailures[3] = "part.1"
|
diskFailures[3] = "part.1"
|
||||||
@@ -637,29 +631,18 @@ func TestDisksWithAllParts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errs = make([]error, len(erasureDisks))
|
errs = make([]error, len(erasureDisks))
|
||||||
filteredDisks, dataErrsPerDisk, _ = disksWithAllParts(ctx, erasureDisks, partsMetadata,
|
dataErrsPerDisk, _ = checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
|
||||||
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
errs, fi, false, bucket, object, madmin.HealDeepScan)
|
||||||
|
|
||||||
if len(filteredDisks) != len(erasureDisks) {
|
for diskIndex := range erasureDisks {
|
||||||
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
|
|
||||||
}
|
|
||||||
|
|
||||||
for diskIndex, disk := range filteredDisks {
|
|
||||||
if _, ok := diskFailures[diskIndex]; ok {
|
if _, ok := diskFailures[diskIndex]; ok {
|
||||||
if disk != nil {
|
|
||||||
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
|
|
||||||
}
|
|
||||||
if !partNeedsHealing(dataErrsPerDisk[diskIndex]) {
|
if !partNeedsHealing(dataErrsPerDisk[diskIndex]) {
|
||||||
t.Errorf("Disk expected to be healed, driveIndex: %d", diskIndex)
|
t.Errorf("Disk expected to be healed, driveIndex: %d", diskIndex)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if disk == nil {
|
|
||||||
t.Errorf("Drive erroneously filtered, driveIndex: %d", diskIndex)
|
|
||||||
}
|
|
||||||
if partNeedsHealing(dataErrsPerDisk[diskIndex]) {
|
if partNeedsHealing(dataErrsPerDisk[diskIndex]) {
|
||||||
t.Errorf("Disk not expected to be healed, driveIndex: %d", diskIndex)
|
t.Errorf("Disk not expected to be healed, driveIndex: %d", diskIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-65
@@ -32,7 +32,6 @@ import (
|
|||||||
"github.com/minio/minio/internal/grid"
|
"github.com/minio/minio/internal/grid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v3/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate stringer -type=healingMetric -trimprefix=healingMetric $GOFILE
|
//go:generate stringer -type=healingMetric -trimprefix=healingMetric $GOFILE
|
||||||
@@ -45,7 +44,8 @@ const (
|
|||||||
healingMetricCheckAbandonedParts
|
healingMetricCheckAbandonedParts
|
||||||
)
|
)
|
||||||
|
|
||||||
func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string, scanMode madmin.HealScanMode, healEntry func(string, metaCacheEntry, madmin.HealScanMode) error) error {
|
// List a prefix or a single object versions and heal
|
||||||
|
func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string, recursive bool, scanMode madmin.HealScanMode, healEntry func(string, metaCacheEntry, madmin.HealScanMode) error) error {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -78,11 +78,14 @@ func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string,
|
|||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
path: path,
|
path: path,
|
||||||
filterPrefix: filterPrefix,
|
filterPrefix: filterPrefix,
|
||||||
recursive: true,
|
recursive: recursive,
|
||||||
forwardTo: "",
|
forwardTo: "",
|
||||||
minDisks: 1,
|
minDisks: 1,
|
||||||
reportNotFound: false,
|
reportNotFound: false,
|
||||||
agreed: func(entry metaCacheEntry) {
|
agreed: func(entry metaCacheEntry) {
|
||||||
|
if !recursive && prefix != entry.name {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := healEntry(bucket, entry, scanMode); err != nil {
|
if err := healEntry(bucket, entry, scanMode); err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
@@ -94,7 +97,9 @@ func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string,
|
|||||||
// proceed to heal nonetheless.
|
// proceed to heal nonetheless.
|
||||||
entry, _ = entries.firstFound()
|
entry, _ = entries.firstFound()
|
||||||
}
|
}
|
||||||
|
if !recursive && prefix != entry.name {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := healEntry(bucket, *entry, scanMode); err != nil {
|
if err := healEntry(bucket, *entry, scanMode); err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
@@ -149,37 +154,40 @@ var errLegacyXLMeta = errors.New("legacy XL meta")
|
|||||||
|
|
||||||
var errOutdatedXLMeta = errors.New("outdated XL meta")
|
var errOutdatedXLMeta = errors.New("outdated XL meta")
|
||||||
|
|
||||||
var errPartMissingOrCorrupt = errors.New("part missing or corrupt")
|
var (
|
||||||
|
errPartCorrupt = errors.New("part corrupt")
|
||||||
|
errPartMissing = errors.New("part missing")
|
||||||
|
)
|
||||||
|
|
||||||
// Only heal on disks where we are sure that healing is needed. We can expand
|
// Only heal on disks where we are sure that healing is needed. We can expand
|
||||||
// this list as and when we figure out more errors can be added to this list safely.
|
// this list as and when we figure out more errors can be added to this list safely.
|
||||||
func shouldHealObjectOnDisk(erErr error, partsErrs []int, meta FileInfo, latestMeta FileInfo) (bool, error) {
|
func shouldHealObjectOnDisk(erErr error, partsErrs []int, meta FileInfo, latestMeta FileInfo) (bool, bool, error) {
|
||||||
if errors.Is(erErr, errFileNotFound) || errors.Is(erErr, errFileVersionNotFound) || errors.Is(erErr, errFileCorrupt) {
|
if errors.Is(erErr, errFileNotFound) || errors.Is(erErr, errFileVersionNotFound) || errors.Is(erErr, errFileCorrupt) {
|
||||||
return true, erErr
|
return true, true, erErr
|
||||||
}
|
}
|
||||||
if erErr == nil {
|
if erErr == nil {
|
||||||
if meta.XLV1 {
|
if meta.XLV1 {
|
||||||
// Legacy means heal always
|
// Legacy means heal always
|
||||||
// always check first.
|
// always check first.
|
||||||
return true, errLegacyXLMeta
|
return true, true, errLegacyXLMeta
|
||||||
}
|
}
|
||||||
if !latestMeta.Equals(meta) {
|
if !latestMeta.Equals(meta) {
|
||||||
return true, errOutdatedXLMeta
|
return true, true, errOutdatedXLMeta
|
||||||
}
|
}
|
||||||
if !meta.Deleted && !meta.IsRemote() {
|
if !meta.Deleted && !meta.IsRemote() {
|
||||||
// If xl.meta was read fine but there may be problem with the part.N files.
|
// If xl.meta was read fine but there may be problem with the part.N files.
|
||||||
for _, partErr := range partsErrs {
|
for _, partErr := range partsErrs {
|
||||||
if slices.Contains([]int{
|
if partErr == checkPartFileNotFound {
|
||||||
checkPartFileNotFound,
|
return true, false, errPartMissing
|
||||||
checkPartFileCorrupt,
|
}
|
||||||
}, partErr) {
|
if partErr == checkPartFileCorrupt {
|
||||||
return true, errPartMissingOrCorrupt
|
return true, false, errPartCorrupt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, false, nil
|
||||||
}
|
}
|
||||||
return false, erErr
|
return false, false, erErr
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -254,6 +262,21 @@ func (er *erasureObjects) auditHealObject(ctx context.Context, bucket, object, v
|
|||||||
auditLogInternal(ctx, opts)
|
auditLogInternal(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func objectErrToDriveState(reason error) string {
|
||||||
|
switch {
|
||||||
|
case reason == nil:
|
||||||
|
return madmin.DriveStateOk
|
||||||
|
case IsErr(reason, errDiskNotFound):
|
||||||
|
return madmin.DriveStateOffline
|
||||||
|
case IsErr(reason, errFileNotFound, errFileVersionNotFound, errVolumeNotFound, errPartMissing, errOutdatedXLMeta, errLegacyXLMeta):
|
||||||
|
return madmin.DriveStateMissing
|
||||||
|
case IsErr(reason, errFileCorrupt, errPartCorrupt):
|
||||||
|
return madmin.DriveStateCorrupt
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%s (%s)", madmin.DriveStateUnknown, reason.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Heals an object by re-writing corrupt/missing erasure blocks.
|
// Heals an object by re-writing corrupt/missing erasure blocks.
|
||||||
func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
|
func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
|
||||||
dryRun := opts.DryRun
|
dryRun := opts.DryRun
|
||||||
@@ -340,16 +363,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
// No modtime quorum
|
// No modtime quorum
|
||||||
filterDisksByETag := quorumETag != ""
|
filterDisksByETag := quorumETag != ""
|
||||||
|
|
||||||
// List of disks having all parts as per latest metadata.
|
dataErrsByDisk, dataErrsByPart := checkObjectWithAllParts(ctx, onlineDisks, partsMetadata,
|
||||||
// NOTE: do not pass in latestDisks to diskWithAllParts since
|
|
||||||
// the diskWithAllParts needs to reach the drive to ensure
|
|
||||||
// validity of the metadata content, we should make sure that
|
|
||||||
// we pass in disks as is for it to be verified. Once verified
|
|
||||||
// the disksWithAllParts() returns the actual disks that can be
|
|
||||||
// used here for reconstruction. This is done to ensure that
|
|
||||||
// we do not skip drives that have inconsistent metadata to be
|
|
||||||
// skipped from purging when they are stale.
|
|
||||||
availableDisks, dataErrsByDisk, dataErrsByPart := disksWithAllParts(ctx, onlineDisks, partsMetadata,
|
|
||||||
errs, latestMeta, filterDisksByETag, bucket, object, scanMode)
|
errs, latestMeta, filterDisksByETag, bucket, object, scanMode)
|
||||||
|
|
||||||
var erasure Erasure
|
var erasure Erasure
|
||||||
@@ -371,26 +385,18 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
// 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.
|
||||||
outDatedDisks := make([]StorageAPI, len(storageDisks))
|
outDatedDisks := make([]StorageAPI, len(storageDisks))
|
||||||
disksToHealCount := 0
|
disksToHealCount, xlMetaToHealCount := 0, 0
|
||||||
for i := range availableDisks {
|
for i := range onlineDisks {
|
||||||
yes, reason := shouldHealObjectOnDisk(errs[i], dataErrsByDisk[i], partsMetadata[i], latestMeta)
|
yes, isMeta, reason := shouldHealObjectOnDisk(errs[i], dataErrsByDisk[i], partsMetadata[i], latestMeta)
|
||||||
if yes {
|
if yes {
|
||||||
outDatedDisks[i] = storageDisks[i]
|
outDatedDisks[i] = storageDisks[i]
|
||||||
disksToHealCount++
|
disksToHealCount++
|
||||||
|
if isMeta {
|
||||||
|
xlMetaToHealCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
driveState := ""
|
driveState := objectErrToDriveState(reason)
|
||||||
switch {
|
|
||||||
case reason == nil:
|
|
||||||
driveState = madmin.DriveStateOk
|
|
||||||
case IsErr(reason, errDiskNotFound):
|
|
||||||
driveState = madmin.DriveStateOffline
|
|
||||||
case IsErr(reason, errFileNotFound, errFileVersionNotFound, errVolumeNotFound, errPartMissingOrCorrupt, errOutdatedXLMeta, errLegacyXLMeta):
|
|
||||||
driveState = madmin.DriveStateMissing
|
|
||||||
default:
|
|
||||||
// all remaining cases imply corrupt data/metadata
|
|
||||||
driveState = madmin.DriveStateCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
||||||
UUID: "",
|
UUID: "",
|
||||||
@@ -404,16 +410,6 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAllNotFound(errs) {
|
|
||||||
// File is fully gone, fileInfo is empty.
|
|
||||||
err := errFileNotFound
|
|
||||||
if versionID != "" {
|
|
||||||
err = errFileVersionNotFound
|
|
||||||
}
|
|
||||||
return er.defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs,
|
|
||||||
bucket, object, versionID), err
|
|
||||||
}
|
|
||||||
|
|
||||||
if disksToHealCount == 0 {
|
if disksToHealCount == 0 {
|
||||||
// Nothing to heal!
|
// Nothing to heal!
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -425,13 +421,23 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cannotHeal := !latestMeta.XLV1 && !latestMeta.Deleted && disksToHealCount > latestMeta.Erasure.ParityBlocks
|
cannotHeal := !latestMeta.XLV1 && !latestMeta.Deleted && xlMetaToHealCount > latestMeta.Erasure.ParityBlocks
|
||||||
if cannotHeal && quorumETag != "" {
|
if cannotHeal && quorumETag != "" {
|
||||||
// This is an object that is supposed to be removed by the dangling code
|
// This is an object that is supposed to be removed by the dangling code
|
||||||
// but we noticed that ETag is the same for all objects, let's give it a shot
|
// but we noticed that ETag is the same for all objects, let's give it a shot
|
||||||
cannotHeal = false
|
cannotHeal = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !latestMeta.Deleted && !latestMeta.IsRemote() {
|
||||||
|
// check if there is a part that lost its quorum
|
||||||
|
for _, partErrs := range dataErrsByPart {
|
||||||
|
if countPartNotSuccess(partErrs) > latestMeta.Erasure.ParityBlocks {
|
||||||
|
cannotHeal = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cannotHeal {
|
if cannotHeal {
|
||||||
// Allow for dangling deletes, on versions that have DataDir missing etc.
|
// Allow for dangling deletes, on versions that have DataDir missing etc.
|
||||||
// this would end up restoring the correct readable versions.
|
// this would end up restoring the correct readable versions.
|
||||||
@@ -470,16 +476,15 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
tmpID := mustGetUUID()
|
tmpID := mustGetUUID()
|
||||||
migrateDataDir := mustGetUUID()
|
migrateDataDir := mustGetUUID()
|
||||||
|
|
||||||
// Reorder so that we have data disks first and parity disks next.
|
if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(onlineDisks) {
|
||||||
if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(availableDisks) {
|
err := fmt.Errorf("unexpected file distribution (%v) from online disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)",
|
||||||
err := fmt.Errorf("unexpected file distribution (%v) from available disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)",
|
latestMeta.Erasure.Distribution, onlineDisks, bucket, object, versionID)
|
||||||
latestMeta.Erasure.Distribution, availableDisks, bucket, object, versionID)
|
healingLogOnceIf(ctx, err, "heal-object-online-disks")
|
||||||
healingLogOnceIf(ctx, err, "heal-object-available-disks")
|
|
||||||
return er.defaultHealResult(latestMeta, storageDisks, storageEndpoints, errs,
|
return er.defaultHealResult(latestMeta, storageDisks, storageEndpoints, errs,
|
||||||
bucket, object, versionID), err
|
bucket, object, versionID), err
|
||||||
}
|
}
|
||||||
|
|
||||||
latestDisks := shuffleDisks(availableDisks, latestMeta.Erasure.Distribution)
|
latestDisks := shuffleDisks(onlineDisks, latestMeta.Erasure.Distribution)
|
||||||
|
|
||||||
if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(outDatedDisks) {
|
if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(outDatedDisks) {
|
||||||
err := fmt.Errorf("unexpected file distribution (%v) from outdated disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)",
|
err := fmt.Errorf("unexpected file distribution (%v) from outdated disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)",
|
||||||
@@ -550,6 +555,10 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
|||||||
if disk == OfflineDisk {
|
if disk == OfflineDisk {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
thisPartErrs := shuffleCheckParts(dataErrsByPart[partIndex], latestMeta.Erasure.Distribution)
|
||||||
|
if thisPartErrs[i] != checkPartSuccess {
|
||||||
|
continue
|
||||||
|
}
|
||||||
checksumInfo := copyPartsMetadata[i].Erasure.GetChecksumInfo(partNumber)
|
checksumInfo := copyPartsMetadata[i].Erasure.GetChecksumInfo(partNumber)
|
||||||
partPath := pathJoin(object, srcDataDir, fmt.Sprintf("part.%d", partNumber))
|
partPath := pathJoin(object, srcDataDir, fmt.Sprintf("part.%d", partNumber))
|
||||||
readers[i] = newBitrotReader(disk, copyPartsMetadata[i].Data, bucket, partPath, tillOffset, checksumAlgo,
|
readers[i] = newBitrotReader(disk, copyPartsMetadata[i].Data, bucket, partPath, tillOffset, checksumAlgo,
|
||||||
@@ -817,13 +826,7 @@ func (er *erasureObjects) defaultHealResult(lfi FileInfo, storageDisks []Storage
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
driveState := madmin.DriveStateCorrupt
|
driveState := objectErrToDriveState(errs[index])
|
||||||
switch errs[index] {
|
|
||||||
case errFileNotFound, errVolumeNotFound:
|
|
||||||
driveState = madmin.DriveStateMissing
|
|
||||||
case nil:
|
|
||||||
driveState = madmin.DriveStateOk
|
|
||||||
}
|
|
||||||
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
||||||
UUID: "",
|
UUID: "",
|
||||||
Endpoint: storageEndpoints[index].String(),
|
Endpoint: storageEndpoints[index].String(),
|
||||||
|
|||||||
@@ -733,7 +733,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
|||||||
t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions)
|
t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true},
|
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Recursive: true, Remove: true},
|
||||||
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
||||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
||||||
return err
|
return err
|
||||||
@@ -780,7 +780,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
|||||||
t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions)
|
t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true},
|
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Recursive: true, Remove: true},
|
||||||
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
||||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
||||||
return err
|
return err
|
||||||
@@ -829,7 +829,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
|||||||
t.Fatalf("Expected versions 3, got %d", fileInfoPreHeal.NumVersions)
|
t.Fatalf("Expected versions 3, got %d", fileInfoPreHeal.NumVersions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true},
|
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Recursive: true, Remove: true},
|
||||||
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
||||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -52,15 +52,15 @@ func reduceCommonVersions(diskVersions [][]byte, writeQuorum int) (versions []by
|
|||||||
}
|
}
|
||||||
|
|
||||||
var commonVersions uint64
|
var commonVersions uint64
|
||||||
max := 0
|
maxCnt := 0
|
||||||
for versions, count := range diskVersionsCount {
|
for versions, count := range diskVersionsCount {
|
||||||
if max < count {
|
if maxCnt < count {
|
||||||
max = count
|
maxCnt = count
|
||||||
commonVersions = versions
|
commonVersions = versions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if max >= writeQuorum {
|
if maxCnt >= writeQuorum {
|
||||||
for _, versions := range diskVersions {
|
for _, versions := range diskVersions {
|
||||||
if binary.BigEndian.Uint64(versions) == commonVersions {
|
if binary.BigEndian.Uint64(versions) == commonVersions {
|
||||||
return versions
|
return versions
|
||||||
@@ -80,15 +80,15 @@ func reduceCommonDataDir(dataDirs []string, writeQuorum int) (dataDir string) {
|
|||||||
dataDirsCount[ddir]++
|
dataDirsCount[ddir]++
|
||||||
}
|
}
|
||||||
|
|
||||||
max := 0
|
maxCnt := 0
|
||||||
for ddir, count := range dataDirsCount {
|
for ddir, count := range dataDirsCount {
|
||||||
if max < count {
|
if maxCnt < count {
|
||||||
max = count
|
maxCnt = count
|
||||||
dataDir = ddir
|
dataDir = ddir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if max >= writeQuorum {
|
if maxCnt >= writeQuorum {
|
||||||
return dataDir
|
return dataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,20 +115,20 @@ func reduceErrs(errs []error, ignoredErrs []error) (maxCount int, maxErr error)
|
|||||||
errorCounts[err]++
|
errorCounts[err]++
|
||||||
}
|
}
|
||||||
|
|
||||||
max := 0
|
maxCnt := 0
|
||||||
for err, count := range errorCounts {
|
for err, count := range errorCounts {
|
||||||
switch {
|
switch {
|
||||||
case max < count:
|
case maxCnt < count:
|
||||||
max = count
|
maxCnt = count
|
||||||
maxErr = err
|
maxErr = err
|
||||||
|
|
||||||
// Prefer `nil` over other error values with the same
|
// Prefer `nil` over other error values with the same
|
||||||
// number of occurrences.
|
// number of occurrences.
|
||||||
case max == count && err == nil:
|
case maxCnt == count && err == nil:
|
||||||
maxErr = err
|
maxErr = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return max, maxErr
|
return maxCnt, maxErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// reduceQuorumErrs behaves like reduceErrs by only for returning
|
// reduceQuorumErrs behaves like reduceErrs by only for returning
|
||||||
@@ -295,34 +295,34 @@ func shuffleDisksAndPartsMetadata(disks []StorageAPI, partsMetadata []FileInfo,
|
|||||||
return shuffledDisks, shuffledPartsMetadata
|
return shuffledDisks, shuffledPartsMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return shuffled partsMetadata depending on distribution.
|
func shuffleWithDist[T any](input []T, distribution []int) []T {
|
||||||
func shufflePartsMetadata(partsMetadata []FileInfo, distribution []int) (shuffledPartsMetadata []FileInfo) {
|
|
||||||
if distribution == nil {
|
if distribution == nil {
|
||||||
return partsMetadata
|
return input
|
||||||
}
|
}
|
||||||
shuffledPartsMetadata = make([]FileInfo, len(partsMetadata))
|
shuffled := make([]T, len(input))
|
||||||
// Shuffle slice xl metadata for expected distribution.
|
for index := range input {
|
||||||
for index := range partsMetadata {
|
|
||||||
blockIndex := distribution[index]
|
blockIndex := distribution[index]
|
||||||
shuffledPartsMetadata[blockIndex-1] = partsMetadata[index]
|
shuffled[blockIndex-1] = input[index]
|
||||||
}
|
}
|
||||||
return shuffledPartsMetadata
|
return shuffled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return shuffled partsMetadata depending on distribution.
|
||||||
|
func shufflePartsMetadata(partsMetadata []FileInfo, distribution []int) []FileInfo {
|
||||||
|
return shuffleWithDist[FileInfo](partsMetadata, distribution)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shuffleCheckParts - shuffle CheckParts slice depending on the
|
||||||
|
// erasure distribution.
|
||||||
|
func shuffleCheckParts(parts []int, distribution []int) []int {
|
||||||
|
return shuffleWithDist[int](parts, distribution)
|
||||||
}
|
}
|
||||||
|
|
||||||
// shuffleDisks - shuffle input disks slice depending on the
|
// shuffleDisks - shuffle input disks slice depending on the
|
||||||
// erasure distribution. Return shuffled slice of disks with
|
// erasure distribution. Return shuffled slice of disks with
|
||||||
// their expected distribution.
|
// their expected distribution.
|
||||||
func shuffleDisks(disks []StorageAPI, distribution []int) (shuffledDisks []StorageAPI) {
|
func shuffleDisks(disks []StorageAPI, distribution []int) []StorageAPI {
|
||||||
if distribution == nil {
|
return shuffleWithDist[StorageAPI](disks, distribution)
|
||||||
return disks
|
|
||||||
}
|
|
||||||
shuffledDisks = make([]StorageAPI, len(disks))
|
|
||||||
// Shuffle disks for expected distribution.
|
|
||||||
for index := range disks {
|
|
||||||
blockIndex := distribution[index]
|
|
||||||
shuffledDisks[blockIndex-1] = disks[index]
|
|
||||||
}
|
|
||||||
return shuffledDisks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalDisks - returns a new slice of disks where nil is set if
|
// evalDisks - returns a new slice of disks where nil is set if
|
||||||
|
|||||||
@@ -514,8 +514,10 @@ func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int)
|
|||||||
if metadata.Deleted || metadata.Size == 0 {
|
if metadata.Deleted || metadata.Size == 0 {
|
||||||
parities[index] = totalShards / 2
|
parities[index] = totalShards / 2
|
||||||
} else if metadata.TransitionStatus == lifecycle.TransitionComplete {
|
} else if metadata.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
// For tiered objects, read quorum is N/2+1 to ensure simple majority on xl.meta. It is not equal to EcM because the data integrity is entrusted with the warm tier.
|
// For tiered objects, read quorum is N/2+1 to ensure simple majority on xl.meta.
|
||||||
parities[index] = totalShards - (totalShards/2 + 1)
|
// It is not equal to EcM because the data integrity is entrusted with the warm tier.
|
||||||
|
// However, we never go below EcM, in case of a EcM=EcN setup.
|
||||||
|
parities[index] = max(totalShards-(totalShards/2+1), metadata.Erasure.ParityBlocks)
|
||||||
} else {
|
} else {
|
||||||
parities[index] = metadata.Erasure.ParityBlocks
|
parities[index] = metadata.Erasure.ParityBlocks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -626,17 +626,13 @@ 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 at least a byte to reach EOF
|
buffer = make([]byte, 1) // Allocate at least a byte to reach EOF
|
||||||
case size == -1:
|
case size >= fi.Erasure.BlockSize || size == -1:
|
||||||
if size := data.ActualSize(); size > 0 && size < fi.Erasure.BlockSize {
|
if int64(globalBytePoolCap.Load().Width()) < fi.Erasure.BlockSize {
|
||||||
// Account for padding and forced compression overhead and encryption.
|
buffer = make([]byte, fi.Erasure.BlockSize, 2*fi.Erasure.BlockSize)
|
||||||
buffer = make([]byte, data.ActualSize()+256+32+32, data.ActualSize()*2+512)
|
|
||||||
} else {
|
} else {
|
||||||
buffer = globalBytePoolCap.Load().Get()
|
buffer = globalBytePoolCap.Load().Get()
|
||||||
defer globalBytePoolCap.Load().Put(buffer)
|
defer globalBytePoolCap.Load().Put(buffer)
|
||||||
}
|
}
|
||||||
case size >= fi.Erasure.BlockSize:
|
|
||||||
buffer = globalBytePoolCap.Load().Get()
|
|
||||||
defer globalBytePoolCap.Load().Put(buffer)
|
|
||||||
case size < fi.Erasure.BlockSize:
|
case size < fi.Erasure.BlockSize:
|
||||||
// No need to allocate fully fi.Erasure.BlockSize buffer if the incoming data is smaller.
|
// No need to allocate fully fi.Erasure.BlockSize buffer if the incoming data is smaller.
|
||||||
buffer = make([]byte, size, 2*size+int64(fi.Erasure.ParityBlocks+fi.Erasure.DataBlocks-1))
|
buffer = make([]byte, size, 2*size+int64(fi.Erasure.ParityBlocks+fi.Erasure.DataBlocks-1))
|
||||||
@@ -744,6 +740,15 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
|||||||
ctx = plkctx.Context()
|
ctx = plkctx.Context()
|
||||||
defer partIDLock.Unlock(plkctx)
|
defer partIDLock.Unlock(plkctx)
|
||||||
|
|
||||||
|
// Read lock for upload id, only held while reading the upload metadata.
|
||||||
|
uploadIDRLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||||
|
rlkctx, err := uploadIDRLock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return PartInfo{}, err
|
||||||
|
}
|
||||||
|
ctx = rlkctx.Context()
|
||||||
|
defer uploadIDRLock.RUnlock(rlkctx)
|
||||||
|
|
||||||
onlineDisks, err = er.renamePart(ctx, onlineDisks, minioMetaTmpBucket, tmpPartPath, minioMetaMultipartBucket, partPath, partFI, writeQuorum)
|
onlineDisks, err = er.renamePart(ctx, onlineDisks, minioMetaTmpBucket, tmpPartPath, minioMetaMultipartBucket, partPath, partFI, writeQuorum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errFileNotFound) {
|
if errors.Is(err, errFileNotFound) {
|
||||||
|
|||||||
@@ -2365,6 +2365,7 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
|
|||||||
|
|
||||||
destObj, err := genTransitionObjName(bucket)
|
destObj, err := genTransitionObjName(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
traceFn(ILMTransition, nil, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2375,9 +2376,14 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var rv remoteVersionID
|
var rv remoteVersionID
|
||||||
rv, err = tgtClient.Put(ctx, destObj, pr, fi.Size)
|
rv, err = tgtClient.PutWithMeta(ctx, destObj, pr, fi.Size, map[string]string{
|
||||||
|
"name": object, // preserve the original name of the object on the remote tier object metadata.
|
||||||
|
// this is just for future reverse lookup() purposes (applies only for new objects)
|
||||||
|
// does not apply retro-actively on already transitioned objects.
|
||||||
|
})
|
||||||
pr.CloseWithError(err)
|
pr.CloseWithError(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
traceFn(ILMTransition, nil, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fi.TransitionStatus = lifecycle.TransitionComplete
|
fi.TransitionStatus = lifecycle.TransitionComplete
|
||||||
|
|||||||
@@ -845,7 +845,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
// to decommission, just skip it, this also includes
|
// to decommission, just skip it, this also includes
|
||||||
// any other versions that have already expired.
|
// any other versions that have already expired.
|
||||||
remainingVersions := len(fivs.Versions) - expired
|
remainingVersions := len(fivs.Versions) - expired
|
||||||
if version.Deleted && remainingVersions == 1 {
|
if version.Deleted && remainingVersions == 1 && rcfg == nil {
|
||||||
decommissioned++
|
decommissioned++
|
||||||
stopFn(version.Size, errors.New("DELETE marked object with no other non-current versions will be skipped"))
|
stopFn(version.Size, errors.New("DELETE marked object with no other non-current versions will be skipped"))
|
||||||
continue
|
continue
|
||||||
|
|||||||
+15
-17
@@ -256,7 +256,7 @@ func (z *erasureServerPools) NewNSLock(bucket string, objects ...string) RWLocke
|
|||||||
|
|
||||||
// GetDisksID will return disks by their ID.
|
// GetDisksID will return disks by their ID.
|
||||||
func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI {
|
func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI {
|
||||||
idMap := make(map[string]struct{})
|
idMap := make(map[string]struct{}, len(ids))
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
idMap[id] = struct{}{}
|
idMap[id] = struct{}{}
|
||||||
}
|
}
|
||||||
@@ -357,7 +357,7 @@ func (p serverPoolsAvailableSpace) TotalAvailable() uint64 {
|
|||||||
|
|
||||||
// FilterMaxUsed will filter out any pools that has used percent bigger than max,
|
// FilterMaxUsed will filter out any pools that has used percent bigger than max,
|
||||||
// unless all have that, in which case all are preserved.
|
// unless all have that, in which case all are preserved.
|
||||||
func (p serverPoolsAvailableSpace) FilterMaxUsed(max int) {
|
func (p serverPoolsAvailableSpace) FilterMaxUsed(maxUsed int) {
|
||||||
// We aren't modifying p, only entries in it, so we don't need to receive a pointer.
|
// We aren't modifying p, only entries in it, so we don't need to receive a pointer.
|
||||||
if len(p) <= 1 {
|
if len(p) <= 1 {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
@@ -365,7 +365,7 @@ func (p serverPoolsAvailableSpace) FilterMaxUsed(max int) {
|
|||||||
}
|
}
|
||||||
var ok bool
|
var ok bool
|
||||||
for _, z := range p {
|
for _, z := range p {
|
||||||
if z.Available > 0 && z.MaxUsedPct < max {
|
if z.Available > 0 && z.MaxUsedPct < maxUsed {
|
||||||
ok = true
|
ok = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -378,7 +378,7 @@ func (p serverPoolsAvailableSpace) FilterMaxUsed(max int) {
|
|||||||
|
|
||||||
// Remove entries that are above.
|
// Remove entries that are above.
|
||||||
for i, z := range p {
|
for i, z := range p {
|
||||||
if z.Available > 0 && z.MaxUsedPct < max {
|
if z.Available > 0 && z.MaxUsedPct < maxUsed {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p[i].Available = 0
|
p[i].Available = 0
|
||||||
@@ -1858,16 +1858,6 @@ func (z *erasureServerPools) PutObjectPart(ctx context.Context, bucket, object,
|
|||||||
return PartInfo{}, err
|
return PartInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read lock for upload id.
|
|
||||||
// Only held while reading the upload metadata.
|
|
||||||
uploadIDRLock := z.NewNSLock(bucket, pathJoin(object, uploadID))
|
|
||||||
rlkctx, err := uploadIDRLock.GetRLock(ctx, globalOperationTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return PartInfo{}, err
|
|
||||||
}
|
|
||||||
ctx = rlkctx.Context()
|
|
||||||
defer uploadIDRLock.RUnlock(rlkctx)
|
|
||||||
|
|
||||||
if z.SinglePool() {
|
if z.SinglePool() {
|
||||||
return z.serverPools[0].PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts)
|
return z.serverPools[0].PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts)
|
||||||
}
|
}
|
||||||
@@ -2478,6 +2468,7 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
|||||||
// HealObjectFn closure function heals the object.
|
// HealObjectFn closure function heals the object.
|
||||||
type HealObjectFn func(bucket, object, versionID string, scanMode madmin.HealScanMode) error
|
type HealObjectFn func(bucket, object, versionID string, scanMode madmin.HealScanMode) error
|
||||||
|
|
||||||
|
// List a prefix or a single object versions and heal
|
||||||
func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObjectFn HealObjectFn) error {
|
func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObjectFn HealObjectFn) error {
|
||||||
healEntry := func(bucket string, entry metaCacheEntry, scanMode madmin.HealScanMode) error {
|
healEntry := func(bucket string, entry metaCacheEntry, scanMode madmin.HealScanMode) error {
|
||||||
if entry.isDir() {
|
if entry.isDir() {
|
||||||
@@ -2541,7 +2532,7 @@ func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix str
|
|||||||
go func(idx int, set *erasureObjects) {
|
go func(idx int, set *erasureObjects) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
errs[idx] = set.listAndHeal(ctx, bucket, prefix, opts.ScanMode, healEntry)
|
errs[idx] = set.listAndHeal(ctx, bucket, prefix, opts.Recursive, opts.ScanMode, healEntry)
|
||||||
}(idx, set)
|
}(idx, set)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -2593,15 +2584,22 @@ func (z *erasureServerPools) HealObject(ctx context.Context, bucket, object, ver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hr := madmin.HealResultItem{
|
||||||
|
Type: madmin.HealItemObject,
|
||||||
|
Bucket: bucket,
|
||||||
|
Object: object,
|
||||||
|
VersionID: versionID,
|
||||||
|
}
|
||||||
|
|
||||||
// At this stage, all errors are 'not found'
|
// At this stage, all errors are 'not found'
|
||||||
if versionID != "" {
|
if versionID != "" {
|
||||||
return madmin.HealResultItem{}, VersionNotFound{
|
return hr, VersionNotFound{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: object,
|
Object: object,
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return madmin.HealResultItem{}, ObjectNotFound{
|
return hr, ObjectNotFound{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: object,
|
Object: object,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -286,6 +288,10 @@ func (driver *ftpDriver) CheckPasswd(c *ftp.Context, username, password string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error) {
|
func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error) {
|
||||||
|
tr := http.RoundTripper(globalRemoteFTPClientTransport)
|
||||||
|
if host, _, err := net.SplitHostPort(ctx.Sess.RemoteAddr().String()); err == nil {
|
||||||
|
tr = forwardForTransport{tr: tr, fwd: host}
|
||||||
|
}
|
||||||
ui, ok := globalIAMSys.GetUser(context.Background(), ctx.Sess.LoginUser())
|
ui, ok := globalIAMSys.GetUser(context.Background(), ctx.Sess.LoginUser())
|
||||||
if !ok && !globalIAMSys.LDAPConfig.Enabled() {
|
if !ok && !globalIAMSys.LDAPConfig.Enabled() {
|
||||||
return nil, errNoSuchUser
|
return nil, errNoSuchUser
|
||||||
@@ -363,7 +369,7 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error)
|
|||||||
return minio.New(driver.endpoint, &minio.Options{
|
return minio.New(driver.endpoint, &minio.Options{
|
||||||
Creds: mcreds,
|
Creds: mcreds,
|
||||||
Secure: globalIsTLS,
|
Secure: globalIsTLS,
|
||||||
Transport: globalRemoteFTPClientTransport,
|
Transport: tr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +383,7 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error)
|
|||||||
return minio.New(driver.endpoint, &minio.Options{
|
return minio.New(driver.endpoint, &minio.Options{
|
||||||
Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""),
|
Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""),
|
||||||
Secure: globalIsTLS,
|
Secure: globalIsTLS,
|
||||||
Transport: globalRemoteFTPClientTransport,
|
Transport: tr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/grid"
|
"github.com/minio/minio/internal/grid"
|
||||||
xnet "github.com/minio/pkg/v3/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
|
|
||||||
"github.com/minio/minio/internal/amztime"
|
"github.com/minio/minio/internal/amztime"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
@@ -75,7 +73,7 @@ const (
|
|||||||
// and must not set by clients
|
// and must not set by clients
|
||||||
func containsReservedMetadata(header http.Header) bool {
|
func containsReservedMetadata(header http.Header) bool {
|
||||||
for key := range header {
|
for key := range header {
|
||||||
if slices.Contains(maps.Keys(validSSEReplicationHeaders), key) {
|
if _, ok := validSSEReplicationHeaders[key]; ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if stringsHasPrefixFold(key, ReservedMetadataPrefix) {
|
if stringsHasPrefixFold(key, ReservedMetadataPrefix) {
|
||||||
|
|||||||
+7
-7
@@ -459,8 +459,6 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var versionHealed bool
|
|
||||||
|
|
||||||
res, err := er.HealObject(ctx, bucket, encodedEntryName,
|
res, err := er.HealObject(ctx, bucket, encodedEntryName,
|
||||||
version.VersionID, madmin.HealOpts{
|
version.VersionID, madmin.HealOpts{
|
||||||
ScanMode: scanMode,
|
ScanMode: scanMode,
|
||||||
@@ -475,21 +473,23 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Look for the healing results
|
// Look for the healing results
|
||||||
if res.After.Drives[tracker.DiskIndex].State == madmin.DriveStateOk {
|
if res.After.Drives[tracker.DiskIndex].State != madmin.DriveStateOk {
|
||||||
versionHealed = true
|
err = fmt.Errorf("unexpected after heal state: %s", res.After.Drives[tracker.DiskIndex].State)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if versionHealed {
|
if err == nil {
|
||||||
bgSeq.countHealed(madmin.HealItemObject)
|
bgSeq.countHealed(madmin.HealItemObject)
|
||||||
result = healEntrySuccess(uint64(version.Size))
|
result = healEntrySuccess(uint64(version.Size))
|
||||||
} else {
|
} else {
|
||||||
bgSeq.countFailed(madmin.HealItemObject)
|
bgSeq.countFailed(madmin.HealItemObject)
|
||||||
result = healEntryFailure(uint64(version.Size))
|
result = healEntryFailure(uint64(version.Size))
|
||||||
if version.VersionID != "" {
|
if version.VersionID != "" {
|
||||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err))
|
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s (version-id=%s): %w",
|
||||||
|
bucket, version.Name, version.VersionID, err))
|
||||||
} else {
|
} else {
|
||||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, version.Name, err))
|
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w",
|
||||||
|
bucket, version.Name, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -793,7 +793,10 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
for _, item := range listedConfigItems[stsListKey] {
|
for _, item := range listedConfigItems[stsListKey] {
|
||||||
userName := path.Dir(item)
|
userName := path.Dir(item)
|
||||||
// loadUser() will delete expired user during the load.
|
// loadUser() will delete expired user during the load.
|
||||||
iamLogIf(ctx, iamOS.loadUser(ctx, userName, stsUser, stsAccountsFromStore))
|
err := iamOS.loadUser(ctx, userName, stsUser, stsAccountsFromStore)
|
||||||
|
if err != nil && !errors.Is(err, errNoSuchUser) {
|
||||||
|
iamLogIf(ctx, err)
|
||||||
|
}
|
||||||
// No need to return errors for failed expiration of STS users
|
// No need to return errors for failed expiration of STS users
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,7 +804,10 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
// (removed during loadUser() in the loop above) are removed from memory.
|
// (removed during loadUser() in the loop above) are removed from memory.
|
||||||
for _, item := range listedConfigItems[policyDBSTSUsersListKey] {
|
for _, item := range listedConfigItems[policyDBSTSUsersListKey] {
|
||||||
stsName := strings.TrimSuffix(item, ".json")
|
stsName := strings.TrimSuffix(item, ".json")
|
||||||
iamLogIf(ctx, iamOS.loadMappedPolicy(ctx, stsName, stsUser, false, stsAccPoliciesFromStore))
|
err := iamOS.loadMappedPolicy(ctx, stsName, stsUser, false, stsAccPoliciesFromStore)
|
||||||
|
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||||
|
iamLogIf(ctx, err)
|
||||||
|
}
|
||||||
// No need to return errors for failed expiration of STS users
|
// No need to return errors for failed expiration of STS users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+91
-14
@@ -26,6 +26,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
@@ -37,6 +38,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/jwt"
|
"github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/pkg/v3/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v3/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
@@ -357,6 +359,68 @@ func (c *iamCache) removeGroupFromMembershipsMap(group string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *iamCache) policyDBGetGroups(store *IAMStoreSys, userPolicyPresent bool, groups ...string) ([]string, error) {
|
||||||
|
var policies []string
|
||||||
|
for _, group := range groups {
|
||||||
|
if store.getUsersSysType() == MinIOUsersSysType {
|
||||||
|
g, ok := c.iamGroupsMap[group]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group is disabled, so we return no policy - this
|
||||||
|
// ensures the request is denied.
|
||||||
|
if g.Status == statusDisabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, ok := c.iamGroupPolicyMap.Load(group)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
policies = append(policies, policy.toSlice()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := len(policies) > 0
|
||||||
|
if found {
|
||||||
|
return policies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if userPolicyPresent {
|
||||||
|
// if user mapping present and no group policies found
|
||||||
|
// rely on user policy for access, instead of fallback.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
// no mappings found, fallback for all groups.
|
||||||
|
g := errgroup.WithNErrs(len(groups)).WithConcurrency(10) // load like 10 groups at a time.
|
||||||
|
|
||||||
|
for index := range groups {
|
||||||
|
index := index
|
||||||
|
g.Go(func() error {
|
||||||
|
err := store.loadMappedPolicy(context.TODO(), groups[index], regUser, true, c.iamGroupPolicyMap)
|
||||||
|
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if errors.Is(err, errNoSuchPolicy) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
policy, _ := c.iamGroupPolicyMap.Load(groups[index])
|
||||||
|
mu.Lock()
|
||||||
|
policies = append(policies, policy.toSlice()...)
|
||||||
|
mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := errors.Join(g.Wait()...)
|
||||||
|
return policies, err
|
||||||
|
}
|
||||||
|
|
||||||
// policyDBGet - lower-level helper; does not take locks.
|
// policyDBGet - lower-level helper; does not take locks.
|
||||||
//
|
//
|
||||||
// If a group is passed, it returns policies associated with the group.
|
// If a group is passed, it returns policies associated with the group.
|
||||||
@@ -676,7 +740,8 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) erro
|
|||||||
type IAMStoreSys struct {
|
type IAMStoreSys struct {
|
||||||
IAMStorageAPI
|
IAMStorageAPI
|
||||||
|
|
||||||
group *singleflight.Group
|
group *singleflight.Group
|
||||||
|
policy *singleflight.Group
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasWatcher - returns if the storage system has a watcher.
|
// HasWatcher - returns if the storage system has a watcher.
|
||||||
@@ -757,21 +822,32 @@ func (store *IAMStoreSys) PolicyDBGet(name string, groups ...string) ([]string,
|
|||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
|
|
||||||
policies, _, err := cache.policyDBGet(store, name, false, false)
|
getPolicies := func() ([]string, error) {
|
||||||
if err != nil {
|
policies, _, err := cache.policyDBGet(store, name, false, false)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userPolicyPresent := len(policies) > 0
|
|
||||||
for _, group := range groups {
|
|
||||||
ps, _, err := cache.policyDBGet(store, group, true, userPolicyPresent)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
policies = append(policies, ps...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return policies, nil
|
userPolicyPresent := len(policies) > 0
|
||||||
|
|
||||||
|
groupPolicies, err := cache.policyDBGetGroups(store, userPolicyPresent, groups...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
policies = append(policies, groupPolicies...)
|
||||||
|
return policies, nil
|
||||||
|
}
|
||||||
|
if store.policy != nil {
|
||||||
|
val, err, _ := store.policy.Do(name, func() (interface{}, error) {
|
||||||
|
return getPolicies()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return val.([]string), nil
|
||||||
|
}
|
||||||
|
return getPolicies()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddUsersToGroup - adds users to group, creating the group if needed.
|
// AddUsersToGroup - adds users to group, creating the group if needed.
|
||||||
@@ -2009,7 +2085,8 @@ type ParentUserInfo struct {
|
|||||||
// GetAllParentUsers - returns all distinct "parent-users" associated with STS
|
// GetAllParentUsers - returns all distinct "parent-users" associated with STS
|
||||||
// or service credentials, mapped to all distinct roleARNs associated with the
|
// or service credentials, mapped to all distinct roleARNs associated with the
|
||||||
// parent user. The dummy role ARN is associated with parent users from
|
// parent user. The dummy role ARN is associated with parent users from
|
||||||
// policy-claim based OpenID providers.
|
// policy-claim based OpenID providers. The root credential as a parent
|
||||||
|
// user is not included in the result.
|
||||||
func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
|
func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
|
||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
@@ -2048,7 +2125,7 @@ func (store *IAMStoreSys) getParentUsers(cache *iamCache) map[string]ParentUserI
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if cred.ParentUser == "" {
|
if cred.ParentUser == "" || cred.ParentUser == globalActiveCred.AccessKey {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-4
@@ -178,13 +178,18 @@ func (sys *IAMSys) initStore(objAPI ObjectLayer, etcdClient *etcd.Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if etcdClient == nil {
|
if etcdClient == nil {
|
||||||
var group *singleflight.Group
|
var (
|
||||||
|
group *singleflight.Group
|
||||||
|
policy *singleflight.Group
|
||||||
|
)
|
||||||
if env.Get("_MINIO_IAM_SINGLE_FLIGHT", config.EnableOn) == config.EnableOn {
|
if env.Get("_MINIO_IAM_SINGLE_FLIGHT", config.EnableOn) == config.EnableOn {
|
||||||
group = &singleflight.Group{}
|
group = &singleflight.Group{}
|
||||||
|
policy = &singleflight.Group{}
|
||||||
}
|
}
|
||||||
sys.store = &IAMStoreSys{
|
sys.store = &IAMStoreSys{
|
||||||
IAMStorageAPI: newIAMObjectStore(objAPI, sys.usersSysType),
|
IAMStorageAPI: newIAMObjectStore(objAPI, sys.usersSysType),
|
||||||
group: group,
|
group: group,
|
||||||
|
policy: policy,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sys.store = &IAMStoreSys{IAMStorageAPI: newIAMEtcdStore(etcdClient, sys.usersSysType)}
|
sys.store = &IAMStoreSys{IAMStorageAPI: newIAMEtcdStore(etcdClient, sys.usersSysType)}
|
||||||
@@ -406,6 +411,7 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
|
|||||||
timer := time.NewTimer(waitInterval())
|
timer := time.NewTimer(waitInterval())
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
|
|
||||||
|
lastPurgeHour := -1
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
@@ -421,9 +427,9 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following actions are performed about once in 4 times that
|
// Run purge routines once in each hour.
|
||||||
// IAM is refreshed:
|
if refreshStart.Hour() != lastPurgeHour {
|
||||||
if r.Intn(4) == 0 {
|
lastPurgeHour = refreshStart.Hour()
|
||||||
// Poll and remove accounts for those users who were removed
|
// Poll and remove accounts for those users who were removed
|
||||||
// from LDAP/OpenID.
|
// from LDAP/OpenID.
|
||||||
if sys.LDAPConfig.Enabled() {
|
if sys.LDAPConfig.Enabled() {
|
||||||
|
|||||||
+12
-16
@@ -29,6 +29,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
@@ -182,8 +183,7 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
|
|||||||
resultsDone := make(chan metaCacheEntriesSorted)
|
resultsDone := make(chan metaCacheEntriesSorted)
|
||||||
// Copy so we can mutate
|
// Copy so we can mutate
|
||||||
resCh := resultsDone
|
resCh := resultsDone
|
||||||
var done bool
|
var done atomic.Bool
|
||||||
var mu sync.Mutex
|
|
||||||
resErr := io.EOF
|
resErr := io.EOF
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -194,9 +194,7 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
|
|||||||
// past limit
|
// past limit
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mu.Lock()
|
returned = done.Load()
|
||||||
returned = done
|
|
||||||
mu.Unlock()
|
|
||||||
if returned {
|
if returned {
|
||||||
resCh = nil
|
resCh = nil
|
||||||
continue
|
continue
|
||||||
@@ -250,9 +248,7 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
|
|||||||
return func() (metaCacheEntriesSorted, error) {
|
return func() (metaCacheEntriesSorted, error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
mu.Lock()
|
done.Store(true)
|
||||||
done = true
|
|
||||||
mu.Unlock()
|
|
||||||
return metaCacheEntriesSorted{}, ctx.Err()
|
return metaCacheEntriesSorted{}, ctx.Err()
|
||||||
case r := <-resultsDone:
|
case r := <-resultsDone:
|
||||||
return r, resErr
|
return r, resErr
|
||||||
@@ -626,18 +622,18 @@ func calcCommonWritesDeletes(infos []DiskInfo, readQuorum int) (commonWrite, com
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter := func(list []uint64) (commonCount uint64) {
|
filter := func(list []uint64) (commonCount uint64) {
|
||||||
max := 0
|
maxCnt := 0
|
||||||
signatureMap := map[uint64]int{}
|
signatureMap := map[uint64]int{}
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
signatureMap[v]++
|
signatureMap[v]++
|
||||||
}
|
}
|
||||||
for ops, count := range signatureMap {
|
for ops, count := range signatureMap {
|
||||||
if max < count && commonCount < ops {
|
if maxCnt < count && commonCount < ops {
|
||||||
max = count
|
maxCnt = count
|
||||||
commonCount = ops
|
commonCount = ops
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if max < readQuorum {
|
if maxCnt < readQuorum {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return commonCount
|
return commonCount
|
||||||
@@ -650,7 +646,7 @@ func calcCommonWritesDeletes(infos []DiskInfo, readQuorum int) (commonWrite, com
|
|||||||
|
|
||||||
func calcCommonCounter(infos []DiskInfo, readQuorum int) (commonCount uint64) {
|
func calcCommonCounter(infos []DiskInfo, readQuorum int) (commonCount uint64) {
|
||||||
filter := func() (commonCount uint64) {
|
filter := func() (commonCount uint64) {
|
||||||
max := 0
|
maxCnt := 0
|
||||||
signatureMap := map[uint64]int{}
|
signatureMap := map[uint64]int{}
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
if info.Error != "" {
|
if info.Error != "" {
|
||||||
@@ -660,12 +656,12 @@ func calcCommonCounter(infos []DiskInfo, readQuorum int) (commonCount uint64) {
|
|||||||
signatureMap[mutations]++
|
signatureMap[mutations]++
|
||||||
}
|
}
|
||||||
for ops, count := range signatureMap {
|
for ops, count := range signatureMap {
|
||||||
if max < count && commonCount < ops {
|
if maxCnt < count && commonCount < ops {
|
||||||
max = count
|
maxCnt = count
|
||||||
commonCount = ops
|
commonCount = ops
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if max < readQuorum {
|
if maxCnt < readQuorum {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return commonCount
|
return commonCount
|
||||||
|
|||||||
+17
-3
@@ -685,6 +685,16 @@ func getUsageLastScanActivityMD() MetricDescription {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBucketUsageLastScanActivityMD() MetricDescription {
|
||||||
|
return MetricDescription{
|
||||||
|
Namespace: bucketMetricNamespace,
|
||||||
|
Subsystem: usageSubsystem,
|
||||||
|
Name: lastActivityTime,
|
||||||
|
Help: "Time elapsed (in nano seconds) since last scan activity",
|
||||||
|
Type: gaugeMetric,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getBucketUsageQuotaTotalBytesMD() MetricDescription {
|
func getBucketUsageQuotaTotalBytesMD() MetricDescription {
|
||||||
return MetricDescription{
|
return MetricDescription{
|
||||||
Namespace: bucketMetricNamespace,
|
Namespace: bucketMetricNamespace,
|
||||||
@@ -1875,13 +1885,17 @@ func getHistogramMetrics(hist *prometheus.HistogramVec, desc MetricDescription,
|
|||||||
// add metrics with +Inf label
|
// add metrics with +Inf label
|
||||||
labels1 := make(map[string]string)
|
labels1 := make(map[string]string)
|
||||||
for _, lp := range dtoMetric.GetLabel() {
|
for _, lp := range dtoMetric.GetLabel() {
|
||||||
labels1[*lp.Name] = *lp.Value
|
if *lp.Name == "api" && toLowerAPILabels {
|
||||||
|
labels1[*lp.Name] = strings.ToLower(*lp.Value)
|
||||||
|
} else {
|
||||||
|
labels1[*lp.Name] = *lp.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
labels1["le"] = fmt.Sprintf("%.3f", math.Inf(+1))
|
labels1["le"] = fmt.Sprintf("%.3f", math.Inf(+1))
|
||||||
metrics = append(metrics, MetricV2{
|
metrics = append(metrics, MetricV2{
|
||||||
Description: desc,
|
Description: desc,
|
||||||
VariableLabels: labels1,
|
VariableLabels: labels1,
|
||||||
Value: dtoMetric.Counter.GetValue(),
|
Value: float64(dtoMetric.Histogram.GetSampleCount()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return metrics
|
return metrics
|
||||||
@@ -3239,7 +3253,7 @@ func getBucketUsageMetrics(opts MetricsGroupOpts) *MetricsGroupV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
metrics = append(metrics, MetricV2{
|
metrics = append(metrics, MetricV2{
|
||||||
Description: getUsageLastScanActivityMD(),
|
Description: getBucketUsageLastScanActivityMD(),
|
||||||
Value: float64(time.Since(dataUsageInfo.LastUpdate)),
|
Value: float64(time.Since(dataUsageInfo.LastUpdate)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+133
-2
@@ -18,13 +18,15 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetHistogramMetrics(t *testing.T) {
|
func TestGetHistogramMetrics_BucketCount(t *testing.T) {
|
||||||
histBuckets := []float64{0.05, 0.1, 0.25, 0.5, 0.75}
|
histBuckets := []float64{0.05, 0.1, 0.25, 0.5, 0.75}
|
||||||
labels := []string{"GetObject", "PutObject", "CopyObject", "CompleteMultipartUpload"}
|
labels := []string{"GetObject", "PutObject", "CopyObject", "CompleteMultipartUpload"}
|
||||||
ttfbHist := prometheus.NewHistogramVec(
|
ttfbHist := prometheus.NewHistogramVec(
|
||||||
@@ -83,6 +85,135 @@ func TestGetHistogramMetrics(t *testing.T) {
|
|||||||
metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), false)
|
metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), false)
|
||||||
// additional labels for +Inf for all histogram metrics
|
// additional labels for +Inf for all histogram metrics
|
||||||
if expPoints := len(labels) * (len(histBuckets) + 1); expPoints != len(metrics) {
|
if expPoints := len(labels) * (len(histBuckets) + 1); expPoints != len(metrics) {
|
||||||
t.Fatalf("Expected %v data points but got %v", expPoints, len(metrics))
|
t.Fatalf("Expected %v data points when toLowerAPILabels=false but got %v", expPoints, len(metrics))
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics = getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), true)
|
||||||
|
// additional labels for +Inf for all histogram metrics
|
||||||
|
if expPoints := len(labels) * (len(histBuckets) + 1); expPoints != len(metrics) {
|
||||||
|
t.Fatalf("Expected %v data points when toLowerAPILabels=true but got %v", expPoints, len(metrics))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHistogramMetrics_Values(t *testing.T) {
|
||||||
|
histBuckets := []float64{0.50, 5.00}
|
||||||
|
labels := []string{"PutObject", "CopyObject"}
|
||||||
|
ttfbHist := prometheus.NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "s3_ttfb_seconds",
|
||||||
|
Help: "Time taken by requests served by current MinIO server instance",
|
||||||
|
Buckets: histBuckets,
|
||||||
|
},
|
||||||
|
[]string{"api"},
|
||||||
|
)
|
||||||
|
observations := []struct {
|
||||||
|
val float64
|
||||||
|
label string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
val: 0.02,
|
||||||
|
label: labels[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 0.19,
|
||||||
|
label: labels[1],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 0.31,
|
||||||
|
label: labels[1],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 0.61,
|
||||||
|
label: labels[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 6.79,
|
||||||
|
label: labels[1],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ticker := time.NewTicker(1 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for _, obs := range observations {
|
||||||
|
// Send observations once every 1ms, to simulate delay between
|
||||||
|
// observations. This is to test the channel based
|
||||||
|
// synchronization used internally.
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
ttfbHist.With(prometheus.Labels{"api": obs.label}).Observe(obs.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate regular-cased API label metrics for 'PutObject' for deeper verification
|
||||||
|
metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), false)
|
||||||
|
capitalPutObjects := make([]MetricV2, 0, len(histBuckets)+1)
|
||||||
|
for _, metric := range metrics {
|
||||||
|
if value := metric.VariableLabels["api"]; value == "PutObject" {
|
||||||
|
capitalPutObjects = append(capitalPutObjects, metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expMetricsPerAPI := len(histBuckets) + 1; expMetricsPerAPI != len(capitalPutObjects) {
|
||||||
|
t.Fatalf("Expected %d api=PutObject metrics but got %d", expMetricsPerAPI, len(capitalPutObjects))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deterministic ordering
|
||||||
|
slices.SortFunc(capitalPutObjects, func(a MetricV2, b MetricV2) int {
|
||||||
|
le1 := a.VariableLabels["le"]
|
||||||
|
le2 := a.VariableLabels["le"]
|
||||||
|
return strings.Compare(le1, le2)
|
||||||
|
})
|
||||||
|
if le := capitalPutObjects[0].VariableLabels["le"]; le != "0.500" {
|
||||||
|
t.Errorf("Expected le='0.050' api=PutObject metrics but got '%v'", le)
|
||||||
|
}
|
||||||
|
if value := capitalPutObjects[0].Value; value != 1 {
|
||||||
|
t.Errorf("Expected le='0.050' api=PutObject value to be 1 but got '%v'", value)
|
||||||
|
}
|
||||||
|
if le := capitalPutObjects[1].VariableLabels["le"]; le != "5.000" {
|
||||||
|
t.Errorf("Expected le='5.000' api=PutObject metrics but got '%v'", le)
|
||||||
|
}
|
||||||
|
if value := capitalPutObjects[1].Value; value != 2 {
|
||||||
|
t.Errorf("Expected le='5.000' api=PutObject value to be 2 but got '%v'", value)
|
||||||
|
}
|
||||||
|
if le := capitalPutObjects[2].VariableLabels["le"]; le != "+Inf" {
|
||||||
|
t.Errorf("Expected le='+Inf' api=PutObject metrics but got '%v'", le)
|
||||||
|
}
|
||||||
|
if value := capitalPutObjects[2].Value; value != 2 {
|
||||||
|
t.Errorf("Expected le='+Inf' api=PutObject value to be 2 but got '%v'", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate lower-cased API label metrics for 'copyobject' for deeper verification
|
||||||
|
metrics = getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), true)
|
||||||
|
lowerCopyObjects := make([]MetricV2, 0, len(histBuckets)+1)
|
||||||
|
for _, metric := range metrics {
|
||||||
|
if value := metric.VariableLabels["api"]; value == "copyobject" {
|
||||||
|
lowerCopyObjects = append(lowerCopyObjects, metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expMetricsPerAPI := len(histBuckets) + 1; expMetricsPerAPI != len(lowerCopyObjects) {
|
||||||
|
t.Fatalf("Expected %d api=copyobject metrics but got %d", expMetricsPerAPI, len(lowerCopyObjects))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deterministic ordering
|
||||||
|
slices.SortFunc(lowerCopyObjects, func(a MetricV2, b MetricV2) int {
|
||||||
|
le1 := a.VariableLabels["le"]
|
||||||
|
le2 := a.VariableLabels["le"]
|
||||||
|
return strings.Compare(le1, le2)
|
||||||
|
})
|
||||||
|
if le := lowerCopyObjects[0].VariableLabels["le"]; le != "0.500" {
|
||||||
|
t.Errorf("Expected le='0.050' api=copyobject metrics but got '%v'", le)
|
||||||
|
}
|
||||||
|
if value := lowerCopyObjects[0].Value; value != 2 {
|
||||||
|
t.Errorf("Expected le='0.050' api=copyobject value to be 2 but got '%v'", value)
|
||||||
|
}
|
||||||
|
if le := lowerCopyObjects[1].VariableLabels["le"]; le != "5.000" {
|
||||||
|
t.Errorf("Expected le='5.000' api=copyobject metrics but got '%v'", le)
|
||||||
|
}
|
||||||
|
if value := lowerCopyObjects[1].Value; value != 2 {
|
||||||
|
t.Errorf("Expected le='5.000' api=copyobject value to be 2 but got '%v'", value)
|
||||||
|
}
|
||||||
|
if le := lowerCopyObjects[2].VariableLabels["le"]; le != "+Inf" {
|
||||||
|
t.Errorf("Expected le='+Inf' api=copyobject metrics but got '%v'", le)
|
||||||
|
}
|
||||||
|
if value := lowerCopyObjects[2].Value; value != 3 {
|
||||||
|
t.Errorf("Expected le='+Inf' api=copyobject value to be 3 but got '%v'", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -27,7 +28,6 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type collectorPath string
|
type collectorPath string
|
||||||
|
|||||||
@@ -404,12 +404,7 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
|
|||||||
metadata = make(map[string]string)
|
metadata = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantCRC, err := hash.GetContentChecksum(hdr)
|
|
||||||
if err != nil {
|
|
||||||
return opts, fmt.Errorf("invalid/unknown checksum sent: %v", err)
|
|
||||||
}
|
|
||||||
etag := strings.TrimSpace(hdr.Get(xhttp.MinIOSourceETag))
|
etag := strings.TrimSpace(hdr.Get(xhttp.MinIOSourceETag))
|
||||||
|
|
||||||
if crypto.S3KMS.IsRequested(hdr) {
|
if crypto.S3KMS.IsRequested(hdr) {
|
||||||
keyID, context, err := crypto.S3KMS.ParseHTTP(hdr)
|
keyID, context, err := crypto.S3KMS.ParseHTTP(hdr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -423,7 +418,6 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
|
|||||||
ServerSideEncryption: sseKms,
|
ServerSideEncryption: sseKms,
|
||||||
UserDefined: metadata,
|
UserDefined: metadata,
|
||||||
MTime: mtime,
|
MTime: mtime,
|
||||||
WantChecksum: wantCRC,
|
|
||||||
PreserveETag: etag,
|
PreserveETag: etag,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -438,7 +432,6 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
|
|||||||
opts.ReplicationSourceRetentionTimestamp = retaintimestmp
|
opts.ReplicationSourceRetentionTimestamp = retaintimestmp
|
||||||
opts.ReplicationSourceTaggingTimestamp = taggingtimestmp
|
opts.ReplicationSourceTaggingTimestamp = taggingtimestmp
|
||||||
opts.PreserveETag = etag
|
opts.PreserveETag = etag
|
||||||
opts.WantChecksum = wantCRC
|
|
||||||
|
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -52,7 +53,6 @@ import (
|
|||||||
"github.com/minio/pkg/v3/trie"
|
"github.com/minio/pkg/v3/trie"
|
||||||
"github.com/minio/pkg/v3/wildcard"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"github.com/valyala/bytebufferpool"
|
"github.com/valyala/bytebufferpool"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -352,11 +352,11 @@ func isETagEqual(left, right string) bool {
|
|||||||
// setPutObjHeaders sets all the necessary headers returned back
|
// setPutObjHeaders sets all the necessary headers returned back
|
||||||
// upon a success Put/Copy/CompleteMultipart/Delete requests
|
// upon a success Put/Copy/CompleteMultipart/Delete requests
|
||||||
// to activate delete only headers set delete as true
|
// to activate delete only headers set delete as true
|
||||||
func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, delete bool, h http.Header) {
|
func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, del bool, h http.Header) {
|
||||||
// We must not use the http.Header().Set method here because some (broken)
|
// We must not use the http.Header().Set method here because some (broken)
|
||||||
// clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive).
|
// clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive).
|
||||||
// Therefore, we have to set the ETag directly as map entry.
|
// Therefore, we have to set the ETag directly as map entry.
|
||||||
if objInfo.ETag != "" && !delete {
|
if objInfo.ETag != "" && !del {
|
||||||
w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`}
|
w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,13 +364,13 @@ func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, delete bool, h
|
|||||||
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
|
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
|
||||||
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
||||||
// If version is a deleted marker, set this header as well
|
// If version is a deleted marker, set this header as well
|
||||||
if objInfo.DeleteMarker && delete { // only returned during delete object
|
if objInfo.DeleteMarker && del { // only returned during delete object
|
||||||
w.Header()[xhttp.AmzDeleteMarker] = []string{strconv.FormatBool(objInfo.DeleteMarker)}
|
w.Header()[xhttp.AmzDeleteMarker] = []string{strconv.FormatBool(objInfo.DeleteMarker)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if objInfo.Bucket != "" && objInfo.Name != "" {
|
if objInfo.Bucket != "" && objInfo.Name != "" {
|
||||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !delete {
|
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !del {
|
||||||
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
|
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-11
@@ -1191,6 +1191,9 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanitize the source object name similar to NewMultipart and PutObject API
|
||||||
|
srcObject = trimLeadingSlash(srcObject)
|
||||||
|
|
||||||
if vid != "" && vid != nullVersionID {
|
if vid != "" && vid != nullVersionID {
|
||||||
_, err := uuid.Parse(vid)
|
_, err := uuid.Parse(vid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1899,6 +1902,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
reader = rd
|
reader = rd
|
||||||
|
|
||||||
|
var opts ObjectOptions
|
||||||
|
opts, err = putOptsFromReq(ctx, r, bucket, object, metadata)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
actualSize := size
|
actualSize := size
|
||||||
var idxCb func() []byte
|
var idxCb func() []byte
|
||||||
if isCompressible(r.Header, object) && size > minCompressibleSize {
|
if isCompressible(r.Header, object) && size > minCompressibleSize {
|
||||||
@@ -1915,6 +1925,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
opts.WantChecksum = actualReader.Checksum()
|
||||||
|
|
||||||
// Set compression metrics.
|
// Set compression metrics.
|
||||||
var s2c io.ReadCloser
|
var s2c io.ReadCloser
|
||||||
wantEncryption := crypto.Requested(r.Header)
|
wantEncryption := crypto.Requested(r.Header)
|
||||||
@@ -1945,22 +1957,17 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := hashReader.AddChecksum(r, size < 0); err != nil {
|
if size >= 0 {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
if err := hashReader.AddChecksum(r, false); err != nil {
|
||||||
return
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.WantChecksum = hashReader.Checksum()
|
||||||
}
|
}
|
||||||
|
|
||||||
rawReader := hashReader
|
rawReader := hashReader
|
||||||
pReader := NewPutObjReader(rawReader)
|
pReader := NewPutObjReader(rawReader)
|
||||||
|
|
||||||
var opts ObjectOptions
|
|
||||||
opts, err = putOptsFromReq(ctx, r, bucket, object, metadata)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
opts.IndexCB = idxCb
|
opts.IndexCB = idxCb
|
||||||
opts.WantChecksum = hashReader.Checksum()
|
|
||||||
|
|
||||||
if opts.PreserveETag != "" ||
|
if opts.PreserveETag != "" ||
|
||||||
r.Header.Get(xhttp.IfMatch) != "" ||
|
r.Header.Get(xhttp.IfMatch) != "" ||
|
||||||
@@ -2899,6 +2906,8 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
|
reqInfo.SetTags("retention", objRetention.String())
|
||||||
|
|
||||||
opts, err := getOpts(ctx, r, bucket, object)
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1204,6 +1204,10 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
|
|||||||
listPartsInfo.Parts[i].ETag = tryDecryptETag(objectEncryptionKey, p.ETag, kind == crypto.S3)
|
listPartsInfo.Parts[i].ETag = tryDecryptETag(objectEncryptionKey, p.ETag, kind == crypto.S3)
|
||||||
listPartsInfo.Parts[i].Size = p.ActualSize
|
listPartsInfo.Parts[i].Size = p.ActualSize
|
||||||
}
|
}
|
||||||
|
} else if _, ok := listPartsInfo.UserDefined[ReservedMetadataPrefix+"compression"]; ok {
|
||||||
|
for i, p := range listPartsInfo.Parts {
|
||||||
|
listPartsInfo.Parts[i].Size = p.ActualSize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response := generateListPartsResponse(listPartsInfo, encodingType)
|
response := generateListPartsResponse(listPartsInfo, encodingType)
|
||||||
|
|||||||
+17
-19
@@ -60,20 +60,19 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if err == syscall.ERROR_NO_MORE_FILES {
|
if err == syscall.ERROR_NO_MORE_FILES {
|
||||||
break
|
break
|
||||||
} else {
|
|
||||||
if isSysErrPathNotFound(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = osErrToFileErr(&os.PathError{
|
|
||||||
Op: "FindNextFile",
|
|
||||||
Path: dirPath,
|
|
||||||
Err: err,
|
|
||||||
})
|
|
||||||
if err == errFileNotFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
if isSysErrPathNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = osErrToFileErr(&os.PathError{
|
||||||
|
Op: "FindNextFile",
|
||||||
|
Path: dirPath,
|
||||||
|
Err: err,
|
||||||
|
})
|
||||||
|
if err == errFileNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
name := syscall.UTF16ToString(data.FileName[0:])
|
name := syscall.UTF16ToString(data.FileName[0:])
|
||||||
if name == "" || name == "." || name == ".." { // Useless names
|
if name == "" || name == "." || name == ".." { // Useless names
|
||||||
@@ -136,13 +135,12 @@ func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if err == syscall.ERROR_NO_MORE_FILES {
|
if err == syscall.ERROR_NO_MORE_FILES {
|
||||||
break
|
break
|
||||||
} else {
|
|
||||||
return nil, osErrToFileErr(&os.PathError{
|
|
||||||
Op: "FindNextFile",
|
|
||||||
Path: dirPath,
|
|
||||||
Err: err,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return nil, osErrToFileErr(&os.PathError{
|
||||||
|
Op: "FindNextFile",
|
||||||
|
Path: dirPath,
|
||||||
|
Err: err,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
name := syscall.UTF16ToString(data.FileName[0:])
|
name := syscall.UTF16ToString(data.FileName[0:])
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ var (
|
|||||||
aoBucketInfo = grid.NewArrayOf[*BucketInfo](func() *BucketInfo { return &BucketInfo{} })
|
aoBucketInfo = grid.NewArrayOf[*BucketInfo](func() *BucketInfo { return &BucketInfo{} })
|
||||||
aoMetricsGroup = grid.NewArrayOf[*MetricV2](func() *MetricV2 { return &MetricV2{} })
|
aoMetricsGroup = grid.NewArrayOf[*MetricV2](func() *MetricV2 { return &MetricV2{} })
|
||||||
madminBgHealState = grid.NewJSONPool[madmin.BgHealState]()
|
madminBgHealState = grid.NewJSONPool[madmin.BgHealState]()
|
||||||
|
madminHealResultItem = grid.NewJSONPool[madmin.HealResultItem]()
|
||||||
madminCPUs = grid.NewJSONPool[madmin.CPUs]()
|
madminCPUs = grid.NewJSONPool[madmin.CPUs]()
|
||||||
madminMemInfo = grid.NewJSONPool[madmin.MemInfo]()
|
madminMemInfo = grid.NewJSONPool[madmin.MemInfo]()
|
||||||
madminNetInfo = grid.NewJSONPool[madmin.NetInfo]()
|
madminNetInfo = grid.NewJSONPool[madmin.NetInfo]()
|
||||||
@@ -97,7 +98,7 @@ var (
|
|||||||
getSysErrorsRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysErrors]](grid.HandlerGetSysErrors, grid.NewMSS, madminSysErrors.NewJSON)
|
getSysErrorsRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysErrors]](grid.HandlerGetSysErrors, grid.NewMSS, madminSysErrors.NewJSON)
|
||||||
getSysServicesRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysServices]](grid.HandlerGetSysServices, grid.NewMSS, madminSysServices.NewJSON)
|
getSysServicesRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysServices]](grid.HandlerGetSysServices, grid.NewMSS, madminSysServices.NewJSON)
|
||||||
headBucketRPC = grid.NewSingleHandler[*grid.MSS, *VolInfo](grid.HandlerHeadBucket, grid.NewMSS, func() *VolInfo { return &VolInfo{} })
|
headBucketRPC = grid.NewSingleHandler[*grid.MSS, *VolInfo](grid.HandlerHeadBucket, grid.NewMSS, func() *VolInfo { return &VolInfo{} })
|
||||||
healBucketRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerHealBucket, grid.NewMSS, grid.NewNoPayload)
|
healBucketRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.HealResultItem]](grid.HandlerHealBucket, grid.NewMSS, madminHealResultItem.NewJSON)
|
||||||
listBucketsRPC = grid.NewSingleHandler[*BucketOptions, *grid.Array[*BucketInfo]](grid.HandlerListBuckets, func() *BucketOptions { return &BucketOptions{} }, aoBucketInfo.New)
|
listBucketsRPC = grid.NewSingleHandler[*BucketOptions, *grid.Array[*BucketInfo]](grid.HandlerListBuckets, func() *BucketOptions { return &BucketOptions{} }, aoBucketInfo.New)
|
||||||
loadBucketMetadataRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadBucketMetadata, grid.NewMSS, grid.NewNoPayload).IgnoreNilConn()
|
loadBucketMetadataRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadBucketMetadata, grid.NewMSS, grid.NewNoPayload).IgnoreNilConn()
|
||||||
loadGroupRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadGroup, grid.NewMSS, grid.NewNoPayload)
|
loadGroupRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadGroup, grid.NewMSS, grid.NewNoPayload)
|
||||||
@@ -1258,21 +1259,21 @@ func (s *peerRESTServer) NetSpeedTestHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
peersLogIf(r.Context(), gob.NewEncoder(w).Encode(result))
|
peersLogIf(r.Context(), gob.NewEncoder(w).Encode(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *peerRESTServer) HealBucketHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
func (s *peerRESTServer) HealBucketHandler(mss *grid.MSS) (np *grid.JSON[madmin.HealResultItem], nerr *grid.RemoteErr) {
|
||||||
bucket := mss.Get(peerS3Bucket)
|
bucket := mss.Get(peerS3Bucket)
|
||||||
if isMinioMetaBucket(bucket) {
|
if isMinioMetaBucket(bucket) {
|
||||||
return np, grid.NewRemoteErr(errInvalidArgument)
|
return np, grid.NewRemoteErr(errInvalidArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketDeleted := mss.Get(peerS3BucketDeleted) == "true"
|
bucketDeleted := mss.Get(peerS3BucketDeleted) == "true"
|
||||||
_, err := healBucketLocal(context.Background(), bucket, madmin.HealOpts{
|
res, err := healBucketLocal(context.Background(), bucket, madmin.HealOpts{
|
||||||
Remove: bucketDeleted,
|
Remove: bucketDeleted,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return np, grid.NewRemoteErr(err)
|
return np, grid.NewRemoteErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return np, nil
|
return madminHealResultItem.NewJSONWith(&res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *peerRESTServer) ListBucketsHandler(opts *BucketOptions) (*grid.Array[*BucketInfo], *grid.RemoteErr) {
|
func (s *peerRESTServer) ListBucketsHandler(opts *BucketOptions) (*grid.Array[*BucketInfo], *grid.RemoteErr) {
|
||||||
|
|||||||
+16
-11
@@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -29,7 +30,6 @@ import (
|
|||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/grid"
|
"github.com/minio/minio/internal/grid"
|
||||||
"github.com/minio/pkg/v3/sync/errgroup"
|
"github.com/minio/pkg/v3/sync/errgroup"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errPeerOffline = errors.New("peer is offline")
|
var errPeerOffline = errors.New("peer is offline")
|
||||||
@@ -178,13 +178,24 @@ func (sys *S3PeerSys) HealBucket(ctx context.Context, bucket string, opts madmin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if healBucketErr := reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, len(errs)/2+1); healBucketErr != nil {
|
||||||
|
return madmin.HealResultItem{}, toObjectErr(healBucketErr, bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := madmin.HealResultItem{
|
||||||
|
Type: madmin.HealItemBucket,
|
||||||
|
Bucket: bucket,
|
||||||
|
SetCount: -1, // explicitly set an invalid value -1, for bucket heal scenario
|
||||||
|
}
|
||||||
|
|
||||||
for i, err := range errs {
|
for i, err := range errs {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return healBucketResults[i], nil
|
res.Before.Drives = append(res.Before.Drives, healBucketResults[i].Before.Drives...)
|
||||||
|
res.After.Drives = append(res.After.Drives, healBucketResults[i].After.Drives...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return madmin.HealResultItem{}, toObjectErr(errVolumeNotFound, bucket)
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBuckets lists buckets across all nodes and returns a consistent view:
|
// ListBuckets lists buckets across all nodes and returns a consistent view:
|
||||||
@@ -355,14 +366,8 @@ func (client *remotePeerS3Client) HealBucket(ctx context.Context, bucket string,
|
|||||||
ctx, cancel := context.WithTimeout(ctx, globalDriveConfig.GetMaxTimeout())
|
ctx, cancel := context.WithTimeout(ctx, globalDriveConfig.GetMaxTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
_, err := healBucketRPC.Call(ctx, conn, mss)
|
resp, err := healBucketRPC.Call(ctx, conn, mss)
|
||||||
|
return resp.ValueOrZero(), toStorageErr(err)
|
||||||
// Initialize heal result info
|
|
||||||
return madmin.HealResultItem{
|
|
||||||
Type: madmin.HealItemBucket,
|
|
||||||
Bucket: bucket,
|
|
||||||
SetCount: -1, // explicitly set an invalid value -1, for bucket heal scenario
|
|
||||||
}, toStorageErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketInfo returns bucket stat info from a peer
|
// GetBucketInfo returns bucket stat info from a peer
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
|||||||
for i := range beforeState {
|
for i := range beforeState {
|
||||||
res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{
|
res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{
|
||||||
UUID: "",
|
UUID: "",
|
||||||
Endpoint: localDrives[i].String(),
|
Endpoint: localDrives[i].Endpoint().String(),
|
||||||
State: beforeState[i],
|
State: beforeState[i],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
|||||||
for i := range afterState {
|
for i := range afterState {
|
||||||
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
|
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
|
||||||
UUID: "",
|
UUID: "",
|
||||||
Endpoint: localDrives[i].String(),
|
Endpoint: localDrives[i].Endpoint().String(),
|
||||||
State: afterState[i],
|
State: afterState[i],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-8
@@ -56,10 +56,12 @@ func newPostPolicyBytesV4WithContentRange(credential, bucketName, objectKey stri
|
|||||||
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
|
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
|
||||||
// Add the meta-uuid string, set to 1234
|
// Add the meta-uuid string, set to 1234
|
||||||
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
|
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
|
||||||
|
// Add the content-encoding string, set to gzip.
|
||||||
|
contentEncodingConditionStr := fmt.Sprintf(`["eq", "$content-encoding", "%s"]`, "gzip")
|
||||||
|
|
||||||
// Combine all conditions into one string.
|
// Combine all conditions into one string.
|
||||||
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr,
|
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr,
|
||||||
keyConditionStr, contentLengthCondStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr)
|
keyConditionStr, contentLengthCondStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr, contentEncodingConditionStr)
|
||||||
retStr := "{"
|
retStr := "{"
|
||||||
retStr = retStr + expirationStr + ","
|
retStr = retStr + expirationStr + ","
|
||||||
retStr += conditionStr
|
retStr += conditionStr
|
||||||
@@ -85,9 +87,11 @@ func newPostPolicyBytesV4(credential, bucketName, objectKey string, expiration t
|
|||||||
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
|
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
|
||||||
// Add the meta-uuid string, set to 1234
|
// Add the meta-uuid string, set to 1234
|
||||||
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
|
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
|
||||||
|
// Add the content-encoding string, set to gzip
|
||||||
|
contentEncodingConditionStr := fmt.Sprintf(`["eq", "$content-encoding", "%s"]`, "gzip")
|
||||||
|
|
||||||
// Combine all conditions into one string.
|
// Combine all conditions into one string.
|
||||||
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s]`, bucketConditionStr, keyConditionStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr)
|
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr, keyConditionStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr, contentEncodingConditionStr)
|
||||||
retStr := "{"
|
retStr := "{"
|
||||||
retStr = retStr + expirationStr + ","
|
retStr = retStr + expirationStr + ","
|
||||||
retStr += conditionStr
|
retStr += conditionStr
|
||||||
@@ -331,7 +335,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
|||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
secretKey: credentials.SecretKey,
|
secretKey: credentials.SecretKey,
|
||||||
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
||||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`,
|
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
|
||||||
},
|
},
|
||||||
// Success case, no multipart filename.
|
// Success case, no multipart filename.
|
||||||
{
|
{
|
||||||
@@ -341,7 +345,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
|||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
secretKey: credentials.SecretKey,
|
secretKey: credentials.SecretKey,
|
||||||
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
||||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`,
|
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
|
||||||
noFilename: true,
|
noFilename: true,
|
||||||
},
|
},
|
||||||
// Success case, big body.
|
// Success case, big body.
|
||||||
@@ -352,7 +356,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
|||||||
accessKey: credentials.AccessKey,
|
accessKey: credentials.AccessKey,
|
||||||
secretKey: credentials.SecretKey,
|
secretKey: credentials.SecretKey,
|
||||||
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
||||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`,
|
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
|
||||||
},
|
},
|
||||||
// Corrupted Base 64 result
|
// Corrupted Base 64 result
|
||||||
{
|
{
|
||||||
@@ -447,7 +451,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
|||||||
malformedBody: false,
|
malformedBody: false,
|
||||||
ignoreContentLength: false,
|
ignoreContentLength: false,
|
||||||
},
|
},
|
||||||
// Failed with Content-Length not specified.
|
// Success with Content-Length not specified.
|
||||||
{
|
{
|
||||||
objectName: "test",
|
objectName: "test",
|
||||||
data: bytes.Repeat([]byte("a"), 1025),
|
data: bytes.Repeat([]byte("a"), 1025),
|
||||||
@@ -547,7 +551,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
|
|||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
dates := []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}
|
dates := []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}
|
||||||
policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}`
|
policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$content-encoding", "gzip"]]}`
|
||||||
|
|
||||||
// Generate the final policy document
|
// Generate the final policy document
|
||||||
policy = fmt.Sprintf(policy, dates...)
|
policy = fmt.Sprintf(policy, dates...)
|
||||||
|
|||||||
+51
-66
@@ -53,18 +53,6 @@ var startsWithConds = map[string]bool{
|
|||||||
"$x-amz-date": false,
|
"$x-amz-date": false,
|
||||||
}
|
}
|
||||||
|
|
||||||
var postPolicyIgnoreKeys = map[string]bool{
|
|
||||||
"Policy": true,
|
|
||||||
xhttp.AmzSignature: true,
|
|
||||||
xhttp.ContentEncoding: true,
|
|
||||||
http.CanonicalHeaderKey(xhttp.AmzChecksumAlgo): true,
|
|
||||||
http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32): true,
|
|
||||||
http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32C): true,
|
|
||||||
http.CanonicalHeaderKey(xhttp.AmzChecksumSHA1): true,
|
|
||||||
http.CanonicalHeaderKey(xhttp.AmzChecksumSHA256): true,
|
|
||||||
http.CanonicalHeaderKey(xhttp.AmzChecksumMode): true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add policy conditionals.
|
// Add policy conditionals.
|
||||||
const (
|
const (
|
||||||
policyCondEqual = "eq"
|
policyCondEqual = "eq"
|
||||||
@@ -232,19 +220,19 @@ func parsePostPolicyForm(r io.Reader) (PostPolicyForm, error) {
|
|||||||
operator, matchType, value,
|
operator, matchType, value,
|
||||||
})
|
})
|
||||||
case policyCondContentLength:
|
case policyCondContentLength:
|
||||||
min, err := toInteger(condt[1])
|
minLen, err := toInteger(condt[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return parsedPolicy, err
|
return parsedPolicy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
max, err := toInteger(condt[2])
|
maxLen, err := toInteger(condt[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return parsedPolicy, err
|
return parsedPolicy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedPolicy.Conditions.ContentLengthRange = contentLengthRange{
|
parsedPolicy.Conditions.ContentLengthRange = contentLengthRange{
|
||||||
Min: min,
|
Min: minLen,
|
||||||
Max: max,
|
Max: maxLen,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -272,60 +260,57 @@ func checkPolicyCond(op string, input1, input2 string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// S3 docs: "Each form field that you specify in a form (except x-amz-signature, file, policy, and field names
|
||||||
|
// that have an x-ignore- prefix) must appear in the list of conditions."
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
|
||||||
|
// keyInPolicyExceptions - list of keys that, when present in the form, can be missing in the conditions of the policy.
|
||||||
|
var keyInPolicyExceptions = map[string]bool{
|
||||||
|
xhttp.AmzSignature: true,
|
||||||
|
"File": true,
|
||||||
|
"Policy": true,
|
||||||
|
|
||||||
|
// MinIO specific exceptions to the general S3 rule above.
|
||||||
|
encrypt.SseKmsKeyID: true,
|
||||||
|
encrypt.SseEncryptionContext: true,
|
||||||
|
encrypt.SseCustomerAlgorithm: true,
|
||||||
|
encrypt.SseCustomerKey: true,
|
||||||
|
encrypt.SseCustomerKeyMD5: true,
|
||||||
|
}
|
||||||
|
|
||||||
// checkPostPolicy - apply policy conditions and validate input values.
|
// checkPostPolicy - apply policy conditions and validate input values.
|
||||||
// (http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html)
|
// Note that content-length-range is checked in the API handler function PostPolicyBucketHandler.
|
||||||
|
// formValues is the already-canonicalized form values from the POST request.
|
||||||
func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) error {
|
func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) error {
|
||||||
// Check if policy document expiry date is still not reached
|
// Check if policy document expiry date is still not reached
|
||||||
if !postPolicyForm.Expiration.After(UTCNow()) {
|
if !postPolicyForm.Expiration.After(UTCNow()) {
|
||||||
return fmt.Errorf("Invalid according to Policy: Policy expired")
|
return fmt.Errorf("Invalid according to Policy: Policy expired")
|
||||||
}
|
}
|
||||||
// check all formValues appear in postPolicyForm or return error. #https://github.com/minio/minio/issues/17391
|
|
||||||
checkHeader := map[string][]string{}
|
|
||||||
ignoreKeys := map[string]bool{}
|
|
||||||
for key, value := range formValues {
|
|
||||||
switch {
|
|
||||||
case ignoreKeys[key], postPolicyIgnoreKeys[key], strings.HasPrefix(key, encrypt.SseGenericHeader):
|
|
||||||
continue
|
|
||||||
case strings.HasPrefix(key, "X-Amz-Ignore-"):
|
|
||||||
ignoreKey := strings.Replace(key, "X-Amz-Ignore-", "", 1)
|
|
||||||
ignoreKeys[ignoreKey] = true
|
|
||||||
// if it have already
|
|
||||||
delete(checkHeader, ignoreKey)
|
|
||||||
default:
|
|
||||||
checkHeader[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// map to store the metadata
|
|
||||||
metaMap := make(map[string]string)
|
|
||||||
for _, policy := range postPolicyForm.Conditions.Policies {
|
|
||||||
if strings.HasPrefix(policy.Key, "$x-amz-meta-") {
|
|
||||||
formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$"))
|
|
||||||
metaMap[formCanonicalName] = policy.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if any extra metadata field is passed as input
|
|
||||||
for key := range formValues {
|
|
||||||
if strings.HasPrefix(key, "X-Amz-Meta-") {
|
|
||||||
if _, ok := metaMap[key]; !ok {
|
|
||||||
return fmt.Errorf("Invalid according to Policy: Extra input fields: %s", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag to indicate if all policies conditions are satisfied
|
// mustFindInPolicy is a map to list all the keys that we must find in the policy as
|
||||||
var condPassed bool
|
// we process it below. At the end of checkPostPolicy function, if any key is left in
|
||||||
|
// this map, that's an error.
|
||||||
|
mustFindInPolicy := make(map[string][]string, len(formValues))
|
||||||
|
for key, values := range formValues {
|
||||||
|
if keyInPolicyExceptions[key] || strings.HasPrefix(key, "X-Ignore-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mustFindInPolicy[key] = values
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate over policy conditions and check them against received form fields
|
// Iterate over policy conditions and check them against received form fields
|
||||||
for _, policy := range postPolicyForm.Conditions.Policies {
|
for _, policy := range postPolicyForm.Conditions.Policies {
|
||||||
// Form fields names are in canonical format, convert conditions names
|
// Form fields names are in canonical format, convert conditions names
|
||||||
// to canonical for simplification purpose, so `$key` will become `Key`
|
// to canonical for simplification purpose, so `$key` will become `Key`
|
||||||
formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$"))
|
formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$"))
|
||||||
|
|
||||||
// Operator for the current policy condition
|
// Operator for the current policy condition
|
||||||
op := policy.Operator
|
op := policy.Operator
|
||||||
// Multiple values should not occur
|
|
||||||
if len(checkHeader[formCanonicalName]) >= 2 {
|
// Multiple values are not allowed for a single form field
|
||||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]. FormValues have multiple values: [%s]", op, policy.Key, policy.Value, strings.Join(checkHeader[formCanonicalName], ", "))
|
if len(mustFindInPolicy[formCanonicalName]) >= 2 {
|
||||||
|
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]. FormValues have multiple values: [%s]", op, policy.Key, policy.Value, strings.Join(mustFindInPolicy[formCanonicalName], ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current policy condition is known
|
// If the current policy condition is known
|
||||||
if startsWithSupported, condFound := startsWithConds[policy.Key]; condFound {
|
if startsWithSupported, condFound := startsWithConds[policy.Key]; condFound {
|
||||||
// Check if the current condition supports starts-with operator
|
// Check if the current condition supports starts-with operator
|
||||||
@@ -333,35 +318,35 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro
|
|||||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
||||||
}
|
}
|
||||||
// Check if current policy condition is satisfied
|
// Check if current policy condition is satisfied
|
||||||
condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value)
|
if !checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) {
|
||||||
if !condPassed {
|
|
||||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(policy.Key, "$x-amz-meta-") || strings.HasPrefix(policy.Key, "$x-amz-") {
|
} else if strings.HasPrefix(policy.Key, "$x-amz-meta-") || strings.HasPrefix(policy.Key, "$x-amz-") {
|
||||||
// This covers all conditions X-Amz-Meta-* and X-Amz-*
|
// This covers all conditions X-Amz-Meta-* and X-Amz-*
|
||||||
// Check if policy condition is satisfied
|
// Check if policy condition is satisfied
|
||||||
condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value)
|
if !checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) {
|
||||||
if !condPassed {
|
|
||||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", op, policy.Key, policy.Value)
|
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", op, policy.Key, policy.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(checkHeader, formCanonicalName)
|
delete(mustFindInPolicy, formCanonicalName)
|
||||||
}
|
}
|
||||||
// For SignV2 - Signature/AWSAccessKeyId field will be ignored.
|
|
||||||
|
// For SignV2 - Signature/AWSAccessKeyId fields do not need to be in the policy
|
||||||
if _, ok := formValues[xhttp.AmzSignatureV2]; ok {
|
if _, ok := formValues[xhttp.AmzSignatureV2]; ok {
|
||||||
delete(checkHeader, xhttp.AmzSignatureV2)
|
delete(mustFindInPolicy, xhttp.AmzSignatureV2)
|
||||||
for k := range checkHeader {
|
for k := range mustFindInPolicy {
|
||||||
// case-insensitivity for AWSAccessKeyId
|
// case-insensitivity for AWSAccessKeyId
|
||||||
if strings.EqualFold(k, xhttp.AmzAccessKeyID) {
|
if strings.EqualFold(k, xhttp.AmzAccessKeyID) {
|
||||||
delete(checkHeader, k)
|
delete(mustFindInPolicy, k)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(checkHeader) != 0 {
|
// Check mustFindInPolicy to see if any key is left, if so, it was not found in policy and we return an error.
|
||||||
logKeys := make([]string, 0, len(checkHeader))
|
if len(mustFindInPolicy) != 0 {
|
||||||
for key := range checkHeader {
|
logKeys := make([]string, 0, len(mustFindInPolicy))
|
||||||
|
for key := range mustFindInPolicy {
|
||||||
logKeys = append(logKeys, key)
|
logKeys = append(logKeys, key)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy.", strings.Join(logKeys, ", "))
|
return fmt.Errorf("Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy.", strings.Join(logKeys, ", "))
|
||||||
|
|||||||
+201
-62
@@ -20,12 +20,12 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParsePostPolicyForm(t *testing.T) {
|
func TestParsePostPolicyForm(t *testing.T) {
|
||||||
@@ -78,6 +78,28 @@ func TestParsePostPolicyForm(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type formValues struct {
|
||||||
|
http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFormValues() formValues {
|
||||||
|
return formValues{make(http.Header)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f formValues) Set(key, value string) formValues {
|
||||||
|
f.Header.Set(key, value)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f formValues) Add(key, value string) formValues {
|
||||||
|
f.Header.Add(key, value)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f formValues) Clone() formValues {
|
||||||
|
return formValues{f.Header.Clone()}
|
||||||
|
}
|
||||||
|
|
||||||
// Test Post Policy parsing and checking conditions
|
// Test Post Policy parsing and checking conditions
|
||||||
func TestPostPolicyForm(t *testing.T) {
|
func TestPostPolicyForm(t *testing.T) {
|
||||||
pp := minio.NewPostPolicy()
|
pp := minio.NewPostPolicy()
|
||||||
@@ -85,76 +107,193 @@ func TestPostPolicyForm(t *testing.T) {
|
|||||||
pp.SetContentType("image/jpeg")
|
pp.SetContentType("image/jpeg")
|
||||||
pp.SetUserMetadata("uuid", "14365123651274")
|
pp.SetUserMetadata("uuid", "14365123651274")
|
||||||
pp.SetKeyStartsWith("user/user1/filename")
|
pp.SetKeyStartsWith("user/user1/filename")
|
||||||
pp.SetContentLengthRange(1048579, 10485760)
|
pp.SetContentLengthRange(100, 999999) // not testable from this layer, condition is checked in the API handler.
|
||||||
pp.SetSuccessStatusAction("201")
|
pp.SetSuccessStatusAction("201")
|
||||||
|
pp.SetCondition("eq", "X-Amz-Credential", "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request")
|
||||||
|
pp.SetCondition("eq", "X-Amz-Algorithm", "AWS4-HMAC-SHA256")
|
||||||
|
pp.SetCondition("eq", xhttp.AmzDate, "20160727T000000Z")
|
||||||
|
|
||||||
|
defaultFormVals := newFormValues()
|
||||||
|
defaultFormVals.Set("Bucket", "testbucket")
|
||||||
|
defaultFormVals.Set("Content-Type", "image/jpeg")
|
||||||
|
defaultFormVals.Set(xhttp.AmzMetaUUID, "14365123651274")
|
||||||
|
defaultFormVals.Set("Key", "user/user1/filename/${filename}/myfile.txt")
|
||||||
|
defaultFormVals.Set("X-Amz-Credential", "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request")
|
||||||
|
defaultFormVals.Set("X-Amz-Algorithm", "AWS4-HMAC-SHA256")
|
||||||
|
defaultFormVals.Set(xhttp.AmzDate, "20160727T000000Z")
|
||||||
|
defaultFormVals.Set("Success_action_status", "201")
|
||||||
|
|
||||||
|
policyCondFailedErr := "Invalid according to Policy: Policy Condition failed"
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
Bucket string
|
name string
|
||||||
Key string
|
fv formValues
|
||||||
XAmzDate string
|
expired bool
|
||||||
XAmzAlgorithm string
|
wantErr string
|
||||||
XAmzCredential string
|
|
||||||
XAmzMetaUUID string
|
|
||||||
ContentType string
|
|
||||||
SuccessActionStatus string
|
|
||||||
Policy string
|
|
||||||
Expired bool
|
|
||||||
expectedErr error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test case just contains fields we override from defaultFormVals.
|
||||||
testCases := []testCase{
|
testCases := []testCase{
|
||||||
// Everything is fine with this test
|
{
|
||||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: nil},
|
name: "happy path no errors",
|
||||||
// Expired policy document
|
fv: defaultFormVals.Clone(),
|
||||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", Expired: true, expectedErr: fmt.Errorf("Invalid according to Policy: Policy expired")},
|
wantErr: "",
|
||||||
// Different AMZ date
|
},
|
||||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "2017T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
{
|
||||||
// Key which doesn't start with user/user1/filename
|
name: "expired policy document",
|
||||||
{Bucket: "testbucket", Key: "myfile.txt", XAmzDate: "20160727T000000Z", XAmzMetaUUID: "14365123651274", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
fv: defaultFormVals.Clone(),
|
||||||
// Incorrect bucket name.
|
expired: true,
|
||||||
{Bucket: "incorrect", Key: "user/user1/filename/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
wantErr: "Invalid according to Policy: Policy expired",
|
||||||
// Incorrect key name
|
},
|
||||||
{Bucket: "testbucket", Key: "incorrect", XAmzDate: "20160727T000000Z", XAmzMetaUUID: "14365123651274", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
{
|
||||||
// Incorrect date
|
name: "different AMZ date",
|
||||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "incorrect", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
fv: defaultFormVals.Clone().Set(xhttp.AmzDate, "2017T000000Z"),
|
||||||
// Incorrect ContentType
|
wantErr: policyCondFailedErr,
|
||||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "incorrect", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
},
|
||||||
// Incorrect Metadata
|
{
|
||||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "151274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed: [eq, $x-amz-meta-uuid, 14365123651274]")},
|
name: "incorrect date",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzDate, "incorrect"),
|
||||||
|
wantErr: policyCondFailedErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key which doesn't start with user/user1/filename",
|
||||||
|
fv: defaultFormVals.Clone().Set("Key", "myfile.txt"),
|
||||||
|
wantErr: policyCondFailedErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect key name",
|
||||||
|
fv: defaultFormVals.Clone().Set("Key", "incorrect"),
|
||||||
|
wantErr: policyCondFailedErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect bucket name",
|
||||||
|
fv: defaultFormVals.Clone().Set("Bucket", "incorrect"),
|
||||||
|
wantErr: policyCondFailedErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect ContentType",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.ContentType, "incorrect"),
|
||||||
|
wantErr: policyCondFailedErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect X-Amz-Algorithm",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzAlgorithm, "incorrect"),
|
||||||
|
wantErr: policyCondFailedErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect X-Amz-Credential",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzCredential, "incorrect"),
|
||||||
|
wantErr: policyCondFailedErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect metadata uuid",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzMetaUUID, "151274"),
|
||||||
|
wantErr: "Invalid according to Policy: Policy Condition failed: [eq, $x-amz-meta-uuid, 14365123651274]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key XAmzMetaName is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzMetaName, "my-name"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Meta-Name" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key XAmzChecksumAlgo is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumAlgo), "algo-val"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Algorithm" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key XAmzChecksumCRC32 is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32), "crc32-val"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Crc32" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key XAmzChecksumCRC32C is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32C), "crc32c-val"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Crc32c" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key XAmzChecksumSHA1 is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumSHA1), "sha1-val"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Sha1" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key XAmzChecksumSHA256 is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumSHA256), "sha256-val"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Sha256" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key XAmzChecksumMode is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumMode), "mode-val"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Mode" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key Content-Encoding is error as it does not appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.ContentEncoding), "encoding-val"),
|
||||||
|
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "Content-Encoding" not specified in the policy.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "many bucket values",
|
||||||
|
fv: defaultFormVals.Clone().Add("Bucket", "anotherbucket"),
|
||||||
|
wantErr: "Invalid according to Policy: Policy Condition failed: [eq, $bucket, testbucket]. FormValues have multiple values: [testbucket, anotherbucket]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "XAmzSignature does not have to appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzSignature, "my-signature"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "XIgnoreFoo does not have to appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set("X-Ignore-Foo", "my-foo-value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File does not have to appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set("File", "file-value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Signature does not have to appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzSignatureV2, "signature-value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AWSAccessKeyID does not have to appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().Set(xhttp.AmzAccessKeyID, "access").Set(xhttp.AmzSignatureV2, "signature-value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "any form value starting with X-Amz-Server-Side-Encryption- does not have to appear in policy",
|
||||||
|
fv: defaultFormVals.Clone().
|
||||||
|
Set(xhttp.AmzServerSideEncryptionKmsContext, "context-val").
|
||||||
|
Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, "algo-val"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
// Validate all the test cases.
|
|
||||||
for i, tt := range testCases {
|
|
||||||
formValues := make(http.Header)
|
|
||||||
formValues.Set("Bucket", tt.Bucket)
|
|
||||||
formValues.Set("Key", tt.Key)
|
|
||||||
formValues.Set("Content-Type", tt.ContentType)
|
|
||||||
formValues.Set("X-Amz-Date", tt.XAmzDate)
|
|
||||||
formValues.Set("X-Amz-Meta-Uuid", tt.XAmzMetaUUID)
|
|
||||||
formValues.Set("X-Amz-Algorithm", tt.XAmzAlgorithm)
|
|
||||||
formValues.Set("X-Amz-Credential", tt.XAmzCredential)
|
|
||||||
if tt.Expired {
|
|
||||||
// Expired already.
|
|
||||||
pp.SetExpires(UTCNow().AddDate(0, 0, -10))
|
|
||||||
} else {
|
|
||||||
// Expires in 10 days.
|
|
||||||
pp.SetExpires(UTCNow().AddDate(0, 0, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
formValues.Set("Policy", base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
// Run tests
|
||||||
formValues.Set("Success_action_status", tt.SuccessActionStatus)
|
for _, tt := range testCases {
|
||||||
policyBytes, err := base64.StdEncoding.DecodeString(base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err != nil {
|
if tt.expired {
|
||||||
t.Fatal(err)
|
// Expired already.
|
||||||
}
|
pp.SetExpires(UTCNow().AddDate(0, 0, -10))
|
||||||
|
} else {
|
||||||
|
// Expires in 10 days.
|
||||||
|
pp.SetExpires(UTCNow().AddDate(0, 0, 10))
|
||||||
|
}
|
||||||
|
|
||||||
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
tt.fv.Set("Policy", base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkPostPolicy(formValues, postPolicyForm)
|
policyBytes, err := base64.StdEncoding.DecodeString(base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
||||||
if err != nil && tt.expectedErr != nil && err.Error() != tt.expectedErr.Error() {
|
if err != nil {
|
||||||
t.Fatalf("Test %d:, Expected %s, got %s", i+1, tt.expectedErr.Error(), err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errStr := ""
|
||||||
|
err = checkPostPolicy(tt.fv.Header, postPolicyForm)
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
}
|
||||||
|
if errStr != tt.wantErr {
|
||||||
|
t.Errorf("test: '%s', want error: '%s', got error: '%s'", tt.name, tt.wantErr, errStr)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -31,6 +31,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -53,7 +54,6 @@ import (
|
|||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v3/certs"
|
"github.com/minio/pkg/v3/certs"
|
||||||
"github.com/minio/pkg/v3/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -421,6 +421,7 @@ func serverHandleCmdArgs(ctxt serverCtxt) {
|
|||||||
Interface: ctxt.Interface,
|
Interface: ctxt.Interface,
|
||||||
SendBufSize: ctxt.SendBufSize,
|
SendBufSize: ctxt.SendBufSize,
|
||||||
RecvBufSize: ctxt.RecvBufSize,
|
RecvBufSize: ctxt.RecvBufSize,
|
||||||
|
IdleTimeout: ctxt.IdleTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow transport to be HTTP/1.1 for proxying.
|
// allow transport to be HTTP/1.1 for proxying.
|
||||||
@@ -878,6 +879,8 @@ func serverMain(ctx *cli.Context) {
|
|||||||
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
||||||
UseTLSConfig(newTLSConfig(getCert)).
|
UseTLSConfig(newTLSConfig(getCert)).
|
||||||
UseIdleTimeout(globalServerCtxt.IdleTimeout).
|
UseIdleTimeout(globalServerCtxt.IdleTimeout).
|
||||||
|
UseReadTimeout(globalServerCtxt.IdleTimeout).
|
||||||
|
UseWriteTimeout(globalServerCtxt.IdleTimeout).
|
||||||
UseReadHeaderTimeout(globalServerCtxt.ReadHeaderTimeout).
|
UseReadHeaderTimeout(globalServerCtxt.ReadHeaderTimeout).
|
||||||
UseBaseContext(GlobalContext).
|
UseBaseContext(GlobalContext).
|
||||||
UseCustomLogger(log.New(io.Discard, "", 0)). // Turn-off random logging by Go stdlib
|
UseCustomLogger(log.New(io.Discard, "", 0)). // Turn-off random logging by Go stdlib
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,6 +46,7 @@ const ftpMaxWriteOffset = 100 << 20
|
|||||||
type sftpDriver struct {
|
type sftpDriver struct {
|
||||||
permissions *ssh.Permissions
|
permissions *ssh.Permissions
|
||||||
endpoint string
|
endpoint string
|
||||||
|
remoteIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
//msgp:ignore sftpMetrics
|
//msgp:ignore sftpMetrics
|
||||||
@@ -89,8 +91,12 @@ func (m *sftpMetrics) log(s *sftp.Request, user string) func(sz int64, err error
|
|||||||
// - sftp.Filewrite
|
// - sftp.Filewrite
|
||||||
// - sftp.Filelist
|
// - sftp.Filelist
|
||||||
// - sftp.Filecmd
|
// - sftp.Filecmd
|
||||||
func NewSFTPDriver(perms *ssh.Permissions) sftp.Handlers {
|
func NewSFTPDriver(perms *ssh.Permissions, remoteIP string) sftp.Handlers {
|
||||||
handler := &sftpDriver{endpoint: fmt.Sprintf("127.0.0.1:%s", globalMinioPort), permissions: perms}
|
handler := &sftpDriver{
|
||||||
|
endpoint: fmt.Sprintf("127.0.0.1:%s", globalMinioPort),
|
||||||
|
permissions: perms,
|
||||||
|
remoteIP: remoteIP,
|
||||||
|
}
|
||||||
return sftp.Handlers{
|
return sftp.Handlers{
|
||||||
FileGet: handler,
|
FileGet: handler,
|
||||||
FilePut: handler,
|
FilePut: handler,
|
||||||
@@ -99,16 +105,31 @@ func NewSFTPDriver(perms *ssh.Permissions) sftp.Handlers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type forwardForTransport struct {
|
||||||
|
tr http.RoundTripper
|
||||||
|
fwd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f forwardForTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
r.Header.Set("X-Forwarded-For", f.fwd)
|
||||||
|
return f.tr.RoundTrip(r)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *sftpDriver) getMinIOClient() (*minio.Client, error) {
|
func (f *sftpDriver) getMinIOClient() (*minio.Client, error) {
|
||||||
mcreds := credentials.NewStaticV4(
|
mcreds := credentials.NewStaticV4(
|
||||||
f.permissions.CriticalOptions["AccessKey"],
|
f.permissions.CriticalOptions["AccessKey"],
|
||||||
f.permissions.CriticalOptions["SecretKey"],
|
f.permissions.CriticalOptions["SecretKey"],
|
||||||
f.permissions.CriticalOptions["SessionToken"],
|
f.permissions.CriticalOptions["SessionToken"],
|
||||||
)
|
)
|
||||||
|
// Set X-Forwarded-For on all requests.
|
||||||
|
tr := http.RoundTripper(globalRemoteFTPClientTransport)
|
||||||
|
if f.remoteIP != "" {
|
||||||
|
tr = forwardForTransport{tr: tr, fwd: f.remoteIP}
|
||||||
|
}
|
||||||
return minio.New(f.endpoint, &minio.Options{
|
return minio.New(f.endpoint, &minio.Options{
|
||||||
Creds: mcreds,
|
Creds: mcreds,
|
||||||
Secure: globalIsTLS,
|
Secure: globalIsTLS,
|
||||||
Transport: globalRemoteFTPClientTransport,
|
Transport: tr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -488,7 +488,12 @@ func startSFTPServer(args []string) {
|
|||||||
sshConfig.AddHostKey(private)
|
sshConfig.AddHostKey(private)
|
||||||
|
|
||||||
handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) {
|
handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) {
|
||||||
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions), sftp.WithRSAllocator())
|
var remoteIP string
|
||||||
|
|
||||||
|
if host, _, err := net.SplitHostPort(sconn.RemoteAddr().String()); err == nil {
|
||||||
|
remoteIP = host
|
||||||
|
}
|
||||||
|
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions, remoteIP), sftp.WithRSAllocator())
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
server.Serve()
|
server.Serve()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -31,7 +32,6 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v3/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the
|
// http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ const (
|
|||||||
// getCanonicalHeaders generate a list of request headers with their values
|
// getCanonicalHeaders generate a list of request headers with their values
|
||||||
func getCanonicalHeaders(signedHeaders http.Header) string {
|
func getCanonicalHeaders(signedHeaders http.Header) string {
|
||||||
var headers []string
|
var headers []string
|
||||||
vals := make(http.Header)
|
vals := make(http.Header, len(signedHeaders))
|
||||||
for k, vv := range signedHeaders {
|
for k, vv := range signedHeaders {
|
||||||
k = strings.ToLower(k)
|
k = strings.ToLower(k)
|
||||||
headers = append(headers, k)
|
headers = append(headers, k)
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import (
|
|||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//msgp:clearomitted
|
||||||
|
|
||||||
//go:generate msgp -file=$GOFILE
|
//go:generate msgp -file=$GOFILE
|
||||||
|
|
||||||
// DeleteOptions represents the disk level delete options available for the APIs
|
// DeleteOptions represents the disk level delete options available for the APIs
|
||||||
|
|||||||
+343
-261
@@ -746,6 +746,8 @@ func (z *DeleteOptions) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, err = dc.ReadMapKeyPtr()
|
field, err = dc.ReadMapKeyPtr()
|
||||||
@@ -801,6 +803,7 @@ func (z *DeleteOptions) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "OldDataDir")
|
err = msgp.WrapError(err, "OldDataDir")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -809,6 +812,11 @@ func (z *DeleteOptions) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.OldDataDir = ""
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,61 +835,62 @@ func (z *DeleteOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// write "BaseOptions"
|
// write "BaseOptions"
|
||||||
err = en.Append(0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
|
err = en.Append(0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// map header, size 0
|
|
||||||
_ = z.BaseOptions
|
|
||||||
err = en.Append(0x80)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "r"
|
|
||||||
err = en.Append(0xa1, 0x72)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteBool(z.Recursive)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Recursive")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "i"
|
|
||||||
err = en.Append(0xa1, 0x69)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteBool(z.Immediate)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Immediate")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "u"
|
|
||||||
err = en.Append(0xa1, 0x75)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteBool(z.UndoWrite)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "UndoWrite")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
|
||||||
// write "o"
|
|
||||||
err = en.Append(0xa1, 0x6f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteString(z.OldDataDir)
|
// map header, size 0
|
||||||
|
_ = z.BaseOptions
|
||||||
|
err = en.Append(0x80)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OldDataDir")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "r"
|
||||||
|
err = en.Append(0xa1, 0x72)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.Recursive)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Recursive")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "i"
|
||||||
|
err = en.Append(0xa1, 0x69)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.Immediate)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Immediate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "u"
|
||||||
|
err = en.Append(0xa1, 0x75)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.UndoWrite)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "UndoWrite")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
||||||
|
// write "o"
|
||||||
|
err = en.Append(0xa1, 0x6f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.OldDataDir)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "OldDataDir")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -899,27 +908,28 @@ func (z *DeleteOptions) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
// variable map header, size zb0001Len
|
// variable map header, size zb0001Len
|
||||||
o = append(o, 0x80|uint8(zb0001Len))
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// string "BaseOptions"
|
// string "BaseOptions"
|
||||||
o = append(o, 0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
|
o = append(o, 0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
|
||||||
// map header, size 0
|
// map header, size 0
|
||||||
_ = z.BaseOptions
|
_ = z.BaseOptions
|
||||||
o = append(o, 0x80)
|
o = append(o, 0x80)
|
||||||
// string "r"
|
// string "r"
|
||||||
o = append(o, 0xa1, 0x72)
|
o = append(o, 0xa1, 0x72)
|
||||||
o = msgp.AppendBool(o, z.Recursive)
|
o = msgp.AppendBool(o, z.Recursive)
|
||||||
// string "i"
|
// string "i"
|
||||||
o = append(o, 0xa1, 0x69)
|
o = append(o, 0xa1, 0x69)
|
||||||
o = msgp.AppendBool(o, z.Immediate)
|
o = msgp.AppendBool(o, z.Immediate)
|
||||||
// string "u"
|
// string "u"
|
||||||
o = append(o, 0xa1, 0x75)
|
o = append(o, 0xa1, 0x75)
|
||||||
o = msgp.AppendBool(o, z.UndoWrite)
|
o = msgp.AppendBool(o, z.UndoWrite)
|
||||||
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
||||||
// string "o"
|
// string "o"
|
||||||
o = append(o, 0xa1, 0x6f)
|
o = append(o, 0xa1, 0x6f)
|
||||||
o = msgp.AppendString(o, z.OldDataDir)
|
o = msgp.AppendString(o, z.OldDataDir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -934,6 +944,8 @@ func (z *DeleteOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
@@ -989,6 +1001,7 @@ func (z *DeleteOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "OldDataDir")
|
err = msgp.WrapError(err, "OldDataDir")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -997,6 +1010,11 @@ func (z *DeleteOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.OldDataDir = ""
|
||||||
|
}
|
||||||
|
|
||||||
o = bts
|
o = bts
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2453,6 +2471,9 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Data")
|
err = msgp.WrapError(err, "Data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if z.Data == nil {
|
||||||
|
z.Data = make([]byte, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
z.NumVersions, err = dc.ReadInt()
|
z.NumVersions, err = dc.ReadInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2487,6 +2508,9 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Checksum")
|
err = msgp.WrapError(err, "Checksum")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if z.Checksum == nil {
|
||||||
|
z.Checksum = make([]byte, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
z.Versioned, err = dc.ReadBool()
|
z.Versioned, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2907,6 +2931,9 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Data")
|
err = msgp.WrapError(err, "Data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if z.Data == nil {
|
||||||
|
z.Data = make([]byte, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
z.NumVersions, bts, err = msgp.ReadIntBytes(bts)
|
z.NumVersions, bts, err = msgp.ReadIntBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2937,6 +2964,9 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Checksum")
|
err = msgp.WrapError(err, "Checksum")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if z.Checksum == nil {
|
||||||
|
z.Checksum = make([]byte, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
z.Versioned, bts, err = msgp.ReadBoolBytes(bts)
|
z.Versioned, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3961,6 +3991,9 @@ func (z *RawFileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Buf")
|
err = msgp.WrapError(err, "Buf")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if z.Buf == nil {
|
||||||
|
z.Buf = make([]byte, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
@@ -4038,6 +4071,9 @@ func (z *RawFileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Buf")
|
err = msgp.WrapError(err, "Buf")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if z.Buf == nil {
|
||||||
|
z.Buf = make([]byte, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
@@ -4220,6 +4256,8 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, err = dc.ReadMapKeyPtr()
|
field, err = dc.ReadMapKeyPtr()
|
||||||
@@ -4240,6 +4278,7 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "fl":
|
case "fl":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, err = dc.ReadArrayHeader()
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
@@ -4291,6 +4330,11 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.Prefix = ""
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4309,87 +4353,88 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// write "bk"
|
// write "bk"
|
||||||
err = en.Append(0xa2, 0x62, 0x6b)
|
err = en.Append(0xa2, 0x62, 0x6b)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Bucket)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Bucket")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
|
||||||
// write "pr"
|
|
||||||
err = en.Append(0xa2, 0x70, 0x72)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteString(z.Prefix)
|
err = en.WriteString(z.Bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
// write "fl"
|
// write "pr"
|
||||||
err = en.Append(0xa2, 0x66, 0x6c)
|
err = en.Append(0xa2, 0x70, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteArrayHeader(uint32(len(z.Files)))
|
err = en.WriteString(z.Prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Files")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for za0001 := range z.Files {
|
}
|
||||||
err = en.WriteString(z.Files[za0001])
|
// write "fl"
|
||||||
|
err = en.Append(0xa2, 0x66, 0x6c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Files", za0001)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
err = en.WriteArrayHeader(uint32(len(z.Files)))
|
||||||
// write "ms"
|
if err != nil {
|
||||||
err = en.Append(0xa2, 0x6d, 0x73)
|
err = msgp.WrapError(err, "Files")
|
||||||
if err != nil {
|
return
|
||||||
return
|
}
|
||||||
}
|
for za0001 := range z.Files {
|
||||||
err = en.WriteInt64(z.MaxSize)
|
err = en.WriteString(z.Files[za0001])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "MaxSize")
|
err = msgp.WrapError(err, "Files", za0001)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "mo"
|
}
|
||||||
err = en.Append(0xa2, 0x6d, 0x6f)
|
// write "ms"
|
||||||
if err != nil {
|
err = en.Append(0xa2, 0x6d, 0x73)
|
||||||
return
|
if err != nil {
|
||||||
}
|
return
|
||||||
err = en.WriteBool(z.MetadataOnly)
|
}
|
||||||
if err != nil {
|
err = en.WriteInt64(z.MaxSize)
|
||||||
err = msgp.WrapError(err, "MetadataOnly")
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "MaxSize")
|
||||||
}
|
return
|
||||||
// write "ab"
|
}
|
||||||
err = en.Append(0xa2, 0x61, 0x62)
|
// write "mo"
|
||||||
if err != nil {
|
err = en.Append(0xa2, 0x6d, 0x6f)
|
||||||
return
|
if err != nil {
|
||||||
}
|
return
|
||||||
err = en.WriteBool(z.AbortOn404)
|
}
|
||||||
if err != nil {
|
err = en.WriteBool(z.MetadataOnly)
|
||||||
err = msgp.WrapError(err, "AbortOn404")
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "MetadataOnly")
|
||||||
}
|
return
|
||||||
// write "mr"
|
}
|
||||||
err = en.Append(0xa2, 0x6d, 0x72)
|
// write "ab"
|
||||||
if err != nil {
|
err = en.Append(0xa2, 0x61, 0x62)
|
||||||
return
|
if err != nil {
|
||||||
}
|
return
|
||||||
err = en.WriteInt(z.MaxResults)
|
}
|
||||||
if err != nil {
|
err = en.WriteBool(z.AbortOn404)
|
||||||
err = msgp.WrapError(err, "MaxResults")
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "AbortOn404")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "mr"
|
||||||
|
err = en.Append(0xa2, 0x6d, 0x72)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.MaxResults)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "MaxResults")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4407,35 +4452,36 @@ func (z *ReadMultipleReq) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
// variable map header, size zb0001Len
|
// variable map header, size zb0001Len
|
||||||
o = append(o, 0x80|uint8(zb0001Len))
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
|
if zb0001Len != 0 {
|
||||||
|
// string "bk"
|
||||||
|
o = append(o, 0xa2, 0x62, 0x6b)
|
||||||
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
|
// string "pr"
|
||||||
|
o = append(o, 0xa2, 0x70, 0x72)
|
||||||
|
o = msgp.AppendString(o, z.Prefix)
|
||||||
|
}
|
||||||
|
// string "fl"
|
||||||
|
o = append(o, 0xa2, 0x66, 0x6c)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Files)))
|
||||||
|
for za0001 := range z.Files {
|
||||||
|
o = msgp.AppendString(o, z.Files[za0001])
|
||||||
|
}
|
||||||
|
// string "ms"
|
||||||
|
o = append(o, 0xa2, 0x6d, 0x73)
|
||||||
|
o = msgp.AppendInt64(o, z.MaxSize)
|
||||||
|
// string "mo"
|
||||||
|
o = append(o, 0xa2, 0x6d, 0x6f)
|
||||||
|
o = msgp.AppendBool(o, z.MetadataOnly)
|
||||||
|
// string "ab"
|
||||||
|
o = append(o, 0xa2, 0x61, 0x62)
|
||||||
|
o = msgp.AppendBool(o, z.AbortOn404)
|
||||||
|
// string "mr"
|
||||||
|
o = append(o, 0xa2, 0x6d, 0x72)
|
||||||
|
o = msgp.AppendInt(o, z.MaxResults)
|
||||||
}
|
}
|
||||||
// string "bk"
|
|
||||||
o = append(o, 0xa2, 0x62, 0x6b)
|
|
||||||
o = msgp.AppendString(o, z.Bucket)
|
|
||||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
|
||||||
// string "pr"
|
|
||||||
o = append(o, 0xa2, 0x70, 0x72)
|
|
||||||
o = msgp.AppendString(o, z.Prefix)
|
|
||||||
}
|
|
||||||
// string "fl"
|
|
||||||
o = append(o, 0xa2, 0x66, 0x6c)
|
|
||||||
o = msgp.AppendArrayHeader(o, uint32(len(z.Files)))
|
|
||||||
for za0001 := range z.Files {
|
|
||||||
o = msgp.AppendString(o, z.Files[za0001])
|
|
||||||
}
|
|
||||||
// string "ms"
|
|
||||||
o = append(o, 0xa2, 0x6d, 0x73)
|
|
||||||
o = msgp.AppendInt64(o, z.MaxSize)
|
|
||||||
// string "mo"
|
|
||||||
o = append(o, 0xa2, 0x6d, 0x6f)
|
|
||||||
o = msgp.AppendBool(o, z.MetadataOnly)
|
|
||||||
// string "ab"
|
|
||||||
o = append(o, 0xa2, 0x61, 0x62)
|
|
||||||
o = msgp.AppendBool(o, z.AbortOn404)
|
|
||||||
// string "mr"
|
|
||||||
o = append(o, 0xa2, 0x6d, 0x72)
|
|
||||||
o = msgp.AppendInt(o, z.MaxResults)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4449,6 +4495,8 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 1 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
@@ -4469,6 +4517,7 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "fl":
|
case "fl":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
@@ -4520,6 +4569,11 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.Prefix = ""
|
||||||
|
}
|
||||||
|
|
||||||
o = bts
|
o = bts
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4544,6 +4598,8 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 2 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, err = dc.ReadMapKeyPtr()
|
field, err = dc.ReadMapKeyPtr()
|
||||||
@@ -4564,6 +4620,7 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "fl":
|
case "fl":
|
||||||
z.File, err = dc.ReadString()
|
z.File, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4582,6 +4639,7 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "Error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x2
|
||||||
case "d":
|
case "d":
|
||||||
z.Data, err = dc.ReadBytes(z.Data)
|
z.Data, err = dc.ReadBytes(z.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4602,6 +4660,15 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if zb0001Mask != 0x3 {
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.Prefix = ""
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x2) == 0 {
|
||||||
|
z.Error = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4624,82 +4691,83 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// write "bk"
|
// write "bk"
|
||||||
err = en.Append(0xa2, 0x62, 0x6b)
|
err = en.Append(0xa2, 0x62, 0x6b)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.Bucket)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Bucket")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
|
||||||
// write "pr"
|
|
||||||
err = en.Append(0xa2, 0x70, 0x72)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteString(z.Prefix)
|
err = en.WriteString(z.Bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
// write "fl"
|
// write "pr"
|
||||||
err = en.Append(0xa2, 0x66, 0x6c)
|
err = en.Append(0xa2, 0x70, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteString(z.File)
|
err = en.WriteString(z.Prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "File")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "ex"
|
}
|
||||||
err = en.Append(0xa2, 0x65, 0x78)
|
// write "fl"
|
||||||
if err != nil {
|
err = en.Append(0xa2, 0x66, 0x6c)
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteBool(z.Exists)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Exists")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
|
||||||
// write "er"
|
|
||||||
err = en.Append(0xa2, 0x65, 0x72)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteString(z.Error)
|
err = en.WriteString(z.File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "File")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ex"
|
||||||
|
err = en.Append(0xa2, 0x65, 0x78)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.Exists)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
||||||
|
// write "er"
|
||||||
|
err = en.Append(0xa2, 0x65, 0x72)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Error)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "d"
|
||||||
|
err = en.Append(0xa1, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBytes(z.Data)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "m"
|
||||||
|
err = en.Append(0xa1, 0x6d)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.Modtime)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Modtime")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// write "d"
|
|
||||||
err = en.Append(0xa1, 0x64)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteBytes(z.Data)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Data")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "m"
|
|
||||||
err = en.Append(0xa1, 0x6d)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteTime(z.Modtime)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Modtime")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4721,34 +4789,35 @@ func (z *ReadMultipleResp) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
// variable map header, size zb0001Len
|
// variable map header, size zb0001Len
|
||||||
o = append(o, 0x80|uint8(zb0001Len))
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
|
if zb0001Len != 0 {
|
||||||
|
// string "bk"
|
||||||
|
o = append(o, 0xa2, 0x62, 0x6b)
|
||||||
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
|
// string "pr"
|
||||||
|
o = append(o, 0xa2, 0x70, 0x72)
|
||||||
|
o = msgp.AppendString(o, z.Prefix)
|
||||||
|
}
|
||||||
|
// string "fl"
|
||||||
|
o = append(o, 0xa2, 0x66, 0x6c)
|
||||||
|
o = msgp.AppendString(o, z.File)
|
||||||
|
// string "ex"
|
||||||
|
o = append(o, 0xa2, 0x65, 0x78)
|
||||||
|
o = msgp.AppendBool(o, z.Exists)
|
||||||
|
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
||||||
|
// string "er"
|
||||||
|
o = append(o, 0xa2, 0x65, 0x72)
|
||||||
|
o = msgp.AppendString(o, z.Error)
|
||||||
|
}
|
||||||
|
// string "d"
|
||||||
|
o = append(o, 0xa1, 0x64)
|
||||||
|
o = msgp.AppendBytes(o, z.Data)
|
||||||
|
// string "m"
|
||||||
|
o = append(o, 0xa1, 0x6d)
|
||||||
|
o = msgp.AppendTime(o, z.Modtime)
|
||||||
}
|
}
|
||||||
// string "bk"
|
|
||||||
o = append(o, 0xa2, 0x62, 0x6b)
|
|
||||||
o = msgp.AppendString(o, z.Bucket)
|
|
||||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
|
||||||
// string "pr"
|
|
||||||
o = append(o, 0xa2, 0x70, 0x72)
|
|
||||||
o = msgp.AppendString(o, z.Prefix)
|
|
||||||
}
|
|
||||||
// string "fl"
|
|
||||||
o = append(o, 0xa2, 0x66, 0x6c)
|
|
||||||
o = msgp.AppendString(o, z.File)
|
|
||||||
// string "ex"
|
|
||||||
o = append(o, 0xa2, 0x65, 0x78)
|
|
||||||
o = msgp.AppendBool(o, z.Exists)
|
|
||||||
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
|
||||||
// string "er"
|
|
||||||
o = append(o, 0xa2, 0x65, 0x72)
|
|
||||||
o = msgp.AppendString(o, z.Error)
|
|
||||||
}
|
|
||||||
// string "d"
|
|
||||||
o = append(o, 0xa1, 0x64)
|
|
||||||
o = msgp.AppendBytes(o, z.Data)
|
|
||||||
// string "m"
|
|
||||||
o = append(o, 0xa1, 0x6d)
|
|
||||||
o = msgp.AppendTime(o, z.Modtime)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4762,6 +4831,8 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 2 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
@@ -4782,6 +4853,7 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "fl":
|
case "fl":
|
||||||
z.File, bts, err = msgp.ReadStringBytes(bts)
|
z.File, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4800,6 +4872,7 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "Error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x2
|
||||||
case "d":
|
case "d":
|
||||||
z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data)
|
z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4820,6 +4893,15 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if zb0001Mask != 0x3 {
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.Prefix = ""
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x2) == 0 {
|
||||||
|
z.Error = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
o = bts
|
o = bts
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -464,16 +464,20 @@ func (client *storageRESTClient) WriteAll(ctx context.Context, volume string, pa
|
|||||||
// CheckParts - stat all file parts.
|
// CheckParts - stat all file parts.
|
||||||
func (client *storageRESTClient) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) {
|
func (client *storageRESTClient) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) {
|
||||||
var resp *CheckPartsResp
|
var resp *CheckPartsResp
|
||||||
resp, err := storageCheckPartsRPC.Call(ctx, client.gridConn, &CheckPartsHandlerParams{
|
st, err := storageCheckPartsRPC.Call(ctx, client.gridConn, &CheckPartsHandlerParams{
|
||||||
DiskID: *client.diskID.Load(),
|
DiskID: *client.diskID.Load(),
|
||||||
Volume: volume,
|
Volume: volume,
|
||||||
FilePath: path,
|
FilePath: path,
|
||||||
FI: fi,
|
FI: fi,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, toStorageErr(err)
|
||||||
}
|
}
|
||||||
return resp, nil
|
err = st.Results(func(r *CheckPartsResp) error {
|
||||||
|
resp = r
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return resp, toStorageErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameData - rename source path to destination path atomically, metadata and data file.
|
// RenameData - rename source path to destination path atomically, metadata and data file.
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ type storageRESTServer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
storageCheckPartsRPC = grid.NewSingleHandler[*CheckPartsHandlerParams, *CheckPartsResp](grid.HandlerCheckParts2, func() *CheckPartsHandlerParams { return &CheckPartsHandlerParams{} }, func() *CheckPartsResp { return &CheckPartsResp{} })
|
storageCheckPartsRPC = grid.NewStream[*CheckPartsHandlerParams, grid.NoPayload, *CheckPartsResp](grid.HandlerCheckParts3, func() *CheckPartsHandlerParams { return &CheckPartsHandlerParams{} }, nil, func() *CheckPartsResp { return &CheckPartsResp{} })
|
||||||
storageDeleteFileRPC = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams { return &DeleteFileHandlerParams{} }, grid.NewNoPayload).AllowCallRequestPool(true)
|
storageDeleteFileRPC = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams { return &DeleteFileHandlerParams{} }, grid.NewNoPayload).AllowCallRequestPool(true)
|
||||||
storageDeleteVersionRPC = grid.NewSingleHandler[*DeleteVersionHandlerParams, grid.NoPayload](grid.HandlerDeleteVersion, func() *DeleteVersionHandlerParams { return &DeleteVersionHandlerParams{} }, grid.NewNoPayload)
|
storageDeleteVersionRPC = grid.NewSingleHandler[*DeleteVersionHandlerParams, grid.NoPayload](grid.HandlerDeleteVersion, func() *DeleteVersionHandlerParams { return &DeleteVersionHandlerParams{} }, grid.NewNoPayload)
|
||||||
storageDiskInfoRPC = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} }, func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse().AllowCallRequestPool(true)
|
storageDiskInfoRPC = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} }, func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse().AllowCallRequestPool(true)
|
||||||
@@ -456,16 +456,20 @@ func (s *storageRESTServer) UpdateMetadataHandler(p *MetadataHandlerParams) (gri
|
|||||||
return grid.NewNPErr(s.getStorage().UpdateMetadata(context.Background(), volume, filePath, p.FI, p.UpdateOpts))
|
return grid.NewNPErr(s.getStorage().UpdateMetadata(context.Background(), volume, filePath, p.FI, p.UpdateOpts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPartsHandler - check if a file metadata exists.
|
// CheckPartsHandler - check if a file parts exists.
|
||||||
func (s *storageRESTServer) CheckPartsHandler(p *CheckPartsHandlerParams) (*CheckPartsResp, *grid.RemoteErr) {
|
func (s *storageRESTServer) CheckPartsHandler(ctx context.Context, p *CheckPartsHandlerParams, out chan<- *CheckPartsResp) *grid.RemoteErr {
|
||||||
if !s.checkID(p.DiskID) {
|
if !s.checkID(p.DiskID) {
|
||||||
return nil, grid.NewRemoteErr(errDiskNotFound)
|
return grid.NewRemoteErr(errDiskNotFound)
|
||||||
}
|
}
|
||||||
volume := p.Volume
|
volume := p.Volume
|
||||||
filePath := p.FilePath
|
filePath := p.FilePath
|
||||||
|
|
||||||
resp, err := s.getStorage().CheckParts(context.Background(), volume, filePath, p.FI)
|
resp, err := s.getStorage().CheckParts(ctx, volume, filePath, p.FI)
|
||||||
return resp, grid.NewRemoteErr(err)
|
if err != nil {
|
||||||
|
return grid.NewRemoteErr(err)
|
||||||
|
}
|
||||||
|
out <- resp
|
||||||
|
return grid.NewRemoteErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *storageRESTServer) WriteAllHandler(p *WriteAllHandlerParams) (grid.NoPayload, *grid.RemoteErr) {
|
func (s *storageRESTServer) WriteAllHandler(p *WriteAllHandlerParams) (grid.NoPayload, *grid.RemoteErr) {
|
||||||
@@ -1382,7 +1386,7 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
|
|||||||
logger.FatalIf(storageRenameDataRPC.Register(gm, server.RenameDataHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageRenameDataRPC.Register(gm, server.RenameDataHandler, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageRenameDataInlineRPC.Register(gm, server.RenameDataInlineHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageRenameDataInlineRPC.Register(gm, server.RenameDataInlineHandler, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageDeleteFileRPC.Register(gm, server.DeleteFileHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageDeleteFileRPC.Register(gm, server.DeleteFileHandler, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageCheckPartsRPC.Register(gm, server.CheckPartsHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageCheckPartsRPC.RegisterNoInput(gm, server.CheckPartsHandler, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageReadVersionRPC.Register(gm, server.ReadVersionHandlerWS, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageReadVersionRPC.Register(gm, server.ReadVersionHandlerWS, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageWriteMetadataRPC.Register(gm, server.WriteMetadataHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageWriteMetadataRPC.Register(gm, server.WriteMetadataHandler, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageUpdateMetadataRPC.Register(gm, server.UpdateMetadataHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageUpdateMetadataRPC.Register(gm, server.UpdateMetadataHandler, endpoint.Path), "unable to register handler")
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -34,7 +35,6 @@ import (
|
|||||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/pkg/v3/ldap"
|
"github.com/minio/pkg/v3/ldap"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) {
|
func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) {
|
||||||
|
|||||||
@@ -2247,12 +2247,12 @@ func getEndpointsLocalAddr(endpointServerPools EndpointServerPools) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetches a random number between range min-max.
|
// fetches a random number between range min-max.
|
||||||
func getRandomRange(min, max int, seed int64) int {
|
func getRandomRange(minN, maxN int, seed int64) int {
|
||||||
// special value -1 means no explicit seeding.
|
// special value -1 means no explicit seeding.
|
||||||
if seed != -1 {
|
if seed == -1 {
|
||||||
rand.Seed(seed)
|
return rand.New(rand.NewSource(time.Now().UnixNano())).Intn(maxN-minN) + minN
|
||||||
}
|
}
|
||||||
return rand.Intn(max-min) + min
|
return rand.New(rand.NewSource(seed)).Intn(maxN-minN) + minN
|
||||||
}
|
}
|
||||||
|
|
||||||
// Randomizes the order of bytes in the byte array
|
// Randomizes the order of bytes in the byte array
|
||||||
|
|||||||
@@ -411,7 +411,12 @@ func startProfiler(profilerType string) (minioProfiler, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
stop := fgprof.Start(f, fgprof.FormatPprof)
|
stop := fgprof.Start(f, fgprof.FormatPprof)
|
||||||
|
startedAt := time.Now()
|
||||||
prof.stopFn = func() ([]byte, error) {
|
prof.stopFn = func() ([]byte, error) {
|
||||||
|
if elapsed := time.Since(startedAt); elapsed < 100*time.Millisecond {
|
||||||
|
// Light hack around https://github.com/felixge/fgprof/pull/34
|
||||||
|
time.Sleep(100*time.Millisecond - elapsed)
|
||||||
|
}
|
||||||
err := stop()
|
err := stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||||
@@ -59,10 +60,15 @@ func (az *warmBackendAzure) getDest(object string) string {
|
|||||||
return destObj
|
return destObj
|
||||||
}
|
}
|
||||||
|
|
||||||
func (az *warmBackendAzure) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
|
func (az *warmBackendAzure) PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
|
||||||
|
azMeta := map[string]*string{}
|
||||||
|
for k, v := range meta {
|
||||||
|
azMeta[k] = to.Ptr(v)
|
||||||
|
}
|
||||||
resp, err := az.clnt.UploadStream(ctx, az.Bucket, az.getDest(object), io.LimitReader(r, length), &azblob.UploadStreamOptions{
|
resp, err := az.clnt.UploadStream(ctx, az.Bucket, az.getDest(object), io.LimitReader(r, length), &azblob.UploadStreamOptions{
|
||||||
Concurrency: 4,
|
Concurrency: 4,
|
||||||
AccessTier: az.tier(), // set tier if specified
|
AccessTier: az.tier(), // set tier if specified
|
||||||
|
Metadata: azMeta,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", azureToObjectError(err, az.Bucket, az.getDest(object))
|
return "", azureToObjectError(err, az.Bucket, az.getDest(object))
|
||||||
@@ -74,6 +80,10 @@ func (az *warmBackendAzure) Put(ctx context.Context, object string, r io.Reader,
|
|||||||
return remoteVersionID(vid), nil
|
return remoteVersionID(vid), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (az *warmBackendAzure) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
|
||||||
|
return az.PutWithMeta(ctx, object, r, length, map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
func (az *warmBackendAzure) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) {
|
func (az *warmBackendAzure) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) {
|
||||||
if opts.startOffset < 0 {
|
if opts.startOffset < 0 {
|
||||||
return nil, InvalidRange{}
|
return nil, InvalidRange{}
|
||||||
|
|||||||
+12
-5
@@ -47,16 +47,17 @@ func (gcs *warmBackendGCS) getDest(object string) string {
|
|||||||
return destObj
|
return destObj
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: add support for remote version ID in GCS remote tier and remove this.
|
func (gcs *warmBackendGCS) PutWithMeta(ctx context.Context, key string, data io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
|
||||||
// Currently it's a no-op.
|
|
||||||
|
|
||||||
func (gcs *warmBackendGCS) Put(ctx context.Context, key string, data io.Reader, length int64) (remoteVersionID, error) {
|
|
||||||
object := gcs.client.Bucket(gcs.Bucket).Object(gcs.getDest(key))
|
object := gcs.client.Bucket(gcs.Bucket).Object(gcs.getDest(key))
|
||||||
// TODO: set storage class
|
|
||||||
w := object.NewWriter(ctx)
|
w := object.NewWriter(ctx)
|
||||||
if gcs.StorageClass != "" {
|
if gcs.StorageClass != "" {
|
||||||
w.ObjectAttrs.StorageClass = gcs.StorageClass
|
w.ObjectAttrs.StorageClass = gcs.StorageClass
|
||||||
}
|
}
|
||||||
|
w.ObjectAttrs.Metadata = meta
|
||||||
|
if _, err := xioutil.Copy(w, data); err != nil {
|
||||||
|
return "", gcsToObjectError(err, gcs.Bucket, key)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := xioutil.Copy(w, data); err != nil {
|
if _, err := xioutil.Copy(w, data); err != nil {
|
||||||
return "", gcsToObjectError(err, gcs.Bucket, key)
|
return "", gcsToObjectError(err, gcs.Bucket, key)
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,12 @@ func (gcs *warmBackendGCS) Put(ctx context.Context, key string, data io.Reader,
|
|||||||
return "", w.Close()
|
return "", w.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: add support for remote version ID in GCS remote tier and remove this.
|
||||||
|
// Currently it's a no-op.
|
||||||
|
func (gcs *warmBackendGCS) Put(ctx context.Context, key string, data io.Reader, length int64) (remoteVersionID, error) {
|
||||||
|
return gcs.PutWithMeta(ctx, key, data, length, map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
func (gcs *warmBackendGCS) Get(ctx context.Context, key string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) {
|
func (gcs *warmBackendGCS) Get(ctx context.Context, key string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) {
|
||||||
// GCS storage decompresses a gzipped object by default and returns the data.
|
// GCS storage decompresses a gzipped object by default and returns the data.
|
||||||
// Refer to https://cloud.google.com/storage/docs/transcoding#decompressive_transcoding
|
// Refer to https://cloud.google.com/storage/docs/transcoding#decompressive_transcoding
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func optimalPartSize(objectSize int64) (partSize int64, err error) {
|
|||||||
return partSize, nil
|
return partSize, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *warmBackendMinIO) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
|
func (m *warmBackendMinIO) PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
|
||||||
partSize, err := optimalPartSize(length)
|
partSize, err := optimalPartSize(length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return remoteVersionID(""), err
|
return remoteVersionID(""), err
|
||||||
@@ -87,10 +87,15 @@ func (m *warmBackendMinIO) Put(ctx context.Context, object string, r io.Reader,
|
|||||||
StorageClass: m.StorageClass,
|
StorageClass: m.StorageClass,
|
||||||
PartSize: uint64(partSize),
|
PartSize: uint64(partSize),
|
||||||
DisableContentSha256: true,
|
DisableContentSha256: true,
|
||||||
|
UserMetadata: meta,
|
||||||
})
|
})
|
||||||
return remoteVersionID(res.VersionID), m.ToObjectError(err, object)
|
return remoteVersionID(res.VersionID), m.ToObjectError(err, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *warmBackendMinIO) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
|
||||||
|
return m.PutWithMeta(ctx, object, r, length, map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
func newWarmBackendMinIO(conf madmin.TierMinIO, tier string) (*warmBackendMinIO, error) {
|
func newWarmBackendMinIO(conf madmin.TierMinIO, tier string) (*warmBackendMinIO, error) {
|
||||||
// Validation of credentials
|
// Validation of credentials
|
||||||
if conf.AccessKey == "" || conf.SecretKey == "" {
|
if conf.AccessKey == "" || conf.SecretKey == "" {
|
||||||
|
|||||||
@@ -56,14 +56,19 @@ func (s3 *warmBackendS3) getDest(object string) string {
|
|||||||
return destObj
|
return destObj
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3 *warmBackendS3) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
|
func (s3 *warmBackendS3) PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
|
||||||
res, err := s3.client.PutObject(ctx, s3.Bucket, s3.getDest(object), r, length, minio.PutObjectOptions{
|
res, err := s3.client.PutObject(ctx, s3.Bucket, s3.getDest(object), r, length, minio.PutObjectOptions{
|
||||||
SendContentMd5: true,
|
SendContentMd5: true,
|
||||||
StorageClass: s3.StorageClass,
|
StorageClass: s3.StorageClass,
|
||||||
|
UserMetadata: meta,
|
||||||
})
|
})
|
||||||
return remoteVersionID(res.VersionID), s3.ToObjectError(err, object)
|
return remoteVersionID(res.VersionID), s3.ToObjectError(err, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s3 *warmBackendS3) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
|
||||||
|
return s3.PutWithMeta(ctx, object, r, length, map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
func (s3 *warmBackendS3) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error) {
|
func (s3 *warmBackendS3) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error) {
|
||||||
gopts := minio.GetObjectOptions{}
|
gopts := minio.GetObjectOptions{}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type WarmBackendGetOpts struct {
|
|||||||
// WarmBackend provides interface to be implemented by remote tier backends
|
// WarmBackend provides interface to be implemented by remote tier backends
|
||||||
type WarmBackend interface {
|
type WarmBackend interface {
|
||||||
Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error)
|
Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error)
|
||||||
|
PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error)
|
||||||
Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error)
|
Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error)
|
||||||
Remove(ctx context.Context, object string, rv remoteVersionID) error
|
Remove(ctx context.Context, object string, rv remoteVersionID) error
|
||||||
InUse(ctx context.Context) (bool, error)
|
InUse(ctx context.Context) (bool, error)
|
||||||
|
|||||||
@@ -509,9 +509,7 @@ func (p *xlStorageDiskIDCheck) CheckParts(ctx context.Context, volume string, pa
|
|||||||
}
|
}
|
||||||
defer done(0, &err)
|
defer done(0, &err)
|
||||||
|
|
||||||
return xioutil.WithDeadline[*CheckPartsResp](ctx, globalDriveConfig.GetMaxTimeout(), func(ctx context.Context) (res *CheckPartsResp, err error) {
|
return p.storage.CheckParts(ctx, volume, path, fi)
|
||||||
return p.storage.CheckParts(ctx, volume, path, fi)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *xlStorageDiskIDCheck) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) {
|
func (p *xlStorageDiskIDCheck) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) {
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ func isXLMetaErasureInfoValid(data, parity int) bool {
|
|||||||
return ((data >= parity) && (data > 0) && (parity >= 0))
|
return ((data >= parity) && (data > 0) && (parity >= 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//msgp:clearomitted
|
||||||
|
|
||||||
//go:generate msgp -file=$GOFILE -unexported
|
//go:generate msgp -file=$GOFILE -unexported
|
||||||
|
|
||||||
// A xlMetaV1Object represents `xl.meta` metadata header.
|
// A xlMetaV1Object represents `xl.meta` metadata header.
|
||||||
|
|||||||
+152
-116
@@ -561,6 +561,8 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 3 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, err = dc.ReadMapKeyPtr()
|
field, err = dc.ReadMapKeyPtr()
|
||||||
@@ -605,6 +607,7 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Index")
|
err = msgp.WrapError(err, "Index")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "crc":
|
case "crc":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, err = dc.ReadMapHeader()
|
zb0002, err = dc.ReadMapHeader()
|
||||||
@@ -635,12 +638,14 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
z.Checksums[za0001] = za0002
|
z.Checksums[za0001] = za0002
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x2
|
||||||
case "err":
|
case "err":
|
||||||
z.Error, err = dc.ReadString()
|
z.Error, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "Error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x4
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -649,6 +654,18 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if zb0001Mask != 0x7 {
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.Index = nil
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x2) == 0 {
|
||||||
|
z.Checksums = nil
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x4) == 0 {
|
||||||
|
z.Error = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,105 +692,106 @@ func (z *ObjectPartInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// write "e"
|
// write "e"
|
||||||
err = en.Append(0xa1, 0x65)
|
err = en.Append(0xa1, 0x65)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteString(z.ETag)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "ETag")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "n"
|
|
||||||
err = en.Append(0xa1, 0x6e)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteInt(z.Number)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Number")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "s"
|
|
||||||
err = en.Append(0xa1, 0x73)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteInt64(z.Size)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "Size")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "as"
|
|
||||||
err = en.Append(0xa2, 0x61, 0x73)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteInt64(z.ActualSize)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "ActualSize")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// write "mt"
|
|
||||||
err = en.Append(0xa2, 0x6d, 0x74)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = en.WriteTime(z.ModTime)
|
|
||||||
if err != nil {
|
|
||||||
err = msgp.WrapError(err, "ModTime")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (zb0001Mask & 0x20) == 0 { // if not omitted
|
|
||||||
// write "i"
|
|
||||||
err = en.Append(0xa1, 0x69)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteBytes(z.Index)
|
err = en.WriteString(z.ETag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Index")
|
err = msgp.WrapError(err, "ETag")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
// write "n"
|
||||||
if (zb0001Mask & 0x40) == 0 { // if not omitted
|
err = en.Append(0xa1, 0x6e)
|
||||||
// write "crc"
|
|
||||||
err = en.Append(0xa3, 0x63, 0x72, 0x63)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteMapHeader(uint32(len(z.Checksums)))
|
err = en.WriteInt(z.Number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Checksums")
|
err = msgp.WrapError(err, "Number")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for za0001, za0002 := range z.Checksums {
|
// write "s"
|
||||||
err = en.WriteString(za0001)
|
err = en.Append(0xa1, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.Size)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Size")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "as"
|
||||||
|
err = en.Append(0xa2, 0x61, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.ActualSize)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ActualSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "mt"
|
||||||
|
err = en.Append(0xa2, 0x6d, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.ModTime)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ModTime")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x20) == 0 { // if not omitted
|
||||||
|
// write "i"
|
||||||
|
err = en.Append(0xa1, 0x69)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBytes(z.Index)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Index")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x40) == 0 { // if not omitted
|
||||||
|
// write "crc"
|
||||||
|
err = en.Append(0xa3, 0x63, 0x72, 0x63)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteMapHeader(uint32(len(z.Checksums)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Checksums")
|
err = msgp.WrapError(err, "Checksums")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteString(za0002)
|
for za0001, za0002 := range z.Checksums {
|
||||||
if err != nil {
|
err = en.WriteString(za0001)
|
||||||
err = msgp.WrapError(err, "Checksums", za0001)
|
if err != nil {
|
||||||
return
|
err = msgp.WrapError(err, "Checksums")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(za0002)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Checksums", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
||||||
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
// write "err"
|
||||||
// write "err"
|
err = en.Append(0xa3, 0x65, 0x72, 0x72)
|
||||||
err = en.Append(0xa3, 0x65, 0x72, 0x72)
|
if err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return
|
}
|
||||||
}
|
err = en.WriteString(z.Error)
|
||||||
err = en.WriteString(z.Error)
|
if err != nil {
|
||||||
if err != nil {
|
err = msgp.WrapError(err, "Error")
|
||||||
err = msgp.WrapError(err, "Error")
|
return
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -800,42 +818,43 @@ func (z *ObjectPartInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
// variable map header, size zb0001Len
|
// variable map header, size zb0001Len
|
||||||
o = append(o, 0x80|uint8(zb0001Len))
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
if zb0001Len == 0 {
|
|
||||||
return
|
// skip if no fields are to be emitted
|
||||||
}
|
if zb0001Len != 0 {
|
||||||
// string "e"
|
// string "e"
|
||||||
o = append(o, 0xa1, 0x65)
|
o = append(o, 0xa1, 0x65)
|
||||||
o = msgp.AppendString(o, z.ETag)
|
o = msgp.AppendString(o, z.ETag)
|
||||||
// string "n"
|
// string "n"
|
||||||
o = append(o, 0xa1, 0x6e)
|
o = append(o, 0xa1, 0x6e)
|
||||||
o = msgp.AppendInt(o, z.Number)
|
o = msgp.AppendInt(o, z.Number)
|
||||||
// string "s"
|
// string "s"
|
||||||
o = append(o, 0xa1, 0x73)
|
o = append(o, 0xa1, 0x73)
|
||||||
o = msgp.AppendInt64(o, z.Size)
|
o = msgp.AppendInt64(o, z.Size)
|
||||||
// string "as"
|
// string "as"
|
||||||
o = append(o, 0xa2, 0x61, 0x73)
|
o = append(o, 0xa2, 0x61, 0x73)
|
||||||
o = msgp.AppendInt64(o, z.ActualSize)
|
o = msgp.AppendInt64(o, z.ActualSize)
|
||||||
// string "mt"
|
// string "mt"
|
||||||
o = append(o, 0xa2, 0x6d, 0x74)
|
o = append(o, 0xa2, 0x6d, 0x74)
|
||||||
o = msgp.AppendTime(o, z.ModTime)
|
o = msgp.AppendTime(o, z.ModTime)
|
||||||
if (zb0001Mask & 0x20) == 0 { // if not omitted
|
if (zb0001Mask & 0x20) == 0 { // if not omitted
|
||||||
// string "i"
|
// string "i"
|
||||||
o = append(o, 0xa1, 0x69)
|
o = append(o, 0xa1, 0x69)
|
||||||
o = msgp.AppendBytes(o, z.Index)
|
o = msgp.AppendBytes(o, z.Index)
|
||||||
}
|
}
|
||||||
if (zb0001Mask & 0x40) == 0 { // if not omitted
|
if (zb0001Mask & 0x40) == 0 { // if not omitted
|
||||||
// string "crc"
|
// string "crc"
|
||||||
o = append(o, 0xa3, 0x63, 0x72, 0x63)
|
o = append(o, 0xa3, 0x63, 0x72, 0x63)
|
||||||
o = msgp.AppendMapHeader(o, uint32(len(z.Checksums)))
|
o = msgp.AppendMapHeader(o, uint32(len(z.Checksums)))
|
||||||
for za0001, za0002 := range z.Checksums {
|
for za0001, za0002 := range z.Checksums {
|
||||||
o = msgp.AppendString(o, za0001)
|
o = msgp.AppendString(o, za0001)
|
||||||
o = msgp.AppendString(o, za0002)
|
o = msgp.AppendString(o, za0002)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
||||||
|
// string "err"
|
||||||
|
o = append(o, 0xa3, 0x65, 0x72, 0x72)
|
||||||
|
o = msgp.AppendString(o, z.Error)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
|
||||||
// string "err"
|
|
||||||
o = append(o, 0xa3, 0x65, 0x72, 0x72)
|
|
||||||
o = msgp.AppendString(o, z.Error)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -850,6 +869,8 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var zb0001Mask uint8 /* 3 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
for zb0001 > 0 {
|
for zb0001 > 0 {
|
||||||
zb0001--
|
zb0001--
|
||||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
@@ -894,6 +915,7 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Index")
|
err = msgp.WrapError(err, "Index")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x1
|
||||||
case "crc":
|
case "crc":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
|
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
@@ -924,12 +946,14 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
z.Checksums[za0001] = za0002
|
z.Checksums[za0001] = za0002
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x2
|
||||||
case "err":
|
case "err":
|
||||||
z.Error, bts, err = msgp.ReadStringBytes(bts)
|
z.Error, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "Error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
zb0001Mask |= 0x4
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -938,6 +962,18 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Clear omitted fields.
|
||||||
|
if zb0001Mask != 0x7 {
|
||||||
|
if (zb0001Mask & 0x1) == 0 {
|
||||||
|
z.Index = nil
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x2) == 0 {
|
||||||
|
z.Checksums = nil
|
||||||
|
}
|
||||||
|
if (zb0001Mask & 0x4) == 0 {
|
||||||
|
z.Error = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
o = bts
|
o = bts
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ var (
|
|||||||
xlVersionCurrent [4]byte
|
xlVersionCurrent [4]byte
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//msgp:clearomitted
|
||||||
|
|
||||||
//go:generate msgp -file=$GOFILE -unexported
|
//go:generate msgp -file=$GOFILE -unexported
|
||||||
//go:generate stringer -type VersionType,ErasureAlgo -output=xl-storage-format-v2_string.go $GOFILE
|
//go:generate stringer -type VersionType,ErasureAlgo -output=xl-storage-format-v2_string.go $GOFILE
|
||||||
|
|
||||||
@@ -460,7 +462,7 @@ func (j *xlMetaV2Version) ToFileInfo(volume, path string, allParts bool) (fi Fil
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
xlHeaderVersion = 3
|
xlHeaderVersion = 3
|
||||||
xlMetaVersion = 2
|
xlMetaVersion = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func (j xlMetaV2DeleteMarker) ToFileInfo(volume, path string) (FileInfo, error) {
|
func (j xlMetaV2DeleteMarker) ToFileInfo(volume, path string) (FileInfo, error) {
|
||||||
@@ -967,6 +969,50 @@ func (x *xlMetaV2) loadIndexed(buf xlMetaBuf, data xlMetaInlineData) error {
|
|||||||
}
|
}
|
||||||
ver.meta = meta
|
ver.meta = meta
|
||||||
|
|
||||||
|
// Fix inconsistent compression index due to https://github.com/minio/minio/pull/20575
|
||||||
|
// First search marshaled content for encoded values.
|
||||||
|
// We have bumped metaV to make this check cheaper.
|
||||||
|
if metaV < 3 && ver.header.Type == ObjectType && bytes.Contains(meta, []byte("\xa7PartIdx")) &&
|
||||||
|
bytes.Contains(meta, []byte("\xbcX-Minio-Internal-compression\xc4\x15klauspost/compress/s2")) {
|
||||||
|
// Likely candidate...
|
||||||
|
version, err := x.getIdx(i)
|
||||||
|
if err == nil {
|
||||||
|
// Check write date...
|
||||||
|
// RELEASE.2023-12-02T10-51-33Z -> RELEASE.2024-10-29T16-01-48Z
|
||||||
|
const dateStart = 1701471618
|
||||||
|
const dateEnd = 1730156418
|
||||||
|
if version.WrittenByVersion > dateStart && version.WrittenByVersion < dateEnd &&
|
||||||
|
version.ObjectV2 != nil && len(version.ObjectV2.PartIndices) > 0 {
|
||||||
|
var changed bool
|
||||||
|
clearField := true
|
||||||
|
for i, sz := range version.ObjectV2.PartActualSizes {
|
||||||
|
if len(version.ObjectV2.PartIndices) > i {
|
||||||
|
// 8<<20 is current 'compMinIndexSize', but we detach it in case it should change in the future.
|
||||||
|
if sz <= 8<<20 && len(version.ObjectV2.PartIndices[i]) > 0 {
|
||||||
|
changed = true
|
||||||
|
version.ObjectV2.PartIndices[i] = nil
|
||||||
|
}
|
||||||
|
clearField = clearField && len(version.ObjectV2.PartIndices[i]) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
// All empty, clear.
|
||||||
|
if clearField {
|
||||||
|
version.ObjectV2.PartIndices = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reindex since it was changed.
|
||||||
|
meta, err := version.MarshalMsg(make([]byte, 0, len(ver.meta)+10))
|
||||||
|
if err == nil {
|
||||||
|
// Override both if fine.
|
||||||
|
ver.header = version.header()
|
||||||
|
ver.meta = meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fix inconsistent x-minio-internal-replication-timestamp by loading and reindexing.
|
// Fix inconsistent x-minio-internal-replication-timestamp by loading and reindexing.
|
||||||
if metaV < 2 && ver.header.Type == DeleteType {
|
if metaV < 2 && ver.header.Type == DeleteType {
|
||||||
// load (and convert) version.
|
// load (and convert) version.
|
||||||
@@ -1629,7 +1675,7 @@ func (x *xlMetaV2) AddVersion(fi FileInfo) error {
|
|||||||
}
|
}
|
||||||
ventry.ObjectV2.PartNumbers[i] = fi.Parts[i].Number
|
ventry.ObjectV2.PartNumbers[i] = fi.Parts[i].Number
|
||||||
ventry.ObjectV2.PartActualSizes[i] = fi.Parts[i].ActualSize
|
ventry.ObjectV2.PartActualSizes[i] = fi.Parts[i].ActualSize
|
||||||
if len(ventry.ObjectV2.PartIndices) > 0 {
|
if len(ventry.ObjectV2.PartIndices) > i {
|
||||||
ventry.ObjectV2.PartIndices[i] = fi.Parts[i].Index
|
ventry.ObjectV2.PartIndices[i] = fi.Parts[i].Index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+569
-487
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -583,6 +584,28 @@ func Test_xlMetaV2Shallow_Load(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// t.Logf("data := %#v\n", data)
|
// t.Logf("data := %#v\n", data)
|
||||||
})
|
})
|
||||||
|
// Test compressed index consistency fix
|
||||||
|
t.Run("comp-index", func(t *testing.T) {
|
||||||
|
// This file has a compressed index, due to https://github.com/minio/minio/pull/20575
|
||||||
|
// We ensure it is rewritten without an index.
|
||||||
|
// We compare this against the signature of the files stored without a version.
|
||||||
|
data, err := base64.StdEncoding.DecodeString(`WEwyIAEAAwDGAAACKgMCAcQml8QQAAAAAAAAAAAAAAAAAAAAANMYGu+UIK7akcQEofwXhAECCAjFAfyDpFR5cGUBpVYyT2Jq3gASoklExBAAAAAAAAAAAAAAAAAAAAAApEREaXLEEFTyKFqhkkXVoWn+8R1Lr2ymRWNBbGdvAaNFY00Io0VjTginRWNCU2l6ZdIAEAAAp0VjSW5kZXgBpkVjRGlzdNwAEAECAwQFBgcICQoLDA0ODxCoQ1N1bUFsZ28BqFBhcnROdW1zkgECqVBhcnRFVGFnc8CpUGFydFNpemVzktIAFtgq0gAGvb+qUGFydEFTaXplc5LSAFKb69IAGZg0p1BhcnRJZHiSxFqKm+4h9J7JCYCAgAFEABSPlBzH5g6z9gah3wOPnwLDlAGeD+os0xbjFd8O8w+TBoM8rz6bHO0KzQWtBu4GwgGSBocH6QPUSu8J5A/8gwSWtQPOtgL0euoMmAPEAKRTaXpl0gAdlemlTVRpbWXTGBrvlCCu2pGnTWV0YVN5c4K8WC1NaW5pby1JbnRlcm5hbC1hY3R1YWwtc2l6ZcQHNzA5MTIzMbxYLU1pbmlvLUludGVybmFsLWNvbXByZXNzaW9uxBVrbGF1c3Bvc3QvY29tcHJlc3MvczKnTWV0YVVzcoKsY29udGVudC10eXBlqHRleHQvY3N2pGV0YWfZIjEzYmYyMDU0NGVjN2VmY2YxNzhiYWRmNjc4NzNjODg2LTKhds5mYYMqzv8Vdtk=`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var xl xlMetaV2
|
||||||
|
err = xl.Load(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, v := range xl.versions {
|
||||||
|
// Signature should match
|
||||||
|
if binary.BigEndian.Uint32(v.header.Signature[:]) != 0x8e5a6406 {
|
||||||
|
t.Log(v.header.String())
|
||||||
|
t.Fatalf("invalid signature 0x%x", binary.BigEndian.Uint32(v.header.Signature[:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_xlMetaV2Shallow_LoadTimeStamp(t *testing.T) {
|
func Test_xlMetaV2Shallow_LoadTimeStamp(t *testing.T) {
|
||||||
|
|||||||
@@ -378,11 +378,11 @@ func (x *xlMetaInlineData) remove(keys ...string) bool {
|
|||||||
// xlMetaV2TrimData will trim any data from the metadata without unmarshalling it.
|
// xlMetaV2TrimData will trim any data from the metadata without unmarshalling it.
|
||||||
// If any error occurs the unmodified data is returned.
|
// If any error occurs the unmodified data is returned.
|
||||||
func xlMetaV2TrimData(buf []byte) []byte {
|
func xlMetaV2TrimData(buf []byte) []byte {
|
||||||
metaBuf, min, maj, err := checkXL2V1(buf)
|
metaBuf, maj, minor, err := checkXL2V1(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
if maj == 1 && min < 1 {
|
if maj == 1 && minor < 1 {
|
||||||
// First version to carry data.
|
// First version to carry data.
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
@@ -393,7 +393,7 @@ func xlMetaV2TrimData(buf []byte) []byte {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
// Skip CRC
|
// Skip CRC
|
||||||
if maj > 1 || min >= 2 {
|
if maj > 1 || minor >= 2 {
|
||||||
_, metaBuf, err = msgp.ReadUint32Bytes(metaBuf)
|
_, metaBuf, err = msgp.ReadUint32Bytes(metaBuf)
|
||||||
storageLogIf(GlobalContext, err)
|
storageLogIf(GlobalContext, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-47
@@ -432,15 +432,19 @@ func (s *xlStorage) Healing() *healingTracker {
|
|||||||
bucketMetaPrefix, healingTrackerFilename)
|
bucketMetaPrefix, healingTrackerFilename)
|
||||||
b, err := os.ReadFile(healingFile)
|
b, err := os.ReadFile(healingFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
internalLogIf(GlobalContext, fmt.Errorf("unable to read %s: %w", healingFile, err))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
|
internalLogIf(GlobalContext, fmt.Errorf("%s is empty", healingFile))
|
||||||
// 'healing.bin' might be truncated
|
// 'healing.bin' might be truncated
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
h := newHealingTracker()
|
h := newHealingTracker()
|
||||||
_, err = h.UnmarshalMsg(b)
|
_, err = h.UnmarshalMsg(b)
|
||||||
bugLogIf(GlobalContext, err)
|
internalLogIf(GlobalContext, err)
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2246,6 +2250,7 @@ func (s *xlStorage) writeAllMeta(ctx context.Context, volume string, path string
|
|||||||
return renameAll(tmpFilePath, filePath, volumeDir)
|
return renameAll(tmpFilePath, filePath, volumeDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create or truncate an existing file before writing
|
||||||
func (s *xlStorage) writeAllInternal(ctx context.Context, filePath string, b []byte, sync bool, skipParent string) (err error) {
|
func (s *xlStorage) writeAllInternal(ctx context.Context, filePath string, b []byte, sync bool, skipParent string) (err error) {
|
||||||
flags := os.O_CREATE | os.O_WRONLY | os.O_TRUNC
|
flags := os.O_CREATE | os.O_WRONLY | os.O_TRUNC
|
||||||
|
|
||||||
@@ -2268,16 +2273,11 @@ func (s *xlStorage) writeAllInternal(ctx context.Context, filePath string, b []b
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := w.Write(b)
|
_, err = w.Write(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n != len(b) {
|
|
||||||
w.Truncate(0) // to indicate that we did partial write.
|
w.Truncate(0) // to indicate that we did partial write.
|
||||||
w.Close()
|
w.Close()
|
||||||
return io.ErrShortWrite
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dealing with error returns from close() - 'man 2 close'
|
// Dealing with error returns from close() - 'man 2 close'
|
||||||
@@ -2367,6 +2367,41 @@ func (s *xlStorage) AppendFile(ctx context.Context, volume string, path string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkPart is a light check of an existing and size of a part, without doing a bitrot operation
|
||||||
|
// For any unexpected error, return checkPartUnknown (zero)
|
||||||
|
func (s *xlStorage) checkPart(volumeDir, path, dataDir string, partNum int, expectedSize int64, skipAccessCheck bool) (resp int) {
|
||||||
|
partPath := pathJoin(path, dataDir, fmt.Sprintf("part.%d", partNum))
|
||||||
|
filePath := pathJoin(volumeDir, partPath)
|
||||||
|
st, err := Lstat(filePath)
|
||||||
|
if err != nil {
|
||||||
|
if osIsNotExist(err) {
|
||||||
|
if !skipAccessCheck {
|
||||||
|
// Stat a volume entry.
|
||||||
|
if verr := Access(volumeDir); verr != nil {
|
||||||
|
if osIsNotExist(verr) {
|
||||||
|
resp = checkPartVolumeNotFound
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if osErrToFileErr(err) == errFileNotFound {
|
||||||
|
resp = checkPartFileNotFound
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if st.Mode().IsDir() {
|
||||||
|
resp = checkPartFileNotFound
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check if shard is truncated.
|
||||||
|
if st.Size() < expectedSize {
|
||||||
|
resp = checkPartFileCorrupt
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return checkPartSuccess
|
||||||
|
}
|
||||||
|
|
||||||
// CheckParts check if path has necessary parts available.
|
// CheckParts check if path has necessary parts available.
|
||||||
func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) {
|
func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) {
|
||||||
volumeDir, err := s.getVolDir(volume)
|
volumeDir, err := s.getVolDir(volume)
|
||||||
@@ -2385,36 +2420,12 @@ func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, part := range fi.Parts {
|
for i, part := range fi.Parts {
|
||||||
partPath := pathJoin(path, fi.DataDir, fmt.Sprintf("part.%d", part.Number))
|
resp.Results[i], err = xioutil.WithDeadline[int](ctx, globalDriveConfig.GetMaxTimeout(), func(ctx context.Context) (int, error) {
|
||||||
filePath := pathJoin(volumeDir, partPath)
|
return s.checkPart(volumeDir, path, fi.DataDir, part.Number, fi.Erasure.ShardFileSize(part.Size), skipAccessChecks(volume)), nil
|
||||||
st, err := Lstat(filePath)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if osIsNotExist(err) {
|
return nil, err
|
||||||
if !skipAccessChecks(volume) {
|
|
||||||
// Stat a volume entry.
|
|
||||||
if verr := Access(volumeDir); verr != nil {
|
|
||||||
if osIsNotExist(verr) {
|
|
||||||
resp.Results[i] = checkPartVolumeNotFound
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if osErrToFileErr(err) == errFileNotFound {
|
|
||||||
resp.Results[i] = checkPartFileNotFound
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if st.Mode().IsDir() {
|
|
||||||
resp.Results[i] = checkPartFileNotFound
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Check if shard is truncated.
|
|
||||||
if st.Size() < fi.Erasure.ShardFileSize(part.Size) {
|
|
||||||
resp.Results[i] = checkPartFileCorrupt
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resp.Results[i] = checkPartSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
@@ -2546,17 +2557,7 @@ func (s *xlStorage) Delete(ctx context.Context, volume string, path string, dele
|
|||||||
}
|
}
|
||||||
|
|
||||||
func skipAccessChecks(volume string) (ok bool) {
|
func skipAccessChecks(volume string) (ok bool) {
|
||||||
for _, prefix := range []string{
|
return strings.HasPrefix(volume, minioMetaBucket)
|
||||||
minioMetaTmpDeletedBucket,
|
|
||||||
minioMetaTmpBucket,
|
|
||||||
minioMetaMultipartBucket,
|
|
||||||
minioMetaBucket,
|
|
||||||
} {
|
|
||||||
if strings.HasPrefix(volume, prefix) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameData - rename source path to destination path atomically, metadata and data directory.
|
// RenameData - rename source path to destination path atomically, metadata and data directory.
|
||||||
|
|||||||
+2
-2
@@ -18,7 +18,7 @@ docker buildx build --push --no-cache \
|
|||||||
-t "quay.io/minio/minio:latest" \
|
-t "quay.io/minio/minio:latest" \
|
||||||
-t "minio/minio:${release}" \
|
-t "minio/minio:${release}" \
|
||||||
-t "quay.io/minio/minio:${release}" \
|
-t "quay.io/minio/minio:${release}" \
|
||||||
--platform=linux/arm64,linux/amd64,linux/ppc64le,linux/s390x \
|
--platform=linux/arm64,linux/amd64,linux/ppc64le \
|
||||||
-f Dockerfile.release .
|
-f Dockerfile.release .
|
||||||
|
|
||||||
docker buildx prune -f
|
docker buildx prune -f
|
||||||
@@ -27,7 +27,7 @@ docker buildx build --push --no-cache \
|
|||||||
--build-arg RELEASE="${release}" \
|
--build-arg RELEASE="${release}" \
|
||||||
-t "minio/minio:${release}-cpuv1" \
|
-t "minio/minio:${release}-cpuv1" \
|
||||||
-t "quay.io/minio/minio:${release}-cpuv1" \
|
-t "quay.io/minio/minio:${release}-cpuv1" \
|
||||||
--platform=linux/arm64,linux/amd64,linux/ppc64le,linux/s390x \
|
--platform=linux/arm64,linux/amd64,linux/ppc64le \
|
||||||
-f Dockerfile.release.old_cpu .
|
-f Dockerfile.release.old_cpu .
|
||||||
|
|
||||||
docker buildx prune -f
|
docker buildx prune -f
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user