mirror of
https://github.com/pgsty/minio.git
synced 2026-11-03 14:22:12 +03:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea66a52ed1 | |||
| 55dd017e62 | |||
| 12353caf35 | |||
| a57c747667 | |||
| 28661c0413 | |||
| 04a152be12 | |||
| bce3f8237d | |||
| 16a45e5aff | |||
| 000a60f238 | |||
| bf278ca36f | |||
| 97f2bc26b9 | |||
| bba562235b | |||
| 2337e5f803 | |||
| ffd7b7059c | |||
| 5c0acbc6fc | |||
| 5a52bc7ff6 | |||
| 045e1fed2b | |||
| a861d38532 | |||
| 20a15567b8 | |||
| 94f67ad224 | |||
| 36ee110563 | |||
| 1dc25bcf5f | |||
| 2d96745156 | |||
| 9d49688c87 | |||
| 8e09374cb8 | |||
| 58d90ed73c | |||
| e857b6741d | |||
| 0505ef83b5 | |||
| 22bc15d89b |
+1
-3
@@ -9,7 +9,6 @@ site/
|
||||
/.idea/
|
||||
/Minio.iml
|
||||
**/access.log
|
||||
build/
|
||||
vendor/**/*.js
|
||||
vendor/**/*.json
|
||||
release
|
||||
@@ -23,5 +22,4 @@ prime/
|
||||
stage/
|
||||
.sia_temp/
|
||||
config.json
|
||||
healthcheck
|
||||
check-user
|
||||
dockerscripts/healthcheck
|
||||
+12
-2
@@ -2,6 +2,14 @@ go_import_path: github.com/minio/minio
|
||||
|
||||
language: go
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- shellcheck
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
# this ensures PRs based on a local branch are not built twice
|
||||
# the downside is that a PR targeting a different branch is not built
|
||||
# but as a workaround you can add the branch to this list
|
||||
@@ -26,7 +34,7 @@ matrix:
|
||||
- $HOME/gopath/pkg/mod
|
||||
- $HOME/go/pkg/mod
|
||||
|
||||
go: 1.12.1
|
||||
go: 1.12.5
|
||||
script:
|
||||
- make
|
||||
- diff -au <(gofmt -s -d cmd) <(printf "")
|
||||
@@ -37,12 +45,14 @@ matrix:
|
||||
- make verify
|
||||
- make coverage
|
||||
- cd browser && yarn && yarn test && cd ..
|
||||
- bash -c 'shopt -s globstar; shellcheck mint/**/*.sh'
|
||||
|
||||
- os: windows
|
||||
env:
|
||||
- ARCH=x86_64
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
go: 1.12.1
|
||||
go: 1.12.5
|
||||
script:
|
||||
- go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe
|
||||
- bash buildscripts/go-coverage.sh
|
||||
|
||||
+3
-4
@@ -10,20 +10,19 @@ RUN \
|
||||
apk add --no-cache git && \
|
||||
git clone https://github.com/minio/minio && cd minio && \
|
||||
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \
|
||||
cd dockerscripts; go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go && \
|
||||
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/check-user check-user.go
|
||||
cd dockerscripts; go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
|
||||
|
||||
FROM alpine:3.9
|
||||
|
||||
ENV MINIO_UPDATE off
|
||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
MINIO_SECRET_KEY_FILE=secret_key
|
||||
MINIO_SECRET_KEY_FILE=secret_key \
|
||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
COPY --from=0 /go/bin/minio /usr/bin/minio
|
||||
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
||||
COPY --from=0 /usr/bin/check-user /usr/bin/check-user
|
||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||
|
||||
RUN \
|
||||
|
||||
+4
-4
@@ -2,20 +2,20 @@ FROM alpine:3.9
|
||||
|
||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck dockerscripts/check-user /usr/bin/
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck /usr/bin/
|
||||
COPY minio /usr/bin/
|
||||
|
||||
ENV MINIO_UPDATE off
|
||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
MINIO_SECRET_KEY_FILE=secret_key
|
||||
MINIO_SECRET_KEY_FILE=secret_key \
|
||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||
|
||||
RUN \
|
||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/healthcheck && \
|
||||
chmod +x /usr/bin/check-user
|
||||
chmod +x /usr/bin/healthcheck
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
ENV GOROOT /usr/local/go
|
||||
|
||||
ENV GOPATH /usr/local
|
||||
|
||||
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
||||
|
||||
ENV MINT_ROOT_DIR /mint
|
||||
|
||||
COPY mint /mint
|
||||
|
||||
RUN apt-get --yes update && apt-get --yes upgrade && \
|
||||
apt-get --yes --quiet install wget jq curl git dnsmasq && \
|
||||
cd /mint && /mint/release.sh
|
||||
|
||||
WORKDIR /mint
|
||||
|
||||
ENTRYPOINT ["/mint/entrypoint.sh"]
|
||||
+4
-6
@@ -7,20 +7,19 @@ ENV GO111MODULE on
|
||||
RUN \
|
||||
apk add --no-cache git && \
|
||||
git clone https://github.com/minio/minio && cd minio/dockerscripts && \
|
||||
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go && \
|
||||
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/check-user check-user.go
|
||||
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
|
||||
|
||||
FROM alpine:3.9
|
||||
|
||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||
|
||||
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
||||
COPY --from=0 /usr/bin/check-user /usr/bin/check-user
|
||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||
|
||||
ENV MINIO_UPDATE off
|
||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
MINIO_SECRET_KEY_FILE=secret_key
|
||||
MINIO_SECRET_KEY_FILE=secret_key \
|
||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||
|
||||
RUN \
|
||||
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
|
||||
@@ -28,8 +27,7 @@ RUN \
|
||||
curl https://dl.min.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/healthcheck && \
|
||||
chmod +x /usr/bin/check-user
|
||||
chmod +x /usr/bin/healthcheck
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
|
||||
+5
-3
@@ -33,7 +33,7 @@ USER ci
|
||||
RUN make
|
||||
RUN bash -c 'diff -au <(gofmt -s -d cmd) <(printf "")'
|
||||
RUN bash -c 'diff -au <(gofmt -s -d pkg) <(printf "")'
|
||||
RUN for d in $(go list ./... | grep -v browser); do go test -v -race --timeout 15m "$d"; done
|
||||
RUN for d in $(go list ./... | grep -v browser); do go test -v -race --timeout 20m "$d"; done
|
||||
RUN make verifiers
|
||||
RUN make crosscompile
|
||||
RUN make verify
|
||||
@@ -56,15 +56,17 @@ FROM ubuntu:16.04
|
||||
|
||||
COPY --from=0 /go/src/github.com/minio/minio/minio /usr/bin/minio
|
||||
COPY buildscripts/gateway-tests.sh /usr/bin/gateway-tests.sh
|
||||
COPY mint /mint
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV LANG C.UTF-8
|
||||
ENV GOROOT /usr/local/go
|
||||
ENV GOPATH /usr/local
|
||||
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
||||
ENV MINT_ROOT_DIR /mint
|
||||
|
||||
RUN apt-get --yes update && apt-get --yes upgrade && apt-get --yes --quiet install wget jq curl git dnsmasq && \
|
||||
git clone https://github.com/minio/mint.git /mint && \
|
||||
RUN apt-get --yes update && apt-get --yes upgrade && \
|
||||
apt-get --yes --quiet install wget jq curl git dnsmasq && \
|
||||
cd /mint && /mint/release.sh
|
||||
|
||||
WORKDIR /mint
|
||||
|
||||
@@ -73,7 +73,6 @@ build: checks
|
||||
@echo "Building minio binary to './minio'"
|
||||
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio 1>/dev/null
|
||||
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/dockerscripts/healthcheck $(PWD)/dockerscripts/healthcheck.go 1>/dev/null
|
||||
@GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/dockerscripts/check-user $(PWD)/dockerscripts/check-user.go 1>/dev/null
|
||||
|
||||
docker: build
|
||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||
|
||||
Generated
+6
-26
@@ -5817,12 +5817,6 @@
|
||||
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"dev": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
@@ -6408,12 +6402,6 @@
|
||||
"requires": {
|
||||
"lodash": "^4.17.11"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7645,14 +7633,14 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||
"version": "4.17.14",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz",
|
||||
"integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
|
||||
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q=="
|
||||
"version": "4.17.14",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.14.tgz",
|
||||
"integrity": "sha512-7zchRrGa8UZXjD/4ivUWP1867jDkhzTG2c/uj739utSd7O/pFFdxspCemIFKEEjErbcqRzn8nKnGsi7mvTgRPA=="
|
||||
},
|
||||
"lodash._baseisequal": {
|
||||
"version": "3.0.7",
|
||||
@@ -11078,14 +11066,6 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"request-promise-native": {
|
||||
|
||||
+7
-10
@@ -5075,8 +5075,9 @@ locate-path@^3.0.0:
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash-es@^4.2.0, lodash-es@^4.2.1:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
|
||||
version "4.17.14"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.14.tgz#12a95a963cc5955683cee3b74e85458954f37ecc"
|
||||
integrity sha512-7zchRrGa8UZXjD/4ivUWP1867jDkhzTG2c/uj739utSd7O/pFFdxspCemIFKEEjErbcqRzn8nKnGsi7mvTgRPA==
|
||||
|
||||
lodash._baseisequal@^3.0.0:
|
||||
version "3.0.7"
|
||||
@@ -5185,14 +5186,10 @@ lodash.words@^3.0.0:
|
||||
dependencies:
|
||||
lodash._root "^3.0.0"
|
||||
|
||||
lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
|
||||
lodash@^4.17.3, lodash@^4.17.5, lodash@^4.3.0:
|
||||
version "4.17.11"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
||||
lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
|
||||
version "4.17.14"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
|
||||
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
|
||||
|
||||
loglevel@^1.4.1:
|
||||
version "1.6.1"
|
||||
|
||||
+11
-12
@@ -241,8 +241,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
thisAddr, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
||||
hostName, err := getHostName(r)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -252,7 +251,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
// Once we have received all the ServerInfo from peers
|
||||
// add the local peer server info as well.
|
||||
serverInfo = append(serverInfo, ServerInfo{
|
||||
Addr: thisAddr.String(),
|
||||
Addr: hostName,
|
||||
Data: &ServerInfoData{
|
||||
StorageInfo: objectAPI.StorageInfo(ctx),
|
||||
ConnStats: globalConnStats.toServerConnStats(),
|
||||
@@ -330,7 +329,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
// Get drive performance details from local server's drive(s)
|
||||
dp := localEndpointsDrivePerf(globalEndpoints)
|
||||
dp := localEndpointsDrivePerf(globalEndpoints, r)
|
||||
|
||||
// Notify all other MinIO peers to report drive performance numbers
|
||||
dps := globalNotificationSys.DrivePerfInfo()
|
||||
@@ -348,7 +347,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
case "cpu":
|
||||
// Get CPU load details from local server's cpu(s)
|
||||
cpu := localEndpointsCPULoad(globalEndpoints)
|
||||
cpu := localEndpointsCPULoad(globalEndpoints, r)
|
||||
// Notify all other MinIO peers to report cpu load numbers
|
||||
cpus := globalNotificationSys.CPULoadInfo()
|
||||
cpus = append(cpus, cpu)
|
||||
@@ -365,7 +364,7 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
case "mem":
|
||||
// Get mem usage details from local server(s)
|
||||
m := localEndpointsMemUsage(globalEndpoints)
|
||||
m := localEndpointsMemUsage(globalEndpoints, r)
|
||||
// Notify all other MinIO peers to report mem usage numbers
|
||||
mems := globalNotificationSys.MemUsageInfo()
|
||||
mems = append(mems, m)
|
||||
@@ -443,8 +442,7 @@ func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
thisAddr, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
||||
hostName, err := getHostName(r)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -455,7 +453,7 @@ func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request
|
||||
// add the local peer locks list as well.
|
||||
localLocks := globalLockServer.ll.DupLockMap()
|
||||
peerLocks = append(peerLocks, &PeerLocks{
|
||||
Addr: thisAddr.String(),
|
||||
Addr: hostName,
|
||||
Locks: localLocks,
|
||||
})
|
||||
|
||||
@@ -683,12 +681,12 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Start writing response to client
|
||||
started = true
|
||||
setCommonHeaders(w)
|
||||
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
// Set 200 OK status
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
// Send whitespace and keep connection open
|
||||
w.Write([]byte("\n\r"))
|
||||
w.Write([]byte(" "))
|
||||
w.(http.Flusher).Flush()
|
||||
case hr := <-respCh:
|
||||
switch hr.apiErr {
|
||||
@@ -1488,7 +1486,8 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Avoid reusing tcp connection if read timeout is hit
|
||||
// This is needed to make r.Context().Done() work as
|
||||
// expected in case of read timeout
|
||||
w.Header().Add(xhttp.Connection, "close")
|
||||
w.Header().Set(xhttp.Connection, "close")
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
|
||||
@@ -91,7 +91,9 @@ var (
|
||||
"enable": false,
|
||||
"format": "namespace",
|
||||
"url": "",
|
||||
"index": ""
|
||||
"index": "",
|
||||
"queueDir": "",
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"kafka": {
|
||||
@@ -166,7 +168,9 @@ var (
|
||||
"tls": {
|
||||
"enable": false,
|
||||
"skipVerify": false
|
||||
}
|
||||
},
|
||||
"queueDir": "",
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"postgresql": {
|
||||
@@ -706,9 +710,6 @@ func TestAdminServerInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, serverInfo := range results {
|
||||
if len(serverInfo.Addr) == 0 {
|
||||
t.Error("Expected server address to be non empty")
|
||||
}
|
||||
if serverInfo.Error != "" {
|
||||
t.Errorf("Unexpected error = %v\n", serverInfo.Error)
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ const (
|
||||
ErrSlowDown
|
||||
ErrInvalidPrefixMarker
|
||||
ErrBadRequest
|
||||
ErrKeyTooLongError
|
||||
// Add new error codes here.
|
||||
|
||||
// SSE-S3 related API errors
|
||||
@@ -187,6 +188,7 @@ const (
|
||||
ErrRequestBodyParse
|
||||
ErrObjectExistsAsDirectory
|
||||
ErrInvalidObjectName
|
||||
ErrInvalidObjectNamePrefixSlash
|
||||
ErrInvalidResourceName
|
||||
ErrServerNotInitialized
|
||||
ErrOperationTimedOut
|
||||
@@ -682,6 +684,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "400 BadRequest",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrKeyTooLongError: {
|
||||
Code: "KeyTooLongError",
|
||||
Description: "Your key is too long",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// FIXME: Actual XML error response also contains the header which missed in list of signed header parameters.
|
||||
ErrUnsignedHeaders: {
|
||||
@@ -885,6 +892,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Object name contains unsupported characters.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidObjectNamePrefixSlash: {
|
||||
Code: "XMinioInvalidObjectName",
|
||||
Description: "Object name contains a leading slash.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidResourceName: {
|
||||
Code: "XMinioInvalidResourceName",
|
||||
Description: "Resource name contains bad components such as \"..\" or \".\".",
|
||||
@@ -1579,6 +1591,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrMethodNotAllowed
|
||||
case ObjectNameInvalid:
|
||||
apiErr = ErrInvalidObjectName
|
||||
case ObjectNamePrefixAsSlash:
|
||||
apiErr = ErrInvalidObjectNamePrefixSlash
|
||||
case InvalidUploadID:
|
||||
apiErr = ErrNoSuchUpload
|
||||
case InvalidPart:
|
||||
@@ -1639,6 +1653,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrBackendDown
|
||||
case crypto.Error:
|
||||
apiErr = ErrObjectTampered
|
||||
case ObjectNameTooLong:
|
||||
apiErr = ErrKeyTooLongError
|
||||
default:
|
||||
var ie, iw int
|
||||
// This work-around is to handle the issue golang/go#30648
|
||||
|
||||
+1
-1
@@ -582,7 +582,7 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
||||
case "AccessDenied":
|
||||
// The request is from browser and also if browser
|
||||
// is enabled we need to redirect.
|
||||
if browser {
|
||||
if browser && globalIsBrowserEnabled {
|
||||
w.Header().Set(xhttp.Location, minioReservedBucketPath+reqURL.Path)
|
||||
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||
return
|
||||
|
||||
@@ -131,7 +131,7 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
||||
b.h.Write(buf)
|
||||
|
||||
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
||||
err = hashMismatchError{hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))}
|
||||
err = HashMismatchError{hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))}
|
||||
logger.LogIf(context.Background(), err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -155,33 +155,3 @@ func bitrotWriterSum(w io.Writer) []byte {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify if a file has bitrot error.
|
||||
func bitrotCheckFile(disk StorageAPI, volume string, filePath string, tillOffset int64, algo BitrotAlgorithm, sum []byte, shardSize int64) (err error) {
|
||||
if algo != HighwayHash256S {
|
||||
buf := []byte{}
|
||||
// For whole-file bitrot we don't need to read the entire file as the bitrot verify happens on the server side even if we read 0-bytes.
|
||||
_, err = disk.ReadFile(volume, filePath, 0, buf, NewBitrotVerifier(algo, sum))
|
||||
return err
|
||||
}
|
||||
buf := make([]byte, shardSize)
|
||||
r := newStreamingBitrotReader(disk, volume, filePath, tillOffset, algo, shardSize)
|
||||
defer closeBitrotReaders([]io.ReaderAt{r})
|
||||
var offset int64
|
||||
for {
|
||||
if offset == tillOffset {
|
||||
break
|
||||
}
|
||||
var n int
|
||||
tmpBuf := buf
|
||||
if int64(len(tmpBuf)) > (tillOffset - offset) {
|
||||
tmpBuf = tmpBuf[:(tillOffset - offset)]
|
||||
}
|
||||
n, err = r.ReadAt(tmpBuf, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset += int64(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -537,7 +537,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
// Remove all tmp files creating during multipart upload
|
||||
// Remove all tmp files created during multipart upload
|
||||
defer form.RemoveAll()
|
||||
|
||||
// Extract all form fields
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
"github.com/minio/minio/pkg/event/target"
|
||||
@@ -246,6 +247,8 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
|
||||
target, err := target.NewHTTPClientTarget(*host, w)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
|
||||
@@ -314,7 +314,7 @@ func (s *serverConfig) TestNotificationTargets() error {
|
||||
if !v.Enable {
|
||||
continue
|
||||
}
|
||||
t, err := target.NewElasticsearchTarget(k, v)
|
||||
t, err := target.NewElasticsearchTarget(k, v, GlobalServiceDoneCh)
|
||||
if err != nil {
|
||||
return fmt.Errorf("elasticsearch(%s): %s", k, err.Error())
|
||||
}
|
||||
@@ -369,7 +369,7 @@ func (s *serverConfig) TestNotificationTargets() error {
|
||||
if !v.Enable {
|
||||
continue
|
||||
}
|
||||
t, err := target.NewNSQTarget(k, v)
|
||||
t, err := target.NewNSQTarget(k, v, GlobalServiceDoneCh)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nsq(%s): %s", k, err.Error())
|
||||
}
|
||||
@@ -651,7 +651,7 @@ func getNotificationTargets(config *serverConfig) *event.TargetList {
|
||||
|
||||
for id, args := range config.Notify.Elasticsearch {
|
||||
if args.Enable {
|
||||
newTarget, err := target.NewElasticsearchTarget(id, args)
|
||||
newTarget, err := target.NewElasticsearchTarget(id, args, GlobalServiceDoneCh)
|
||||
if err != nil {
|
||||
logger.LogIf(context.Background(), err)
|
||||
continue
|
||||
@@ -724,7 +724,7 @@ func getNotificationTargets(config *serverConfig) *event.TargetList {
|
||||
|
||||
for id, args := range config.Notify.NSQ {
|
||||
if args.Enable {
|
||||
newTarget, err := target.NewNSQTarget(id, args)
|
||||
newTarget, err := target.NewNSQTarget(id, args, GlobalServiceDoneCh)
|
||||
if err != nil {
|
||||
logger.LogIf(context.Background(), err)
|
||||
continue
|
||||
|
||||
@@ -224,7 +224,7 @@ func TestValidateConfig(t *testing.T) {
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "invalid", "url": "example.com", "index": "myindex" } }}}`, false},
|
||||
|
||||
// Test 24 - Test valid Format for ElasticSearch
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "namespace", "url": "example.com", "index": "myindex" } }}}`, true},
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "namespace", "url": "example.com", "index": "myindex", "queueDir": "", "queueLimit": 0 } }}}`, true},
|
||||
|
||||
// Test 25 - Test Format for Redis
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "redis": { "1": { "enable": true, "format": "invalid", "address": "example.com:80", "password": "xxx", "key": "key1" } }}}`, false},
|
||||
@@ -236,7 +236,7 @@ func TestValidateConfig(t *testing.T) {
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mqtt": { "1": { "enable": true, "broker": "", "topic": "", "qos": 0, "username": "", "password": "", "queueDir": "", "queueLimit": 0}}}}`, false},
|
||||
|
||||
// Test 28 - Test NSQ
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "nsq": { "1": { "enable": true, "nsqdAddress": "", "topic": ""} }}}`, false},
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "nsq": { "1": { "enable": true, "nsqdAddress": "", "topic": "", "queueDir": "", "queueLimit": 0} }}}`, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/event/target"
|
||||
"github.com/minio/minio/pkg/iam/policy"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/iam/validator"
|
||||
"github.com/minio/minio/pkg/quick"
|
||||
)
|
||||
@@ -884,7 +884,7 @@ type serverConfigV32 struct {
|
||||
} `json:"policy"`
|
||||
}
|
||||
|
||||
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit in notification targets.
|
||||
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit in all notification targets.
|
||||
type serverConfigV33 struct {
|
||||
quick.Config `json:"-"` // ignore interfaces
|
||||
|
||||
|
||||
@@ -119,9 +119,9 @@ func dailySweeper() {
|
||||
break
|
||||
}
|
||||
|
||||
// Perform a sweep round each 24 hours
|
||||
// Perform a sweep round each month
|
||||
for {
|
||||
if time.Since(lastSweepTime) < 24*time.Hour {
|
||||
if time.Since(lastSweepTime) < 30*24*time.Hour {
|
||||
time.Sleep(time.Hour)
|
||||
continue
|
||||
}
|
||||
|
||||
+4
-1
@@ -945,7 +945,10 @@ func checkAtimeSupport(dir string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.Copy(ioutil.Discard, file); err != io.EOF {
|
||||
// add a sleep to ensure atime change is detected
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
if _, err = io.Copy(ioutil.Discard, file); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+5
-12
@@ -124,17 +124,10 @@ func TestGetCacheFSMaxUse(t *testing.T) {
|
||||
|
||||
// test wildcard patterns for excluding entries from cache
|
||||
func TestCacheExclusion(t *testing.T) {
|
||||
fsDirs, err := getRandomDisks(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
cobjects := &cacheObjects{
|
||||
cache: nil,
|
||||
}
|
||||
cconfig := CacheConfig{Expiry: 30, Drives: fsDirs}
|
||||
cobjects, err := newServerCacheObjects(cconfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cobj := cobjects.(*cacheObjects)
|
||||
GlobalServiceDoneCh <- struct{}{}
|
||||
|
||||
testCases := []struct {
|
||||
bucketName string
|
||||
objectName string
|
||||
@@ -155,8 +148,8 @@ func TestCacheExclusion(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
cobj.exclude = []string{testCase.excludePattern}
|
||||
if cobj.isCacheExclude(testCase.bucketName, testCase.objectName) != testCase.expectedResult {
|
||||
cobjects.exclude = []string{testCase.excludePattern}
|
||||
if cobjects.isCacheExclude(testCase.bucketName, testCase.objectName) != testCase.expectedResult {
|
||||
t.Fatal("Cache exclusion test failed for case ", i)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-7
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
@@ -288,7 +289,7 @@ func (endpoints EndpointList) UpdateIsLocal() error {
|
||||
|
||||
// localEndpointsMemUsage - returns ServerMemUsageInfo for only the
|
||||
// local endpoints from given list of endpoints
|
||||
func localEndpointsMemUsage(endpoints EndpointList) ServerMemUsageInfo {
|
||||
func localEndpointsMemUsage(endpoints EndpointList, r *http.Request) ServerMemUsageInfo {
|
||||
var memUsages []mem.Usage
|
||||
var historicUsages []mem.Usage
|
||||
scratchSpace := map[string]bool{}
|
||||
@@ -303,8 +304,12 @@ func localEndpointsMemUsage(endpoints EndpointList) ServerMemUsageInfo {
|
||||
scratchSpace[endpoint.Host] = true
|
||||
}
|
||||
}
|
||||
addr := r.Host
|
||||
if globalIsDistXL {
|
||||
addr = GetLocalPeer(endpoints)
|
||||
}
|
||||
return ServerMemUsageInfo{
|
||||
Addr: GetLocalPeer(endpoints),
|
||||
Addr: addr,
|
||||
Usage: memUsages,
|
||||
HistoricUsage: historicUsages,
|
||||
}
|
||||
@@ -312,7 +317,7 @@ func localEndpointsMemUsage(endpoints EndpointList) ServerMemUsageInfo {
|
||||
|
||||
// localEndpointsCPULoad - returns ServerCPULoadInfo for only the
|
||||
// local endpoints from given list of endpoints
|
||||
func localEndpointsCPULoad(endpoints EndpointList) ServerCPULoadInfo {
|
||||
func localEndpointsCPULoad(endpoints EndpointList, r *http.Request) ServerCPULoadInfo {
|
||||
var cpuLoads []cpu.Load
|
||||
var historicLoads []cpu.Load
|
||||
scratchSpace := map[string]bool{}
|
||||
@@ -327,8 +332,12 @@ func localEndpointsCPULoad(endpoints EndpointList) ServerCPULoadInfo {
|
||||
scratchSpace[endpoint.Host] = true
|
||||
}
|
||||
}
|
||||
addr := r.Host
|
||||
if globalIsDistXL {
|
||||
addr = GetLocalPeer(endpoints)
|
||||
}
|
||||
return ServerCPULoadInfo{
|
||||
Addr: GetLocalPeer(endpoints),
|
||||
Addr: addr,
|
||||
Load: cpuLoads,
|
||||
HistoricLoad: historicLoads,
|
||||
}
|
||||
@@ -336,7 +345,7 @@ func localEndpointsCPULoad(endpoints EndpointList) ServerCPULoadInfo {
|
||||
|
||||
// localEndpointsDrivePerf - returns ServerDrivesPerfInfo for only the
|
||||
// local endpoints from given list of endpoints
|
||||
func localEndpointsDrivePerf(endpoints EndpointList) ServerDrivesPerfInfo {
|
||||
func localEndpointsDrivePerf(endpoints EndpointList, r *http.Request) ServerDrivesPerfInfo {
|
||||
var dps []disk.Performance
|
||||
for _, endpoint := range endpoints {
|
||||
// Only proceed for local endpoints
|
||||
@@ -351,9 +360,12 @@ func localEndpointsDrivePerf(endpoints EndpointList) ServerDrivesPerfInfo {
|
||||
dps = append(dps, dp)
|
||||
}
|
||||
}
|
||||
|
||||
addr := r.Host
|
||||
if globalIsDistXL {
|
||||
addr = GetLocalPeer(endpoints)
|
||||
}
|
||||
return ServerDrivesPerfInfo{
|
||||
Addr: GetLocalPeer(endpoints),
|
||||
Addr: addr,
|
||||
Perf: dps,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ EXAMPLES:
|
||||
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: azureBackend,
|
||||
Usage: "Microsoft Azure Blob Storage.",
|
||||
Usage: "Microsoft Azure Blob Storage",
|
||||
Action: azureGatewayMain,
|
||||
CustomHelpTemplate: azureGatewayTemplate,
|
||||
HideHelpCommand: true,
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
b2 "github.com/minio/blazer/base"
|
||||
b2 "github.com/kurin/blazer/base"
|
||||
"github.com/minio/cli"
|
||||
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
@@ -147,6 +147,29 @@ func b2ToObjectError(err error, params ...string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
code, msgCode, msg := b2.MsgCode(err)
|
||||
if code == 0 {
|
||||
// We don't interpret non B2 errors. B2 errors have statusCode
|
||||
// to help us convert them to S3 object errors.
|
||||
return err
|
||||
}
|
||||
|
||||
objErr := b2MsgCodeToObjectError(code, msgCode, msg, params...)
|
||||
if objErr == nil {
|
||||
return err
|
||||
}
|
||||
return objErr
|
||||
}
|
||||
|
||||
func b2MsgCodeToObjectError(code int, msgCode string, msg string, params ...string) error {
|
||||
// Following code is a non-exhaustive check to convert
|
||||
// B2 errors into S3 compatible errors.
|
||||
//
|
||||
// For a more complete information - https://www.backblaze.com/b2/docs/
|
||||
|
||||
var err error
|
||||
|
||||
bucket := ""
|
||||
object := ""
|
||||
uploadID := ""
|
||||
@@ -160,18 +183,7 @@ func b2ToObjectError(err error, params ...string) error {
|
||||
uploadID = params[2]
|
||||
}
|
||||
|
||||
// Following code is a non-exhaustive check to convert
|
||||
// B2 errors into S3 compatible errors.
|
||||
//
|
||||
// For a more complete information - https://www.backblaze.com/b2/docs/
|
||||
statusCode, code, msg := b2.Code(err)
|
||||
if statusCode == 0 {
|
||||
// We don't interpret non B2 errors. B2 errors have statusCode
|
||||
// to help us convert them to S3 object errors.
|
||||
return err
|
||||
}
|
||||
|
||||
switch code {
|
||||
switch msgCode {
|
||||
case "duplicate_bucket_name":
|
||||
err = minio.BucketAlreadyOwnedByYou{Bucket: bucket}
|
||||
case "bad_request":
|
||||
@@ -343,7 +355,7 @@ func (l *b2Objects) ListObjects(ctx context.Context, bucket string, prefix strin
|
||||
Name: file.Name,
|
||||
ModTime: file.Timestamp,
|
||||
Size: file.Size,
|
||||
ETag: minio.ToS3ETag(file.Info.ID),
|
||||
ETag: minio.ToS3ETag(file.ID),
|
||||
ContentType: file.Info.ContentType,
|
||||
UserDefined: file.Info.Info,
|
||||
})
|
||||
@@ -386,7 +398,7 @@ func (l *b2Objects) ListObjectsV2(ctx context.Context, bucket, prefix, continuat
|
||||
Name: file.Name,
|
||||
ModTime: file.Timestamp,
|
||||
Size: file.Size,
|
||||
ETag: minio.ToS3ETag(file.Info.ID),
|
||||
ETag: minio.ToS3ETag(file.ID),
|
||||
ContentType: file.Info.ContentType,
|
||||
UserDefined: file.Info.Info,
|
||||
})
|
||||
@@ -462,7 +474,7 @@ func (l *b2Objects) GetObjectInfo(ctx context.Context, bucket string, object str
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
ETag: minio.ToS3ETag(fi.ID),
|
||||
ETag: minio.ToS3ETag(f.ID),
|
||||
Size: fi.Size,
|
||||
ModTime: fi.Timestamp,
|
||||
ContentType: fi.ContentType,
|
||||
@@ -569,7 +581,7 @@ func (l *b2Objects) PutObject(ctx context.Context, bucket string, object string,
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
ETag: minio.ToS3ETag(fi.ID),
|
||||
ETag: minio.ToS3ETag(f.ID),
|
||||
Size: fi.Size,
|
||||
ModTime: fi.Timestamp,
|
||||
ContentType: fi.ContentType,
|
||||
@@ -617,7 +629,7 @@ func (l *b2Objects) ListMultipartUploads(ctx context.Context, bucket string, pre
|
||||
if maxUploads > 100 {
|
||||
maxUploads = 100
|
||||
}
|
||||
largeFiles, nextMarker, err := bkt.ListUnfinishedLargeFiles(l.ctx, uploadIDMarker, maxUploads)
|
||||
largeFiles, nextMarker, err := bkt.ListUnfinishedLargeFiles(l.ctx, maxUploads, uploadIDMarker)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return lmi, b2ToObjectError(err, bucket)
|
||||
@@ -677,7 +689,7 @@ func (l *b2Objects) PutObjectPart(ctx context.Context, bucket string, object str
|
||||
}
|
||||
|
||||
hr := newB2Reader(data, data.Size())
|
||||
sha1, err := fc.UploadPart(l.ctx, hr, sha1AtEOF, int(hr.Size()), partID)
|
||||
_, err = fc.UploadPart(l.ctx, hr, sha1AtEOF, int(hr.Size()), partID)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return pi, b2ToObjectError(err, bucket, object, uploadID)
|
||||
@@ -686,7 +698,7 @@ func (l *b2Objects) PutObjectPart(ctx context.Context, bucket string, object str
|
||||
return minio.PartInfo{
|
||||
PartNumber: partID,
|
||||
LastModified: minio.UTCNow(),
|
||||
ETag: minio.ToS3ETag(sha1),
|
||||
ETag: minio.ToS3ETag(fmt.Sprintf("%x", hr.sha1Hash.Sum(nil))),
|
||||
Size: data.Size(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
b2 "github.com/minio/blazer/base"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
@@ -41,71 +39,6 @@ func TestB2ObjectError(t *testing.T) {
|
||||
{
|
||||
[]string{}, fmt.Errorf("Non B2 Error"), fmt.Errorf("Non B2 Error"),
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "duplicate_bucket_name",
|
||||
}, minio.BucketAlreadyOwnedByYou{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "bad_request",
|
||||
}, minio.BucketNotFound{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "bad_request",
|
||||
}, minio.ObjectNameInvalid{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "bad_bucket_id",
|
||||
}, minio.BucketNotFound{Bucket: "bucket"},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "file_not_present",
|
||||
}, minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "not_found",
|
||||
}, minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "cannot_delete_non_empty_bucket",
|
||||
}, minio.BucketNotEmpty{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object", "uploadID"}, b2.Error{
|
||||
StatusCode: 1,
|
||||
Message: "No active upload for",
|
||||
}, minio.InvalidUploadID{
|
||||
UploadID: "uploadID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
@@ -117,3 +50,97 @@ func TestB2ObjectError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test b2 msg code to object error.
|
||||
func TestB2MsgCodeToObjectError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
params []string
|
||||
code int
|
||||
msgCode string
|
||||
msg string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
[]string{"bucket"},
|
||||
1,
|
||||
"duplicate_bucket_name",
|
||||
"",
|
||||
minio.BucketAlreadyOwnedByYou{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"},
|
||||
1,
|
||||
"bad_request",
|
||||
"",
|
||||
minio.BucketNotFound{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
1,
|
||||
"bad_request",
|
||||
"",
|
||||
minio.ObjectNameInvalid{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"},
|
||||
1,
|
||||
"bad_bucket_id",
|
||||
"",
|
||||
minio.BucketNotFound{Bucket: "bucket"},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
1,
|
||||
"file_not_present",
|
||||
"",
|
||||
minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
1,
|
||||
"not_found",
|
||||
"",
|
||||
minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"},
|
||||
1,
|
||||
"cannot_delete_non_empty_bucket",
|
||||
"",
|
||||
minio.BucketNotEmpty{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object", "uploadID"},
|
||||
1,
|
||||
"",
|
||||
"No active upload for",
|
||||
minio.InvalidUploadID{
|
||||
UploadID: "uploadID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
actualErr := b2MsgCodeToObjectError(testCase.code, testCase.msgCode, testCase.msg, testCase.params...)
|
||||
if actualErr != nil {
|
||||
if actualErr.Error() != testCase.expectedErr.Error() {
|
||||
t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,37 +77,37 @@ ENVIRONMENT VARIABLES:
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for AWS S3 backend.
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}accesskey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}secretkey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}accesskey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}secretkey
|
||||
{{.Prompt}} {{.HelpName}}
|
||||
|
||||
2. Start minio gateway server for S3 backend on custom endpoint.
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
{{.Prompt}} {{.HelpName}} https://play.min.io:9000
|
||||
|
||||
3. Start minio gateway server for AWS S3 backend logging all requests to http endpoint.
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}"http://localhost:8000/"
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}Q3AM3UQ867SPQQA43P2F
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_LOGGER_HTTP_ENDPOINT{{.AssignmentOperator}}"http://localhost:8000/"
|
||||
{{.Prompt}} {{.HelpName}} https://play.min.io:9000
|
||||
|
||||
4. Start minio gateway server for AWS S3 backend with edge caching enabled.
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}accesskey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}secretkey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}"/mnt/drive1;/mnt/drive2;/mnt/drive3;/mnt/drive4"
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}"bucket1/*;*.png"
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}40
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}80
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}accesskey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}secretkey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_DRIVES{{.AssignmentOperator}}"/mnt/drive1;/mnt/drive2;/mnt/drive3;/mnt/drive4"
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_EXCLUDE{{.AssignmentOperator}}"bucket1/*;*.png"
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_EXPIRY{{.AssignmentOperator}}40
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_MAXUSE{{.AssignmentOperator}}80
|
||||
{{.Prompt}} {{.HelpName}}
|
||||
|
||||
4. Start minio gateway server for AWS S3 backend using AWS environment variables.
|
||||
NOTE: The access and secret key in this case will authenticate with MinIO instead
|
||||
of AWS and AWS envs will be used to authenticate to AWS S3.
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}aws_access_key
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}aws_secret_key
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}accesskey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}}(.*){{.AssignmentOperator}}secretkey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} AWS_ACCESS_KEY_ID{{.AssignmentOperator}}aws_access_key
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} AWS_SECRET_ACCESS_KEY{{.AssignmentOperator}}aws_secret_key
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ACCESS_KEY{{.AssignmentOperator}}accesskey
|
||||
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_SECRET_KEY{{.AssignmentOperator}}secretkey
|
||||
{{.Prompt}} {{.HelpName}}
|
||||
`
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
)
|
||||
|
||||
// Parses location constraint from the incoming reader.
|
||||
@@ -384,3 +385,17 @@ func notFoundHandlerJSON(w http.ResponseWriter, r *http.Request) {
|
||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
||||
}
|
||||
|
||||
// gets host name for current node
|
||||
func getHostName(r *http.Request) (hostName string, err error) {
|
||||
var thisAddr *xnet.Host
|
||||
hostName = r.Host
|
||||
if globalIsDistXL {
|
||||
thisAddr, err = xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hostName = thisAddr.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if s.Backend.Type == Unknown {
|
||||
// ListBuckets to confirm gateway backend is up
|
||||
if _, err := objLayer.ListBuckets(ctx); err != nil {
|
||||
logger.LogOnceIf(ctx, err, struct{}{})
|
||||
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
|
||||
return
|
||||
}
|
||||
|
||||
+46
-33
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -28,10 +29,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
trace "github.com/minio/minio/pkg/trace"
|
||||
)
|
||||
|
||||
var traceBodyPlaceHolder = []byte("<BODY>")
|
||||
|
||||
// recordRequest - records the first recLen bytes
|
||||
// of a given io.Reader
|
||||
type recordRequest struct {
|
||||
@@ -56,7 +58,12 @@ func (r *recordRequest) Read(p []byte) (n int, err error) {
|
||||
|
||||
// Return the bytes that were recorded.
|
||||
func (r *recordRequest) Data() []byte {
|
||||
return r.buf.Bytes()
|
||||
// If body logging is enabled then we return the actual body
|
||||
if r.logBody {
|
||||
return r.buf.Bytes()
|
||||
}
|
||||
// ... otherwise we return <BODY> placeholder
|
||||
return traceBodyPlaceHolder
|
||||
}
|
||||
|
||||
// recordResponseWriter - records the first recLen bytes
|
||||
@@ -115,7 +122,13 @@ func (r *recordResponseWriter) Flush() {
|
||||
|
||||
// Return response body.
|
||||
func (r *recordResponseWriter) Body() []byte {
|
||||
return r.body.Bytes()
|
||||
// If there was an error response or body logging is enabled
|
||||
// then we return the body contents
|
||||
if r.statusCode >= 400 || r.logBody {
|
||||
return r.body.Bytes()
|
||||
}
|
||||
// ... otherwise we return the <BODY> place holder
|
||||
return traceBodyPlaceHolder
|
||||
}
|
||||
|
||||
// getOpName sanitizes the operation name for mc
|
||||
@@ -136,54 +149,54 @@ func getOpName(name string) (op string) {
|
||||
|
||||
// Trace gets trace of http request
|
||||
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info {
|
||||
|
||||
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
|
||||
|
||||
bodyPlaceHolder := []byte("<BODY>")
|
||||
var reqBodyRecorder *recordRequest
|
||||
|
||||
t := trace.Info{FuncName: name}
|
||||
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody}
|
||||
r.Body = ioutil.NopCloser(reqBodyRecorder)
|
||||
|
||||
host, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
|
||||
if err == nil {
|
||||
t.NodeName = host.Name
|
||||
t.NodeName = r.Host
|
||||
if globalIsDistXL {
|
||||
t.NodeName = GetLocalPeer(globalEndpoints)
|
||||
}
|
||||
rq := trace.RequestInfo{Time: time.Now().UTC(), Method: r.Method, Path: r.URL.Path, RawQuery: r.URL.RawQuery, Client: r.RemoteAddr}
|
||||
rq.Headers = cloneHeader(r.Header)
|
||||
rq.Headers.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
|
||||
rq.Headers.Set("Host", r.Host)
|
||||
// strip port from the host address
|
||||
if host, _, err := net.SplitHostPort(t.NodeName); err == nil {
|
||||
t.NodeName = host
|
||||
}
|
||||
|
||||
// Setup a http request body recorder
|
||||
reqHeaders := cloneHeader(r.Header)
|
||||
reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
|
||||
reqHeaders.Set("Host", r.Host)
|
||||
for _, enc := range r.TransferEncoding {
|
||||
rq.Headers.Add("Transfer-Encoding", enc)
|
||||
reqHeaders.Add("Transfer-Encoding", enc)
|
||||
}
|
||||
if logBody {
|
||||
// If body logging is disabled then we print <BODY> as a placeholder
|
||||
// for the actual body.
|
||||
rq.Body = reqBodyRecorder.Data()
|
||||
|
||||
} else {
|
||||
rq.Body = bodyPlaceHolder
|
||||
rq := trace.RequestInfo{
|
||||
Time: time.Now().UTC(),
|
||||
Method: r.Method,
|
||||
Path: r.URL.Path,
|
||||
RawQuery: r.URL.RawQuery,
|
||||
Client: r.RemoteAddr,
|
||||
Headers: reqHeaders,
|
||||
Body: reqBodyRecorder.Data(),
|
||||
}
|
||||
|
||||
// Setup a http response body recorder
|
||||
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody}
|
||||
f(respBodyRecorder, r)
|
||||
|
||||
rs := trace.ResponseInfo{Time: time.Now().UTC()}
|
||||
rs.Headers = cloneHeader(respBodyRecorder.Header())
|
||||
rs.StatusCode = respBodyRecorder.statusCode
|
||||
rs := trace.ResponseInfo{
|
||||
Time: time.Now().UTC(),
|
||||
Headers: cloneHeader(respBodyRecorder.Header()),
|
||||
StatusCode: respBodyRecorder.statusCode,
|
||||
Body: respBodyRecorder.Body(),
|
||||
}
|
||||
|
||||
if rs.StatusCode == 0 {
|
||||
rs.StatusCode = http.StatusOK
|
||||
}
|
||||
bodyContents := respBodyRecorder.Body()
|
||||
if bodyContents != nil {
|
||||
rs.Body = bodyContents
|
||||
}
|
||||
if !logBody {
|
||||
// If there was no error response and body logging is disabled
|
||||
// then we print <BODY> as a placeholder for the actual body.
|
||||
rs.Body = bodyPlaceHolder
|
||||
}
|
||||
|
||||
t.ReqInfo = rq
|
||||
t.RespInfo = rs
|
||||
return t
|
||||
|
||||
@@ -195,3 +195,10 @@ func (d *naughtyDisk) ReadAll(volume string, path string) (buf []byte, err error
|
||||
}
|
||||
return d.disk.ReadAll(volume, path)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) VerifyFile(volume, path string, empty bool, algo BitrotAlgorithm, sum []byte, shardSize int64) error {
|
||||
if err := d.calcError(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.disk.VerifyFile(volume, path, empty, algo, sum, shardSize)
|
||||
}
|
||||
|
||||
@@ -269,11 +269,27 @@ func (e BucketNameInvalid) Error() string {
|
||||
// ObjectNameInvalid - object name provided is invalid.
|
||||
type ObjectNameInvalid GenericError
|
||||
|
||||
// ObjectNameTooLong - object name too long.
|
||||
type ObjectNameTooLong GenericError
|
||||
|
||||
// ObjectNamePrefixAsSlash - object name has a slash as prefix.
|
||||
type ObjectNamePrefixAsSlash GenericError
|
||||
|
||||
// Return string an error formatted as the given text.
|
||||
func (e ObjectNameInvalid) Error() string {
|
||||
return "Object name invalid: " + e.Bucket + "#" + e.Object
|
||||
}
|
||||
|
||||
// Return string an error formatted as the given text.
|
||||
func (e ObjectNameTooLong) Error() string {
|
||||
return "Object name too long: " + e.Bucket + "#" + e.Object
|
||||
}
|
||||
|
||||
// Return string an error formatted as the given text.
|
||||
func (e ObjectNamePrefixAsSlash) Error() string {
|
||||
return "Object name contains forward slash as pefix: " + e.Bucket + "#" + e.Object
|
||||
}
|
||||
|
||||
// AllAccessDisabled All access to this object has been disabled
|
||||
type AllAccessDisabled GenericError
|
||||
|
||||
|
||||
@@ -166,18 +166,18 @@ func checkObjectArgs(ctx context.Context, bucket, object string, obj ObjectLayer
|
||||
if err := checkBucketExist(ctx, bucket, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkObjectNameForLengthAndSlash(bucket, object); err != nil {
|
||||
return err
|
||||
}
|
||||
// Validates object name validity after bucket exists.
|
||||
if !IsValidObjectName(object) {
|
||||
logger.LogIf(ctx, ObjectNameInvalid{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
})
|
||||
|
||||
return ObjectNameInvalid{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -192,8 +192,10 @@ func checkPutObjectArgs(ctx context.Context, bucket, object string, obj ObjectLa
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkObjectNameForLengthAndSlash(bucket, object); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(object) == 0 ||
|
||||
hasPrefix(object, slashSeparator) ||
|
||||
(hasSuffix(object, slashSeparator) && size != 0) ||
|
||||
!IsValidObjectPrefix(object) {
|
||||
return ObjectNameInvalid{
|
||||
|
||||
+20
-4
@@ -136,7 +136,7 @@ func IsValidObjectName(object string) bool {
|
||||
if len(object) == 0 {
|
||||
return false
|
||||
}
|
||||
if hasSuffix(object, slashSeparator) || hasPrefix(object, slashSeparator) {
|
||||
if hasSuffix(object, slashSeparator) {
|
||||
return false
|
||||
}
|
||||
return IsValidObjectPrefix(object)
|
||||
@@ -148,9 +148,6 @@ func IsValidObjectPrefix(object string) bool {
|
||||
if hasBadPathComponent(object) {
|
||||
return false
|
||||
}
|
||||
if len(object) > 1024 {
|
||||
return false
|
||||
}
|
||||
if !utf8.ValidString(object) {
|
||||
return false
|
||||
}
|
||||
@@ -161,6 +158,25 @@ func IsValidObjectPrefix(object string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// checkObjectNameForLengthAndSlash -check for the validity of object name length and prefis as slash
|
||||
func checkObjectNameForLengthAndSlash(bucket, object string) error {
|
||||
// Check for the length of object name
|
||||
if len(object) > 1024 {
|
||||
return ObjectNameTooLong{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
}
|
||||
// Check for slash as prefix in object name
|
||||
if hasPrefix(object, slashSeparator) {
|
||||
return ObjectNamePrefixAsSlash{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Slash separator.
|
||||
const slashSeparator = "/"
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ func TestIsValidObjectName(t *testing.T) {
|
||||
// passing invalid object names.
|
||||
{"", false},
|
||||
{"a/b/c/", false},
|
||||
{"/a/b/c", false},
|
||||
{"../../etc", false},
|
||||
{"../../", false},
|
||||
{"/../../etc", false},
|
||||
|
||||
@@ -2280,6 +2280,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
w.Write(encodedErrorResponse)
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
w = &whiteSpaceWriter{ResponseWriter: w, Flusher: w.(http.Flusher)}
|
||||
completeDoneCh := sendWhiteSpace(ctx, w)
|
||||
objInfo, err := completeMultiPartUpload(ctx, bucket, object, uploadID, completeParts, opts)
|
||||
|
||||
@@ -332,7 +332,7 @@ func (s *peerRESTServer) CPULoadInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
ctx := newContext(r, w, "CPULoadInfo")
|
||||
info := localEndpointsCPULoad(globalEndpoints)
|
||||
info := localEndpointsCPULoad(globalEndpoints, r)
|
||||
|
||||
defer w.(http.Flusher).Flush()
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
|
||||
@@ -346,7 +346,7 @@ func (s *peerRESTServer) DrivePerfInfoHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
ctx := newContext(r, w, "DrivePerfInfo")
|
||||
info := localEndpointsDrivePerf(globalEndpoints)
|
||||
info := localEndpointsDrivePerf(globalEndpoints, r)
|
||||
|
||||
defer w.(http.Flusher).Flush()
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
|
||||
@@ -359,7 +359,7 @@ func (s *peerRESTServer) MemUsageInfoHandler(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
ctx := newContext(r, w, "MemUsageInfo")
|
||||
info := localEndpointsMemUsage(globalEndpoints)
|
||||
info := localEndpointsMemUsage(globalEndpoints, r)
|
||||
|
||||
defer w.(http.Flusher).Flush()
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
|
||||
|
||||
+107
-1
@@ -958,7 +958,7 @@ func (s *posix) ReadFile(volume, path string, offset int64, buffer []byte, verif
|
||||
}
|
||||
|
||||
if !bytes.Equal(h.Sum(nil), verifier.sum) {
|
||||
return 0, hashMismatchError{hex.EncodeToString(verifier.sum), hex.EncodeToString(h.Sum(nil))}
|
||||
return 0, HashMismatchError{hex.EncodeToString(verifier.sum), hex.EncodeToString(h.Sum(nil))}
|
||||
}
|
||||
|
||||
return int64(len(buffer)), nil
|
||||
@@ -1525,3 +1525,109 @@ func (s *posix) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) (err e
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *posix) VerifyFile(volume, path string, empty bool, algo BitrotAlgorithm, sum []byte, shardSize int64) (err error) {
|
||||
defer func() {
|
||||
if err == errFaultyDisk {
|
||||
atomic.AddInt32(&s.ioErrCount, 1)
|
||||
}
|
||||
}()
|
||||
|
||||
if atomic.LoadInt32(&s.ioErrCount) > maxAllowedIOError {
|
||||
return errFaultyDisk
|
||||
}
|
||||
|
||||
if err = s.checkDiskFound(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Stat a volume entry.
|
||||
_, err = os.Stat(volumeDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errVolumeNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate effective path length before reading.
|
||||
filePath := pathJoin(volumeDir, path)
|
||||
if err = checkPathLength(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open the file for reading.
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return errFileNotFound
|
||||
case os.IsPermission(err):
|
||||
return errFileAccessDenied
|
||||
case isSysErrNotDir(err):
|
||||
return errFileAccessDenied
|
||||
case isSysErrIO(err):
|
||||
return errFaultyDisk
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Close the file descriptor.
|
||||
defer file.Close()
|
||||
|
||||
if algo != HighwayHash256S {
|
||||
bufp := s.pool.Get().(*[]byte)
|
||||
defer s.pool.Put(bufp)
|
||||
|
||||
h := algo.New()
|
||||
if _, err = io.CopyBuffer(h, file, *bufp); err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(h.Sum(nil), sum) {
|
||||
return HashMismatchError{hex.EncodeToString(sum), hex.EncodeToString(h.Sum(nil))}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, shardSize)
|
||||
h := algo.New()
|
||||
hashBuf := make([]byte, h.Size())
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if empty && fi.Size() != 0 || !empty && fi.Size() == 0 {
|
||||
return errFileUnexpectedSize
|
||||
}
|
||||
|
||||
size := fi.Size()
|
||||
for {
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
h.Reset()
|
||||
n, err := file.Read(hashBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size -= int64(n)
|
||||
if size < int64(len(buf)) {
|
||||
buf = buf[:size]
|
||||
}
|
||||
n, err = file.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size -= int64(n)
|
||||
h.Write(buf)
|
||||
if !bytes.Equal(h.Sum(nil), hashBuf) {
|
||||
return HashMismatchError{hex.EncodeToString(hashBuf), hex.EncodeToString(h.Sum(nil))}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+96
-5
@@ -1251,13 +1251,13 @@ var posixReadFileWithVerifyTests = []struct {
|
||||
{file: "myobject", offset: 25, length: 74, algorithm: SHA256, expError: nil}, // 1
|
||||
{file: "myobject", offset: 29, length: 70, algorithm: SHA256, expError: nil}, // 2
|
||||
{file: "myobject", offset: 100, length: 0, algorithm: SHA256, expError: nil}, // 3
|
||||
{file: "myobject", offset: 1, length: 120, algorithm: SHA256, expError: hashMismatchError{}}, // 4
|
||||
{file: "myobject", offset: 1, length: 120, algorithm: SHA256, expError: HashMismatchError{}}, // 4
|
||||
{file: "myobject", offset: 3, length: 1100, algorithm: SHA256, expError: nil}, // 5
|
||||
{file: "myobject", offset: 2, length: 100, algorithm: SHA256, expError: hashMismatchError{}}, // 6
|
||||
{file: "myobject", offset: 2, length: 100, algorithm: SHA256, expError: HashMismatchError{}}, // 6
|
||||
{file: "myobject", offset: 1000, length: 1001, algorithm: SHA256, expError: nil}, // 7
|
||||
{file: "myobject", offset: 0, length: 100, algorithm: BLAKE2b512, expError: hashMismatchError{}}, // 8
|
||||
{file: "myobject", offset: 0, length: 100, algorithm: BLAKE2b512, expError: HashMismatchError{}}, // 8
|
||||
{file: "myobject", offset: 25, length: 74, algorithm: BLAKE2b512, expError: nil}, // 9
|
||||
{file: "myobject", offset: 29, length: 70, algorithm: BLAKE2b512, expError: hashMismatchError{}}, // 10
|
||||
{file: "myobject", offset: 29, length: 70, algorithm: BLAKE2b512, expError: HashMismatchError{}}, // 10
|
||||
{file: "myobject", offset: 100, length: 0, algorithm: BLAKE2b512, expError: nil}, // 11
|
||||
{file: "myobject", offset: 1, length: 120, algorithm: BLAKE2b512, expError: nil}, // 12
|
||||
{file: "myobject", offset: 3, length: 1100, algorithm: BLAKE2b512, expError: nil}, // 13
|
||||
@@ -1296,7 +1296,7 @@ func TestPosixReadFileWithVerify(t *testing.T) {
|
||||
if test.expError != nil {
|
||||
expected := h.Sum(nil)
|
||||
h.Write([]byte{0})
|
||||
test.expError = hashMismatchError{hex.EncodeToString(h.Sum(nil)), hex.EncodeToString(expected)}
|
||||
test.expError = HashMismatchError{hex.EncodeToString(h.Sum(nil)), hex.EncodeToString(expected)}
|
||||
}
|
||||
|
||||
buffer := make([]byte, test.length)
|
||||
@@ -1763,6 +1763,97 @@ func TestPosixStatFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test posix.VerifyFile()
|
||||
func TestPosixVerifyFile(t *testing.T) {
|
||||
// We test 4 cases:
|
||||
// 1) Whole-file bitrot check on proper file
|
||||
// 2) Whole-file bitrot check on corrupted file
|
||||
// 3) Streaming bitrot check on proper file
|
||||
// 4) Streaming bitrot check on corrupted file
|
||||
|
||||
// create posix test setup
|
||||
posixStorage, path, err := newPosixTestSetup()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create posix test setup, %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
volName := "testvol"
|
||||
fileName := "testfile"
|
||||
if err := posixStorage.MakeVol(volName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 1) Whole-file bitrot check on proper file
|
||||
size := int64(4*1024*1024 + 100*1024) // 4.1 MB
|
||||
data := make([]byte, size)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
algo := HighwayHash256
|
||||
h := algo.New()
|
||||
h.Write(data)
|
||||
hashBytes := h.Sum(nil)
|
||||
if err := posixStorage.WriteAll(volName, fileName, bytes.NewBuffer(data)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := posixStorage.VerifyFile(volName, fileName, false, algo, hashBytes, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 2) Whole-file bitrot check on corrupted file
|
||||
if err := posixStorage.AppendFile(volName, fileName, []byte("a")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := posixStorage.VerifyFile(volName, fileName, false, algo, hashBytes, 0); err == nil {
|
||||
t.Fatal("expected to fail bitrot check")
|
||||
}
|
||||
|
||||
if err := posixStorage.DeleteFile(volName, fileName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 3) Streaming bitrot check on proper file
|
||||
algo = HighwayHash256S
|
||||
shardSize := int64(1024 * 1024)
|
||||
shard := make([]byte, shardSize)
|
||||
w := newStreamingBitrotWriter(posixStorage, volName, fileName, size, algo, shardSize)
|
||||
reader := bytes.NewReader(data)
|
||||
for {
|
||||
// Using io.CopyBuffer instead of this loop will not work for us as io.CopyBuffer
|
||||
// will use bytes.Buffer.ReadFrom() which will not do shardSize'ed writes causing error.
|
||||
n, err := reader.Read(shard)
|
||||
w.Write(shard[:n])
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
w.Close()
|
||||
if err := posixStorage.VerifyFile(volName, fileName, false, algo, nil, shardSize); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 4) Streaming bitrot check on corrupted file
|
||||
filePath := pathJoin(posixStorage.String(), volName, fileName)
|
||||
f, err := os.OpenFile(filePath, os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := f.WriteString("a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
if err := posixStorage.VerifyFile(volName, fileName, false, algo, nil, shardSize); err == nil {
|
||||
t.Fatal("expected to fail bitrot check")
|
||||
}
|
||||
}
|
||||
|
||||
// Checks for restrictions for min total disk space and inodes.
|
||||
func TestCheckDiskTotalMin(t *testing.T) {
|
||||
testCases := []struct {
|
||||
|
||||
@@ -109,7 +109,7 @@ type PostPolicyForm struct {
|
||||
}
|
||||
}
|
||||
|
||||
// parsePostPolicyForm - Parse JSON policy string into typed POostPolicyForm structure.
|
||||
// parsePostPolicyForm - Parse JSON policy string into typed PostPolicyForm structure.
|
||||
func parsePostPolicyForm(policy string) (ppf PostPolicyForm, e error) {
|
||||
// Convert po into interfaces and
|
||||
// perform strict type conversion using reflection.
|
||||
@@ -256,6 +256,7 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro
|
||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
||||
}
|
||||
// Check if current policy condition is satisfied
|
||||
condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value)
|
||||
if !condPassed {
|
||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -36,6 +37,7 @@ import (
|
||||
func init() {
|
||||
logger.Init(GOPATH, GOROOT)
|
||||
logger.RegisterUIError(fmtError)
|
||||
gob.Register(HashMismatchError{})
|
||||
}
|
||||
|
||||
// ServerFlags - server command specific flags
|
||||
|
||||
+31
-1
@@ -1354,7 +1354,37 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) {
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, nil)
|
||||
c.Assert(response.StatusCode, http.StatusOK)
|
||||
// make long object name.
|
||||
|
||||
//make long object name.
|
||||
longObjName = fmt.Sprintf("%0255d/%0255d/%0255d/%0255d/%0255d", 1, 1, 1, 1, 1)
|
||||
if IsDocker() || IsKubernetes() {
|
||||
longObjName = fmt.Sprintf("%0242d/%0242d/%0242d/%0242d/%0242d", 1, 1, 1, 1, 1)
|
||||
}
|
||||
// create new HTTP request to insert the object.
|
||||
buffer = bytes.NewReader([]byte("hello world"))
|
||||
request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName),
|
||||
int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer)
|
||||
c.Assert(err, nil)
|
||||
// execute the HTTP request.
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, nil)
|
||||
c.Assert(response.StatusCode, http.StatusBadRequest)
|
||||
verifyError(c, response, "KeyTooLongError", "Your key is too long", http.StatusBadRequest)
|
||||
|
||||
// make object name with prefix as slash
|
||||
longObjName = fmt.Sprintf("/%0255d/%0255d", 1, 1)
|
||||
buffer = bytes.NewReader([]byte("hello world"))
|
||||
// create new HTTP request to insert the object.
|
||||
request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName),
|
||||
int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer)
|
||||
c.Assert(err, nil)
|
||||
// execute the HTTP request.
|
||||
response, err = client.Do(request)
|
||||
c.Assert(err, nil)
|
||||
c.Assert(response.StatusCode, http.StatusBadRequest)
|
||||
verifyError(c, response, "XMinioInvalidObjectName", "Object name contains a leading slash.", http.StatusBadRequest)
|
||||
|
||||
//make object name as unsuported
|
||||
longObjName = fmt.Sprintf("%0256d", 1)
|
||||
buffer = bytes.NewReader([]byte("hello world"))
|
||||
request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName),
|
||||
|
||||
+10
-8
@@ -72,6 +72,9 @@ var errVolumeAccessDenied = errors.New("volume access denied")
|
||||
// errFileAccessDenied - cannot access file, insufficient permissions.
|
||||
var errFileAccessDenied = errors.New("file access denied")
|
||||
|
||||
// errFileUnexpectedSize - file has an unexpected size
|
||||
var errFileUnexpectedSize = errors.New("file has unexpected size")
|
||||
|
||||
// errFileParentIsFile - cannot have overlapping objects, parent is already a file.
|
||||
var errFileParentIsFile = errors.New("parent is a file")
|
||||
|
||||
@@ -91,18 +94,17 @@ var errLessData = errors.New("less data available than what was requested")
|
||||
// errMoreData = returned when more data was sent by the caller than what it was supposed to.
|
||||
var errMoreData = errors.New("more data was sent than what was advertised")
|
||||
|
||||
// hashMisMatchError - represents a bit-rot hash verification failure
|
||||
// error.
|
||||
type hashMismatchError struct {
|
||||
expected string
|
||||
computed string
|
||||
// HashMismatchError represents a bit-rot hash verification failure error.
|
||||
type HashMismatchError struct {
|
||||
Expected string
|
||||
Computed string
|
||||
}
|
||||
|
||||
// error method for the hashMismatchError
|
||||
func (h hashMismatchError) Error() string {
|
||||
// Error method for the hashMismatchError
|
||||
func (h HashMismatchError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Bitrot verification mismatch - expected %v, received %v",
|
||||
h.expected, h.computed)
|
||||
h.Expected, h.Computed)
|
||||
}
|
||||
|
||||
// Collection of basic errors.
|
||||
|
||||
@@ -52,6 +52,7 @@ type StorageAPI interface {
|
||||
StatFile(volume string, path string) (file FileInfo, err error)
|
||||
DeleteFile(volume string, path string) (err error)
|
||||
DeleteFileBulk(volume string, paths []string) (errs []error, err error)
|
||||
VerifyFile(volume, path string, empty bool, algo BitrotAlgorithm, sum []byte, shardSize int64) error
|
||||
|
||||
// Write all data, syncs the data to disk.
|
||||
WriteAll(volume string, path string, reader io.Reader) (err error)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
@@ -66,6 +67,8 @@ func toStorageErr(err error) error {
|
||||
return io.EOF
|
||||
case io.ErrUnexpectedEOF.Error():
|
||||
return io.ErrUnexpectedEOF
|
||||
case errFileUnexpectedSize.Error():
|
||||
return errFileUnexpectedSize
|
||||
case errUnexpected.Error():
|
||||
return errUnexpected
|
||||
case errDiskFull.Error():
|
||||
@@ -105,7 +108,7 @@ func toStorageErr(err error) error {
|
||||
fmt.Sscanf(err.Error(), "Bitrot verification mismatch - expected %s received %s", &expected, &received)
|
||||
// Go's Sscanf %s scans "," that comes after the expected hash, hence remove it. Providing "," in the format string does not help.
|
||||
expected = strings.TrimSuffix(expected, ",")
|
||||
bitrotErr := hashMismatchError{expected, received}
|
||||
bitrotErr := HashMismatchError{expected, received}
|
||||
return bitrotErr
|
||||
}
|
||||
return err
|
||||
@@ -433,6 +436,40 @@ func (client *storageRESTClient) getInstanceID() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *storageRESTClient) VerifyFile(volume, path string, empty bool, algo BitrotAlgorithm, sum []byte, shardSize int64) error {
|
||||
values := make(url.Values)
|
||||
values.Set(storageRESTVolume, volume)
|
||||
values.Set(storageRESTFilePath, path)
|
||||
values.Set(storageRESTBitrotAlgo, algo.String())
|
||||
values.Set(storageRESTEmpty, strconv.FormatBool(empty))
|
||||
values.Set(storageRESTLength, strconv.Itoa(int(shardSize)))
|
||||
if len(sum) != 0 {
|
||||
values.Set(storageRESTBitrotHash, hex.EncodeToString(sum))
|
||||
}
|
||||
respBody, err := client.call(storageRESTMethodVerifyFile, values, nil, -1)
|
||||
defer http.DrainBody(respBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader := bufio.NewReader(respBody)
|
||||
for {
|
||||
b, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if b != ' ' {
|
||||
reader.UnreadByte()
|
||||
break
|
||||
}
|
||||
}
|
||||
verifyResp := &VerifyFileResp{}
|
||||
err = gob.NewDecoder(reader).Decode(verifyResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return toStorageErr(verifyResp.Err)
|
||||
}
|
||||
|
||||
// Close - marks the client as closed.
|
||||
func (client *storageRESTClient) Close() error {
|
||||
client.connected = false
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package cmd
|
||||
|
||||
const storageRESTVersion = "v6"
|
||||
const storageRESTVersion = "v8"
|
||||
const storageRESTPath = minioReservedBucketPath + "/storage/" + storageRESTVersion + "/"
|
||||
|
||||
const (
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
storageRESTMethodDeleteFile = "deletefile"
|
||||
storageRESTMethodDeleteFileBulk = "deletefilebulk"
|
||||
storageRESTMethodRenameFile = "renamefile"
|
||||
storageRESTMethodVerifyFile = "verifyfile"
|
||||
storageRESTMethodGetInstanceID = "getinstanceid"
|
||||
)
|
||||
|
||||
@@ -51,6 +52,7 @@ const (
|
||||
storageRESTDstPath = "destination-path"
|
||||
storageRESTOffset = "offset"
|
||||
storageRESTLength = "length"
|
||||
storageRESTEmpty = "empty"
|
||||
storageRESTCount = "count"
|
||||
storageRESTMarkerPath = "marker"
|
||||
storageRESTLeafFile = "leaf-file"
|
||||
|
||||
@@ -487,6 +487,71 @@ func (s *storageRESTServer) RenameFileHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
// Send whitespace to the client to avoid timeouts as bitrot verification can take time on spinning/slow disks.
|
||||
func sendWhiteSpaceVerifyFile(w http.ResponseWriter) <-chan struct{} {
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second * 10)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
w.Write([]byte(" "))
|
||||
w.(http.Flusher).Flush()
|
||||
case doneCh <- struct{}{}:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
return doneCh
|
||||
}
|
||||
|
||||
// VerifyFileResp - VerifyFile()'s response.
|
||||
type VerifyFileResp struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// VerifyFile - Verify the file for bitrot errors.
|
||||
func (s *storageRESTServer) VerifyFile(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
volume := vars[storageRESTVolume]
|
||||
filePath := vars[storageRESTFilePath]
|
||||
empty, err := strconv.ParseBool(vars[storageRESTEmpty])
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
shardSize, err := strconv.Atoi(vars[storageRESTLength])
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
hashStr := vars[storageRESTBitrotHash]
|
||||
var hash []byte
|
||||
if hashStr != "" {
|
||||
hash, err = hex.DecodeString(hashStr)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
algoStr := vars[storageRESTBitrotAlgo]
|
||||
if algoStr == "" {
|
||||
s.writeErrorResponse(w, errInvalidArgument)
|
||||
return
|
||||
}
|
||||
algo := BitrotAlgorithmFromString(algoStr)
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
doneCh := sendWhiteSpaceVerifyFile(w)
|
||||
err = s.storage.VerifyFile(volume, filePath, empty, algo, hash, int64(shardSize))
|
||||
<-doneCh
|
||||
gob.NewEncoder(w).Encode(VerifyFileResp{err})
|
||||
}
|
||||
|
||||
// registerStorageRPCRouter - register storage rpc router.
|
||||
func registerStorageRESTHandlers(router *mux.Router, endpoints EndpointList) {
|
||||
for _, endpoint := range endpoints {
|
||||
@@ -534,6 +599,8 @@ func registerStorageRESTHandlers(router *mux.Router, endpoints EndpointList) {
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodRenameFile).HandlerFunc(httpTraceHdrs(server.RenameFileHandler)).
|
||||
Queries(restQueries(storageRESTSrcVolume, storageRESTSrcPath, storageRESTDstVolume, storageRESTDstPath)...)
|
||||
subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodVerifyFile).HandlerFunc(httpTraceHdrs(server.VerifyFile)).
|
||||
Queries(restQueries(storageRESTVolume, storageRESTFilePath, storageRESTBitrotAlgo, storageRESTLength)...)
|
||||
subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodGetInstanceID).HandlerFunc(httpTraceAll(server.GetInstanceID))
|
||||
}
|
||||
|
||||
|
||||
+18
-3
@@ -30,12 +30,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/inconshreveable/go-update"
|
||||
"github.com/minio/cli"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
_ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier.
|
||||
"github.com/segmentio/go-prompt"
|
||||
)
|
||||
|
||||
// Check for new software updates.
|
||||
@@ -452,7 +452,7 @@ func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256
|
||||
|
||||
func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateStatusMsg string, err error) {
|
||||
if !ok {
|
||||
updateStatusMsg = colorGreenBold("MinIO update to version RELEASE.%s canceled.",
|
||||
updateStatusMsg = colorRedBold("MinIO update to version RELEASE.%s canceled.",
|
||||
latestReleaseTime.Format(minioReleaseTagTimeLayout))
|
||||
return updateStatusMsg, nil
|
||||
}
|
||||
@@ -482,10 +482,25 @@ func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateSta
|
||||
latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil
|
||||
}
|
||||
|
||||
// Confirm continues prompting until the input is boolean-ish.
|
||||
func confirm(prompt string, args ...interface{}) bool {
|
||||
for {
|
||||
var s string
|
||||
fmt.Fprintf(color.Output, prompt+": ", args...)
|
||||
fmt.Scanln(&s)
|
||||
switch s {
|
||||
case "Yes", "yes", "y", "Y":
|
||||
return true
|
||||
case "No", "no", "n", "N":
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shouldUpdate(quiet bool, sha256Hex string, latestReleaseTime time.Time) (ok bool) {
|
||||
ok = true
|
||||
if !quiet {
|
||||
ok = prompt.Confirm(colorGreenBold("Update to RELEASE.%s [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "yes"))
|
||||
ok = confirm(colorGreenBold("Update to RELEASE.%s ? [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "y/n"))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
)
|
||||
|
||||
const (
|
||||
kmBucket = "BucketName"
|
||||
kmObject = "ObjectName"
|
||||
kmObjects = "Objects"
|
||||
kmPrefix = "Prefix"
|
||||
kmMarker = "Marker"
|
||||
kmUsername = "UserName"
|
||||
kmHostname = "HostName"
|
||||
kmPolicy = "Policy"
|
||||
)
|
||||
|
||||
// KeyValueMap extends builtin map to support setting and getting
|
||||
// select fields like BucketName, ObjectName, Prefix, etc.
|
||||
type KeyValueMap map[string]string
|
||||
|
||||
// Bucket returns the BucketName
|
||||
func (km KeyValueMap) Bucket() string {
|
||||
return km[kmBucket]
|
||||
}
|
||||
|
||||
// Object returns the ObjectName
|
||||
func (km KeyValueMap) Object() string {
|
||||
return km[kmObject]
|
||||
}
|
||||
|
||||
// Prefix returns the Prefix
|
||||
func (km KeyValueMap) Prefix() string {
|
||||
return km[kmPrefix]
|
||||
}
|
||||
|
||||
// Username returns the Username
|
||||
func (km KeyValueMap) Username() string {
|
||||
return km[kmUsername]
|
||||
}
|
||||
|
||||
// Hostname returns the Hostname
|
||||
func (km KeyValueMap) Hostname() string {
|
||||
return km[kmHostname]
|
||||
}
|
||||
|
||||
// Policy returns the Policy
|
||||
func (km KeyValueMap) Policy() string {
|
||||
return km[kmPolicy]
|
||||
}
|
||||
|
||||
// Objects returns the Objects
|
||||
func (km KeyValueMap) Objects() []string {
|
||||
var objects []string
|
||||
_ = json.Unmarshal([]byte(km[kmObjects]), &objects)
|
||||
return objects
|
||||
}
|
||||
|
||||
// SetBucket sets the given bucket to the KeyValueMap
|
||||
func (km *KeyValueMap) SetBucket(bucket string) {
|
||||
(*km)[kmBucket] = bucket
|
||||
}
|
||||
|
||||
// SetPrefix sets the given prefix to the KeyValueMap
|
||||
func (km *KeyValueMap) SetPrefix(prefix string) {
|
||||
(*km)[kmPrefix] = prefix
|
||||
}
|
||||
|
||||
// SetObject sets the given object to the KeyValueMap
|
||||
func (km *KeyValueMap) SetObject(object string) {
|
||||
(*km)[kmObject] = object
|
||||
}
|
||||
|
||||
// SetMarker sets the given marker to the KeyValueMap
|
||||
func (km *KeyValueMap) SetMarker(marker string) {
|
||||
(*km)[kmMarker] = marker
|
||||
}
|
||||
|
||||
// SetPolicy sets the given policy to the KeyValueMap
|
||||
func (km *KeyValueMap) SetPolicy(policy string) {
|
||||
(*km)[kmPolicy] = policy
|
||||
}
|
||||
|
||||
// SetExpiry sets the expiry to the KeyValueMap
|
||||
func (km *KeyValueMap) SetExpiry(expiry int64) {
|
||||
(*km)[kmPolicy] = fmt.Sprintf("%d", expiry)
|
||||
}
|
||||
|
||||
// SetObjects sets the list of objects to the KeyValueMap
|
||||
func (km *KeyValueMap) SetObjects(objects []string) {
|
||||
objsVal, err := json.Marshal(objects)
|
||||
if err != nil {
|
||||
// NB this can only happen when we can't marshal a Go
|
||||
// slice to its json representation.
|
||||
objsVal = []byte("[]")
|
||||
}
|
||||
(*km)[kmObjects] = string(objsVal)
|
||||
}
|
||||
|
||||
// SetUsername sets the username to the KeyValueMap
|
||||
func (km *KeyValueMap) SetUsername(username string) {
|
||||
(*km)[kmUsername] = username
|
||||
}
|
||||
|
||||
// SetHostname sets the hostname to the KeyValueMap
|
||||
func (km *KeyValueMap) SetHostname(hostname string) {
|
||||
(*km)[kmHostname] = hostname
|
||||
}
|
||||
|
||||
// ToKeyValuer interface wraps ToKeyValue method that allows types to
|
||||
// marshal their values as a map of structure member names to their
|
||||
// values, as strings
|
||||
type ToKeyValuer interface {
|
||||
ToKeyValue() KeyValueMap
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for WebGenericArgs
|
||||
func (args *WebGenericArgs) ToKeyValue() KeyValueMap {
|
||||
return KeyValueMap{}
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for MakeBucketArgs
|
||||
func (args *MakeBucketArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(args.BucketName)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for RemoveBucketArgs
|
||||
func (args *RemoveBucketArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(args.BucketName)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for ListObjectsArgs
|
||||
func (args *ListObjectsArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(args.BucketName)
|
||||
km.SetPrefix(args.Prefix)
|
||||
km.SetMarker(args.Marker)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for RemoveObjectArgs
|
||||
func (args *RemoveObjectArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(args.BucketName)
|
||||
km.SetObjects(args.Objects)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for LoginArgs
|
||||
func (args *LoginArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetUsername(args.Username)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for GetBucketPolicyArgs
|
||||
func (args *GetBucketPolicyArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(args.BucketName)
|
||||
km.SetPrefix(args.Prefix)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for ListAllBucketPoliciesArgs
|
||||
func (args *ListAllBucketPoliciesArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(args.BucketName)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for SetBucketPolicyWebArgs
|
||||
func (args *SetBucketPolicyWebArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(args.BucketName)
|
||||
km.SetPrefix(args.Prefix)
|
||||
km.SetPolicy(args.Policy)
|
||||
return km
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for SetAuthArgs
|
||||
// SetAuthArgs doesn't implement the ToKeyValue interface that will be
|
||||
// used by logger subsystem down the line, to avoid leaking
|
||||
// credentials to an external log target
|
||||
func (args *SetAuthArgs) ToKeyValue() KeyValueMap {
|
||||
return KeyValueMap{}
|
||||
}
|
||||
|
||||
// ToKeyValue implementation for PresignedGetArgs
|
||||
func (args *PresignedGetArgs) ToKeyValue() KeyValueMap {
|
||||
km := KeyValueMap{}
|
||||
km.SetHostname(args.HostName)
|
||||
km.SetBucket(args.BucketName)
|
||||
km.SetObject(args.ObjectName)
|
||||
km.SetExpiry(args.Expiry)
|
||||
return km
|
||||
}
|
||||
|
||||
// newWebContext creates a context with ReqInfo values from the given
|
||||
// http request and api name.
|
||||
func newWebContext(r *http.Request, args ToKeyValuer, api string) context.Context {
|
||||
argsMap := args.ToKeyValue()
|
||||
bucket := argsMap.Bucket()
|
||||
object := argsMap.Object()
|
||||
prefix := argsMap.Prefix()
|
||||
|
||||
if prefix != "" {
|
||||
object = prefix
|
||||
}
|
||||
reqInfo := &logger.ReqInfo{
|
||||
DeploymentID: globalDeploymentID,
|
||||
RemoteHost: handlers.GetSourceIP(r),
|
||||
UserAgent: r.UserAgent(),
|
||||
API: api,
|
||||
BucketName: bucket,
|
||||
ObjectName: object,
|
||||
}
|
||||
return logger.SetReqInfo(context.Background(), reqInfo)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
)
|
||||
|
||||
func TestKeyValueMap(t *testing.T) {
|
||||
bucket := "bucket"
|
||||
object := "object"
|
||||
prefix := "prefix"
|
||||
username := "username"
|
||||
policy := "policy"
|
||||
host := "min.io"
|
||||
objects := []string{object, object}
|
||||
|
||||
km := KeyValueMap{}
|
||||
km.SetBucket(bucket)
|
||||
km.SetPrefix(prefix)
|
||||
km.SetUsername(username)
|
||||
km.SetHostname(host)
|
||||
km.SetObject(object)
|
||||
km.SetObjects(objects)
|
||||
km.SetPolicy(policy)
|
||||
|
||||
if got := km.Bucket(); got != bucket {
|
||||
t.Errorf("Expected %s but got %s", bucket, got)
|
||||
}
|
||||
|
||||
if got := km.Object(); got != object {
|
||||
t.Errorf("Expected %s but got %s", object, got)
|
||||
}
|
||||
|
||||
areEqualObjects := func(as, bs []string) bool {
|
||||
if len(as) != len(bs) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, a := range as {
|
||||
b := bs[i]
|
||||
if a != b {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if got := km.Objects(); !areEqualObjects(got, objects) {
|
||||
t.Errorf("Expected %s but got %s", objects, got)
|
||||
}
|
||||
|
||||
if got := km.Policy(); got != policy {
|
||||
t.Errorf("Expected %s but got %s", policy, got)
|
||||
}
|
||||
|
||||
if got := km.Prefix(); got != prefix {
|
||||
t.Errorf("Expected %s but got %s", prefix, got)
|
||||
}
|
||||
|
||||
if got := km.Username(); got != username {
|
||||
t.Errorf("Expected %s but got %s", username, got)
|
||||
}
|
||||
|
||||
if got := km.Hostname(); got != host {
|
||||
t.Errorf("Expected %s but got %s", host, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWebContext(t *testing.T) {
|
||||
api := "Test API"
|
||||
args := ListObjectsArgs{
|
||||
BucketName: "bucket",
|
||||
Prefix: "prefix",
|
||||
Marker: "marker",
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "http://min.io", bytes.NewReader([]byte("nothing")))
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure while creating a test request")
|
||||
}
|
||||
|
||||
ctx := newWebContext(req, &args, api)
|
||||
reqInfo := logger.GetReqInfo(ctx)
|
||||
|
||||
if reqInfo.API != api {
|
||||
t.Errorf("Expected %s got %s", api, reqInfo.API)
|
||||
}
|
||||
|
||||
if reqInfo.BucketName != args.BucketName {
|
||||
t.Errorf("Expected %s got %s", args.BucketName, reqInfo.BucketName)
|
||||
}
|
||||
}
|
||||
+25
-39
@@ -74,31 +74,9 @@ type ServerInfoRep struct {
|
||||
UIVersion string `json:"uiVersion"`
|
||||
}
|
||||
|
||||
// newWebContext creates a context with ReqInfo values from the given
|
||||
// http request and api name.
|
||||
func newWebContext(r *http.Request, api string) context.Context {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object := vars["object"]
|
||||
prefix := vars["prefix"]
|
||||
|
||||
if prefix != "" {
|
||||
object = prefix
|
||||
}
|
||||
reqInfo := &logger.ReqInfo{
|
||||
DeploymentID: globalDeploymentID,
|
||||
RemoteHost: handlers.GetSourceIP(r),
|
||||
UserAgent: r.UserAgent(),
|
||||
API: api,
|
||||
BucketName: bucket,
|
||||
ObjectName: object,
|
||||
}
|
||||
return logger.SetReqInfo(context.Background(), reqInfo)
|
||||
}
|
||||
|
||||
// ServerInfo - get server info.
|
||||
func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error {
|
||||
ctx := newWebContext(r, "webServerInfo")
|
||||
ctx := newWebContext(r, args, "webServerInfo")
|
||||
_, owner, authErr := webRequestAuthenticate(r)
|
||||
if authErr != nil {
|
||||
return toJSONError(ctx, authErr)
|
||||
@@ -148,7 +126,7 @@ type StorageInfoRep struct {
|
||||
|
||||
// StorageInfo - web call to gather storage usage statistics.
|
||||
func (web *webAPIHandlers) StorageInfo(r *http.Request, args *WebGenericArgs, reply *StorageInfoRep) error {
|
||||
ctx := newWebContext(r, "webStorageInfo")
|
||||
ctx := newWebContext(r, args, "webStorageInfo")
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
return toJSONError(ctx, errServerNotInitialized)
|
||||
@@ -169,7 +147,7 @@ type MakeBucketArgs struct {
|
||||
|
||||
// MakeBucket - creates a new bucket.
|
||||
func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *WebGenericRep) error {
|
||||
ctx := newWebContext(r, "webMakeBucket")
|
||||
ctx := newWebContext(r, args, "webMakeBucket")
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
return toJSONError(ctx, errServerNotInitialized)
|
||||
@@ -230,7 +208,7 @@ type RemoveBucketArgs struct {
|
||||
|
||||
// DeleteBucket - removes a bucket, must be empty.
|
||||
func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, reply *WebGenericRep) error {
|
||||
ctx := newWebContext(r, "webDeleteBucket")
|
||||
ctx := newWebContext(r, args, "webDeleteBucket")
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
return toJSONError(ctx, errServerNotInitialized)
|
||||
@@ -318,7 +296,7 @@ type WebBucketInfo struct {
|
||||
|
||||
// ListBuckets - list buckets api.
|
||||
func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, reply *ListBucketsRep) error {
|
||||
ctx := newWebContext(r, "webListBuckets")
|
||||
ctx := newWebContext(r, args, "webListBuckets")
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
return toJSONError(ctx, errServerNotInitialized)
|
||||
@@ -421,7 +399,7 @@ type WebObjectInfo struct {
|
||||
|
||||
// ListObjects - list objects api.
|
||||
func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error {
|
||||
ctx := newWebContext(r, "webListObjects")
|
||||
ctx := newWebContext(r, args, "webListObjects")
|
||||
reply.UIVersion = browser.UIVersion
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
@@ -616,7 +594,7 @@ type RemoveObjectArgs struct {
|
||||
|
||||
// RemoveObject - removes an object, or all the objects at a given prefix.
|
||||
func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *WebGenericRep) error {
|
||||
ctx := newWebContext(r, "webRemoveObject")
|
||||
ctx := newWebContext(r, args, "webRemoveObject")
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
return toJSONError(ctx, errServerNotInitialized)
|
||||
@@ -776,7 +754,7 @@ type LoginRep struct {
|
||||
|
||||
// Login - user login handler.
|
||||
func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
|
||||
ctx := newWebContext(r, "webLogin")
|
||||
ctx := newWebContext(r, args, "webLogin")
|
||||
token, err := authenticateWeb(args.Username, args.Password)
|
||||
if err != nil {
|
||||
return toJSONError(ctx, err)
|
||||
@@ -795,7 +773,7 @@ type GenerateAuthReply struct {
|
||||
}
|
||||
|
||||
func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error {
|
||||
ctx := newWebContext(r, "webGenerateAuth")
|
||||
ctx := newWebContext(r, args, "webGenerateAuth")
|
||||
_, owner, authErr := webRequestAuthenticate(r)
|
||||
if authErr != nil {
|
||||
return toJSONError(ctx, authErr)
|
||||
@@ -830,7 +808,7 @@ type SetAuthReply struct {
|
||||
|
||||
// SetAuth - Set accessKey and secretKey credentials.
|
||||
func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
|
||||
ctx := newWebContext(r, "webSetAuth")
|
||||
ctx := newWebContext(r, args, "webSetAuth")
|
||||
claims, owner, authErr := webRequestAuthenticate(r)
|
||||
if authErr != nil {
|
||||
return toJSONError(ctx, authErr)
|
||||
@@ -919,7 +897,7 @@ type URLTokenReply struct {
|
||||
|
||||
// CreateURLToken creates a URL token (short-lived) for GET requests.
|
||||
func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs, reply *URLTokenReply) error {
|
||||
ctx := newWebContext(r, "webCreateURLToken")
|
||||
ctx := newWebContext(r, args, "webCreateURLToken")
|
||||
claims, owner, authErr := webRequestAuthenticate(r)
|
||||
if authErr != nil {
|
||||
return toJSONError(ctx, authErr)
|
||||
@@ -1503,7 +1481,7 @@ type GetBucketPolicyRep struct {
|
||||
|
||||
// GetBucketPolicy - get bucket policy for the requested prefix.
|
||||
func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolicyArgs, reply *GetBucketPolicyRep) error {
|
||||
ctx := newWebContext(r, "webGetBucketPolicy")
|
||||
ctx := newWebContext(r, args, "webGetBucketPolicy")
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
return toJSONError(ctx, errServerNotInitialized)
|
||||
@@ -1513,6 +1491,7 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
|
||||
if authErr != nil {
|
||||
return toJSONError(ctx, authErr)
|
||||
}
|
||||
|
||||
// For authenticated users apply IAM policy.
|
||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: claims.Subject,
|
||||
@@ -1599,18 +1578,25 @@ type ListAllBucketPoliciesRep struct {
|
||||
|
||||
// ListAllBucketPolicies - get all bucket policy.
|
||||
func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllBucketPoliciesArgs, reply *ListAllBucketPoliciesRep) error {
|
||||
ctx := newWebContext(r, "WebListAllBucketPolicies")
|
||||
ctx := newWebContext(r, args, "WebListAllBucketPolicies")
|
||||
objectAPI := web.ObjectAPI()
|
||||
if objectAPI == nil {
|
||||
return toJSONError(ctx, errServerNotInitialized)
|
||||
}
|
||||
|
||||
_, owner, authErr := webRequestAuthenticate(r)
|
||||
claims, owner, authErr := webRequestAuthenticate(r)
|
||||
if authErr != nil {
|
||||
return toJSONError(ctx, authErr)
|
||||
}
|
||||
|
||||
if !owner {
|
||||
// For authenticated users apply IAM policy.
|
||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: claims.Subject,
|
||||
Action: iampolicy.GetBucketPolicyAction,
|
||||
BucketName: args.BucketName,
|
||||
ConditionValues: getConditionValues(r, "", claims.Subject),
|
||||
IsOwner: owner,
|
||||
}) {
|
||||
return toJSONError(ctx, errAccessDenied)
|
||||
}
|
||||
|
||||
@@ -1680,7 +1666,7 @@ type SetBucketPolicyWebArgs struct {
|
||||
|
||||
// SetBucketPolicy - set bucket policy.
|
||||
func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyWebArgs, reply *WebGenericRep) error {
|
||||
ctx := newWebContext(r, "webSetBucketPolicy")
|
||||
ctx := newWebContext(r, args, "webSetBucketPolicy")
|
||||
objectAPI := web.ObjectAPI()
|
||||
reply.UIVersion = browser.UIVersion
|
||||
|
||||
@@ -1832,7 +1818,7 @@ type PresignedGetRep struct {
|
||||
|
||||
// PresignedGET - returns presigned-Get url.
|
||||
func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error {
|
||||
ctx := newWebContext(r, "webPresignedGet")
|
||||
ctx := newWebContext(r, args, "webPresignedGet")
|
||||
claims, owner, authErr := webRequestAuthenticate(r)
|
||||
if authErr != nil {
|
||||
return toJSONError(ctx, authErr)
|
||||
|
||||
@@ -183,11 +183,10 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
||||
// it needs healing too.
|
||||
for _, part := range partsMetadata[i].Parts {
|
||||
checksumInfo := erasureInfo.GetChecksumInfo(part.Name)
|
||||
tillOffset := erasure.ShardFileTillOffset(0, part.Size, part.Size)
|
||||
err = bitrotCheckFile(onlineDisk, bucket, pathJoin(object, part.Name), tillOffset, checksumInfo.Algorithm, checksumInfo.Hash, erasure.ShardSize())
|
||||
err = onlineDisk.VerifyFile(bucket, pathJoin(object, part.Name), part.Size == 0, checksumInfo.Algorithm, checksumInfo.Hash, erasure.ShardSize())
|
||||
if err != nil {
|
||||
isCorrupt := strings.HasPrefix(err.Error(), "Bitrot verification mismatch - expected ")
|
||||
if !isCorrupt && err != errFileNotFound && err != errVolumeNotFound {
|
||||
if !isCorrupt && err != errFileNotFound && err != errVolumeNotFound && err != errFileUnexpectedSize {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
dataErrs[i] = err
|
||||
|
||||
@@ -195,7 +195,10 @@ func shouldHealObjectOnDisk(xlErr, dataErr error, meta xlMetaV1, quorumModTime t
|
||||
if dataErr == errFileNotFound {
|
||||
return true
|
||||
}
|
||||
if _, ok := dataErr.(hashMismatchError); ok {
|
||||
if dataErr == errFileUnexpectedSize {
|
||||
return true
|
||||
}
|
||||
if _, ok := dataErr.(HashMismatchError); ok {
|
||||
return true
|
||||
}
|
||||
if quorumModTime != meta.Stat.ModTime {
|
||||
@@ -351,7 +354,7 @@ func (xl xlObjects) healObject(ctx context.Context, bucket string, object string
|
||||
}
|
||||
|
||||
// Reorder so that we have data disks first and parity disks next.
|
||||
latestDisks = shuffleDisks(latestDisks, latestMeta.Erasure.Distribution)
|
||||
latestDisks = shuffleDisks(availableDisks, latestMeta.Erasure.Distribution)
|
||||
outDatedDisks = shuffleDisks(outDatedDisks, latestMeta.Erasure.Distribution)
|
||||
partsMetadata = shufflePartsMetadata(partsMetadata, latestMeta.Erasure.Distribution)
|
||||
for i := range outDatedDisks {
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestHealObjectCorrupted(t *testing.T) {
|
||||
t.Fatalf("Failed to complete multipart upload - %v", err)
|
||||
}
|
||||
|
||||
// Remove the object backend files from the first disk.
|
||||
// Test 1: Remove the object backend files from the first disk.
|
||||
xl := obj.(*xlObjects)
|
||||
firstDisk := xl.storageDisks[0]
|
||||
err = firstDisk.DeleteFile(bucket, filepath.Join(object, xlMetaJSONFile))
|
||||
@@ -125,7 +125,34 @@ func TestHealObjectCorrupted(t *testing.T) {
|
||||
t.Errorf("Expected xl.json file to be present but stat failed - %v", err)
|
||||
}
|
||||
|
||||
// Delete xl.json from more than read quorum number of disks, to create a corrupted situation.
|
||||
// Test 2: Heal when part.1 is empty
|
||||
partSt1, err := firstDisk.StatFile(bucket, filepath.Join(object, "part.1"))
|
||||
if err != nil {
|
||||
t.Errorf("Expected part.1 file to be present but stat failed - %v", err)
|
||||
}
|
||||
err = firstDisk.DeleteFile(bucket, filepath.Join(object, "part.1"))
|
||||
if err != nil {
|
||||
t.Errorf("Failure during part.1 removal - %v", err)
|
||||
}
|
||||
err = firstDisk.AppendFile(bucket, filepath.Join(object, "part.1"), []byte{})
|
||||
if err != nil {
|
||||
t.Errorf("Failure during creating part.1 - %v", err)
|
||||
}
|
||||
_, err = obj.HealObject(context.Background(), bucket, object, false, true, madmin.HealDeepScan)
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil but received %v", err)
|
||||
}
|
||||
partSt2, err := firstDisk.StatFile(bucket, filepath.Join(object, "part.1"))
|
||||
if err != nil {
|
||||
t.Errorf("Expected from part.1 file to be present but stat failed - %v", err)
|
||||
}
|
||||
if partSt1.Size != partSt2.Size {
|
||||
t.Errorf("part.1 file size is not the same before and after heal")
|
||||
}
|
||||
|
||||
// Test 3: checks if HealObject returns an error when xl.json is not found
|
||||
// in more than read quorum number of disks, to create a corrupted situation.
|
||||
|
||||
for i := 0; i <= len(xl.storageDisks)/2; i++ {
|
||||
xl.storageDisks[i].DeleteFile(bucket, filepath.Join(object, xlMetaJSONFile))
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"syscall"
|
||||
|
||||
"github.com/minio/cli"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
var defaultUserGroup string
|
||||
|
||||
func init() {
|
||||
username := os.Getenv("MINIO_USERNAME")
|
||||
groupname := os.Getenv("MINIO_GROUPNAME")
|
||||
defaultUserGroup = username + ":" + groupname
|
||||
}
|
||||
|
||||
func getUserGroup(path string) (string, error) {
|
||||
fi, err := os.Stat(minio.PathJoin(path, ".minio.sys"))
|
||||
if err != nil {
|
||||
// Fresh directory we should default to what was requested by user.
|
||||
if os.IsNotExist(err) {
|
||||
fi, err = os.Stat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
stat, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
// Unable to figure out uid/gid, default to defaultUserGroup
|
||||
return defaultUserGroup, nil
|
||||
}
|
||||
u, err := user.LookupId(fmt.Sprintf("%d", stat.Uid))
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%d:%d", stat.Uid, stat.Gid), nil
|
||||
}
|
||||
g, err := user.LookupGroupId(fmt.Sprintf("%d", stat.Gid))
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%d:%d", stat.Uid, stat.Gid), nil
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", u.Username, g.Name), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Flags = append(minio.ServerFlags, minio.GlobalFlags...)
|
||||
app.Action = func(ctx *cli.Context) {
|
||||
// Fetch address option
|
||||
serverAddr := ctx.GlobalString("address")
|
||||
if serverAddr == "" || serverAddr == ":9000" {
|
||||
serverAddr = ctx.String("address")
|
||||
}
|
||||
if ctx.Args().First() == "help" {
|
||||
cli.ShowCommandHelpAndExit(ctx, "check-user", 1)
|
||||
}
|
||||
if ctx.Args().First() != "minio" {
|
||||
cli.ShowCommandHelpAndExit(ctx, "check-user", 1)
|
||||
}
|
||||
args := cli.Args(ctx.Args().Tail())
|
||||
if !args.Present() {
|
||||
cli.ShowCommandHelpAndExit(ctx, "check-user", 1)
|
||||
}
|
||||
var ug string
|
||||
var err error
|
||||
switch args.First() {
|
||||
case "gateway":
|
||||
args = cli.Args(args.Tail())
|
||||
if args.First() != "nas" {
|
||||
fmt.Println(defaultUserGroup)
|
||||
return
|
||||
}
|
||||
args = cli.Args(args.Tail())
|
||||
if args.First() == "" {
|
||||
fmt.Println("")
|
||||
return
|
||||
}
|
||||
ug, err = getUserGroup(args.First())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
case "server":
|
||||
var setArgs [][]string
|
||||
setArgs, err = minio.GetAllSets(args.Tail()...)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
var endpoints minio.EndpointList
|
||||
_, endpoints, _, err = minio.CreateEndpoints(serverAddr, setArgs...)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for _, endpoint := range endpoints {
|
||||
if !endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
ug, err = getUserGroup(endpoint.Path)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
default:
|
||||
cli.ShowCommandHelpAndExit(ctx, "check-user", 1)
|
||||
}
|
||||
fmt.Println(ug)
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,6 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
export MINIO_USERNAME=${MINIO_USERNAME:-"minio"}
|
||||
export MINIO_GROUPNAME=${MINIO_GROUPNAME:-"minio"}
|
||||
|
||||
# If command starts with an option, prepend minio.
|
||||
if [ "${1}" != "minio" ]; then
|
||||
if [ -n "${1}" ]; then
|
||||
@@ -42,37 +39,35 @@ docker_secrets_env() {
|
||||
fi
|
||||
}
|
||||
|
||||
## Create UID/GID based on available environment variables.
|
||||
docker_set_uid_gid() {
|
||||
addgroup -S "$MINIO_GROUPNAME" >/dev/null 2>&1 && \
|
||||
adduser -S -G "$MINIO_GROUPNAME" "$MINIO_USERNAME" >/dev/null 2>&1
|
||||
## Set SSE_MASTER_KEY from docker secrets if provided
|
||||
docker_sse_encryption_env() {
|
||||
SSE_MASTER_KEY_FILE="/run/secrets/$MINIO_SSE_MASTER_KEY_FILE"
|
||||
|
||||
if [ -f "$SSE_MASTER_KEY_FILE" ]; then
|
||||
MINIO_SSE_MASTER_KEY="$(cat "$SSE_MASTER_KEY_FILE")"
|
||||
export MINIO_SSE_MASTER_KEY
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
# su-exec to requested user, if user cannot be requested
|
||||
# existing user is used automatically.
|
||||
# su-exec to requested user, if service cannot run exec will fail.
|
||||
docker_switch_user() {
|
||||
owner=$(check-user "$@")
|
||||
if [ "${owner}" != "${MINIO_USERNAME}:${MINIO_GROUPNAME}" ]; then
|
||||
## Print the message only if we are not using non-default username:groupname.
|
||||
if [ "${MINIO_USERNAME}:${MINIO_GROUPNAME}" != "minio:minio" ]; then
|
||||
echo "Requested username/group ${MINIO_USERNAME}:${MINIO_GROUPNAME} cannot be used"
|
||||
echo "Found existing data with user ${owner}, we will continue and use ${owner} instead."
|
||||
return
|
||||
fi
|
||||
if [ -z "${MINIO_USERNAME}" ] || [ -z "${MINIO_GROUPNAME}" ]; then
|
||||
addgroup -S "$MINIO_GROUPNAME" >/dev/null 2>&1 && \
|
||||
adduser -S -G "$MINIO_GROUPNAME" "$MINIO_USERNAME" >/dev/null 2>&1
|
||||
|
||||
exec su-exec "${MINIO_USERNAME}:${MINIO_GROUPNAME}" "$@"
|
||||
else
|
||||
# fallback
|
||||
exec "$@"
|
||||
fi
|
||||
# check if su-exec is allowed, if yes proceed proceed.
|
||||
if su-exec "${owner}" "/bin/ls" >/dev/null 2>&1; then
|
||||
exec su-exec "${owner}" "$@"
|
||||
fi
|
||||
# fallback
|
||||
exec "$@"
|
||||
}
|
||||
|
||||
## Set access env from secrets if necessary.
|
||||
docker_secrets_env
|
||||
|
||||
## User Input UID and GID
|
||||
docker_set_uid_gid
|
||||
## Set sse encryption from secrets if necessary.
|
||||
docker_sse_encryption_env
|
||||
|
||||
## Switch to user if applicable.
|
||||
docker_switch_user "$@"
|
||||
|
||||
@@ -286,12 +286,14 @@ MinIO requires a 5.x series version of Elasticsearch. This is the latest major r
|
||||
|
||||
The MinIO server configuration file is stored on the backend in json format. The Elasticsearch configuration is located in the `elasticsearch` key under the `notify` top-level key. Create a configuration key-value pair here for your Elasticsearch instance. The key is a name for your Elasticsearch endpoint, and the value is a collection of key-value parameters described in the table below.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :-------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `enable` | _bool_ | (Required) Is this server endpoint configuration active/enabled? |
|
||||
| `format` | _string_ | (Required) Either `namespace` or `access`. |
|
||||
| `url` | _string_ | (Required) The Elasticsearch server's address, with optional authentication info. For example: `http://localhost:9200` or with authentication info `http://elastic:MagicWord@127.0.0.1:9200`. |
|
||||
| `index` | _string_ | (Required) The name of an Elasticsearch index in which MinIO will store documents. |
|
||||
| Parameter | Type | Description |
|
||||
| :----------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `enable` | _bool_ | (Required) Is this server endpoint configuration active/enabled? |
|
||||
| `format` | _string_ | (Required) Either `namespace` or `access`. |
|
||||
| `url` | _string_ | (Required) The Elasticsearch server's address, with optional authentication info. For example: `http://localhost:9200` or with authentication info `http://elastic:MagicWord@127.0.0.1:9200`. |
|
||||
| `index` | _string_ | (Required) The name of an Elasticsearch index in which MinIO will store documents. |
|
||||
| `queueDir` | _string_ | Persistent store for events when Elasticsearch broker is offline |
|
||||
| `queueLimit` | _int_ | Set the maximum event limit for the persistent store. The default limit is 10000 |
|
||||
|
||||
An example of Elasticsearch configuration is as follows:
|
||||
|
||||
@@ -301,11 +303,15 @@ An example of Elasticsearch configuration is as follows:
|
||||
"enable": true,
|
||||
"format": "namespace",
|
||||
"url": "http://127.0.0.1:9200",
|
||||
"index": "minio_events"
|
||||
"index": "minio_events",
|
||||
"queueDir": "",
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
Minio supports persistent event store. The persistent store will backup events when the Elasticsearch broker goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queueDir` field and the maximum limit of events in the queueDir in `queueLimit` field. For eg, the `queueDir` can be `/home/events` and `queueLimit` can be `1000`. By default, the `queueLimit` is set to 10000.
|
||||
|
||||
If Elasticsearch has authentication enabled, the credentials can be supplied to MinIO via the `url` parameter formatted as `PROTO://USERNAME:PASSWORD@ELASTICSEARCH_HOST:PORT`.
|
||||
|
||||
To update the configuration, use `mc admin config get` command to get the current configuration file for the minio deployment in json format, and save it locally.
|
||||
@@ -1104,12 +1110,17 @@ An example configuration for NSQ is shown below:
|
||||
"tls": {
|
||||
"enable": false,
|
||||
"skipVerify": true
|
||||
}
|
||||
},
|
||||
"queueDir": "",
|
||||
"queueLimit": 0
|
||||
}
|
||||
}
|
||||
|
||||
MinIO supports persistent event store. The persistent store will backup events when the NSQ broker goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queueDir` field and the maximum limit of events in the queueDir in `queueLimit` field. For eg, the `queueDir` can be `/home/events` and `queueLimit` can be `1000`. By default, the `queueLimit` is set to 10000.
|
||||
|
||||
```
|
||||
|
||||
To update the configuration, use `mc admin config get` command to get the current configuration file for the minio deployment in json format, and save it locally.
|
||||
To update the configuration, use `mc admin config get` command to get the current configuration file for the MinIO deployment in json format, and save it locally.
|
||||
|
||||
```sh
|
||||
$ mc admin config get myminio/ > /tmp/myconfig
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"enable": false,
|
||||
"format": "",
|
||||
"url": "",
|
||||
"index": ""
|
||||
"index": "",
|
||||
"queueDir": "",
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"kafka": {
|
||||
@@ -131,7 +133,9 @@
|
||||
"tls": {
|
||||
"enable": false,
|
||||
"skipVerify": true
|
||||
}
|
||||
},
|
||||
"queueDir": "",
|
||||
"queueLimit": 0
|
||||
}
|
||||
},
|
||||
"postgresql": {
|
||||
|
||||
@@ -58,20 +58,19 @@ docker run -p 9000:9000 --name minio1 \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
### Run MinIO Docker as non root user
|
||||
MinIO server runs as non-root within the container by default. However, this is applicable only if you're deploying new MinIO instance (not upgrading from older releases). Deployments upgrading from older MinIO deployments, will continue to run as the user previously used if any.
|
||||
### Run MinIO Docker as regular user
|
||||
MinIO server doesn't run as a regular user by default in docker containers. To run MinIO container as regular user use environment variables `MINIO_USERNAME` and `MINIO_GROUPNAME`.
|
||||
|
||||
By default `minio` is username and groupname. Use environment variables `MINIO_USERNAME` and `MINIO_GROUPNAME` to override these default values.
|
||||
> NOTE: If you are upgrading from existing deployments, you need to make sure this user has write access to previous persistent volumes. MinIO will not migrate the content automatically.
|
||||
|
||||
#### GNU/Linux and macOS
|
||||
```sh
|
||||
docker run -p 9000:9000 --name minio1 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
-e "MINIO_USERNAME=custom" \
|
||||
-e "MINIO_GROUPNAME=custom" \
|
||||
-e "MINIO_USERNAME=minio-user" \
|
||||
-e "MINIO_GROUPNAME=minio-user" \
|
||||
-v /mnt/data:/data \
|
||||
-v /mnt/config:/root/.minio \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
@@ -80,10 +79,9 @@ docker run -p 9000:9000 --name minio1 \
|
||||
docker run -p 9000:9000 --name minio1 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
-e "MINIO_USERNAME=custom" \
|
||||
-e "MINIO_GROUPNAME=custom" \
|
||||
-e "MINIO_USERNAME=minio-user" \
|
||||
-e "MINIO_GROUPNAME=minio-user" \
|
||||
-v D:\data:/data \
|
||||
-v D:\minio\config:/root/.minio \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
|
||||
+138
-32
@@ -12,17 +12,17 @@ MinIO supports two different KMS concepts:
|
||||
by enabling or disabling the corresponding master keys on demand.
|
||||
|
||||
- Direct KMS master keys:
|
||||
MinIO can also be configured to directly use a master key specified by the environment variable `MINIO_SSE_MASTER_KEY`.
|
||||
MinIO can also be configured to directly use a master key specified by the environment variable `MINIO_SSE_MASTER_KEY` or with a docker secret key.
|
||||
Direct master keys are useful if the storage backend is not on the same machine as the MinIO server, e.g.,
|
||||
if network drives or MinIO gateway is used and an external KMS would cause too much management overhead.
|
||||
|
||||
if network drives or MinIO gateway is used and an external KMS would cause too much management overhead.
|
||||
|
||||
Note: KMS master keys are mainly for testing purposes. It's not recommended to use them for production deployments.
|
||||
Further if the MinIO server machine is ever compromised, then the master key must also be treated as compromised.
|
||||
|
||||
**Important:**
|
||||
**Important:**
|
||||
If multiple MinIO servers are configured as [gateways](https://github.com/minio/minio/blob/master/docs/gateway/README.md)
|
||||
pointing to the *same* backend - for example the same NAS storage - then the KMS configuration **must** be the same for
|
||||
all gateways. Otherwise one gateway may not be able to decrypt objects created by another gateway. It is the operators'
|
||||
all gateways. Otherwise one gateway may not be able to decrypt objects created by another gateway. It is the operators'
|
||||
responsibility to ensure consistency.
|
||||
|
||||
## Get started
|
||||
@@ -36,47 +36,135 @@ Either use Hashicorp Vault as external KMS or specify a master key directly depe
|
||||
|
||||
#### 2.1 Setup Hashicorp Vault
|
||||
|
||||
Here is a sample quick start for configuring vault with a transit backend and Approle with correct policy
|
||||
Here is a sample quick start for configuring vault with a transit backend and Approle with correct policy
|
||||
|
||||
MinIO requires the following Vault setup:
|
||||
- The [transit backend](https://www.vaultproject.io/api/secret/transit/index.html) configured with a named encryption key-ring
|
||||
- [AppRole](https://www.vaultproject.io/docs/auth/approle.html) based authentication with read/update policy for transit backend. In particular, read and update policy are required for the [Generate Data Key](https://www.vaultproject.io/api/secret/transit/index.html#generate-data-key) endpoint and [Decrypt Data](https://www.vaultproject.io/api/secret/transit/index.html#decrypt-data) endpoint.
|
||||
|
||||
**2.1.1 Start Vault server in dev mode**
|
||||
|
||||
In dev mode, Vault server runs in-memory and starts unsealed. Note that running Vault in dev mode is insecure and any data stored in the Vault is lost upon restart.
|
||||
**2.1.1 Start Vault server**
|
||||
|
||||
Vault requires access to `mlock` syscall by default. Use `setcap` to give the Vault executable the ability to use the `mlock` syscall without running the process as root. To do so run:
|
||||
```
|
||||
vault server -dev
|
||||
sudo setcap cap_ipc_lock=+ep $(readlink -f $(which vault))
|
||||
```
|
||||
|
||||
**2.1.2 Set up vault transit backend and create an app role**
|
||||
Create `vault-config.json` to use file backend and start the server.
|
||||
```
|
||||
cat > vault-config.json <<EOF
|
||||
{
|
||||
"api_addr": "http://127.0.0.1:8200",
|
||||
"backend": {
|
||||
"file": {
|
||||
"path": "vault/file"
|
||||
}
|
||||
},
|
||||
"default_lease_ttl": "168h",
|
||||
"max_lease_ttl": "720h",
|
||||
"listener": {
|
||||
"tcp": {
|
||||
"address": "0.0.0.0:8200",
|
||||
"tls_disable": true
|
||||
}
|
||||
},
|
||||
"ui": true
|
||||
}
|
||||
EOF
|
||||
|
||||
vault server -config vault-config.json
|
||||
```
|
||||
|
||||
> NOTE: In this example we use `"tls_disable": true` for demonstration purposes only,
|
||||
> in production setup you should generate proper TLS certificates by specifying
|
||||
> - [`tls_cert_file`](https://www.vaultproject.io/docs/configuration/listener/tcp.html#tls_cert_file)
|
||||
> - [`tls_key_file`](https://www.vaultproject.io/docs/configuration/listener/tcp.html#tls_key_file)
|
||||
|
||||
|
||||
**2.1.2 Initialize vault and unseal it**
|
||||
|
||||
```
|
||||
export VAULT_ADDR='http://127.0.0.1:8200'
|
||||
vault operator init
|
||||
Unseal Key 1: eyW/+8ZtsgT81Cb0e8OVxzJAQP5lY7Dcamnze+JnWEDT
|
||||
Unseal Key 2: 0tZn+7QQCxphpHwTm6/dC3LpP5JGIbYl6PK8Sy79R+P2
|
||||
Unseal Key 3: cmhs+AUMXUuB6Lzsvgcbp3bRT6VDGQjgCBwB2xm0ANeF
|
||||
Unseal Key 4: /fTPpec5fWpGqWHK+uhnnTNMQyAbl5alUi4iq2yNgyqj
|
||||
Unseal Key 5: UPdDVPto+H6ko+20NKmagK40MOskqOBw4y/S51WpgVy/
|
||||
|
||||
Initial Root Token: s.zaU4Gbcu0Wh46uj2V3VuUde0
|
||||
|
||||
Vault is initialized with 5 key shares and a key threshold of 3. Please securely
|
||||
distribute the key shares printed above. When the Vault is re-sealed,
|
||||
restarted, or stopped, you must supply at least 3 of these keys to unseal it
|
||||
before it can start servicing requests.
|
||||
|
||||
Vault does not store the generated master key. Without at least 3 key to
|
||||
reconstruct the master key, Vault will remain permanently sealed!
|
||||
|
||||
It is possible to generate new unseal keys, provided you have a quorum of
|
||||
existing unseal keys shares. See "vault operator rekey" for more information.
|
||||
```
|
||||
|
||||
Use any of the previously generated keys to unseal the vault
|
||||
```
|
||||
vault operator unseal eyW/+8ZtsgT81Cb0e8OVxzJAQP5lY7Dcamnze+JnWEDT
|
||||
vault operator unseal 0tZn+7QQCxphpHwTm6/dC3LpP5JGIbYl6PK8Sy79R+P2
|
||||
vault operator unseal cmhs+AUMXUuB6Lzsvgcbp3bRT6VDGQjgCBwB2xm0ANeF
|
||||
Key Value
|
||||
--- -----
|
||||
Seal Type shamir
|
||||
Initialized true
|
||||
Sealed false ---> NOTE: vault is unsealed
|
||||
Total Shares 5
|
||||
Threshold 3
|
||||
Version 1.1.3
|
||||
Cluster Name vault-cluster-3f084948
|
||||
Cluster ID 8c92e999-7062-4da6-4434-0fc05f34824d
|
||||
HA Enabled false
|
||||
```
|
||||
|
||||
Obtain root token from the `vault operator init` output above. It is displayed as `Initial Root Token: s.zaU4Gbcu0Wh46uj2V3VuUde0`
|
||||
|
||||
**2.1.3 Set up vault transit backend and create an app role**
|
||||
```
|
||||
export VAULT_TOKEN=s.zaU4Gbcu0Wh46uj2V3VuUde0
|
||||
|
||||
vault auth enable approle # enable approle style auth
|
||||
vault secrets enable transit # enable transit secrets engine
|
||||
# define an encryption key-ring for the transit path
|
||||
vault write -f transit/keys/my-minio-key
|
||||
|
||||
cat > vaultpolicy.hcl <<EOF
|
||||
path "transit/datakey/plaintext/my-minio-key" {
|
||||
path "transit/datakey/plaintext/my-minio-key" {
|
||||
capabilities = [ "read", "update"]
|
||||
}
|
||||
path "transit/decrypt/my-minio-key" {
|
||||
path "transit/decrypt/my-minio-key" {
|
||||
capabilities = [ "read", "update"]
|
||||
}
|
||||
path "transit/encrypt/my-minio-key" {
|
||||
path "transit/encrypt/my-minio-key" {
|
||||
capabilities = [ "read", "update"]
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
export VAULT_ADDR='http://127.0.0.1:8200'
|
||||
vault auth enable approle # enable approle style auth
|
||||
vault secrets enable transit # enable transit secrets engine
|
||||
vault write -f transit/keys/my-minio-key #define a encryption key-ring for the transit path
|
||||
vault policy write minio-policy ./vaultpolicy.hcl #define a policy for AppRole to access transit path
|
||||
vault write auth/approle/role/my-role token_num_uses=0 secret_id_num_uses=0 period=5m # period indicates it is renewable if token is renewed before the period is over
|
||||
# define a policy for AppRole to access transit path
|
||||
vault policy write minio-policy ./vaultpolicy.hcl
|
||||
|
||||
# period indicates it is renewable if token is renewed before the period is over
|
||||
vault write auth/approle/role/my-role token_num_uses=0 secret_id_num_uses=0 period=5m
|
||||
|
||||
# define an AppRole
|
||||
vault write auth/approle/role/my-role policies=minio-policy # apply policy to role
|
||||
vault read auth/approle/role/my-role/role-id # get Approle ID
|
||||
vault write -f auth/approle/role/my-role/secret-id
|
||||
Key Value
|
||||
--- -----
|
||||
role_id 8c03926c-6c51-7a1d-cf7d-62e48ab8d6d7
|
||||
|
||||
vault write -f auth/approle/role/my-role/secret-id
|
||||
Key Value
|
||||
--- -----
|
||||
secret_id edd8738c-6efe-c226-74f9-ef5b66e119d7
|
||||
secret_id_accessor 57d1db64-6350-c321-4a3e-fc6aeb7d00b6
|
||||
```
|
||||
|
||||
The AppRole ID, AppRole Secret Id, Vault endpoint and Vault key name can now be used to start minio server with Vault as KMS.
|
||||
@@ -85,10 +173,10 @@ The AppRole ID, AppRole Secret Id, Vault endpoint and Vault key name can now be
|
||||
|
||||
You'll need the Vault endpoint, AppRole ID, AppRole SecretID and encryption key-ring name defined in step 2.1.2
|
||||
|
||||
```sh
|
||||
export MINIO_SSE_VAULT_APPROLE_ID=9b56cc08-8258-45d5-24a3-679876769126
|
||||
export MINIO_SSE_VAULT_APPROLE_SECRET=4e30c52f-13e4-a6f5-0763-d50e8cb4321f
|
||||
export MINIO_SSE_VAULT_ENDPOINT=https://vault-endpoint-ip:8200
|
||||
```
|
||||
export MINIO_SSE_VAULT_APPROLE_ID=8c03926c-6c51-7a1d-cf7d-62e48ab8d6d7
|
||||
export MINIO_SSE_VAULT_APPROLE_SECRET=edd8738c-6efe-c226-74f9-ef5b66e119d7
|
||||
export MINIO_SSE_VAULT_ENDPOINT=http://vault-endpoint-ip:8200
|
||||
export MINIO_SSE_VAULT_KEY_NAME=my-minio-key
|
||||
export MINIO_SSE_VAULT_AUTH_TYPE=approle
|
||||
minio server ~/export
|
||||
@@ -127,19 +215,38 @@ minio gateway s3
|
||||
|
||||
#### 2.2 Specify a master key
|
||||
|
||||
**2.2.1 KMS master key from environment variables**
|
||||
|
||||
A KMS master key consists of a master-key ID (CMK) and the 256 bit master key encoded as HEX value separated by a `:`.
|
||||
A KMS master key can be specified directly using:
|
||||
|
||||
```sh
|
||||
```
|
||||
export MINIO_SSE_MASTER_KEY=my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574
|
||||
```
|
||||
|
||||
Please use your own master key. A random master key can be generated using e.g. this command on Linux/Mac/BSD* systems:
|
||||
|
||||
```sh
|
||||
```
|
||||
head -c 32 /dev/urandom | xxd -c 32 -ps
|
||||
```
|
||||
|
||||
**2.2.2 KMS master key from docker secret**
|
||||
|
||||
Alternatively, you may pass a master key as a [Docker secret](https://docs.docker.com/engine/swarm/secrets/).
|
||||
|
||||
```bash
|
||||
echo "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574" | docker secret create sse_master_key
|
||||
```
|
||||
|
||||
Obviously, do not use this demo key for anything real!
|
||||
|
||||
To use another secret name, follow the instructions above and replace sse_master_key with your custom names (e.g. my_sse_master_key).
|
||||
Then, set the MINIO_SSE_MASTER_KEY_FILE environment variable to your secret name:
|
||||
|
||||
```bash
|
||||
export MINIO_SSE_MASTER_KEY_FILE=my_sse_master_key
|
||||
```
|
||||
|
||||
### 3. Test your setup
|
||||
To test this setup, start minio server with environment variables set in Step 3, and server is ready to handle SSE-S3 requests.
|
||||
|
||||
@@ -149,18 +256,18 @@ MinIO can also enable auto-encryption **if** a valid KMS configuration is specif
|
||||
encrypted objects. Auto-Encryption, if enabled, ensures that all uploaded objects are encrypted using the specified
|
||||
KMS configuration.
|
||||
|
||||
Auto-Encryption is useful especially if the MinIO operator wants to ensure that objects are **never** stored in
|
||||
Auto-Encryption is useful especially if the MinIO operator wants to ensure that objects are **never** stored in
|
||||
plaintext - for example if sensitive data is stored on public cloud storage.
|
||||
|
||||
To enable auto-encryption set the environment variable to `on`:
|
||||
|
||||
```sh
|
||||
```
|
||||
export MINIO_SSE_AUTO_ENCRYPTION=on
|
||||
```
|
||||
|
||||
To verify auto-encryption, use the `mc` command:
|
||||
|
||||
```sh
|
||||
```
|
||||
mc cp test.file myminio/crypt/
|
||||
test.file: 5 B / 5 B ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 100.00% 337 B/s 0s
|
||||
mc stat myminio/crypt/test.file
|
||||
@@ -171,8 +278,7 @@ Encrypted :
|
||||
```
|
||||
|
||||
Note: Auto-Encryption only affects non-SSE-C requests since objects uploaded using SSE-C are already encrypted
|
||||
and S3 only allows either SSE-S3 or SSE-C but not both for the same object.
|
||||
|
||||
and S3 only allows either SSE-S3 or SSE-C but not both for the same object.
|
||||
|
||||
# Explore Further
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"api_addr": "http://127.0.0.1:8200",
|
||||
"backend": {
|
||||
"file": {
|
||||
"path": "vault/file"
|
||||
}
|
||||
},
|
||||
"default_lease_ttl": "168h",
|
||||
"max_lease_ttl": "720h",
|
||||
"listener": {
|
||||
"tcp": {
|
||||
"address": "0.0.0.0:8200",
|
||||
"tls_disable": true
|
||||
}
|
||||
},
|
||||
"ui": true
|
||||
}
|
||||
@@ -5,7 +5,7 @@ version: '2'
|
||||
# 9001 through 9004.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
volumes:
|
||||
- data1:/data
|
||||
ports:
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
MINIO_SECRET_KEY: minio123
|
||||
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
volumes:
|
||||
- data2:/data
|
||||
ports:
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
MINIO_SECRET_KEY: minio123
|
||||
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
volumes:
|
||||
- data3:/data
|
||||
ports:
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
MINIO_SECRET_KEY: minio123
|
||||
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
volumes:
|
||||
- data4:/data
|
||||
ports:
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.1'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
- access_key
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
- access_key
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
- access_key
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -30,7 +30,7 @@ spec:
|
||||
value: "minio"
|
||||
- name: MINIO_SECRET_KEY
|
||||
value: "minio123"
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
# Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future.
|
||||
args:
|
||||
- server
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
value: "minio"
|
||||
- name: MINIO_SECRET_KEY
|
||||
value: "minio123"
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
args:
|
||||
- server
|
||||
- http://minio-0.minio.default.svc.cluster.local/data
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
containers:
|
||||
- name: minio
|
||||
# Pulls the default Minio image from Docker Hub
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
args:
|
||||
- gateway
|
||||
- gcs
|
||||
|
||||
@@ -29,7 +29,7 @@ spec:
|
||||
- name: data
|
||||
mountPath: "/data"
|
||||
# Pulls the lastest Minio image from Docker Hub
|
||||
image: minio/minio:RELEASE.2019-06-27T21-13-50Z
|
||||
image: minio/minio:RELEASE.2019-07-10T00-34-56Z
|
||||
args:
|
||||
- server
|
||||
- /data
|
||||
|
||||
@@ -27,6 +27,7 @@ require (
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/gorilla/handlers v1.4.0
|
||||
@@ -47,12 +48,12 @@ require (
|
||||
github.com/klauspost/pgzip v1.2.1
|
||||
github.com/klauspost/readahead v1.3.0
|
||||
github.com/klauspost/reedsolomon v1.9.1
|
||||
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3
|
||||
github.com/lib/pq v1.0.0
|
||||
github.com/marstr/guid v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.7
|
||||
github.com/miekg/dns v1.1.8
|
||||
github.com/minio/blazer v0.0.0-20171126203752-2081f5bf0465
|
||||
github.com/minio/cli v1.20.0
|
||||
github.com/minio/cli v1.21.0
|
||||
github.com/minio/dsync/v2 v2.0.0
|
||||
github.com/minio/hdfs/v3 v3.0.0
|
||||
github.com/minio/highwayhash v1.0.0
|
||||
@@ -73,6 +74,7 @@ require (
|
||||
github.com/nats-io/stan.go v0.4.5
|
||||
github.com/ncw/directio v1.0.5
|
||||
github.com/nsqio/go-nsq v1.0.7
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pkg/profile v1.3.0
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190704165056-9c2d0518ed81 // indirect
|
||||
@@ -80,7 +82,6 @@ require (
|
||||
github.com/rs/cors v1.6.0
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/segmentio/go-prompt v1.2.1-0.20161017233205-f0d19b6901ad
|
||||
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e
|
||||
github.com/streadway/amqp v0.0.0-20190402114354-16ed540749f6
|
||||
github.com/tidwall/gjson v1.2.1
|
||||
@@ -90,11 +91,9 @@ require (
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
go.uber.org/atomic v1.3.2
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
|
||||
golang.org/x/tools v0.0.0-20190627203933-19ff4fff8850 // indirect
|
||||
google.golang.org/api v0.4.0
|
||||
gopkg.in/Shopify/sarama.v1 v1.20.0
|
||||
gopkg.in/olivere/elastic.v5 v5.0.80
|
||||
|
||||
@@ -180,6 +180,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -376,6 +378,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3 h1:1sl2HmNtqGnDuydLgCJwZIpDLGqZOdwOkcY8WtUl8Cw=
|
||||
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
|
||||
github.com/lib/pq v0.0.0-20181016162627-9eb73efc1fcc/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@@ -408,6 +412,8 @@ github.com/minio/cli v1.3.0 h1:vB0iUpmyaH54+1jJJj62Aa0qFF3xO3i0J3IcKiM6bHM=
|
||||
github.com/minio/cli v1.3.0/go.mod h1:hLsWNQy2wIf3FKFnMlH69f4RdEyn8nbRA2shaulTjGY=
|
||||
github.com/minio/cli v1.20.0 h1:OVNIt8Rg5+mpYb8siWT2gBV5hvUyFbRvBikC+Ytvf5A=
|
||||
github.com/minio/cli v1.20.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/cli v1.21.0 h1:8gE8iZc0ONOhHy/T28tCsNew5f5VzWU558U9Myjfq50=
|
||||
github.com/minio/cli v1.21.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/dsync v0.0.0-20190104003057-61c41ffdeea2/go.mod h1:eLQe3mXL0h02kNpPtBJiLr1fIEIJftgXRAjncjQbxJo=
|
||||
github.com/minio/dsync v1.0.0 h1:l6pQgUPBM41idlR0UOcpAP+EYim9MCwIAUh6sQQI1gk=
|
||||
github.com/minio/dsync v1.0.0/go.mod h1:eLQe3mXL0h02kNpPtBJiLr1fIEIJftgXRAjncjQbxJo=
|
||||
@@ -420,8 +426,6 @@ github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2
|
||||
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
|
||||
github.com/minio/lsync v0.0.0-20190207022115-a4e43e3d0887 h1:MIpCDz3d2FR2a+FjdizuFdjsoeHuLlSkl3YNQJ55jV8=
|
||||
github.com/minio/lsync v0.0.0-20190207022115-a4e43e3d0887/go.mod h1:ni10+iSX7FO8N2rv41XM444V6w4rYO0dZo5KIkbn/YA=
|
||||
github.com/minio/lsync v0.1.0 h1:VczQvieoFS1Ayyxw6RhODkqN/UhMQXom14oJuSbR7vo=
|
||||
github.com/minio/lsync v0.1.0/go.mod h1:ni10+iSX7FO8N2rv41XM444V6w4rYO0dZo5KIkbn/YA=
|
||||
github.com/minio/lsync v1.0.1 h1:AVvILxA976xc27hstd1oR+X9PQG0sPSom1MNb1ImfUs=
|
||||
github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA=
|
||||
github.com/minio/mc v0.0.0-20190311071728-2e612b23d665/go.mod h1:7qLZXNjCD55DJ3iqe1uWoUh1MASRVd1M6wnqSdyhx7Y=
|
||||
@@ -685,10 +689,8 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -697,7 +699,6 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -720,10 +721,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b h1:lkjdUzSyJ5P1+eal9fxXX9Xg2BTfswsonKUse48C0uE=
|
||||
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180603041954-1e0a3fa8ba9a/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -764,8 +763,6 @@ golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f h1:dHNZYIYdq2QuU6w73vZ/DzesPbVlZVYZTtTZmrnsbQ8=
|
||||
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -788,12 +785,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190318200714-bb1270c20edf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190619181801-b76e30ffa0aa h1:OKbX3LR+DrC3Oj0rhniAcIbi43SoQW6ennRcN9MTxpE=
|
||||
golang.org/x/tools v0.0.0-20190619181801-b76e30ffa0aa/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190627203933-19ff4fff8850 h1:fA72gpMxsjJw+HAMdKku/cilEAHBQ9yEhmYkRO3wzyM=
|
||||
golang.org/x/tools v0.0.0-20190627203933-19ff4fff8850/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
google.golang.org/api v0.0.0-20180603000442-8e296ef26005/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20180916000451-19ff8768a5c0/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
*.test
|
||||
*.jar
|
||||
src/*
|
||||
temp
|
||||
__pycache__/
|
||||
log/*
|
||||
minio.test
|
||||
bin/*
|
||||
node_modules
|
||||
# exception to the rule
|
||||
!log/.gitkeep
|
||||
!bin/.gitkeep
|
||||
*.class
|
||||
*~
|
||||
run/core/minio-dotnet/bin/*
|
||||
run/core/minio-dotnet/obj/*
|
||||
run/core/minio-dotnet/out/*
|
||||
@@ -0,0 +1,84 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
ENV GOROOT /usr/local/go
|
||||
|
||||
ENV GOPATH /usr/local
|
||||
|
||||
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
|
||||
|
||||
WORKDIR /mint
|
||||
|
||||
RUN apt-get --yes update && apt-get --yes upgrade && \
|
||||
apt-get --yes --quiet install wget jq curl dnsmasq
|
||||
|
||||
ENV MINT_ROOT_DIR /mint
|
||||
ENV MINT_RUN_CORE_DIR $MINT_ROOT_DIR/run/core
|
||||
ENV MINT_RUN_SECURITY_DIR $MINT_ROOT_DIR/run/security
|
||||
ENV WGET "wget --quiet --no-check-certificate"
|
||||
|
||||
COPY create-data-files.sh /mint
|
||||
RUN /mint/create-data-files.sh
|
||||
|
||||
COPY install-packages.list /mint
|
||||
COPY preinstall.sh /mint
|
||||
RUN /mint/preinstall.sh
|
||||
|
||||
COPY run /mint/run
|
||||
|
||||
COPY build/awscli /mint/build/awscli
|
||||
RUN build/awscli/install.sh
|
||||
|
||||
COPY build/aws-sdk-java /mint/build/aws-sdk-java
|
||||
RUN build/aws-sdk-java/install.sh
|
||||
|
||||
COPY build/aws-sdk-go /mint/build/aws-sdk-go
|
||||
RUN build/aws-sdk-go/install.sh
|
||||
|
||||
COPY build/aws-sdk-php /mint/build/aws-sdk-php
|
||||
RUN build/aws-sdk-php/install.sh
|
||||
|
||||
COPY build/aws-sdk-ruby /mint/build/aws-sdk-ruby
|
||||
RUN build/aws-sdk-ruby/install.sh
|
||||
|
||||
COPY build/mc /mint/build/mc
|
||||
RUN build/mc/install.sh
|
||||
|
||||
COPY build/minio-go /mint/build/minio-go
|
||||
RUN build/minio-go/install.sh
|
||||
|
||||
COPY build/minio-java /mint/build/minio-java
|
||||
RUN build/minio-java/install.sh
|
||||
|
||||
COPY build/minio-js /mint/build/minio-js
|
||||
RUN build/minio-js/install.sh
|
||||
|
||||
COPY build/minio-py /mint/build/minio-py
|
||||
RUN build/minio-py/install.sh
|
||||
|
||||
COPY build/s3cmd /mint/build/s3cmd
|
||||
RUN build/s3cmd/install.sh
|
||||
|
||||
COPY build/minio-dotnet/ /mint/build/minio-dotnet/
|
||||
RUN /mint/build/minio-dotnet/install.sh
|
||||
|
||||
COPY build/security /mint/build/security
|
||||
RUN build/security/install.sh
|
||||
|
||||
COPY build/worm /mint/build/worm
|
||||
RUN build/worm/install.sh
|
||||
|
||||
COPY build/healthcheck /mint/build/healthcheck
|
||||
RUN build/healthcheck/install.sh
|
||||
|
||||
COPY remove-packages.list /mint
|
||||
COPY postinstall.sh /mint
|
||||
RUN /mint/postinstall.sh
|
||||
|
||||
COPY mint.sh /mint/mint.sh
|
||||
COPY entrypoint.sh /mint/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/mint/entrypoint.sh"]
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# Mint [](https://slack.minio.io) [](https://hub.docker.com/r/minio/mint/)
|
||||
|
||||
Mint is a testing framework for Minio object server, available as a docker image. It runs correctness, benchmarking and stress tests. Following are the SDKs/tools used in correctness tests.
|
||||
|
||||
- awscli
|
||||
- aws-sdk-go
|
||||
- aws-sdk-php
|
||||
- aws-sdk-ruby
|
||||
- aws-sdk-java
|
||||
- mc
|
||||
- minio-go
|
||||
- minio-java
|
||||
- minio-js
|
||||
- minio-py
|
||||
- minio-dotnet
|
||||
- s3cmd
|
||||
- worm
|
||||
|
||||
## Running Mint
|
||||
|
||||
Mint is run by `docker run` command which requires Docker to be installed. For Docker installation follow the steps [here](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/).
|
||||
|
||||
To run Mint with Minio Play server as test target,
|
||||
|
||||
```sh
|
||||
$ docker run -e SERVER_ENDPOINT=play.minio.io:9000 -e ACCESS_KEY=Q3AM3UQ867SPQQA43P2F \
|
||||
-e SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG -e ENABLE_HTTPS=1 minio/mint
|
||||
```
|
||||
|
||||
After the tests are run, output is stored in `/mint/log` directory inside the container. To get these logs, use `docker cp` command. For example
|
||||
```sh
|
||||
docker cp <container-id>:/mint/log /tmp/logs
|
||||
```
|
||||
|
||||
### Mint environment variables
|
||||
|
||||
Below environment variables are required to be passed to the docker container. Supported environment variables:
|
||||
|
||||
| Environment variable | Description | Example |
|
||||
|:--- |:--- |:--- |
|
||||
| `SERVER_ENDPOINT` | Endpoint of Minio server in the format `HOST:PORT`; for virtual style `IP:PORT` | `play.minio.io:9000` |
|
||||
| `ACCESS_KEY` | Access key of access `SERVER_ENDPOINT` | `Q3AM3UQ867SPQQA43P2F` |
|
||||
| `SECRET_KEY` | Secret Key of access `SERVER_ENDPOINT` | `zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG` |
|
||||
| `ENABLE_HTTPS` | (Optional) Set `1` to indicate to use HTTPS to access `SERVER_ENDPOINT`. Defaults to `0` (HTTP) | `1` |
|
||||
| `MINT_MODE` | (Optional) Set mode indicating what category of tests to be run by values `core`, `full` or `worm`. Defaults to `core` | `full` |
|
||||
| `DOMAIN` | (Optional) Value of MINIO_DOMAIN environment variable used in Minio server | `myminio.com` |
|
||||
| `ENABLE_VIRTUAL_STYLE` | (Optional) Set `1` to indicate virtual style access . Defaults to `0` (Path style) | `1` |
|
||||
|
||||
|
||||
### Test virtual style access against Minio server
|
||||
|
||||
To test Minio server virtual style access with Mint, follow these steps:
|
||||
|
||||
- Set a domain in your Minio server using environment variable MINIO_DOMAIN. For example `export MINIO_DOMAIN=myminio.com`.
|
||||
- Start Minio server.
|
||||
- Execute Mint against Minio server (with `MINIO_DOMAIN` set to `myminio.com`) using this command
|
||||
```sh
|
||||
$ docker run -e "SERVER_ENDPOINT=192.168.86.133:9000" -e "DOMAIN=minio.com" \
|
||||
-e "ACCESS_KEY=minio" -e "SECRET_KEY=minio123" -e "ENABLE_HTTPS=0" \
|
||||
-e "ENABLE_VIRTUAL_STYLE=1" minio/mint
|
||||
```
|
||||
|
||||
### Mint log format
|
||||
|
||||
All test logs are stored in `/mint/log/log.json` as multiple JSON document. Below is the JSON format for every entry in the log file.
|
||||
|
||||
| JSON field | Type | Description | Example |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `name` | _string_ | Testing tool/SDK name | `"aws-sdk-php"` |
|
||||
| `function` | _string_ | Test function name | `"getBucketLocation ( array $params = [] )"` |
|
||||
| `args` | _object_ | (Optional) Key/Value map of arguments passed to test function | `{"Bucket":"aws-sdk-php-bucket-20341"}` |
|
||||
| `duration` | _int_ | Time taken in milliseconds to run the test | `384` |
|
||||
| `status` | _string_ | one of `PASS`, `FAIL` or `NA` | `"PASS"` |
|
||||
| `alert` | _string_ | (Optional) Alert message indicating test failure | `"I/O error on create file"` |
|
||||
| `message` | _string_ | (Optional) Any log message | `"validating checksum of downloaded object"` |
|
||||
| `error` | _string_ | Detailed error message including stack trace on status `FAIL` | `"Error executing \"CompleteMultipartUpload\" on ...` |
|
||||
|
||||
## For Developers
|
||||
|
||||
### Running Mint development code
|
||||
|
||||
After making changes to Mint source code a local docker image can be built/run by
|
||||
|
||||
```sh
|
||||
$ docker build -t minio/mint . -f Dockerfile.dev
|
||||
$ docker run -e SERVER_ENDPOINT=play.minio.io:9000 -e ACCESS_KEY=Q3AM3UQ867SPQQA43P2F \
|
||||
-e SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG \
|
||||
-e ENABLE_HTTPS=1 -e MINT_MODE=full minio/mint:latest
|
||||
```
|
||||
In case of Worm Mode, start your server with configuration `MINT_MODE` set to `worm`.
|
||||
Build/Run of local docker image for Worm mode, is to be tested against your server configuration, by
|
||||
|
||||
```
|
||||
### Adding tests with new tool/SDK
|
||||
|
||||
Below are the steps need to be followed
|
||||
|
||||
* Create new app directory under [build](https://github.com/minio/mint/tree/master/build) and [run/core](https://github.com/minio/mint/tree/master/run/core) directories.
|
||||
* Create `install.sh` which does installation of required tool/SDK under app directory.
|
||||
* Any build and install time dependencies should be added to [install-packages.list](https://github.com/minio/mint/blob/master/install-packages.list).
|
||||
* Build time dependencies should be added to [remove-packages.list](https://github.com/minio/mint/blob/master/remove-packages.list) for removal to have clean Mint docker image.
|
||||
* Add `run.sh` in app directory under `run/core` which execute actual tests.
|
||||
|
||||
#### Test data
|
||||
|
||||
Tests may use pre-created data set to perform various object operations on Minio server. Below data files are available under `/mint/data` directory.
|
||||
|
||||
| File name | Size |
|
||||
|:--- |:--- |
|
||||
| datafile-0-b | 0B |
|
||||
| datafile-1-b | 1B |
|
||||
| datafile-1-kB |1KiB |
|
||||
| datafile-10-kB |10KiB |
|
||||
| datafile-33-kB |33KiB |
|
||||
| datafile-100-kB |100KiB |
|
||||
| datafile-1-MB |1MiB |
|
||||
| datafile-1.03-MB |1.03MiB |
|
||||
| datafile-5-MB |5MiB |
|
||||
| datafile-6-MB |6MiB |
|
||||
| datafile-10-MB |10MiB |
|
||||
| datafile-11-MB |11MiB |
|
||||
| datafile-65-MB |65MiB |
|
||||
| datafile-129-MB |129MiB |
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/aws-sdk-go"
|
||||
GO111MODULE=on go build -o "$test_run_dir/aws-sdk-go" "$test_run_dir/quick-tests.go"
|
||||
@@ -0,0 +1,58 @@
|
||||
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="aws-sdk-java-tests" default="run">
|
||||
<property name="ivy.install.version" value="2.1.0-rc2" />
|
||||
<condition property="ivy.home" value="${env.IVY_HOME}">
|
||||
<isset property="env.IVY_HOME" />
|
||||
</condition>
|
||||
<property name="ivy.home" value="${user.home}/.ant" />
|
||||
<property name="ivy.jar.dir" value="${ivy.home}/lib" />
|
||||
<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />
|
||||
|
||||
<target name="download-ivy" unless="offline">
|
||||
<mkdir dir="${ivy.jar.dir}"/>
|
||||
<get src="https://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
|
||||
dest="${ivy.jar.file}" usetimestamp="true"/>
|
||||
</target>
|
||||
|
||||
<target name="init-ivy" depends="download-ivy">
|
||||
<path id="ivy.lib.path">
|
||||
<fileset dir="${ivy.jar.dir}" includes="*.jar"/>
|
||||
|
||||
</path>
|
||||
<taskdef resource="org/apache/ivy/ant/antlib.xml"
|
||||
uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
|
||||
</target>
|
||||
<target name="resolve" description="--> retrieve dependencies with ivy">
|
||||
<ivy:retrieve />
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="build"/>
|
||||
</target>
|
||||
|
||||
<path id="aws-s3-sdk-deps">
|
||||
<fileset dir="lib">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<target name="compile">
|
||||
<mkdir dir="build/classes"/>
|
||||
<javac srcdir="src" destdir="build/classes">
|
||||
<classpath refid="aws-s3-sdk-deps" />
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jar">
|
||||
<mkdir dir="build/jar"/>
|
||||
<jar destfile="build/jar/FunctionalTests.jar" basedir="build/classes">
|
||||
<archives>
|
||||
<zips>
|
||||
<fileset dir="lib/" includes="*.jar"/>
|
||||
</zips>
|
||||
</archives>
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="io.minio.awssdk.tests.FunctionalTests"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
</project>
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/aws-sdk-java"
|
||||
|
||||
cd "$(dirname "$(realpath "$0")")"
|
||||
|
||||
ant init-ivy && \
|
||||
ant resolve && \
|
||||
ant compile && \
|
||||
ant jar
|
||||
|
||||
cp build/jar/FunctionalTests.jar "$test_run_dir/"
|
||||
|
||||
rm -rf lib/ build/
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<ivy-module version="2.0">
|
||||
<info organisation="org.apache" module="aws-sdk-java-tests"/>
|
||||
<dependencies>
|
||||
<dependency org="com.amazonaws" name="aws-java-sdk-s3" rev="1.11.289"/>
|
||||
</dependencies>
|
||||
</ivy-module>
|
||||
@@ -0,0 +1,632 @@
|
||||
/*
|
||||
* Mint, (C) 2018 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 io.minio.awssdk.tests;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.amazonaws.auth.AWSCredentials;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
|
||||
import com.amazonaws.AmazonClientException;
|
||||
import com.amazonaws.AmazonServiceException;
|
||||
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.services.s3.AmazonS3;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.amazonaws.services.s3.model.CreateBucketRequest;
|
||||
import com.amazonaws.services.s3.model.ObjectListing;
|
||||
import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
import com.amazonaws.services.s3.model.SSECustomerKey;
|
||||
|
||||
// Main Testing class
|
||||
public class FunctionalTests {
|
||||
|
||||
private static final String PASS = "PASS";
|
||||
private static final String FAILED = "FAIL";
|
||||
private static final String IGNORED = "NA";
|
||||
|
||||
private static String accessKey;
|
||||
private static String secretKey;
|
||||
private static String region;
|
||||
private static String endpoint;
|
||||
private static boolean enableHTTPS;
|
||||
|
||||
private static final Random random = new Random(new SecureRandom().nextLong());
|
||||
private static String bucketName = getRandomName();
|
||||
private static boolean mintEnv = false;
|
||||
|
||||
private static String file1Kb;
|
||||
private static String file1Mb;
|
||||
private static String file6Mb;
|
||||
|
||||
private static SSECustomerKey sseKey1;
|
||||
private static SSECustomerKey sseKey2;
|
||||
private static SSECustomerKey sseKey3;
|
||||
|
||||
private static AmazonS3 s3Client;
|
||||
private static S3TestUtils s3TestUtils;
|
||||
|
||||
public static String getRandomName() {
|
||||
return "aws-java-sdk-test-" + new BigInteger(32, random).toString(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a success log entry in JSON format.
|
||||
*/
|
||||
public static void mintSuccessLog(String function, String args, long startTime) {
|
||||
if (mintEnv) {
|
||||
System.out.println(
|
||||
new MintLogger(function, args, System.currentTimeMillis() - startTime, PASS, null, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a failure log entry in JSON format.
|
||||
*/
|
||||
public static void mintFailedLog(String function, String args, long startTime, String message, String error) {
|
||||
if (mintEnv) {
|
||||
System.out.println(new MintLogger(function, args, System.currentTimeMillis() - startTime, FAILED, null,
|
||||
message, error));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a ignore log entry in JSON format.
|
||||
*/
|
||||
public static void mintIgnoredLog(String function, String args, long startTime) {
|
||||
if (mintEnv) {
|
||||
System.out.println(
|
||||
new MintLogger(function, args, System.currentTimeMillis() - startTime, IGNORED, null, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
public static void initTests() throws IOException {
|
||||
// Create encryption key.
|
||||
byte[] rawKey1 = "32byteslongsecretkeymustgenerate".getBytes();
|
||||
SecretKey secretKey1 = new SecretKeySpec(rawKey1, 0, rawKey1.length, "AES");
|
||||
sseKey1 = new SSECustomerKey(secretKey1);
|
||||
|
||||
// Create new encryption key for target so it is saved using sse-c
|
||||
byte[] rawKey2 = "xxbytescopysecretkeymustprovided".getBytes();
|
||||
SecretKey secretKey2 = new SecretKeySpec(rawKey2, 0, rawKey2.length, "AES");
|
||||
sseKey2 = new SSECustomerKey(secretKey2);
|
||||
|
||||
// Create new encryption key for target so it is saved using sse-c
|
||||
byte[] rawKey3 = "32byteslongsecretkeymustgenerat1".getBytes();
|
||||
SecretKey secretKey3 = new SecretKeySpec(rawKey3, 0, rawKey3.length, "AES");
|
||||
sseKey3 = new SSECustomerKey(secretKey3);
|
||||
|
||||
// Create bucket
|
||||
s3Client.createBucket(new CreateBucketRequest(bucketName));
|
||||
}
|
||||
|
||||
public static void teardown() throws IOException {
|
||||
|
||||
// Remove all objects under the test bucket & the bucket itself
|
||||
// TODO: use multi delete API instead
|
||||
ObjectListing objectListing = s3Client.listObjects(bucketName);
|
||||
while (true) {
|
||||
for (Iterator<?> iterator = objectListing.getObjectSummaries().iterator(); iterator.hasNext();) {
|
||||
S3ObjectSummary summary = (S3ObjectSummary) iterator.next();
|
||||
s3Client.deleteObject(bucketName, summary.getKey());
|
||||
}
|
||||
// more objectListing to retrieve?
|
||||
if (objectListing.isTruncated()) {
|
||||
objectListing = s3Client.listNextBatchOfObjects(objectListing);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
;
|
||||
s3Client.deleteBucket(bucketName);
|
||||
}
|
||||
|
||||
// Test regular object upload using encryption
|
||||
public static void uploadObjectEncryption_test1() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println(
|
||||
"Test: uploadObject(String bucketName, String objectName, String f, SSECustomerKey sseKey)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
String file1KbMD5 = Utils.getFileMD5(file1Kb);
|
||||
String objectName = "testobject";
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Kb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey1, file1KbMD5);
|
||||
mintSuccessLog("uploadObject(String bucketName, String objectName, String f, SSECustomerKey sseKey)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", String: " + file1Kb
|
||||
+ ", SSECustomerKey: " + sseKey1,
|
||||
startTime);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog("uploadObject(String bucketName, String objectName, String f, SSECustomerKey sseKey)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", String: " + file1Kb
|
||||
+ ", SSECustomerKey: " + sseKey1,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Test downloading an object with a wrong encryption key
|
||||
public static void downloadObjectEncryption_test1() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: downloadObject(String bucketName, String objectName, SSECustomerKey sseKey)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String file1KbMD5 = Utils.getFileMD5(file1Kb);
|
||||
String objectName = "testobject";
|
||||
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, "testobject", file1Kb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey2);
|
||||
Exception ex = new Exception("downloadObject did not throw an S3 Access denied exception");
|
||||
mintFailedLog("downloadObject(String bucketName, String objectName, SSECustomerKey sseKey)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey2,
|
||||
startTime, null, ex.toString() + " >>> " + Arrays.toString(ex.getStackTrace()));
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
if (!e.getMessage().contains("Access Denied")) {
|
||||
Exception ex = new Exception(
|
||||
"downloadObject did not throw S3 Access denied Exception but it did throw: " + e.getMessage());
|
||||
mintFailedLog("downloadObject(String bucketName, String objectName, SSECustomerKey sseKey)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey2,
|
||||
startTime, null, ex.toString() + " >>> " + Arrays.toString(ex.getStackTrace()));
|
||||
throw ex;
|
||||
}
|
||||
mintSuccessLog("downloadObject(String bucketName, String objectName, SSECustomerKey sseKey)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey2,
|
||||
startTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Test copying object with a new different encryption key
|
||||
public static void copyObjectEncryption_test1() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
String file1KbMD5 = Utils.getFileMD5(file1Kb);
|
||||
String objectName = "testobject";
|
||||
String dstObjectName = "dir/newobject";
|
||||
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Kb, sseKey1);
|
||||
s3TestUtils.copyObject(bucketName, objectName, sseKey1, bucketName, dstObjectName, sseKey2, false);
|
||||
s3TestUtils.downloadObject(bucketName, dstObjectName, sseKey2, file1KbMD5);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog("copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ "DstbucketName: " + bucketName + ", DstObjectName: " + dstObjectName
|
||||
+ ", SSECustomerKey: " + sseKey2 + ", replaceDirective: " + false,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog("copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ "DstbucketName: " + bucketName + ", DstObjectName: " + dstObjectName + ", SSECustomerKey: "
|
||||
+ sseKey2 + ", replaceDirective: " + false,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Test copying object with wrong source encryption key
|
||||
public static void copyObjectEncryption_test2() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
String objectName = "testobject";
|
||||
String dstObjectName = "dir/newobject";
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
s3TestUtils.copyObject(bucketName, objectName, sseKey3, bucketName, dstObjectName, sseKey2, false);
|
||||
Exception ex = new Exception("copyObject did not throw an S3 Access denied exception");
|
||||
mintFailedLog("copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey3
|
||||
+ "DstbucketName: " + bucketName + ", DstObjectName: " + dstObjectName
|
||||
+ ", SSECustomerKey: " + sseKey2 + ", replaceDirective: " + false,
|
||||
startTime, null, ex.toString() + " >>> " + Arrays.toString(ex.getStackTrace()));
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
if (!e.getMessage().contains("Access Denied")) {
|
||||
Exception ex = new Exception(
|
||||
"copyObject did not throw S3 Access denied Exception but it did throw: " + e.getMessage());
|
||||
mintFailedLog("copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey3
|
||||
+ "DstbucketName: " + bucketName + ", DstObjectName: " + dstObjectName
|
||||
+ ", SSECustomerKey: " + sseKey2 + ", replaceDirective: " + false,
|
||||
startTime, null, ex.toString() + " >>> " + Arrays.toString(ex.getStackTrace()));
|
||||
throw ex;
|
||||
}
|
||||
mintSuccessLog("copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey3
|
||||
+ "DstbucketName: " + bucketName + ", DstObjectName: " + dstObjectName
|
||||
+ ", SSECustomerKey: " + sseKey2 + ", replaceDirective: " + false,
|
||||
startTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Test copying multipart object
|
||||
public static void copyObjectEncryption_test3() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
String file6MbMD5 = Utils.getFileMD5(file6Mb);
|
||||
String objectName = "testobject";
|
||||
String dstObjectName = "dir/newobject";
|
||||
|
||||
try {
|
||||
s3TestUtils.uploadMultipartObject(bucketName, objectName, file6Mb, sseKey1);
|
||||
s3TestUtils.copyObject(bucketName, objectName, sseKey1, bucketName, dstObjectName, sseKey2, false);
|
||||
s3TestUtils.downloadObject(bucketName, dstObjectName, sseKey2, file6MbMD5);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog("copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ "DstbucketName: " + bucketName + ", DstObjectName: " + dstObjectName
|
||||
+ ", SSECustomerKey: " + sseKey2 + ", replaceDirective: " + false,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog("copyObject(String bucketName, String objectName, SSECustomerKey sseKey, "
|
||||
+ "String destBucketName, String dstObjectName, SSECustomerKey sseKey2, boolean replaceDirective)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ "DstbucketName: " + bucketName + ", DstObjectName: " + dstObjectName + ", SSECustomerKey: "
|
||||
+ sseKey2 + ", replaceDirective: " + false,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Test downloading encrypted object with Get Range, 0 -> 1024
|
||||
public static void downloadGetRangeEncryption_test1() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String objectName = "testobject";
|
||||
String range1MD5 = Utils.getFileMD5(file1Kb);
|
||||
int start = 0;
|
||||
int length = 1024;
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Kb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey1, range1MD5, start, length);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Test downloading encrypted object with Get Range, 0 -> 1
|
||||
public static void downloadGetRangeEncryption_test2() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String objectName = "testobject";
|
||||
int start = 0;
|
||||
int length = 1;
|
||||
String range1MD5 = Utils.getFileMD5(file1Kb, start, length);
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Kb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey1, range1MD5, start, length);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Test downloading encrypted object with Get Range, 0 -> 1024-1
|
||||
public static void downloadGetRangeEncryption_test3() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String objectName = "testobject";
|
||||
int start = 0;
|
||||
int length = 1023;
|
||||
String range1MD5 = Utils.getFileMD5(file1Kb, start, length);
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Kb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey1, range1MD5, start, length);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Test downloading encrypted object with Get Range, 1 -> 1024-1
|
||||
public static void downloadGetRangeEncryption_test4() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String objectName = "testobject";
|
||||
int start = 1;
|
||||
int length = 1023;
|
||||
String range1MD5 = Utils.getFileMD5(file1Kb, start, length);
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Kb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey1, range1MD5, start, length);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Test downloading encrypted object with Get Range, 64*1024 -> 64*1024
|
||||
public static void downloadGetRangeEncryption_test5() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String objectName = "testobject";
|
||||
int start = 64 * 1024;
|
||||
int length = 64 * 1024;
|
||||
String range1MD5 = Utils.getFileMD5(file1Mb, start, length);
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Mb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey1, range1MD5, start, length);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Test downloading encrypted object with Get Range, 64*1024 ->
|
||||
// 1024*1024-64*1024
|
||||
public static void downloadGetRangeEncryption_test6() throws Exception {
|
||||
if (!mintEnv) {
|
||||
System.out.println("Test: downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)");
|
||||
}
|
||||
|
||||
if (!enableHTTPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String objectName = "testobject";
|
||||
int start = 64 * 1024;
|
||||
int length = 1024 * 1024 - 64 * 1024;
|
||||
String range1MD5 = Utils.getFileMD5(file1Mb, start, length);
|
||||
try {
|
||||
s3TestUtils.uploadObject(bucketName, objectName, file1Mb, sseKey1);
|
||||
s3TestUtils.downloadObject(bucketName, objectName, sseKey1, range1MD5, start, length);
|
||||
} catch (Exception e) {
|
||||
mintFailedLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime, null, e.toString() + " >>> " + Arrays.toString(e.getStackTrace()));
|
||||
throw e;
|
||||
}
|
||||
mintSuccessLog(
|
||||
"downloadObjectGetRange(String bucketName, String objectName, "
|
||||
+ "SSECustomerKey sseKey, String expectedMD5, int start, int length)",
|
||||
"bucketName: " + bucketName + ", objectName: " + objectName + ", SSECustomerKey: " + sseKey1
|
||||
+ ", expectedMD5: " + range1MD5 + ", start: " + start + ", length: " + length,
|
||||
startTime);
|
||||
}
|
||||
|
||||
// Run tests
|
||||
public static void runTests() throws Exception {
|
||||
|
||||
uploadObjectEncryption_test1();
|
||||
|
||||
downloadObjectEncryption_test1();
|
||||
|
||||
copyObjectEncryption_test1();
|
||||
copyObjectEncryption_test2();
|
||||
copyObjectEncryption_test3();
|
||||
|
||||
downloadGetRangeEncryption_test1();
|
||||
downloadGetRangeEncryption_test2();
|
||||
downloadGetRangeEncryption_test3();
|
||||
downloadGetRangeEncryption_test4();
|
||||
downloadGetRangeEncryption_test5();
|
||||
downloadGetRangeEncryption_test6();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception, IOException, NoSuchAlgorithmException {
|
||||
|
||||
endpoint = System.getenv("SERVER_ENDPOINT");
|
||||
accessKey = System.getenv("ACCESS_KEY");
|
||||
secretKey = System.getenv("SECRET_KEY");
|
||||
enableHTTPS = System.getenv("ENABLE_HTTPS").equals("1");
|
||||
|
||||
region = "us-east-1";
|
||||
|
||||
if (enableHTTPS) {
|
||||
endpoint = "https://" + endpoint;
|
||||
} else {
|
||||
endpoint = "http://" + endpoint;
|
||||
}
|
||||
|
||||
String dataDir = System.getenv("MINT_DATA_DIR");
|
||||
if (dataDir != null && !dataDir.equals("")) {
|
||||
mintEnv = true;
|
||||
file1Kb = Paths.get(dataDir, "datafile-1-kB").toString();
|
||||
file1Mb = Paths.get(dataDir, "datafile-1-MB").toString();
|
||||
file6Mb = Paths.get(dataDir, "datafile-6-MB").toString();
|
||||
}
|
||||
|
||||
String mintMode = null;
|
||||
if (mintEnv) {
|
||||
mintMode = System.getenv("MINT_MODE");
|
||||
}
|
||||
|
||||
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
|
||||
AmazonS3ClientBuilder.EndpointConfiguration endpointConfiguration = new AmazonS3ClientBuilder.EndpointConfiguration(
|
||||
endpoint, region);
|
||||
|
||||
AmazonS3ClientBuilder clientBuilder = AmazonS3ClientBuilder.standard();
|
||||
clientBuilder.setCredentials(new AWSStaticCredentialsProvider(credentials));
|
||||
clientBuilder.setEndpointConfiguration(endpointConfiguration);
|
||||
clientBuilder.setPathStyleAccessEnabled(true);
|
||||
|
||||
s3Client = clientBuilder.build();
|
||||
s3TestUtils = new S3TestUtils(s3Client);
|
||||
|
||||
try {
|
||||
initTests();
|
||||
FunctionalTests.runTests();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(-1);
|
||||
} finally {
|
||||
teardown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Mint, (C) 2018 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 io.minio.awssdk.tests;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
// LimitedInputStream wraps a regular InputStream, calling
|
||||
// read() will skip some bytes as configured and will also
|
||||
// return only data with configured length
|
||||
|
||||
class LimitedInputStream extends InputStream {
|
||||
|
||||
private int skip;
|
||||
private int length;
|
||||
private InputStream is;
|
||||
|
||||
LimitedInputStream(InputStream is, int skip, int length) {
|
||||
this.is = is;
|
||||
this.skip = skip;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int r;
|
||||
while (skip > 0) {
|
||||
r = is.read();
|
||||
if (r < 0) {
|
||||
throw new IOException("stream ended before being able to skip all bytes");
|
||||
}
|
||||
skip--;
|
||||
}
|
||||
if (length == 0) {
|
||||
return -1;
|
||||
}
|
||||
r = is.read();
|
||||
if (r < 0) {
|
||||
throw new IOException("stream ended before being able to read all bytes");
|
||||
}
|
||||
length--;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Minio Java SDK for Amazon S3 Compatible Cloud Storage,
|
||||
* (C) 2015, 2016, 2017, 2018 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 io.minio.awssdk.tests;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
|
||||
public class MintLogger {
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("function")
|
||||
private String function;
|
||||
|
||||
@JsonProperty("args")
|
||||
private String args;
|
||||
|
||||
@JsonProperty("duration")
|
||||
private long duration;
|
||||
|
||||
@JsonProperty("status")
|
||||
private String status;
|
||||
|
||||
@JsonProperty("alert")
|
||||
private String alert;
|
||||
|
||||
@JsonProperty("message")
|
||||
private String message;
|
||||
|
||||
@JsonProperty("error")
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
**/
|
||||
public MintLogger(String function,
|
||||
String args,
|
||||
long duration,
|
||||
String status,
|
||||
String alert,
|
||||
String message,
|
||||
String error) {
|
||||
this.name = "aws-sdk-java";
|
||||
this.function = function;
|
||||
this.duration = duration;
|
||||
this.args = args;
|
||||
this.status = status;
|
||||
this.alert = alert;
|
||||
this.message = message;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON Log Entry.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String toString() {
|
||||
|
||||
try {
|
||||
return new ObjectMapper().setSerializationInclusion(Include.NON_NULL).writeValueAsString(this);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Alert.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String alert() {
|
||||
return alert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Error.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String error() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Message.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return args.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String args() {
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return status.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return name.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return function.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public String function() {
|
||||
return function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return duration.
|
||||
**/
|
||||
@JsonIgnore
|
||||
public long duration() {
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Mint, (C) 2018 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 io.minio.awssdk.tests;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.nio.channels.Channels;
|
||||
|
||||
import com.amazonaws.services.s3.model.GetObjectMetadataRequest;
|
||||
import com.amazonaws.services.s3.model.GetObjectRequest;
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata;
|
||||
import com.amazonaws.services.s3.model.PutObjectRequest;
|
||||
import com.amazonaws.services.s3.model.CopyObjectRequest;
|
||||
import com.amazonaws.services.s3.model.S3Object;
|
||||
import com.amazonaws.services.s3.model.S3ObjectInputStream;
|
||||
import com.amazonaws.services.s3.model.SSECustomerKey;
|
||||
|
||||
import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
|
||||
import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest;
|
||||
import com.amazonaws.services.s3.model.InitiateMultipartUploadResult;
|
||||
import com.amazonaws.services.s3.model.PartETag;
|
||||
import com.amazonaws.services.s3.model.UploadPartRequest;
|
||||
|
||||
import com.amazonaws.services.s3.model.MetadataDirective;
|
||||
|
||||
import com.amazonaws.services.s3.AmazonS3;
|
||||
|
||||
class S3TestUtils {
|
||||
|
||||
private AmazonS3 s3Client;
|
||||
|
||||
S3TestUtils(AmazonS3 s3Client) {
|
||||
this.s3Client = s3Client;
|
||||
}
|
||||
|
||||
void uploadMultipartObject(String bucketName, String keyName,
|
||||
String filePath, SSECustomerKey sseKey) throws IOException {
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
List<PartETag> partETags = new ArrayList<PartETag>();
|
||||
|
||||
// Step 1: Initialize.
|
||||
InitiateMultipartUploadRequest initRequest = new
|
||||
InitiateMultipartUploadRequest(bucketName, keyName);
|
||||
|
||||
if (sseKey != null) {
|
||||
initRequest.setSSECustomerKey(sseKey);
|
||||
}
|
||||
|
||||
InitiateMultipartUploadResult initResponse =
|
||||
s3Client.initiateMultipartUpload(initRequest);
|
||||
|
||||
long contentLength = file.length();
|
||||
long partSize = 5242880; // Set part size to 5 MB.
|
||||
|
||||
// Step 2: Upload parts.
|
||||
long filePosition = 0;
|
||||
for (int i = 1; filePosition < contentLength; i++) {
|
||||
// Last part can be less than 5 MB. Adjust part size.
|
||||
partSize = Math.min(partSize, (contentLength - filePosition));
|
||||
|
||||
// Create request to upload a part.
|
||||
UploadPartRequest uploadRequest = new UploadPartRequest()
|
||||
.withBucketName(bucketName).withKey(keyName)
|
||||
.withUploadId(initResponse.getUploadId()).withPartNumber(i)
|
||||
.withFileOffset(filePosition)
|
||||
.withFile(file)
|
||||
.withPartSize(partSize);
|
||||
|
||||
if (sseKey != null) {
|
||||
uploadRequest.withSSECustomerKey(sseKey);
|
||||
}
|
||||
|
||||
// Upload part and add response to our list.
|
||||
partETags.add(s3Client.uploadPart(uploadRequest).getPartETag());
|
||||
|
||||
filePosition += partSize;
|
||||
}
|
||||
|
||||
// Step 3: Complete.
|
||||
CompleteMultipartUploadRequest compRequest = new
|
||||
CompleteMultipartUploadRequest(
|
||||
bucketName,
|
||||
keyName,
|
||||
initResponse.getUploadId(),
|
||||
partETags);
|
||||
|
||||
s3Client.completeMultipartUpload(compRequest);
|
||||
}
|
||||
|
||||
void uploadObject(String bucketName, String keyName,
|
||||
String filePath, SSECustomerKey sseKey) throws IOException {
|
||||
|
||||
File f = new File(filePath);
|
||||
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, keyName, f);
|
||||
if (sseKey != null) {
|
||||
putObjectRequest.withSSECustomerKey(sseKey);
|
||||
}
|
||||
s3Client.putObject(putObjectRequest);
|
||||
}
|
||||
|
||||
void downloadObject(String bucketName, String keyName, SSECustomerKey sseKey)
|
||||
throws Exception, IOException {
|
||||
downloadObject(bucketName, keyName, sseKey, "", -1, -1);
|
||||
}
|
||||
|
||||
void downloadObject(String bucketName, String keyName, SSECustomerKey sseKey,
|
||||
String expectedMD5)
|
||||
throws Exception, IOException {
|
||||
downloadObject(bucketName, keyName, sseKey, expectedMD5, -1, -1);
|
||||
}
|
||||
|
||||
void downloadObject(String bucketName, String keyName, SSECustomerKey sseKey,
|
||||
String expectedMD5, int start, int length) throws Exception, IOException {
|
||||
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, keyName)
|
||||
.withSSECustomerKey(sseKey);
|
||||
|
||||
if (start >= 0 && length >= 0) {
|
||||
getObjectRequest.setRange(start, start+length-1);
|
||||
}
|
||||
|
||||
S3Object s3Object = s3Client.getObject(getObjectRequest);
|
||||
|
||||
int size = 0;
|
||||
int c;
|
||||
|
||||
S3ObjectInputStream input = s3Object.getObjectContent();
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
String data = "";
|
||||
while ((c = input.read()) != -1) {
|
||||
output.write((byte) c);
|
||||
size++;
|
||||
}
|
||||
|
||||
if (length >= 0 && size != length) {
|
||||
throw new Exception("downloaded object has unexpected size, expected: " + length + ", received: " + size);
|
||||
}
|
||||
|
||||
String calculatedMD5 = Utils.getBufferMD5(output.toByteArray());
|
||||
|
||||
if (!expectedMD5.equals("") && !calculatedMD5.equals(expectedMD5)) {
|
||||
throw new Exception("downloaded object has unexpected md5sum, expected: " + expectedMD5 + ", found: " + calculatedMD5);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void copyObject(String bucketName, String keyName, SSECustomerKey sseKey,
|
||||
String targetBucketName, String targetKeyName, SSECustomerKey newSseKey,
|
||||
boolean replace) {
|
||||
CopyObjectRequest copyRequest = new CopyObjectRequest(bucketName, keyName, targetBucketName, targetKeyName);
|
||||
if (sseKey != null) {
|
||||
copyRequest.withSourceSSECustomerKey(sseKey);
|
||||
}
|
||||
if (newSseKey != null) {
|
||||
copyRequest.withDestinationSSECustomerKey(newSseKey);
|
||||
}
|
||||
if (replace) {
|
||||
copyRequest.withMetadataDirective(MetadataDirective.COPY);
|
||||
}
|
||||
s3Client.copyObject(copyRequest);
|
||||
}
|
||||
|
||||
long retrieveObjectMetadata(String bucketName, String keyName, SSECustomerKey sseKey) {
|
||||
GetObjectMetadataRequest getMetadataRequest = new GetObjectMetadataRequest(bucketName, keyName)
|
||||
.withSSECustomerKey(sseKey);
|
||||
ObjectMetadata objectMetadata = s3Client.getObjectMetadata(getMetadataRequest);
|
||||
return objectMetadata.getContentLength();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Mint, (C) 2018 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 io.minio.awssdk.tests;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.security.*;
|
||||
|
||||
class Utils {
|
||||
|
||||
public static byte[] createChecksum(InputStream is, int skip, int length) throws Exception {
|
||||
int numRead;
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
MessageDigest complete = MessageDigest.getInstance("MD5");
|
||||
|
||||
if (skip > -1 && length > -1) {
|
||||
is = new LimitedInputStream(is, skip, length);
|
||||
}
|
||||
|
||||
do {
|
||||
numRead = is.read(buffer);
|
||||
if (numRead > 0) {
|
||||
complete.update(buffer, 0, numRead);
|
||||
}
|
||||
} while (numRead != -1);
|
||||
|
||||
return complete.digest();
|
||||
}
|
||||
|
||||
public static String getInputStreamMD5(InputStream is) throws Exception {
|
||||
return getInputStreamMD5(is, -1, -1);
|
||||
}
|
||||
|
||||
public static String getInputStreamMD5(InputStream is, int start, int length) throws Exception {
|
||||
byte[] b = createChecksum(is, start, length);
|
||||
String result = "";
|
||||
|
||||
for (int i=0; i < b.length; i++) {
|
||||
result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String getFileMD5(String filePath) throws Exception {
|
||||
return getFileMD5(filePath, -1, -1);
|
||||
}
|
||||
|
||||
public static String getFileMD5(String filePath, int start, int length) throws Exception {
|
||||
File f = new File(filePath);
|
||||
InputStream is = new FileInputStream(f);
|
||||
return getInputStreamMD5(is, start, length);
|
||||
}
|
||||
|
||||
public static String getBufferMD5(byte[] data) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(data);
|
||||
return getInputStreamMD5(bis);
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/aws-sdk-php"
|
||||
$WGET --output-document=- https://getcomposer.org/installer | php -- --install-dir="$test_run_dir"
|
||||
php "$test_run_dir/composer.phar" --working-dir="$test_run_dir" install
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
gem install --no-rdoc --no-ri aws-sdk multipart_body
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
AWS_CLI_VERSION="1.11.112"
|
||||
|
||||
python -m pip install awscli==$AWS_CLI_VERSION
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2019 Minio, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/healthcheck"
|
||||
GO111MODULE=on go build -o "$test_run_dir/healthcheck" "$test_run_dir/healthcheck.go"
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
MC_VERSION=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/minio/mc/releases/latest | sed "s/https:\/\/github.com\/minio\/mc\/releases\/tag\///")
|
||||
if [ -z "$MC_VERSION" ]; then
|
||||
echo "unable to get mc version from github"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/mc"
|
||||
$WGET --output-document="${test_run_dir}/mc" "https://dl.minio.io/client/mc/release/linux-amd64/mc.${MC_VERSION}"
|
||||
chmod a+x "${test_run_dir}/mc"
|
||||
|
||||
git clone --quiet https://github.com/minio/mc.git "$test_run_dir/mc.git"
|
||||
(cd "$test_run_dir/mc.git"; git checkout --quiet "tags/${MC_VERSION}")
|
||||
cp -a "${test_run_dir}/mc.git/functional-tests.sh" "$test_run_dir/"
|
||||
rm -fr "$test_run_dir/mc.git"
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Mint (C) 2017 Minio, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
MINIO_DOTNET_SDK_PATH="$MINT_RUN_CORE_DIR/minio-dotnet"
|
||||
|
||||
MINIO_DOTNET_SDK_VERSION=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/minio/minio-dotnet/releases/latest | sed "s/https:\/\/github.com\/minio\/minio-dotnet\/releases\/tag\///")
|
||||
if [ -z "$MINIO_DOTNET_SDK_VERSION" ]; then
|
||||
echo "unable to get minio-dotnet version from github"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
out_dir="$MINIO_DOTNET_SDK_PATH/out"
|
||||
if [ -z "$out_dir" ]; then
|
||||
mkdir "$out_dir"
|
||||
fi
|
||||
|
||||
temp_dir="$MINIO_DOTNET_SDK_PATH/temp"
|
||||
git clone --quiet https://github.com/minio/minio-dotnet.git "${temp_dir}/minio-dotnet.git/"
|
||||
(cd "${temp_dir}/minio-dotnet.git"; git checkout --quiet "tags/${MINIO_DOTNET_SDK_VERSION}")
|
||||
|
||||
cp -a "${temp_dir}/minio-dotnet.git/Minio.Functional.Tests/"* "${MINIO_DOTNET_SDK_PATH}/"
|
||||
rm -fr "${temp_dir}"
|
||||
|
||||
cd "$MINIO_DOTNET_SDK_PATH"
|
||||
dotnet restore /p:Configuration=Mint
|
||||
dotnet publish --runtime ubuntu.16.04-x64 --output out /p:Configuration=Mint
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
MINIO_GO_VERSION=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/minio/minio-go/releases/latest | sed "s/https:\/\/github.com\/minio\/minio-go\/releases\/tag\///")
|
||||
if [ -z "$MINIO_GO_VERSION" ]; then
|
||||
echo "unable to get minio-go version from github"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/minio-go"
|
||||
(git clone https://github.com/minio/minio-go && cd minio-go && git checkout --quiet "tags/$MINIO_GO_VERSION")
|
||||
GO111MODULE=on CGO_ENABLED=0 go build -o "$test_run_dir/minio-go" "minio-go/functional_tests.go"
|
||||
rm -rf minio-go
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
MINIO_JAVA_VERSION=$(curl --retry 10 -s "http://repo1.maven.org/maven2/io/minio/minio/maven-metadata.xml" | sed -n "/<latest>/{s/<.[^>]*>//g;p;q}" | sed "s/ *//g")
|
||||
if [ -z "$MINIO_JAVA_VERSION" ]; then
|
||||
echo "unable to get latest minio-java version from maven"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/minio-java"
|
||||
git clone --quiet https://github.com/minio/minio-java.git "$test_run_dir/minio-java.git"
|
||||
(cd "$test_run_dir/minio-java.git"; git checkout --quiet "tags/${MINIO_JAVA_VERSION}")
|
||||
$WGET --output-document="$test_run_dir/minio-${MINIO_JAVA_VERSION}-all.jar" "http://repo1.maven.org/maven2/io/minio/minio/${MINIO_JAVA_VERSION}/minio-${MINIO_JAVA_VERSION}-all.jar"
|
||||
javac -cp "$test_run_dir/minio-${MINIO_JAVA_VERSION}-all.jar" "${test_run_dir}/minio-java.git/functional"/*.java
|
||||
cp -a "${test_run_dir}/minio-java.git/functional"/*.class "$test_run_dir/"
|
||||
rm -fr "$test_run_dir/minio-java.git"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
MINIO_JS_VERSION=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/minio/minio-js/releases/latest | sed "s/https:\/\/github.com\/minio\/minio-js\/releases\/tag\///")
|
||||
if [ -z "$MINIO_JS_VERSION" ]; then
|
||||
echo "unable to get minio-js version from github"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/minio-js"
|
||||
mkdir "${test_run_dir}/test"
|
||||
$WGET --output-document="${test_run_dir}/test/functional-tests.js" "https://raw.githubusercontent.com/minio/minio-js/${MINIO_JS_VERSION}/src/test/functional/functional-tests.js"
|
||||
npm --prefix "$test_run_dir" install --save "minio@$MINIO_JS_VERSION"
|
||||
npm --prefix "$test_run_dir" install
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
MINIO_PY_VERSION=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/minio/minio-py/releases/latest | sed "s/https:\/\/github.com\/minio\/minio-py\/releases\/tag\///")
|
||||
if [ -z "$MINIO_PY_VERSION" ]; then
|
||||
echo "unable to get minio-py version from github"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/minio-py"
|
||||
# FIX https://github.com/pypa/pip/issues/5221
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --user faker
|
||||
python -m pip install minio=="$MINIO_PY_VERSION"
|
||||
$WGET --output-document="$test_run_dir/tests.py" "https://raw.githubusercontent.com/minio/minio-py/${MINIO_PY_VERSION}/tests/functional/tests.py"
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
# Always install the latest.
|
||||
python -m pip install s3cmd
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2018 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.
|
||||
#
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/security"
|
||||
GO111MODULE=on go build -o "$test_run_dir/tls-tests" "$test_run_dir/tls-tests.go"
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
test_run_dir="$MINT_RUN_CORE_DIR/worm"
|
||||
GO111MODULE=on CGO_ENABLED=0 go build -o "$test_run_dir/worm" "$test_run_dir/quick-worm-tests.go"
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
MINT_DATA_DIR="$MINT_ROOT_DIR/data"
|
||||
|
||||
declare -A data_file_map
|
||||
data_file_map["datafile-0-b"]="0"
|
||||
data_file_map["datafile-1-b"]="1"
|
||||
data_file_map["datafile-1-kB"]="1K"
|
||||
data_file_map["datafile-10-kB"]="10K"
|
||||
data_file_map["datafile-33-kB"]="33K"
|
||||
data_file_map["datafile-100-kB"]="100K"
|
||||
data_file_map["datafile-1.03-MB"]="1056K"
|
||||
data_file_map["datafile-1-MB"]="1M"
|
||||
data_file_map["datafile-5-MB"]="5M"
|
||||
data_file_map["datafile-5243880-b"]="5243880"
|
||||
data_file_map["datafile-6-MB"]="6M"
|
||||
data_file_map["datafile-10-MB"]="10M"
|
||||
data_file_map["datafile-11-MB"]="11M"
|
||||
data_file_map["datafile-65-MB"]="65M"
|
||||
data_file_map["datafile-129-MB"]="129M"
|
||||
|
||||
mkdir -p "$MINT_DATA_DIR"
|
||||
for filename in "${!data_file_map[@]}"; do
|
||||
echo "creating $MINT_DATA_DIR/$filename"
|
||||
if ! shred -n 1 -s "${data_file_map[$filename]}" - 1>"$MINT_DATA_DIR/$filename" 2>/dev/null; then
|
||||
echo "unable to create data file $MINT_DATA_DIR/$filename"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Mint (C) 2017 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.
|
||||
#
|
||||
|
||||
./mint.sh "$@" &
|
||||
|
||||
# Get the pid to be used for kill command if required
|
||||
main_pid="$!"
|
||||
trap 'echo -e "\nAborting Mint..."; kill $main_pid' SIGINT SIGTERM
|
||||
# use -n here to catch mint.sh exit code, notify to ci
|
||||
wait -n
|
||||
@@ -0,0 +1,17 @@
|
||||
git
|
||||
python3-pip
|
||||
nodejs
|
||||
ruby
|
||||
ruby-dev
|
||||
ruby-bundler
|
||||
php
|
||||
php7.0-curl
|
||||
php-xml
|
||||
default-jre
|
||||
default-jdk
|
||||
ant
|
||||
dirmngr
|
||||
dotnet-sdk-2.1
|
||||
ca-certificates-mono
|
||||
nuget
|
||||
libunwind8
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user