mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 10:43:15 +03:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59d3639396 | |||
| 027e17468a | |||
| 45ea161f8d | |||
| 7b8a456f68 | |||
| b43906f6ee | |||
| 6a66f142d4 | |||
| 5982965839 | |||
| bfb92a27b7 | |||
| 8565cefe4e | |||
| 8cdf2106b0 | |||
| 35fafb837b | |||
| 274bbad5cb | |||
| 5c451d1690 | |||
| c987313431 | |||
| 2ecaab55a6 | |||
| 3e792ae2a2 | |||
| 6df6ac0f34 | |||
| 4cc500a041 | |||
| d8e28830cf | |||
| 3e16ec457a | |||
| e5d378931d | |||
| 6128304f6e | |||
| e63a10e505 | |||
| 5434088c51 | |||
| a773cf48d8 | |||
| 386dd56856 | |||
| f714840da7 | |||
| 7c9ef76f66 | |||
| cffdb01279 | |||
| 970ddb424b | |||
| b390a2a0b9 | |||
| cce5d7152a | |||
| 90158f1e33 | |||
| 26624552be | |||
| c606c76323 | |||
| d674263eb7 | |||
| e7d3b49a20 | |||
| 5df61ab96b | |||
| 3456b03b12 | |||
| f6fb27e8f0 | |||
| 8368ab76aa | |||
| 3e83643320 | |||
| 2eb52ca5f4 | |||
| 705e196b6c | |||
| 7b5223d83d | |||
| f164085227 | |||
| 31bf6f0c25 | |||
| 48191dd748 | |||
| c4f29d24da | |||
| db7890660e | |||
| 9adc33efbb | |||
| 8f65aba04b | |||
| 3a0082f0f1 | |||
| 14792cdbc6 | |||
| 4939987eb8 | |||
| 4bca62a0bd | |||
| 82e2be4239 | |||
| 4550ac6fff | |||
| 97856bfebf | |||
| a60a0e52bb | |||
| 83a67a1d21 | |||
| 12391ec4ba | |||
| e65ed2e44f | |||
| d90044b847 | |||
| d8c1f93de6 | |||
| 54d243cd98 | |||
| d74e4642e3 | |||
| a51488cbaa | |||
| 04848dfa1c | |||
| 78d18d8fc8 | |||
| dc819afa44 | |||
| 4a564336fe | |||
| 6b7ced80fe | |||
| f60bbdf86b | |||
| 8c79f87f02 | |||
| f3beb1236a | |||
| 934bed47fa | |||
| 038bcd9079 | |||
| 6d70f6a4ac | |||
| ce93b2681b | |||
| 8d036ed6d8 | |||
| 9c53cc1b83 | |||
| 3514e89eb3 | |||
| 6ff12f5f01 | |||
| ee2a436a5b | |||
| a896125490 | |||
| e083471ec4 | |||
| de9b64834e | |||
| 919441d9c4 | |||
| 80d31113e5 | |||
| 7e2b79984e | |||
| d54cf77356 | |||
| 951b6b203b | |||
| 44e23b7f4f | |||
| c22a387695 | |||
| 1ab4d6a6aa |
@@ -26,7 +26,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
MINIO_CI_CD: 1
|
|
||||||
run: |
|
run: |
|
||||||
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
||||||
go test -v --timeout 50m ./...
|
go test -v --timeout 50m ./...
|
||||||
@@ -35,7 +34,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
MINIO_CI_CD: 1
|
|
||||||
run: |
|
run: |
|
||||||
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
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ issues:
|
|||||||
run:
|
run:
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
- pkg/rpc
|
- pkg/rpc
|
||||||
|
- pkg/argon2
|
||||||
|
|
||||||
service:
|
service:
|
||||||
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
|
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
|
||||||
|
|||||||
+6
-3
@@ -11,7 +11,7 @@ RUN \
|
|||||||
git clone https://github.com/minio/minio && cd minio && \
|
git clone https://github.com/minio/minio && cd minio && \
|
||||||
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
||||||
|
|
||||||
FROM alpine:3.12
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
@@ -22,11 +22,14 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
COPY --from=builder /go/bin/minio /usr/bin/minio
|
COPY --from=builder /go/bin/minio /usr/bin/minio
|
||||||
COPY --from=builder /go/minio/CREDITS /third_party/
|
COPY --from=builder /go/minio/CREDITS /licenses/CREDITS
|
||||||
|
COPY --from=builder /go/minio/LICENSE /licenses/LICENSE
|
||||||
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
microdnf update --nodocs && \
|
||||||
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
|
microdnf clean all && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
|
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|||||||
+8
-4
@@ -11,7 +11,9 @@ RUN \
|
|||||||
git clone https://github.com/minio/minio && cd minio && \
|
git clone https://github.com/minio/minio && cd minio && \
|
||||||
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
||||||
|
|
||||||
FROM alpine:3.12
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
@@ -22,12 +24,14 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
COPY --from=builder /go/bin/minio /usr/bin/minio
|
COPY --from=builder /go/bin/minio /usr/bin/minio
|
||||||
COPY --from=builder /go/minio/CREDITS /third_party/
|
COPY --from=builder /go/minio/CREDITS /licenses/CREDITS
|
||||||
|
COPY --from=builder /go/minio/LICENSE /licenses/LICENSE
|
||||||
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
microdnf update --nodocs && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
|
microdnf clean all
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
|||||||
+10
-6
@@ -1,20 +1,24 @@
|
|||||||
FROM alpine:3.12
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||||
COPY minio /usr/bin/
|
COPY minio /usr/bin/
|
||||||
COPY CREDITS /third_party/
|
COPY CREDITS /licenses/CREDITS
|
||||||
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|
||||||
ENV MINIO_UPDATE off
|
ENV MINIO_UPDATE=off \
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
microdnf update --nodocs && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
|
microdnf clean all && \
|
||||||
chmod +x /usr/bin/minio && \
|
chmod +x /usr/bin/minio && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh
|
chmod +x /usr/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||||
|
|
||||||
|
|||||||
+13
-6
@@ -1,8 +1,14 @@
|
|||||||
FROM alpine:3.12
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
LABEL name="MinIO" \
|
||||||
|
vendor="MinIO Inc <dev@min.io>" \
|
||||||
|
maintainer="MinIO Inc <dev@min.io>" \
|
||||||
|
version="RELEASE.2020-11-25T22-36-25Z" \
|
||||||
|
release="RELEASE.2020-11-25T22-36-25Z" \
|
||||||
|
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
||||||
|
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
@@ -16,16 +22,17 @@ COPY CREDITS /licenses/CREDITS
|
|||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
microdnf update --nodocs && \
|
||||||
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \
|
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
||||||
|
microdnf install minisign --nodocs && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio -o /usr/bin/minio && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio -o /usr/bin/minio && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.minisig -o /usr/bin/minio.minisig && \
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.minisig -o /usr/bin/minio.minisig && \
|
||||||
|
microdnf clean all && \
|
||||||
chmod +x /usr/bin/minio && \
|
chmod +x /usr/bin/minio && \
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
chmod +x /usr/bin/verify-minio.sh && \
|
||||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
|
||||||
/usr/bin/verify-minio.sh
|
/usr/bin/verify-minio.sh
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
|
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
LABEL name="MinIO" \
|
|
||||||
vendor="MinIO Inc <dev@min.io>" \
|
|
||||||
maintainer="MinIO Inc <dev@min.io>" \
|
|
||||||
version="RELEASE.2020-11-25T22-36-25Z" \
|
|
||||||
release="RELEASE.2020-11-25T22-36-25Z" \
|
|
||||||
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
|
||||||
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
|
||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
|
||||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
|
||||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
|
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.ubi.sh /usr/bin/docker-entrypoint.ubi.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
|
||||||
COPY LICENSE /licenses/LICENSE
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
microdnf install curl ca-certificates shadow-utils --nodocs && \
|
|
||||||
curl -s -q https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm -o epel-release.rpm && \
|
|
||||||
rpm -ivh epel-release.rpm && microdnf install minisign --nodocs && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio -o /usr/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.sha256sum -o /usr/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.minisig -o /usr/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.ubi.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh && \
|
|
||||||
groupadd --gid 1000 minio && \
|
|
||||||
useradd -M --uid 1000 --gid 1000 --home /usr/share/minio minio && \
|
|
||||||
mkdir -p /data && chown -R minio:minio /usr/bin /data
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
USER minio
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.ubi.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -57,7 +57,7 @@ test-race: verifiers build
|
|||||||
# Verify minio binary
|
# Verify minio binary
|
||||||
verify:
|
verify:
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
@GO111MODULE=on CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GO111MODULE=on CGO_ENABLED=1 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||||
|
|
||||||
# Verify healing of disks with minio binary
|
# Verify healing of disks with minio binary
|
||||||
@@ -71,6 +71,10 @@ build: checks
|
|||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|
||||||
|
hotfix: LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
|
||||||
|
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#'))
|
||||||
|
hotfix: install
|
||||||
|
|
||||||
docker: checks
|
docker: checks
|
||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ service iptables restart
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Test using MinIO Browser
|
## Test using MinIO Browser
|
||||||
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully.
|
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 to ensure your server has started successfully.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
## Vulnerability Management Policy
|
||||||
|
|
||||||
|
This document formally describes the process of addressing and managing a
|
||||||
|
reported vulnerability that has been found in the MinIO server code base,
|
||||||
|
any directly connected ecosystem component or a direct / indirect dependency
|
||||||
|
of the code base.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
The vulnerability management policy described in this document covers the
|
||||||
|
process of investigating, assessing and resolving a vulnerability report
|
||||||
|
opened by a MinIO employee or an external third party.
|
||||||
|
|
||||||
|
Therefore, it lists pre-conditions and actions that should be performed to
|
||||||
|
resolve and fix a reported vulnerability.
|
||||||
|
|
||||||
|
### Vulnerability Management Process
|
||||||
|
|
||||||
|
The vulnerability management process requires that the vulnerability report
|
||||||
|
contains the following information:
|
||||||
|
|
||||||
|
- The project / component that contains the reported vulnerability.
|
||||||
|
- A description of the vulnerability. In particular, the type of the
|
||||||
|
reported vulnerability and how it might be exploited. Alternatively,
|
||||||
|
a well-established vulnerability identifier, e.g. CVE number, can be
|
||||||
|
used instead.
|
||||||
|
|
||||||
|
Based on the description mentioned above, a MinIO engineer or security team
|
||||||
|
member investigates:
|
||||||
|
|
||||||
|
- Whether the reported vulnerability exists.
|
||||||
|
- The conditions that are required such that the vulnerability can be exploited.
|
||||||
|
- The steps required to fix the vulnerability.
|
||||||
|
|
||||||
|
In general, if the vulnerability exists in one of the MinIO code bases
|
||||||
|
itself - not in a code dependency - then MinIO will, if possible, fix
|
||||||
|
the vulnerability or implement reasonable countermeasures such that the
|
||||||
|
vulnerability cannot be exploited anymore.
|
||||||
|
|
||||||
Generated
+231
-315
@@ -2271,7 +2271,9 @@
|
|||||||
"asap": {
|
"asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
|
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
@@ -2283,19 +2285,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"asn1.js": {
|
"asn1.js": {
|
||||||
"version": "4.10.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||||
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.0.0",
|
"bn.js": "^4.0.0",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"minimalistic-assert": "^1.0.0"
|
"minimalistic-assert": "^1.0.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3541,9 +3544,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"base64-js": {
|
"base64-js": {
|
||||||
"version": "1.3.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||||
},
|
},
|
||||||
"batch": {
|
"batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
@@ -3574,13 +3577,12 @@
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
||||||
"integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA=="
|
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
|
||||||
},
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
"version": "1.19.0",
|
"version": "1.19.0",
|
||||||
@@ -3754,31 +3756,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browserify-rsa": {
|
"browserify-rsa": {
|
||||||
"version": "4.0.1",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
|
||||||
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
|
"integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.1.0",
|
"bn.js": "^5.0.0",
|
||||||
"randombytes": "^2.0.1"
|
"randombytes": "^2.0.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"bn.js": {
|
|
||||||
"version": "4.11.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browserify-sign": {
|
"browserify-sign": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
|
||||||
"integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==",
|
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^5.1.1",
|
"bn.js": "^5.1.1",
|
||||||
"browserify-rsa": "^4.0.1",
|
"browserify-rsa": "^4.0.1",
|
||||||
"create-hash": "^1.2.0",
|
"create-hash": "^1.2.0",
|
||||||
"create-hmac": "^1.1.7",
|
"create-hmac": "^1.1.7",
|
||||||
"elliptic": "^6.5.2",
|
"elliptic": "^6.5.3",
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.4",
|
||||||
"parse-asn1": "^5.1.5",
|
"parse-asn1": "^5.1.5",
|
||||||
"readable-stream": "^3.6.0",
|
"readable-stream": "^3.6.0",
|
||||||
@@ -4022,9 +4017,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
|
||||||
"integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
|
"integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "~3.1.1",
|
"anymatch": "~3.1.1",
|
||||||
@@ -4034,7 +4029,7 @@
|
|||||||
"is-binary-path": "~2.1.0",
|
"is-binary-path": "~2.1.0",
|
||||||
"is-glob": "~4.0.1",
|
"is-glob": "~4.0.1",
|
||||||
"normalize-path": "~3.0.0",
|
"normalize-path": "~3.0.0",
|
||||||
"readdirp": "~3.4.0"
|
"readdirp": "~3.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
@@ -4048,9 +4043,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"binary-extensions": {
|
"binary-extensions": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
|
||||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"braces": {
|
"braces": {
|
||||||
@@ -4123,9 +4118,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"readdirp": {
|
"readdirp": {
|
||||||
"version": "3.4.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||||
"integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
|
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
@@ -4143,9 +4138,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chownr": {
|
"chownr": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||||
},
|
},
|
||||||
"chrome-trace-event": {
|
"chrome-trace-event": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -4713,18 +4708,18 @@
|
|||||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
},
|
},
|
||||||
"create-ecdh": {
|
"create-ecdh": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
|
||||||
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
|
"integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.1.0",
|
"bn.js": "^4.1.0",
|
||||||
"elliptic": "^6.0.0"
|
"elliptic": "^6.5.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4925,9 +4920,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cyclist": {
|
"cyclist": {
|
||||||
"version": "0.2.2",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
|
||||||
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
|
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
|
||||||
},
|
},
|
||||||
"dashdash": {
|
"dashdash": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
@@ -5242,9 +5237,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5420,9 +5415,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"duplexify": {
|
"duplexify": {
|
||||||
"version": "3.6.1",
|
"version": "3.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
||||||
"integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==",
|
"integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "^1.0.0",
|
"end-of-stream": "^1.0.0",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
@@ -5471,9 +5466,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.2",
|
"version": "6.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||||
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
|
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.4.0",
|
||||||
"brorand": "^1.0.1",
|
"brorand": "^1.0.1",
|
||||||
@@ -5485,9 +5480,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5509,14 +5504,6 @@
|
|||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"encoding": {
|
|
||||||
"version": "0.1.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
|
||||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
|
||||||
"requires": {
|
|
||||||
"iconv-lite": "~0.4.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||||
@@ -5526,9 +5513,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enhanced-resolve": {
|
"enhanced-resolve": {
|
||||||
"version": "4.1.1",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
|
||||||
"integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==",
|
"integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"memory-fs": "^0.5.0",
|
"memory-fs": "^0.5.0",
|
||||||
@@ -5886,9 +5873,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
@@ -6162,11 +6149,18 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"esrecurse": {
|
"esrecurse": {
|
||||||
"version": "4.2.1",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||||
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
|
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"estraverse": "^4.1.0"
|
"estraverse": "^5.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"estraverse": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"estraverse": {
|
"estraverse": {
|
||||||
@@ -6193,9 +6187,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
|
||||||
"integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg=="
|
"integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg=="
|
||||||
},
|
},
|
||||||
"eventsource": {
|
"eventsource": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
@@ -6722,27 +6716,6 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fbjs": {
|
|
||||||
"version": "0.8.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
|
|
||||||
"integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
|
|
||||||
"requires": {
|
|
||||||
"core-js": "^1.0.0",
|
|
||||||
"isomorphic-fetch": "^2.1.1",
|
|
||||||
"loose-envify": "^1.0.0",
|
|
||||||
"object-assign": "^4.1.0",
|
|
||||||
"promise": "^7.1.1",
|
|
||||||
"setimmediate": "^1.0.5",
|
|
||||||
"ua-parser-js": "^0.7.9"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"core-js": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
|
||||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"figgy-pudding": {
|
"figgy-pudding": {
|
||||||
"version": "3.5.2",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
|
||||||
@@ -7013,12 +6986,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flush-write-stream": {
|
"flush-write-stream": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
|
||||||
"integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
|
"integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.3",
|
||||||
"readable-stream": "^2.0.4"
|
"readable-stream": "^2.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
@@ -8376,9 +8349,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"tapable": {
|
"tapable": {
|
||||||
@@ -8651,14 +8624,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
|
||||||
"integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ="
|
"integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ="
|
||||||
},
|
},
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.4.23",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
|
||||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"icss-utils": {
|
"icss-utils": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
|
||||||
@@ -8677,9 +8642,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ieee754": {
|
"ieee754": {
|
||||||
"version": "1.1.13",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||||
},
|
},
|
||||||
"iferr": {
|
"iferr": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
@@ -8746,9 +8711,9 @@
|
|||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"internal-ip": {
|
"internal-ip": {
|
||||||
@@ -9013,7 +8978,8 @@
|
|||||||
"is-stream": {
|
"is-stream": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-subset": {
|
"is-subset": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
@@ -9068,15 +9034,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||||
},
|
},
|
||||||
"isomorphic-fetch": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
|
||||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
|
||||||
"requires": {
|
|
||||||
"node-fetch": "^1.0.1",
|
|
||||||
"whatwg-fetch": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
@@ -10573,9 +10530,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.14",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash._baseisequal": {
|
"lodash._baseisequal": {
|
||||||
@@ -10682,6 +10639,14 @@
|
|||||||
"tslib": "^1.10.0"
|
"tslib": "^1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lru-cache": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||||
|
"requires": {
|
||||||
|
"yallist": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
|
||||||
@@ -10868,9 +10833,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -10993,6 +10958,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mississippi": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
||||||
|
"requires": {
|
||||||
|
"concat-stream": "^1.5.0",
|
||||||
|
"duplexify": "^3.4.2",
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"flush-write-stream": "^1.0.0",
|
||||||
|
"from2": "^2.1.0",
|
||||||
|
"parallel-transform": "^1.1.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"pumpify": "^1.3.3",
|
||||||
|
"stream-each": "^1.1.0",
|
||||||
|
"through2": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mixin-deep": {
|
"mixin-deep": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||||
@@ -11165,19 +11147,10 @@
|
|||||||
"tslib": "^1.10.0"
|
"tslib": "^1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
|
||||||
"version": "1.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz",
|
|
||||||
"integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=",
|
|
||||||
"requires": {
|
|
||||||
"encoding": "^0.1.11",
|
|
||||||
"is-stream": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "0.9.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||||
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
|
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node-int64": {
|
"node-int64": {
|
||||||
@@ -11768,11 +11741,11 @@
|
|||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||||
},
|
},
|
||||||
"parallel-transform": {
|
"parallel-transform": {
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
|
||||||
"integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
|
"integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cyclist": "~0.2.2",
|
"cyclist": "^1.0.1",
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"readable-stream": "^2.1.5"
|
"readable-stream": "^2.1.5"
|
||||||
}
|
}
|
||||||
@@ -11788,13 +11761,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parse-asn1": {
|
"parse-asn1": {
|
||||||
"version": "5.1.5",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
|
||||||
"integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
|
"integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"asn1.js": "^4.0.0",
|
"asn1.js": "^5.2.0",
|
||||||
"browserify-aes": "^1.0.0",
|
"browserify-aes": "^1.0.0",
|
||||||
"create-hash": "^1.1.0",
|
|
||||||
"evp_bytestokey": "^1.0.0",
|
"evp_bytestokey": "^1.0.0",
|
||||||
"pbkdf2": "^3.0.3",
|
"pbkdf2": "^3.0.3",
|
||||||
"safe-buffer": "^5.1.1"
|
"safe-buffer": "^5.1.1"
|
||||||
@@ -11913,9 +11885,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2": {
|
"pbkdf2": {
|
||||||
"version": "3.0.17",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
|
||||||
"integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
|
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"create-hash": "^1.1.2",
|
"create-hash": "^1.1.2",
|
||||||
"create-hmac": "^1.1.4",
|
"create-hmac": "^1.1.4",
|
||||||
@@ -12218,6 +12190,8 @@
|
|||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asap": "~2.0.3"
|
"asap": "~2.0.3"
|
||||||
}
|
}
|
||||||
@@ -12238,13 +12212,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.6.0",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
|
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fbjs": "^0.8.16",
|
"loose-envify": "^1.4.0",
|
||||||
"loose-envify": "^1.3.1",
|
"object-assign": "^4.1.1",
|
||||||
"object-assign": "^4.1.1"
|
"react-is": "^16.8.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"requires": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types-exact": {
|
"prop-types-exact": {
|
||||||
@@ -12330,16 +12314,16 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
"version": "2.0.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "^1.1.0",
|
"end-of-stream": "^1.1.0",
|
||||||
"once": "^1.3.1"
|
"once": "^1.3.1"
|
||||||
@@ -12353,6 +12337,17 @@
|
|||||||
"duplexify": "^3.6.0",
|
"duplexify": "^3.6.0",
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"pump": "^2.0.0"
|
"pump": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pump": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||||
|
"requires": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
@@ -13492,9 +13487,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13958,12 +13953,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"selfsigned": {
|
"selfsigned": {
|
||||||
"version": "1.10.7",
|
"version": "1.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
|
||||||
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
|
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-forge": "0.9.0"
|
"node-forge": "^0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
@@ -14001,10 +13996,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serialize-javascript": {
|
"serialize-javascript": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
|
||||||
"integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==",
|
"integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"randombytes": "^2.1.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"serializerr": {
|
"serializerr": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@@ -14644,9 +14642,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stream-shift": {
|
"stream-shift": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
|
||||||
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
|
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
|
||||||
},
|
},
|
||||||
"strict-uri-encode": {
|
"strict-uri-encode": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -15356,26 +15354,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"terser-webpack-plugin": {
|
"terser-webpack-plugin": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
|
||||||
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
|
"integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cacache": "^12.0.2",
|
"cacache": "^12.0.2",
|
||||||
"find-cache-dir": "^2.1.0",
|
"find-cache-dir": "^2.1.0",
|
||||||
"is-wsl": "^1.1.0",
|
"is-wsl": "^1.1.0",
|
||||||
"schema-utils": "^1.0.0",
|
"schema-utils": "^1.0.0",
|
||||||
"serialize-javascript": "^2.1.2",
|
"serialize-javascript": "^4.0.0",
|
||||||
"source-map": "^0.6.1",
|
"source-map": "^0.6.1",
|
||||||
"terser": "^4.1.2",
|
"terser": "^4.1.2",
|
||||||
"webpack-sources": "^1.4.0",
|
"webpack-sources": "^1.4.0",
|
||||||
"worker-farm": "^1.7.0"
|
"worker-farm": "^1.7.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": {
|
|
||||||
"version": "3.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
|
||||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
|
||||||
},
|
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "12.0.4",
|
"version": "12.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
|
||||||
@@ -15443,14 +15436,6 @@
|
|||||||
"path-exists": "^3.0.0"
|
"path-exists": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lru-cache": {
|
|
||||||
"version": "5.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
|
||||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
|
||||||
"requires": {
|
|
||||||
"yallist": "^3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
@@ -15460,23 +15445,6 @@
|
|||||||
"semver": "^5.6.0"
|
"semver": "^5.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mississippi": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
|
||||||
"requires": {
|
|
||||||
"concat-stream": "^1.5.0",
|
|
||||||
"duplexify": "^3.4.2",
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"flush-write-stream": "^1.0.0",
|
|
||||||
"from2": "^2.1.0",
|
|
||||||
"parallel-transform": "^1.1.0",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"pumpify": "^1.3.3",
|
|
||||||
"stream-each": "^1.1.0",
|
|
||||||
"through2": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-limit": {
|
"p-limit": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
@@ -15511,24 +15479,13 @@
|
|||||||
"find-up": "^3.0.0"
|
"find-up": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pump": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
|
||||||
"requires": {
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serialize-javascript": {
|
"serialize-javascript": {
|
||||||
"version": "2.1.2",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
|
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||||
},
|
"requires": {
|
||||||
"source-list-map": {
|
"randombytes": "^2.1.0"
|
||||||
"version": "2.0.1",
|
}
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
|
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
@@ -15542,15 +15499,6 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"webpack-sources": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
|
||||||
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
|
||||||
"requires": {
|
|
||||||
"source-list-map": "^2.0.0",
|
|
||||||
"source-map": "~0.6.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -15589,9 +15537,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"timers-browserify": {
|
"timers-browserify": {
|
||||||
"version": "2.0.11",
|
"version": "2.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
|
||||||
"integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
|
"integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"setimmediate": "^1.0.4"
|
"setimmediate": "^1.0.4"
|
||||||
}
|
}
|
||||||
@@ -15790,11 +15738,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
|
||||||
"version": "0.7.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz",
|
|
||||||
"integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw=="
|
|
||||||
},
|
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.9.3",
|
"version": "3.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz",
|
||||||
@@ -16172,20 +16115,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchpack": {
|
"watchpack": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.5",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
|
||||||
"integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==",
|
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": "^3.4.0",
|
"chokidar": "^3.4.1",
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"neo-async": "^2.5.0",
|
"neo-async": "^2.5.0",
|
||||||
"watchpack-chokidar2": "^2.0.0"
|
"watchpack-chokidar2": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watchpack-chokidar2": {
|
"watchpack-chokidar2": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz",
|
||||||
"integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==",
|
"integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": "^2.1.8"
|
"chokidar": "^2.1.8"
|
||||||
@@ -16447,9 +16390,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"version": "4.43.0",
|
"version": "4.44.2",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz",
|
||||||
"integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==",
|
"integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@webassemblyjs/ast": "1.9.0",
|
"@webassemblyjs/ast": "1.9.0",
|
||||||
"@webassemblyjs/helper-module-context": "1.9.0",
|
"@webassemblyjs/helper-module-context": "1.9.0",
|
||||||
@@ -16459,7 +16402,7 @@
|
|||||||
"ajv": "^6.10.2",
|
"ajv": "^6.10.2",
|
||||||
"ajv-keywords": "^3.4.1",
|
"ajv-keywords": "^3.4.1",
|
||||||
"chrome-trace-event": "^1.0.2",
|
"chrome-trace-event": "^1.0.2",
|
||||||
"enhanced-resolve": "^4.1.0",
|
"enhanced-resolve": "^4.3.0",
|
||||||
"eslint-scope": "^4.0.3",
|
"eslint-scope": "^4.0.3",
|
||||||
"json-parse-better-errors": "^1.0.2",
|
"json-parse-better-errors": "^1.0.2",
|
||||||
"loader-runner": "^2.4.0",
|
"loader-runner": "^2.4.0",
|
||||||
@@ -16472,14 +16415,14 @@
|
|||||||
"schema-utils": "^1.0.0",
|
"schema-utils": "^1.0.0",
|
||||||
"tapable": "^1.1.3",
|
"tapable": "^1.1.3",
|
||||||
"terser-webpack-plugin": "^1.4.3",
|
"terser-webpack-plugin": "^1.4.3",
|
||||||
"watchpack": "^1.6.1",
|
"watchpack": "^1.7.4",
|
||||||
"webpack-sources": "^1.4.1"
|
"webpack-sources": "^1.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.2",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -16578,9 +16521,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fast-deep-equal": {
|
"fast-deep-equal": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
"is-accessor-descriptor": {
|
"is-accessor-descriptor": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
@@ -16661,29 +16604,10 @@
|
|||||||
"to-regex": "^3.0.2"
|
"to-regex": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"source-list-map": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
|
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
|
||||||
},
|
|
||||||
"tapable": {
|
"tapable": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
|
||||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
|
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
|
||||||
},
|
|
||||||
"webpack-sources": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
|
||||||
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
|
||||||
"requires": {
|
|
||||||
"source-list-map": "^2.0.0",
|
|
||||||
"source-map": "~0.6.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -17605,7 +17529,6 @@
|
|||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
||||||
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"source-list-map": "^2.0.0",
|
"source-list-map": "^2.0.0",
|
||||||
"source-map": "~0.6.1"
|
"source-map": "~0.6.1"
|
||||||
@@ -17614,14 +17537,12 @@
|
|||||||
"source-list-map": {
|
"source-list-map": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||||
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
|
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -17660,11 +17581,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"whatwg-fetch": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
|
||||||
},
|
|
||||||
"whatwg-mimetype": {
|
"whatwg-mimetype": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
||||||
@@ -17780,9 +17696,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||||
},
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
|||||||
+32
-32
File diff suppressed because one or more lines are too long
@@ -44,10 +44,21 @@ func releaseTag(version string) string {
|
|||||||
relPrefix = prefix
|
relPrefix = prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relSuffix := ""
|
||||||
|
if hotfix := os.Getenv("MINIO_HOTFIX"); hotfix != "" {
|
||||||
|
relSuffix = hotfix
|
||||||
|
}
|
||||||
|
|
||||||
relTag := strings.Replace(version, " ", "-", -1)
|
relTag := strings.Replace(version, " ", "-", -1)
|
||||||
relTag = strings.Replace(relTag, ":", "-", -1)
|
relTag = strings.Replace(relTag, ":", "-", -1)
|
||||||
relTag = strings.Replace(relTag, ",", "", -1)
|
relTag = strings.Replace(relTag, ",", "", -1)
|
||||||
return relPrefix + "." + relTag
|
relTag = relPrefix + "." + relTag
|
||||||
|
|
||||||
|
if relSuffix != "" {
|
||||||
|
relTag += "." + relSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
return relTag
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitID returns the abbreviated commit-id hash of the last commit.
|
// commitID returns the abbreviated commit-id hash of the last commit.
|
||||||
@@ -68,5 +79,12 @@ func commitID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println(genLDFlags(time.Now().UTC().Format(time.RFC3339)))
|
var version string
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
version = os.Args[1]
|
||||||
|
} else {
|
||||||
|
version = time.Now().UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(genLDFlags(version))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,28 +56,29 @@ function start_minio_erasure()
|
|||||||
|
|
||||||
function start_minio_erasure_sets()
|
function start_minio_erasure_sets()
|
||||||
{
|
{
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk-sets{1...32}" >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||||
|
"${MINIO[@]}" server > "$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||||
sleep 15
|
sleep 15
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_zone_erasure_sets()
|
function start_minio_pool_erasure_sets()
|
||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_zone_erasure_sets_ipv6()
|
function start_minio_pool_erasure_sets_ipv6()
|
||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
|
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
"${MINIO[@]}" server --address="[::1]:9000" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-ipv6-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9000" > "$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-ipv6-9001.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
@@ -86,10 +87,10 @@ function start_minio_dist_erasure()
|
|||||||
{
|
{
|
||||||
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
export MINIO_ACCESS_KEY=$ACCESS_KEY
|
||||||
export MINIO_SECRET_KEY=$SECRET_KEY
|
export MINIO_SECRET_KEY=$SECRET_KEY
|
||||||
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9000.log" 2>&1 &
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
||||||
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9001.log" 2>&1 &
|
for i in $(seq 0 3); do
|
||||||
"${MINIO[@]}" server --address=:9002 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9002.log" 2>&1 &
|
"${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9003.log" 2>&1 &
|
done
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,8 @@ function run_test_fs()
|
|||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_erasure_sets() {
|
function run_test_erasure_sets()
|
||||||
|
{
|
||||||
start_minio_erasure_sets
|
start_minio_erasure_sets
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
@@ -129,9 +131,9 @@ function run_test_erasure_sets() {
|
|||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_zone_erasure_sets()
|
function run_test_pool_erasure_sets()
|
||||||
{
|
{
|
||||||
start_minio_zone_erasure_sets
|
start_minio_pool_erasure_sets
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
@@ -142,20 +144,20 @@ function run_test_zone_erasure_sets()
|
|||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/zone-minio-900$i.log"
|
cat "$WORK_DIR/pool-minio-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/zone-minio-900$i.log"
|
rm -f "$WORK_DIR/pool-minio-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_zone_erasure_sets_ipv6()
|
function run_test_pool_erasure_sets_ipv6()
|
||||||
{
|
{
|
||||||
start_minio_zone_erasure_sets_ipv6
|
start_minio_pool_erasure_sets_ipv6
|
||||||
|
|
||||||
export SERVER_ENDPOINT="[::1]:9000"
|
export SERVER_ENDPOINT="[::1]:9000"
|
||||||
|
|
||||||
@@ -168,12 +170,12 @@ function run_test_zone_erasure_sets_ipv6()
|
|||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/zone-minio-ipv6-900$i.log"
|
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/zone-minio-ipv6-900$i.log"
|
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
@@ -293,14 +295,14 @@ function main()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Eraure expanded setup"
|
echo "Testing in Distributed Eraure expanded setup"
|
||||||
if ! run_test_zone_erasure_sets; then
|
if ! run_test_pool_erasure_sets; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
||||||
if ! run_test_zone_erasure_sets_ipv6; then
|
if ! run_test_pool_erasure_sets_ipv6; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -29,32 +29,27 @@ MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
|||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
||||||
|
|
||||||
function start_minio_3_node() {
|
function start_minio_3_node() {
|
||||||
declare -a minio_pids
|
|
||||||
declare -a ARGS
|
|
||||||
export MINIO_ACCESS_KEY=minio
|
export MINIO_ACCESS_KEY=minio
|
||||||
export MINIO_SECRET_KEY=minio123
|
export MINIO_SECRET_KEY=minio123
|
||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
|
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
args=""
|
||||||
for i in $(seq 1 3); do
|
for i in $(seq 1 3); do
|
||||||
ARGS+=("http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/")
|
args="$args http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/"
|
||||||
done
|
done
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+1]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
"${MINIO[@]}" --address ":$[$start_port+1]" $args > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||||
minio_pids[0]=$!
|
disown $!
|
||||||
disown "${minio_pids[0]}"
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+2]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
"${MINIO[@]}" --address ":$[$start_port+2]" $args > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
||||||
minio_pids[1]=$!
|
disown $!
|
||||||
disown "${minio_pids[1]}"
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+3]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
"${MINIO[@]}" --address ":$[$start_port+3]" $args > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
||||||
minio_pids[2]=$!
|
disown $!
|
||||||
disown "${minio_pids[2]}"
|
|
||||||
|
|
||||||
sleep "$1"
|
sleep "$1"
|
||||||
for pid in "${minio_pids[@]}"; do
|
if [ "$(pgrep -c minio)" -ne 3 ]; then
|
||||||
if ! kill "$pid"; then
|
|
||||||
for i in $(seq 1 3); do
|
for i in $(seq 1 3); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
@@ -63,10 +58,23 @@ function start_minio_3_node() {
|
|||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# forcibly killing, to proceed further properly.
|
if ! pkill minio; then
|
||||||
kill -9 "$pid"
|
for i in $(seq 1 3); do
|
||||||
sleep 1 # wait 1sec per pid
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
done
|
done
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1;
|
||||||
|
if pgrep minio; then
|
||||||
|
# forcibly killing, to proceed further properly.
|
||||||
|
if ! pkill -9 minio; then
|
||||||
|
echo "no minio process running anymore, proceed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *htt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
cred, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.ConfigUpdateAdminAction, "")
|
cred, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ConfigUpdateAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return cred, nil
|
return cred, nil
|
||||||
@@ -130,8 +130,8 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
|
||||||
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -158,6 +158,17 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply dynamic values.
|
||||||
|
if err := applyDynamicConfig(GlobalContext, cfg); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
globalNotificationSys.SignalService(serviceReloadDynamic)
|
||||||
|
|
||||||
|
// If all values were dynamic, tell the client.
|
||||||
|
if dynamic {
|
||||||
|
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
||||||
|
}
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +277,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
if _, err = cfg.ReadConfig(bytes.NewReader(kvBytes)); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -378,7 +389,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := newServerConfig()
|
cfg := newServerConfig()
|
||||||
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil {
|
if _, err = cfg.ReadConfig(bytes.NewReader(kvBytes)); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -42,7 +43,7 @@ func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "")
|
cred, adminAPIErr = checkAdminRequestAuth(ctx, r, action, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return nil, cred
|
return nil, cred
|
||||||
@@ -358,7 +359,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
accessKey := vars["accessKey"]
|
accessKey := path.Clean(vars["accessKey"])
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
@@ -373,23 +374,29 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.IsTemp() || cred.IsServiceAccount() {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccountNotEligible), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not allowed to add a user with same access key as root credential
|
// Not allowed to add a user with same access key as root credential
|
||||||
if owner && accessKey == cred.AccessKey {
|
if owner && accessKey == cred.AccessKey {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
||||||
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, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
implicitPerm := accessKey == cred.AccessKey
|
implicitPerm := accessKey == cred.AccessKey
|
||||||
if !implicitPerm {
|
if !implicitPerm {
|
||||||
|
parentUser := cred.ParentUser
|
||||||
|
if parentUser == "" {
|
||||||
|
parentUser = cred.AccessKey
|
||||||
|
}
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: accessKey,
|
AccountName: parentUser,
|
||||||
Action: iampolicy.CreateUserAdminAction,
|
Action: iampolicy.CreateUserAdminAction,
|
||||||
ConditionValues: getConditionValues(r, "", accessKey, claims),
|
ConditionValues: getConditionValues(r, "", parentUser, claims),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims,
|
Claims: claims,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
+98
-61
@@ -41,6 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/cmd/logger/message/log"
|
"github.com/minio/minio/cmd/logger/message/log"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
"github.com/minio/minio/pkg/bandwidth"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
@@ -204,9 +205,10 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var objectAPI ObjectLayer
|
var objectAPI ObjectLayer
|
||||||
if serviceSig == serviceRestart {
|
switch serviceSig {
|
||||||
|
case serviceRestart:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction)
|
||||||
} else {
|
case serviceStop:
|
||||||
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction)
|
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction)
|
||||||
}
|
}
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
@@ -371,8 +373,7 @@ func topLockEntries(peerLocks []*PeerLocks, stale bool) madmin.LockEntries {
|
|||||||
if peerLock == nil {
|
if peerLock == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, locks := range peerLock.Locks {
|
for k, v := range peerLock.Locks {
|
||||||
for k, v := range locks {
|
|
||||||
for _, lockReqInfo := range v {
|
for _, lockReqInfo := range v {
|
||||||
if val, ok := entryMap[lockReqInfo.UID]; ok {
|
if val, ok := entryMap[lockReqInfo.UID]; ok {
|
||||||
val.ServerList = append(val.ServerList, peerLock.Addr)
|
val.ServerList = append(val.ServerList, peerLock.Addr)
|
||||||
@@ -382,7 +383,6 @@ func topLockEntries(peerLocks []*PeerLocks, stale bool) madmin.LockEntries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
var lockEntries madmin.LockEntries
|
var lockEntries madmin.LockEntries
|
||||||
for _, v := range entryMap {
|
for _, v := range entryMap {
|
||||||
if stale {
|
if stale {
|
||||||
@@ -400,7 +400,7 @@ func topLockEntries(peerLocks []*PeerLocks, stale bool) madmin.LockEntries {
|
|||||||
// PeerLocks holds server information result of one node
|
// PeerLocks holds server information result of one node
|
||||||
type PeerLocks struct {
|
type PeerLocks struct {
|
||||||
Addr string
|
Addr string
|
||||||
Locks GetLocksResp
|
Locks map[string][]lockRequesterInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// TopLocksHandler Get list of locks in use
|
// TopLocksHandler Get list of locks in use
|
||||||
@@ -461,8 +461,15 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "StartProfiling", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "StartProfiling", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ProfilingAdminAction)
|
// Validate request signature.
|
||||||
if objectAPI == nil {
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,8 +566,15 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "DownloadProfiling", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "DownloadProfiling", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ProfilingAdminAction)
|
// Validate request signature.
|
||||||
if objectAPI == nil {
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,7 +914,7 @@ func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "")
|
cred, adminAPIErr = checkAdminRequestAuth(ctx, r, action, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return nil, cred
|
return nil, cred
|
||||||
@@ -1032,7 +1046,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
trcErr := r.URL.Query().Get("err") == "true"
|
trcErr := r.URL.Query().Get("err") == "true"
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.TraceAdminAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.TraceAdminAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -1428,23 +1442,51 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "BandwidthMonitor")
|
ctx := newContext(r, w, "BandwidthMonitor")
|
||||||
|
|
||||||
|
defer logger.AuditLog(w, r, "BandwidthMonitor", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
// Validate request signature.
|
// Validate request signature.
|
||||||
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.BandwidthMonitorAction, "")
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.BandwidthMonitorAction, "")
|
||||||
if adminAPIErr != ErrNone {
|
if adminAPIErr != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setEventStreamHeaders(w)
|
setEventStreamHeaders(w)
|
||||||
|
reportCh := make(chan bandwidth.Report, 1)
|
||||||
|
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer keepAliveTicker.Stop()
|
||||||
bucketsRequestedString := r.URL.Query().Get("buckets")
|
bucketsRequestedString := r.URL.Query().Get("buckets")
|
||||||
bucketsRequested := strings.Split(bucketsRequestedString, ",")
|
bucketsRequested := strings.Split(bucketsRequestedString, ",")
|
||||||
consolidatedReport := globalNotificationSys.GetBandwidthReports(ctx, bucketsRequested...)
|
go func() {
|
||||||
|
for {
|
||||||
|
reportCh <- globalNotificationSys.GetBandwidthReports(ctx, bucketsRequested...)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case report := <-reportCh:
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
err := enc.Encode(consolidatedReport)
|
err := enc.Encode(report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), r.URL)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
|
case <-keepAliveTicker.C:
|
||||||
|
if _, err := w.Write([]byte(" ")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerInfoHandler - GET /minio/admin/v3/info
|
// ServerInfoHandler - GET /minio/admin/v3/info
|
||||||
@@ -1455,22 +1497,13 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
defer logger.AuditLog(w, r, "ServerInfo", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "ServerInfo", mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction)
|
// Validate request signature.
|
||||||
if objectAPI == nil {
|
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
|
||||||
|
if adminAPIErr != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets := madmin.Buckets{}
|
|
||||||
objects := madmin.Objects{}
|
|
||||||
usage := madmin.Usage{}
|
|
||||||
|
|
||||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
|
||||||
if err == nil {
|
|
||||||
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
|
||||||
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
|
||||||
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
|
||||||
}
|
|
||||||
|
|
||||||
vault := fetchVaultStatus()
|
vault := fetchVaultStatus()
|
||||||
|
|
||||||
ldap := madmin.LDAP{}
|
ldap := madmin.LDAP{}
|
||||||
@@ -1492,30 +1525,53 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
// Get the notification target info
|
// Get the notification target info
|
||||||
notifyTarget := fetchLambdaInfo()
|
notifyTarget := fetchLambdaInfo()
|
||||||
|
|
||||||
// Fetching the Storage information, ignore any errors.
|
server := getLocalServerProperty(globalEndpoints, r)
|
||||||
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
|
servers := globalNotificationSys.ServerInfo()
|
||||||
|
servers = append(servers, server)
|
||||||
|
|
||||||
var backend interface{}
|
var backend interface{}
|
||||||
if storageInfo.Backend.Type == BackendType(madmin.Erasure) {
|
mode := madmin.ObjectLayerInitializing
|
||||||
|
|
||||||
|
buckets := madmin.Buckets{}
|
||||||
|
objects := madmin.Objects{}
|
||||||
|
usage := madmin.Usage{}
|
||||||
|
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI != nil {
|
||||||
|
mode = madmin.ObjectLayerOnline
|
||||||
|
// Load data usage
|
||||||
|
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
||||||
|
if err == nil {
|
||||||
|
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
|
||||||
|
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
|
||||||
|
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetching the backend information
|
||||||
|
backendInfo := objectAPI.BackendInfo()
|
||||||
|
if backendInfo.Type == BackendType(madmin.Erasure) {
|
||||||
|
// Calculate the number of online/offline disks of all nodes
|
||||||
|
var allDisks []madmin.Disk
|
||||||
|
for _, s := range servers {
|
||||||
|
allDisks = append(allDisks, s.Disks...)
|
||||||
|
}
|
||||||
|
onlineDisks, offlineDisks := getOnlineOfflineDisksStats(allDisks)
|
||||||
|
|
||||||
backend = madmin.ErasureBackend{
|
backend = madmin.ErasureBackend{
|
||||||
Type: madmin.ErasureType,
|
Type: madmin.ErasureType,
|
||||||
OnlineDisks: storageInfo.Backend.OnlineDisks.Sum(),
|
OnlineDisks: onlineDisks.Sum(),
|
||||||
OfflineDisks: storageInfo.Backend.OfflineDisks.Sum(),
|
OfflineDisks: offlineDisks.Sum(),
|
||||||
StandardSCData: storageInfo.Backend.StandardSCData,
|
StandardSCData: backendInfo.StandardSCData,
|
||||||
StandardSCParity: storageInfo.Backend.StandardSCParity,
|
StandardSCParity: backendInfo.StandardSCParity,
|
||||||
RRSCData: storageInfo.Backend.RRSCData,
|
RRSCData: backendInfo.RRSCData,
|
||||||
RRSCParity: storageInfo.Backend.RRSCParity,
|
RRSCParity: backendInfo.RRSCParity,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
backend = madmin.FSBackend{
|
backend = madmin.FSBackend{
|
||||||
Type: madmin.FsType,
|
Type: madmin.FsType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mode := "online"
|
|
||||||
server := getLocalServerProperty(globalEndpoints, r)
|
|
||||||
servers := globalNotificationSys.ServerInfo()
|
|
||||||
servers = append(servers, server)
|
|
||||||
|
|
||||||
domain := globalDomainNames
|
domain := globalDomainNames
|
||||||
services := madmin.Services{
|
services := madmin.Services{
|
||||||
@@ -1526,25 +1582,6 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
Notifications: notifyTarget,
|
Notifications: notifyTarget,
|
||||||
}
|
}
|
||||||
|
|
||||||
// find all disks which belong to each respective endpoints
|
|
||||||
for i := range servers {
|
|
||||||
for _, disk := range storageInfo.Disks {
|
|
||||||
if strings.Contains(disk.Endpoint, servers[i].Endpoint) {
|
|
||||||
servers[i].Disks = append(servers[i].Disks, disk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add all the disks local to this server.
|
|
||||||
for _, disk := range storageInfo.Disks {
|
|
||||||
if disk.DrivePath == "" && disk.Endpoint == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if disk.Endpoint == disk.DrivePath {
|
|
||||||
servers[len(servers)-1].Disks = append(servers[len(servers)-1].Disks, disk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infoMsg := madmin.InfoMessage{
|
infoMsg := madmin.InfoMessage{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
|
|||||||
+36
-14
@@ -21,6 +21,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -93,15 +95,15 @@ type allHealState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newHealState - initialize global heal state management
|
// newHealState - initialize global heal state management
|
||||||
func newHealState() *allHealState {
|
func newHealState(cleanup bool) *allHealState {
|
||||||
healState := &allHealState{
|
hstate := &allHealState{
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
healLocalDisks: map[Endpoint]struct{}{},
|
healLocalDisks: map[Endpoint]struct{}{},
|
||||||
}
|
}
|
||||||
|
if cleanup {
|
||||||
go healState.periodicHealSeqsClean(GlobalContext)
|
go hstate.periodicHealSeqsClean(GlobalContext)
|
||||||
|
}
|
||||||
return healState
|
return hstate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) healDriveCount() int {
|
func (ahs *allHealState) healDriveCount() int {
|
||||||
@@ -143,9 +145,13 @@ func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
|||||||
func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
||||||
// Launch clean-up routine to remove this heal sequence (after
|
// Launch clean-up routine to remove this heal sequence (after
|
||||||
// it ends) from the global state after timeout has elapsed.
|
// it ends) from the global state after timeout has elapsed.
|
||||||
|
periodicTimer := time.NewTimer(time.Minute * 5)
|
||||||
|
defer periodicTimer.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Minute * 5):
|
case <-periodicTimer.C:
|
||||||
|
periodicTimer.Reset(time.Minute * 5)
|
||||||
now := UTCNow()
|
now := UTCNow()
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
for path, h := range ahs.healSeqMap {
|
for path, h := range ahs.healSeqMap {
|
||||||
@@ -657,7 +663,9 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||||
|
globalHealConfigMu.Lock()
|
||||||
opts := globalHealConfig
|
opts := globalHealConfig
|
||||||
|
globalHealConfigMu.Unlock()
|
||||||
|
|
||||||
// Send heal request
|
// Send heal request
|
||||||
task := healTask{
|
task := healTask{
|
||||||
@@ -669,11 +677,10 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
}
|
}
|
||||||
if source.opts != nil {
|
if source.opts != nil {
|
||||||
task.opts = *source.opts
|
task.opts = *source.opts
|
||||||
} else {
|
}
|
||||||
if opts.Bitrot {
|
if opts.Bitrot {
|
||||||
task.opts.ScanMode = madmin.HealDeepScan
|
task.opts.ScanMode = madmin.HealDeepScan
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Wait and proceed if there are active requests
|
// Wait and proceed if there are active requests
|
||||||
waitForLowHTTPReq(opts.IOCount, opts.Sleep)
|
waitForLowHTTPReq(opts.IOCount, opts.Sleep)
|
||||||
@@ -776,13 +783,18 @@ func (h *healSequence) healFromSourceCh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
|
func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
|
||||||
// Start healing the config prefix.
|
// Try to pro-actively heal backend-encrypted file.
|
||||||
if err := h.healMinioSysMeta(objAPI, minioConfigPrefix)(); err != nil {
|
if err := h.queueHealTask(healSource{
|
||||||
|
bucket: minioMetaBucket,
|
||||||
|
object: backendEncryptedFile,
|
||||||
|
}, madmin.HealItemBucketMetadata); err != nil {
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start healing the bucket config prefix.
|
// Start healing the config prefix.
|
||||||
return h.healMinioSysMeta(objAPI, bucketConfigPrefix)()
|
return h.healMinioSysMeta(objAPI, minioConfigPrefix)()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
||||||
@@ -819,6 +831,11 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
|
|||||||
return errHealStopSignalled
|
return errHealStopSignalled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip metacache entries healing
|
||||||
|
if strings.HasPrefix(object, "buckets/.minio.sys/.metacache/") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err := h.queueHealTask(healSource{
|
err := h.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: object,
|
object: object,
|
||||||
@@ -855,11 +872,16 @@ func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
|
|||||||
return h.healBucket(objAPI, h.bucket, bucketsOnly)
|
return h.healBucket(objAPI, h.bucket, bucketsOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, err := objAPI.ListBucketsHeal(h.ctx)
|
buckets, err := objAPI.ListBuckets(h.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errFnHealFromAPIErr(h.ctx, err)
|
return errFnHealFromAPIErr(h.ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Heal latest buckets first.
|
||||||
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
|
})
|
||||||
|
|
||||||
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, bucketsOnly); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
||||||
|
var localEndpoints Endpoints
|
||||||
addr := r.Host
|
addr := r.Host
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
addr = GetLocalPeer(endpointServerPools)
|
addr = GetLocalPeer(endpointServerPools)
|
||||||
@@ -39,6 +40,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal {
|
||||||
// Only proceed for local endpoints
|
// Only proceed for local endpoints
|
||||||
network[nodeName] = "online"
|
network[nodeName] = "online"
|
||||||
|
localEndpoints = append(localEndpoints, endpoint)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, present := network[nodeName]
|
_, present := network[nodeName]
|
||||||
@@ -52,6 +54,11 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localDisks, _ := initStorageDisksWithErrors(localEndpoints)
|
||||||
|
defer closeStorageDisks(localDisks)
|
||||||
|
|
||||||
|
storageInfo, _ := getStorageInfo(localDisks, localEndpoints.GetAllStrings())
|
||||||
|
|
||||||
return madmin.ServerProperties{
|
return madmin.ServerProperties{
|
||||||
State: "ok",
|
State: "ok",
|
||||||
Endpoint: addr,
|
Endpoint: addr,
|
||||||
@@ -59,5 +66,6 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
Version: Version,
|
Version: Version,
|
||||||
CommitID: CommitID,
|
CommitID: CommitID,
|
||||||
Network: network,
|
Network: network,
|
||||||
|
Disks: storageInfo.Disks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-9
@@ -157,20 +157,14 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.PartNumber > 0 {
|
if opts.PartNumber > 0 {
|
||||||
var start, end int64
|
rs = partNumberToRangeSpec(objInfo, opts.PartNumber)
|
||||||
for i := 0; i < len(objInfo.Parts) && i < opts.PartNumber; i++ {
|
|
||||||
start = end
|
|
||||||
end = start + objInfo.Parts[i].ActualSize - 1
|
|
||||||
}
|
}
|
||||||
rs = &HTTPRangeSpec{Start: start, End: end}
|
|
||||||
rangeLen = end - start + 1
|
// For providing ranged content
|
||||||
} else {
|
|
||||||
// for providing ranged content
|
|
||||||
start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
|
start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Set content length.
|
// Set content length.
|
||||||
w.Header().Set(xhttp.ContentLength, strconv.FormatInt(rangeLen, 10))
|
w.Header().Set(xhttp.ContentLength, strconv.FormatInt(rangeLen, 10))
|
||||||
|
|||||||
+1
-1
@@ -388,7 +388,7 @@ func getObjectLocation(r *http.Request, domains []string, bucket, object string)
|
|||||||
}
|
}
|
||||||
proto := handlers.GetSourceScheme(r)
|
proto := handlers.GetSourceScheme(r)
|
||||||
if proto == "" {
|
if proto == "" {
|
||||||
proto = getURLScheme(globalIsSSL)
|
proto = getURLScheme(globalIsTLS)
|
||||||
}
|
}
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Host: r.Host,
|
Host: r.Host,
|
||||||
|
|||||||
+4
-3
@@ -151,9 +151,10 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
return cred, claims, owner, ErrNone
|
return cred, claims, owner, ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAdminRequestAuthType checks whether the request is a valid signature V2 or V4 request.
|
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
||||||
// It does not accept presigned or JWT or anonymous requests.
|
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
||||||
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
// are automatically rejected.
|
||||||
|
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
||||||
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, s3Err
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuthType(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,20 +54,25 @@ func (h *healRoutine) queueHealTask(task healTask) {
|
|||||||
h.tasks <- task
|
h.tasks <- task
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForLowHTTPReq(tolerance int, maxWait time.Duration) {
|
func waitForLowHTTPReq(maxIO int, maxWait time.Duration) {
|
||||||
|
// No need to wait run at full speed.
|
||||||
|
if maxIO <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// At max 10 attempts to wait with 100 millisecond interval before proceeding
|
// At max 10 attempts to wait with 100 millisecond interval before proceeding
|
||||||
waitCount := 10
|
waitCount := 10
|
||||||
waitTick := 100 * time.Millisecond
|
waitTick := 100 * time.Millisecond
|
||||||
|
|
||||||
// Bucket notification and http trace are not costly, it is okay to ignore them
|
// Bucket notification and http trace are not costly, it is okay to ignore them
|
||||||
// while counting the number of concurrent connections
|
// while counting the number of concurrent connections
|
||||||
toleranceFn := func() int {
|
maxIOFn := func() int {
|
||||||
return tolerance + globalHTTPListen.NumSubscribers() + globalHTTPTrace.NumSubscribers()
|
return maxIO + globalHTTPListen.NumSubscribers() + globalHTTPTrace.NumSubscribers()
|
||||||
}
|
}
|
||||||
|
|
||||||
if httpServer := newHTTPServerFn(); httpServer != nil {
|
if httpServer := newHTTPServerFn(); httpServer != nil {
|
||||||
// Any requests in progress, delay the heal.
|
// Any requests in progress, delay the heal.
|
||||||
for httpServer.GetRequestCount() >= toleranceFn() {
|
for httpServer.GetRequestCount() >= maxIOFn() {
|
||||||
time.Sleep(waitTick)
|
time.Sleep(waitTick)
|
||||||
waitCount--
|
waitCount--
|
||||||
if waitCount == 0 {
|
if waitCount == 0 {
|
||||||
@@ -97,7 +102,7 @@ func (h *healRoutine) run(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
case task.bucket == SlashSeparator:
|
case task.bucket == SlashSeparator:
|
||||||
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
||||||
case task.bucket != "" && task.object == "":
|
case task.bucket != "" && task.object == "":
|
||||||
res, err = objAPI.HealBucket(ctx, task.bucket, task.opts.DryRun, task.opts.Remove)
|
res, err = objAPI.HealBucket(ctx, task.bucket, task.opts)
|
||||||
case task.bucket != "" && task.object != "":
|
case task.bucket != "" && task.object != "":
|
||||||
res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts)
|
res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -46,16 +48,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
||||||
|
|
||||||
var bgSeq *healSequence
|
bgSeq := mustGetHealSequence(ctx)
|
||||||
var found bool
|
|
||||||
|
|
||||||
for {
|
|
||||||
bgSeq, found = globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
|
|
||||||
if found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||||
|
|
||||||
@@ -118,12 +111,17 @@ func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
// 2. Only the node hosting the disk is responsible to perform the heal
|
// 2. Only the node hosting the disk is responsible to perform the heal
|
||||||
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq *healSequence) {
|
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq *healSequence) {
|
||||||
// Perform automatic disk healing when a disk is replaced locally.
|
// Perform automatic disk healing when a disk is replaced locally.
|
||||||
|
diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval)
|
||||||
|
defer diskCheckTimer.Stop()
|
||||||
wait:
|
wait:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.After(defaultMonitorNewDiskInterval):
|
case <-diskCheckTimer.C:
|
||||||
|
// Reset to next interval.
|
||||||
|
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
|
||||||
|
|
||||||
var erasureSetInZoneDisksToHeal []map[int][]StorageAPI
|
var erasureSetInZoneDisksToHeal []map[int][]StorageAPI
|
||||||
|
|
||||||
healDisks := globalBackgroundHealState.getHealLocalDisks()
|
healDisks := globalBackgroundHealState.getHealLocalDisks()
|
||||||
@@ -143,6 +141,10 @@ wait:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugf("disk check timer fired, attempting to heal %d drives\n", len(healDisks))
|
||||||
|
}
|
||||||
|
|
||||||
// heal only if new disks found.
|
// heal only if new disks found.
|
||||||
for _, endpoint := range healDisks {
|
for _, endpoint := range healDisks {
|
||||||
disk, format, err := connectEndpoint(endpoint)
|
disk, format, err := connectEndpoint(endpoint)
|
||||||
@@ -169,7 +171,13 @@ wait:
|
|||||||
erasureSetInZoneDisksToHeal[zoneIdx][setIndex] = append(erasureSetInZoneDisksToHeal[zoneIdx][setIndex], disk)
|
erasureSetInZoneDisksToHeal[zoneIdx][setIndex] = append(erasureSetInZoneDisksToHeal[zoneIdx][setIndex], disk)
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, _ := z.ListBucketsHeal(ctx)
|
buckets, _ := z.ListBuckets(ctx)
|
||||||
|
|
||||||
|
// Heal latest buckets first.
|
||||||
|
sort.Slice(buckets, func(i, j int) bool {
|
||||||
|
return buckets[i].Created.After(buckets[j].Created)
|
||||||
|
})
|
||||||
|
|
||||||
for i, setMap := range erasureSetInZoneDisksToHeal {
|
for i, setMap := range erasureSetInZoneDisksToHeal {
|
||||||
for setIndex, disks := range setMap {
|
for setIndex, disks := range setMap {
|
||||||
for _, disk := range disks {
|
for _, disk := range disks {
|
||||||
|
|||||||
+19
-4
@@ -28,6 +28,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -433,6 +434,20 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if object.VersionID != "" && object.VersionID != nullVersionID {
|
||||||
|
if _, err := uuid.Parse(object.VersionID); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("invalid version-id specified %w", err))
|
||||||
|
apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion)
|
||||||
|
dErrs[index] = DeleteError{
|
||||||
|
Code: apiErr.Code,
|
||||||
|
Message: apiErr.Description,
|
||||||
|
Key: object.ObjectName,
|
||||||
|
VersionID: object.VersionID,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if replicateDeletes || hasLockEnabled || hasLifecycleConfig {
|
if replicateDeletes || hasLockEnabled || hasLifecycleConfig {
|
||||||
goi, gerr = getObjectInfoFn(ctx, bucket, object.ObjectName, ObjectOptions{
|
goi, gerr = getObjectInfoFn(ctx, bucket, object.ObjectName, ObjectOptions{
|
||||||
VersionID: object.VersionID,
|
VersionID: object.VersionID,
|
||||||
@@ -503,7 +518,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus,
|
DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus,
|
||||||
PurgeTransitioned: dObjects[i].PurgeTransitioned,
|
PurgeTransitioned: dObjects[i].PurgeTransitioned,
|
||||||
}]
|
}]
|
||||||
if errs[i] == nil || isErrObjectNotFound(errs[i]) {
|
if errs[i] == nil || isErrObjectNotFound(errs[i]) || isErrVersionNotFound(errs[i]) {
|
||||||
if replicateDeletes {
|
if replicateDeletes {
|
||||||
dObjects[i].DeleteMarkerReplicationStatus = deleteList[i].DeleteMarkerReplicationStatus
|
dObjects[i].DeleteMarkerReplicationStatus = deleteList[i].DeleteMarkerReplicationStatus
|
||||||
dObjects[i].VersionPurgeStatus = deleteList[i].VersionPurgeStatus
|
dObjects[i].VersionPurgeStatus = deleteList[i].VersionPurgeStatus
|
||||||
@@ -733,7 +748,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) {
|
if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -880,7 +895,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption {
|
if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption {
|
||||||
// This request header needs to be set prior to setting ObjectOptions
|
// This request header needs to be set prior to setting ObjectOptions
|
||||||
if !crypto.SSEC.IsRequested(r.Header) {
|
if !crypto.SSEC.IsRequested(r.Header) {
|
||||||
r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
|
r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,7 +907,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
if crypto.IsRequested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
if _, ok := crypto.IsRequested(formValues); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
||||||
if crypto.SSECopy.IsRequested(r.Header) {
|
if crypto.SSECopy.IsRequested(r.Header) {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
|
|||||||
+17
-6
@@ -28,7 +28,6 @@ import (
|
|||||||
|
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
sse "github.com/minio/minio/pkg/bucket/encryption"
|
sse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
@@ -356,11 +355,23 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, objInfo Object
|
|||||||
opts.Versioned = globalBucketVersioningSys.Enabled(oi.Bucket)
|
opts.Versioned = globalBucketVersioningSys.Enabled(oi.Bucket)
|
||||||
opts.VersionID = oi.VersionID
|
opts.VersionID = oi.VersionID
|
||||||
opts.TransitionStatus = lifecycle.TransitionComplete
|
opts.TransitionStatus = lifecycle.TransitionComplete
|
||||||
|
eventName := event.ObjectTransitionComplete
|
||||||
|
|
||||||
if _, err = objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts); err != nil {
|
_, err = objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts)
|
||||||
return err
|
if err != nil {
|
||||||
|
eventName = event.ObjectTransitionFailed
|
||||||
}
|
}
|
||||||
return nil
|
// Notify object deleted event.
|
||||||
|
sendEvent(eventArgs{
|
||||||
|
EventName: eventName,
|
||||||
|
BucketName: oi.Bucket,
|
||||||
|
Object: ObjectInfo{
|
||||||
|
Name: oi.Name,
|
||||||
|
VersionID: opts.VersionID,
|
||||||
|
},
|
||||||
|
Host: "Internal: [ILM-Transition]",
|
||||||
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLifecycleTransitionTargetArn returns transition ARN for storage class specified in the config.
|
// getLifecycleTransitionTargetArn returns transition ARN for storage class specified in the config.
|
||||||
@@ -535,7 +546,7 @@ func (r *RestoreObjectRequest) validate(ctx context.Context, objAPI ObjectLayer)
|
|||||||
if r.OutputLocation.S3.Prefix == "" {
|
if r.OutputLocation.S3.Prefix == "" {
|
||||||
return fmt.Errorf("Prefix is a required parameter in OutputLocation")
|
return fmt.Errorf("Prefix is a required parameter in OutputLocation")
|
||||||
}
|
}
|
||||||
if r.OutputLocation.S3.Encryption.EncryptionType != crypto.SSEAlgorithmAES256 {
|
if r.OutputLocation.S3.Encryption.EncryptionType != xhttp.AmzEncryptionAES {
|
||||||
return NotImplemented{}
|
return NotImplemented{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -561,7 +572,7 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
|
|||||||
}
|
}
|
||||||
meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String()
|
meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String()
|
||||||
if rreq.OutputLocation.S3.Encryption.EncryptionType != "" {
|
if rreq.OutputLocation.S3.Encryption.EncryptionType != "" {
|
||||||
meta[crypto.SSEHeader] = crypto.SSEAlgorithmAES256
|
meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
||||||
}
|
}
|
||||||
return ObjectOptions{
|
return ObjectOptions{
|
||||||
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
Versioned: globalBucketVersioningSys.Enabled(bucket),
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
@@ -168,7 +169,10 @@ func (sys *BucketMetadataSys) Update(bucket string, configFile string, configDat
|
|||||||
}
|
}
|
||||||
meta.ReplicationConfigXML = configData
|
meta.ReplicationConfigXML = configData
|
||||||
case bucketTargetsFile:
|
case bucketTargetsFile:
|
||||||
meta.BucketTargetsConfigJSON = configData
|
meta.BucketTargetsConfigJSON, meta.BucketTargetsConfigMetaJSON, err = encryptBucketMetadata(meta.Name, configData, crypto.Context{bucket: meta.Name, bucketTargetsFile: bucketTargetsFile})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error encrypting bucket target metadata %w", err)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
||||||
}
|
}
|
||||||
@@ -191,7 +195,6 @@ func (sys *BucketMetadataSys) Update(bucket string, configFile string, configDat
|
|||||||
// This function should only be used with
|
// This function should only be used with
|
||||||
// - GetBucketInfo
|
// - GetBucketInfo
|
||||||
// - ListBuckets
|
// - ListBuckets
|
||||||
// - ListBucketsHeal (only in case of erasure coding mode)
|
|
||||||
// For all other bucket specific metadata, use the relevant
|
// For all other bucket specific metadata, use the relevant
|
||||||
// calls implemented specifically for each of those features.
|
// calls implemented specifically for each of those features.
|
||||||
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
|
||||||
@@ -440,6 +443,10 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
for index := range buckets {
|
for index := range buckets {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
|
_, _ = objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
|
||||||
|
// Ensure heal opts for bucket metadata be deep healed all the time.
|
||||||
|
ScanMode: madmin.HealDeepScan,
|
||||||
|
})
|
||||||
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name)
|
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+88
-7
@@ -19,6 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
@@ -28,6 +29,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
@@ -37,6 +39,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/bucket/versioning"
|
"github.com/minio/minio/pkg/bucket/versioning"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -74,6 +77,7 @@ type BucketMetadata struct {
|
|||||||
QuotaConfigJSON []byte
|
QuotaConfigJSON []byte
|
||||||
ReplicationConfigXML []byte
|
ReplicationConfigXML []byte
|
||||||
BucketTargetsConfigJSON []byte
|
BucketTargetsConfigJSON []byte
|
||||||
|
BucketTargetsConfigMetaJSON []byte
|
||||||
|
|
||||||
// Unexported fields. Must be updated atomically.
|
// Unexported fields. Must be updated atomically.
|
||||||
policyConfig *policy.Policy
|
policyConfig *policy.Policy
|
||||||
@@ -86,6 +90,7 @@ type BucketMetadata struct {
|
|||||||
quotaConfig *madmin.BucketQuota
|
quotaConfig *madmin.BucketQuota
|
||||||
replicationConfig *replication.Config
|
replicationConfig *replication.Config
|
||||||
bucketTargetConfig *madmin.BucketTargets
|
bucketTargetConfig *madmin.BucketTargets
|
||||||
|
bucketTargetConfigMeta map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
||||||
@@ -101,6 +106,7 @@ func newBucketMetadata(name string) BucketMetadata {
|
|||||||
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
|
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
|
||||||
},
|
},
|
||||||
bucketTargetConfig: &madmin.BucketTargets{},
|
bucketTargetConfig: &madmin.BucketTargets{},
|
||||||
|
bucketTargetConfigMeta: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,16 +146,16 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string)
|
|||||||
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
||||||
b := newBucketMetadata(bucket)
|
b := newBucketMetadata(bucket)
|
||||||
err := b.Load(ctx, objectAPI, b.Name)
|
err := b.Load(ctx, objectAPI, b.Name)
|
||||||
if err == nil {
|
if err != nil && !errors.Is(err, errConfigNotFound) {
|
||||||
return b, b.convertLegacyConfigs(ctx, objectAPI)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.Is(err, errConfigNotFound) {
|
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
||||||
return b, b.convertLegacyConfigs(ctx, objectAPI)
|
if err := b.convertLegacyConfigs(ctx, objectAPI); err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
// migrate unencrypted remote targets
|
||||||
|
return b, b.migrateTargetConfig(ctx, objectAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAllConfigs will parse all configs and populate the private fields.
|
// parseAllConfigs will parse all configs and populate the private fields.
|
||||||
@@ -234,7 +240,8 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(b.BucketTargetsConfigJSON) != 0 {
|
if len(b.BucketTargetsConfigJSON) != 0 {
|
||||||
if err = json.Unmarshal(b.BucketTargetsConfigJSON, b.bucketTargetConfig); err != nil {
|
b.bucketTargetConfig, err = parseBucketTargetConfig(b.Name, b.BucketTargetsConfigJSON, b.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -354,6 +361,7 @@ func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile)
|
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile)
|
||||||
return saveConfig(ctx, api, configFile, data)
|
return saveConfig(ctx, api, configFile, data)
|
||||||
}
|
}
|
||||||
@@ -373,3 +381,76 @@ func deleteBucketMetadata(ctx context.Context, obj objectDeleter, bucket string)
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
|
||||||
|
func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error {
|
||||||
|
var err error
|
||||||
|
// early return if no targets or already encrypted
|
||||||
|
if len(b.BucketTargetsConfigJSON) == 0 || GlobalKMS == nil || len(b.BucketTargetsConfigMetaJSON) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
encBytes, metaBytes, err := encryptBucketMetadata(b.Name, b.BucketTargetsConfigJSON, crypto.Context{b.Name: b.Name, bucketTargetsFile: bucketTargetsFile})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.BucketTargetsConfigJSON = encBytes
|
||||||
|
b.BucketTargetsConfigMetaJSON = metaBytes
|
||||||
|
return b.Save(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt bucket metadata if kms is configured.
|
||||||
|
func encryptBucketMetadata(bucket string, input []byte, kmsContext crypto.Context) (output, metabytes []byte, err error) {
|
||||||
|
var sealedKey crypto.SealedKey
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
output = input
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
key [32]byte
|
||||||
|
encKey []byte
|
||||||
|
)
|
||||||
|
metadata := make(map[string]string)
|
||||||
|
key, encKey, err = GlobalKMS.GenerateKey(GlobalKMS.DefaultKeyID(), kmsContext)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outbuf := bytes.NewBuffer(nil)
|
||||||
|
objectKey := crypto.GenerateKey(key, rand.Reader)
|
||||||
|
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
|
||||||
|
crypto.S3.CreateMetadata(metadata, GlobalKMS.DefaultKeyID(), encKey, sealedKey)
|
||||||
|
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20})
|
||||||
|
if err != nil {
|
||||||
|
return output, metabytes, err
|
||||||
|
}
|
||||||
|
metabytes, err = json.Marshal(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return outbuf.Bytes(), metabytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt bucket metadata if kms is configured.
|
||||||
|
func decryptBucketMetadata(input []byte, bucket string, meta map[string]string, kmsContext crypto.Context) ([]byte, error) {
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
return nil, errKMSNotConfigured
|
||||||
|
}
|
||||||
|
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(meta)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extKey, err := GlobalKMS.UnsealKey(keyID, kmsKey, kmsContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var objectKey crypto.ObjectKey
|
||||||
|
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outbuf := bytes.NewBuffer(nil)
|
||||||
|
_, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20})
|
||||||
|
|
||||||
|
return outbuf.Bytes(), err
|
||||||
|
}
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "BucketTargetsConfigMetaJSON":
|
||||||
|
z.BucketTargetsConfigMetaJSON, err = dc.ReadBytes(z.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -115,9 +121,9 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 13
|
// map header, size 14
|
||||||
// write "Name"
|
// write "Name"
|
||||||
err = en.Append(0x8d, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
err = en.Append(0x8e, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -246,15 +252,25 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// write "BucketTargetsConfigMetaJSON"
|
||||||
|
err = en.Append(0xbb, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x4a, 0x53, 0x4f, 0x4e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBytes(z.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 13
|
// map header, size 14
|
||||||
// string "Name"
|
// string "Name"
|
||||||
o = append(o, 0x8d, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
o = append(o, 0x8e, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||||
o = msgp.AppendString(o, z.Name)
|
o = msgp.AppendString(o, z.Name)
|
||||||
// string "Created"
|
// string "Created"
|
||||||
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64)
|
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64)
|
||||||
@@ -292,6 +308,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "BucketTargetsConfigJSON"
|
// string "BucketTargetsConfigJSON"
|
||||||
o = append(o, 0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
o = append(o, 0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
||||||
o = msgp.AppendBytes(o, z.BucketTargetsConfigJSON)
|
o = msgp.AppendBytes(o, z.BucketTargetsConfigJSON)
|
||||||
|
// string "BucketTargetsConfigMetaJSON"
|
||||||
|
o = append(o, 0xbb, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x4a, 0x53, 0x4f, 0x4e)
|
||||||
|
o = msgp.AppendBytes(o, z.BucketTargetsConfigMetaJSON)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +410,12 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "BucketTargetsConfigMetaJSON":
|
||||||
|
z.BucketTargetsConfigMetaJSON, bts, err = msgp.ReadBytesBytes(bts, z.BucketTargetsConfigMetaJSON)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -405,6 +430,6 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *BucketMetadata) Msgsize() (s int) {
|
func (z *BucketMetadata) Msgsize() (s int) {
|
||||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON)
|
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
|||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isErrObjectNotFound(gerr) || isErrVersionNotFound(gerr) {
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
return toAPIErrorCode(ctx, gerr)
|
return toAPIErrorCode(ctx, gerr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ func getCopyObjMetadata(oi ObjectInfo, dest replication.Destination) map[string]
|
|||||||
if oi.UserTags != "" {
|
if oi.UserTags != "" {
|
||||||
meta[xhttp.AmzObjectTagging] = oi.UserTags
|
meta[xhttp.AmzObjectTagging] = oi.UserTags
|
||||||
}
|
}
|
||||||
meta[xhttp.MinIOSourceMTime] = oi.ModTime.Format(time.RFC3339)
|
meta[xhttp.MinIOSourceMTime] = oi.ModTime.Format(time.RFC3339Nano)
|
||||||
meta[xhttp.MinIOSourceETag] = oi.ETag
|
meta[xhttp.MinIOSourceETag] = oi.ETag
|
||||||
meta[xhttp.AmzBucketReplicationStatus] = replication.Replica.String()
|
meta[xhttp.AmzBucketReplicationStatus] = replication.Replica.String()
|
||||||
return meta
|
return meta
|
||||||
@@ -330,7 +330,7 @@ func putReplicationOpts(ctx context.Context, dest replication.Destination, objIn
|
|||||||
putOpts.Mode = rmode
|
putOpts.Mode = rmode
|
||||||
}
|
}
|
||||||
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
||||||
rdate, err := time.Parse(time.RFC3339, retainDateStr)
|
rdate, err := time.Parse(time.RFC3339Nano, retainDateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-1
@@ -19,6 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -27,6 +28,7 @@ import (
|
|||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/pkg/bucket/versioning"
|
"github.com/minio/minio/pkg/bucket/versioning"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
sha256 "github.com/minio/sha256-simd"
|
sha256 "github.com/minio/sha256-simd"
|
||||||
@@ -348,7 +350,7 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*m
|
|||||||
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
||||||
|
|
||||||
getRemoteTargetInstanceTransportOnce.Do(func() {
|
getRemoteTargetInstanceTransportOnce.Do(func() {
|
||||||
getRemoteTargetInstanceTransport = newGatewayHTTPTransport(1 * time.Hour)
|
getRemoteTargetInstanceTransport = newGatewayHTTPTransport(10 * time.Minute)
|
||||||
})
|
})
|
||||||
|
|
||||||
core, err := miniogo.NewCore(tcfg.URL().Host, &miniogo.Options{
|
core, err := miniogo.NewCore(tcfg.URL().Host, &miniogo.Options{
|
||||||
@@ -391,3 +393,32 @@ func generateARN(t *madmin.BucketTarget) string {
|
|||||||
}
|
}
|
||||||
return arn.String()
|
return arn.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns parsed target config. If KMS is configured, remote target is decrypted
|
||||||
|
func parseBucketTargetConfig(bucket string, cdata, cmetadata []byte) (*madmin.BucketTargets, error) {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
t madmin.BucketTargets
|
||||||
|
meta map[string]string
|
||||||
|
)
|
||||||
|
if len(cdata) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
data = cdata
|
||||||
|
if len(cmetadata) != 0 {
|
||||||
|
if err := json.Unmarshal(cmetadata, &meta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if crypto.S3.IsEncrypted(meta) {
|
||||||
|
if data, err = decryptBucketMetadata(cdata, bucket, meta, crypto.Context{bucket: bucket, bucketTargetsFile: bucketTargetsFile}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(data, &t); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|||||||
+15
-1
@@ -30,6 +30,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
dns2 "github.com/miekg/dns"
|
dns2 "github.com/miekg/dns"
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -38,15 +39,26 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/certs"
|
"github.com/minio/minio/pkg/certs"
|
||||||
|
"github.com/minio/minio/pkg/console"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// serverDebugLog will enable debug printing
|
||||||
|
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logger.Init(GOPATH, GOROOT)
|
logger.Init(GOPATH, GOROOT)
|
||||||
logger.RegisterError(config.FmtError)
|
logger.RegisterError(config.FmtError)
|
||||||
|
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
globalDNSCache = xhttp.NewDNSCache(3*time.Second, 10*time.Second)
|
globalDNSCache = xhttp.NewDNSCache(10*time.Second, 3*time.Second)
|
||||||
|
|
||||||
|
initGlobalContext()
|
||||||
|
|
||||||
|
globalReplicationState = newReplicationState()
|
||||||
|
globalTransitionState = newTransitionState()
|
||||||
|
|
||||||
|
console.SetColor("Debug", color.New())
|
||||||
|
|
||||||
gob.Register(StorageErr(""))
|
gob.Register(StorageErr(""))
|
||||||
}
|
}
|
||||||
@@ -64,10 +76,12 @@ func verifyObjectLayerFeatures(name string, objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalCompressConfigMu.Lock()
|
||||||
if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() {
|
if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() {
|
||||||
logger.Fatal(errInvalidArgument,
|
logger.Fatal(errInvalidArgument,
|
||||||
"Compression support is requested but '%s' does not support compression", name)
|
"Compression support is requested but '%s' does not support compression", name)
|
||||||
}
|
}
|
||||||
|
globalCompressConfigMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for updates and print a notification message
|
// Check for updates and print a notification message
|
||||||
|
|||||||
+85
-10
@@ -17,6 +17,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/config/api"
|
"github.com/minio/minio/cmd/config/api"
|
||||||
"github.com/minio/minio/cmd/config/cache"
|
"github.com/minio/minio/cmd/config/cache"
|
||||||
"github.com/minio/minio/cmd/config/compress"
|
"github.com/minio/minio/cmd/config/compress"
|
||||||
|
"github.com/minio/minio/cmd/config/crawler"
|
||||||
"github.com/minio/minio/cmd/config/dns"
|
"github.com/minio/minio/cmd/config/dns"
|
||||||
"github.com/minio/minio/cmd/config/etcd"
|
"github.com/minio/minio/cmd/config/etcd"
|
||||||
"github.com/minio/minio/cmd/config/heal"
|
"github.com/minio/minio/cmd/config/heal"
|
||||||
@@ -57,6 +59,7 @@ func initHelp() {
|
|||||||
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
||||||
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
||||||
config.HealSubSys: heal.DefaultKVS,
|
config.HealSubSys: heal.DefaultKVS,
|
||||||
|
config.CrawlerSubSys: crawler.DefaultKVS,
|
||||||
}
|
}
|
||||||
for k, v := range notify.DefaultNotificationKVS {
|
for k, v := range notify.DefaultNotificationKVS {
|
||||||
kvs[k] = v
|
kvs[k] = v
|
||||||
@@ -112,6 +115,10 @@ func initHelp() {
|
|||||||
Key: config.HealSubSys,
|
Key: config.HealSubSys,
|
||||||
Description: "manage object healing frequency and bitrot verification checks",
|
Description: "manage object healing frequency and bitrot verification checks",
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.CrawlerSubSys,
|
||||||
|
Description: "manage crawling for usage calculation, lifecycle, healing and more",
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.LoggerWebhookSubSys,
|
Key: config.LoggerWebhookSubSys,
|
||||||
Description: "send server logs to webhook endpoints",
|
Description: "send server logs to webhook endpoints",
|
||||||
@@ -192,6 +199,7 @@ func initHelp() {
|
|||||||
config.CacheSubSys: cache.Help,
|
config.CacheSubSys: cache.Help,
|
||||||
config.CompressionSubSys: compress.Help,
|
config.CompressionSubSys: compress.Help,
|
||||||
config.HealSubSys: heal.Help,
|
config.HealSubSys: heal.Help,
|
||||||
|
config.CrawlerSubSys: crawler.Help,
|
||||||
config.IdentityOpenIDSubSys: openid.Help,
|
config.IdentityOpenIDSubSys: openid.Help,
|
||||||
config.IdentityLDAPSubSys: xldap.Help,
|
config.IdentityLDAPSubSys: xldap.Help,
|
||||||
config.PolicyOPASubSys: opa.Help,
|
config.PolicyOPASubSys: opa.Help,
|
||||||
@@ -221,6 +229,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func validateConfig(s config.Config, setDriveCount int) error {
|
func validateConfig(s config.Config, setDriveCount int) error {
|
||||||
|
// We must have a global lock for this so nobody else modifies env while we do.
|
||||||
|
defer env.LockSetEnv()()
|
||||||
|
|
||||||
// Disable merging env values with config for validation.
|
// Disable merging env values with config for validation.
|
||||||
env.SetEnvOff()
|
env.SetEnvOff()
|
||||||
|
|
||||||
@@ -249,14 +260,25 @@ func validateConfig(s config.Config, setDriveCount int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
|
compCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI != nil {
|
||||||
|
if compCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||||
|
return fmt.Errorf("Backend does not support compression")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs)
|
etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -438,10 +460,6 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalHealConfig, err = heal.LookupConfig(s[config.HealSubSys][config.Default])
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to read heal config: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
|
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -459,11 +477,6 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
logger.LogIf(ctx, fmt.Errorf("%s env is deprecated please migrate to using `mc encrypt` at bucket level", crypto.EnvKMSAutoEncryption))
|
logger.LogIf(ctx, fmt.Errorf("%s env is deprecated please migrate to using `mc encrypt` at bucket level", crypto.EnvKMSAutoEncryption))
|
||||||
}
|
}
|
||||||
|
|
||||||
globalCompressConfig, err = compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup Compression: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
||||||
NewGatewayHTTPTransport(), xhttp.DrainBody)
|
NewGatewayHTTPTransport(), xhttp.DrainBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -538,6 +551,68 @@ func lookupConfigs(s config.Config, setDriveCount int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply dynamic config values
|
||||||
|
logger.LogIf(ctx, applyDynamicConfig(ctx, s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyDynamicConfig will apply dynamic config values.
|
||||||
|
// Dynamic systems should be in config.SubSystemsDynamic as well.
|
||||||
|
func applyDynamicConfig(ctx context.Context, s config.Config) error {
|
||||||
|
// Read all dynamic configs.
|
||||||
|
// API
|
||||||
|
apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compression
|
||||||
|
cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to setup Compression: %w", err)
|
||||||
|
}
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI != nil {
|
||||||
|
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||||
|
return fmt.Errorf("Backend does not support compression")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heal
|
||||||
|
healCfg, err := heal.LookupConfig(s[config.HealSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to apply heal config: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crawler
|
||||||
|
crawlerCfg, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to apply crawler config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply configurations.
|
||||||
|
// We should not fail after this.
|
||||||
|
globalAPIConfig.init(apiConfig, globalAPIConfig.setDriveCount)
|
||||||
|
|
||||||
|
globalCompressConfigMu.Lock()
|
||||||
|
globalCompressConfig = cmpCfg
|
||||||
|
globalCompressConfigMu.Unlock()
|
||||||
|
|
||||||
|
globalHealConfigMu.Lock()
|
||||||
|
globalHealConfig = healCfg
|
||||||
|
globalHealConfigMu.Unlock()
|
||||||
|
|
||||||
|
logger.LogIf(ctx, crawlerSleeper.Update(crawlerCfg.Delay, crawlerCfg.MaxWait))
|
||||||
|
|
||||||
|
// Update all dynamic config values in memory.
|
||||||
|
globalServerConfigMu.Lock()
|
||||||
|
defer globalServerConfigMu.Unlock()
|
||||||
|
if globalServerConfig != nil {
|
||||||
|
for k := range config.SubSystemsDynamic {
|
||||||
|
globalServerConfig[k] = s[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help - return sub-system level help
|
// Help - return sub-system level help
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
etcd "go.etcd.io/etcd/v3/clientv3"
|
etcd "go.etcd.io/etcd/clientv3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
|
func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ func (sCfg *Config) UnmarshalJSON(data []byte) error {
|
|||||||
// acceptable quorum expected for list operations
|
// acceptable quorum expected for list operations
|
||||||
func (sCfg Config) GetListQuorum() int {
|
func (sCfg Config) GetListQuorum() int {
|
||||||
switch sCfg.ListQuorum {
|
switch sCfg.ListQuorum {
|
||||||
case "optimal":
|
|
||||||
return 3
|
|
||||||
case "reduced":
|
case "reduced":
|
||||||
return 2
|
return 2
|
||||||
case "disk":
|
case "disk":
|
||||||
@@ -123,7 +121,7 @@ func (sCfg Config) GetListQuorum() int {
|
|||||||
case "strict":
|
case "strict":
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
// Defaults to 3 drives per set.
|
// Defaults to 3 drives per set, defaults to "optimal" value
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-18
@@ -77,6 +77,7 @@ const (
|
|||||||
LoggerWebhookSubSys = "logger_webhook"
|
LoggerWebhookSubSys = "logger_webhook"
|
||||||
AuditWebhookSubSys = "audit_webhook"
|
AuditWebhookSubSys = "audit_webhook"
|
||||||
HealSubSys = "heal"
|
HealSubSys = "heal"
|
||||||
|
CrawlerSubSys = "crawler"
|
||||||
|
|
||||||
// Add new constants here if you add new fields to config.
|
// Add new constants here if you add new fields to config.
|
||||||
)
|
)
|
||||||
@@ -98,7 +99,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SubSystems - all supported sub-systems
|
// SubSystems - all supported sub-systems
|
||||||
var SubSystems = set.CreateStringSet([]string{
|
var SubSystems = set.CreateStringSet(
|
||||||
CredentialsSubSys,
|
CredentialsSubSys,
|
||||||
RegionSubSys,
|
RegionSubSys,
|
||||||
EtcdSubSys,
|
EtcdSubSys,
|
||||||
@@ -113,6 +114,7 @@ var SubSystems = set.CreateStringSet([]string{
|
|||||||
PolicyOPASubSys,
|
PolicyOPASubSys,
|
||||||
IdentityLDAPSubSys,
|
IdentityLDAPSubSys,
|
||||||
IdentityOpenIDSubSys,
|
IdentityOpenIDSubSys,
|
||||||
|
CrawlerSubSys,
|
||||||
HealSubSys,
|
HealSubSys,
|
||||||
NotifyAMQPSubSys,
|
NotifyAMQPSubSys,
|
||||||
NotifyESSubSys,
|
NotifyESSubSys,
|
||||||
@@ -124,7 +126,15 @@ var SubSystems = set.CreateStringSet([]string{
|
|||||||
NotifyPostgresSubSys,
|
NotifyPostgresSubSys,
|
||||||
NotifyRedisSubSys,
|
NotifyRedisSubSys,
|
||||||
NotifyWebhookSubSys,
|
NotifyWebhookSubSys,
|
||||||
}...)
|
)
|
||||||
|
|
||||||
|
// SubSystemsDynamic - all sub-systems that have dynamic config.
|
||||||
|
var SubSystemsDynamic = set.CreateStringSet(
|
||||||
|
APISubSys,
|
||||||
|
CompressionSubSys,
|
||||||
|
CrawlerSubSys,
|
||||||
|
HealSubSys,
|
||||||
|
)
|
||||||
|
|
||||||
// SubSystemsSingleTargets - subsystems which only support single target.
|
// SubSystemsSingleTargets - subsystems which only support single target.
|
||||||
var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
||||||
@@ -141,6 +151,7 @@ var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
|||||||
IdentityLDAPSubSys,
|
IdentityLDAPSubSys,
|
||||||
IdentityOpenIDSubSys,
|
IdentityOpenIDSubSys,
|
||||||
HealSubSys,
|
HealSubSys,
|
||||||
|
CrawlerSubSys,
|
||||||
}...)
|
}...)
|
||||||
|
|
||||||
// Constant separators
|
// Constant separators
|
||||||
@@ -309,25 +320,29 @@ func (c Config) DelFrom(r io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFrom - implements io.ReaderFrom interface
|
// ReadConfig - read content from input and write into c.
|
||||||
func (c Config) ReadFrom(r io.Reader) (int64, error) {
|
// Returns whether all parameters were dynamic.
|
||||||
|
func (c Config) ReadConfig(r io.Reader) (dynOnly bool, err error) {
|
||||||
var n int
|
var n int
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
|
dynOnly = true
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// Skip any empty lines, or comment like characters
|
// Skip any empty lines, or comment like characters
|
||||||
text := scanner.Text()
|
text := scanner.Text()
|
||||||
if text == "" || strings.HasPrefix(text, KvComment) {
|
if text == "" || strings.HasPrefix(text, KvComment) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := c.SetKVS(text, DefaultKVS); err != nil {
|
dynamic, err := c.SetKVS(text, DefaultKVS)
|
||||||
return 0, err
|
if err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
dynOnly = dynOnly && dynamic
|
||||||
n += len(text)
|
n += len(text)
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return 0, err
|
return false, err
|
||||||
}
|
}
|
||||||
return int64(n), nil
|
return dynOnly, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type configWriteTo struct {
|
type configWriteTo struct {
|
||||||
@@ -618,26 +633,27 @@ func (c Config) Clone() Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetKVS - set specific key values per sub-system.
|
// SetKVS - set specific key values per sub-system.
|
||||||
func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
func (c Config) SetKVS(s string, defaultKVS map[string]KVS) (dynamic bool, err error) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return Errorf("input arguments cannot be empty")
|
return false, Errorf("input arguments cannot be empty")
|
||||||
}
|
}
|
||||||
inputs := strings.SplitN(s, KvSpaceSeparator, 2)
|
inputs := strings.SplitN(s, KvSpaceSeparator, 2)
|
||||||
if len(inputs) <= 1 {
|
if len(inputs) <= 1 {
|
||||||
return Errorf("invalid number of arguments '%s'", s)
|
return false, Errorf("invalid number of arguments '%s'", s)
|
||||||
}
|
}
|
||||||
subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
|
subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
|
||||||
if len(subSystemValue) == 0 {
|
if len(subSystemValue) == 0 {
|
||||||
return Errorf("invalid number of arguments %s", s)
|
return false, Errorf("invalid number of arguments %s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !SubSystems.Contains(subSystemValue[0]) {
|
if !SubSystems.Contains(subSystemValue[0]) {
|
||||||
return Errorf("unknown sub-system %s", s)
|
return false, Errorf("unknown sub-system %s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
|
if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
|
||||||
return Errorf("sub-system '%s' only supports single target", subSystemValue[0])
|
return false, Errorf("sub-system '%s' only supports single target", subSystemValue[0])
|
||||||
}
|
}
|
||||||
|
dynamic = SubSystemsDynamic.Contains(subSystemValue[0])
|
||||||
|
|
||||||
tgt := Default
|
tgt := Default
|
||||||
subSys := subSystemValue[0]
|
subSys := subSystemValue[0]
|
||||||
@@ -647,7 +663,7 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
|||||||
|
|
||||||
fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys())
|
fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys())
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return Errorf("sub-system '%s' cannot have empty keys", subSys)
|
return false, Errorf("sub-system '%s' cannot have empty keys", subSys)
|
||||||
}
|
}
|
||||||
|
|
||||||
var kvs = KVS{}
|
var kvs = KVS{}
|
||||||
@@ -670,7 +686,7 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
|||||||
kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
|
kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return Errorf("key '%s', cannot have empty value", kv[0])
|
return false, Errorf("key '%s', cannot have empty value", kv[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := kvs.Lookup(Enable)
|
_, ok := kvs.Lookup(Enable)
|
||||||
@@ -720,11 +736,11 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
|
|||||||
// Return error only if the
|
// Return error only if the
|
||||||
// key is enabled, for state=off
|
// key is enabled, for state=off
|
||||||
// let it be empty.
|
// let it be empty.
|
||||||
return Errorf(
|
return false, Errorf(
|
||||||
"'%s' is not optional for '%s' sub-system, please check '%s' documentation",
|
"'%s' is not optional for '%s' sub-system, please check '%s' documentation",
|
||||||
hkv.Key, subSys, subSys)
|
hkv.Key, subSys, subSys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c[subSys][tgt] = currKVS
|
c[subSys][tgt] = currKVS
|
||||||
return nil
|
return dynamic, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package crawler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/cmd/config"
|
||||||
|
"github.com/minio/minio/pkg/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compression environment variables
|
||||||
|
const (
|
||||||
|
Delay = "delay"
|
||||||
|
MaxWait = "max_wait"
|
||||||
|
|
||||||
|
EnvDelay = "MINIO_CRAWLER_DELAY"
|
||||||
|
EnvMaxWait = "MINIO_CRAWLER_MAX_WAIT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config represents the heal settings.
|
||||||
|
type Config struct {
|
||||||
|
// Delay is the sleep multiplier.
|
||||||
|
Delay float64 `json:"delay"`
|
||||||
|
// MaxWait is maximum wait time between operations
|
||||||
|
MaxWait time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultKVS - default KV config for heal settings
|
||||||
|
DefaultKVS = config.KVS{
|
||||||
|
config.KV{
|
||||||
|
Key: Delay,
|
||||||
|
Value: "10",
|
||||||
|
},
|
||||||
|
config.KV{
|
||||||
|
Key: MaxWait,
|
||||||
|
Value: "15s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help provides help for config values
|
||||||
|
Help = config.HelpKVS{
|
||||||
|
config.HelpKV{
|
||||||
|
Key: Delay,
|
||||||
|
Description: `crawler delay multiplier, defaults to '10.0'`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "float",
|
||||||
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: MaxWait,
|
||||||
|
Description: `maximum wait time between operations, defaults to '15s'`,
|
||||||
|
Optional: true,
|
||||||
|
Type: "duration",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// LookupConfig - lookup config and override with valid environment settings if any.
|
||||||
|
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
||||||
|
if err = config.CheckValidKeys(config.CrawlerSubSys, kvs, DefaultKVS); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
cfg.Delay, err = strconv.ParseFloat(env.Get(EnvDelay, kvs.Get(Delay)), 64)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
cfg.MaxWait, err = time.ParseDuration(env.Get(EnvMaxWait, kvs.Get(MaxWait)))
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
|
|
||||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
"github.com/coredns/coredns/plugin/etcd/msg"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"go.etcd.io/etcd/v3/clientv3"
|
"go.etcd.io/etcd/clientv3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoEntriesFound - Indicates no entries were found for the given key (directory)
|
// ErrNoEntriesFound - Indicates no entries were found for the given key (directory)
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import (
|
|||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
"go.etcd.io/etcd/v3/clientv3"
|
"go.etcd.io/etcd/clientv3"
|
||||||
"go.etcd.io/etcd/v3/clientv3/namespace"
|
"go.etcd.io/etcd/clientv3/namespace"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+10
-179
@@ -19,231 +19,62 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SSEHeader is the general AWS SSE HTTP header key.
|
|
||||||
const SSEHeader = "X-Amz-Server-Side-Encryption"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSEKmsID is the HTTP header key referencing the SSE-KMS
|
|
||||||
// key ID.
|
|
||||||
SSEKmsID = SSEHeader + "-Aws-Kms-Key-Id"
|
|
||||||
|
|
||||||
// SSEKmsContext is the HTTP header key referencing the
|
|
||||||
// SSE-KMS encryption context.
|
|
||||||
SSEKmsContext = SSEHeader + "-Context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSECAlgorithm is the HTTP header key referencing
|
|
||||||
// the SSE-C algorithm.
|
|
||||||
SSECAlgorithm = SSEHeader + "-Customer-Algorithm"
|
|
||||||
|
|
||||||
// SSECKey is the HTTP header key referencing the
|
|
||||||
// SSE-C client-provided key..
|
|
||||||
SSECKey = SSEHeader + "-Customer-Key"
|
|
||||||
|
|
||||||
// SSECKeyMD5 is the HTTP header key referencing
|
|
||||||
// the MD5 sum of the client-provided key.
|
|
||||||
SSECKeyMD5 = SSEHeader + "-Customer-Key-Md5"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSECopyAlgorithm is the HTTP header key referencing
|
|
||||||
// the SSE-C algorithm for SSE-C copy requests.
|
|
||||||
SSECopyAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
|
|
||||||
|
|
||||||
// SSECopyKey is the HTTP header key referencing the SSE-C
|
|
||||||
// client-provided key for SSE-C copy requests.
|
|
||||||
SSECopyKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
|
|
||||||
|
|
||||||
// SSECopyKeyMD5 is the HTTP header key referencing the
|
|
||||||
// MD5 sum of the client key for SSE-C copy requests.
|
|
||||||
SSECopyKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SSEAlgorithmAES256 is the only supported value for the SSE-S3 or SSE-C algorithm header.
|
|
||||||
// For SSE-S3 see: https://docs.aws.amazon.com/AmazonS3/latest/dev/SSEUsingRESTAPI.html
|
|
||||||
// For SSE-C see: https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
|
|
||||||
SSEAlgorithmAES256 = "AES256"
|
|
||||||
|
|
||||||
// SSEAlgorithmKMS is the value of 'X-Amz-Server-Side-Encryption' for SSE-KMS.
|
|
||||||
// See: https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
|
|
||||||
SSEAlgorithmKMS = "aws:kms"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RemoveSensitiveHeaders removes confidential encryption
|
// RemoveSensitiveHeaders removes confidential encryption
|
||||||
// information - e.g. the SSE-C key - from the HTTP headers.
|
// information - e.g. the SSE-C key - from the HTTP headers.
|
||||||
// It has the same semantics as RemoveSensitiveEntires.
|
// It has the same semantics as RemoveSensitiveEntires.
|
||||||
func RemoveSensitiveHeaders(h http.Header) {
|
func RemoveSensitiveHeaders(h http.Header) {
|
||||||
h.Del(SSECKey)
|
h.Del(xhttp.AmzServerSideEncryptionCustomerKey)
|
||||||
h.Del(SSECopyKey)
|
h.Del(xhttp.AmzServerSideEncryptionCopyCustomerKey)
|
||||||
h.Del(xhttp.AmzMetaUnencryptedContentLength)
|
h.Del(xhttp.AmzMetaUnencryptedContentLength)
|
||||||
h.Del(xhttp.AmzMetaUnencryptedContentMD5)
|
h.Del(xhttp.AmzMetaUnencryptedContentMD5)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers indicates
|
|
||||||
// that any form server-side encryption (SSE-C, SSE-S3 or SSE-KMS)
|
|
||||||
// is requested.
|
|
||||||
func IsRequested(h http.Header) bool {
|
|
||||||
return S3.IsRequested(h) || SSEC.IsRequested(h) || SSECopy.IsRequested(h) || S3KMS.IsRequested(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// S3 represents AWS SSE-S3. It provides functionality to handle
|
|
||||||
// SSE-S3 requests.
|
|
||||||
var S3 = s3{}
|
|
||||||
|
|
||||||
type s3 struct{}
|
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers indicates that
|
|
||||||
// the S3 client requests SSE-S3.
|
|
||||||
func (s3) IsRequested(h http.Header) bool {
|
|
||||||
_, ok := h[SSEHeader]
|
|
||||||
return ok && strings.ToLower(h.Get(SSEHeader)) != SSEAlgorithmKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-S3 related HTTP headers and checks
|
|
||||||
// whether they contain valid values.
|
|
||||||
func (s3) ParseHTTP(h http.Header) (err error) {
|
|
||||||
if h.Get(SSEHeader) != SSEAlgorithmAES256 {
|
|
||||||
err = ErrInvalidEncryptionMethod
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// S3KMS represents AWS SSE-KMS. It provides functionality to
|
|
||||||
// handle SSE-KMS requests.
|
|
||||||
var S3KMS = s3KMS{}
|
|
||||||
|
|
||||||
type s3KMS struct{}
|
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers indicates that
|
|
||||||
// the S3 client requests SSE-KMS.
|
|
||||||
func (s3KMS) IsRequested(h http.Header) bool {
|
|
||||||
if _, ok := h[SSEKmsID]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSEKmsContext]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSEHeader]; ok {
|
|
||||||
return strings.ToUpper(h.Get(SSEHeader)) != SSEAlgorithmAES256 // Return only true if the SSE header is specified and does not contain the SSE-S3 value
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID
|
|
||||||
// and context, if present, on success.
|
|
||||||
func (s3KMS) ParseHTTP(h http.Header) (string, interface{}, error) {
|
|
||||||
algorithm := h.Get(SSEHeader)
|
|
||||||
if algorithm != SSEAlgorithmKMS {
|
|
||||||
return "", nil, ErrInvalidEncryptionMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
contextStr, ok := h[SSEKmsContext]
|
|
||||||
if ok {
|
|
||||||
var context map[string]interface{}
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
if err := json.Unmarshal([]byte(contextStr[0]), &context); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return h.Get(SSEKmsID), context, nil
|
|
||||||
}
|
|
||||||
return h.Get(SSEKmsID), nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// SSEC represents AWS SSE-C. It provides functionality to handle
|
|
||||||
// SSE-C requests.
|
|
||||||
SSEC = ssec{}
|
|
||||||
|
|
||||||
// SSECopy represents AWS SSE-C for copy requests. It provides
|
// SSECopy represents AWS SSE-C for copy requests. It provides
|
||||||
// functionality to handle SSE-C copy requests.
|
// functionality to handle SSE-C copy requests.
|
||||||
SSECopy = ssecCopy{}
|
SSECopy = ssecCopy{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type ssec struct{}
|
|
||||||
type ssecCopy struct{}
|
type ssecCopy struct{}
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers contains
|
|
||||||
// at least one SSE-C header. SSE-C copy headers are ignored.
|
|
||||||
func (ssec) IsRequested(h http.Header) bool {
|
|
||||||
if _, ok := h[SSECAlgorithm]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSECKey]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := h[SSECKeyMD5]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequested returns true if the HTTP headers contains
|
// IsRequested returns true if the HTTP headers contains
|
||||||
// at least one SSE-C copy header. Regular SSE-C headers
|
// at least one SSE-C copy header. Regular SSE-C headers
|
||||||
// are ignored.
|
// are ignored.
|
||||||
func (ssecCopy) IsRequested(h http.Header) bool {
|
func (ssecCopy) IsRequested(h http.Header) bool {
|
||||||
if _, ok := h[SSECopyAlgorithm]; ok {
|
if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := h[SSECopyKey]; ok {
|
if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKey]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := h[SSECopyKeyMD5]; ok {
|
if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-C headers and returns the SSE-C client key
|
|
||||||
// on success. SSE-C copy headers are ignored.
|
|
||||||
func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) {
|
|
||||||
if h.Get(SSECAlgorithm) != SSEAlgorithmAES256 {
|
|
||||||
return key, ErrInvalidCustomerAlgorithm
|
|
||||||
}
|
|
||||||
if h.Get(SSECKey) == "" {
|
|
||||||
return key, ErrMissingCustomerKey
|
|
||||||
}
|
|
||||||
if h.Get(SSECKeyMD5) == "" {
|
|
||||||
return key, ErrMissingCustomerKeyMD5
|
|
||||||
}
|
|
||||||
|
|
||||||
clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECKey))
|
|
||||||
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
|
|
||||||
return key, ErrInvalidCustomerKey
|
|
||||||
}
|
|
||||||
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECKeyMD5))
|
|
||||||
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
|
|
||||||
return key, ErrCustomerKeyMD5Mismatch
|
|
||||||
}
|
|
||||||
copy(key[:], clientKey)
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
|
// ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
|
||||||
// on success. Regular SSE-C headers are ignored.
|
// on success. Regular SSE-C headers are ignored.
|
||||||
func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) {
|
func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) {
|
||||||
if h.Get(SSECopyAlgorithm) != SSEAlgorithmAES256 {
|
if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) != xhttp.AmzEncryptionAES {
|
||||||
return key, ErrInvalidCustomerAlgorithm
|
return key, ErrInvalidCustomerAlgorithm
|
||||||
}
|
}
|
||||||
if h.Get(SSECopyKey) == "" {
|
if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) == "" {
|
||||||
return key, ErrMissingCustomerKey
|
return key, ErrMissingCustomerKey
|
||||||
}
|
}
|
||||||
if h.Get(SSECopyKeyMD5) == "" {
|
if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) == "" {
|
||||||
return key, ErrMissingCustomerKeyMD5
|
return key, ErrMissingCustomerKeyMD5
|
||||||
}
|
}
|
||||||
|
|
||||||
clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKey))
|
clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey))
|
||||||
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
|
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
|
||||||
return key, ErrInvalidCustomerKey
|
return key, ErrInvalidCustomerKey
|
||||||
}
|
}
|
||||||
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKeyMD5))
|
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5))
|
||||||
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
|
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
|
||||||
return key, ErrCustomerKeyMD5Mismatch
|
return key, ErrCustomerKeyMD5Mismatch
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-30
@@ -18,26 +18,29 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsRequested(t *testing.T) {
|
func TestIsRequested(t *testing.T) {
|
||||||
for i, test := range kmsIsRequestedTests {
|
for i, test := range kmsIsRequestedTests {
|
||||||
if got := IsRequested(test.Header) && S3KMS.IsRequested(test.Header); got != test.Expected {
|
_, got := IsRequested(test.Header)
|
||||||
|
got = got && S3KMS.IsRequested(test.Header)
|
||||||
|
if got != test.Expected {
|
||||||
t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, test := range s3IsRequestedTests {
|
for i, test := range s3IsRequestedTests {
|
||||||
if got := IsRequested(test.Header) && S3.IsRequested(test.Header); got != test.Expected {
|
_, got := IsRequested(test.Header)
|
||||||
|
got = got && S3.IsRequested(test.Header)
|
||||||
|
if got != test.Expected {
|
||||||
t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, test := range ssecIsRequestedTests {
|
for i, test := range ssecIsRequestedTests {
|
||||||
if got := IsRequested(test.Header) && SSEC.IsRequested(test.Header); got != test.Expected {
|
_, got := IsRequested(test.Header)
|
||||||
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
got = got && SSEC.IsRequested(test.Header)
|
||||||
}
|
if got != test.Expected {
|
||||||
}
|
|
||||||
for i, test := range ssecCopyIsRequestedTests {
|
|
||||||
if got := IsRequested(test.Header) && SSECopy.IsRequested(test.Header); got != test.Expected {
|
|
||||||
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +138,7 @@ var s3IsRequestedTests = []struct {
|
|||||||
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3
|
{Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3
|
||||||
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{SSEAlgorithmKMS}}, Expected: false}, // 4
|
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{xhttp.AmzEncryptionKMS}}, Expected: false}, // 4
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestS3IsRequested(t *testing.T) {
|
func TestS3IsRequested(t *testing.T) {
|
||||||
@@ -403,7 +406,7 @@ func TestSSECopyParse(t *testing.T) {
|
|||||||
if err == nil && key == zeroKey {
|
if err == nil && key == zeroKey {
|
||||||
t.Errorf("Test %d: parsed client key is zero key", i)
|
t.Errorf("Test %d: parsed client key is zero key", i)
|
||||||
}
|
}
|
||||||
if _, ok := test.Header[SSECKey]; ok {
|
if _, ok := test.Header[xhttp.AmzServerSideEncryptionCustomerKey]; ok {
|
||||||
t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i)
|
t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,46 +417,46 @@ var removeSensitiveHeadersTests = []struct {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECKey: []string{""},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{""},
|
||||||
SSECopyKey: []string{""},
|
xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{""},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{},
|
ExpectedHeader: http.Header{},
|
||||||
},
|
},
|
||||||
{ // Standard SSE-C request headers
|
{ // Standard SSE-C request headers
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{
|
ExpectedHeader: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ // Standard SSE-C + SSE-C-copy request headers
|
{ // Standard SSE-C + SSE-C-copy request headers
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{
|
ExpectedHeader: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ // Standard SSE-C + metadata request headers
|
{ // Standard SSE-C + metadata request headers
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
||||||
},
|
},
|
||||||
ExpectedHeader: http.Header{
|
ExpectedHeader: http.Header{
|
||||||
SSECAlgorithm: []string{SSEAlgorithmAES256},
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
|
||||||
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
|
||||||
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
"X-Amz-Meta-Test-1": []string{"Test-1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+55
-196
@@ -15,19 +15,42 @@
|
|||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MetaMultipart indicates that the object has been uploaded
|
||||||
|
// in multiple parts - via the S3 multipart API.
|
||||||
|
MetaMultipart = "X-Minio-Internal-Encrypted-Multipart"
|
||||||
|
|
||||||
|
// MetaIV is the random initialization vector (IV) used for
|
||||||
|
// the MinIO-internal key derivation.
|
||||||
|
MetaIV = "X-Minio-Internal-Server-Side-Encryption-Iv"
|
||||||
|
|
||||||
|
// MetaAlgorithm is the algorithm used to derive internal keys
|
||||||
|
// and encrypt the objects.
|
||||||
|
MetaAlgorithm = "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm"
|
||||||
|
|
||||||
|
// MetaSealedKeySSEC is the sealed object encryption key in case of SSE-C.
|
||||||
|
MetaSealedKeySSEC = "X-Minio-Internal-Server-Side-Encryption-Sealed-Key"
|
||||||
|
// MetaSealedKeyS3 is the sealed object encryption key in case of SSE-S3
|
||||||
|
MetaSealedKeyS3 = "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key"
|
||||||
|
// MetaSealedKeyKMS is the sealed object encryption key in case of SSE-KMS
|
||||||
|
MetaSealedKeyKMS = "X-Minio-Internal-Server-Side-Encryption-Kms-Sealed-Key"
|
||||||
|
|
||||||
|
// MetaKeyID is the KMS master key ID used to generate/encrypt the data
|
||||||
|
// encryption key (DEK).
|
||||||
|
MetaKeyID = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id"
|
||||||
|
// MetaDataEncryptionKey is the sealed data encryption key (DEK) received from
|
||||||
|
// the KMS.
|
||||||
|
MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsMultiPart returns true if the object metadata indicates
|
// IsMultiPart returns true if the object metadata indicates
|
||||||
// that it was uploaded using some form of server-side-encryption
|
// that it was uploaded using some form of server-side-encryption
|
||||||
// and the S3 multipart API.
|
// and the S3 multipart API.
|
||||||
func IsMultiPart(metadata map[string]string) bool {
|
func IsMultiPart(metadata map[string]string) bool {
|
||||||
if _, ok := metadata[SSEMultipart]; ok {
|
if _, ok := metadata[MetaMultipart]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -37,8 +60,8 @@ func IsMultiPart(metadata map[string]string) bool {
|
|||||||
// information - e.g. the SSE-C key - from the metadata map.
|
// information - e.g. the SSE-C key - from the metadata map.
|
||||||
// It has the same semantics as RemoveSensitiveHeaders.
|
// It has the same semantics as RemoveSensitiveHeaders.
|
||||||
func RemoveSensitiveEntries(metadata map[string]string) { // The functions is tested in TestRemoveSensitiveHeaders for compatibility reasons
|
func RemoveSensitiveEntries(metadata map[string]string) { // The functions is tested in TestRemoveSensitiveHeaders for compatibility reasons
|
||||||
delete(metadata, SSECKey)
|
delete(metadata, xhttp.AmzServerSideEncryptionCustomerKey)
|
||||||
delete(metadata, SSECopyKey)
|
delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKey)
|
||||||
delete(metadata, xhttp.AmzMetaUnencryptedContentLength)
|
delete(metadata, xhttp.AmzMetaUnencryptedContentLength)
|
||||||
delete(metadata, xhttp.AmzMetaUnencryptedContentMD5)
|
delete(metadata, xhttp.AmzMetaUnencryptedContentMD5)
|
||||||
}
|
}
|
||||||
@@ -46,31 +69,36 @@ func RemoveSensitiveEntries(metadata map[string]string) { // The functions is te
|
|||||||
// RemoveSSEHeaders removes all crypto-specific SSE
|
// RemoveSSEHeaders removes all crypto-specific SSE
|
||||||
// header entries from the metadata map.
|
// header entries from the metadata map.
|
||||||
func RemoveSSEHeaders(metadata map[string]string) {
|
func RemoveSSEHeaders(metadata map[string]string) {
|
||||||
delete(metadata, SSEHeader)
|
delete(metadata, xhttp.AmzServerSideEncryption)
|
||||||
delete(metadata, SSEKmsID)
|
delete(metadata, xhttp.AmzServerSideEncryptionKmsID)
|
||||||
delete(metadata, SSEKmsContext)
|
delete(metadata, xhttp.AmzServerSideEncryptionKmsContext)
|
||||||
delete(metadata, SSECKeyMD5)
|
delete(metadata, xhttp.AmzServerSideEncryptionCustomerAlgorithm)
|
||||||
delete(metadata, SSECAlgorithm)
|
delete(metadata, xhttp.AmzServerSideEncryptionCustomerKey)
|
||||||
|
delete(metadata, xhttp.AmzServerSideEncryptionCustomerKeyMD5)
|
||||||
|
delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm)
|
||||||
|
delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKey)
|
||||||
|
delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveInternalEntries removes all crypto-specific internal
|
// RemoveInternalEntries removes all crypto-specific internal
|
||||||
// metadata entries from the metadata map.
|
// metadata entries from the metadata map.
|
||||||
func RemoveInternalEntries(metadata map[string]string) {
|
func RemoveInternalEntries(metadata map[string]string) {
|
||||||
delete(metadata, SSEMultipart)
|
delete(metadata, MetaMultipart)
|
||||||
delete(metadata, SSEIV)
|
delete(metadata, MetaAlgorithm)
|
||||||
delete(metadata, SSESealAlgorithm)
|
delete(metadata, MetaIV)
|
||||||
delete(metadata, SSECSealedKey)
|
delete(metadata, MetaSealedKeySSEC)
|
||||||
delete(metadata, S3SealedKey)
|
delete(metadata, MetaSealedKeyS3)
|
||||||
delete(metadata, S3KMSKeyID)
|
delete(metadata, MetaSealedKeyKMS)
|
||||||
delete(metadata, S3KMSSealedKey)
|
delete(metadata, MetaKeyID)
|
||||||
|
delete(metadata, MetaDataEncryptionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSourceEncrypted returns true if the source is encrypted
|
// IsSourceEncrypted returns true if the source is encrypted
|
||||||
func IsSourceEncrypted(metadata map[string]string) bool {
|
func IsSourceEncrypted(metadata map[string]string) bool {
|
||||||
if _, ok := metadata[SSECAlgorithm]; ok {
|
if _, ok := metadata[xhttp.AmzServerSideEncryptionCustomerAlgorithm]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := metadata[SSEHeader]; ok {
|
if _, ok := metadata[xhttp.AmzServerSideEncryption]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -82,10 +110,10 @@ func IsSourceEncrypted(metadata map[string]string) bool {
|
|||||||
// IsEncrypted only checks whether the metadata contains at least
|
// IsEncrypted only checks whether the metadata contains at least
|
||||||
// one entry indicating SSE-C or SSE-S3.
|
// one entry indicating SSE-C or SSE-S3.
|
||||||
func IsEncrypted(metadata map[string]string) bool {
|
func IsEncrypted(metadata map[string]string) bool {
|
||||||
if _, ok := metadata[SSEIV]; ok {
|
if _, ok := metadata[MetaIV]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := metadata[SSESealAlgorithm]; ok {
|
if _, ok := metadata[MetaAlgorithm]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if IsMultiPart(metadata) {
|
if IsMultiPart(metadata) {
|
||||||
@@ -97,28 +125,7 @@ func IsEncrypted(metadata map[string]string) bool {
|
|||||||
if SSEC.IsEncrypted(metadata) {
|
if SSEC.IsEncrypted(metadata) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
if S3KMS.IsEncrypted(metadata) {
|
||||||
}
|
|
||||||
|
|
||||||
// IsEncrypted returns true if the object metadata indicates
|
|
||||||
// that the object was uploaded using SSE-S3.
|
|
||||||
func (s3) IsEncrypted(metadata map[string]string) bool {
|
|
||||||
if _, ok := metadata[S3SealedKey]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := metadata[S3KMSKeyID]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := metadata[S3KMSSealedKey]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEncrypted returns true if the object metadata indicates
|
|
||||||
// that the object was uploaded using SSE-C.
|
|
||||||
func (ssec) IsEncrypted(metadata map[string]string) bool {
|
|
||||||
if _, ok := metadata[SSECSealedKey]; ok {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -129,159 +136,11 @@ func (ssec) IsEncrypted(metadata map[string]string) bool {
|
|||||||
// metadata is nil.
|
// metadata is nil.
|
||||||
func CreateMultipartMetadata(metadata map[string]string) map[string]string {
|
func CreateMultipartMetadata(metadata map[string]string) map[string]string {
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
return map[string]string{SSEMultipart: ""}
|
return map[string]string{MetaMultipart: ""}
|
||||||
}
|
}
|
||||||
metadata[SSEMultipart] = ""
|
metadata[MetaMultipart] = ""
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMetadata encodes the sealed object key into the metadata and returns
|
|
||||||
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
|
|
||||||
// both into the metadata as well. It allocates a new metadata map if metadata
|
|
||||||
// is nil.
|
|
||||||
func (s3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
|
|
||||||
if sealedKey.Algorithm != SealAlgorithm {
|
|
||||||
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are two possibilites:
|
|
||||||
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
|
|
||||||
// - We use a K/V -> There must be no key ID and no KMS data key.
|
|
||||||
// Otherwise, the caller has passed an invalid argument combination.
|
|
||||||
if keyID == "" && len(kmsKey) != 0 {
|
|
||||||
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
|
|
||||||
}
|
|
||||||
if keyID != "" && len(kmsKey) == 0 {
|
|
||||||
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata == nil {
|
|
||||||
metadata = make(map[string]string, 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata[SSESealAlgorithm] = sealedKey.Algorithm
|
|
||||||
metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
|
||||||
metadata[S3SealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
|
||||||
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
|
|
||||||
metadata[S3KMSKeyID] = keyID
|
|
||||||
metadata[S3KMSSealedKey] = base64.StdEncoding.EncodeToString(kmsKey)
|
|
||||||
}
|
|
||||||
return metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseMetadata extracts all SSE-S3 related values from the object metadata
|
|
||||||
// and checks whether they are well-formed. It returns the sealed object key
|
|
||||||
// on success. If the metadata contains both, a KMS master key ID and a sealed
|
|
||||||
// KMS data key it returns both. If the metadata does not contain neither a
|
|
||||||
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
|
|
||||||
// KMS data key. Otherwise, it returns an error.
|
|
||||||
func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
|
|
||||||
// Extract all required values from object metadata
|
|
||||||
b64IV, ok := metadata[SSEIV]
|
|
||||||
if !ok {
|
|
||||||
return keyID, kmsKey, sealedKey, errMissingInternalIV
|
|
||||||
}
|
|
||||||
algorithm, ok := metadata[SSESealAlgorithm]
|
|
||||||
if !ok {
|
|
||||||
return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
|
|
||||||
}
|
|
||||||
b64SealedKey, ok := metadata[S3SealedKey]
|
|
||||||
if !ok {
|
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are two possibilites:
|
|
||||||
// - We use a KMS -> There must be a key ID and a KMS data key.
|
|
||||||
// - We use a K/V -> There must be no key ID and no KMS data key.
|
|
||||||
// Otherwise, the metadata is corrupted.
|
|
||||||
keyID, idPresent := metadata[S3KMSKeyID]
|
|
||||||
b64KMSSealedKey, kmsKeyPresent := metadata[S3KMSSealedKey]
|
|
||||||
if !idPresent && kmsKeyPresent {
|
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
|
|
||||||
}
|
|
||||||
if idPresent && !kmsKeyPresent {
|
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether all extracted values are well-formed
|
|
||||||
var iv [32]byte
|
|
||||||
n, err := base64.StdEncoding.Decode(iv[:], []byte(b64IV))
|
|
||||||
if err != nil || n != 32 {
|
|
||||||
return keyID, kmsKey, sealedKey, errInvalidInternalIV
|
|
||||||
}
|
|
||||||
if algorithm != SealAlgorithm {
|
|
||||||
return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
|
|
||||||
}
|
|
||||||
var encryptedKey [64]byte
|
|
||||||
n, err = base64.StdEncoding.Decode(encryptedKey[:], []byte(b64SealedKey))
|
|
||||||
if err != nil || n != 64 {
|
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
|
|
||||||
}
|
|
||||||
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
|
|
||||||
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
|
|
||||||
if err != nil {
|
|
||||||
return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealedKey.Algorithm = algorithm
|
|
||||||
sealedKey.IV = iv
|
|
||||||
sealedKey.Key = encryptedKey
|
|
||||||
return keyID, kmsKey, sealedKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMetadata encodes the sealed key into the metadata and returns the modified metadata.
|
|
||||||
// It allocates a new metadata map if metadata is nil.
|
|
||||||
func (ssec) CreateMetadata(metadata map[string]string, sealedKey SealedKey) map[string]string {
|
|
||||||
if sealedKey.Algorithm != SealAlgorithm {
|
|
||||||
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-C", sealedKey.Algorithm))
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata == nil {
|
|
||||||
metadata = make(map[string]string, 3)
|
|
||||||
}
|
|
||||||
metadata[SSESealAlgorithm] = SealAlgorithm
|
|
||||||
metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
|
||||||
metadata[SSECSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
|
||||||
return metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseMetadata extracts all SSE-C related values from the object metadata
|
|
||||||
// and checks whether they are well-formed. It returns the sealed object key
|
|
||||||
// on success.
|
|
||||||
func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err error) {
|
|
||||||
// Extract all required values from object metadata
|
|
||||||
b64IV, ok := metadata[SSEIV]
|
|
||||||
if !ok {
|
|
||||||
return sealedKey, errMissingInternalIV
|
|
||||||
}
|
|
||||||
algorithm, ok := metadata[SSESealAlgorithm]
|
|
||||||
if !ok {
|
|
||||||
return sealedKey, errMissingInternalSealAlgorithm
|
|
||||||
}
|
|
||||||
b64SealedKey, ok := metadata[SSECSealedKey]
|
|
||||||
if !ok {
|
|
||||||
return sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-C")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether all extracted values are well-formed
|
|
||||||
iv, err := base64.StdEncoding.DecodeString(b64IV)
|
|
||||||
if err != nil || len(iv) != 32 {
|
|
||||||
return sealedKey, errInvalidInternalIV
|
|
||||||
}
|
|
||||||
if algorithm != SealAlgorithm && algorithm != InsecureSealAlgorithm {
|
|
||||||
return sealedKey, errInvalidInternalSealAlgorithm
|
|
||||||
}
|
|
||||||
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
|
|
||||||
if err != nil || len(encryptedKey) != 64 {
|
|
||||||
return sealedKey, Errorf("The internal sealed key for SSE-C is invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
sealedKey.Algorithm = algorithm
|
|
||||||
copy(sealedKey.IV[:], iv)
|
|
||||||
copy(sealedKey.Key[:], encryptedKey)
|
|
||||||
return sealedKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsETagSealed returns true if the etag seems to be encrypted.
|
// IsETagSealed returns true if the etag seems to be encrypted.
|
||||||
func IsETagSealed(etag []byte) bool { return len(etag) > 16 }
|
func IsETagSealed(etag []byte) bool { return len(etag) > 16 }
|
||||||
|
|||||||
+59
-59
@@ -27,9 +27,9 @@ var isMultipartTests = []struct {
|
|||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
Multipart bool
|
Multipart bool
|
||||||
}{
|
}{
|
||||||
{Multipart: true, Metadata: map[string]string{SSEMultipart: ""}}, // 0
|
{Multipart: true, Metadata: map[string]string{MetaMultipart: ""}}, // 0
|
||||||
{Multipart: true, Metadata: map[string]string{"X-Minio-Internal-Encrypted-Multipart": ""}}, // 1
|
{Multipart: true, Metadata: map[string]string{"X-Minio-Internal-Encrypted-Multipart": ""}}, // 1
|
||||||
{Multipart: true, Metadata: map[string]string{SSEMultipart: "some-value"}}, // 2
|
{Multipart: true, Metadata: map[string]string{MetaMultipart: "some-value"}}, // 2
|
||||||
{Multipart: false, Metadata: map[string]string{"": ""}}, // 3
|
{Multipart: false, Metadata: map[string]string{"": ""}}, // 3
|
||||||
{Multipart: false, Metadata: map[string]string{"X-Minio-Internal-EncryptedMultipart": ""}}, // 4
|
{Multipart: false, Metadata: map[string]string{"X-Minio-Internal-EncryptedMultipart": ""}}, // 4
|
||||||
}
|
}
|
||||||
@@ -46,13 +46,13 @@ var isEncryptedTests = []struct {
|
|||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
Encrypted bool
|
Encrypted bool
|
||||||
}{
|
}{
|
||||||
{Encrypted: true, Metadata: map[string]string{SSEMultipart: ""}}, // 0
|
{Encrypted: true, Metadata: map[string]string{MetaMultipart: ""}}, // 0
|
||||||
{Encrypted: true, Metadata: map[string]string{SSEIV: ""}}, // 1
|
{Encrypted: true, Metadata: map[string]string{MetaIV: ""}}, // 1
|
||||||
{Encrypted: true, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2
|
{Encrypted: true, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
|
||||||
{Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3
|
{Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
|
||||||
{Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4
|
{Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
|
||||||
{Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5
|
{Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5
|
||||||
{Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6
|
{Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
|
||||||
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
||||||
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
||||||
}
|
}
|
||||||
@@ -69,13 +69,13 @@ var s3IsEncryptedTests = []struct {
|
|||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
Encrypted bool
|
Encrypted bool
|
||||||
}{
|
}{
|
||||||
{Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0
|
{Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0
|
||||||
{Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1
|
{Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1
|
||||||
{Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2
|
{Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
|
||||||
{Encrypted: false, Metadata: map[string]string{SSECSealedKey: ""}}, // 3
|
{Encrypted: false, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
|
||||||
{Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4
|
{Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
|
||||||
{Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5
|
{Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5
|
||||||
{Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6
|
{Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
|
||||||
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
||||||
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
||||||
}
|
}
|
||||||
@@ -92,13 +92,13 @@ var ssecIsEncryptedTests = []struct {
|
|||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
Encrypted bool
|
Encrypted bool
|
||||||
}{
|
}{
|
||||||
{Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0
|
{Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0
|
||||||
{Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1
|
{Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1
|
||||||
{Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2
|
{Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
|
||||||
{Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3
|
{Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
|
||||||
{Encrypted: false, Metadata: map[string]string{S3SealedKey: ""}}, // 4
|
{Encrypted: false, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
|
||||||
{Encrypted: false, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5
|
{Encrypted: false, Metadata: map[string]string{MetaKeyID: ""}}, // 5
|
||||||
{Encrypted: false, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6
|
{Encrypted: false, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
|
||||||
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
|
||||||
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
|
||||||
}
|
}
|
||||||
@@ -121,65 +121,65 @@ var s3ParseMetadataTests = []struct {
|
|||||||
}{
|
}{
|
||||||
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}}, // 0
|
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}}, // 0
|
||||||
{
|
{
|
||||||
ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""},
|
ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{MetaIV: ""},
|
||||||
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
||||||
}, // 1
|
}, // 1
|
||||||
{
|
{
|
||||||
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-S3"),
|
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-S3"),
|
||||||
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
Metadata: map[string]string{MetaIV: "", MetaAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
||||||
}, // 2
|
}, // 2
|
||||||
{
|
{
|
||||||
ExpectedErr: Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3"),
|
ExpectedErr: Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3"),
|
||||||
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSSealedKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaDataEncryptionKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
||||||
}, // 3
|
}, // 3
|
||||||
{
|
{
|
||||||
ExpectedErr: Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3"),
|
ExpectedErr: Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3"),
|
||||||
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: ""},
|
Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: ""},
|
||||||
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
||||||
}, // 4
|
}, // 4
|
||||||
{
|
{
|
||||||
ExpectedErr: errInvalidInternalIV,
|
ExpectedErr: errInvalidInternalIV,
|
||||||
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: "", S3KMSSealedKey: ""},
|
Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: "", MetaDataEncryptionKey: ""},
|
||||||
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
||||||
}, // 5
|
}, // 5
|
||||||
{
|
{
|
||||||
ExpectedErr: errInvalidInternalSealAlgorithm,
|
ExpectedErr: errInvalidInternalSealAlgorithm,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: "", S3KMSSealedKey: "",
|
MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: "", MetaDataEncryptionKey: "",
|
||||||
},
|
},
|
||||||
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
||||||
}, // 6
|
}, // 6
|
||||||
{
|
{
|
||||||
ExpectedErr: Errorf("The internal sealed key for SSE-S3 is invalid"),
|
ExpectedErr: Errorf("The internal sealed key for SSE-S3 is invalid"),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, S3SealedKey: "",
|
MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, MetaSealedKeyS3: "",
|
||||||
S3KMSKeyID: "", S3KMSSealedKey: "",
|
MetaKeyID: "", MetaDataEncryptionKey: "",
|
||||||
},
|
},
|
||||||
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
|
||||||
}, // 7
|
}, // 7
|
||||||
{
|
{
|
||||||
ExpectedErr: Errorf("The internal sealed KMS data key for SSE-S3 is invalid"),
|
ExpectedErr: Errorf("The internal sealed KMS data key for SSE-S3 is invalid"),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm,
|
MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
|
||||||
S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "key-1",
|
MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "key-1",
|
||||||
S3KMSSealedKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64
|
MetaDataEncryptionKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64
|
||||||
},
|
},
|
||||||
DataKey: []byte{}, KeyID: "key-1", SealedKey: SealedKey{},
|
DataKey: []byte{}, KeyID: "key-1", SealedKey: SealedKey{},
|
||||||
}, // 8
|
}, // 8
|
||||||
{
|
{
|
||||||
ExpectedErr: nil,
|
ExpectedErr: nil,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm,
|
MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
|
||||||
S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "", S3KMSSealedKey: "",
|
MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "", MetaDataEncryptionKey: "",
|
||||||
},
|
},
|
||||||
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{Algorithm: SealAlgorithm},
|
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{Algorithm: SealAlgorithm},
|
||||||
}, // 9
|
}, // 9
|
||||||
{
|
{
|
||||||
ExpectedErr: nil,
|
ExpectedErr: nil,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: SealAlgorithm,
|
MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: SealAlgorithm,
|
||||||
S3SealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), S3KMSKeyID: "key-1",
|
MetaSealedKeyS3: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), MetaKeyID: "key-1",
|
||||||
S3KMSSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 48)),
|
MetaDataEncryptionKey: base64.StdEncoding.EncodeToString(make([]byte, 48)),
|
||||||
},
|
},
|
||||||
DataKey: make([]byte, 48), KeyID: "key-1", SealedKey: SealedKey{Algorithm: SealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
|
DataKey: make([]byte, 48), KeyID: "key-1", SealedKey: SealedKey{Algorithm: SealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
|
||||||
}, // 10
|
}, // 10
|
||||||
@@ -224,42 +224,42 @@ var ssecParseMetadataTests = []struct {
|
|||||||
SealedKey SealedKey
|
SealedKey SealedKey
|
||||||
}{
|
}{
|
||||||
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0
|
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0
|
||||||
{ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, SealedKey: SealedKey{}}, // 1
|
{ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{MetaIV: ""}, SealedKey: SealedKey{}}, // 1
|
||||||
{
|
{
|
||||||
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-C"),
|
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-C"),
|
||||||
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, SealedKey: SealedKey{},
|
Metadata: map[string]string{MetaIV: "", MetaAlgorithm: ""}, SealedKey: SealedKey{},
|
||||||
}, // 2
|
}, // 2
|
||||||
{
|
{
|
||||||
ExpectedErr: errInvalidInternalIV,
|
ExpectedErr: errInvalidInternalIV,
|
||||||
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", SSECSealedKey: ""}, SealedKey: SealedKey{},
|
Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeySSEC: ""}, SealedKey: SealedKey{},
|
||||||
}, // 3
|
}, // 3
|
||||||
{
|
{
|
||||||
ExpectedErr: errInvalidInternalSealAlgorithm,
|
ExpectedErr: errInvalidInternalSealAlgorithm,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: "", SSECSealedKey: "",
|
MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: "", MetaSealedKeySSEC: "",
|
||||||
},
|
},
|
||||||
SealedKey: SealedKey{},
|
SealedKey: SealedKey{},
|
||||||
}, // 4
|
}, // 4
|
||||||
{
|
{
|
||||||
ExpectedErr: Errorf("The internal sealed key for SSE-C is invalid"),
|
ExpectedErr: Errorf("The internal sealed key for SSE-C is invalid"),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, SSECSealedKey: "",
|
MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, MetaSealedKeySSEC: "",
|
||||||
},
|
},
|
||||||
SealedKey: SealedKey{},
|
SealedKey: SealedKey{},
|
||||||
}, // 5
|
}, // 5
|
||||||
{
|
{
|
||||||
ExpectedErr: nil,
|
ExpectedErr: nil,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm,
|
MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
|
||||||
SSECSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)),
|
MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(make([]byte, 64)),
|
||||||
},
|
},
|
||||||
SealedKey: SealedKey{Algorithm: SealAlgorithm},
|
SealedKey: SealedKey{Algorithm: SealAlgorithm},
|
||||||
}, // 6
|
}, // 6
|
||||||
{
|
{
|
||||||
ExpectedErr: nil,
|
ExpectedErr: nil,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: InsecureSealAlgorithm,
|
MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: InsecureSealAlgorithm,
|
||||||
SSECSealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)),
|
MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)),
|
||||||
},
|
},
|
||||||
SealedKey: SealedKey{Algorithm: InsecureSealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
|
SealedKey: SealedKey{Algorithm: InsecureSealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
|
||||||
}, // 7
|
}, // 7
|
||||||
@@ -267,8 +267,8 @@ var ssecParseMetadataTests = []struct {
|
|||||||
|
|
||||||
func TestCreateMultipartMetadata(t *testing.T) {
|
func TestCreateMultipartMetadata(t *testing.T) {
|
||||||
metadata := CreateMultipartMetadata(nil)
|
metadata := CreateMultipartMetadata(nil)
|
||||||
if v, ok := metadata[SSEMultipart]; !ok || v != "" {
|
if v, ok := metadata[MetaMultipart]; !ok || v != "" {
|
||||||
t.Errorf("Metadata is missing the correct value for '%s': got '%s' - want '%s'", SSEMultipart, v, "")
|
t.Errorf("Metadata is missing the correct value for '%s': got '%s' - want '%s'", MetaMultipart, v, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,20 +411,20 @@ var removeInternalEntriesTests = []struct {
|
|||||||
}{
|
}{
|
||||||
{ // 0
|
{ // 0
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEMultipart: "",
|
MetaMultipart: "",
|
||||||
SSEIV: "",
|
MetaIV: "",
|
||||||
SSESealAlgorithm: "",
|
MetaAlgorithm: "",
|
||||||
SSECSealedKey: "",
|
MetaSealedKeySSEC: "",
|
||||||
S3SealedKey: "",
|
MetaSealedKeyS3: "",
|
||||||
S3KMSKeyID: "",
|
MetaKeyID: "",
|
||||||
S3KMSSealedKey: "",
|
MetaDataEncryptionKey: "",
|
||||||
},
|
},
|
||||||
Expected: map[string]string{},
|
Expected: map[string]string{},
|
||||||
},
|
},
|
||||||
{ // 1
|
{ // 1
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
SSEMultipart: "",
|
MetaMultipart: "",
|
||||||
SSEIV: "",
|
MetaIV: "",
|
||||||
"X-Amz-Meta-A": "X",
|
"X-Amz-Meta-A": "X",
|
||||||
"X-Minio-Internal-B": "Y",
|
"X-Minio-Internal-B": "Y",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 2019-2020 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ssec struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// SSEC represents AWS SSE-C. It provides functionality to handle
|
||||||
|
// SSE-C requests.
|
||||||
|
SSEC = ssec{}
|
||||||
|
|
||||||
|
_ Type = SSEC
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the SSE domain as string. For SSE-C the
|
||||||
|
// domain is "SSE-C".
|
||||||
|
func (ssec) String() string { return "SSE-C" }
|
||||||
|
|
||||||
|
// IsRequested returns true if the HTTP headers contains
|
||||||
|
// at least one SSE-C header. SSE-C copy headers are ignored.
|
||||||
|
func (ssec) IsRequested(h http.Header) bool {
|
||||||
|
if _, ok := h[xhttp.AmzServerSideEncryptionCustomerAlgorithm]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := h[xhttp.AmzServerSideEncryptionCustomerKey]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := h[xhttp.AmzServerSideEncryptionCustomerKeyMD5]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEncrypted returns true if the metadata contains an SSE-C
|
||||||
|
// entry inidicating that the object has been encrypted using
|
||||||
|
// SSE-C.
|
||||||
|
func (ssec) IsEncrypted(metadata map[string]string) bool {
|
||||||
|
if _, ok := metadata[MetaSealedKeySSEC]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHTTP parses the SSE-C headers and returns the SSE-C client key
|
||||||
|
// on success. SSE-C copy headers are ignored.
|
||||||
|
func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) {
|
||||||
|
if h.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm) != xhttp.AmzEncryptionAES {
|
||||||
|
return key, ErrInvalidCustomerAlgorithm
|
||||||
|
}
|
||||||
|
if h.Get(xhttp.AmzServerSideEncryptionCustomerKey) == "" {
|
||||||
|
return key, ErrMissingCustomerKey
|
||||||
|
}
|
||||||
|
if h.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5) == "" {
|
||||||
|
return key, ErrMissingCustomerKeyMD5
|
||||||
|
}
|
||||||
|
|
||||||
|
clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCustomerKey))
|
||||||
|
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
|
||||||
|
return key, ErrInvalidCustomerKey
|
||||||
|
}
|
||||||
|
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||||
|
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
|
||||||
|
return key, ErrCustomerKeyMD5Mismatch
|
||||||
|
}
|
||||||
|
copy(key[:], clientKey)
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsealObjectKey extracts and decrypts the sealed object key
|
||||||
|
// from the metadata using the SSE-C client key of the HTTP headers
|
||||||
|
// and returns the decrypted object key.
|
||||||
|
func (s3 ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
|
||||||
|
clientKey, err := s3.ParseHTTP(h)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return unsealObjectKey(clientKey, metadata, bucket, object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMetadata encodes the sealed key into the metadata
|
||||||
|
// and returns the modified metadata. It allocates a new
|
||||||
|
// metadata map if metadata is nil.
|
||||||
|
func (ssec) CreateMetadata(metadata map[string]string, sealedKey SealedKey) map[string]string {
|
||||||
|
if sealedKey.Algorithm != SealAlgorithm {
|
||||||
|
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-C", sealedKey.Algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata == nil {
|
||||||
|
metadata = make(map[string]string, 3)
|
||||||
|
}
|
||||||
|
metadata[MetaAlgorithm] = SealAlgorithm
|
||||||
|
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
||||||
|
metadata[MetaSealedKeySSEC] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMetadata extracts all SSE-C related values from the object metadata
|
||||||
|
// and checks whether they are well-formed. It returns the sealed object key
|
||||||
|
// on success.
|
||||||
|
func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err error) {
|
||||||
|
// Extract all required values from object metadata
|
||||||
|
b64IV, ok := metadata[MetaIV]
|
||||||
|
if !ok {
|
||||||
|
return sealedKey, errMissingInternalIV
|
||||||
|
}
|
||||||
|
algorithm, ok := metadata[MetaAlgorithm]
|
||||||
|
if !ok {
|
||||||
|
return sealedKey, errMissingInternalSealAlgorithm
|
||||||
|
}
|
||||||
|
b64SealedKey, ok := metadata[MetaSealedKeySSEC]
|
||||||
|
if !ok {
|
||||||
|
return sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-C")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether all extracted values are well-formed
|
||||||
|
iv, err := base64.StdEncoding.DecodeString(b64IV)
|
||||||
|
if err != nil || len(iv) != 32 {
|
||||||
|
return sealedKey, errInvalidInternalIV
|
||||||
|
}
|
||||||
|
if algorithm != SealAlgorithm && algorithm != InsecureSealAlgorithm {
|
||||||
|
return sealedKey, errInvalidInternalSealAlgorithm
|
||||||
|
}
|
||||||
|
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
|
||||||
|
if err != nil || len(encryptedKey) != 64 {
|
||||||
|
return sealedKey, Errorf("The internal sealed key for SSE-C is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
sealedKey.Algorithm = algorithm
|
||||||
|
copy(sealedKey.IV[:], iv)
|
||||||
|
copy(sealedKey.Key[:], encryptedKey)
|
||||||
|
return sealedKey, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 2019-2020 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ssekms struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// S3KMS represents AWS SSE-KMS. It provides functionality to
|
||||||
|
// handle SSE-KMS requests.
|
||||||
|
S3KMS = ssekms{}
|
||||||
|
|
||||||
|
_ Type = S3KMS
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the SSE domain as string. For SSE-KMS the
|
||||||
|
// domain is "SSE-KMS".
|
||||||
|
func (ssekms) String() string { return "SSE-KMS" }
|
||||||
|
|
||||||
|
// IsRequested returns true if the HTTP headers contains
|
||||||
|
// at least one SSE-KMS header.
|
||||||
|
func (ssekms) IsRequested(h http.Header) bool {
|
||||||
|
if _, ok := h[xhttp.AmzServerSideEncryptionKmsID]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := h[xhttp.AmzServerSideEncryption]; ok {
|
||||||
|
return strings.ToUpper(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionAES // Return only true if the SSE header is specified and does not contain the SSE-S3 value
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID
|
||||||
|
// and the KMS context on success.
|
||||||
|
func (ssekms) ParseHTTP(h http.Header) (string, Context, error) {
|
||||||
|
algorithm := h.Get(xhttp.AmzServerSideEncryption)
|
||||||
|
if algorithm != xhttp.AmzEncryptionKMS {
|
||||||
|
return "", nil, ErrInvalidEncryptionMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctx Context
|
||||||
|
if context, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok {
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
if err := json.Unmarshal([]byte(context[0]), &ctx); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h.Get(xhttp.AmzServerSideEncryptionKmsID), ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEncrypted returns true if the object metadata indicates
|
||||||
|
// that the object was uploaded using SSE-KMS.
|
||||||
|
func (ssekms) IsEncrypted(metadata map[string]string) bool {
|
||||||
|
if _, ok := metadata[MetaSealedKeyKMS]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaKeyID]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaDataEncryptionKey]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsealObjectKey extracts and decrypts the sealed object key
|
||||||
|
// from the metadata using KMS and returns the decrypted object
|
||||||
|
// key.
|
||||||
|
func (s3 ssekms) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
|
||||||
|
keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
|
||||||
|
if err != nil {
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
err = key.Unseal(unsealKey, sealedKey, s3.String(), bucket, object)
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMetadata encodes the sealed object key into the metadata and returns
|
||||||
|
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
|
||||||
|
// both into the metadata as well. It allocates a new metadata map if metadata
|
||||||
|
// is nil.
|
||||||
|
func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
|
||||||
|
if sealedKey.Algorithm != SealAlgorithm {
|
||||||
|
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two possibilites:
|
||||||
|
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
|
||||||
|
// - We use a K/V -> There must be no key ID and no KMS data key.
|
||||||
|
// Otherwise, the caller has passed an invalid argument combination.
|
||||||
|
if keyID == "" && len(kmsKey) != 0 {
|
||||||
|
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
|
||||||
|
}
|
||||||
|
if keyID != "" && len(kmsKey) == 0 {
|
||||||
|
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata == nil {
|
||||||
|
metadata = make(map[string]string, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata[MetaAlgorithm] = sealedKey.Algorithm
|
||||||
|
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
||||||
|
metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
||||||
|
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
|
||||||
|
metadata[MetaKeyID] = keyID
|
||||||
|
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMetadata extracts all SSE-KMS related values from the object metadata
|
||||||
|
// and checks whether they are well-formed. It returns the sealed object key
|
||||||
|
// on success. If the metadata contains both, a KMS master key ID and a sealed
|
||||||
|
// KMS data key it returns both. If the metadata does not contain neither a
|
||||||
|
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
|
||||||
|
// KMS data key. Otherwise, it returns an error.
|
||||||
|
func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
|
||||||
|
// Extract all required values from object metadata
|
||||||
|
b64IV, ok := metadata[MetaIV]
|
||||||
|
if !ok {
|
||||||
|
return keyID, kmsKey, sealedKey, errMissingInternalIV
|
||||||
|
}
|
||||||
|
algorithm, ok := metadata[MetaAlgorithm]
|
||||||
|
if !ok {
|
||||||
|
return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
|
||||||
|
}
|
||||||
|
b64SealedKey, ok := metadata[MetaSealedKeyKMS]
|
||||||
|
if !ok {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two possibilites:
|
||||||
|
// - We use a KMS -> There must be a key ID and a KMS data key.
|
||||||
|
// - We use a K/V -> There must be no key ID and no KMS data key.
|
||||||
|
// Otherwise, the metadata is corrupted.
|
||||||
|
keyID, idPresent := metadata[MetaKeyID]
|
||||||
|
b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey]
|
||||||
|
if !idPresent && kmsKeyPresent {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
|
||||||
|
}
|
||||||
|
if idPresent && !kmsKeyPresent {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether all extracted values are well-formed
|
||||||
|
iv, err := base64.StdEncoding.DecodeString(b64IV)
|
||||||
|
if err != nil || len(iv) != 32 {
|
||||||
|
return keyID, kmsKey, sealedKey, errInvalidInternalIV
|
||||||
|
}
|
||||||
|
if algorithm != SealAlgorithm {
|
||||||
|
return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
|
||||||
|
}
|
||||||
|
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
|
||||||
|
if err != nil || len(encryptedKey) != 64 {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
|
||||||
|
}
|
||||||
|
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
|
||||||
|
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
|
||||||
|
if err != nil {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealedKey.Algorithm = algorithm
|
||||||
|
copy(sealedKey.IV[:], iv)
|
||||||
|
copy(sealedKey.Key[:], encryptedKey)
|
||||||
|
return keyID, kmsKey, sealedKey, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 2019-2020 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sses3 struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// S3 represents AWS SSE-S3. It provides functionality to handle
|
||||||
|
// SSE-S3 requests.
|
||||||
|
S3 = sses3{}
|
||||||
|
|
||||||
|
_ Type = S3
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the SSE domain as string. For SSE-S3 the
|
||||||
|
// domain is "SSE-S3".
|
||||||
|
func (sses3) String() string { return "SSE-S3" }
|
||||||
|
|
||||||
|
func (sses3) IsRequested(h http.Header) bool {
|
||||||
|
_, ok := h[xhttp.AmzServerSideEncryption]
|
||||||
|
return ok && strings.ToLower(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHTTP parses the SSE-S3 related HTTP headers and checks
|
||||||
|
// whether they contain valid values.
|
||||||
|
func (sses3) ParseHTTP(h http.Header) error {
|
||||||
|
if h.Get(xhttp.AmzServerSideEncryption) != xhttp.AmzEncryptionAES {
|
||||||
|
return ErrInvalidEncryptionMethod
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEncrypted returns true if the object metadata indicates
|
||||||
|
// that the object was uploaded using SSE-S3.
|
||||||
|
func (sses3) IsEncrypted(metadata map[string]string) bool {
|
||||||
|
if _, ok := metadata[MetaSealedKeyS3]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaKeyID]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := metadata[MetaDataEncryptionKey]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsealObjectKey extracts and decrypts the sealed object key
|
||||||
|
// from the metadata using KMS and returns the decrypted object
|
||||||
|
// key.
|
||||||
|
func (s3 sses3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
|
||||||
|
keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
|
||||||
|
if err != nil {
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
err = key.Unseal(unsealKey, sealedKey, s3.String(), bucket, object)
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMetadata encodes the sealed object key into the metadata and returns
|
||||||
|
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
|
||||||
|
// both into the metadata as well. It allocates a new metadata map if metadata
|
||||||
|
// is nil.
|
||||||
|
func (sses3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
|
||||||
|
if sealedKey.Algorithm != SealAlgorithm {
|
||||||
|
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two possibilites:
|
||||||
|
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
|
||||||
|
// - We use a K/V -> There must be no key ID and no KMS data key.
|
||||||
|
// Otherwise, the caller has passed an invalid argument combination.
|
||||||
|
if keyID == "" && len(kmsKey) != 0 {
|
||||||
|
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
|
||||||
|
}
|
||||||
|
if keyID != "" && len(kmsKey) == 0 {
|
||||||
|
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata == nil {
|
||||||
|
metadata = make(map[string]string, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata[MetaAlgorithm] = sealedKey.Algorithm
|
||||||
|
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
||||||
|
metadata[MetaSealedKeyS3] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
||||||
|
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
|
||||||
|
metadata[MetaKeyID] = keyID
|
||||||
|
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMetadata extracts all SSE-S3 related values from the object metadata
|
||||||
|
// and checks whether they are well-formed. It returns the sealed object key
|
||||||
|
// on success. If the metadata contains both, a KMS master key ID and a sealed
|
||||||
|
// KMS data key it returns both. If the metadata does not contain neither a
|
||||||
|
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
|
||||||
|
// KMS data key. Otherwise, it returns an error.
|
||||||
|
func (sses3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
|
||||||
|
// Extract all required values from object metadata
|
||||||
|
b64IV, ok := metadata[MetaIV]
|
||||||
|
if !ok {
|
||||||
|
return keyID, kmsKey, sealedKey, errMissingInternalIV
|
||||||
|
}
|
||||||
|
algorithm, ok := metadata[MetaAlgorithm]
|
||||||
|
if !ok {
|
||||||
|
return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
|
||||||
|
}
|
||||||
|
b64SealedKey, ok := metadata[MetaSealedKeyS3]
|
||||||
|
if !ok {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two possibilites:
|
||||||
|
// - We use a KMS -> There must be a key ID and a KMS data key.
|
||||||
|
// - We use a K/V -> There must be no key ID and no KMS data key.
|
||||||
|
// Otherwise, the metadata is corrupted.
|
||||||
|
keyID, idPresent := metadata[MetaKeyID]
|
||||||
|
b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey]
|
||||||
|
if !idPresent && kmsKeyPresent {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
|
||||||
|
}
|
||||||
|
if idPresent && !kmsKeyPresent {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether all extracted values are well-formed
|
||||||
|
iv, err := base64.StdEncoding.DecodeString(b64IV)
|
||||||
|
if err != nil || len(iv) != 32 {
|
||||||
|
return keyID, kmsKey, sealedKey, errInvalidInternalIV
|
||||||
|
}
|
||||||
|
if algorithm != SealAlgorithm {
|
||||||
|
return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
|
||||||
|
}
|
||||||
|
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
|
||||||
|
if err != nil || len(encryptedKey) != 64 {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
|
||||||
|
}
|
||||||
|
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
|
||||||
|
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
|
||||||
|
if err != nil {
|
||||||
|
return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealedKey.Algorithm = algorithm
|
||||||
|
copy(sealedKey.IV[:], iv)
|
||||||
|
copy(sealedKey.Key[:], encryptedKey)
|
||||||
|
return keyID, kmsKey, sealedKey, nil
|
||||||
|
}
|
||||||
+25
-59
@@ -17,44 +17,15 @@ package crypto
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/ioutil"
|
"github.com/minio/minio/pkg/ioutil"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// SSEMultipart is the metadata key indicating that the object
|
|
||||||
// was uploaded using the S3 multipart API and stored using
|
|
||||||
// some from of server-side-encryption.
|
|
||||||
SSEMultipart = "X-Minio-Internal-Encrypted-Multipart"
|
|
||||||
|
|
||||||
// SSEIV is the metadata key referencing the random initialization
|
|
||||||
// vector (IV) used for SSE-S3 and SSE-C key derivation.
|
|
||||||
SSEIV = "X-Minio-Internal-Server-Side-Encryption-Iv"
|
|
||||||
|
|
||||||
// SSESealAlgorithm is the metadata key referencing the algorithm
|
|
||||||
// used by SSE-C and SSE-S3 to encrypt the object.
|
|
||||||
SSESealAlgorithm = "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm"
|
|
||||||
|
|
||||||
// SSECSealedKey is the metadata key referencing the sealed object-key for SSE-C.
|
|
||||||
SSECSealedKey = "X-Minio-Internal-Server-Side-Encryption-Sealed-Key"
|
|
||||||
|
|
||||||
// S3SealedKey is the metadata key referencing the sealed object-key for SSE-S3.
|
|
||||||
S3SealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key"
|
|
||||||
|
|
||||||
// S3KMSKeyID is the metadata key referencing the KMS key-id used to
|
|
||||||
// generate/decrypt the S3-KMS-Sealed-Key. It is only used for SSE-S3 + KMS.
|
|
||||||
S3KMSKeyID = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id"
|
|
||||||
|
|
||||||
// S3KMSSealedKey is the metadata key referencing the encrypted key generated
|
|
||||||
// by KMS. It is only used for SSE-S3 + KMS.
|
|
||||||
S3KMSSealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SealAlgorithm is the encryption/sealing algorithm used to derive & seal
|
// SealAlgorithm is the encryption/sealing algorithm used to derive & seal
|
||||||
// the key-encryption-key and to en/decrypt the object data.
|
// the key-encryption-key and to en/decrypt the object data.
|
||||||
@@ -67,39 +38,34 @@ const (
|
|||||||
InsecureSealAlgorithm = "DARE-SHA256"
|
InsecureSealAlgorithm = "DARE-SHA256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns the SSE domain as string. For SSE-S3 the
|
// Type represents an AWS SSE type:
|
||||||
// domain is "SSE-S3".
|
// • SSE-C
|
||||||
func (s3) String() string { return "SSE-S3" }
|
// • SSE-S3
|
||||||
|
// • SSE-KMS
|
||||||
|
type Type interface {
|
||||||
|
fmt.Stringer
|
||||||
|
|
||||||
// UnsealObjectKey extracts and decrypts the sealed object key
|
IsRequested(http.Header) bool
|
||||||
// from the metadata using KMS and returns the decrypted object
|
|
||||||
// key.
|
IsEncrypted(map[string]string) bool
|
||||||
func (sse s3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
|
|
||||||
keyID, kmsKey, sealedKey, err := sse.ParseMetadata(metadata)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = key.Unseal(unsealKey, sealedKey, sse.String(), bucket, object)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the SSE domain as string. For SSE-C the
|
// IsRequested returns true and the SSE Type if the HTTP headers
|
||||||
// domain is "SSE-C".
|
// indicate that some form server-side encryption is requested.
|
||||||
func (ssec) String() string { return "SSE-C" }
|
//
|
||||||
|
// If no SSE headers are present then IsRequested returns false
|
||||||
// UnsealObjectKey extracts and decrypts the sealed object key
|
// and no Type.
|
||||||
// from the metadata using the SSE-C client key of the HTTP headers
|
func IsRequested(h http.Header) (Type, bool) {
|
||||||
// and returns the decrypted object key.
|
switch {
|
||||||
func (sse ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
|
case S3.IsRequested(h):
|
||||||
clientKey, err := sse.ParseHTTP(h)
|
return S3, true
|
||||||
if err != nil {
|
case S3KMS.IsRequested(h):
|
||||||
return
|
return S3KMS, true
|
||||||
|
case SSEC.IsRequested(h):
|
||||||
|
return SSEC, true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
return unsealObjectKey(clientKey, metadata, bucket, object)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsealObjectKey extracts and decrypts the sealed object key
|
// UnsealObjectKey extracts and decrypts the sealed object key
|
||||||
|
|||||||
+216
-62
@@ -21,11 +21,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
@@ -34,6 +35,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
"github.com/minio/minio/pkg/bucket/replication"
|
"github.com/minio/minio/pkg/bucket/replication"
|
||||||
"github.com/minio/minio/pkg/color"
|
"github.com/minio/minio/pkg/color"
|
||||||
|
"github.com/minio/minio/pkg/console"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
@@ -43,7 +45,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
dataCrawlSleepPerFolder = time.Millisecond // Time to wait between folders.
|
dataCrawlSleepPerFolder = time.Millisecond // Time to wait between folders.
|
||||||
dataCrawlSleepDefMult = 10.0 // Default multiplier for waits between operations.
|
|
||||||
dataCrawlStartDelay = 5 * time.Minute // Time to wait on startup and between cycles.
|
dataCrawlStartDelay = 5 * time.Minute // Time to wait on startup and between cycles.
|
||||||
dataUsageUpdateDirCycles = 16 // Visit all folders every n cycles.
|
dataUsageUpdateDirCycles = 16 // Visit all folders every n cycles.
|
||||||
|
|
||||||
@@ -54,7 +55,11 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
globalHealConfig heal.Config
|
globalHealConfig heal.Config
|
||||||
|
globalHealConfigMu sync.Mutex
|
||||||
|
|
||||||
dataCrawlerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
dataCrawlerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||||
|
// Sleeper values are updated when config is loaded.
|
||||||
|
crawlerSleeper = newDynamicSleeper(10, 10*time.Second)
|
||||||
)
|
)
|
||||||
|
|
||||||
// initDataCrawler will start the crawler unless disabled.
|
// initDataCrawler will start the crawler unless disabled.
|
||||||
@@ -95,11 +100,21 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crawlTimer := time.NewTimer(dataCrawlStartDelay)
|
||||||
|
defer crawlTimer.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.NewTimer(dataCrawlStartDelay).C:
|
case <-crawlTimer.C:
|
||||||
|
// Reset the timer for next cycle.
|
||||||
|
crawlTimer.Reset(dataCrawlStartDelay)
|
||||||
|
|
||||||
|
if intDataUpdateTracker.debug {
|
||||||
|
console.Debugln("starting crawler cycle")
|
||||||
|
}
|
||||||
|
|
||||||
// Wait before starting next cycle and wait on startup.
|
// Wait before starting next cycle and wait on startup.
|
||||||
results := make(chan DataUsageInfo, 1)
|
results := make(chan DataUsageInfo, 1)
|
||||||
go storeDataUsageInBackend(ctx, objAPI, results)
|
go storeDataUsageInBackend(ctx, objAPI, results)
|
||||||
@@ -141,7 +156,6 @@ type folderScanner struct {
|
|||||||
newCache dataUsageCache
|
newCache dataUsageCache
|
||||||
withFilter *bloomFilter
|
withFilter *bloomFilter
|
||||||
|
|
||||||
dataUsageCrawlMult float64
|
|
||||||
dataUsageCrawlDebug bool
|
dataUsageCrawlDebug bool
|
||||||
healFolderInclude uint32 // Include a clean folder one in n cycles.
|
healFolderInclude uint32 // Include a clean folder one in n cycles.
|
||||||
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
||||||
@@ -172,11 +186,6 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
return cache, errors.New("internal error: root scan attempted")
|
return cache, errors.New("internal error: root scan attempted")
|
||||||
}
|
}
|
||||||
|
|
||||||
delayMult, err := strconv.ParseFloat(env.Get(envDataUsageCrawlDelay, "10.0"), 64)
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
delayMult = dataCrawlSleepDefMult
|
|
||||||
}
|
|
||||||
s := folderScanner{
|
s := folderScanner{
|
||||||
root: basePath,
|
root: basePath,
|
||||||
getSize: getSize,
|
getSize: getSize,
|
||||||
@@ -184,7 +193,6 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
|
|||||||
newCache: dataUsageCache{Info: cache.Info},
|
newCache: dataUsageCache{Info: cache.Info},
|
||||||
newFolders: nil,
|
newFolders: nil,
|
||||||
existingFolders: nil,
|
existingFolders: nil,
|
||||||
dataUsageCrawlMult: delayMult,
|
|
||||||
dataUsageCrawlDebug: intDataUpdateTracker.debug,
|
dataUsageCrawlDebug: intDataUpdateTracker.debug,
|
||||||
healFolderInclude: 0,
|
healFolderInclude: 0,
|
||||||
healObjectSelect: 0,
|
healObjectSelect: 0,
|
||||||
@@ -357,17 +365,15 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
|
|
||||||
// If there are lifecycle rules for the prefix, remove the filter.
|
// If there are lifecycle rules for the prefix, remove the filter.
|
||||||
filter := f.withFilter
|
filter := f.withFilter
|
||||||
var activeLifeCycle *lifecycle.Lifecycle
|
|
||||||
if f.oldCache.Info.lifeCycle != nil {
|
|
||||||
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
|
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
|
||||||
if f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) {
|
var activeLifeCycle *lifecycle.Lifecycle
|
||||||
|
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) {
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageCrawlDebug {
|
||||||
logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
|
logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
|
||||||
}
|
}
|
||||||
activeLifeCycle = f.oldCache.Info.lifeCycle
|
activeLifeCycle = f.oldCache.Info.lifeCycle
|
||||||
filter = nil
|
filter = nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if _, ok := f.oldCache.Cache[thisHash.Key()]; filter != nil && ok {
|
if _, ok := f.oldCache.Cache[thisHash.Key()]; filter != nil && ok {
|
||||||
// If folder isn't in filter and we have data, skip it completely.
|
// If folder isn't in filter and we have data, skip it completely.
|
||||||
if folder.name != dataUsageRoot && !filter.containsDir(folder.name) {
|
if folder.name != dataUsageRoot && !filter.containsDir(folder.name) {
|
||||||
@@ -386,7 +392,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sleepDuration(dataCrawlSleepPerFolder, f.dataUsageCrawlMult)
|
crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||||
|
|
||||||
cache := dataUsageEntry{}
|
cache := dataUsageEntry{}
|
||||||
|
|
||||||
@@ -435,7 +441,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
// Dynamic time delay.
|
||||||
t := UTCNow()
|
wait := crawlerSleeper.Timer(ctx)
|
||||||
|
|
||||||
// Get file size, ignore errors.
|
// Get file size, ignore errors.
|
||||||
item := crawlItem{
|
item := crawlItem{
|
||||||
@@ -446,18 +452,18 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
objectName: path.Base(entName),
|
objectName: path.Base(entName),
|
||||||
debug: f.dataUsageCrawlDebug,
|
debug: f.dataUsageCrawlDebug,
|
||||||
lifeCycle: activeLifeCycle,
|
lifeCycle: activeLifeCycle,
|
||||||
heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv),
|
heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
||||||
}
|
}
|
||||||
size, err := f.getSize(item)
|
sizeSummary, err := f.getSize(item)
|
||||||
|
|
||||||
sleepDuration(time.Since(t), f.dataUsageCrawlMult)
|
wait()
|
||||||
if err == errSkipFile {
|
if err == errSkipFile {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
cache.Size += size
|
cache.addSizes(sizeSummary)
|
||||||
cache.Objects++
|
cache.Objects++
|
||||||
cache.ObjSizes.add(size)
|
cache.ObjSizes.add(sizeSummary.totalSize)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -506,8 +512,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
// Dynamic time delay.
|
||||||
t := UTCNow()
|
wait := crawlerSleeper.Timer(ctx)
|
||||||
|
|
||||||
resolver.bucket = bucket
|
resolver.bucket = bucket
|
||||||
|
|
||||||
foundObjs := false
|
foundObjs := false
|
||||||
@@ -535,8 +540,8 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
logger.Info(color.Green("healObjects:")+" got partial, %d agreed, errs: %v", nAgreed, errs)
|
logger.Info(color.Green("healObjects:")+" got partial, %d agreed, errs: %v", nAgreed, errs)
|
||||||
}
|
}
|
||||||
// Sleep and reset.
|
// Sleep and reset.
|
||||||
sleepDuration(time.Since(t), f.dataUsageCrawlMult)
|
wait()
|
||||||
t = UTCNow()
|
wait = crawlerSleeper.Timer(ctx)
|
||||||
entry, ok := entries.resolve(&resolver)
|
entry, ok := entries.resolve(&resolver)
|
||||||
if !ok {
|
if !ok {
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
@@ -566,20 +571,24 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
object: entry.name,
|
object: entry.name,
|
||||||
versionID: "",
|
versionID: "",
|
||||||
}, madmin.HealItemObject)
|
}, madmin.HealItemObject)
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
foundObjs = foundObjs || err == nil
|
foundObjs = foundObjs || err == nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, ver := range fiv.Versions {
|
for _, ver := range fiv.Versions {
|
||||||
// Sleep and reset.
|
// Sleep and reset.
|
||||||
sleepDuration(time.Since(t), f.dataUsageCrawlMult)
|
wait()
|
||||||
t = UTCNow()
|
wait = crawlerSleeper.Timer(ctx)
|
||||||
err := bgSeq.queueHealTask(healSource{
|
err := bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: fiv.Name,
|
object: fiv.Name,
|
||||||
versionID: ver.VersionID,
|
versionID: ver.VersionID,
|
||||||
}, madmin.HealItemObject)
|
}, madmin.HealItemObject)
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
foundObjs = foundObjs || err == nil
|
foundObjs = foundObjs || err == nil
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -605,6 +614,9 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
// If we have quorum, found directories, but no objects, issue heal to delete the dangling.
|
// If we have quorum, found directories, but no objects, issue heal to delete the dangling.
|
||||||
objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{Recursive: true, Remove: true},
|
objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{Recursive: true, Remove: true},
|
||||||
func(bucket, object, versionID string) error {
|
func(bucket, object, versionID string) error {
|
||||||
|
// Wait for each heal as per crawler frequency.
|
||||||
|
wait()
|
||||||
|
wait = crawlerSleeper.Timer(ctx)
|
||||||
return bgSeq.queueHealTask(healSource{
|
return bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: object,
|
object: object,
|
||||||
@@ -613,7 +625,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sleepDuration(time.Since(t), f.dataUsageCrawlMult)
|
wait()
|
||||||
|
|
||||||
// Add unless healing returned an error.
|
// Add unless healing returned an error.
|
||||||
if foundObjs {
|
if foundObjs {
|
||||||
@@ -651,12 +663,12 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
|
|||||||
dirStack = append(dirStack, entName)
|
dirStack = append(dirStack, entName)
|
||||||
err := readDirFn(path.Join(dirStack...), addDir)
|
err := readDirFn(path.Join(dirStack...), addDir)
|
||||||
dirStack = dirStack[:len(dirStack)-1]
|
dirStack = dirStack[:len(dirStack)-1]
|
||||||
sleepDuration(dataCrawlSleepPerFolder, f.dataUsageCrawlMult)
|
crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic time delay.
|
// Dynamic time delay.
|
||||||
t := UTCNow()
|
wait := crawlerSleeper.Timer(ctx)
|
||||||
|
|
||||||
// Get file size, ignore errors.
|
// Get file size, ignore errors.
|
||||||
dirStack = append(dirStack, entName)
|
dirStack = append(dirStack, entName)
|
||||||
@@ -665,16 +677,14 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
|
|||||||
|
|
||||||
bucket, prefix := path2BucketObjectWithBasePath(f.root, fileName)
|
bucket, prefix := path2BucketObjectWithBasePath(f.root, fileName)
|
||||||
var activeLifeCycle *lifecycle.Lifecycle
|
var activeLifeCycle *lifecycle.Lifecycle
|
||||||
if f.oldCache.Info.lifeCycle != nil {
|
if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, false) {
|
||||||
if f.oldCache.Info.lifeCycle.HasActiveRules(prefix, false) {
|
|
||||||
if f.dataUsageCrawlDebug {
|
if f.dataUsageCrawlDebug {
|
||||||
logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
|
logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
|
||||||
}
|
}
|
||||||
activeLifeCycle = f.oldCache.Info.lifeCycle
|
activeLifeCycle = f.oldCache.Info.lifeCycle
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
size, err := f.getSize(
|
sizeSummary, err := f.getSize(
|
||||||
crawlItem{
|
crawlItem{
|
||||||
Path: fileName,
|
Path: fileName,
|
||||||
Typ: typ,
|
Typ: typ,
|
||||||
@@ -683,19 +693,19 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
|
|||||||
objectName: path.Base(entName),
|
objectName: path.Base(entName),
|
||||||
debug: f.dataUsageCrawlDebug,
|
debug: f.dataUsageCrawlDebug,
|
||||||
lifeCycle: activeLifeCycle,
|
lifeCycle: activeLifeCycle,
|
||||||
heal: hashPath(path.Join(prefix, entName)).mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv),
|
heal: hashPath(path.Join(prefix, entName)).mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Don't sleep for really small amount of time
|
// Wait to throttle IO
|
||||||
sleepDuration(time.Since(t), f.dataUsageCrawlMult)
|
wait()
|
||||||
|
|
||||||
if err == errSkipFile {
|
if err == errSkipFile {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
cache.Size += size
|
cache.addSizes(sizeSummary)
|
||||||
cache.Objects++
|
cache.Objects++
|
||||||
cache.ObjSizes.add(size)
|
cache.ObjSizes.add(sizeSummary.totalSize)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := readDirFn(path.Join(dirStack...), addDir)
|
err := readDirFn(path.Join(dirStack...), addDir)
|
||||||
@@ -718,7 +728,15 @@ type crawlItem struct {
|
|||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type getSizeFn func(item crawlItem) (int64, error)
|
type sizeSummary struct {
|
||||||
|
totalSize int64
|
||||||
|
replicatedSize int64
|
||||||
|
pendingSize int64
|
||||||
|
failedSize int64
|
||||||
|
replicaSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type getSizeFn func(item crawlItem) (sizeSummary, error)
|
||||||
|
|
||||||
// transformMetaDir will transform a directory to prefix/file.ext
|
// transformMetaDir will transform a directory to prefix/file.ext
|
||||||
func (i *crawlItem) transformMetaDir() {
|
func (i *crawlItem) transformMetaDir() {
|
||||||
@@ -750,7 +768,11 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
}
|
}
|
||||||
if i.heal {
|
if i.heal {
|
||||||
if i.debug {
|
if i.debug {
|
||||||
|
if meta.oi.VersionID != "" {
|
||||||
logger.Info(color.Green("applyActions:")+" heal checking: %v/%v v%s", i.bucket, i.objectPath(), meta.oi.VersionID)
|
logger.Info(color.Green("applyActions:")+" heal checking: %v/%v v%s", i.bucket, i.objectPath(), meta.oi.VersionID)
|
||||||
|
} else {
|
||||||
|
logger.Info(color.Green("applyActions:")+" heal checking: %v/%v", i.bucket, i.objectPath())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res, err := o.HealObject(ctx, i.bucket, i.objectPath(), meta.oi.VersionID, madmin.HealOpts{Remove: healDeleteDangling})
|
res, err := o.HealObject(ctx, i.bucket, i.objectPath(), meta.oi.VersionID, madmin.HealOpts{Remove: healDeleteDangling})
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
@@ -763,6 +785,9 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
size = res.ObjectSize
|
size = res.ObjectSize
|
||||||
}
|
}
|
||||||
if i.lifeCycle == nil {
|
if i.lifeCycle == nil {
|
||||||
|
if i.debug {
|
||||||
|
logger.Info(color.Green("applyActions:")+" no lifecycle rules to apply: %q", i.objectPath())
|
||||||
|
}
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,7 +807,11 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
TransitionStatus: meta.oi.TransitionStatus,
|
TransitionStatus: meta.oi.TransitionStatus,
|
||||||
})
|
})
|
||||||
if i.debug {
|
if i.debug {
|
||||||
|
if versionID != "" {
|
||||||
logger.Info(color.Green("applyActions:")+" lifecycle: %q (version-id=%s), Initial scan: %v", i.objectPath(), versionID, action)
|
logger.Info(color.Green("applyActions:")+" lifecycle: %q (version-id=%s), Initial scan: %v", i.objectPath(), versionID, action)
|
||||||
|
} else {
|
||||||
|
logger.Info(color.Green("applyActions:")+" lifecycle: %q Initial scan: %v", i.objectPath(), action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
switch action {
|
switch action {
|
||||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
||||||
@@ -790,6 +819,9 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
default:
|
default:
|
||||||
// No action.
|
// No action.
|
||||||
|
if i.debug {
|
||||||
|
logger.Info(color.Green("applyActions:")+" object not expirable: %q", i.objectPath())
|
||||||
|
}
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,7 +862,6 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
TransitionStatus: obj.TransitionStatus,
|
TransitionStatus: obj.TransitionStatus,
|
||||||
}
|
}
|
||||||
action = i.lifeCycle.ComputeAction(lcOpts)
|
action = i.lifeCycle.ComputeAction(lcOpts)
|
||||||
|
|
||||||
if i.debug {
|
if i.debug {
|
||||||
logger.Info(color.Green("applyActions:")+" lifecycle: Secondary scan: %v", action)
|
logger.Info(color.Green("applyActions:")+" lifecycle: Secondary scan: %v", action)
|
||||||
}
|
}
|
||||||
@@ -854,8 +885,12 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
locked := enforceRetentionForDeletion(ctx, obj)
|
locked := enforceRetentionForDeletion(ctx, obj)
|
||||||
if locked {
|
if locked {
|
||||||
if i.debug {
|
if i.debug {
|
||||||
|
if obj.VersionID != "" {
|
||||||
|
logger.Info(color.Green("applyActions:")+" lifecycle: %s v%s is locked, not deleting", i.objectPath(), obj.VersionID)
|
||||||
|
} else {
|
||||||
logger.Info(color.Green("applyActions:")+" lifecycle: %s is locked, not deleting", i.objectPath())
|
logger.Info(color.Green("applyActions:")+" lifecycle: %s is locked, not deleting", i.objectPath())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -876,19 +911,22 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
|||||||
globalTransitionState.queueTransitionTask(obj)
|
globalTransitionState.queueTransitionTask(obj)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.TransitionStatus != "" {
|
if obj.TransitionStatus != "" {
|
||||||
if err := deleteTransitionedObject(ctx, o, i.bucket, i.objectPath(), lcOpts, action, false); err != nil {
|
if err := deleteTransitionedObject(ctx, o, i.bucket, i.objectPath(), lcOpts, action, false); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
} else {
|
// Notification already sent at *deleteTransitionedObject*, return '0' here.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
obj, err = o.DeleteObject(ctx, i.bucket, i.objectPath(), opts)
|
obj, err = o.DeleteObject(ctx, i.bucket, i.objectPath(), opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Assume it is still there.
|
// Assume it is still there.
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
eventName := event.ObjectRemovedDelete
|
eventName := event.ObjectRemovedDelete
|
||||||
if obj.DeleteMarker {
|
if obj.DeleteMarker {
|
||||||
@@ -910,25 +948,10 @@ func (i *crawlItem) objectPath() string {
|
|||||||
return path.Join(i.prefix, i.objectName)
|
return path.Join(i.prefix, i.objectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sleepDuration multiplies the duration d by x
|
|
||||||
// and sleeps if is more than 100 micro seconds.
|
|
||||||
// Sleep is limited to max 15 seconds.
|
|
||||||
func sleepDuration(d time.Duration, x float64) {
|
|
||||||
const maxWait = 15 * time.Second
|
|
||||||
const minWait = 100 * time.Microsecond
|
|
||||||
// Don't sleep for really small amount of time
|
|
||||||
if d := time.Duration(float64(d) * x); d > minWait {
|
|
||||||
if d > maxWait {
|
|
||||||
d = maxWait
|
|
||||||
}
|
|
||||||
time.Sleep(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// healReplication will heal a scanned item that has failed replication.
|
// healReplication will heal a scanned item that has failed replication.
|
||||||
func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, meta actionMeta) {
|
func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, meta actionMeta, sizeS *sizeSummary) {
|
||||||
if meta.oi.DeleteMarker || !meta.oi.VersionPurgeStatus.Empty() {
|
if meta.oi.DeleteMarker || !meta.oi.VersionPurgeStatus.Empty() {
|
||||||
//heal delete marker replication failure or versioned delete replication failure
|
// heal delete marker replication failure or versioned delete replication failure
|
||||||
if meta.oi.ReplicationStatus == replication.Pending ||
|
if meta.oi.ReplicationStatus == replication.Pending ||
|
||||||
meta.oi.ReplicationStatus == replication.Failed ||
|
meta.oi.ReplicationStatus == replication.Failed ||
|
||||||
meta.oi.VersionPurgeStatus == Failed || meta.oi.VersionPurgeStatus == Pending {
|
meta.oi.VersionPurgeStatus == Failed || meta.oi.VersionPurgeStatus == Pending {
|
||||||
@@ -936,9 +959,17 @@ func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, meta act
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if meta.oi.ReplicationStatus == replication.Pending ||
|
switch meta.oi.ReplicationStatus {
|
||||||
meta.oi.ReplicationStatus == replication.Failed {
|
case replication.Pending:
|
||||||
|
sizeS.pendingSize += meta.oi.Size
|
||||||
globalReplicationState.queueReplicaTask(meta.oi)
|
globalReplicationState.queueReplicaTask(meta.oi)
|
||||||
|
case replication.Failed:
|
||||||
|
sizeS.failedSize += meta.oi.Size
|
||||||
|
globalReplicationState.queueReplicaTask(meta.oi)
|
||||||
|
case replication.Complete:
|
||||||
|
sizeS.replicatedSize += meta.oi.Size
|
||||||
|
case replication.Replica:
|
||||||
|
sizeS.replicaSize += meta.oi.Size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,3 +998,126 @@ func (i *crawlItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, m
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dynamicSleeper struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
// Sleep factor
|
||||||
|
factor float64
|
||||||
|
|
||||||
|
// maximum sleep cap,
|
||||||
|
// set to <= 0 to disable.
|
||||||
|
maxSleep time.Duration
|
||||||
|
|
||||||
|
// Don't sleep at all, if time taken is below this value.
|
||||||
|
// This is to avoid too small costly sleeps.
|
||||||
|
minSleep time.Duration
|
||||||
|
|
||||||
|
// cycle will be closed
|
||||||
|
cycle chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDynamicSleeper
|
||||||
|
func newDynamicSleeper(factor float64, maxWait time.Duration) *dynamicSleeper {
|
||||||
|
return &dynamicSleeper{
|
||||||
|
factor: factor,
|
||||||
|
cycle: make(chan struct{}),
|
||||||
|
maxSleep: maxWait,
|
||||||
|
minSleep: 100 * time.Microsecond,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer returns a timer that has started.
|
||||||
|
// When the returned function is called it will wait.
|
||||||
|
func (d *dynamicSleeper) Timer(ctx context.Context) func() {
|
||||||
|
t := time.Now()
|
||||||
|
return func() {
|
||||||
|
doneAt := time.Now()
|
||||||
|
for {
|
||||||
|
// Grab current values
|
||||||
|
d.mu.RLock()
|
||||||
|
minWait, maxWait := d.minSleep, d.maxSleep
|
||||||
|
factor := d.factor
|
||||||
|
cycle := d.cycle
|
||||||
|
d.mu.RUnlock()
|
||||||
|
elapsed := doneAt.Sub(t)
|
||||||
|
// Don't sleep for really small amount of time
|
||||||
|
wantSleep := time.Duration(float64(elapsed) * factor)
|
||||||
|
if wantSleep <= minWait {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if maxWait > 0 && wantSleep > maxWait {
|
||||||
|
wantSleep = maxWait
|
||||||
|
}
|
||||||
|
timer := time.NewTimer(wantSleep)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case <-timer.C:
|
||||||
|
return
|
||||||
|
case <-cycle:
|
||||||
|
if !timer.Stop() {
|
||||||
|
// We expired.
|
||||||
|
<-timer.C
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep sleeps the specified time multiplied by the sleep factor.
|
||||||
|
// If the factor is updated the sleep will be done again with the new factor.
|
||||||
|
func (d *dynamicSleeper) Sleep(ctx context.Context, base time.Duration) {
|
||||||
|
for {
|
||||||
|
// Grab current values
|
||||||
|
d.mu.RLock()
|
||||||
|
minWait, maxWait := d.minSleep, d.maxSleep
|
||||||
|
factor := d.factor
|
||||||
|
cycle := d.cycle
|
||||||
|
d.mu.RUnlock()
|
||||||
|
// Don't sleep for really small amount of time
|
||||||
|
wantSleep := time.Duration(float64(base) * factor)
|
||||||
|
if wantSleep <= minWait {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if maxWait > 0 && wantSleep > maxWait {
|
||||||
|
wantSleep = maxWait
|
||||||
|
}
|
||||||
|
timer := time.NewTimer(wantSleep)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case <-timer.C:
|
||||||
|
return
|
||||||
|
case <-cycle:
|
||||||
|
if !timer.Stop() {
|
||||||
|
// We expired.
|
||||||
|
<-timer.C
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the current settings and cycle all waiting.
|
||||||
|
// Parameters are the same as in the contructor.
|
||||||
|
func (d *dynamicSleeper) Update(factor float64, maxWait time.Duration) error {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
if math.Abs(d.factor-factor) < 1e-10 && d.maxSleep == maxWait {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Update values and cycle waiting.
|
||||||
|
close(d.cycle)
|
||||||
|
d.factor = factor
|
||||||
|
d.maxSleep = maxWait
|
||||||
|
d.cycle = make(chan struct{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func newDataUpdateTracker() *dataUpdateTracker {
|
|||||||
Current: dataUpdateFilter{
|
Current: dataUpdateFilter{
|
||||||
idx: 1,
|
idx: 1,
|
||||||
},
|
},
|
||||||
debug: env.Get(envDataUsageCrawlDebug, config.EnableOff) == config.EnableOn,
|
debug: env.Get(envDataUsageCrawlDebug, config.EnableOff) == config.EnableOn || serverDebugLog,
|
||||||
input: make(chan string, dataUpdateTrackerQueueSize),
|
input: make(chan string, dataUpdateTrackerQueueSize),
|
||||||
save: make(chan struct{}, 1),
|
save: make(chan struct{}, 1),
|
||||||
saveExited: make(chan struct{}),
|
saveExited: make(chan struct{}),
|
||||||
|
|||||||
+52
-20
@@ -47,9 +47,12 @@ type sizeHistogram [dataUsageBucketLen]uint64
|
|||||||
type dataUsageEntry struct {
|
type dataUsageEntry struct {
|
||||||
// These fields do no include any children.
|
// These fields do no include any children.
|
||||||
Size int64
|
Size int64
|
||||||
|
ReplicatedSize uint64
|
||||||
|
ReplicationPendingSize uint64
|
||||||
|
ReplicationFailedSize uint64
|
||||||
|
ReplicaSize uint64
|
||||||
Objects uint64
|
Objects uint64
|
||||||
ObjSizes sizeHistogram
|
ObjSizes sizeHistogram
|
||||||
|
|
||||||
Children dataUsageHashMap
|
Children dataUsageHashMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,10 +79,23 @@ type dataUsageCacheInfo struct {
|
|||||||
lifeCycle *lifecycle.Lifecycle `msg:"-"`
|
lifeCycle *lifecycle.Lifecycle `msg:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *dataUsageEntry) addSizes(summary sizeSummary) {
|
||||||
|
e.Size += summary.totalSize
|
||||||
|
e.ReplicatedSize += uint64(summary.replicatedSize)
|
||||||
|
e.ReplicationFailedSize += uint64(summary.failedSize)
|
||||||
|
e.ReplicationPendingSize += uint64(summary.pendingSize)
|
||||||
|
e.ReplicaSize += uint64(summary.replicaSize)
|
||||||
|
}
|
||||||
|
|
||||||
// merge other data usage entry into this, excluding children.
|
// merge other data usage entry into this, excluding children.
|
||||||
func (e *dataUsageEntry) merge(other dataUsageEntry) {
|
func (e *dataUsageEntry) merge(other dataUsageEntry) {
|
||||||
e.Objects += other.Objects
|
e.Objects += other.Objects
|
||||||
e.Size += other.Size
|
e.Size += other.Size
|
||||||
|
e.ReplicationPendingSize += other.ReplicationPendingSize
|
||||||
|
e.ReplicationFailedSize += other.ReplicationFailedSize
|
||||||
|
e.ReplicatedSize += other.ReplicatedSize
|
||||||
|
e.ReplicaSize += other.ReplicaSize
|
||||||
|
|
||||||
for i, v := range other.ObjSizes[:] {
|
for i, v := range other.ObjSizes[:] {
|
||||||
e.ObjSizes[i] += v
|
e.ObjSizes[i] += v
|
||||||
}
|
}
|
||||||
@@ -216,6 +232,10 @@ func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo {
|
|||||||
LastUpdate: d.Info.LastUpdate,
|
LastUpdate: d.Info.LastUpdate,
|
||||||
ObjectsTotalCount: flat.Objects,
|
ObjectsTotalCount: flat.Objects,
|
||||||
ObjectsTotalSize: uint64(flat.Size),
|
ObjectsTotalSize: uint64(flat.Size),
|
||||||
|
ReplicatedSize: flat.ReplicatedSize,
|
||||||
|
ReplicationFailedSize: flat.ReplicationFailedSize,
|
||||||
|
ReplicationPendingSize: flat.ReplicationPendingSize,
|
||||||
|
ReplicaSize: flat.ReplicaSize,
|
||||||
BucketsCount: uint64(len(e.Children)),
|
BucketsCount: uint64(len(e.Children)),
|
||||||
BucketsUsage: d.bucketsUsageInfo(buckets),
|
BucketsUsage: d.bucketsUsageInfo(buckets),
|
||||||
}
|
}
|
||||||
@@ -345,6 +365,10 @@ func (d *dataUsageCache) bucketsUsageInfo(buckets []BucketInfo) map[string]Bucke
|
|||||||
dst[bucket.Name] = BucketUsageInfo{
|
dst[bucket.Name] = BucketUsageInfo{
|
||||||
Size: uint64(flat.Size),
|
Size: uint64(flat.Size),
|
||||||
ObjectsCount: flat.Objects,
|
ObjectsCount: flat.Objects,
|
||||||
|
ReplicationPendingSize: flat.ReplicationPendingSize,
|
||||||
|
ReplicatedSize: flat.ReplicatedSize,
|
||||||
|
ReplicationFailedSize: flat.ReplicationFailedSize,
|
||||||
|
ReplicaSize: flat.ReplicaSize,
|
||||||
ObjectSizesHistogram: flat.ObjSizes.toMap(),
|
ObjectSizesHistogram: flat.ObjSizes.toMap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,6 +386,10 @@ func (d *dataUsageCache) bucketUsageInfo(bucket string) BucketUsageInfo {
|
|||||||
return BucketUsageInfo{
|
return BucketUsageInfo{
|
||||||
Size: uint64(flat.Size),
|
Size: uint64(flat.Size),
|
||||||
ObjectsCount: flat.Objects,
|
ObjectsCount: flat.Objects,
|
||||||
|
ReplicationPendingSize: flat.ReplicationPendingSize,
|
||||||
|
ReplicatedSize: flat.ReplicatedSize,
|
||||||
|
ReplicationFailedSize: flat.ReplicationFailedSize,
|
||||||
|
ReplicaSize: flat.ReplicaSize,
|
||||||
ObjectSizesHistogram: flat.ObjSizes.toMap(),
|
ObjectSizesHistogram: flat.ObjSizes.toMap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,9 +489,12 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
|
|||||||
|
|
||||||
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
|
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
|
||||||
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
|
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
|
||||||
b := d.serialize()
|
pr, pw := io.Pipe()
|
||||||
size := int64(len(b))
|
go func() {
|
||||||
r, err := hash.NewReader(bytes.NewReader(b), size, "", "", size, false)
|
pw.CloseWithError(d.serializeTo(pw))
|
||||||
|
}()
|
||||||
|
defer pr.Close()
|
||||||
|
r, err := hash.NewReader(pr, -1, "", "", -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -482,35 +513,36 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
|||||||
// dataUsageCacheVer indicates the cache version.
|
// dataUsageCacheVer indicates the cache version.
|
||||||
// Bumping the cache version will drop data from previous versions
|
// Bumping the cache version will drop data from previous versions
|
||||||
// and write new data with the new version.
|
// and write new data with the new version.
|
||||||
const dataUsageCacheVer = 2
|
const dataUsageCacheVer = 3
|
||||||
|
|
||||||
// serialize the contents of the cache.
|
// serialize the contents of the cache.
|
||||||
func (d *dataUsageCache) serialize() []byte {
|
func (d *dataUsageCache) serializeTo(dst io.Writer) error {
|
||||||
// Prepend version and compress.
|
// Add version and compress.
|
||||||
dst := make([]byte, 0, d.Msgsize()+1)
|
_, err := dst.Write([]byte{dataUsageCacheVer})
|
||||||
dst = append(dst, dataUsageCacheVer)
|
if err != nil {
|
||||||
buf := bytes.NewBuffer(dst)
|
return err
|
||||||
enc, err := zstd.NewWriter(buf,
|
}
|
||||||
|
enc, err := zstd.NewWriter(dst,
|
||||||
zstd.WithEncoderLevel(zstd.SpeedFastest),
|
zstd.WithEncoderLevel(zstd.SpeedFastest),
|
||||||
zstd.WithWindowSize(1<<20),
|
zstd.WithWindowSize(1<<20),
|
||||||
zstd.WithEncoderConcurrency(2))
|
zstd.WithEncoderConcurrency(2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, err)
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
mEnc := msgp.NewWriter(enc)
|
mEnc := msgp.NewWriter(enc)
|
||||||
err = d.EncodeMsg(mEnc)
|
err = d.EncodeMsg(mEnc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, err)
|
return err
|
||||||
return nil
|
}
|
||||||
|
err = mEnc.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
mEnc.Flush()
|
|
||||||
err = enc.Close()
|
err = enc.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, err)
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deserialize the supplied byte slice into the cache.
|
// deserialize the supplied byte slice into the cache.
|
||||||
@@ -521,7 +553,7 @@ func (d *dataUsageCache) deserialize(r io.Reader) error {
|
|||||||
return io.ErrUnexpectedEOF
|
return io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
switch b[0] {
|
switch b[0] {
|
||||||
case 1:
|
case 1, 2:
|
||||||
return errors.New("cache version deprecated (will autoupdate)")
|
return errors.New("cache version deprecated (will autoupdate)")
|
||||||
case dataUsageCacheVer:
|
case dataUsageCacheVer:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -492,8 +492,8 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if zb0001 != 4 {
|
if zb0001 != 8 {
|
||||||
err = msgp.ArrayError{Wanted: 4, Got: zb0001}
|
err = msgp.ArrayError{Wanted: 8, Got: zb0001}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
z.Size, err = dc.ReadInt64()
|
z.Size, err = dc.ReadInt64()
|
||||||
@@ -501,6 +501,26 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Size")
|
err = msgp.WrapError(err, "Size")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
z.ReplicatedSize, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicatedSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicationPendingSize, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationPendingSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicationFailedSize, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationFailedSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicaSize, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicaSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
z.Objects, err = dc.ReadUint64()
|
z.Objects, err = dc.ReadUint64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Objects")
|
err = msgp.WrapError(err, "Objects")
|
||||||
@@ -533,8 +553,8 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// array header, size 4
|
// array header, size 8
|
||||||
err = en.Append(0x94)
|
err = en.Append(0x98)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -543,6 +563,26 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Size")
|
err = msgp.WrapError(err, "Size")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = en.WriteUint64(z.ReplicatedSize)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicatedSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ReplicationPendingSize)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationPendingSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ReplicationFailedSize)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationFailedSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ReplicaSize)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicaSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
err = en.WriteUint64(z.Objects)
|
err = en.WriteUint64(z.Objects)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Objects")
|
err = msgp.WrapError(err, "Objects")
|
||||||
@@ -571,9 +611,13 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// array header, size 4
|
// array header, size 8
|
||||||
o = append(o, 0x94)
|
o = append(o, 0x98)
|
||||||
o = msgp.AppendInt64(o, z.Size)
|
o = msgp.AppendInt64(o, z.Size)
|
||||||
|
o = msgp.AppendUint64(o, z.ReplicatedSize)
|
||||||
|
o = msgp.AppendUint64(o, z.ReplicationPendingSize)
|
||||||
|
o = msgp.AppendUint64(o, z.ReplicationFailedSize)
|
||||||
|
o = msgp.AppendUint64(o, z.ReplicaSize)
|
||||||
o = msgp.AppendUint64(o, z.Objects)
|
o = msgp.AppendUint64(o, z.Objects)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
|
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
|
||||||
for za0001 := range z.ObjSizes {
|
for za0001 := range z.ObjSizes {
|
||||||
@@ -595,8 +639,8 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err)
|
err = msgp.WrapError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if zb0001 != 4 {
|
if zb0001 != 8 {
|
||||||
err = msgp.ArrayError{Wanted: 4, Got: zb0001}
|
err = msgp.ArrayError{Wanted: 8, Got: zb0001}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
z.Size, bts, err = msgp.ReadInt64Bytes(bts)
|
z.Size, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
@@ -604,6 +648,26 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Size")
|
err = msgp.WrapError(err, "Size")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
z.ReplicatedSize, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicatedSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicationPendingSize, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationPendingSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicationFailedSize, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicationFailedSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.ReplicaSize, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ReplicaSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
z.Objects, bts, err = msgp.ReadUint64Bytes(bts)
|
z.Objects, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Objects")
|
err = msgp.WrapError(err, "Objects")
|
||||||
@@ -637,7 +701,7 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *dataUsageEntry) Msgsize() (s int) {
|
func (z *dataUsageEntry) Msgsize() (s int) {
|
||||||
s = 1 + msgp.Int64Size + msgp.Uint64Size + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + z.Children.Msgsize()
|
s = 1 + msgp.Int64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + z.Children.Msgsize()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -28,7 +28,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
envDataUsageCrawlConf = "MINIO_DISK_USAGE_CRAWL_ENABLE"
|
envDataUsageCrawlConf = "MINIO_DISK_USAGE_CRAWL_ENABLE"
|
||||||
envDataUsageCrawlDelay = "MINIO_DISK_USAGE_CRAWL_DELAY"
|
|
||||||
envDataUsageCrawlDebug = "MINIO_DISK_USAGE_CRAWL_DEBUG"
|
envDataUsageCrawlDebug = "MINIO_DISK_USAGE_CRAWL_DEBUG"
|
||||||
|
|
||||||
dataUsageRoot = SlashSeparator
|
dataUsageRoot = SlashSeparator
|
||||||
@@ -40,8 +39,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// storeDataUsageInBackend will store all objects sent on the gui channel until closed.
|
// storeDataUsageInBackend will store all objects sent on the gui channel until closed.
|
||||||
func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, gui <-chan DataUsageInfo) {
|
func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) {
|
||||||
for dataUsageInfo := range gui {
|
for dataUsageInfo := range dui {
|
||||||
dataUsageJSON, err := json.Marshal(dataUsageInfo)
|
dataUsageJSON, err := json.Marshal(dataUsageInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -53,7 +52,6 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, gui <-chan
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageObjName, NewPutObjReader(r, nil, nil), ObjectOptions{})
|
_, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageObjName, NewPutObjReader(r, nil, nil), ObjectOptions{})
|
||||||
if !isErrBucketNotFound(err) {
|
if !isErrBucketNotFound(err) {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
|
|||||||
+29
-20
@@ -51,15 +51,17 @@ func TestDataUsageUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, bucket, files)
|
createUsageTestFiles(t, base, bucket, files)
|
||||||
|
|
||||||
getSize := func(item crawlItem) (i int64, err error) {
|
getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
|
||||||
if item.Typ&os.ModeDir == 0 {
|
if item.Typ&os.ModeDir == 0 {
|
||||||
s, err := os.Stat(item.Path)
|
var s os.FileInfo
|
||||||
|
s, err = os.Stat(item.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return
|
||||||
}
|
}
|
||||||
return s.Size(), nil
|
sizeS.totalSize = s.Size()
|
||||||
|
return sizeS, nil
|
||||||
}
|
}
|
||||||
return 0, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
||||||
@@ -345,15 +347,17 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, "", files)
|
createUsageTestFiles(t, base, "", files)
|
||||||
|
|
||||||
getSize := func(item crawlItem) (i int64, err error) {
|
getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
|
||||||
if item.Typ&os.ModeDir == 0 {
|
if item.Typ&os.ModeDir == 0 {
|
||||||
s, err := os.Stat(item.Path)
|
var s os.FileInfo
|
||||||
|
s, err = os.Stat(item.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return
|
||||||
}
|
}
|
||||||
return s.Size(), nil
|
sizeS.totalSize = s.Size()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return 0, nil
|
return
|
||||||
}
|
}
|
||||||
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize)
|
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -642,28 +646,33 @@ func TestDataUsageCacheSerialize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
createUsageTestFiles(t, base, bucket, files)
|
createUsageTestFiles(t, base, bucket, files)
|
||||||
|
|
||||||
getSize := func(item crawlItem) (i int64, err error) {
|
getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
|
||||||
if item.Typ&os.ModeDir == 0 {
|
if item.Typ&os.ModeDir == 0 {
|
||||||
s, err := os.Stat(item.Path)
|
var s os.FileInfo
|
||||||
|
s, err = os.Stat(item.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return
|
||||||
}
|
}
|
||||||
return s.Size(), nil
|
sizeS.totalSize = s.Size()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return 0, nil
|
return
|
||||||
}
|
}
|
||||||
want, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
want, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
b := want.serialize()
|
err = want.serializeTo(&buf)
|
||||||
var got dataUsageCache
|
if err != nil {
|
||||||
err = got.deserialize(bytes.NewBuffer(b))
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("serialized size:", buf.Len(), "bytes")
|
||||||
|
var got dataUsageCache
|
||||||
|
err = got.deserialize(&buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log("serialized size:", len(b), "bytes")
|
|
||||||
if got.Info.LastUpdate.IsZero() {
|
if got.Info.LastUpdate.IsZero() {
|
||||||
t.Error("lastupdate not set")
|
t.Error("lastupdate not set")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -962,7 +962,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
|
|||||||
}
|
}
|
||||||
if globalCacheKMS != nil {
|
if globalCacheKMS != nil {
|
||||||
// clean up internal SSE cache metadata
|
// clean up internal SSE cache metadata
|
||||||
delete(gr.ObjInfo.UserDefined, crypto.SSEHeader)
|
delete(gr.ObjInfo.UserDefined, xhttp.AmzServerSideEncryption)
|
||||||
}
|
}
|
||||||
if !rngInfo.Empty() {
|
if !rngInfo.Empty() {
|
||||||
// overlay Size with actual object size and not the range size
|
// overlay Size with actual object size and not the range size
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import (
|
|||||||
// CacheDiskStats represents cache disk statistics
|
// CacheDiskStats represents cache disk statistics
|
||||||
// such as current disk usage and available.
|
// such as current disk usage and available.
|
||||||
type CacheDiskStats struct {
|
type CacheDiskStats struct {
|
||||||
|
// used cache size
|
||||||
|
UsageSize uint64
|
||||||
|
// total cache disk capacity
|
||||||
|
TotalCapacity uint64
|
||||||
// indicates if usage is high or low, if high value is '1', if low its '0'
|
// indicates if usage is high or low, if high value is '1', if low its '0'
|
||||||
UsageState int32
|
UsageState int32
|
||||||
// indicates the current usage percentage of this cache disk
|
// indicates the current usage percentage of this cache disk
|
||||||
|
|||||||
@@ -781,6 +781,10 @@ func newServerCacheObjects(ctx context.Context, config cache.Config) (CacheObjec
|
|||||||
dcache := c.cache[i]
|
dcache := c.cache[i]
|
||||||
cacheDiskStats[i] = CacheDiskStats{}
|
cacheDiskStats[i] = CacheDiskStats{}
|
||||||
if dcache != nil {
|
if dcache != nil {
|
||||||
|
info, err := getDiskInfo(dcache.dir)
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
cacheDiskStats[i].UsageSize = info.Used
|
||||||
|
cacheDiskStats[i].TotalCapacity = info.Total
|
||||||
cacheDiskStats[i].Dir = dcache.stats.Dir
|
cacheDiskStats[i].Dir = dcache.stats.Dir
|
||||||
atomic.StoreInt32(&cacheDiskStats[i].UsageState, atomic.LoadInt32(&dcache.stats.UsageState))
|
atomic.StoreInt32(&cacheDiskStats[i].UsageState, atomic.LoadInt32(&dcache.stats.UsageState))
|
||||||
atomic.StoreUint64(&cacheDiskStats[i].UsagePercent, atomic.LoadUint64(&dcache.stats.UsagePercent))
|
atomic.StoreUint64(&cacheDiskStats[i].UsagePercent, atomic.LoadUint64(&dcache.stats.UsagePercent))
|
||||||
|
|||||||
@@ -386,13 +386,13 @@ func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, offset,
|
|||||||
header: h,
|
header: h,
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: object,
|
object: object,
|
||||||
customerKeyHeader: h.Get(crypto.SSECKey),
|
customerKeyHeader: h.Get(xhttp.AmzServerSideEncryptionCustomerKey),
|
||||||
copySource: copySource,
|
copySource: copySource,
|
||||||
metadata: cloneMSS(oi.UserDefined),
|
metadata: cloneMSS(oi.UserDefined),
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.copySource {
|
if w.copySource {
|
||||||
w.customerKeyHeader = h.Get(crypto.SSECopyKey)
|
w.customerKeyHeader = h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil {
|
if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil {
|
||||||
@@ -434,12 +434,12 @@ func (d *DecryptBlocksReader) buildDecrypter(partID int) error {
|
|||||||
var err error
|
var err error
|
||||||
if d.copySource {
|
if d.copySource {
|
||||||
if crypto.SSEC.IsEncrypted(d.metadata) {
|
if crypto.SSEC.IsEncrypted(d.metadata) {
|
||||||
d.header.Set(crypto.SSECopyKey, d.customerKeyHeader)
|
d.header.Set(xhttp.AmzServerSideEncryptionCopyCustomerKey, d.customerKeyHeader)
|
||||||
key, err = ParseSSECopyCustomerRequest(d.header, d.metadata)
|
key, err = ParseSSECopyCustomerRequest(d.header, d.metadata)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if crypto.SSEC.IsEncrypted(d.metadata) {
|
if crypto.SSEC.IsEncrypted(d.metadata) {
|
||||||
d.header.Set(crypto.SSECKey, d.customerKeyHeader)
|
d.header.Set(xhttp.AmzServerSideEncryptionCustomerKey, d.customerKeyHeader)
|
||||||
key, err = ParseSSECustomerHeader(d.header)
|
key, err = ParseSSECustomerHeader(d.header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-50
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/sio"
|
"github.com/minio/sio"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,27 +39,27 @@ var encryptRequestTests = []struct {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
header: map[string]string{
|
header: map[string]string{
|
||||||
crypto.SSECAlgorithm: "AES256",
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
|
||||||
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
|
xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
|
||||||
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
|
||||||
},
|
},
|
||||||
metadata: map[string]string{},
|
metadata: map[string]string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: map[string]string{
|
header: map[string]string{
|
||||||
crypto.SSECAlgorithm: "AES256",
|
xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
|
||||||
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
|
xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
|
||||||
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
|
||||||
},
|
},
|
||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
|
xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptRequest(t *testing.T) {
|
func TestEncryptRequest(t *testing.T) {
|
||||||
defer func(flag bool) { globalIsSSL = flag }(globalIsSSL)
|
defer func(flag bool) { globalIsTLS = flag }(globalIsTLS)
|
||||||
globalIsSSL = true
|
globalIsTLS = true
|
||||||
for i, test := range encryptRequestTests {
|
for i, test := range encryptRequestTests {
|
||||||
content := bytes.NewReader(make([]byte, 64))
|
content := bytes.NewReader(make([]byte, 64))
|
||||||
req := &http.Request{Header: http.Header{}}
|
req := &http.Request{Header: http.Header{}}
|
||||||
@@ -70,13 +71,13 @@ func TestEncryptRequest(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d: Failed to encrypt request: %v", i, err)
|
t.Fatalf("Test %d: Failed to encrypt request: %v", i, err)
|
||||||
}
|
}
|
||||||
if kdf, ok := test.metadata[crypto.SSESealAlgorithm]; !ok {
|
if kdf, ok := test.metadata[crypto.MetaAlgorithm]; !ok {
|
||||||
t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf)
|
t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf)
|
||||||
}
|
}
|
||||||
if iv, ok := test.metadata[crypto.SSEIV]; !ok {
|
if iv, ok := test.metadata[crypto.MetaIV]; !ok {
|
||||||
t.Errorf("Test %d: crypto.SSEIV must be part of metadata: %v", i, iv)
|
t.Errorf("Test %d: crypto.SSEIV must be part of metadata: %v", i, iv)
|
||||||
}
|
}
|
||||||
if mac, ok := test.metadata[crypto.SSECSealedKey]; !ok {
|
if mac, ok := test.metadata[crypto.MetaSealedKeySSEC]; !ok {
|
||||||
t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac)
|
t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,33 +94,33 @@ var decryptObjectInfoTests = []struct {
|
|||||||
expErr: nil,
|
expErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
|
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
|
||||||
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
|
||||||
expErr: nil,
|
expErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
|
info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
|
||||||
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
|
||||||
expErr: nil,
|
expErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSECSealedKey: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}},
|
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaSealedKeySSEC: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}},
|
||||||
request: &http.Request{Header: http.Header{}},
|
request: &http.Request{Header: http.Header{}},
|
||||||
expErr: errEncryptedObject,
|
expErr: errEncryptedObject,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
|
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
|
||||||
request: &http.Request{Method: http.MethodGet, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
request: &http.Request{Method: http.MethodGet, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
|
||||||
expErr: errInvalidEncryptionParameters,
|
expErr: errInvalidEncryptionParameters,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
|
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
|
||||||
request: &http.Request{Method: http.MethodHead, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
request: &http.Request{Method: http.MethodHead, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
|
||||||
expErr: errInvalidEncryptionParameters,
|
expErr: errInvalidEncryptionParameters,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
|
info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
|
||||||
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
|
||||||
expErr: errObjectTampered,
|
expErr: errObjectTampered,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -221,10 +222,10 @@ func TestGetDecryptedRange_Issue50(t *testing.T) {
|
|||||||
Name: "object",
|
Name: "object",
|
||||||
Size: 595160760,
|
Size: 595160760,
|
||||||
UserDefined: map[string]string{
|
UserDefined: map[string]string{
|
||||||
crypto.SSEMultipart: "",
|
crypto.MetaMultipart: "",
|
||||||
crypto.SSEIV: "HTexa=",
|
crypto.MetaIV: "HTexa=",
|
||||||
crypto.SSESealAlgorithm: "DAREv2-HMAC-SHA256",
|
crypto.MetaAlgorithm: "DAREv2-HMAC-SHA256",
|
||||||
crypto.SSECSealedKey: "IAA8PGAA==",
|
crypto.MetaSealedKeySSEC: "IAA8PGAA==",
|
||||||
ReservedMetadataPrefix + "actual-size": "594870264",
|
ReservedMetadataPrefix + "actual-size": "594870264",
|
||||||
"content-type": "application/octet-stream",
|
"content-type": "application/octet-stream",
|
||||||
"etag": "166b1545b4c1535294ee0686678bea8c-2",
|
"etag": "166b1545b4c1535294ee0686678bea8c-2",
|
||||||
@@ -276,11 +277,11 @@ func TestGetDecryptedRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
udMap = func(isMulti bool) map[string]string {
|
udMap = func(isMulti bool) map[string]string {
|
||||||
m := map[string]string{
|
m := map[string]string{
|
||||||
crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm,
|
crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm,
|
||||||
crypto.SSEMultipart: "1",
|
crypto.MetaMultipart: "1",
|
||||||
}
|
}
|
||||||
if !isMulti {
|
if !isMulti {
|
||||||
delete(m, crypto.SSEMultipart)
|
delete(m, crypto.MetaMultipart)
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -553,56 +554,56 @@ var getDefaultOptsTests = []struct {
|
|||||||
encryptionType encrypt.Type
|
encryptionType encrypt.Type
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"},
|
{headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
|
||||||
crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
||||||
copySource: false,
|
copySource: false,
|
||||||
metadata: nil,
|
metadata: nil,
|
||||||
encryptionType: encrypt.SSEC,
|
encryptionType: encrypt.SSEC,
|
||||||
err: nil}, // 0
|
err: nil}, // 0
|
||||||
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"},
|
{headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
|
||||||
crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
||||||
copySource: true,
|
copySource: true,
|
||||||
metadata: nil,
|
metadata: nil,
|
||||||
encryptionType: "",
|
encryptionType: "",
|
||||||
err: nil}, // 1
|
err: nil}, // 1
|
||||||
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"},
|
{headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
|
||||||
crypto.SSECKey: []string{"Mz"},
|
xhttp.AmzServerSideEncryptionCustomerKey: []string{"Mz"},
|
||||||
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
||||||
copySource: false,
|
copySource: false,
|
||||||
metadata: nil,
|
metadata: nil,
|
||||||
encryptionType: "",
|
encryptionType: "",
|
||||||
err: crypto.ErrInvalidCustomerKey}, // 2
|
err: crypto.ErrInvalidCustomerKey}, // 2
|
||||||
{headers: http.Header{crypto.SSEHeader: []string{"AES256"}},
|
{headers: http.Header{xhttp.AmzServerSideEncryption: []string{"AES256"}},
|
||||||
copySource: false,
|
copySource: false,
|
||||||
metadata: nil,
|
metadata: nil,
|
||||||
encryptionType: encrypt.S3,
|
encryptionType: encrypt.S3,
|
||||||
err: nil}, // 3
|
err: nil}, // 3
|
||||||
{headers: http.Header{},
|
{headers: http.Header{},
|
||||||
copySource: false,
|
copySource: false,
|
||||||
metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)),
|
metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
|
||||||
crypto.S3KMSKeyID: "kms-key",
|
crypto.MetaKeyID: "kms-key",
|
||||||
crypto.S3KMSSealedKey: "m-key"},
|
crypto.MetaDataEncryptionKey: "m-key"},
|
||||||
encryptionType: encrypt.S3,
|
encryptionType: encrypt.S3,
|
||||||
err: nil}, // 4
|
err: nil}, // 4
|
||||||
{headers: http.Header{},
|
{headers: http.Header{},
|
||||||
copySource: true,
|
copySource: true,
|
||||||
metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)),
|
metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
|
||||||
crypto.S3KMSKeyID: "kms-key",
|
crypto.MetaKeyID: "kms-key",
|
||||||
crypto.S3KMSSealedKey: "m-key"},
|
crypto.MetaDataEncryptionKey: "m-key"},
|
||||||
encryptionType: "",
|
encryptionType: "",
|
||||||
err: nil}, // 5
|
err: nil}, // 5
|
||||||
{headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"},
|
{headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
|
||||||
crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
||||||
copySource: true,
|
copySource: true,
|
||||||
metadata: nil,
|
metadata: nil,
|
||||||
encryptionType: encrypt.SSEC,
|
encryptionType: encrypt.SSEC,
|
||||||
err: nil}, // 6
|
err: nil}, // 6
|
||||||
{headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"},
|
{headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
|
||||||
crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
|
||||||
crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
|
||||||
copySource: false,
|
copySource: false,
|
||||||
metadata: nil,
|
metadata: nil,
|
||||||
encryptionType: "",
|
encryptionType: "",
|
||||||
|
|||||||
+26
-15
@@ -18,7 +18,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -38,7 +37,6 @@ import (
|
|||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/cmd/rest"
|
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
"github.com/minio/minio/pkg/mountinfo"
|
"github.com/minio/minio/pkg/mountinfo"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
xnet "github.com/minio/minio/pkg/net"
|
||||||
@@ -59,7 +57,7 @@ const (
|
|||||||
// See proxyRequest() for details.
|
// See proxyRequest() for details.
|
||||||
type ProxyEndpoint struct {
|
type ProxyEndpoint struct {
|
||||||
Endpoint
|
Endpoint
|
||||||
Transport *http.Transport
|
Transport http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint - any type of endpoint.
|
// Endpoint - any type of endpoint.
|
||||||
@@ -242,6 +240,22 @@ func (l *EndpointServerPools) Add(zeps ZoneEndpoints) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Localhost - returns the local hostname from list of endpoints
|
||||||
|
func (l EndpointServerPools) Localhost() string {
|
||||||
|
for _, ep := range l {
|
||||||
|
for _, endpoint := range ep.Endpoints {
|
||||||
|
if endpoint.IsLocal {
|
||||||
|
u := &url.URL{
|
||||||
|
Scheme: endpoint.Scheme,
|
||||||
|
Host: endpoint.Host,
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// FirstLocal returns true if the first endpoint is local.
|
// FirstLocal returns true if the first endpoint is local.
|
||||||
func (l EndpointServerPools) FirstLocal() bool {
|
func (l EndpointServerPools) FirstLocal() bool {
|
||||||
return l[0].Endpoints[0].IsLocal
|
return l[0].Endpoints[0].IsLocal
|
||||||
@@ -337,6 +351,14 @@ func (endpoints Endpoints) GetString(i int) string {
|
|||||||
return endpoints[i].String()
|
return endpoints[i].String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllStrings - returns allstring of all endpoints
|
||||||
|
func (endpoints Endpoints) GetAllStrings() (all []string) {
|
||||||
|
for _, e := range endpoints {
|
||||||
|
all = append(all, e.String())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func hostResolveToLocalhost(endpoint Endpoint) bool {
|
func hostResolveToLocalhost(endpoint Endpoint) bool {
|
||||||
hostIPs, err := getHostIP(endpoint.Hostname())
|
hostIPs, err := getHostIP(endpoint.Hostname())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -857,20 +879,9 @@ func GetProxyEndpoints(endpointServerPools EndpointServerPools) []ProxyEndpoint
|
|||||||
}
|
}
|
||||||
proxyEpSet.Add(host)
|
proxyEpSet.Add(host)
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
if globalIsSSL {
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
ServerName: endpoint.Hostname(),
|
|
||||||
RootCAs: globalRootCAs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow transport to be HTTP/1.1 for proxying.
|
|
||||||
tr := newCustomHTTPProxyTransport(tlsConfig, rest.DefaultTimeout)()
|
|
||||||
|
|
||||||
proxyEps = append(proxyEps, ProxyEndpoint{
|
proxyEps = append(proxyEps, ProxyEndpoint{
|
||||||
Endpoint: endpoint,
|
Endpoint: endpoint,
|
||||||
Transport: tr,
|
Transport: globalProxyTransport,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ import (
|
|||||||
// Heals a bucket if it doesn't exist on one of the disks, additionally
|
// Heals a bucket if it doesn't exist on one of the disks, additionally
|
||||||
// also heals the missing entries for bucket metadata files
|
// also heals the missing entries for bucket metadata files
|
||||||
// `policy.json, notification.xml, listeners.json`.
|
// `policy.json, notification.xml, listeners.json`.
|
||||||
func (er erasureObjects) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (
|
func (er erasureObjects) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||||
result madmin.HealResultItem, err error) {
|
result madmin.HealResultItem, err error) {
|
||||||
if !dryRun {
|
if !opts.DryRun {
|
||||||
defer ObjectPathUpdated(bucket)
|
defer ObjectPathUpdated(bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ func (er erasureObjects) HealBucket(ctx context.Context, bucket string, dryRun,
|
|||||||
writeQuorum := getWriteQuorum(len(storageDisks))
|
writeQuorum := getWriteQuorum(len(storageDisks))
|
||||||
|
|
||||||
// Heal bucket.
|
// Heal bucket.
|
||||||
return healBucket(ctx, storageDisks, storageEndpoints, bucket, writeQuorum, dryRun)
|
return healBucket(ctx, storageDisks, storageEndpoints, bucket, writeQuorum, opts.DryRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal bucket - create buckets on disks where it does not exist.
|
// Heal bucket - create buckets on disks where it does not exist.
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ func TestHealing(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// This would create the bucket.
|
// This would create the bucket.
|
||||||
_, err = er.HealBucket(ctx, bucket, false, false)
|
_, err = er.HealBucket(ctx, bucket, madmin.HealOpts{
|
||||||
|
DryRun: false,
|
||||||
|
Remove: false,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-27
@@ -89,27 +89,12 @@ func (er erasureObjects) removeObjectPart(bucket, object, uploadID, dataDir stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean-up the old multipart uploads. Should be run in a Go routine.
|
// Clean-up the old multipart uploads. Should be run in a Go routine.
|
||||||
func (er erasureObjects) cleanupStaleUploads(ctx context.Context, cleanupInterval, expiry time.Duration) {
|
func (er erasureObjects) cleanupStaleUploads(ctx context.Context, expiry time.Duration) {
|
||||||
ticker := time.NewTicker(cleanupInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
var disk StorageAPI
|
|
||||||
// run multiple cleanup's local to this server.
|
// run multiple cleanup's local to this server.
|
||||||
for _, d := range er.getLoadBalancedLocalDisks() {
|
for _, disk := range er.getLoadBalancedLocalDisks() {
|
||||||
if d != nil {
|
if disk != nil {
|
||||||
disk = d
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if disk == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
er.cleanupStaleUploadsOnDisk(ctx, disk, expiry)
|
er.cleanupStaleUploadsOnDisk(ctx, disk, expiry)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,8 +367,10 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
|||||||
return pi, toObjectErr(err, bucket, object, uploadID)
|
return pi, toObjectErr(err, bucket, object, uploadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storageDisks := er.getDisks()
|
||||||
|
|
||||||
// Read metadata associated with the object from all disks.
|
// Read metadata associated with the object from all disks.
|
||||||
partsMetadata, errs = readAllFileInfo(ctx, er.getDisks(), minioMetaMultipartBucket,
|
partsMetadata, errs = readAllFileInfo(ctx, storageDisks, minioMetaMultipartBucket,
|
||||||
uploadIDPath, "")
|
uploadIDPath, "")
|
||||||
|
|
||||||
// get Quorum for this object
|
// get Quorum for this object
|
||||||
@@ -398,7 +385,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List all online disks.
|
// List all online disks.
|
||||||
onlineDisks, modTime := listOnlineDisks(er.getDisks(), partsMetadata, errs)
|
onlineDisks, modTime := listOnlineDisks(storageDisks, partsMetadata, errs)
|
||||||
|
|
||||||
// Pick one from the first valid metadata.
|
// Pick one from the first valid metadata.
|
||||||
fi, err := pickValidFileInfo(ctx, partsMetadata, modTime, writeQuorum)
|
fi, err := pickValidFileInfo(ctx, partsMetadata, modTime, writeQuorum)
|
||||||
@@ -859,22 +846,22 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is any offline disk and add it to the MRF list
|
// Check if there is any offline disk and add it to the MRF list
|
||||||
for i, disk := range onlineDisks {
|
for _, disk := range onlineDisks {
|
||||||
if disk == nil || storageDisks[i] == nil {
|
if disk != nil && disk.IsOnline() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
er.addPartial(bucket, object, fi.VersionID)
|
er.addPartial(bucket, object, fi.VersionID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(onlineDisks); i++ {
|
for i := 0; i < len(onlineDisks); i++ {
|
||||||
if onlineDisks[i] == nil {
|
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Object info is the same in all disks, so we can pick
|
// Object info is the same in all disks, so we can pick
|
||||||
// the first meta from online disk
|
// the first meta from online disk
|
||||||
fi = partsMetadata[i]
|
fi = partsMetadata[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Success, return object info.
|
// Success, return object info.
|
||||||
return fi.ToObjectInfo(bucket, object), nil
|
return fi.ToObjectInfo(bucket, object), nil
|
||||||
|
|||||||
+17
-16
@@ -608,7 +608,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize parts metadata
|
// Initialize parts metadata
|
||||||
partsMetadata := make([]FileInfo, len(er.getDisks()))
|
partsMetadata := make([]FileInfo, len(storageDisks))
|
||||||
|
|
||||||
fi := newFileInfo(object, dataDrives, parityDrives)
|
fi := newFileInfo(object, dataDrives, parityDrives)
|
||||||
|
|
||||||
@@ -674,11 +674,13 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
return ObjectInfo{}, IncompleteBody{Bucket: bucket, Object: object}
|
return ObjectInfo{}, IncompleteBody{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !opts.NoLock {
|
||||||
lk := er.NewNSLock(bucket, object)
|
lk := er.NewNSLock(bucket, object)
|
||||||
if err := lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
if err := lk.GetLock(ctx, globalOperationTimeout); err != nil {
|
||||||
return ObjectInfo{}, err
|
return ObjectInfo{}, err
|
||||||
}
|
}
|
||||||
defer lk.Unlock()
|
defer lk.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
for i, w := range writers {
|
for i, w := range writers {
|
||||||
if w == nil {
|
if w == nil {
|
||||||
@@ -727,21 +729,21 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
// Whether a disk was initially or becomes offline
|
// Whether a disk was initially or becomes offline
|
||||||
// during this upload, send it to the MRF list.
|
// during this upload, send it to the MRF list.
|
||||||
for i := 0; i < len(onlineDisks); i++ {
|
for i := 0; i < len(onlineDisks); i++ {
|
||||||
if onlineDisks[i] == nil || storageDisks[i] == nil {
|
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
er.addPartial(bucket, object, fi.VersionID)
|
er.addPartial(bucket, object, fi.VersionID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(onlineDisks); i++ {
|
for i := 0; i < len(onlineDisks); i++ {
|
||||||
if onlineDisks[i] == nil {
|
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Object info is the same in all disks, so we can pick
|
// Object info is the same in all disks, so we can pick
|
||||||
// the first meta from online disk
|
// the first meta from online disk
|
||||||
fi = partsMetadata[i]
|
fi = partsMetadata[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fi.ToObjectInfo(bucket, object), nil
|
return fi.ToObjectInfo(bucket, object), nil
|
||||||
}
|
}
|
||||||
@@ -767,19 +769,18 @@ func (er erasureObjects) deleteObjectVersion(ctx context.Context, bucket, object
|
|||||||
// all the disks in parallel, including `xl.meta` associated with the
|
// all the disks in parallel, including `xl.meta` associated with the
|
||||||
// object.
|
// object.
|
||||||
func (er erasureObjects) deleteObject(ctx context.Context, bucket, object string, writeQuorum int) error {
|
func (er erasureObjects) deleteObject(ctx context.Context, bucket, object string, writeQuorum int) error {
|
||||||
var disks []StorageAPI
|
|
||||||
var err error
|
var err error
|
||||||
defer ObjectPathUpdated(pathJoin(bucket, object))
|
defer ObjectPathUpdated(pathJoin(bucket, object))
|
||||||
|
|
||||||
tmpObj := mustGetUUID()
|
tmpObj := mustGetUUID()
|
||||||
|
disks := er.getDisks()
|
||||||
if bucket == minioMetaTmpBucket {
|
if bucket == minioMetaTmpBucket {
|
||||||
tmpObj = object
|
tmpObj = object
|
||||||
disks = er.getDisks()
|
|
||||||
} else {
|
} else {
|
||||||
// Rename the current object while requiring write quorum, but also consider
|
// Rename the current object while requiring write quorum, but also consider
|
||||||
// that a non found object in a given disk as a success since it already
|
// that a non found object in a given disk as a success since it already
|
||||||
// confirms that the object doesn't have a part in that disk (already removed)
|
// confirms that the object doesn't have a part in that disk (already removed)
|
||||||
disks, err = rename(ctx, er.getDisks(), bucket, object, minioMetaTmpBucket, tmpObj, true, writeQuorum,
|
disks, err = rename(ctx, disks, bucket, object, minioMetaTmpBucket, tmpObj, true, writeQuorum,
|
||||||
[]error{errFileNotFound})
|
[]error{errFileNotFound})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toObjectErr(err, bucket, object)
|
return toObjectErr(err, bucket, object)
|
||||||
@@ -787,7 +788,6 @@ func (er erasureObjects) deleteObject(ctx context.Context, bucket, object string
|
|||||||
}
|
}
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(disks))
|
g := errgroup.WithNErrs(len(disks))
|
||||||
|
|
||||||
for index := range disks {
|
for index := range disks {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
@@ -924,18 +924,17 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
|
|||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
// Check if there is any offline disk and add it to the MRF list
|
// Check if there is any offline disk and add it to the MRF list
|
||||||
for _, disk := range storageDisks {
|
for _, disk := range storageDisks {
|
||||||
if disk == nil {
|
if disk != nil && disk.IsOnline() {
|
||||||
// ignore delete markers for quorum
|
// Skip attempted heal on online disks.
|
||||||
if version.Deleted {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// all other direct versionId references we should
|
// all other direct versionId references we should
|
||||||
// ensure no dangling file is left over.
|
// ensure no dangling file is left over.
|
||||||
er.addPartial(bucket, version.Name, version.VersionID)
|
er.addPartial(bucket, version.Name, version.VersionID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return dobjects, errs
|
return dobjects, errs
|
||||||
}
|
}
|
||||||
@@ -945,6 +944,7 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
|
|||||||
// response to the client request.
|
// response to the client request.
|
||||||
func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
||||||
versionFound := true
|
versionFound := true
|
||||||
|
objInfo = ObjectInfo{VersionID: opts.VersionID} // version id needed in Delete API response.
|
||||||
goi, gerr := er.GetObjectInfo(ctx, bucket, object, opts)
|
goi, gerr := er.GetObjectInfo(ctx, bucket, object, opts)
|
||||||
if gerr != nil && goi.Name == "" {
|
if gerr != nil && goi.Name == "" {
|
||||||
switch gerr.(type) {
|
switch gerr.(type) {
|
||||||
@@ -1043,11 +1043,12 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, disk := range storageDisks {
|
for _, disk := range storageDisks {
|
||||||
if disk == nil {
|
if disk != nil && disk.IsOnline() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
er.addPartial(bucket, object, opts.VersionID)
|
er.addPartial(bucket, object, opts.VersionID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return ObjectInfo{
|
return ObjectInfo{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
|
|||||||
+73
-62
@@ -23,6 +23,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -99,16 +100,22 @@ func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI {
|
|||||||
idMap[id] = struct{}{}
|
idMap[id] = struct{}{}
|
||||||
}
|
}
|
||||||
res := make([]StorageAPI, 0, len(idMap))
|
res := make([]StorageAPI, 0, len(idMap))
|
||||||
for _, ss := range z.serverPools {
|
for _, s := range z.serverPools {
|
||||||
for _, disks := range ss.erasureDisks {
|
s.erasureDisksMu.RLock()
|
||||||
|
defer s.erasureDisksMu.RUnlock()
|
||||||
|
for _, disks := range s.erasureDisks {
|
||||||
for _, disk := range disks {
|
for _, disk := range disks {
|
||||||
id, _ := disk.GetDiskID()
|
if disk == OfflineDisk {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if id, _ := disk.GetDiskID(); id != "" {
|
||||||
if _, ok := idMap[id]; ok {
|
if _, ok := idMap[id]; ok {
|
||||||
res = append(res, disk)
|
res = append(res, disk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,9 +273,24 @@ func (z *erasureServerPools) Shutdown(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (z *erasureServerPools) BackendInfo() (b BackendInfo) {
|
||||||
|
b.Type = BackendErasure
|
||||||
|
|
||||||
|
scParity := globalStorageClass.GetParityForSC(storageclass.STANDARD)
|
||||||
|
if scParity == 0 {
|
||||||
|
scParity = z.SetDriveCount() / 2
|
||||||
|
}
|
||||||
|
b.StandardSCData = z.SetDriveCount() - scParity
|
||||||
|
b.StandardSCParity = scParity
|
||||||
|
|
||||||
|
rrSCParity := globalStorageClass.GetParityForSC(storageclass.RRS)
|
||||||
|
b.RRSCData = z.SetDriveCount() - rrSCParity
|
||||||
|
b.RRSCParity = rrSCParity
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) StorageInfo(ctx context.Context, local bool) (StorageInfo, []error) {
|
func (z *erasureServerPools) StorageInfo(ctx context.Context, local bool) (StorageInfo, []error) {
|
||||||
var storageInfo StorageInfo
|
var storageInfo StorageInfo
|
||||||
storageInfo.Backend.Type = BackendErasure
|
|
||||||
|
|
||||||
storageInfos := make([]StorageInfo, len(z.serverPools))
|
storageInfos := make([]StorageInfo, len(z.serverPools))
|
||||||
storageInfosErrs := make([][]error, len(z.serverPools))
|
storageInfosErrs := make([][]error, len(z.serverPools))
|
||||||
@@ -284,23 +306,11 @@ func (z *erasureServerPools) StorageInfo(ctx context.Context, local bool) (Stora
|
|||||||
// Wait for the go routines.
|
// Wait for the go routines.
|
||||||
g.Wait()
|
g.Wait()
|
||||||
|
|
||||||
|
storageInfo.Backend = z.BackendInfo()
|
||||||
for _, lstorageInfo := range storageInfos {
|
for _, lstorageInfo := range storageInfos {
|
||||||
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||||
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
|
|
||||||
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scParity := globalStorageClass.GetParityForSC(storageclass.STANDARD)
|
|
||||||
if scParity == 0 {
|
|
||||||
scParity = z.SetDriveCount() / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
storageInfo.Backend.StandardSCData = z.SetDriveCount() - scParity
|
|
||||||
storageInfo.Backend.StandardSCParity = scParity
|
|
||||||
rrSCParity := globalStorageClass.GetParityForSC(storageclass.RRS)
|
|
||||||
storageInfo.Backend.RRSCData = z.SetDriveCount() - rrSCParity
|
|
||||||
storageInfo.Backend.RRSCParity = rrSCParity
|
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
for i := range z.serverPools {
|
for i := range z.serverPools {
|
||||||
errs = append(errs, storageInfosErrs[i]...)
|
errs = append(errs, storageInfosErrs[i]...)
|
||||||
@@ -316,23 +326,19 @@ func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloom
|
|||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
var results []dataUsageCache
|
var results []dataUsageCache
|
||||||
var firstErr error
|
var firstErr error
|
||||||
var knownBuckets = make(map[string]struct{}) // used to deduplicate buckets.
|
|
||||||
var allBuckets []BucketInfo
|
|
||||||
|
|
||||||
// Collect for each set in serverPools.
|
allBuckets, err := z.ListBuckets(ctx)
|
||||||
for _, z := range z.serverPools {
|
|
||||||
buckets, err := z.ListBuckets(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Add new buckets.
|
|
||||||
for _, b := range buckets {
|
// Crawl latest allBuckets first.
|
||||||
if _, ok := knownBuckets[b.Name]; ok {
|
sort.Slice(allBuckets, func(i, j int) bool {
|
||||||
continue
|
return allBuckets[i].Created.After(allBuckets[j].Created)
|
||||||
}
|
})
|
||||||
allBuckets = append(allBuckets, b)
|
|
||||||
knownBuckets[b.Name] = struct{}{}
|
// Collect for each set in serverPools.
|
||||||
}
|
for _, z := range z.serverPools {
|
||||||
for _, erObj := range z.sets {
|
for _, erObj := range z.sets {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
results = append(results, dataUsageCache{})
|
results = append(results, dataUsageCache{})
|
||||||
@@ -349,7 +355,7 @@ func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloom
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Start crawler. Blocks until done.
|
// Start crawler. Blocks until done.
|
||||||
err := erObj.crawlAndGetDataUsage(ctx, buckets, bf, updates)
|
err := erObj.crawlAndGetDataUsage(ctx, allBuckets, bf, updates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -701,7 +707,7 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
|||||||
// more objects matching the prefix.
|
// more objects matching the prefix.
|
||||||
ri := logger.GetReqInfo(ctx)
|
ri := logger.GetReqInfo(ctx)
|
||||||
if ri != nil && strings.Contains(ri.UserAgent, `1.0 Veeam/1.0 Backup`) && strings.HasSuffix(prefix, ".blk") {
|
if ri != nil && strings.Contains(ri.UserAgent, `1.0 Veeam/1.0 Backup`) && strings.HasSuffix(prefix, ".blk") {
|
||||||
opts.singleObject = true
|
opts.discardResult = true
|
||||||
opts.Transient = true
|
opts.Transient = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,14 +715,21 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
|||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return loi, err
|
return loi, err
|
||||||
}
|
}
|
||||||
loi.Objects, loi.Prefixes = merged.fileInfoVersions(bucket, prefix, delimiter, versionMarker)
|
objects := merged.fileInfoVersions(bucket, prefix, delimiter, versionMarker)
|
||||||
loi.IsTruncated = err == nil && len(loi.Objects) > 0
|
loi.IsTruncated = err == nil && len(objects) > 0
|
||||||
if maxKeys > 0 && len(loi.Objects) > maxKeys {
|
if maxKeys > 0 && len(objects) > maxKeys {
|
||||||
loi.Objects = loi.Objects[:maxKeys]
|
objects = objects[:maxKeys]
|
||||||
loi.IsTruncated = true
|
loi.IsTruncated = true
|
||||||
}
|
}
|
||||||
|
for _, obj := range objects {
|
||||||
|
if obj.IsDir && delimiter != "" {
|
||||||
|
loi.Prefixes = append(loi.Prefixes, obj.Name)
|
||||||
|
} else {
|
||||||
|
loi.Objects = append(loi.Objects, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
if loi.IsTruncated {
|
if loi.IsTruncated {
|
||||||
last := loi.Objects[len(loi.Objects)-1]
|
last := objects[len(objects)-1]
|
||||||
loi.NextMarker = encodeMarker(last.Name, merged.listID)
|
loi.NextMarker = encodeMarker(last.Name, merged.listID)
|
||||||
loi.NextVersionIDMarker = last.VersionID
|
loi.NextVersionIDMarker = last.VersionID
|
||||||
}
|
}
|
||||||
@@ -725,6 +738,7 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
|||||||
|
|
||||||
func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||||
var loi ListObjectsInfo
|
var loi ListObjectsInfo
|
||||||
|
|
||||||
merged, err := z.listPath(ctx, listPathOptions{
|
merged, err := z.listPath(ctx, listPathOptions{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
@@ -738,11 +752,24 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return loi, err
|
return loi, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default is recursive, if delimiter is set then list non recursive.
|
// Default is recursive, if delimiter is set then list non recursive.
|
||||||
loi.Objects, loi.Prefixes = merged.fileInfos(bucket, prefix, delimiter)
|
objects := merged.fileInfos(bucket, prefix, delimiter)
|
||||||
loi.IsTruncated = err == nil && len(loi.Objects) > 0
|
loi.IsTruncated = err == nil && len(objects) > 0
|
||||||
|
if maxKeys > 0 && len(objects) > maxKeys {
|
||||||
|
objects = objects[:maxKeys]
|
||||||
|
loi.IsTruncated = true
|
||||||
|
}
|
||||||
|
for _, obj := range objects {
|
||||||
|
if obj.IsDir && delimiter != "" {
|
||||||
|
loi.Prefixes = append(loi.Prefixes, obj.Name)
|
||||||
|
} else {
|
||||||
|
loi.Objects = append(loi.Objects, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
if loi.IsTruncated {
|
if loi.IsTruncated {
|
||||||
loi.NextMarker = encodeMarker(loi.Objects[len(loi.Objects)-1].Name, merged.listID)
|
last := objects[len(objects)-1]
|
||||||
|
loi.NextMarker = encodeMarker(last.Name, merged.listID)
|
||||||
}
|
}
|
||||||
return loi, nil
|
return loi, nil
|
||||||
}
|
}
|
||||||
@@ -1147,14 +1174,17 @@ func (z *erasureServerPools) HealFormat(ctx context.Context, dryRun bool) (madmi
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error) {
|
func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||||
var r = madmin.HealResultItem{
|
var r = madmin.HealResultItem{
|
||||||
Type: madmin.HealItemBucket,
|
Type: madmin.HealItemBucket,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt heal on the bucket metadata, ignore any failures
|
||||||
|
_, _ = z.HealObject(ctx, minioMetaBucket, pathJoin(bucketConfigPrefix, bucket, bucketMetadataFile), "", opts)
|
||||||
|
|
||||||
for _, zone := range z.serverPools {
|
for _, zone := range z.serverPools {
|
||||||
result, err := zone.HealBucket(ctx, bucket, dryRun, remove)
|
result, err := zone.HealBucket(ctx, bucket, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case BucketNotFound:
|
case BucketNotFound:
|
||||||
@@ -1167,6 +1197,7 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, dryR
|
|||||||
r.Before.Drives = append(r.Before.Drives, result.Before.Drives...)
|
r.Before.Drives = append(r.Before.Drives, result.Before.Drives...)
|
||||||
r.After.Drives = append(r.After.Drives, result.After.Drives...)
|
r.After.Drives = append(r.After.Drives, result.After.Drives...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1336,26 +1367,6 @@ func (z *erasureServerPools) HealObject(ctx context.Context, bucket, object, ver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *erasureServerPools) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
|
|
||||||
var healBuckets []BucketInfo
|
|
||||||
for _, zone := range z.serverPools {
|
|
||||||
bucketsInfo, err := zone.ListBucketsHeal(ctx)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
healBuckets = append(healBuckets, bucketsInfo...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range healBuckets {
|
|
||||||
meta, err := globalBucketMetadataSys.Get(healBuckets[i].Name)
|
|
||||||
if err == nil {
|
|
||||||
healBuckets[i].Created = meta.Created
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return healBuckets, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetrics - no op
|
// GetMetrics - no op
|
||||||
func (z *erasureServerPools) GetMetrics(ctx context.Context) (*Metrics, error) {
|
func (z *erasureServerPools) GetMetrics(ctx context.Context) (*Metrics, error) {
|
||||||
logger.LogIf(ctx, NotImplemented{})
|
logger.LogIf(ctx, NotImplemented{})
|
||||||
|
|||||||
+111
-63
@@ -30,9 +30,11 @@ import (
|
|||||||
|
|
||||||
"github.com/dchest/siphash"
|
"github.com/dchest/siphash"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/bpool"
|
"github.com/minio/minio/pkg/bpool"
|
||||||
|
"github.com/minio/minio/pkg/console"
|
||||||
"github.com/minio/minio/pkg/dsync"
|
"github.com/minio/minio/pkg/dsync"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"github.com/minio/minio/pkg/sync/errgroup"
|
||||||
@@ -244,10 +246,13 @@ func (s *erasureSets) connectDisks() {
|
|||||||
s.endpointStrings[setIndex*s.setDriveCount+diskIndex] = disk.String()
|
s.endpointStrings[setIndex*s.setDriveCount+diskIndex] = disk.String()
|
||||||
s.erasureDisksMu.Unlock()
|
s.erasureDisksMu.Unlock()
|
||||||
go func(setIndex int) {
|
go func(setIndex int) {
|
||||||
|
idler := time.NewTimer(100 * time.Millisecond)
|
||||||
|
defer idler.Stop()
|
||||||
|
|
||||||
// Send a new disk connect event with a timeout
|
// Send a new disk connect event with a timeout
|
||||||
select {
|
select {
|
||||||
case s.disksConnectEvent <- diskConnectInfo{setIndex: setIndex}:
|
case s.disksConnectEvent <- diskConnectInfo{setIndex: setIndex}:
|
||||||
case <-time.After(100 * time.Millisecond):
|
case <-idler.C:
|
||||||
}
|
}
|
||||||
}(setIndex)
|
}(setIndex)
|
||||||
}(endpoint)
|
}(endpoint)
|
||||||
@@ -266,11 +271,21 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
|
|||||||
// Pre-emptively connect the disks if possible.
|
// Pre-emptively connect the disks if possible.
|
||||||
s.connectDisks()
|
s.connectDisks()
|
||||||
|
|
||||||
|
monitor := time.NewTimer(monitorInterval)
|
||||||
|
defer monitor.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.After(monitorInterval):
|
case <-monitor.C:
|
||||||
|
// Reset the timer once fired for required interval.
|
||||||
|
monitor.Reset(monitorInterval)
|
||||||
|
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugln("running disk monitoring")
|
||||||
|
}
|
||||||
|
|
||||||
s.connectDisks()
|
s.connectDisks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,10 +293,20 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
|
|||||||
|
|
||||||
// GetAllLockers return a list of all lockers for all sets.
|
// GetAllLockers return a list of all lockers for all sets.
|
||||||
func (s *erasureSets) GetAllLockers() []dsync.NetLocker {
|
func (s *erasureSets) GetAllLockers() []dsync.NetLocker {
|
||||||
allLockers := make([]dsync.NetLocker, s.setDriveCount*s.setCount)
|
var allLockers []dsync.NetLocker
|
||||||
for i, lockers := range s.erasureLockers {
|
lockEpSet := set.NewStringSet()
|
||||||
for j, locker := range lockers {
|
for _, lockers := range s.erasureLockers {
|
||||||
allLockers[i*s.setDriveCount+j] = locker
|
for _, locker := range lockers {
|
||||||
|
if locker == nil || !locker.IsOnline() {
|
||||||
|
// Skip any offline lockers.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if lockEpSet.Contains(locker.String()) {
|
||||||
|
// Skip duplicate lockers.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lockEpSet.Add(locker.String())
|
||||||
|
allLockers = append(allLockers, locker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allLockers
|
return allLockers
|
||||||
@@ -289,15 +314,8 @@ func (s *erasureSets) GetAllLockers() []dsync.NetLocker {
|
|||||||
|
|
||||||
func (s *erasureSets) GetLockers(setIndex int) func() ([]dsync.NetLocker, string) {
|
func (s *erasureSets) GetLockers(setIndex int) func() ([]dsync.NetLocker, string) {
|
||||||
return func() ([]dsync.NetLocker, string) {
|
return func() ([]dsync.NetLocker, string) {
|
||||||
lockers := make([]dsync.NetLocker, s.setDriveCount)
|
lockers := make([]dsync.NetLocker, len(s.erasureLockers[setIndex]))
|
||||||
copy(lockers, s.erasureLockers[setIndex])
|
copy(lockers, s.erasureLockers[setIndex])
|
||||||
sort.Slice(lockers, func(i, j int) bool {
|
|
||||||
// re-order lockers with affinity for
|
|
||||||
// - non-local lockers
|
|
||||||
// - online lockers
|
|
||||||
// are used first
|
|
||||||
return !lockers[i].IsLocal() && lockers[i].IsOnline()
|
|
||||||
})
|
|
||||||
return lockers, s.erasureLockOwner
|
return lockers, s.erasureLockOwner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,14 +380,24 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
|
|
||||||
for i := 0; i < setCount; i++ {
|
for i := 0; i < setCount; i++ {
|
||||||
s.erasureDisks[i] = make([]StorageAPI, setDriveCount)
|
s.erasureDisks[i] = make([]StorageAPI, setDriveCount)
|
||||||
s.erasureLockers[i] = make([]dsync.NetLocker, setDriveCount)
|
}
|
||||||
|
|
||||||
|
var erasureLockers = map[string]dsync.NetLocker{}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
if _, ok := erasureLockers[endpoint.Host]; !ok {
|
||||||
|
erasureLockers[endpoint.Host] = newLockAPI(endpoint)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < setCount; i++ {
|
for i := 0; i < setCount; i++ {
|
||||||
|
var lockerEpSet = set.NewStringSet()
|
||||||
for j := 0; j < setDriveCount; j++ {
|
for j := 0; j < setDriveCount; j++ {
|
||||||
endpoint := endpoints[i*setDriveCount+j]
|
endpoint := endpoints[i*setDriveCount+j]
|
||||||
// Rely on endpoints list to initialize, init lockers and available disks.
|
// Only add lockers only one per endpoint and per erasure set.
|
||||||
s.erasureLockers[i][j] = newLockAPI(endpoint)
|
if locker, ok := erasureLockers[endpoint.Host]; ok && !lockerEpSet.Contains(endpoint.Host) {
|
||||||
|
lockerEpSet.Add(endpoint.Host)
|
||||||
|
s.erasureLockers[i] = append(s.erasureLockers[i], locker)
|
||||||
|
}
|
||||||
disk := storageDisks[i*setDriveCount+j]
|
disk := storageDisks[i*setDriveCount+j]
|
||||||
if disk == nil {
|
if disk == nil {
|
||||||
continue
|
continue
|
||||||
@@ -388,6 +416,7 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
|
|
||||||
// Initialize erasure objects for a given set.
|
// Initialize erasure objects for a given set.
|
||||||
s.sets[i] = &erasureObjects{
|
s.sets[i] = &erasureObjects{
|
||||||
|
setDriveCount: setDriveCount,
|
||||||
getDisks: s.GetDisks(i),
|
getDisks: s.GetDisks(i),
|
||||||
getLockers: s.GetLockers(i),
|
getLockers: s.GetLockers(i),
|
||||||
getEndpoints: s.GetEndpoints(i),
|
getEndpoints: s.GetEndpoints(i),
|
||||||
@@ -395,11 +424,11 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
bp: bp,
|
bp: bp,
|
||||||
mrfOpCh: make(chan partialOperation, 10000),
|
mrfOpCh: make(chan partialOperation, 10000),
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.sets[i].cleanupStaleUploads(ctx,
|
|
||||||
GlobalStaleUploadsCleanupInterval, GlobalStaleUploadsExpiry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start cleanup stale uploads go-routine.
|
||||||
|
go s.cleanupStaleUploads(ctx, GlobalStaleUploadsCleanupInterval, GlobalStaleUploadsExpiry)
|
||||||
|
|
||||||
// Start the disk monitoring and connect routine.
|
// Start the disk monitoring and connect routine.
|
||||||
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
|
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
|
||||||
go s.maintainMRFList()
|
go s.maintainMRFList()
|
||||||
@@ -408,6 +437,22 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *erasureSets) cleanupStaleUploads(ctx context.Context, cleanupInterval, expiry time.Duration) {
|
||||||
|
ticker := time.NewTicker(cleanupInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
for _, set := range s.sets {
|
||||||
|
set.cleanupStaleUploads(ctx, expiry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewNSLock - initialize a new namespace RWLocker instance.
|
// NewNSLock - initialize a new namespace RWLocker instance.
|
||||||
func (s *erasureSets) NewNSLock(bucket string, objects ...string) RWLocker {
|
func (s *erasureSets) NewNSLock(bucket string, objects ...string) RWLocker {
|
||||||
if len(objects) == 1 {
|
if len(objects) == 1 {
|
||||||
@@ -446,8 +491,6 @@ func (s *erasureSets) StorageUsageInfo(ctx context.Context) StorageInfo {
|
|||||||
|
|
||||||
for _, lstorageInfo := range storageInfos {
|
for _, lstorageInfo := range storageInfos {
|
||||||
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||||
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
|
|
||||||
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return storageInfo
|
return storageInfo
|
||||||
@@ -485,8 +528,6 @@ func (s *erasureSets) StorageInfo(ctx context.Context, local bool) (StorageInfo,
|
|||||||
|
|
||||||
for _, lstorageInfo := range storageInfos {
|
for _, lstorageInfo := range storageInfos {
|
||||||
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||||
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
|
|
||||||
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if local {
|
if local {
|
||||||
@@ -683,8 +724,24 @@ func undoDeleteBucketSets(ctx context.Context, bucket string, sets []*erasureObj
|
|||||||
// sort here just for simplification. As per design it is assumed
|
// sort here just for simplification. As per design it is assumed
|
||||||
// that all buckets are present on all sets.
|
// that all buckets are present on all sets.
|
||||||
func (s *erasureSets) ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) {
|
func (s *erasureSets) ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) {
|
||||||
// Always lists from the same set signified by the empty string.
|
var listBuckets []BucketInfo
|
||||||
return s.ListBucketsHeal(ctx)
|
var healBuckets = map[string]VolInfo{}
|
||||||
|
for _, set := range s.sets {
|
||||||
|
// lists all unique buckets across drives.
|
||||||
|
if err := listAllBuckets(ctx, set.getDisks(), healBuckets); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range healBuckets {
|
||||||
|
listBuckets = append(listBuckets, BucketInfo(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(listBuckets, func(i, j int) bool {
|
||||||
|
return listBuckets[i].Name < listBuckets[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
return listBuckets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Object Operations ---
|
// --- Object Operations ---
|
||||||
@@ -1230,7 +1287,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HealBucket - heals inconsistent buckets and bucket metadata on all sets.
|
// HealBucket - heals inconsistent buckets and bucket metadata on all sets.
|
||||||
func (s *erasureSets) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (result madmin.HealResultItem, err error) {
|
func (s *erasureSets) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
|
||||||
// Initialize heal result info
|
// Initialize heal result info
|
||||||
result = madmin.HealResultItem{
|
result = madmin.HealResultItem{
|
||||||
Type: madmin.HealItemBucket,
|
Type: madmin.HealItemBucket,
|
||||||
@@ -1241,7 +1298,7 @@ func (s *erasureSets) HealBucket(ctx context.Context, bucket string, dryRun, rem
|
|||||||
|
|
||||||
for _, s := range s.sets {
|
for _, s := range s.sets {
|
||||||
var healResult madmin.HealResultItem
|
var healResult madmin.HealResultItem
|
||||||
healResult, err = s.HealBucket(ctx, bucket, dryRun, remove)
|
healResult, err = s.HealBucket(ctx, bucket, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -1263,23 +1320,6 @@ func (s *erasureSets) HealObject(ctx context.Context, bucket, object, versionID
|
|||||||
return s.getHashedSet(object).HealObject(ctx, bucket, object, versionID, opts)
|
return s.getHashedSet(object).HealObject(ctx, bucket, object, versionID, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lists all buckets which need healing.
|
|
||||||
func (s *erasureSets) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
|
|
||||||
var listBuckets []BucketInfo
|
|
||||||
var healBuckets = map[string]VolInfo{}
|
|
||||||
for _, set := range s.sets {
|
|
||||||
// lists all unique buckets across drives.
|
|
||||||
if err := listAllBuckets(ctx, set.getDisks(), healBuckets); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, v := range healBuckets {
|
|
||||||
listBuckets = append(listBuckets, BucketInfo(v))
|
|
||||||
}
|
|
||||||
sort.Sort(byBucketName(listBuckets))
|
|
||||||
return listBuckets, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutObjectTags - replace or add tags to an existing object
|
// PutObjectTags - replace or add tags to an existing object
|
||||||
func (s *erasureSets) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) error {
|
func (s *erasureSets) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) error {
|
||||||
return s.getHashedSet(object).PutObjectTags(ctx, bucket, object, tags, opts)
|
return s.getHashedSet(object).PutObjectTags(ctx, bucket, object, tags, opts)
|
||||||
@@ -1327,23 +1367,34 @@ func (s *erasureSets) maintainMRFList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toSourceChTimed(t *time.Timer, sourceCh chan healSource, u healSource) {
|
||||||
|
t.Reset(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// No defer, as we don't know which
|
||||||
|
// case will be selected
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sourceCh <- u:
|
||||||
|
case <-t.C:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still need to check the return value
|
||||||
|
// of Stop, because t could have fired
|
||||||
|
// between the send on sourceCh and this line.
|
||||||
|
if !t.Stop() {
|
||||||
|
<-t.C
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// healMRFRoutine monitors new disks connection, sweep the MRF list
|
// healMRFRoutine monitors new disks connection, sweep the MRF list
|
||||||
// to find objects related to the new disk that needs to be healed.
|
// to find objects related to the new disk that needs to be healed.
|
||||||
func (s *erasureSets) healMRFRoutine() {
|
func (s *erasureSets) healMRFRoutine() {
|
||||||
// Wait until background heal state is initialized
|
// Wait until background heal state is initialized
|
||||||
var bgSeq *healSequence
|
bgSeq := mustGetHealSequence(GlobalContext)
|
||||||
for {
|
|
||||||
if globalBackgroundHealState == nil {
|
idler := time.NewTimer(100 * time.Millisecond)
|
||||||
time.Sleep(time.Second)
|
defer idler.Stop()
|
||||||
continue
|
|
||||||
}
|
|
||||||
var ok bool
|
|
||||||
bgSeq, ok = globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
|
|
||||||
if ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
for e := range s.disksConnectEvent {
|
for e := range s.disksConnectEvent {
|
||||||
// Get the list of objects related the er.set
|
// Get the list of objects related the er.set
|
||||||
@@ -1359,11 +1410,8 @@ func (s *erasureSets) healMRFRoutine() {
|
|||||||
|
|
||||||
// Heal objects
|
// Heal objects
|
||||||
for _, u := range mrfOperations {
|
for _, u := range mrfOperations {
|
||||||
// Send an object to be healed with a timeout
|
// Send an object to background heal
|
||||||
select {
|
toSourceChTimed(idler, bgSeq.sourceCh, u)
|
||||||
case bgSeq.sourceCh <- u:
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mrfMU.Lock()
|
s.mrfMU.Lock()
|
||||||
delete(s.mrfOperations, u)
|
delete(s.mrfOperations, u)
|
||||||
|
|||||||
+55
-51
@@ -48,6 +48,8 @@ type partialOperation struct {
|
|||||||
type erasureObjects struct {
|
type erasureObjects struct {
|
||||||
GatewayUnsupported
|
GatewayUnsupported
|
||||||
|
|
||||||
|
setDriveCount int
|
||||||
|
|
||||||
// getDisks returns list of storageAPIs.
|
// getDisks returns list of storageAPIs.
|
||||||
getDisks func() []StorageAPI
|
getDisks func() []StorageAPI
|
||||||
|
|
||||||
@@ -72,11 +74,6 @@ func (er erasureObjects) NewNSLock(bucket string, objects ...string) RWLocker {
|
|||||||
return er.nsMutex.NewNSLock(er.getLockers, bucket, objects...)
|
return er.nsMutex.NewNSLock(er.getLockers, bucket, objects...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDriveCount returns the current drives per set.
|
|
||||||
func (er erasureObjects) SetDriveCount() int {
|
|
||||||
return len(er.getDisks())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown function for object storage interface.
|
// Shutdown function for object storage interface.
|
||||||
func (er erasureObjects) Shutdown(ctx context.Context) error {
|
func (er erasureObjects) Shutdown(ctx context.Context) error {
|
||||||
// Add any object layer shutdown activities here.
|
// Add any object layer shutdown activities here.
|
||||||
@@ -112,13 +109,12 @@ func diskErrToDriveState(err error) (state string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDisksInfo - fetch disks info across all other storage API.
|
func getOnlineOfflineDisksStats(disksInfo []madmin.Disk) (onlineDisks, offlineDisks madmin.BackendDisks) {
|
||||||
func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Disk, errs []error, onlineDisks, offlineDisks madmin.BackendDisks) {
|
|
||||||
disksInfo = make([]madmin.Disk, len(disks))
|
|
||||||
onlineDisks = make(madmin.BackendDisks)
|
onlineDisks = make(madmin.BackendDisks)
|
||||||
offlineDisks = make(madmin.BackendDisks)
|
offlineDisks = make(madmin.BackendDisks)
|
||||||
|
|
||||||
for _, ep := range endpoints {
|
for _, disk := range disksInfo {
|
||||||
|
ep := disk.Endpoint
|
||||||
if _, ok := offlineDisks[ep]; !ok {
|
if _, ok := offlineDisks[ep]; !ok {
|
||||||
offlineDisks[ep] = 0
|
offlineDisks[ep] = 0
|
||||||
}
|
}
|
||||||
@@ -127,6 +123,47 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the routines.
|
||||||
|
for _, disk := range disksInfo {
|
||||||
|
ep := disk.Endpoint
|
||||||
|
state := disk.State
|
||||||
|
if state != madmin.DriveStateOk && state != madmin.DriveStateUnformatted {
|
||||||
|
offlineDisks[ep]++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
onlineDisks[ep]++
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDiskCount := 0
|
||||||
|
for _, di := range disksInfo {
|
||||||
|
if di.RootDisk {
|
||||||
|
rootDiskCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count offline disks as well to ensure consistent
|
||||||
|
// reportability of offline drives on local setups.
|
||||||
|
if len(disksInfo) == (rootDiskCount + offlineDisks.Sum()) {
|
||||||
|
// Success.
|
||||||
|
return onlineDisks, offlineDisks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root disk should be considered offline
|
||||||
|
for i := range disksInfo {
|
||||||
|
ep := disksInfo[i].Endpoint
|
||||||
|
if disksInfo[i].RootDisk {
|
||||||
|
offlineDisks[ep]++
|
||||||
|
onlineDisks[ep]--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return onlineDisks, offlineDisks
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDisksInfo - fetch disks info across all other storage API.
|
||||||
|
func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Disk, errs []error) {
|
||||||
|
disksInfo = make([]madmin.Disk, len(disks))
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(disks))
|
g := errgroup.WithNErrs(len(disks))
|
||||||
for index := range disks {
|
for index := range disks {
|
||||||
index := index
|
index := index
|
||||||
@@ -160,46 +197,12 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
|
|||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
errs = g.Wait()
|
return disksInfo, g.Wait()
|
||||||
// Wait for the routines.
|
|
||||||
for i, diskInfoErr := range errs {
|
|
||||||
ep := disksInfo[i].Endpoint
|
|
||||||
if diskInfoErr != nil && !errors.Is(diskInfoErr, errUnformattedDisk) {
|
|
||||||
offlineDisks[ep]++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
onlineDisks[ep]++
|
|
||||||
}
|
|
||||||
|
|
||||||
rootDiskCount := 0
|
|
||||||
for _, di := range disksInfo {
|
|
||||||
if di.RootDisk {
|
|
||||||
rootDiskCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count offline disks as well to ensure consistent
|
|
||||||
// reportability of offline drives on local setups.
|
|
||||||
if len(disksInfo) == (rootDiskCount + offlineDisks.Sum()) {
|
|
||||||
// Success.
|
|
||||||
return disksInfo, errs, onlineDisks, offlineDisks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root disk should be considered offline
|
|
||||||
for i := range disksInfo {
|
|
||||||
ep := disksInfo[i].Endpoint
|
|
||||||
if disksInfo[i].RootDisk {
|
|
||||||
offlineDisks[ep]++
|
|
||||||
onlineDisks[ep]--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return disksInfo, errs, onlineDisks, offlineDisks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an aggregated storage info across all disks.
|
// Get an aggregated storage info across all disks.
|
||||||
func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []error) {
|
func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []error) {
|
||||||
disksInfo, errs, onlineDisks, offlineDisks := getDisksInfo(disks, endpoints)
|
disksInfo, errs := getDisksInfo(disks, endpoints)
|
||||||
|
|
||||||
// Sort so that the first element is the smallest.
|
// Sort so that the first element is the smallest.
|
||||||
sort.Sort(byDiskTotal(disksInfo))
|
sort.Sort(byDiskTotal(disksInfo))
|
||||||
@@ -209,9 +212,6 @@ func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
storageInfo.Backend.Type = BackendErasure
|
storageInfo.Backend.Type = BackendErasure
|
||||||
storageInfo.Backend.OnlineDisks = onlineDisks
|
|
||||||
storageInfo.Backend.OfflineDisks = offlineDisks
|
|
||||||
|
|
||||||
return storageInfo, errs
|
return storageInfo, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,10 +254,14 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
|
|||||||
|
|
||||||
// Collect disks for healing.
|
// Collect disks for healing.
|
||||||
allDisks := er.getDisks()
|
allDisks := er.getDisks()
|
||||||
allDiskIDs := make([]string, len(allDisks))
|
allDiskIDs := make([]string, 0, len(allDisks))
|
||||||
for i, disk := range allDisks {
|
for _, disk := range allDisks {
|
||||||
|
if disk == OfflineDisk {
|
||||||
|
// its possible that disk is OfflineDisk
|
||||||
|
continue
|
||||||
|
}
|
||||||
id, _ := disk.GetDiskID()
|
id, _ := disk.GetDiskID()
|
||||||
allDiskIDs[i] = id
|
allDiskIDs = append(allDiskIDs, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load bucket totals
|
// Load bucket totals
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
etcd "go.etcd.io/etcd/v3/clientv3"
|
etcd "go.etcd.io/etcd/clientv3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errEtcdUnreachable = errors.New("etcd is unreachable, please check your endpoints")
|
var errEtcdUnreachable = errors.New("etcd is unreachable, please check your endpoints")
|
||||||
|
|||||||
+20
-18
@@ -200,6 +200,11 @@ func (fs *FSObjects) Shutdown(ctx context.Context) error {
|
|||||||
return fsRemoveAll(ctx, pathJoin(fs.fsPath, minioMetaTmpBucket, fs.fsUUID))
|
return fsRemoveAll(ctx, pathJoin(fs.fsPath, minioMetaTmpBucket, fs.fsUUID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BackendInfo - returns backend information
|
||||||
|
func (fs *FSObjects) BackendInfo() BackendInfo {
|
||||||
|
return BackendInfo{Type: BackendFS}
|
||||||
|
}
|
||||||
|
|
||||||
// StorageInfo - returns underlying storage statistics.
|
// StorageInfo - returns underlying storage statistics.
|
||||||
func (fs *FSObjects) StorageInfo(ctx context.Context, _ bool) (StorageInfo, []error) {
|
func (fs *FSObjects) StorageInfo(ctx context.Context, _ bool) (StorageInfo, []error) {
|
||||||
|
|
||||||
@@ -317,11 +322,14 @@ func (fs *FSObjects) crawlBucket(ctx context.Context, bucket string, cache dataU
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load bucket info.
|
// Load bucket info.
|
||||||
cache, err = crawlDataFolder(ctx, fs.fsPath, cache, func(item crawlItem) (int64, error) {
|
cache, err = crawlDataFolder(ctx, fs.fsPath, cache, func(item crawlItem) (sizeSummary, error) {
|
||||||
bucket, object := item.bucket, item.objectPath()
|
bucket, object := item.bucket, item.objectPath()
|
||||||
fsMetaBytes, err := ioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
|
fsMetaBytes, err := ioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
|
||||||
if err != nil && !osIsNotExist(err) {
|
if err != nil && !osIsNotExist(err) {
|
||||||
return 0, errSkipFile
|
if intDataUpdateTracker.debug {
|
||||||
|
logger.Info(color.Green("crawlBucket:")+" object return unexpected error: %v/%v: %w", item.bucket, item.objectPath(), err)
|
||||||
|
}
|
||||||
|
return sizeSummary{}, errSkipFile
|
||||||
}
|
}
|
||||||
|
|
||||||
fsMeta := newFSMetaV1()
|
fsMeta := newFSMetaV1()
|
||||||
@@ -339,18 +347,19 @@ func (fs *FSObjects) crawlBucket(ctx context.Context, bucket string, cache dataU
|
|||||||
// Stat the file.
|
// Stat the file.
|
||||||
fi, fiErr := os.Stat(item.Path)
|
fi, fiErr := os.Stat(item.Path)
|
||||||
if fiErr != nil {
|
if fiErr != nil {
|
||||||
return 0, errSkipFile
|
if intDataUpdateTracker.debug {
|
||||||
|
logger.Info(color.Green("crawlBucket:")+" object path missing: %v: %w", item.Path, fiErr)
|
||||||
|
}
|
||||||
|
return sizeSummary{}, errSkipFile
|
||||||
}
|
}
|
||||||
// We cannot heal in FS mode.
|
|
||||||
item.heal = false
|
|
||||||
|
|
||||||
oi := fsMeta.ToObjectInfo(bucket, object, fi)
|
oi := fsMeta.ToObjectInfo(bucket, object, fi)
|
||||||
sz := item.applyActions(ctx, fs, actionMeta{oi: oi})
|
sz := item.applyActions(ctx, fs, actionMeta{oi: oi})
|
||||||
if sz >= 0 {
|
if sz >= 0 {
|
||||||
return sz, nil
|
return sizeSummary{totalSize: sz}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fi.Size(), nil
|
return sizeSummary{totalSize: fi.Size()}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return cache, err
|
return cache, err
|
||||||
@@ -527,7 +536,9 @@ func (fs *FSObjects) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort bucket infos by bucket name.
|
// Sort bucket infos by bucket name.
|
||||||
sort.Sort(byBucketName(bucketInfos))
|
sort.Slice(bucketInfos, func(i, j int) bool {
|
||||||
|
return bucketInfos[i].Name < bucketInfos[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
// Succes.
|
// Succes.
|
||||||
return bucketInfos, nil
|
return bucketInfos, nil
|
||||||
@@ -1528,21 +1539,18 @@ func (fs *FSObjects) DeleteObjectTags(ctx context.Context, bucket, object string
|
|||||||
|
|
||||||
// HealFormat - no-op for fs, Valid only for Erasure.
|
// HealFormat - no-op for fs, Valid only for Erasure.
|
||||||
func (fs *FSObjects) HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error) {
|
func (fs *FSObjects) HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error) {
|
||||||
logger.LogIf(ctx, NotImplemented{})
|
|
||||||
return madmin.HealResultItem{}, NotImplemented{}
|
return madmin.HealResultItem{}, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealObject - no-op for fs. Valid only for Erasure.
|
// HealObject - no-op for fs. Valid only for Erasure.
|
||||||
func (fs *FSObjects) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (
|
func (fs *FSObjects) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (
|
||||||
res madmin.HealResultItem, err error) {
|
res madmin.HealResultItem, err error) {
|
||||||
logger.LogIf(ctx, NotImplemented{})
|
|
||||||
return res, NotImplemented{}
|
return res, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealBucket - no-op for fs, Valid only for Erasure.
|
// HealBucket - no-op for fs, Valid only for Erasure.
|
||||||
func (fs *FSObjects) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem,
|
func (fs *FSObjects) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem,
|
||||||
error) {
|
error) {
|
||||||
logger.LogIf(ctx, NotImplemented{})
|
|
||||||
return madmin.HealResultItem{}, NotImplemented{}
|
return madmin.HealResultItem{}, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1561,12 +1569,6 @@ func (fs *FSObjects) HealObjects(ctx context.Context, bucket, prefix string, opt
|
|||||||
return NotImplemented{}
|
return NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBucketsHeal - list all buckets to be healed. Valid only for Erasure
|
|
||||||
func (fs *FSObjects) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
|
|
||||||
logger.LogIf(ctx, NotImplemented{})
|
|
||||||
return []BucketInfo{}, NotImplemented{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetrics - no op
|
// GetMetrics - no op
|
||||||
func (fs *FSObjects) GetMetrics(ctx context.Context) (*Metrics, error) {
|
func (fs *FSObjects) GetMetrics(ctx context.Context) (*Metrics, error) {
|
||||||
logger.LogIf(ctx, NotImplemented{})
|
logger.LogIf(ctx, NotImplemented{})
|
||||||
|
|||||||
+2
-2
@@ -179,7 +179,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
|
|
||||||
// Check and load TLS certificates.
|
// Check and load TLS certificates.
|
||||||
var err error
|
var err error
|
||||||
globalPublicCerts, globalTLSCerts, globalIsSSL, err = getTLSConfig()
|
globalPublicCerts, globalTLSCerts, globalIsTLS, err = getTLSConfig()
|
||||||
logger.FatalIf(err, "Invalid TLS certificate file")
|
logger.FatalIf(err, "Invalid TLS certificate file")
|
||||||
|
|
||||||
// Check and load Root CAs.
|
// Check and load Root CAs.
|
||||||
@@ -211,7 +211,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
if host == "" {
|
if host == "" {
|
||||||
host = sortIPs(localIP4.ToSlice())[0]
|
host = sortIPs(localIP4.ToSlice())[0]
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s://%s", getURLScheme(globalIsSSL), net.JoinHostPort(host, globalMinioPort))
|
return fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(host, globalMinioPort))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Handle gateway specific env
|
// Handle gateway specific env
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func printGatewayStartupMessage(apiEndPoints []string, backendType string) {
|
|||||||
// SSL is configured reads certification chain, prints
|
// SSL is configured reads certification chain, prints
|
||||||
// authority and expiry.
|
// authority and expiry.
|
||||||
if color.IsTerminal() && !globalCLIContext.Anonymous {
|
if color.IsTerminal() && !globalCLIContext.Anonymous {
|
||||||
if globalIsSSL {
|
if globalIsTLS {
|
||||||
printCertificateMsg(globalPublicCerts)
|
printCertificateMsg(globalPublicCerts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ import (
|
|||||||
// GatewayUnsupported list of unsupported call stubs for gateway.
|
// GatewayUnsupported list of unsupported call stubs for gateway.
|
||||||
type GatewayUnsupported struct{}
|
type GatewayUnsupported struct{}
|
||||||
|
|
||||||
|
// BackendInfo returns the underlying backend information
|
||||||
|
func (a GatewayUnsupported) BackendInfo() BackendInfo {
|
||||||
|
return BackendInfo{Type: BackendGateway}
|
||||||
|
}
|
||||||
|
|
||||||
// CrawlAndGetDataUsage - crawl is not implemented for gateway
|
// CrawlAndGetDataUsage - crawl is not implemented for gateway
|
||||||
func (a GatewayUnsupported) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter, updates chan<- DataUsageInfo) error {
|
func (a GatewayUnsupported) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter, updates chan<- DataUsageInfo) error {
|
||||||
logger.CriticalIf(ctx, errors.New("not implemented"))
|
logger.CriticalIf(ctx, errors.New("not implemented"))
|
||||||
@@ -166,15 +171,10 @@ func (a GatewayUnsupported) HealFormat(ctx context.Context, dryRun bool) (madmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HealBucket - Not implemented stub
|
// HealBucket - Not implemented stub
|
||||||
func (a GatewayUnsupported) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error) {
|
func (a GatewayUnsupported) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||||
return madmin.HealResultItem{}, NotImplemented{}
|
return madmin.HealResultItem{}, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBucketsHeal - Not implemented stub
|
|
||||||
func (a GatewayUnsupported) ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error) {
|
|
||||||
return nil, NotImplemented{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealObject - Not implemented stub
|
// HealObject - Not implemented stub
|
||||||
func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (h madmin.HealResultItem, e error) {
|
func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (h madmin.HealResultItem, e error) {
|
||||||
return h, NotImplemented{}
|
return h, NotImplemented{}
|
||||||
|
|||||||
@@ -812,7 +812,7 @@ func (a *azureObjects) GetObjectNInfo(ctx context.Context, bucket, object string
|
|||||||
|
|
||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
err := a.GetObject(ctx, bucket, object, startOffset, length, pw, objInfo.ETag, opts)
|
err := a.GetObject(ctx, bucket, object, startOffset, length, pw, objInfo.InnerETag, opts)
|
||||||
pw.CloseWithError(err)
|
pw.CloseWithError(err)
|
||||||
}()
|
}()
|
||||||
// Setup cleanup function to cause the above go-routine to
|
// Setup cleanup function to cause the above go-routine to
|
||||||
@@ -833,8 +833,13 @@ func (a *azureObjects) GetObject(ctx context.Context, bucket, object string, sta
|
|||||||
return azureToObjectError(minio.InvalidRange{}, bucket, object)
|
return azureToObjectError(minio.InvalidRange{}, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessCond := azblob.BlobAccessConditions{}
|
||||||
|
if etag != "" {
|
||||||
|
accessCond.ModifiedAccessConditions.IfMatch = azblob.ETag(etag)
|
||||||
|
}
|
||||||
|
|
||||||
blobURL := a.client.NewContainerURL(bucket).NewBlobURL(object)
|
blobURL := a.client.NewContainerURL(bucket).NewBlobURL(object)
|
||||||
blob, err := blobURL.Download(ctx, startOffset, length, azblob.BlobAccessConditions{}, false)
|
blob, err := blobURL.Download(ctx, startOffset, length, accessCond, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return azureToObjectError(err, bucket, object)
|
return azureToObjectError(err, bucket, object)
|
||||||
}
|
}
|
||||||
@@ -855,6 +860,8 @@ func (a *azureObjects) GetObjectInfo(ctx context.Context, bucket, object string,
|
|||||||
return objInfo, azureToObjectError(err, bucket, object)
|
return objInfo, azureToObjectError(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
realETag := string(blob.ETag())
|
||||||
|
|
||||||
// Populate correct ETag's if possible, this code primarily exists
|
// Populate correct ETag's if possible, this code primarily exists
|
||||||
// because AWS S3 indicates that
|
// because AWS S3 indicates that
|
||||||
//
|
//
|
||||||
@@ -866,7 +873,7 @@ func (a *azureObjects) GetObjectInfo(ctx context.Context, bucket, object string,
|
|||||||
//
|
//
|
||||||
// Some applications depend on this behavior refer https://github.com/minio/minio/issues/6550
|
// Some applications depend on this behavior refer https://github.com/minio/minio/issues/6550
|
||||||
// So we handle it here and make this consistent.
|
// So we handle it here and make this consistent.
|
||||||
etag := minio.ToS3ETag(string(blob.ETag()))
|
etag := minio.ToS3ETag(realETag)
|
||||||
metadata := blob.NewMetadata()
|
metadata := blob.NewMetadata()
|
||||||
contentMD5 := blob.ContentMD5()
|
contentMD5 := blob.ContentMD5()
|
||||||
switch {
|
switch {
|
||||||
@@ -881,6 +888,7 @@ func (a *azureObjects) GetObjectInfo(ctx context.Context, bucket, object string,
|
|||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
UserDefined: azurePropertiesToS3Meta(metadata, blob.NewHTTPHeaders(), blob.ContentLength()),
|
UserDefined: azurePropertiesToS3Meta(metadata, blob.NewHTTPHeaders(), blob.ContentLength()),
|
||||||
ETag: etag,
|
ETag: etag,
|
||||||
|
InnerETag: realETag,
|
||||||
ModTime: blob.LastModified(),
|
ModTime: blob.LastModified(),
|
||||||
Name: object,
|
Name: object,
|
||||||
Size: blob.ContentLength(),
|
Size: blob.ContentLength(),
|
||||||
|
|||||||
@@ -431,6 +431,11 @@ func (l *s3Objects) GetObject(ctx context.Context, bucket string, key string, st
|
|||||||
return minio.ErrorRespToObjectError(err, bucket, key)
|
return minio.ErrorRespToObjectError(err, bucket, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if etag != "" {
|
||||||
|
opts.SetMatchETag(etag)
|
||||||
|
}
|
||||||
|
|
||||||
object, _, _, err := l.Client.GetObject(ctx, bucket, key, opts)
|
object, _, _, err := l.Client.GetObject(ctx, bucket, key, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return minio.ErrorRespToObjectError(err, bucket, key)
|
return minio.ErrorRespToObjectError(err, bucket, key)
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
// For browser requests, when federation is setup we need to
|
// For browser requests, when federation is setup we need to
|
||||||
// specifically handle download and upload for browser requests.
|
// specifically handle download and upload for browser requests.
|
||||||
if guessIsBrowserReq(r) && globalDNSConfig != nil && len(globalDomainNames) > 0 {
|
if globalDNSConfig != nil && len(globalDomainNames) > 0 && guessIsBrowserReq(r) {
|
||||||
var bucket, _ string
|
var bucket, _ string
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
@@ -665,7 +665,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
||||||
r.URL.Scheme = "http"
|
r.URL.Scheme = "http"
|
||||||
if globalIsSSL {
|
if globalIsTLS {
|
||||||
r.URL.Scheme = "https"
|
r.URL.Scheme = "https"
|
||||||
}
|
}
|
||||||
r.URL.Host = getHostFromSrv(sr)
|
r.URL.Host = getHostFromSrv(sr)
|
||||||
@@ -689,7 +689,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MakeBucket requests should be handled at current endpoint
|
// MakeBucket requests should be handled at current endpoint
|
||||||
if r.Method == http.MethodPut && bucket != "" && object == "" {
|
if r.Method == http.MethodPut && bucket != "" && object == "" && r.URL.RawQuery == "" {
|
||||||
f.handler.ServeHTTP(w, r)
|
f.handler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -715,7 +715,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
||||||
r.URL.Scheme = "http"
|
r.URL.Scheme = "http"
|
||||||
if globalIsSSL {
|
if globalIsTLS {
|
||||||
r.URL.Scheme = "https"
|
r.URL.Scheme = "https"
|
||||||
}
|
}
|
||||||
r.URL.Host = getHostFromSrv(sr)
|
r.URL.Host = getHostFromSrv(sr)
|
||||||
@@ -798,7 +798,7 @@ type sseTLSHandler struct{ handler http.Handler }
|
|||||||
|
|
||||||
func (h sseTLSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h sseTLSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// Deny SSE-C requests if not made over TLS
|
// Deny SSE-C requests if not made over TLS
|
||||||
if !globalIsSSL && (crypto.SSEC.IsRequested(r.Header) || crypto.SSECopy.IsRequested(r.Header)) {
|
if !globalIsTLS && (crypto.SSEC.IsRequested(r.Header) || crypto.SSECopy.IsRequested(r.Header)) {
|
||||||
if r.Method == http.MethodHead {
|
if r.Method == http.MethodHead {
|
||||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInsecureSSECustomerRequest))
|
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInsecureSSECustomerRequest))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests getRedirectLocation function for all its criteria.
|
// Tests getRedirectLocation function for all its criteria.
|
||||||
@@ -181,15 +182,15 @@ var containsReservedMetadataTests = []struct {
|
|||||||
header: http.Header{"X-Minio-Key": []string{"value"}},
|
header: http.Header{"X-Minio-Key": []string{"value"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{crypto.SSEIV: []string{"iv"}},
|
header: http.Header{crypto.MetaIV: []string{"iv"}},
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{crypto.SSESealAlgorithm: []string{crypto.InsecureSealAlgorithm}},
|
header: http.Header{crypto.MetaAlgorithm: []string{crypto.InsecureSealAlgorithm}},
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{crypto.SSECSealedKey: []string{"mac"}},
|
header: http.Header{crypto.MetaSealedKeySSEC: []string{"mac"}},
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -218,20 +219,20 @@ var sseTLSHandlerTests = []struct {
|
|||||||
IsTLS, ShouldFail bool
|
IsTLS, ShouldFail bool
|
||||||
}{
|
}{
|
||||||
{URL: &url.URL{}, Header: http.Header{}, IsTLS: false, ShouldFail: false}, // 0
|
{URL: &url.URL{}, Header: http.Header{}, IsTLS: false, ShouldFail: false}, // 0
|
||||||
{URL: &url.URL{}, Header: http.Header{crypto.SSECAlgorithm: []string{"AES256"}}, IsTLS: false, ShouldFail: true}, // 1
|
{URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}}, IsTLS: false, ShouldFail: true}, // 1
|
||||||
{URL: &url.URL{}, Header: http.Header{crypto.SSECAlgorithm: []string{"AES256"}}, IsTLS: true, ShouldFail: false}, // 2
|
{URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}}, IsTLS: true, ShouldFail: false}, // 2
|
||||||
{URL: &url.URL{}, Header: http.Header{crypto.SSECKey: []string{""}}, IsTLS: true, ShouldFail: false}, // 3
|
{URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerKey: []string{""}}, IsTLS: true, ShouldFail: false}, // 3
|
||||||
{URL: &url.URL{}, Header: http.Header{crypto.SSECopyAlgorithm: []string{""}}, IsTLS: false, ShouldFail: true}, // 4
|
{URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{""}}, IsTLS: false, ShouldFail: true}, // 4
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSSETLSHandler(t *testing.T) {
|
func TestSSETLSHandler(t *testing.T) {
|
||||||
defer func(isSSL bool) { globalIsSSL = isSSL }(globalIsSSL) // reset globalIsSSL after test
|
defer func(isSSL bool) { globalIsTLS = isSSL }(globalIsTLS) // reset globalIsTLS after test
|
||||||
|
|
||||||
var okHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
var okHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
for i, test := range sseTLSHandlerTests {
|
for i, test := range sseTLSHandlerTests {
|
||||||
globalIsSSL = test.IsTLS
|
globalIsTLS = test.IsTLS
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
r := new(http.Request)
|
r := new(http.Request)
|
||||||
|
|||||||
+35
-18
@@ -96,41 +96,54 @@ func getLocalBackgroundHealStatus() (madmin.BgHealState, bool) {
|
|||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// healErasureSet lists and heals all objects in a specific erasure set
|
func mustGetHealSequence(ctx context.Context) *healSequence {
|
||||||
func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, disks []StorageAPI) error {
|
|
||||||
// Get background heal sequence to send elements to heal
|
// Get background heal sequence to send elements to heal
|
||||||
var bgSeq *healSequence
|
|
||||||
var ok bool
|
|
||||||
for {
|
for {
|
||||||
bgSeq, ok = globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
|
globalHealStateLK.RLock()
|
||||||
if ok {
|
hstate := globalBackgroundHealState
|
||||||
break
|
globalHealStateLK.RUnlock()
|
||||||
}
|
|
||||||
select {
|
if hstate == nil {
|
||||||
case <-ctx.Done():
|
time.Sleep(time.Second)
|
||||||
return nil
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bgSeq, ok := hstate.getHealSequenceByToken(bgHealingUUID)
|
||||||
|
if !ok {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
return bgSeq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// healErasureSet lists and heals all objects in a specific erasure set
|
||||||
|
func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, disks []StorageAPI) error {
|
||||||
|
bgSeq := mustGetHealSequence(ctx)
|
||||||
|
|
||||||
buckets = append(buckets, BucketInfo{
|
buckets = append(buckets, BucketInfo{
|
||||||
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
||||||
}, BucketInfo{
|
})
|
||||||
Name: pathJoin(minioMetaBucket, bucketConfigPrefix),
|
|
||||||
}) // add metadata .minio.sys/ bucket prefixes to heal
|
|
||||||
|
|
||||||
// Try to pro-actively heal backend-encrypted file.
|
// Try to pro-actively heal backend-encrypted file.
|
||||||
bgSeq.sourceCh <- healSource{
|
if err := bgSeq.queueHealTask(healSource{
|
||||||
bucket: minioMetaBucket,
|
bucket: minioMetaBucket,
|
||||||
object: backendEncryptedFile,
|
object: backendEncryptedFile,
|
||||||
|
}, madmin.HealItemMetadata); err != nil {
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal all buckets with all objects
|
// Heal all buckets with all objects
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
// Heal current bucket
|
// Heal current bucket
|
||||||
bgSeq.sourceCh <- healSource{
|
if err := bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket.Name,
|
bucket: bucket.Name,
|
||||||
|
}, madmin.HealItemBucket); err != nil {
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var entryChs []FileInfoVersionsCh
|
var entryChs []FileInfoVersionsCh
|
||||||
@@ -165,10 +178,14 @@ func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, dis
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, version := range entry.Versions {
|
for _, version := range entry.Versions {
|
||||||
bgSeq.sourceCh <- healSource{
|
if err := bgSeq.queueHealTask(healSource{
|
||||||
bucket: bucket.Name,
|
bucket: bucket.Name,
|
||||||
object: version.Name,
|
object: version.Name,
|
||||||
versionID: version.VersionID,
|
versionID: version.VersionID,
|
||||||
|
}, madmin.HealItemObject); err != nil {
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -20,6 +20,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
@@ -36,7 +37,7 @@ import (
|
|||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
etcd "go.etcd.io/etcd/v3/clientv3"
|
etcd "go.etcd.io/etcd/clientv3"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/certs"
|
"github.com/minio/minio/pkg/certs"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
@@ -170,7 +171,7 @@ var (
|
|||||||
globalRootCAs *x509.CertPool
|
globalRootCAs *x509.CertPool
|
||||||
|
|
||||||
// IsSSL indicates if the server is configured with SSL.
|
// IsSSL indicates if the server is configured with SSL.
|
||||||
globalIsSSL bool
|
globalIsTLS bool
|
||||||
|
|
||||||
globalTLSCerts *certs.Manager
|
globalTLSCerts *certs.Manager
|
||||||
|
|
||||||
@@ -245,6 +246,7 @@ var (
|
|||||||
globalAutoEncryption bool
|
globalAutoEncryption bool
|
||||||
|
|
||||||
// Is compression enabled?
|
// Is compression enabled?
|
||||||
|
globalCompressConfigMu sync.Mutex
|
||||||
globalCompressConfig compress.Config
|
globalCompressConfig compress.Config
|
||||||
|
|
||||||
// Some standard object extensions which we strictly dis-allow for compression.
|
// Some standard object extensions which we strictly dis-allow for compression.
|
||||||
@@ -278,6 +280,8 @@ var (
|
|||||||
|
|
||||||
globalInternodeTransport http.RoundTripper
|
globalInternodeTransport http.RoundTripper
|
||||||
|
|
||||||
|
globalProxyTransport http.RoundTripper
|
||||||
|
|
||||||
globalDNSCache *xhttp.DNSCache
|
globalDNSCache *xhttp.DNSCache
|
||||||
// Add new variable global values here.
|
// Add new variable global values here.
|
||||||
)
|
)
|
||||||
|
|||||||
+24
-9
@@ -35,6 +35,7 @@ type apiConfig struct {
|
|||||||
listQuorum int
|
listQuorum int
|
||||||
extendListLife time.Duration
|
extendListLife time.Duration
|
||||||
corsAllowOrigins []string
|
corsAllowOrigins []string
|
||||||
|
setDriveCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *apiConfig) init(cfg api.Config, setDriveCount int) {
|
func (t *apiConfig) init(cfg api.Config, setDriveCount int) {
|
||||||
@@ -43,6 +44,7 @@ func (t *apiConfig) init(cfg api.Config, setDriveCount int) {
|
|||||||
|
|
||||||
t.clusterDeadline = cfg.ClusterDeadline
|
t.clusterDeadline = cfg.ClusterDeadline
|
||||||
t.corsAllowOrigins = cfg.CorsAllowOrigin
|
t.corsAllowOrigins = cfg.CorsAllowOrigin
|
||||||
|
t.setDriveCount = setDriveCount
|
||||||
|
|
||||||
var apiRequestsMaxPerNode int
|
var apiRequestsMaxPerNode int
|
||||||
if cfg.RequestsMax <= 0 {
|
if cfg.RequestsMax <= 0 {
|
||||||
@@ -62,8 +64,14 @@ func (t *apiConfig) init(cfg api.Config, setDriveCount int) {
|
|||||||
apiRequestsMaxPerNode /= len(globalEndpoints.Hostnames())
|
apiRequestsMaxPerNode /= len(globalEndpoints.Hostnames())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if cap(t.requestsPool) < apiRequestsMaxPerNode {
|
||||||
|
// Only replace if needed.
|
||||||
|
// Existing requests will use the previous limit,
|
||||||
|
// but new requests will use the new limit.
|
||||||
|
// There will be a short overlap window,
|
||||||
|
// but this shouldn't last long.
|
||||||
t.requestsPool = make(chan struct{}, apiRequestsMaxPerNode)
|
t.requestsPool = make(chan struct{}, apiRequestsMaxPerNode)
|
||||||
|
}
|
||||||
t.requestsDeadline = cfg.RequestsDeadline
|
t.requestsDeadline = cfg.RequestsDeadline
|
||||||
t.listQuorum = cfg.GetListQuorum()
|
t.listQuorum = cfg.GetListQuorum()
|
||||||
t.extendListLife = cfg.ExtendListLife
|
t.extendListLife = cfg.ExtendListLife
|
||||||
@@ -76,6 +84,13 @@ func (t *apiConfig) getListQuorum() int {
|
|||||||
return t.listQuorum
|
return t.listQuorum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *apiConfig) getSetDriveCount() int {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
|
||||||
|
return t.setDriveCount
|
||||||
|
}
|
||||||
|
|
||||||
func (t *apiConfig) getExtendListLife() time.Duration {
|
func (t *apiConfig) getExtendListLife() time.Duration {
|
||||||
t.mu.RLock()
|
t.mu.RLock()
|
||||||
defer t.mu.RUnlock()
|
defer t.mu.RUnlock()
|
||||||
@@ -103,34 +118,34 @@ func (t *apiConfig) getClusterDeadline() time.Duration {
|
|||||||
return t.clusterDeadline
|
return t.clusterDeadline
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *apiConfig) getRequestsPool() (chan struct{}, <-chan time.Time) {
|
func (t *apiConfig) getRequestsPool() (chan struct{}, time.Duration) {
|
||||||
t.mu.RLock()
|
t.mu.RLock()
|
||||||
defer t.mu.RUnlock()
|
defer t.mu.RUnlock()
|
||||||
|
|
||||||
if t.requestsPool == nil {
|
if t.requestsPool == nil {
|
||||||
return nil, nil
|
return nil, time.Duration(0)
|
||||||
}
|
|
||||||
if t.requestsDeadline <= 0 {
|
|
||||||
return t.requestsPool, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.requestsPool, time.NewTimer(t.requestsDeadline).C
|
return t.requestsPool, t.requestsDeadline
|
||||||
}
|
}
|
||||||
|
|
||||||
// maxClients throttles the S3 API calls
|
// maxClients throttles the S3 API calls
|
||||||
func maxClients(f http.HandlerFunc) http.HandlerFunc {
|
func maxClients(f http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
pool, deadlineTimer := globalAPIConfig.getRequestsPool()
|
pool, deadline := globalAPIConfig.getRequestsPool()
|
||||||
if pool == nil {
|
if pool == nil {
|
||||||
f.ServeHTTP(w, r)
|
f.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deadlineTimer := time.NewTimer(deadline)
|
||||||
|
defer deadlineTimer.Stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case pool <- struct{}{}:
|
case pool <- struct{}{}:
|
||||||
defer func() { <-pool }()
|
defer func() { <-pool }()
|
||||||
f.ServeHTTP(w, r)
|
f.ServeHTTP(w, r)
|
||||||
case <-deadlineTimer:
|
case <-deadlineTimer.C:
|
||||||
// Send a http timeout message
|
// Send a http timeout message
|
||||||
writeErrorResponse(r.Context(), w,
|
writeErrorResponse(r.Context(), w,
|
||||||
errorCodes.ToAPIErr(ErrOperationMaxedOut),
|
errorCodes.ToAPIErr(ErrOperationMaxedOut),
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
@@ -379,12 +378,7 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
|
|||||||
|
|
||||||
f.ServeHTTP(statsWriter, r)
|
f.ServeHTTP(statsWriter, r)
|
||||||
|
|
||||||
// Time duration in secs since the call started.
|
globalHTTPStats.updateStats(api, r, statsWriter)
|
||||||
// We don't need to do nanosecond precision in this
|
|
||||||
// simply for the fact that it is not human readable.
|
|
||||||
durationSecs := time.Since(statsWriter.StartTime).Seconds()
|
|
||||||
|
|
||||||
globalHTTPStats.updateStats(api, r, statsWriter, durationSecs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,7 +516,7 @@ func proxyRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, e
|
|||||||
})
|
})
|
||||||
|
|
||||||
r.URL.Scheme = "http"
|
r.URL.Scheme = "http"
|
||||||
if globalIsSSL {
|
if globalIsTLS {
|
||||||
r.URL.Scheme = "https"
|
r.URL.Scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -161,7 +161,7 @@ func (st *HTTPStats) toServerHTTPStats() ServerHTTPStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update statistics from http request and response data
|
// Update statistics from http request and response data
|
||||||
func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.ResponseWriter, durationSecs float64) {
|
func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.ResponseWriter) {
|
||||||
// A successful request has a 2xx response code
|
// A successful request has a 2xx response code
|
||||||
successReq := (w.StatusCode >= 200 && w.StatusCode < 300)
|
successReq := (w.StatusCode >= 200 && w.StatusCode < 300)
|
||||||
|
|
||||||
@@ -172,10 +172,8 @@ func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.Response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
// Increment the prometheus http request response histogram with appropriate label
|
// Increment the prometheus http request response histogram with appropriate label
|
||||||
httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(durationSecs)
|
httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(w.TimeToFirstByte.Seconds())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare new HTTPStats structure
|
// Prepare new HTTPStats structure
|
||||||
|
|||||||
+13
-17
@@ -98,7 +98,8 @@ type DNSCache struct {
|
|||||||
lookupTimeout time.Duration
|
lookupTimeout time.Duration
|
||||||
|
|
||||||
cache map[string][]string
|
cache map[string][]string
|
||||||
closer func()
|
doneOnce sync.Once
|
||||||
|
doneCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSCache initializes DNS cache resolver and starts auto refreshing
|
// NewDNSCache initializes DNS cache resolver and starts auto refreshing
|
||||||
@@ -113,26 +114,24 @@ func NewDNSCache(freq time.Duration, lookupTimeout time.Duration) *DNSCache {
|
|||||||
lookupTimeout = defaultLookupTimeout
|
lookupTimeout = defaultLookupTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(freq)
|
|
||||||
ch := make(chan struct{})
|
|
||||||
closer := func() {
|
|
||||||
ticker.Stop()
|
|
||||||
close(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &DNSCache{
|
r := &DNSCache{
|
||||||
lookupHostFn: net.DefaultResolver.LookupHost,
|
lookupHostFn: net.DefaultResolver.LookupHost,
|
||||||
lookupTimeout: lookupTimeout,
|
lookupTimeout: lookupTimeout,
|
||||||
cache: make(map[string][]string, cacheSize),
|
cache: make(map[string][]string, cacheSize),
|
||||||
closer: closer,
|
doneCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(freq)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-timer.C:
|
||||||
|
timer.Reset(freq)
|
||||||
|
|
||||||
r.Refresh()
|
r.Refresh()
|
||||||
case <-ch:
|
case <-r.doneCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,10 +187,7 @@ func (r *DNSCache) Refresh() {
|
|||||||
|
|
||||||
// Stop stops auto refreshing.
|
// Stop stops auto refreshing.
|
||||||
func (r *DNSCache) Stop() {
|
func (r *DNSCache) Stop() {
|
||||||
r.Lock()
|
r.doneOnce.Do(func() {
|
||||||
defer r.Unlock()
|
close(r.doneCh)
|
||||||
if r.closer != nil {
|
})
|
||||||
r.closer()
|
|
||||||
r.closer = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,20 @@ const (
|
|||||||
AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length"
|
AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length"
|
||||||
AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5"
|
AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5"
|
||||||
|
|
||||||
|
// AWS server-side encryption headers for SSE-S3, SSE-KMS and SSE-C.
|
||||||
|
AmzServerSideEncryption = "X-Amz-Server-Side-Encryption"
|
||||||
|
AmzServerSideEncryptionKmsID = AmzServerSideEncryption + "-Aws-Kms-Key-Id"
|
||||||
|
AmzServerSideEncryptionKmsContext = AmzServerSideEncryption + "-Context"
|
||||||
|
AmzServerSideEncryptionCustomerAlgorithm = AmzServerSideEncryption + "-Customer-Algorithm"
|
||||||
|
AmzServerSideEncryptionCustomerKey = AmzServerSideEncryption + "-Customer-Key"
|
||||||
|
AmzServerSideEncryptionCustomerKeyMD5 = AmzServerSideEncryption + "-Customer-Key-Md5"
|
||||||
|
AmzServerSideEncryptionCopyCustomerAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
|
||||||
|
AmzServerSideEncryptionCopyCustomerKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
|
||||||
|
AmzServerSideEncryptionCopyCustomerKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5"
|
||||||
|
|
||||||
|
AmzEncryptionAES = "AES256"
|
||||||
|
AmzEncryptionKMS = "aws:kms"
|
||||||
|
|
||||||
// Signature v2 related constants
|
// Signature v2 related constants
|
||||||
AmzSignatureV2 = "Signature"
|
AmzSignatureV2 = "Signature"
|
||||||
AmzAccessKeyID = "AWSAccessKeyId"
|
AmzAccessKeyID = "AWSAccessKeyId"
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ import (
|
|||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
etcd "go.etcd.io/etcd/v3/clientv3"
|
etcd "go.etcd.io/etcd/clientv3"
|
||||||
"go.etcd.io/etcd/v3/mvcc/mvccpb"
|
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultContextTimeout = 30 * time.Second
|
var defaultContextTimeout = 30 * time.Second
|
||||||
|
|||||||
+20
-42
@@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -82,13 +83,12 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context, isSTS b
|
|||||||
basePrefix = iamConfigSTSPrefix
|
basePrefix = iamConfigSTSPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix, true) {
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
|
||||||
if item.Err != nil {
|
if item.Err != nil {
|
||||||
return item.Err
|
return item.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
user := item.Item
|
user := path.Dir(item.Item)
|
||||||
|
|
||||||
{
|
{
|
||||||
// 1. check if there is policy file in old location.
|
// 1. check if there is policy file in old location.
|
||||||
oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile)
|
oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile)
|
||||||
@@ -250,12 +250,12 @@ func (iamOS *IAMObjectStore) loadPolicyDoc(ctx context.Context, policy string, m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]iampolicy.Policy) error {
|
func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]iampolicy.Policy) error {
|
||||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPoliciesPrefix, true) {
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPoliciesPrefix) {
|
||||||
if item.Err != nil {
|
if item.Err != nil {
|
||||||
return item.Err
|
return item.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
policyName := item.Item
|
policyName := path.Dir(item.Item)
|
||||||
if err := iamOS.loadPolicyDoc(ctx, policyName, m); err != nil && err != errNoSuchPolicy {
|
if err := iamOS.loadPolicyDoc(ctx, policyName, m); err != nil && err != errNoSuchPolicy {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -319,12 +319,12 @@ func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType
|
|||||||
basePrefix = iamConfigUsersPrefix
|
basePrefix = iamConfigUsersPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix, true) {
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
|
||||||
if item.Err != nil {
|
if item.Err != nil {
|
||||||
return item.Err
|
return item.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
userName := item.Item
|
userName := path.Dir(item.Item)
|
||||||
if err := iamOS.loadUser(ctx, userName, userType, m); err != nil && err != errNoSuchUser {
|
if err := iamOS.loadUser(ctx, userName, userType, m); err != nil && err != errNoSuchUser {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -346,12 +346,12 @@ func (iamOS *IAMObjectStore) loadGroup(ctx context.Context, group string, m map[
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error {
|
func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error {
|
||||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigGroupsPrefix, true) {
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigGroupsPrefix) {
|
||||||
if item.Err != nil {
|
if item.Err != nil {
|
||||||
return item.Err
|
return item.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
group := item.Item
|
group := path.Dir(item.Item)
|
||||||
if err := iamOS.loadGroup(ctx, group, m); err != nil && err != errNoSuchGroup {
|
if err := iamOS.loadGroup(ctx, group, m); err != nil && err != errNoSuchGroup {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IA
|
|||||||
basePath = iamConfigPolicyDBUsersPrefix
|
basePath = iamConfigPolicyDBUsersPrefix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePath, false) {
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePath) {
|
||||||
if item.Err != nil {
|
if item.Err != nil {
|
||||||
return item.Err
|
return item.Err
|
||||||
}
|
}
|
||||||
@@ -566,27 +566,16 @@ type itemOrErr struct {
|
|||||||
// prefix. If dirs is true, only directories are listed, otherwise
|
// prefix. If dirs is true, only directories are listed, otherwise
|
||||||
// only objects are listed. All returned items have the pathPrefix
|
// only objects are listed. All returned items have the pathPrefix
|
||||||
// removed from their names.
|
// removed from their names.
|
||||||
func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix string, dirs bool) <-chan itemOrErr {
|
func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix string) <-chan itemOrErr {
|
||||||
ch := make(chan itemOrErr)
|
ch := make(chan itemOrErr)
|
||||||
|
|
||||||
dirList := func(lo ListObjectsInfo) []string {
|
|
||||||
return lo.Prefixes
|
|
||||||
}
|
|
||||||
filesList := func(lo ListObjectsInfo) (r []string) {
|
|
||||||
for _, o := range lo.Objects {
|
|
||||||
r = append(r, o.Name)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
|
|
||||||
marker := ""
|
// Allocate new results channel to receive ObjectInfo.
|
||||||
for {
|
objInfoCh := make(chan ObjectInfo)
|
||||||
lo, err := objAPI.ListObjects(ctx,
|
|
||||||
minioMetaBucket, pathPrefix, marker, SlashSeparator, maxObjectList)
|
if err := objAPI.Walk(ctx, minioMetaBucket, pathPrefix, objInfoCh, ObjectOptions{}); err != nil {
|
||||||
if err != nil {
|
|
||||||
select {
|
select {
|
||||||
case ch <- itemOrErr{Err: err}:
|
case ch <- itemOrErr{Err: err}:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -594,13 +583,8 @@ func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix stri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
marker = lo.NextMarker
|
for obj := range objInfoCh {
|
||||||
lister := dirList(lo)
|
item := strings.TrimPrefix(obj.Name, pathPrefix)
|
||||||
if !dirs {
|
|
||||||
lister = filesList(lo)
|
|
||||||
}
|
|
||||||
for _, itemPrefix := range lister {
|
|
||||||
item := strings.TrimPrefix(itemPrefix, pathPrefix)
|
|
||||||
item = strings.TrimSuffix(item, SlashSeparator)
|
item = strings.TrimSuffix(item, SlashSeparator)
|
||||||
select {
|
select {
|
||||||
case ch <- itemOrErr{Item: item}:
|
case ch <- itemOrErr{Item: item}:
|
||||||
@@ -608,10 +592,6 @@ func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix stri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !lo.IsTruncated {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return ch
|
return ch
|
||||||
@@ -620,11 +600,9 @@ func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix stri
|
|||||||
func (iamOS *IAMObjectStore) watch(ctx context.Context, sys *IAMSys) {
|
func (iamOS *IAMObjectStore) watch(ctx context.Context, sys *IAMSys) {
|
||||||
// Refresh IAMSys.
|
// Refresh IAMSys.
|
||||||
for {
|
for {
|
||||||
select {
|
time.Sleep(globalRefreshIAMInterval)
|
||||||
case <-ctx.Done():
|
if err := iamOS.loadAll(ctx, sys); err != nil {
|
||||||
return
|
logger.LogIf(ctx, err)
|
||||||
case <-time.NewTimer(globalRefreshIAMInterval).C:
|
|
||||||
logger.LogIf(ctx, iamOS.loadAll(ctx, sys))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-8
@@ -466,11 +466,10 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
var err error
|
|
||||||
for {
|
for {
|
||||||
// let one of the server acquire the lock, if not let them timeout.
|
// let one of the server acquire the lock, if not let them timeout.
|
||||||
// which shall be retried again by this loop.
|
// which shall be retried again by this loop.
|
||||||
if err = txnLk.GetLock(retryCtx, iamLockTimeout); err != nil {
|
if err := txnLk.GetLock(retryCtx, iamLockTimeout); err != nil {
|
||||||
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. trying to acquire lock")
|
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. trying to acquire lock")
|
||||||
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
|
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
|
||||||
continue
|
continue
|
||||||
@@ -480,7 +479,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
// **** WARNING ****
|
// **** WARNING ****
|
||||||
// Migrating to encrypted backend on etcd should happen before initialization of
|
// Migrating to encrypted backend on etcd should happen before initialization of
|
||||||
// IAM sub-system, make sure that we do not move the above codeblock elsewhere.
|
// IAM sub-system, make sure that we do not move the above codeblock elsewhere.
|
||||||
if err = migrateIAMConfigsEtcdToEncrypted(ctx, globalEtcdClient); err != nil {
|
if err := migrateIAMConfigsEtcdToEncrypted(ctx, globalEtcdClient); err != nil {
|
||||||
txnLk.Unlock()
|
txnLk.Unlock()
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to decrypt an encrypted ETCD backend for IAM users and policies: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to decrypt an encrypted ETCD backend for IAM users and policies: %w", err))
|
||||||
logger.LogIf(ctx, errors.New("IAM sub-system is partially initialized, some users may not be available"))
|
logger.LogIf(ctx, errors.New("IAM sub-system is partially initialized, some users may not be available"))
|
||||||
@@ -494,7 +493,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Migrate IAM configuration, if necessary.
|
// Migrate IAM configuration, if necessary.
|
||||||
if err = sys.doIAMConfigMigration(ctx); err != nil {
|
if err := sys.doIAMConfigMigration(ctx); err != nil {
|
||||||
txnLk.Unlock()
|
txnLk.Unlock()
|
||||||
if errors.Is(err, errDiskNotFound) ||
|
if errors.Is(err, errDiskNotFound) ||
|
||||||
errors.Is(err, errConfigNotFound) ||
|
errors.Is(err, errConfigNotFound) ||
|
||||||
@@ -515,14 +514,27 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sys.store.loadAll(ctx, sys)
|
for {
|
||||||
|
if err := sys.store.loadAll(ctx, sys); err != nil {
|
||||||
// Invalidate the old cred always, even upon error to avoid any leakage.
|
if errors.Is(err, errDiskNotFound) ||
|
||||||
globalOldCred = auth.Credentials{}
|
errors.Is(err, errConfigNotFound) ||
|
||||||
|
errors.Is(err, context.DeadlineExceeded) ||
|
||||||
|
errors.As(err, &rquorum) ||
|
||||||
|
errors.As(err, &wquorum) ||
|
||||||
|
isErrBucketNotFound(err) {
|
||||||
|
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v)", err)
|
||||||
|
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize IAM sub-system, some users may not be available %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize IAM sub-system, some users may not be available %w", err))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the old cred always, even upon error to avoid any leakage.
|
||||||
|
globalOldCred = auth.Credentials{}
|
||||||
go sys.store.watch(ctx, sys)
|
go sys.store.watch(ctx, sys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-5
@@ -47,12 +47,11 @@ func isWriteLock(lri []lockRequesterInfo) bool {
|
|||||||
// localLocker implements Dsync.NetLocker
|
// localLocker implements Dsync.NetLocker
|
||||||
type localLocker struct {
|
type localLocker struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
endpoint Endpoint
|
|
||||||
lockMap map[string][]lockRequesterInfo
|
lockMap map[string][]lockRequesterInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localLocker) String() string {
|
func (l *localLocker) String() string {
|
||||||
return l.endpoint.String()
|
return globalEndpoints.Localhost()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localLocker) canTakeUnlock(resources ...string) bool {
|
func (l *localLocker) canTakeUnlock(resources ...string) bool {
|
||||||
@@ -194,7 +193,7 @@ func (l *localLocker) DupLockMap() map[string][]lockRequesterInfo {
|
|||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
|
|
||||||
lockCopy := make(map[string][]lockRequesterInfo)
|
lockCopy := map[string][]lockRequesterInfo{}
|
||||||
for k, v := range l.lockMap {
|
for k, v := range l.lockMap {
|
||||||
lockCopy[k] = append(lockCopy[k], v...)
|
lockCopy[k] = append(lockCopy[k], v...)
|
||||||
}
|
}
|
||||||
@@ -253,9 +252,8 @@ func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocker(endpoint Endpoint) *localLocker {
|
func newLocker() *localLocker {
|
||||||
return &localLocker{
|
return &localLocker{
|
||||||
endpoint: endpoint,
|
|
||||||
lockMap: make(map[string][]lockRequesterInfo),
|
lockMap: make(map[string][]lockRequesterInfo),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
// lockRESTClient is authenticable lock REST client
|
// lockRESTClient is authenticable lock REST client
|
||||||
type lockRESTClient struct {
|
type lockRESTClient struct {
|
||||||
restClient *rest.Client
|
restClient *rest.Client
|
||||||
endpoint Endpoint
|
u *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func toLockError(err error) error {
|
func toLockError(err error) error {
|
||||||
@@ -51,7 +51,7 @@ func toLockError(err error) error {
|
|||||||
|
|
||||||
// String stringer *dsync.NetLocker* interface compatible method.
|
// String stringer *dsync.NetLocker* interface compatible method.
|
||||||
func (client *lockRESTClient) String() string {
|
func (client *lockRESTClient) String() string {
|
||||||
return client.endpoint.String()
|
return client.u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
|
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
|
||||||
@@ -137,7 +137,7 @@ func (client *lockRESTClient) Expired(ctx context.Context, args dsync.LockArgs)
|
|||||||
|
|
||||||
func newLockAPI(endpoint Endpoint) dsync.NetLocker {
|
func newLockAPI(endpoint Endpoint) dsync.NetLocker {
|
||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal {
|
||||||
return globalLockServers[endpoint]
|
return globalLockServer
|
||||||
}
|
}
|
||||||
return newlockRESTClient(endpoint)
|
return newlockRESTClient(endpoint)
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
|
|||||||
serverURL := &url.URL{
|
serverURL := &url.URL{
|
||||||
Scheme: endpoint.Scheme,
|
Scheme: endpoint.Scheme,
|
||||||
Host: endpoint.Host,
|
Host: endpoint.Host,
|
||||||
Path: pathJoin(lockRESTPrefix, endpoint.Path, lockRESTVersion),
|
Path: pathJoin(lockRESTPrefix, lockRESTVersion),
|
||||||
}
|
}
|
||||||
|
|
||||||
restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
|
restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
|
||||||
@@ -163,5 +163,8 @@ func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
|
|||||||
return !isNetworkError(err)
|
return !isNetworkError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &lockRESTClient{endpoint: endpoint, restClient: restClient}
|
return &lockRESTClient{u: &url.URL{
|
||||||
|
Scheme: endpoint.Scheme,
|
||||||
|
Host: endpoint.Host,
|
||||||
|
}, restClient: restClient}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-35
@@ -22,7 +22,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -217,16 +216,14 @@ type nameLockRequesterInfoPair struct {
|
|||||||
|
|
||||||
// getLongLivedLocks returns locks that are older than a certain time and
|
// getLongLivedLocks returns locks that are older than a certain time and
|
||||||
// have not been 'checked' for validity too soon enough
|
// have not been 'checked' for validity too soon enough
|
||||||
func getLongLivedLocks(interval time.Duration) map[Endpoint][]nameLockRequesterInfoPair {
|
func getLongLivedLocks(interval time.Duration) []nameLockRequesterInfoPair {
|
||||||
nlripMap := make(map[Endpoint][]nameLockRequesterInfoPair)
|
nlrip := []nameLockRequesterInfoPair{}
|
||||||
for endpoint, locker := range globalLockServers {
|
globalLockServer.mutex.Lock()
|
||||||
rslt := []nameLockRequesterInfoPair{}
|
for name, lriArray := range globalLockServer.lockMap {
|
||||||
locker.mutex.Lock()
|
|
||||||
for name, lriArray := range locker.lockMap {
|
|
||||||
for idx := range lriArray {
|
for idx := range lriArray {
|
||||||
// Check whether enough time has gone by since last check
|
// Check whether enough time has gone by since last check
|
||||||
if time.Since(lriArray[idx].TimeLastCheck) >= interval {
|
if time.Since(lriArray[idx].TimeLastCheck) >= interval {
|
||||||
rslt = append(rslt, nameLockRequesterInfoPair{
|
nlrip = append(nlrip, nameLockRequesterInfoPair{
|
||||||
name: name,
|
name: name,
|
||||||
lri: lriArray[idx],
|
lri: lriArray[idx],
|
||||||
})
|
})
|
||||||
@@ -234,10 +231,8 @@ func getLongLivedLocks(interval time.Duration) map[Endpoint][]nameLockRequesterI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nlripMap[endpoint] = rslt
|
globalLockServer.mutex.Unlock()
|
||||||
locker.mutex.Unlock()
|
return nlrip
|
||||||
}
|
|
||||||
return nlripMap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lockMaintenance loops over locks that have been active for some time and checks back
|
// lockMaintenance loops over locks that have been active for some time and checks back
|
||||||
@@ -277,18 +272,12 @@ func lockMaintenance(ctx context.Context, interval time.Duration) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allLockersFn := z.GetAllLockers
|
|
||||||
|
|
||||||
// Validate if long lived locks are indeed clean.
|
// Validate if long lived locks are indeed clean.
|
||||||
// Get list of long lived locks to check for staleness.
|
// Get list of long lived locks to check for staleness.
|
||||||
for lendpoint, nlrips := range getLongLivedLocks(interval) {
|
nlrips := getLongLivedLocks(interval)
|
||||||
nlripsMap := make(map[string]nlock, len(nlrips))
|
nlripsMap := make(map[string]nlock, len(nlrips))
|
||||||
for _, nlrip := range nlrips {
|
for _, nlrip := range nlrips {
|
||||||
for _, c := range allLockersFn() {
|
for _, c := range z.GetAllLockers() {
|
||||||
if !c.IsOnline() || c == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(GlobalContext, 5*time.Second)
|
ctx, cancel := context.WithTimeout(GlobalContext, 5*time.Second)
|
||||||
|
|
||||||
// Call back to original server verify whether the lock is
|
// Call back to original server verify whether the lock is
|
||||||
@@ -312,9 +301,7 @@ func lockMaintenance(ctx context.Context, interval time.Duration) error {
|
|||||||
// less than the quorum, we have locks expired.
|
// less than the quorum, we have locks expired.
|
||||||
if nlripsMap[nlrip.name].locks < nlrip.lri.Quorum {
|
if nlripsMap[nlrip.name].locks < nlrip.lri.Quorum {
|
||||||
// Purge the stale entry if it exists.
|
// Purge the stale entry if it exists.
|
||||||
globalLockServers[lendpoint].removeEntryIfExists(nlrip)
|
globalLockServer.removeEntryIfExists(nlrip)
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,18 +348,12 @@ func startLockMaintenance(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// registerLockRESTHandlers - register lock rest router.
|
// registerLockRESTHandlers - register lock rest router.
|
||||||
func registerLockRESTHandlers(router *mux.Router, endpointServerPools EndpointServerPools) {
|
func registerLockRESTHandlers(router *mux.Router) {
|
||||||
for _, ep := range endpointServerPools {
|
|
||||||
for _, endpoint := range ep.Endpoints {
|
|
||||||
if !endpoint.IsLocal {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lockServer := &lockRESTServer{
|
lockServer := &lockRESTServer{
|
||||||
ll: newLocker(endpoint),
|
ll: newLocker(),
|
||||||
}
|
}
|
||||||
|
|
||||||
subrouter := router.PathPrefix(path.Join(lockRESTPrefix, endpoint.Path)).Subrouter()
|
subrouter := router.PathPrefix(lockRESTPrefix).Subrouter()
|
||||||
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodHealth).HandlerFunc(httpTraceHdrs(lockServer.HealthHandler))
|
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodHealth).HandlerFunc(httpTraceHdrs(lockServer.HealthHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodLock).HandlerFunc(httpTraceHdrs(lockServer.LockHandler))
|
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodLock).HandlerFunc(httpTraceHdrs(lockServer.LockHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRLock).HandlerFunc(httpTraceHdrs(lockServer.RLockHandler))
|
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRLock).HandlerFunc(httpTraceHdrs(lockServer.RLockHandler))
|
||||||
@@ -380,9 +361,7 @@ func registerLockRESTHandlers(router *mux.Router, endpointServerPools EndpointSe
|
|||||||
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRUnlock).HandlerFunc(httpTraceHdrs(lockServer.RUnlockHandler))
|
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRUnlock).HandlerFunc(httpTraceHdrs(lockServer.RUnlockHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodExpired).HandlerFunc(httpTraceAll(lockServer.ExpiredHandler))
|
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodExpired).HandlerFunc(httpTraceAll(lockServer.ExpiredHandler))
|
||||||
|
|
||||||
globalLockServers[endpoint] = lockServer.ll
|
globalLockServer = lockServer.ll
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go startLockMaintenance(GlobalContext)
|
go startLockMaintenance(GlobalContext)
|
||||||
}
|
}
|
||||||
|
|||||||
+115
-77
@@ -22,6 +22,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -29,6 +30,7 @@ import (
|
|||||||
|
|
||||||
"github.com/klauspost/compress/s2"
|
"github.com/klauspost/compress/s2"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/console"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
"github.com/tinylib/msgp/msgp"
|
"github.com/tinylib/msgp/msgp"
|
||||||
)
|
)
|
||||||
@@ -43,6 +45,8 @@ type bucketMetacache struct {
|
|||||||
|
|
||||||
// caches indexed by id.
|
// caches indexed by id.
|
||||||
caches map[string]metacache
|
caches map[string]metacache
|
||||||
|
// cache ids indexed by root paths
|
||||||
|
cachesRoot map[string][]string `msg:"-"`
|
||||||
|
|
||||||
// Internal state
|
// Internal state
|
||||||
mu sync.RWMutex `msg:"-"`
|
mu sync.RWMutex `msg:"-"`
|
||||||
@@ -65,6 +69,13 @@ func newBucketMetacache(bucket string, cleanup bool) *bucketMetacache {
|
|||||||
return &bucketMetacache{
|
return &bucketMetacache{
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
caches: make(map[string]metacache, 10),
|
caches: make(map[string]metacache, 10),
|
||||||
|
cachesRoot: make(map[string][]string, 10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bucketMetacache) debugf(format string, data ...interface{}) {
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugf(format+"\n", data...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +87,8 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
case <-time.After(250 * time.Millisecond):
|
default:
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
}
|
}
|
||||||
objAPI = newObjectLayerFn()
|
objAPI = newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@@ -101,6 +113,7 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
|
|||||||
// Use global context for this.
|
// Use global context for this.
|
||||||
err := objAPI.GetObject(GlobalContext, minioMetaBucket, pathJoin("buckets", bucket, ".metacache", "index.s2"), 0, -1, w, "", ObjectOptions{})
|
err := objAPI.GetObject(GlobalContext, minioMetaBucket, pathJoin("buckets", bucket, ".metacache", "index.s2"), 0, -1, w, "", ObjectOptions{})
|
||||||
logger.LogIf(ctx, w.CloseWithError(err))
|
logger.LogIf(ctx, w.CloseWithError(err))
|
||||||
|
wg.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ObjectNotFound:
|
case ObjectNotFound:
|
||||||
@@ -113,7 +126,6 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
|
|||||||
}
|
}
|
||||||
return newBucketMetacache(bucket, false), err
|
return newBucketMetacache(bucket, false), err
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
if decErr != nil {
|
if decErr != nil {
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
return newBucketMetacache(bucket, false), err
|
return newBucketMetacache(bucket, false), err
|
||||||
@@ -128,6 +140,11 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
|
|||||||
logger.Info("loadBucketMetaCache: loaded cache name mismatch, want %s, got %s. Discarding.", bucket, meta.bucket)
|
logger.Info("loadBucketMetaCache: loaded cache name mismatch, want %s, got %s. Discarding.", bucket, meta.bucket)
|
||||||
return newBucketMetacache(bucket, true), nil
|
return newBucketMetacache(bucket, true), nil
|
||||||
}
|
}
|
||||||
|
meta.cachesRoot = make(map[string][]string, len(meta.caches)/10)
|
||||||
|
// Index roots
|
||||||
|
for id, cache := range meta.caches {
|
||||||
|
meta.cachesRoot[cache.root] = append(meta.cachesRoot[cache.root], id)
|
||||||
|
}
|
||||||
return &meta, nil
|
return &meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,10 +205,7 @@ func (b *bucketMetacache) findCache(o listPathOptions) metacache {
|
|||||||
return metacache{}
|
return metacache{}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint := func(msg string, data ...interface{}) {}
|
extend := globalAPIConfig.getExtendListLife()
|
||||||
if false {
|
|
||||||
debugPrint = logger.Info
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab a write lock, since we create one if we cannot find one.
|
// Grab a write lock, since we create one if we cannot find one.
|
||||||
if o.Create {
|
if o.Create {
|
||||||
@@ -204,81 +218,55 @@ func (b *bucketMetacache) findCache(o listPathOptions) metacache {
|
|||||||
|
|
||||||
// Check if exists already.
|
// Check if exists already.
|
||||||
if c, ok := b.caches[o.ID]; ok {
|
if c, ok := b.caches[o.ID]; ok {
|
||||||
debugPrint("returning existing %v", o.ID)
|
b.debugf("returning existing %v", o.ID)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
// No need to do expensive checks on transients.
|
||||||
|
if b.transient {
|
||||||
|
if !o.Create {
|
||||||
|
return metacache{
|
||||||
|
id: o.ID,
|
||||||
|
bucket: o.Bucket,
|
||||||
|
status: scanStateNone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new
|
||||||
|
best := o.newMetacache()
|
||||||
|
b.caches[o.ID] = best
|
||||||
|
b.updated = true
|
||||||
|
b.debugf("returning new cache %s, bucket: %v", best.id, best.bucket)
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
var best metacache
|
var best metacache
|
||||||
extend := globalAPIConfig.getExtendListLife()
|
rootSplit := strings.Split(o.BaseDir, slashSeparator)
|
||||||
for _, cached := range b.caches {
|
for i := range rootSplit {
|
||||||
// Never return transient caches if there is no id.
|
interesting := b.cachesRoot[path.Join(rootSplit[:i+1]...)]
|
||||||
if b.transient {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if cached.status == scanStateError || cached.status == scanStateNone || cached.dataVersion != metacacheStreamVersion {
|
|
||||||
debugPrint("cache %s state or stream version mismatch", cached.id)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cached.startedCycle < o.OldestCycle {
|
|
||||||
debugPrint("cache %s cycle too old", cached.id)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Root of what we are looking for must at least have
|
|
||||||
if !strings.HasPrefix(o.BaseDir, cached.root) {
|
|
||||||
debugPrint("cache %s prefix mismatch, cached:%v, want:%v", cached.id, cached.root, o.BaseDir)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cached.filter != "" && strings.HasPrefix(cached.filter, o.FilterPrefix) {
|
|
||||||
debugPrint("cache %s cannot be used because of filter %s", cached.id, cached.filter)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If the existing listing wasn't recursive root must match.
|
|
||||||
if !cached.recursive && o.BaseDir != cached.root {
|
|
||||||
debugPrint("cache %s non rec prefix mismatch, cached:%v, want:%v", cached.id, cached.root, o.BaseDir)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if o.Recursive && !cached.recursive {
|
|
||||||
debugPrint("cache %s not recursive", cached.id)
|
|
||||||
// If this is recursive the cached listing must be as well.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if o.Separator != slashSeparator && !cached.recursive {
|
|
||||||
debugPrint("cache %s not slashsep and not recursive", cached.id)
|
|
||||||
// Non slash separator requires recursive.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !cached.finished() && time.Since(cached.lastUpdate) > metacacheMaxRunningAge {
|
|
||||||
debugPrint("cache %s not running, time: %v", cached.id, time.Since(cached.lastUpdate))
|
|
||||||
// Abandoned
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if cached.finished() && cached.endedCycle <= o.OldestCycle {
|
for _, id := range interesting {
|
||||||
if extend <= 0 {
|
cached, ok := b.caches[id]
|
||||||
// If scan has ended the oldest requested must be less.
|
if !ok {
|
||||||
debugPrint("cache %s ended and cycle (%v) <= oldest allowed (%v)", cached.id, cached.endedCycle, o.OldestCycle)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if time.Since(cached.lastUpdate) > metacacheMaxRunningAge+extend {
|
if !cached.matches(&o, extend) {
|
||||||
// Cache ended within bloom cycle, but we can extend the life.
|
|
||||||
debugPrint("cache %s ended (%v) and beyond extended life (%v)", cached.id, cached.lastUpdate, extend+metacacheMaxRunningAge)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if cached.started.Before(best.started) {
|
if cached.started.Before(best.started) {
|
||||||
debugPrint("cache %s disregarded - we have a better", cached.id)
|
b.debugf("cache %s disregarded - we have a better", cached.id)
|
||||||
// If we already have a newer, keep that.
|
// If we already have a newer, keep that.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
best = cached
|
best = cached
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if !best.started.IsZero() {
|
if !best.started.IsZero() {
|
||||||
if o.Create {
|
if o.Create {
|
||||||
best.lastHandout = UTCNow()
|
best.lastHandout = UTCNow()
|
||||||
b.caches[best.id] = best
|
b.caches[best.id] = best
|
||||||
b.updated = true
|
b.updated = true
|
||||||
}
|
}
|
||||||
debugPrint("returning cached %s, status: %v, ended: %v", best.id, best.status, best.ended)
|
b.debugf("returning cached %s, status: %v, ended: %v", best.id, best.status, best.ended)
|
||||||
return best
|
return best
|
||||||
}
|
}
|
||||||
if !o.Create {
|
if !o.Create {
|
||||||
@@ -292,8 +280,9 @@ func (b *bucketMetacache) findCache(o listPathOptions) metacache {
|
|||||||
// Create new and add.
|
// Create new and add.
|
||||||
best = o.newMetacache()
|
best = o.newMetacache()
|
||||||
b.caches[o.ID] = best
|
b.caches[o.ID] = best
|
||||||
|
b.cachesRoot[best.root] = append(b.cachesRoot[best.root], best.id)
|
||||||
b.updated = true
|
b.updated = true
|
||||||
debugPrint("returning new cache %s, bucket: %v", best.id, best.bucket)
|
b.debugf("returning new cache %s, bucket: %v", best.id, best.bucket)
|
||||||
return best
|
return best
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,20 +292,18 @@ func (b *bucketMetacache) cleanup() {
|
|||||||
remove := make(map[string]struct{})
|
remove := make(map[string]struct{})
|
||||||
currentCycle := intDataUpdateTracker.current()
|
currentCycle := intDataUpdateTracker.current()
|
||||||
|
|
||||||
debugPrint := func(msg string, data ...interface{}) {}
|
// Test on a copy
|
||||||
if false {
|
// cleanup is the only one deleting caches.
|
||||||
debugPrint = logger.Info
|
caches, rootIdx := b.cloneCaches()
|
||||||
}
|
|
||||||
|
|
||||||
b.mu.RLock()
|
for id, cache := range caches {
|
||||||
for id, cache := range b.caches {
|
|
||||||
if b.transient && time.Since(cache.lastUpdate) > 15*time.Minute && time.Since(cache.lastHandout) > 15*time.Minute {
|
if b.transient && time.Since(cache.lastUpdate) > 15*time.Minute && time.Since(cache.lastHandout) > 15*time.Minute {
|
||||||
// Keep transient caches only for 1 hour.
|
// Keep transient caches only for 15 minutes.
|
||||||
remove[id] = struct{}{}
|
remove[id] = struct{}{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !cache.worthKeeping(currentCycle) {
|
if !cache.worthKeeping(currentCycle) {
|
||||||
debugPrint("cache %s not worth keeping", id)
|
b.debugf("cache %s not worth keeping", id)
|
||||||
remove[id] = struct{}{}
|
remove[id] = struct{}{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -334,30 +321,52 @@ func (b *bucketMetacache) cleanup() {
|
|||||||
|
|
||||||
// Check all non-deleted against eachother.
|
// Check all non-deleted against eachother.
|
||||||
// O(n*n), but should still be rather quick.
|
// O(n*n), but should still be rather quick.
|
||||||
for id, cache := range b.caches {
|
for id, cache := range caches {
|
||||||
if b.transient {
|
if b.transient {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if _, ok := remove[id]; ok {
|
if _, ok := remove[id]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, cache2 := range b.caches {
|
|
||||||
|
interesting := interestingCaches(cache.root, rootIdx)
|
||||||
|
for _, id2 := range interesting {
|
||||||
|
if _, ok := remove[id2]; ok || id2 == id {
|
||||||
|
// Don't check against one we are already removing
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cache2, ok := caches[id2]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if cache.canBeReplacedBy(&cache2) {
|
if cache.canBeReplacedBy(&cache2) {
|
||||||
debugPrint("cache %s can be replaced by %s", id, cache2.id)
|
b.debugf("cache %s can be replaced by %s", id, cache2.id)
|
||||||
remove[id] = struct{}{}
|
remove[id] = struct{}{}
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
debugPrint("cache %s can be NOT replaced by %s", id, cache2.id)
|
b.debugf("cache %s can be NOT replaced by %s", id, cache2.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.mu.RUnlock()
|
|
||||||
for id := range remove {
|
for id := range remove {
|
||||||
b.deleteCache(id)
|
b.deleteCache(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Potentially interesting caches.
|
||||||
|
// Will only add root if request is for root.
|
||||||
|
func interestingCaches(root string, cachesRoot map[string][]string) []string {
|
||||||
|
var interesting []string
|
||||||
|
rootSplit := strings.Split(root, slashSeparator)
|
||||||
|
for i := range rootSplit {
|
||||||
|
want := path.Join(rootSplit[:i+1]...)
|
||||||
|
interesting = append(interesting, cachesRoot[want]...)
|
||||||
|
}
|
||||||
|
return interesting
|
||||||
|
}
|
||||||
|
|
||||||
// updateCache will update a cache by id.
|
// updateCache will update a cache by id.
|
||||||
// If the cache cannot be found nil is returned.
|
// If the cache cannot be found nil is returned.
|
||||||
// The bucket cache will be locked until the done .
|
// The bucket cache will be locked until the done .
|
||||||
@@ -391,6 +400,25 @@ func (b *bucketMetacache) updateCacheEntry(update metacache) (metacache, error)
|
|||||||
return existing, nil
|
return existing, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cloneCaches will return a clone of all current caches.
|
||||||
|
func (b *bucketMetacache) cloneCaches() (map[string]metacache, map[string][]string) {
|
||||||
|
b.mu.RLock()
|
||||||
|
defer b.mu.RUnlock()
|
||||||
|
dst := make(map[string]metacache, len(b.caches))
|
||||||
|
for k, v := range b.caches {
|
||||||
|
dst[k] = v
|
||||||
|
}
|
||||||
|
// Copy indexes
|
||||||
|
dst2 := make(map[string][]string, len(b.cachesRoot))
|
||||||
|
for k, v := range b.cachesRoot {
|
||||||
|
tmp := make([]string, len(v))
|
||||||
|
copy(tmp, v)
|
||||||
|
dst2[k] = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst, dst2
|
||||||
|
}
|
||||||
|
|
||||||
// getCache will return a clone of a specific metacache.
|
// getCache will return a clone of a specific metacache.
|
||||||
// Will return nil if the cache doesn't exist.
|
// Will return nil if the cache doesn't exist.
|
||||||
func (b *bucketMetacache) getCache(id string) *metacache {
|
func (b *bucketMetacache) getCache(id string) *metacache {
|
||||||
@@ -406,9 +434,6 @@ func (b *bucketMetacache) getCache(id string) *metacache {
|
|||||||
// deleteAll will delete all on disk data for ALL caches.
|
// deleteAll will delete all on disk data for ALL caches.
|
||||||
// Deletes are performed concurrently.
|
// Deletes are performed concurrently.
|
||||||
func (b *bucketMetacache) deleteAll() {
|
func (b *bucketMetacache) deleteAll() {
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ez, ok := newObjectLayerFn().(*erasureServerPools)
|
ez, ok := newObjectLayerFn().(*erasureServerPools)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -416,11 +441,15 @@ func (b *bucketMetacache) deleteAll() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
b.updated = true
|
b.updated = true
|
||||||
if !b.transient {
|
if !b.transient {
|
||||||
// Delete all.
|
// Delete all.
|
||||||
ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(b.bucket, slashSeparator))
|
ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(b.bucket, slashSeparator))
|
||||||
b.caches = make(map[string]metacache, 10)
|
b.caches = make(map[string]metacache, 10)
|
||||||
|
b.cachesRoot = make(map[string][]string, 10)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,6 +471,15 @@ func (b *bucketMetacache) deleteCache(id string) {
|
|||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
c, ok := b.caches[id]
|
c, ok := b.caches[id]
|
||||||
if ok {
|
if ok {
|
||||||
|
// Delete from root map.
|
||||||
|
list := b.cachesRoot[c.root]
|
||||||
|
for i, lid := range list {
|
||||||
|
if id == lid {
|
||||||
|
list = append(list[:i], list[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.cachesRoot[c.root] = list
|
||||||
delete(b.caches, id)
|
delete(b.caches, id)
|
||||||
b.updated = true
|
b.updated = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Benchmark_bucketMetacache_findCache(b *testing.B) {
|
||||||
|
bm := newBucketMetacache("", false)
|
||||||
|
const elements = 50000
|
||||||
|
const paths = 100
|
||||||
|
if elements%paths != 0 {
|
||||||
|
b.Fatal("elements must be divisible by the number of paths")
|
||||||
|
}
|
||||||
|
var pathNames [paths]string
|
||||||
|
for i := range pathNames[:] {
|
||||||
|
pathNames[i] = fmt.Sprintf("prefix/%d", i)
|
||||||
|
}
|
||||||
|
for i := 0; i < elements; i++ {
|
||||||
|
bm.findCache(listPathOptions{
|
||||||
|
ID: mustGetUUID(),
|
||||||
|
Bucket: "",
|
||||||
|
BaseDir: pathNames[i%paths],
|
||||||
|
Prefix: "",
|
||||||
|
FilterPrefix: "",
|
||||||
|
Marker: "",
|
||||||
|
Limit: 0,
|
||||||
|
AskDisks: 0,
|
||||||
|
Recursive: false,
|
||||||
|
Separator: slashSeparator,
|
||||||
|
Create: true,
|
||||||
|
CurrentCycle: uint64(i),
|
||||||
|
OldestCycle: uint64(i - 1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bm.findCache(listPathOptions{
|
||||||
|
ID: mustGetUUID(),
|
||||||
|
Bucket: "",
|
||||||
|
BaseDir: pathNames[i%paths],
|
||||||
|
Prefix: "",
|
||||||
|
FilterPrefix: "",
|
||||||
|
Marker: "",
|
||||||
|
Limit: 0,
|
||||||
|
AskDisks: 0,
|
||||||
|
Recursive: false,
|
||||||
|
Separator: slashSeparator,
|
||||||
|
Create: true,
|
||||||
|
CurrentCycle: uint64(i % elements),
|
||||||
|
OldestCycle: uint64(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-19
@@ -73,11 +73,13 @@ func (e *metaCacheEntry) matches(other *metaCacheEntry, bucket string) bool {
|
|||||||
if len(e.metadata) != len(other.metadata) || e.name != other.name {
|
if len(e.metadata) != len(other.metadata) || e.name != other.name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
eFi, eErr := e.fileInfo(bucket)
|
eFi, eErr := e.fileInfo(bucket)
|
||||||
oFi, oErr := e.fileInfo(bucket)
|
oFi, oErr := other.fileInfo(bucket)
|
||||||
if eErr != nil || oErr != nil {
|
if eErr != nil || oErr != nil {
|
||||||
return eErr == oErr
|
return eErr == oErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return eFi.ModTime.Equal(oFi.ModTime) && eFi.Size == oFi.Size && eFi.VersionID == oFi.VersionID
|
return eFi.ModTime.Equal(oFi.ModTime) && eFi.Size == oFi.Size && eFi.VersionID == oFi.VersionID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,11 +215,12 @@ func (m metaCacheEntries) resolve(r *metadataResolutionParams) (selected *metaCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get new entry metadata
|
// Get new entry metadata
|
||||||
objExists++
|
|
||||||
fiv, err := entry.fileInfo(r.bucket)
|
fiv, err := entry.fileInfo(r.bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
objExists++
|
||||||
if selFIV == nil {
|
if selFIV == nil {
|
||||||
selected = entry
|
selected = entry
|
||||||
selFIV = fiv
|
selFIV = fiv
|
||||||
@@ -227,14 +230,8 @@ func (m metaCacheEntries) resolve(r *metadataResolutionParams) (selected *metaCa
|
|||||||
if selected.matches(entry, r.bucket) {
|
if selected.matches(entry, r.bucket) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Select latest modtime.
|
|
||||||
if fiv.ModTime.After(selFIV.ModTime) {
|
|
||||||
selected = entry
|
|
||||||
selFIV = fiv
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If directory, we need quorum.
|
// If directory, we need quorum.
|
||||||
if dirExists > 0 && dirExists < r.dirQuorum {
|
if dirExists > 0 && dirExists < r.dirQuorum {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -309,7 +306,7 @@ func (m *metaCacheEntriesSorted) iterate(fn func(entry metaCacheEntry) (cont boo
|
|||||||
|
|
||||||
// fileInfoVersions converts the metadata to FileInfoVersions where possible.
|
// fileInfoVersions converts the metadata to FileInfoVersions where possible.
|
||||||
// Metadata that cannot be decoded is skipped.
|
// Metadata that cannot be decoded is skipped.
|
||||||
func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, afterV string) (versions []ObjectInfo, commonPrefixes []string) {
|
func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, afterV string) (versions []ObjectInfo) {
|
||||||
versions = make([]ObjectInfo, 0, m.len())
|
versions = make([]ObjectInfo, 0, m.len())
|
||||||
prevPrefix := ""
|
prevPrefix := ""
|
||||||
for _, entry := range m.o {
|
for _, entry := range m.o {
|
||||||
@@ -323,7 +320,11 @@ func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, aft
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prevPrefix = currPrefix
|
prevPrefix = currPrefix
|
||||||
commonPrefixes = append(commonPrefixes, currPrefix)
|
versions = append(versions, ObjectInfo{
|
||||||
|
IsDir: true,
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: currPrefix,
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,17 +357,20 @@ func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, aft
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prevPrefix = currPrefix
|
prevPrefix = currPrefix
|
||||||
commonPrefixes = append(commonPrefixes, currPrefix)
|
versions = append(versions, ObjectInfo{
|
||||||
continue
|
IsDir: true,
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: currPrefix,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return versions, commonPrefixes
|
return versions
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileInfoVersions converts the metadata to FileInfoVersions where possible.
|
// fileInfoVersions converts the metadata to FileInfoVersions where possible.
|
||||||
// Metadata that cannot be decoded is skipped.
|
// Metadata that cannot be decoded is skipped.
|
||||||
func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (objects []ObjectInfo, commonPrefixes []string) {
|
func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (objects []ObjectInfo) {
|
||||||
objects = make([]ObjectInfo, 0, m.len())
|
objects = make([]ObjectInfo, 0, m.len())
|
||||||
prevPrefix := ""
|
prevPrefix := ""
|
||||||
for _, entry := range m.o {
|
for _, entry := range m.o {
|
||||||
@@ -380,7 +384,11 @@ func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (ob
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prevPrefix = currPrefix
|
prevPrefix = currPrefix
|
||||||
commonPrefixes = append(commonPrefixes, currPrefix)
|
objects = append(objects, ObjectInfo{
|
||||||
|
IsDir: true,
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: currPrefix,
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,12 +413,15 @@ func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (ob
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prevPrefix = currPrefix
|
prevPrefix = currPrefix
|
||||||
commonPrefixes = append(commonPrefixes, currPrefix)
|
objects = append(objects, ObjectInfo{
|
||||||
continue
|
IsDir: true,
|
||||||
|
Bucket: bucket,
|
||||||
|
Name: currPrefix,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return objects, commonPrefixes
|
return objects
|
||||||
}
|
}
|
||||||
|
|
||||||
// forwardTo will truncate m so only entries that are s or after is in the list.
|
// forwardTo will truncate m so only entries that are s or after is in the list.
|
||||||
|
|||||||
@@ -84,11 +84,21 @@ func (z *erasureServerPools) listPath(ctx context.Context, o listPathOptions) (e
|
|||||||
o.ID = mustGetUUID()
|
o.ID = mustGetUUID()
|
||||||
}
|
}
|
||||||
o.BaseDir = baseDirFromPrefix(o.Prefix)
|
o.BaseDir = baseDirFromPrefix(o.Prefix)
|
||||||
if o.singleObject {
|
if o.discardResult {
|
||||||
// Override for single object.
|
// Override for single object.
|
||||||
o.BaseDir = o.Prefix
|
o.BaseDir = o.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For very small recursive listings, don't same cache.
|
||||||
|
// Attempts to avoid expensive listings to run for a long
|
||||||
|
// while when clients aren't interested in results.
|
||||||
|
// If the client DOES resume the listing a full cache
|
||||||
|
// will be generated due to the marker without ID and this check failing.
|
||||||
|
if o.Limit < 10 && o.Marker == "" && o.Create && o.Recursive {
|
||||||
|
o.discardResult = true
|
||||||
|
o.Transient = true
|
||||||
|
}
|
||||||
|
|
||||||
var cache metacache
|
var cache metacache
|
||||||
// If we don't have a list id we must ask the server if it has a cache or create a new.
|
// If we don't have a list id we must ask the server if it has a cache or create a new.
|
||||||
if o.Create {
|
if o.Create {
|
||||||
@@ -137,13 +147,11 @@ func (z *erasureServerPools) listPath(ctx context.Context, o listPathOptions) (e
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var errs []error
|
var errs []error
|
||||||
allAtEOF := true
|
allAtEOF := true
|
||||||
asked := 0
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
// Ask all sets and merge entries.
|
// Ask all sets and merge entries.
|
||||||
for _, zone := range z.serverPools {
|
for _, zone := range z.serverPools {
|
||||||
for _, set := range zone.sets {
|
for _, set := range zone.sets {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
asked++
|
|
||||||
go func(i int, set *erasureObjects) {
|
go func(i int, set *erasureObjects) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
e, err := set.listPath(ctx, o)
|
e, err := set.listPath(ctx, o)
|
||||||
@@ -206,7 +214,9 @@ func (z *erasureServerPools) listPath(ctx context.Context, o listPathOptions) (e
|
|||||||
}
|
}
|
||||||
truncated := entries.len() > o.Limit || !allAtEOF
|
truncated := entries.len() > o.Limit || !allAtEOF
|
||||||
entries.truncate(o.Limit)
|
entries.truncate(o.Limit)
|
||||||
|
if !o.discardResult {
|
||||||
entries.listID = o.ID
|
entries.listID = o.ID
|
||||||
|
}
|
||||||
if !truncated {
|
if !truncated {
|
||||||
return entries, io.EOF
|
return entries, io.EOF
|
||||||
}
|
}
|
||||||
+92
-83
@@ -29,6 +29,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/console"
|
"github.com/minio/minio/pkg/console"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
@@ -94,8 +95,9 @@ type listPathOptions struct {
|
|||||||
// A transient result will never be returned from the cache so knowing the list id is required.
|
// A transient result will never be returned from the cache so knowing the list id is required.
|
||||||
Transient bool
|
Transient bool
|
||||||
|
|
||||||
// singleObject will assume that prefix refers to an exact single object.
|
// discardResult will not persist the cache to storage.
|
||||||
singleObject bool
|
// When the initial results are returned listing will be canceled.
|
||||||
|
discardResult bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -122,11 +124,22 @@ func (o listPathOptions) newMetacache() metacache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *listPathOptions) debugf(format string, data ...interface{}) {
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugf(format, data...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *listPathOptions) debugln(data ...interface{}) {
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugln(data...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// gatherResults will collect all results on the input channel and filter results according to the options.
|
// gatherResults will collect all results on the input channel and filter results according to the options.
|
||||||
// Caller should close the channel when done.
|
// Caller should close the channel when done.
|
||||||
// The returned function will return the results once there is enough or input is closed.
|
// The returned function will return the results once there is enough or input is closed.
|
||||||
func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCacheEntriesSorted, error) {
|
func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCacheEntriesSorted, error) {
|
||||||
const debugPrint = false
|
|
||||||
var resultsDone = make(chan metaCacheEntriesSorted)
|
var resultsDone = make(chan metaCacheEntriesSorted)
|
||||||
// Copy so we can mutate
|
// Copy so we can mutate
|
||||||
resCh := resultsDone
|
resCh := resultsDone
|
||||||
@@ -142,35 +155,23 @@ func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCa
|
|||||||
if !o.IncludeDirectories && entry.isDir() {
|
if !o.IncludeDirectories && entry.isDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if debugPrint {
|
o.debugln("gather got:", entry.name)
|
||||||
console.Infoln("gather got:", entry.name)
|
|
||||||
}
|
|
||||||
if o.Marker != "" && entry.name <= o.Marker {
|
if o.Marker != "" && entry.name <= o.Marker {
|
||||||
if debugPrint {
|
o.debugln("pre marker")
|
||||||
console.Infoln("pre marker")
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(entry.name, o.Prefix) {
|
if !strings.HasPrefix(entry.name, o.Prefix) {
|
||||||
if debugPrint {
|
o.debugln("not in prefix")
|
||||||
console.Infoln("not in prefix")
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !o.Recursive && !entry.isInDir(o.Prefix, o.Separator) {
|
if !o.Recursive && !entry.isInDir(o.Prefix, o.Separator) {
|
||||||
if debugPrint {
|
o.debugln("not in dir", o.Prefix, o.Separator)
|
||||||
console.Infoln("not in dir", o.Prefix, o.Separator)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !o.InclDeleted && entry.isObject() {
|
if !o.InclDeleted && entry.isObject() && entry.isLatestDeletemarker() {
|
||||||
if entry.isLatestDeletemarker() {
|
o.debugln("latest is delete marker")
|
||||||
if debugPrint {
|
|
||||||
console.Infoln("latest delete")
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if o.Limit > 0 && results.len() >= o.Limit {
|
if o.Limit > 0 && results.len() >= o.Limit {
|
||||||
// We have enough and we have more.
|
// We have enough and we have more.
|
||||||
// Do not return io.EOF
|
// Do not return io.EOF
|
||||||
@@ -181,9 +182,7 @@ func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCa
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if debugPrint {
|
o.debugln("adding...")
|
||||||
console.Infoln("adding...")
|
|
||||||
}
|
|
||||||
results.o = append(results.o, entry)
|
results.o = append(results.o, entry)
|
||||||
}
|
}
|
||||||
if resCh != nil {
|
if resCh != nil {
|
||||||
@@ -207,19 +206,15 @@ func (o *listPathOptions) findFirstPart(fi FileInfo) (int, error) {
|
|||||||
if search == "" {
|
if search == "" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
const debugPrint = false
|
o.debugln("searching for ", search)
|
||||||
if debugPrint {
|
|
||||||
console.Infoln("searching for ", search)
|
|
||||||
}
|
|
||||||
var tmp metacacheBlock
|
var tmp metacacheBlock
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
i := 0
|
i := 0
|
||||||
for {
|
for {
|
||||||
partKey := fmt.Sprintf("%s-metacache-part-%d", ReservedMetadataPrefixLower, i)
|
partKey := fmt.Sprintf("%s-metacache-part-%d", ReservedMetadataPrefixLower, i)
|
||||||
v, ok := fi.Metadata[partKey]
|
v, ok := fi.Metadata[partKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
if debugPrint {
|
o.debugln("no match in metadata, waiting")
|
||||||
console.Infoln("no match in metadata, waiting")
|
|
||||||
}
|
|
||||||
return -1, io.ErrUnexpectedEOF
|
return -1, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
err := json.Unmarshal([]byte(v), &tmp)
|
err := json.Unmarshal([]byte(v), &tmp)
|
||||||
@@ -231,27 +226,18 @@ func (o *listPathOptions) findFirstPart(fi FileInfo) (int, error) {
|
|||||||
return 0, errFileNotFound
|
return 0, errFileNotFound
|
||||||
}
|
}
|
||||||
if tmp.First >= search {
|
if tmp.First >= search {
|
||||||
if debugPrint {
|
o.debugln("First >= search", v)
|
||||||
console.Infoln("First >= search", v)
|
|
||||||
}
|
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
if tmp.Last >= search {
|
if tmp.Last >= search {
|
||||||
if debugPrint {
|
o.debugln("Last >= search", v)
|
||||||
|
|
||||||
console.Infoln("Last >= search", v)
|
|
||||||
}
|
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
if tmp.EOS {
|
if tmp.EOS {
|
||||||
if debugPrint {
|
o.debugln("no match, at EOS", v)
|
||||||
console.Infoln("no match, at EOS", v)
|
|
||||||
}
|
|
||||||
return -3, io.EOF
|
return -3, io.EOF
|
||||||
}
|
}
|
||||||
if debugPrint {
|
o.debugln("First ", tmp.First, "<", search, " search", i)
|
||||||
console.Infoln("First ", tmp.First, "<", search, " search", i)
|
|
||||||
}
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,8 +263,10 @@ func getMetacacheBlockInfo(fi FileInfo, block int) (*metacacheBlock, error) {
|
|||||||
return &tmp, json.Unmarshal([]byte(v), &tmp)
|
return &tmp, json.Unmarshal([]byte(v), &tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const metacachePrefix = ".metacache"
|
||||||
|
|
||||||
func metacachePrefixForID(bucket, id string) string {
|
func metacachePrefixForID(bucket, id string) string {
|
||||||
return pathJoin("buckets", bucket, ".metacache", id)
|
return pathJoin(bucketMetaPrefix, bucket, metacachePrefix, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectPath returns the object path of the cache.
|
// objectPath returns the object path of the cache.
|
||||||
@@ -312,7 +300,6 @@ func (o *listPathOptions) SetFilter() {
|
|||||||
// Will return io.EOF if there are no more entries with the same filter.
|
// Will return io.EOF if there are no more entries with the same filter.
|
||||||
// The last entry can be used as a marker to resume the listing.
|
// The last entry can be used as a marker to resume the listing.
|
||||||
func (r *metacacheReader) filter(o listPathOptions) (entries metaCacheEntriesSorted, err error) {
|
func (r *metacacheReader) filter(o listPathOptions) (entries metaCacheEntriesSorted, err error) {
|
||||||
const debugPrint = false
|
|
||||||
// Forward to prefix, if any
|
// Forward to prefix, if any
|
||||||
err = r.forwardTo(o.Prefix)
|
err = r.forwardTo(o.Prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -334,9 +321,8 @@ func (r *metacacheReader) filter(o listPathOptions) (entries metaCacheEntriesSor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if debugPrint {
|
o.debugln("forwarded to ", o.Prefix, "marker:", o.Marker, "sep:", o.Separator)
|
||||||
console.Infoln("forwarded to ", o.Prefix, "marker:", o.Marker, "sep:", o.Separator)
|
|
||||||
}
|
|
||||||
// Filter
|
// Filter
|
||||||
if !o.Recursive {
|
if !o.Recursive {
|
||||||
entries.o = make(metaCacheEntries, 0, o.Limit)
|
entries.o = make(metaCacheEntries, 0, o.Limit)
|
||||||
@@ -446,7 +432,11 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
|||||||
|
|
||||||
// We got a stream to start at.
|
// We got a stream to start at.
|
||||||
loadedPart := 0
|
loadedPart := 0
|
||||||
var buf bytes.Buffer
|
buf := bufferPool.Get().(*bytes.Buffer)
|
||||||
|
defer func() {
|
||||||
|
buf.Reset()
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
}()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -496,7 +486,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
err := er.getObjectWithFileInfo(ctx, minioMetaBucket, o.objectPath(partN), 0, fi.Size, &buf, fi, metaArr, onlineDisks)
|
err := er.getObjectWithFileInfo(ctx, minioMetaBucket, o.objectPath(partN), 0, fi.Size, buf, fi, metaArr, onlineDisks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch toObjectErr(err, minioMetaBucket, o.objectPath(partN)).(type) {
|
switch toObjectErr(err, minioMetaBucket, o.objectPath(partN)).(type) {
|
||||||
case ObjectNotFound:
|
case ObjectNotFound:
|
||||||
@@ -512,7 +502,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
|||||||
return entries, err
|
return entries, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tmp, err := newMetacacheReader(&buf)
|
tmp, err := newMetacacheReader(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entries, err
|
return entries, err
|
||||||
}
|
}
|
||||||
@@ -549,15 +539,16 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (er erasureObjects) SetDriveCount() int {
|
||||||
|
return er.setDriveCount
|
||||||
|
}
|
||||||
|
|
||||||
// Will return io.EOF if continuing would not yield more results.
|
// Will return io.EOF if continuing would not yield more results.
|
||||||
func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entries metaCacheEntriesSorted, err error) {
|
func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entries metaCacheEntriesSorted, err error) {
|
||||||
const debugPrint = false
|
o.debugf("listPath with options: %#v\n", o)
|
||||||
if debugPrint {
|
|
||||||
console.Printf("listPath with options: %#v\n", o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if we have the listing stored.
|
// See if we have the listing stored.
|
||||||
if !o.Create && !o.singleObject {
|
if !o.Create && !o.discardResult {
|
||||||
entries, err := er.streamMetadataParts(ctx, o)
|
entries, err := er.streamMetadataParts(ctx, o)
|
||||||
if IsErr(err, []error{
|
if IsErr(err, []error{
|
||||||
nil,
|
nil,
|
||||||
@@ -580,9 +571,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
rpc := globalNotificationSys.restClientFromHash(o.Bucket)
|
rpc := globalNotificationSys.restClientFromHash(o.Bucket)
|
||||||
var metaMu sync.Mutex
|
var metaMu sync.Mutex
|
||||||
|
|
||||||
if debugPrint {
|
o.debugln("listPath: scanning bucket:", o.Bucket, "basedir:", o.BaseDir, "prefix:", o.Prefix, "marker:", o.Marker)
|
||||||
console.Println("listPath: scanning bucket:", o.Bucket, "basedir:", o.BaseDir, "prefix:", o.Prefix, "marker:", o.Marker)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect from call above, but cancel on exit.
|
// Disconnect from call above, but cancel on exit.
|
||||||
ctx, cancel := context.WithCancel(GlobalContext)
|
ctx, cancel := context.WithCancel(GlobalContext)
|
||||||
@@ -590,9 +579,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
disks := er.getOnlineDisks()
|
disks := er.getOnlineDisks()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if debugPrint {
|
o.debugln("listPath returning:", entries.len(), "err:", err)
|
||||||
console.Println("listPath returning:", entries.len(), "err:", err)
|
|
||||||
}
|
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
go func(err string) {
|
go func(err string) {
|
||||||
metaMu.Lock()
|
metaMu.Lock()
|
||||||
@@ -608,15 +595,15 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
askDisks := o.AskDisks
|
askDisks := o.AskDisks
|
||||||
if askDisks == -1 {
|
listingQuorum := askDisks - 1
|
||||||
askDisks = getReadQuorum(er.SetDriveCount())
|
// Special case: ask all disks if the drive count is 4
|
||||||
|
if askDisks == -1 || er.SetDriveCount() == 4 {
|
||||||
|
askDisks = len(disks) // with 'strict' quorum list on all online disks.
|
||||||
|
listingQuorum = getReadQuorum(er.SetDriveCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(disks) < askDisks {
|
if len(disks) < askDisks {
|
||||||
err = InsufficientReadQuorum{}
|
err = InsufficientReadQuorum{}
|
||||||
if debugPrint {
|
|
||||||
console.Errorf("listPath: Insufficient disks, %d of %d needed are available", len(disks), askDisks)
|
|
||||||
}
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("listPath: Insufficient disks, %d of %d needed are available", len(disks), askDisks))
|
logger.LogIf(ctx, fmt.Errorf("listPath: Insufficient disks, %d of %d needed are available", len(disks), askDisks))
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
@@ -628,16 +615,26 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create output for our results.
|
// Create output for our results.
|
||||||
cacheCh := make(chan metaCacheEntry, metacacheBlockSize)
|
var cacheCh chan metaCacheEntry
|
||||||
|
if !o.discardResult {
|
||||||
|
cacheCh = make(chan metaCacheEntry, metacacheBlockSize)
|
||||||
|
}
|
||||||
|
|
||||||
// Create filter for results.
|
// Create filter for results.
|
||||||
filterCh := make(chan metaCacheEntry, 100)
|
filterCh := make(chan metaCacheEntry, 100)
|
||||||
filteredResults := o.gatherResults(filterCh)
|
filteredResults := o.gatherResults(filterCh)
|
||||||
closeChannels := func() {
|
closeChannels := func() {
|
||||||
|
if !o.discardResult {
|
||||||
close(cacheCh)
|
close(cacheCh)
|
||||||
|
}
|
||||||
close(filterCh)
|
close(filterCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancel listing on return if non-saved list.
|
||||||
|
if o.discardResult {
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
// Save continuous updates
|
// Save continuous updates
|
||||||
@@ -665,21 +662,26 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
const retryDelay = 200 * time.Millisecond
|
const retryDelay = 200 * time.Millisecond
|
||||||
const maxTries = 10
|
const maxTries = 5
|
||||||
|
|
||||||
// Write results to disk.
|
var bw *metacacheBlockWriter
|
||||||
bw := newMetacacheBlockWriter(cacheCh, func(b *metacacheBlock) error {
|
|
||||||
if o.singleObject {
|
|
||||||
// Don't save single object listings.
|
// Don't save single object listings.
|
||||||
|
if !o.discardResult {
|
||||||
|
// Write results to disk.
|
||||||
|
bw = newMetacacheBlockWriter(cacheCh, func(b *metacacheBlock) error {
|
||||||
|
// if the block is 0 bytes and its a first block skip it.
|
||||||
|
// skip only this for Transient caches.
|
||||||
|
if len(b.data) == 0 && b.n == 0 && o.Transient {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if debugPrint {
|
o.debugln("listPath: saving block", b.n, "to", o.objectPath(b.n))
|
||||||
console.Println("listPath: saving block", b.n, "to", o.objectPath(b.n))
|
r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)), false)
|
||||||
}
|
|
||||||
r, err := hash.NewReader(bytes.NewBuffer(b.data), int64(len(b.data)), "", "", int64(len(b.data)), false)
|
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
custom := b.headerKV()
|
custom := b.headerKV()
|
||||||
_, err = er.putObject(ctx, minioMetaBucket, o.objectPath(b.n), NewPutObjReader(r, nil, nil), ObjectOptions{UserDefined: custom})
|
_, err = er.putObject(ctx, minioMetaBucket, o.objectPath(b.n), NewPutObjReader(r, nil, nil), ObjectOptions{
|
||||||
|
UserDefined: custom,
|
||||||
|
NoLock: true, // No need to hold namespace lock, each prefix caches uniquely.
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
metaMu.Lock()
|
metaMu.Lock()
|
||||||
if meta.error != "" {
|
if meta.error != "" {
|
||||||
@@ -715,11 +717,12 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// How to resolve results.
|
// How to resolve results.
|
||||||
resolver := metadataResolutionParams{
|
resolver := metadataResolutionParams{
|
||||||
dirQuorum: askDisks - 1,
|
dirQuorum: listingQuorum,
|
||||||
objQuorum: askDisks - 1,
|
objQuorum: listingQuorum,
|
||||||
bucket: o.Bucket,
|
bucket: o.Bucket,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,16 +732,20 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
path: o.BaseDir,
|
path: o.BaseDir,
|
||||||
recursive: o.Recursive,
|
recursive: o.Recursive,
|
||||||
filterPrefix: o.FilterPrefix,
|
filterPrefix: o.FilterPrefix,
|
||||||
minDisks: askDisks - 1,
|
minDisks: listingQuorum,
|
||||||
agreed: func(entry metaCacheEntry) {
|
agreed: func(entry metaCacheEntry) {
|
||||||
|
if !o.discardResult {
|
||||||
cacheCh <- entry
|
cacheCh <- entry
|
||||||
|
}
|
||||||
filterCh <- entry
|
filterCh <- entry
|
||||||
},
|
},
|
||||||
partial: func(entries metaCacheEntries, nAgreed int, errs []error) {
|
partial: func(entries metaCacheEntries, nAgreed int, errs []error) {
|
||||||
// Results Disagree :-(
|
// Results Disagree :-(
|
||||||
entry, ok := entries.resolve(&resolver)
|
entry, ok := entries.resolve(&resolver)
|
||||||
if ok {
|
if ok {
|
||||||
|
if !o.discardResult {
|
||||||
cacheCh <- *entry
|
cacheCh <- *entry
|
||||||
|
}
|
||||||
filterCh <- *entry
|
filterCh <- *entry
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -759,6 +766,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
metaMu.Unlock()
|
metaMu.Unlock()
|
||||||
|
|
||||||
closeChannels()
|
closeChannels()
|
||||||
|
if !o.discardResult {
|
||||||
if err := bw.Close(); err != nil {
|
if err := bw.Close(); err != nil {
|
||||||
metaMu.Lock()
|
metaMu.Lock()
|
||||||
meta.error = err.Error()
|
meta.error = err.Error()
|
||||||
@@ -766,6 +774,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
|||||||
meta, err = o.updateMetacacheListing(meta, rpc)
|
meta, err = o.updateMetacacheListing(meta, rpc)
|
||||||
metaMu.Unlock()
|
metaMu.Unlock()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return filteredResults()
|
return filteredResults()
|
||||||
@@ -814,7 +823,7 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
askDisks := len(disks)
|
askDisks := len(disks)
|
||||||
var readers = make([]*metacacheReader, askDisks)
|
readers := make([]*metacacheReader, askDisks)
|
||||||
for i := range disks {
|
for i := range disks {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
d := disks[i]
|
d := disks[i]
|
||||||
@@ -831,7 +840,7 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
|||||||
ReportNotFound: opts.reportNotFound,
|
ReportNotFound: opts.reportNotFound,
|
||||||
FilterPrefix: opts.filterPrefix}, w)
|
FilterPrefix: opts.filterPrefix}, w)
|
||||||
w.CloseWithError(err)
|
w.CloseWithError(err)
|
||||||
if err != io.EOF {
|
if err != io.EOF && err != nil && err.Error() != errFileNotFound.Error() {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
+15
-6
@@ -519,7 +519,7 @@ func (r *metacacheReader) readN(n int, inclDeleted, inclDirs bool, prefix string
|
|||||||
if !inclDirs && meta.isDir() {
|
if !inclDirs && meta.isDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if meta.isDir() && !inclDeleted && meta.isLatestDeletemarker() {
|
if !inclDeleted && meta.isLatestDeletemarker() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res = append(res, meta)
|
res = append(res, meta)
|
||||||
@@ -745,6 +745,12 @@ type metacacheBlockWriter struct {
|
|||||||
blockEntries int
|
blockEntries int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bufferPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// newMetacacheBlockWriter provides a streaming block writer.
|
// newMetacacheBlockWriter provides a streaming block writer.
|
||||||
// Each block is the size of the capacity of the input channel.
|
// Each block is the size of the capacity of the input channel.
|
||||||
// The caller should close to indicate the stream has ended.
|
// The caller should close to indicate the stream has ended.
|
||||||
@@ -755,12 +761,15 @@ func newMetacacheBlockWriter(in <-chan metaCacheEntry, nextBlock func(b *metacac
|
|||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
var current metacacheBlock
|
var current metacacheBlock
|
||||||
var n int
|
var n int
|
||||||
var buf bytes.Buffer
|
buf := bufferPool.Get().(*bytes.Buffer)
|
||||||
block := newMetacacheWriter(&buf, 1<<20)
|
defer func() {
|
||||||
|
buf.Reset()
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
}()
|
||||||
|
block := newMetacacheWriter(buf, 1<<20)
|
||||||
defer block.Close()
|
defer block.Close()
|
||||||
finishBlock := func() {
|
finishBlock := func() {
|
||||||
err := block.Close()
|
if err := block.Close(); err != nil {
|
||||||
if err != nil {
|
|
||||||
w.streamErr = err
|
w.streamErr = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -769,7 +778,7 @@ func newMetacacheBlockWriter(in <-chan metaCacheEntry, nextBlock func(b *metacac
|
|||||||
// Prepare for next
|
// Prepare for next
|
||||||
current.n++
|
current.n++
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
block.Reset(&buf)
|
block.Reset(buf)
|
||||||
current.First = ""
|
current.First = ""
|
||||||
}
|
}
|
||||||
for o := range in {
|
for o := range in {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user