Compare commits

..

23 Commits

Author SHA1 Message Date
Harshavardhana c22439c82e Update minio-go v6.0.29 (#7778)
Bring improved retry logic
2019-06-12 18:09:21 -07:00
Harshavardhana 38224a4c1a Ignore errors reading fs.json (#7777) 2019-06-12 16:42:03 -07:00
Harshavardhana b4ab778cb2 Fix user IAM policy regression, reload policy appropriately (#7770)
Introduce in commit 7e4c9a9e1e

Fixes #7769
2019-06-12 14:49:45 -07:00
Harshavardhana 22f6756ce6 Allow su-exec to fail when users explicity use --user (#7776)
This allows MinIO containers to run properly without
expecting higher privileges in situations where following
restrictions on containers are used

 - docker run --user uid:gid
 - docker-compose up (with docker-compose.yml with user)
 ```yml
 ...
 user: "1001:1001"
 command: minio server /data
 ...
 ```
 - All openshift containers

Fixes #7773
2019-06-12 12:16:21 -07:00
Krishna Srinivas 0394a8f013 Send Content-Length in the response headers (#7771)
curl using http1.0 would hang sometimes when Content-Length is missing in response headers
fixes #7661
2019-06-11 21:04:52 -07:00
Harshavardhana 1039311184 Update UI assets (#7764) 2019-06-11 15:31:10 -07:00
Harshavardhana a075015293 doc: Merge large bucket with distributed docs (#7761) 2019-06-11 13:44:33 -07:00
Nitish Tiwari d90d4841b8 Add su-exec download for changing to non-root user (#7768)
Fixes #7767
2019-06-11 09:32:16 -07:00
Minio Trusted d3a2efbf91 Update yaml files to latest version RELEASE.2019-06-11T00-44-33Z 2019-06-11 00:50:31 +00:00
Harshavardhana 002a205c9c Fix OPA result response handling (#7763)
Also update the document with updated rego policy
and updated OPA agent REST API.

This PR is to fix a regression caused by PR #7637
2019-06-10 17:06:32 -07:00
Harshavardhana 91ceae23d0 Add support for customizable user (#7569) 2019-06-10 20:27:42 +05:30
kannappanr 1008c2c069 Do not display error logs if user does not have listbuckets privilege (#7370)
Fixes #7367
2019-06-09 13:15:57 -07:00
Anis Elleuch 7abadfccc2 Add self-healing feature (#7604)
- Background Heal routine receives heal requests from a channel, either to
heal format, buckets or objects
- Daily sweeper lists all objects in all buckets, these objects
don't necessarly have read quorum so they can be removed if
these objects are unhealable
- Heal daily ops receives objects from the daily sweeper
and send them to the heal routine.
2019-06-08 22:14:07 -07:00
poornas 97090aa16c Add admin API to send trace notifications to registered (#7128)
Remove current functionality to log trace to file
using MINIO_HTTP_TRACE env, and replace it with
mc admin trace command on mc client.
2019-06-08 15:54:41 -07:00
dependabot[bot] fb531235de Bump nwmatcher from 1.4.3 to 1.4.4 in /browser (#7760)
Bumps [nwmatcher](https://github.com/dperini/nwmatcher) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/dperini/nwmatcher/releases)
- [Commits](https://github.com/dperini/nwmatcher/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-07 23:41:41 -07:00
Harshavardhana cb1566c6e6 S3 Gateway: Handle restricted access credentials (#7757) 2019-06-07 15:49:13 -07:00
Harshavardhana 6d89435356 Reload a specific user or policy on peers (#7705)
Fixes #7587
2019-06-06 17:46:22 -07:00
Andreas Auernhammer 975237cbf8 Create SECURITY.md (#7692) 2019-06-06 16:56:43 -07:00
dependabot[bot] 501af06001 Bump handlebars from 4.0.11 to 4.1.2 in /browser (#7755)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.11 to 4.1.2.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.11...v4.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-06 11:16:09 -07:00
Harshavardhana a69f74533c Add region as part of error XML (#7752) 2019-06-05 16:28:21 -07:00
Joe Stevens a19cf063b5 Fixes for multiplatform dev and testing from forks (#7734)
Add support for correct dependency URLs on all platforms

only build mountinfo.go on linux

make testfile path relative to support fork work
2019-06-04 00:59:40 -07:00
Waldemar Quevedo 8b0c86298d Update import for NATS clients (#7743)
Signed-off-by: Waldemar Quevedo <wally@synadia.com>
2019-06-04 11:16:07 +05:30
Minio Trusted df19546cc8 Update yaml files to latest version RELEASE.2019-06-04T01-15-58Z 2019-06-04 01:22:32 +00:00
86 changed files with 16706 additions and 924 deletions
+1
View File
@@ -24,3 +24,4 @@ stage/
.sia_temp/
config.json
healthcheck
check-user
+4 -2
View File
@@ -10,7 +10,8 @@ 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 -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.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
FROM alpine:3.9
@@ -22,10 +23,11 @@ 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 \
apk add --no-cache ca-certificates 'curl>7.61.0' && \
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
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
+4 -3
View File
@@ -2,7 +2,7 @@ FROM alpine:3.9
LABEL maintainer="MinIO Inc <dev@min.io>"
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck /usr/bin/
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck dockerscripts/check-user /usr/bin/
COPY minio /usr/bin/
ENV MINIO_UPDATE off
@@ -10,11 +10,12 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key
RUN \
apk add --no-cache ca-certificates 'curl>7.61.0' && \
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/healthcheck && \
chmod +x /usr/bin/check-user
EXPOSE 9000
+6 -3
View File
@@ -7,13 +7,15 @@ ENV GO111MODULE on
RUN \
apk add --no-cache git && \
git clone https://github.com/minio/minio && cd minio/dockerscripts && \
go build -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
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
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
@@ -21,12 +23,13 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key
RUN \
apk add --no-cache ca-certificates 'curl>7.61.0' && \
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 && \
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/healthcheck && \
chmod +x /usr/bin/check-user
EXPOSE 9000
+9 -2
View File
@@ -2,6 +2,12 @@ PWD := $(shell pwd)
GOPATH := $(shell go env GOPATH)
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
GOOS := $(shell go env GOOS)
GOOSALT ?= 'linux'
ifeq ($(GOOS),'darwin')
GOOSALT = 'mac'
endif
TAG ?= $(USER)
BUILD_LDFLAGS := '$(LDFLAGS)'
@@ -14,8 +20,8 @@ checks:
getdeps:
@mkdir -p ${GOPATH}/bin
@which golint 1>/dev/null || (echo "Installing golint" && go get -u golang.org/x/lint/golint)
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && wget --quiet -O ${GOPATH}/bin/staticcheck https://github.com/dominikh/go-tools/releases/download/2019.1/staticcheck_linux_amd64 && chmod +x ${GOPATH}/bin/staticcheck)
@which misspell 1>/dev/null || (echo "Installing misspell" && wget --quiet https://github.com/client9/misspell/releases/download/v0.3.4/misspell_0.3.4_linux_64bit.tar.gz && tar xf misspell_0.3.4_linux_64bit.tar.gz && mv misspell ${GOPATH}/bin/misspell && chmod +x ${GOPATH}/bin/misspell && rm -f misspell_0.3.4_linux_64bit.tar.gz)
@which staticcheck 1>/dev/null || (echo "Installing staticcheck" && wget --quiet -O ${GOPATH}/bin/staticcheck https://github.com/dominikh/go-tools/releases/download/2019.1/staticcheck_${GOOS}_amd64 && chmod +x ${GOPATH}/bin/staticcheck)
@which misspell 1>/dev/null || (echo "Installing misspell" && wget --quiet https://github.com/client9/misspell/releases/download/v0.3.4/misspell_0.3.4_${GOOSALT}_64bit.tar.gz && tar xf misspell_0.3.4_${GOOSALT}_64bit.tar.gz && mv misspell ${GOPATH}/bin/misspell && chmod +x ${GOPATH}/bin/misspell && rm -f misspell_0.3.4_${GOOSALT}_64bit.tar.gz)
crosscompile:
@(env bash $(PWD)/buildscripts/cross-compile.sh)
@@ -67,6 +73,7 @@ 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
+41
View File
@@ -0,0 +1,41 @@
# Security Policy
## Supported Versions
We always provide security updates for the [latest release](https://github.com/minio/minio/releases/latest).
Whenever there is a security update you just need to upgrade to the latest version.
## Reporting a Vulnerability
All security bugs in [minio/minio](https://github,com/minio/minio) (or other minio/* repositories)
should be reported by email to security@min.io. Your email will be acknowledged within 48 hours,
and you'll receive a more detailed response to your email within 72 hours indicating the next steps
in handling your report.
Please, provide a detailed explanation of the issue. In particular, outline the type of the security
issue (DoS, authentication bypass, information disclouse, ...) and the assumptions you're making (e.g. do
you need access credentials for a successful exploit).
If you have not received a reply to your email within 48 hours or you have not heard from the security team
for the past five days please contact the security team directly:
- Primary security coordinator: aead@min.io
- Secondary coordinator: harsha@min.io
- If you receive no response: dev@min.io
### Disclosure Process
MinIO uses the following disclosure process:
1. Once the security report is received one member of the security team tries to verify and reproduce
the issue and determines the impact it has.
2. A member of the security team will respond and either confrim or reject the security report.
If the report is rejected the repsonse explains why.
3. Code is audited to find any potential similar problems.
4. Fixes are prepared for the latest release.
5. On the date that the fixes are applied a security advisory will be published on https://blog.min.io.
Please inform us in your report email whether MinIO should mention your contribution w.r.t. fixing
the security issue. By default MinIO will **not** publish this information to protect your privacy.
This process can take some time, especially when coordination is required with maintainers of other projects.
Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we
follow the process described above to ensure that disclosures are handled consistently.
+9 -8
View File
@@ -4,10 +4,11 @@
## Installation
### Install yarn
### Install node
```sh
curl -o- -L https://yarnpkg.com/install.sh | bash
yarn
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
exec -l $SHELL
nvm install stable
```
### Install `go-bindata` and `go-bindata-assetfs`
@@ -15,8 +16,8 @@ yarn
If you do not have a working Golang environment, please follow [Install Golang](https://docs.min.io/docs/how-to-install-golang)
```sh
go get github.com/jteeuwen/go-bindata/...
go get github.com/elazarl/go-bindata-assetfs/...
go get github.com/go-bindata/go-bindata/go-bindata
go get github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs
```
## Generating Assets
@@ -24,7 +25,7 @@ go get github.com/elazarl/go-bindata-assetfs/...
### Generate ui-assets.go
```sh
yarn release
npm run release
```
This generates ui-assets.go in the current directory. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
@@ -32,7 +33,7 @@ This generates ui-assets.go in the current directory. Now do `make` in the paren
### Run MinIO Browser with live reload
```sh
yarn dev
npm run dev
```
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application
@@ -66,7 +67,7 @@ index 3ccdaba..9496c56 100644
```
```sh
yarn dev
npm run dev
```
Open [http://localhost:8888/minio/](http://localhost:8888/minio/) in your browser to play with the application
+14259
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -29,8 +29,8 @@
"homepage": "https://github.com/minio/minio",
"devDependencies": {
"async": "^1.5.2",
"babel-cli": "^6.14.0",
"babel-core": "^6.14.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-jest": "^22.1.0",
"babel-loader": "^7.1.2",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
@@ -38,10 +38,10 @@
"babel-polyfill": "^6.23.0",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
"babel-register": "^6.14.0",
"babel-register": "^6.26.0",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^0.23.1",
"enzyme": "^3.3.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.1.1",
"esformatter": "^0.10.0",
"esformatter-jsx": "^7.4.1",
@@ -73,7 +73,7 @@
"material-design-iconic-font": "^2.2.0",
"mime-db": "^1.25.0",
"mime-types": "^2.1.13",
"moment": "^2.15.1",
"moment": "^2.24.0",
"react": "^16.2.0",
"react-addons-test-utils": "^0.14.8",
"react-bootstrap": "^0.32.0",
+52 -46
View File
File diff suppressed because one or more lines are too long
+36 -98
View File
@@ -251,22 +251,10 @@ ajv@^6.1.0:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
dependencies:
kind-of "^3.0.2"
longest "^1.0.1"
repeat-string "^1.5.2"
alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
ansi-colors@^3.0.0:
version "3.2.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
@@ -467,7 +455,7 @@ async-limiter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
async@^1.4.0, async@^1.5.2:
async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -1509,10 +1497,6 @@ camel-case@3.0.x:
no-case "^2.2.0"
upper-case "^1.1.1"
camelcase@^1.0.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -1540,13 +1524,6 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
center-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
dependencies:
align-text "^0.1.3"
lazy-cache "^1.0.3"
chain-function@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
@@ -1695,14 +1672,6 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
cliui@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
dependencies:
center-align "^0.1.1"
right-align "^0.1.1"
wordwrap "0.0.2"
cliui@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
@@ -1808,6 +1777,11 @@ commander@^2.2.0, commander@^2.8.1, commander@^2.9.0:
dependencies:
graceful-readlink ">= 1.0.0"
commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -2207,7 +2181,7 @@ debug@^4.1.0:
dependencies:
ms "^2.1.1"
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -3578,14 +3552,15 @@ handle-thing@^2.0.0:
integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
handlebars@^4.0.3:
version "4.0.11"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
version "4.1.2"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
dependencies:
async "^1.4.0"
neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.4.4"
source-map "^0.6.1"
optionalDependencies:
uglify-js "^2.6"
uglify-js "^3.1.4"
har-schema@^1.0.5:
version "1.0.5"
@@ -4953,10 +4928,6 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
lazy-cache@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
@@ -5189,10 +5160,6 @@ loglevel@^1.4.1:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
@@ -5454,7 +5421,7 @@ minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimist@0.0.8, minimist@~0.0.1:
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
@@ -5469,6 +5436,11 @@ minimist@^0.1.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
integrity sha1-md9lelJXTCHJBXSX33QnkLK0wN4=
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
minipass@^2.2.1, minipass@^2.3.4:
version "2.3.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
@@ -5630,10 +5602,10 @@ negotiator@0.6.1:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
neo-async@^2.5.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
neo-async@^2.5.0, neo-async@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
nice-try@^1.0.4:
version "1.0.5"
@@ -5861,8 +5833,9 @@ number-is-nan@^1.0.0:
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
nwmatcher@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c"
version "1.4.4"
resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e"
integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==
oauth-sign@~0.8.1, oauth-sign@~0.8.2:
version "0.8.2"
@@ -6019,6 +5992,7 @@ opn@^5.1.0:
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
dependencies:
minimist "~0.0.1"
wordwrap "~0.0.2"
@@ -7400,12 +7374,6 @@ ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
dependencies:
align-text "^0.1.1"
rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@@ -7807,17 +7775,7 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
dependencies:
amdefine ">=0.0.4"
source-map@^0.5.0, source-map@^0.5.3:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6:
source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -7825,6 +7783,7 @@ source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6:
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
spdx-correct@^3.0.0:
version "3.1.0"
@@ -8408,18 +8367,13 @@ uglify-js@3.4.x:
commander "~2.17.1"
source-map "~0.6.1"
uglify-js@^2.6:
version "2.8.29"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
uglify-js@^3.1.4:
version "3.6.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
dependencies:
source-map "~0.5.1"
yargs "~3.10.0"
optionalDependencies:
uglify-to-browserify "~1.0.0"
uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
commander "~2.20.0"
source-map "~0.6.1"
uid-number@^0.0.6:
version "0.0.6"
@@ -8855,17 +8809,10 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2"
window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
wordwrap@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
wordwrap@~1.0.0:
version "1.0.0"
@@ -9019,12 +8966,3 @@ yargs@~1.2.6:
integrity sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=
dependencies:
minimist "^0.1.0"
yargs@~3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
dependencies:
camelcase "^1.0.2"
cliui "^2.1.0"
decamelize "^1.0.0"
window-size "0.1.0"
+56 -15
View File
@@ -940,8 +940,12 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
accessKey := vars["accessKey"]
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
// Notify all other MinIO peers to delete user.
for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
@@ -1006,8 +1010,8 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
return
}
// Notify all other MinIO peers to reload users
for _, nerr := range globalNotificationSys.LoadUsers() {
// Notify all other MinIO peers to reload user.
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
@@ -1065,8 +1069,8 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
return
}
// Notify all other MinIO peers to reload users
for _, nerr := range globalNotificationSys.LoadUsers() {
// Notify all other Minio peers to reload user
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
@@ -1083,7 +1087,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
return
}
policies, err := globalIAMSys.ListCannedPolicies()
policies, err := globalIAMSys.ListPolicies()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -1115,13 +1119,13 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
return
}
if err := globalIAMSys.DeleteCannedPolicy(policyName); err != nil {
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload users
for _, nerr := range globalNotificationSys.LoadUsers() {
// Notify all other MinIO peers to delete policy
for _, nerr := range globalNotificationSys.DeletePolicy(policyName) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
@@ -1171,13 +1175,13 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
return
}
if err = globalIAMSys.SetCannedPolicy(policyName, *iamPolicy); err != nil {
if err = globalIAMSys.SetPolicy(policyName, *iamPolicy); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload users
for _, nerr := range globalNotificationSys.LoadUsers() {
// Notify all other MinIO peers to reload policy
for _, nerr := range globalNotificationSys.LoadPolicy(policyName) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
@@ -1214,8 +1218,8 @@ func (a adminAPIHandlers) SetUserPolicy(w http.ResponseWriter, r *http.Request)
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
}
// Notify all other MinIO peers to reload users
for _, nerr := range globalNotificationSys.LoadUsers() {
// Notify all other Minio peers to reload user
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
@@ -1415,3 +1419,40 @@ func (a adminAPIHandlers) SetConfigKeysHandler(w http.ResponseWriter, r *http.Re
// Send success response
writeSuccessResponseHeadersOnly(w)
}
// TraceHandler - POST /minio/admin/v1/trace
// ----------
// The handler sends http trace to the connected HTTP client.
func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "HTTPTrace")
trcAll := r.URL.Query().Get("all") == "true"
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
// 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("Connection", "close")
doneCh := make(chan struct{})
defer close(doneCh)
traceCh := globalTrace.Trace(doneCh, trcAll)
for {
select {
case entry := <-traceCh:
if _, err := w.Write(entry); err != nil {
return
}
if _, err := w.Write([]byte("\n")); err != nil {
return
}
w.(http.Flusher).Flush()
case <-r.Context().Done():
return
case <-GlobalServiceDoneCh:
return
}
}
}
+3 -1
View File
@@ -268,7 +268,9 @@ func prepareAdminXLTestBed() (*adminXLTestBed, error) {
initNSLock(isDistXL)
// Init global heal state
initAllHealState(globalIsXL)
if globalIsXL {
globalAllHealState = initHealState()
}
globalConfigSys = NewConfigSys()
+121 -99
View File
@@ -96,22 +96,15 @@ type allHealState struct {
healSeqMap map[string]*healSequence
}
var (
// global server heal state
globalAllHealState allHealState
)
// initAllHealState - initialize healing apparatus
func initAllHealState(isErasureMode bool) {
if !isErasureMode {
return
}
globalAllHealState = allHealState{
// initHealState - initialize healing apparatus
func initHealState() *allHealState {
healState := &allHealState{
healSeqMap: make(map[string]*healSequence),
}
go globalAllHealState.periodicHealSeqsClean()
go healState.periodicHealSeqsClean()
return healState
}
func (ahs *allHealState) periodicHealSeqsClean() {
@@ -305,6 +298,14 @@ type healSequence struct {
// path is just pathJoin(bucket, objPrefix)
path string
// List of entities (format, buckets, objects) to heal
sourceCh chan string
// Report healing progress, false if this is a background
// healing since currently there is no entity which will
// receive realtime healing status
reportProgress bool
// time at which heal sequence was started
startTime time.Time
@@ -348,14 +349,15 @@ func newHealSequence(bucket, objPrefix, clientAddr string,
ctx := logger.SetReqInfo(context.Background(), reqInfo)
return &healSequence{
bucket: bucket,
objPrefix: objPrefix,
path: pathJoin(bucket, objPrefix),
startTime: UTCNow(),
clientToken: mustGetUUID(),
clientAddress: clientAddr,
forceStarted: forceStart,
settings: hs,
bucket: bucket,
objPrefix: objPrefix,
path: pathJoin(bucket, objPrefix),
reportProgress: true,
startTime: UTCNow(),
clientToken: mustGetUUID(),
clientAddress: clientAddr,
forceStarted: forceStart,
settings: hs,
currentStatus: healSequenceStatus{
Summary: healNotStartedStatus,
HealSettings: hs,
@@ -484,7 +486,11 @@ func (h *healSequence) healSequenceStart() {
h.currentStatus.StartTime = UTCNow()
h.currentStatus.updateLock.Unlock()
go h.traverseAndHeal()
if h.sourceCh == nil {
go h.traverseAndHeal()
} else {
go h.healFromSourceCh()
}
select {
case err, ok := <-h.traverseAndHealDoneCh:
@@ -519,6 +525,89 @@ func (h *healSequence) healSequenceStart() {
}
}
func (h *healSequence) queueHealTask(path string, healType madmin.HealItemType) error {
var respCh = make(chan healResult)
defer close(respCh)
// Send heal request
globalBackgroundHealing.queueHealTask(healTask{path: path, responseCh: respCh, opts: h.settings})
// Wait for answer and push result to the client
res := <-respCh
if !h.reportProgress {
return nil
}
res.result.Type = healType
if res.err != nil {
// Object might have been deleted, by the time heal
// was attempted, we should ignore this object and return success.
if isErrObjectNotFound(res.err) {
return nil
}
// Only report object error
if healType != madmin.HealItemObject {
return res.err
}
res.result.Detail = res.err.Error()
}
return h.pushHealResultItem(res.result)
}
func (h *healSequence) healItemsFromSourceCh() error {
// Start healing the config prefix.
if err := h.healMinioSysMeta(minioConfigPrefix)(); err != nil {
return err
}
// Start healing the bucket config prefix.
if err := h.healMinioSysMeta(bucketConfigPrefix)(); err != nil {
return err
}
for path := range h.sourceCh {
var itemType madmin.HealItemType
switch {
case path == "/":
itemType = madmin.HealItemMetadata
case !strings.Contains(path, "/"):
itemType = madmin.HealItemBucket
default:
itemType = madmin.HealItemObject
}
if err := h.queueHealTask(path, itemType); err != nil {
return err
}
}
return nil
}
func (h *healSequence) healFromSourceCh() {
if err := h.healItemsFromSourceCh(); err != nil {
h.traverseAndHealDoneCh <- err
}
close(h.traverseAndHealDoneCh)
}
func (h *healSequence) healItems() error {
// Start with format healing
if err := h.healDiskFormat(); err != nil {
return err
}
// Start healing the config prefix.
if err := h.healMinioSysMeta(minioConfigPrefix)(); err != nil {
return err
}
// Start healing the bucket config prefix.
if err := h.healMinioSysMeta(bucketConfigPrefix)(); err != nil {
return err
}
// Heal buckets and objects
return h.healBuckets()
}
// traverseAndHeal - traverses on-disk data and performs healing
// according to settings. At each "safe" point it also checks if an
// external quit signal has been received and quits if so. Since the
@@ -527,31 +616,10 @@ func (h *healSequence) healSequenceStart() {
// has to wait until a safe point is reached, such as between scanning
// two objects.
func (h *healSequence) traverseAndHeal() {
var err error
checkErr := func(f func() error) {
switch {
case err != nil:
return
case h.isQuitting():
if err := h.healItems(); err != nil {
if h.isQuitting() {
err = errHealStopSignalled
return
}
err = f()
}
// Start with format healing
checkErr(h.healDiskFormat)
// Start healing the config prefix.
checkErr(h.healMinioSysMeta(minioConfigPrefix))
// Start healing the bucket config prefix.
checkErr(h.healMinioSysMeta(bucketConfigPrefix))
// Heal buckets and objects
checkErr(h.healBuckets)
if err != nil {
h.traverseAndHealDoneCh <- err
}
@@ -575,17 +643,14 @@ func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
if h.isQuitting() {
return errHealStopSignalled
}
res, herr := objectAPI.HealObject(h.ctx, bucket, object, h.settings.DryRun, h.settings.Remove, h.settings.ScanMode)
herr := h.queueHealTask(pathJoin(bucket, object), madmin.HealItemBucketMetadata)
// Object might have been deleted, by the time heal
// was attempted we ignore this object an move on.
if isErrObjectNotFound(herr) {
return nil
}
if herr != nil {
return herr
}
res.Type = madmin.HealItemBucketMetadata
return h.pushHealResultItem(res)
return herr
})
}
}
@@ -603,26 +668,7 @@ func (h *healSequence) healDiskFormat() error {
return errServerNotInitialized
}
res, err := objectAPI.HealFormat(h.ctx, h.settings.DryRun)
// return any error, ignore error returned when disks have
// already healed.
if err != nil && err != errNoHealRequired {
return errFnHealFromAPIErr(h.ctx, err)
}
// Healing succeeded notify the peers to reload format and re-initialize disks.
// We will not notify peers only if healing succeeded.
if err == nil {
for _, nerr := range globalNotificationSys.ReloadFormat(h.settings.DryRun) {
if nerr.Err != nil {
logger.GetReqInfo(h.ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(h.ctx, nerr.Err)
}
}
}
// Push format heal result
return h.pushHealResultItem(res)
return h.queueHealTask("/", madmin.HealItemMetadata)
}
// healBuckets - check for all buckets heal or just particular bucket.
@@ -664,13 +710,7 @@ func (h *healSequence) healBucket(bucket string) error {
return errServerNotInitialized
}
result, err := objectAPI.HealBucket(h.ctx, bucket, h.settings.DryRun, h.settings.Remove)
// handle heal-bucket error
if err != nil {
return err
}
if err = h.pushHealResultItem(result); err != nil {
if err := h.queueHealTask(bucket, madmin.HealItemBucket); err != nil {
return err
}
@@ -678,7 +718,7 @@ func (h *healSequence) healBucket(bucket string) error {
if h.objPrefix != "" {
// Check if an object named as the objPrefix exists,
// and if so heal it.
_, err = objectAPI.GetObjectInfo(h.ctx, bucket, h.objPrefix, ObjectOptions{})
_, err := objectAPI.GetObjectInfo(h.ctx, bucket, h.objPrefix, ObjectOptions{})
if err == nil {
if err = h.healObject(bucket, h.objPrefix); err != nil {
return err
@@ -689,8 +729,7 @@ func (h *healSequence) healBucket(bucket string) error {
return nil
}
if err = objectAPI.HealObjects(h.ctx, bucket,
h.objPrefix, h.healObject); err != nil {
if err := objectAPI.HealObjects(h.ctx, bucket, h.objPrefix, h.healObject); err != nil {
return errFnHealFromAPIErr(h.ctx, err)
}
return nil
@@ -702,28 +741,11 @@ func (h *healSequence) healObject(bucket, object string) error {
return errHealStopSignalled
}
if globalHTTPServer != nil {
// Wait at max 1 minute for an inprogress request
// before proceeding to heal
waitCount := 60
// Any requests in progress, delay the heal.
for globalHTTPServer.GetRequestCount() > 2 && waitCount > 0 {
waitCount--
time.Sleep(1 * time.Second)
}
}
// Get current object layer instance.
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
}
hri, err := objectAPI.HealObject(h.ctx, bucket, object, h.settings.DryRun, h.settings.Remove, h.settings.ScanMode)
if isErrObjectNotFound(err) {
return nil
}
if err != nil {
hri.Detail = err.Error()
}
return h.pushHealResultItem(hri)
return h.queueHealTask(pathJoin(bucket, object), madmin.HealItemObject)
}
+2
View File
@@ -116,6 +116,8 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
// Top locks
adminV1Router.Methods(http.MethodGet).Path("/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
// HTTP Trace
adminV1Router.Methods(http.MethodGet).Path("/trace").HandlerFunc(adminAPI.TraceHandler)
// If none of the routes match, return error.
adminV1Router.NotFoundHandler = http.HandlerFunc(httpTraceHdrs(notFoundHandlerJSON))
}
+2
View File
@@ -51,6 +51,7 @@ type APIErrorResponse struct {
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
Resource string
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
RequestID string `xml:"RequestId" json:"RequestId"`
HostID string `xml:"HostId" json:"HostId"`
}
@@ -1735,6 +1736,7 @@ func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID,
BucketName: reqInfo.BucketName,
Key: reqInfo.ObjectName,
Resource: resource,
Region: globalServerConfig.GetRegion(),
RequestID: requestID,
HostID: hostID,
}
+2
View File
@@ -22,6 +22,7 @@ import (
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
@@ -523,6 +524,7 @@ func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType
if mType != mimeNone {
w.Header().Set("Content-Type", string(mType))
}
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.WriteHeader(statusCode)
if response != nil {
w.Write(response)
+160
View File
@@ -0,0 +1,160 @@
/*
* 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"
"time"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/madmin"
)
// healTask represents what to heal along with options
// path: '/' => Heal disk formats along with metadata
// path: 'bucket/' or '/bucket/' => Heal bucket
// path: 'bucket/object' => Heal object
type healTask struct {
path string
opts madmin.HealOpts
// Healing response will be sent here
responseCh chan healResult
}
// healResult represents a healing result with a possible error
type healResult struct {
result madmin.HealResultItem
err error
}
// healRoutine receives heal tasks, to heal buckets, objects and format.json
type healRoutine struct {
tasks chan healTask
doneCh chan struct{}
}
// Add a new task in the tasks queue
func (h *healRoutine) queueHealTask(task healTask) {
h.tasks <- task
}
// Wait for heal requests and process them
func (h *healRoutine) run() {
ctx := context.Background()
for {
select {
case task, ok := <-h.tasks:
if !ok {
break
}
if globalHTTPServer != nil {
// Wait at max 1 minute for an inprogress request
// before proceeding to heal
waitCount := 60
// Any requests in progress, delay the heal.
for globalHTTPServer.GetRequestCount() > 2 && waitCount > 0 {
waitCount--
time.Sleep(1 * time.Second)
}
}
var res madmin.HealResultItem
var err error
bucket, object := urlPath2BucketObjectName(task.path)
switch {
case bucket == "" && object == "":
res, err = bgHealDiskFormat(ctx, task.opts)
case bucket != "" && object == "":
res, err = bgHealBucket(ctx, bucket, task.opts)
case bucket != "" && object != "":
res, err = bgHealObject(ctx, bucket, object, task.opts)
}
task.responseCh <- healResult{result: res, err: err}
case <-h.doneCh:
return
case <-GlobalServiceDoneCh:
return
}
}
}
func initHealRoutine() *healRoutine {
return &healRoutine{
tasks: make(chan healTask),
doneCh: make(chan struct{}),
}
}
func initBackgroundHealing() {
healBg := initHealRoutine()
go healBg.run()
globalBackgroundHealing = healBg
}
// bgHealDiskFormat - heals format.json, return value indicates if a
// failure error occurred.
func bgHealDiskFormat(ctx context.Context, opts madmin.HealOpts) (madmin.HealResultItem, error) {
// Get current object layer instance.
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return madmin.HealResultItem{}, errServerNotInitialized
}
res, err := objectAPI.HealFormat(ctx, opts.DryRun)
// return any error, ignore error returned when disks have
// already healed.
if err != nil && err != errNoHealRequired {
return madmin.HealResultItem{}, err
}
// Healing succeeded notify the peers to reload format and re-initialize disks.
// We will not notify peers if healing is not required.
if err == nil {
for _, nerr := range globalNotificationSys.ReloadFormat(opts.DryRun) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
return res, nil
}
// bghealBucket - traverses and heals given bucket
func bgHealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
// Get current object layer instance.
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return madmin.HealResultItem{}, errServerNotInitialized
}
return objectAPI.HealBucket(ctx, bucket, opts.DryRun, opts.Remove)
}
// bgHealObject - heal the given object and record result
func bgHealObject(ctx context.Context, bucket, object string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
// Get current object layer instance.
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return madmin.HealResultItem{}, errServerNotInitialized
}
return objectAPI.HealObject(ctx, bucket, object, opts.DryRun, opts.Remove, opts.ScanMode)
}
-7
View File
@@ -211,13 +211,6 @@ func handleCommonEnvVars() {
globalIsBrowserEnabled = bool(browserFlag)
}
traceFile := os.Getenv("MINIO_HTTP_TRACE")
if traceFile != "" {
var err error
globalHTTPTraceFile, err = os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
logger.FatalIf(err, "error opening file %s", traceFile)
}
etcdEndpointsEnv, ok := os.LookupEnv("MINIO_ETCD_ENDPOINTS")
if ok {
etcdEndpoints := strings.Split(etcdEndpointsEnv, ",")
+89
View File
@@ -0,0 +1,89 @@
/*
* 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"
"sync"
"time"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/madmin"
)
const (
bgHealingUUID = "0000-0000-0000-0000"
)
// NewBgHealSequence creates a background healing sequence
// operation which crawls all objects and heal them.
func newBgHealSequence(numDisks int) *healSequence {
reqInfo := &logger.ReqInfo{API: "BackgroundHeal"}
ctx := logger.SetReqInfo(context.Background(), reqInfo)
hs := madmin.HealOpts{
// Remove objects that do not have read-quorum
Remove: true,
ScanMode: madmin.HealDeepScan,
}
return &healSequence{
sourceCh: make(chan string),
startTime: UTCNow(),
clientToken: bgHealingUUID,
settings: hs,
currentStatus: healSequenceStatus{
Summary: healNotStartedStatus,
HealSettings: hs,
NumDisks: numDisks,
updateLock: &sync.RWMutex{},
},
traverseAndHealDoneCh: make(chan error),
stopSignalCh: make(chan struct{}),
ctx: ctx,
reportProgress: false,
}
}
func initDailyHeal() {
go startDailyHeal()
}
func startDailyHeal() {
var objAPI ObjectLayer
var ctx = context.Background()
// Wait until the object API is ready
for {
objAPI = newObjectLayerFn()
if objAPI == nil {
time.Sleep(time.Second)
continue
}
break
}
// Find number of disks in the setup
info := objAPI.StorageInfo(ctx)
numDisks := info.Backend.OnlineDisks + info.Backend.OfflineDisks
nh := newBgHealSequence(numDisks)
globalSweepHealState.LaunchNewHealSequence(nh)
registerDailySweepListener(nh.sourceCh)
}
+145
View File
@@ -0,0 +1,145 @@
/*
* 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"
"sync"
"time"
"github.com/minio/minio/cmd/logger"
)
// The list of modules listening for the daily listing of all objects
// such as the daily heal ops, disk usage and bucket lifecycle management.
var globalDailySweepListeners = make([]chan string, 0)
var globalDailySweepListenersMu = sync.Mutex{}
// Add a new listener to the daily objects listing
func registerDailySweepListener(ch chan string) {
globalDailySweepListenersMu.Lock()
defer globalDailySweepListenersMu.Unlock()
globalDailySweepListeners = append(globalDailySweepListeners, ch)
}
// Safe copy of globalDailySweepListeners content
func copyDailySweepListeners() []chan string {
globalDailySweepListenersMu.Lock()
defer globalDailySweepListenersMu.Unlock()
var listenersCopy = make([]chan string, len(globalDailySweepListeners))
copy(listenersCopy, globalDailySweepListeners)
return listenersCopy
}
// sweepRound will list all objects, having read quorum or not and
// feeds to all listeners, such as the background healing
func sweepRound(ctx context.Context, objAPI ObjectLayer) error {
zeroDuration := time.Millisecond
zeroDynamicTimeout := newDynamicTimeout(zeroDuration, zeroDuration)
// General lock so we avoid parallel daily sweep by different instances.
sweepLock := globalNSMutex.NewNSLock("system", "daily-sweep")
if err := sweepLock.GetLock(zeroDynamicTimeout); err != nil {
return err
}
defer sweepLock.Unlock()
buckets, err := objAPI.ListBuckets(ctx)
if err != nil {
return err
}
// List all objects, having read quorum or not in all buckets
// and send them to all the registered sweep listeners
for _, bucket := range buckets {
// Send bucket names to all listeners
for _, l := range copyDailySweepListeners() {
l <- bucket.Name
}
marker := ""
for {
res, err := objAPI.ListObjectsHeal(ctx, bucket.Name, "", marker, "", 1000)
if err != nil {
continue
}
for _, obj := range res.Objects {
for _, l := range copyDailySweepListeners() {
l <- pathJoin(bucket.Name, obj.Name)
}
}
if !res.IsTruncated {
break
} else {
marker = res.NextMarker
}
}
}
return nil
}
// initDailySweeper creates a go-routine which will list all
// objects in all buckets in a daily basis
func initDailySweeper() {
go dailySweeper()
}
// List all objects in all buckets in a daily basis
func dailySweeper() {
var lastSweepTime time.Time
var objAPI ObjectLayer
var ctx = context.Background()
// Wait until the object layer is ready
for {
objAPI = newObjectLayerFn()
if objAPI == nil {
time.Sleep(time.Second)
continue
}
break
}
// Perform a sweep round each 24 hours
for {
if time.Since(lastSweepTime) < 24*time.Hour {
time.Sleep(time.Hour)
continue
}
err := sweepRound(ctx, objAPI)
if err != nil {
switch err.(type) {
// Unable to hold a lock means there is another
// instance doing the sweep round
case OperationTimedOut:
lastSweepTime = time.Now()
default:
logger.LogIf(ctx, err)
time.Sleep(time.Minute)
continue
}
} else {
lastSweepTime = time.Now()
}
}
}
+2 -2
View File
@@ -435,7 +435,7 @@ func (c cacheObjects) listCacheObjects(ctx context.Context, bucket, prefix, mark
if delimiter == slashSeparator {
recursive = false
}
walkResultCh, endWalkCh := c.listPool.Release(listParams{bucket, recursive, marker, prefix})
walkResultCh, endWalkCh := c.listPool.Release(listParams{bucket, recursive, marker, prefix, false})
if walkResultCh == nil {
endWalkCh = make(chan struct{})
@@ -494,7 +494,7 @@ func (c cacheObjects) listCacheObjects(ctx context.Context, bucket, prefix, mark
}
}
params := listParams{bucket, recursive, nextMarker, prefix}
params := listParams{bucket, recursive, nextMarker, prefix, false}
if !eof {
c.listPool.Set(params, walkResultCh, endWalkCh)
}
+5 -5
View File
@@ -213,12 +213,12 @@ func parseEndpointSet(args ...string) (ep endpointSet, err error) {
return ep, nil
}
// Parses all ellipses input arguments, expands them into corresponding
// list of endpoints chunked evenly in accordance with a specific
// set size.
// GetAllSets - parses all ellipses input arguments, expands them into
// corresponding list of endpoints chunked evenly in accordance with a
// specific set size.
// For example: {1...64} is divided into 4 sets each of size 16.
// This applies to even distributed setup syntax as well.
func getAllSets(args ...string) ([][]string, error) {
func GetAllSets(args ...string) ([][]string, error) {
if len(args) == 0 {
return nil, errInvalidArgument
}
@@ -266,7 +266,7 @@ func getAllSets(args ...string) ([][]string, error) {
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
// both ellipses and without ellipses transparently.
func createServerEndpoints(serverAddr string, args ...string) (string, EndpointList, SetupType, int, int, error) {
setArgs, err := getAllSets(args...)
setArgs, err := GetAllSets(args...)
if err != nil {
return serverAddr, nil, -1, 0, 0, err
}
+10 -6
View File
@@ -431,8 +431,9 @@ func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBu
// Save objects' metadata in `fs.json`.
fsMeta := newFSMetaV1()
if _, err = fsMeta.ReadFrom(ctx, wlk); err != nil && err != io.EOF {
return oi, toObjectErr(err, srcBucket, srcObject)
if _, err = fsMeta.ReadFrom(ctx, wlk); err != nil {
// For any error to read fsMeta, set default ETag and proceed.
fsMeta = fs.defaultFsJSON(srcObject)
}
fsMeta.Meta = srcInfo.UserDefined
@@ -710,10 +711,7 @@ func (fs *FSObjects) getObjectInfo(ctx context.Context, bucket, object string) (
_, rerr := fsMeta.ReadFrom(ctx, rlk.LockedFile)
fs.rwPool.Close(fsMetaPath)
if rerr != nil {
if rerr != io.EOF {
return oi, rerr
}
// Set Default ETag, if fs.json is empty
// For any error to read fsMeta, set default ETag and proceed.
fsMeta = fs.defaultFsJSON(object)
}
}
@@ -1151,6 +1149,12 @@ func (fs *FSObjects) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error)
return []BucketInfo{}, NotImplemented{}
}
// ListObjectsHeal - list all objects to be healed. Valid only for XL
func (fs *FSObjects) ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) {
logger.LogIf(ctx, NotImplemented{})
return ListObjectsInfo{}, NotImplemented{}
}
// SetBucketPolicy sets policy on bucket
func (fs *FSObjects) SetBucketPolicy(ctx context.Context, bucket string, policy *policy.Policy) error {
return savePolicyConfig(ctx, fs, bucket, policy)
+8 -5
View File
@@ -41,14 +41,14 @@ var (
gatewayCmd = cli.Command{
Name: "gateway",
Usage: "start object storage gateway",
Flags: append(serverFlags, globalFlags...),
Flags: append(ServerFlags, GlobalFlags...),
HideHelpCommand: true,
}
)
// RegisterGatewayCommand registers a new command for gateway.
func RegisterGatewayCommand(cmd cli.Command) error {
cmd.Flags = append(append(cmd.Flags, append(cmd.Flags, serverFlags...)...), globalFlags...)
cmd.Flags = append(append(cmd.Flags, append(cmd.Flags, ServerFlags...)...), GlobalFlags...)
gatewayCmd.Subcommands = append(gatewayCmd.Subcommands, cmd)
return nil
}
@@ -148,6 +148,9 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
initNSLock(false) // Enable local namespace lock.
// Set when gateway is enabled
globalIsGateway = true
router := mux.NewRouter().SkipClean(true)
if globalEtcdClient != nil {
@@ -155,6 +158,9 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
registerSTSRouter(router)
}
// initialize globalTrace system
globalTrace = NewTraceSys(context.Background(), globalEndpoints)
enableConfigOps := globalEtcdClient != nil && gatewayName == "nas"
enableIAMOps := globalEtcdClient != nil
@@ -304,9 +310,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
printGatewayStartupMessage(getAPIEndpoints(), gatewayName)
}
// Set when gateway is enabled
globalIsGateway = true
// Set uptime time after object layer has initialized.
globalBootTime = UTCNow()
+5
View File
@@ -101,6 +101,11 @@ func (a GatewayUnsupported) ListBucketsHeal(ctx context.Context) (buckets []Buck
return nil, NotImplemented{}
}
// ListObjectsHeal - Not implemented stub
func (a GatewayUnsupported) ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) {
return ListObjectsInfo{}, NotImplemented{}
}
// HealObject - Not implemented stub
func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool, scanMode madmin.HealScanMode) (h madmin.HealResultItem, e error) {
return h, NotImplemented{}
-5
View File
@@ -395,10 +395,6 @@ func (l *gcsGateway) CleanupGCSMinioSysTmp(ctx context.Context) {
for {
attrs, err := it.Next()
if err != nil {
if err != iterator.Done {
ctx := logger.SetReqInfo(context.Background(), &logger.ReqInfo{})
logger.LogIf(ctx, err)
}
break
}
l.CleanupGCSMinioSysTmpBucket(ctx, attrs.Name)
@@ -461,7 +457,6 @@ func (l *gcsGateway) ListBuckets(ctx context.Context) (buckets []minio.BucketInf
}
if ierr != nil {
logger.LogIf(ctx, ierr)
return buckets, gcsToObjectError(ierr)
}
+32 -12
View File
@@ -169,14 +169,6 @@ func randString(n int, src rand.Source, prefix string) string {
return prefix + string(b[0:30-len(prefix)])
}
func isAmazonS3Endpoint(urlStr string) bool {
u, err := url.Parse(urlStr)
if err != nil {
panic(err)
}
return s3utils.IsAmazonEndpoint(*u)
}
// Chains all credential types, in the following order:
// - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
// - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
@@ -210,6 +202,11 @@ func newS3(urlStr string) (*miniogo.Core, error) {
urlStr = "https://s3.amazonaws.com"
}
u, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
// Override default params if the host is provided
endpoint, secure, err := minio.ParseGatewayEndpoint(urlStr)
if err != nil {
@@ -217,15 +214,23 @@ func newS3(urlStr string) (*miniogo.Core, error) {
}
var creds *credentials.Credentials
if isAmazonS3Endpoint(urlStr) {
if s3utils.IsAmazonEndpoint(*u) {
// If we see an Amazon S3 endpoint, then we use more ways to fetch backend credentials.
// Specifically IAM style rotating credentials are only supported with AWS S3 endpoint.
creds = credentials.NewChainCredentials(defaultAWSCredProviders)
} else {
creds = credentials.NewChainCredentials(defaultProviders)
}
clnt, err := miniogo.NewWithCredentials(endpoint, creds, secure, "")
options := miniogo.Options{
Creds: creds,
Secure: secure,
Region: s3utils.GetRegionFromURL(*u),
BucketLookup: miniogo.BucketLookupAuto,
}
clnt, err := miniogo.NewWithOptions(endpoint, &options)
if err != nil {
return nil, err
}
@@ -234,9 +239,12 @@ func newS3(urlStr string) (*miniogo.Core, error) {
clnt.SetCustomTransport(minio.NewCustomHTTPTransport())
probeBucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "probe-bucket-sign-")
// Check if the provided keys are valid.
if _, err = clnt.BucketExists(probeBucketName); err != nil {
return nil, err
if miniogo.ToErrorResponse(err).Code != "AccessDenied" {
return nil, err
}
}
return &miniogo.Core{Client: clnt}, nil
@@ -313,7 +321,19 @@ func (l *s3Objects) MakeBucketWithLocation(ctx context.Context, bucket, location
func (l *s3Objects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.BucketInfo, e error) {
buckets, err := l.Client.ListBuckets()
if err != nil {
return bi, minio.ErrorRespToObjectError(err, bucket)
// Listbuckets may be disallowed, proceed to check if
// bucket indeed exists, if yes return success.
var ok bool
if ok, err = l.Client.BucketExists(bucket); err != nil {
return bi, minio.ErrorRespToObjectError(err, bucket)
}
if !ok {
return bi, minio.BucketNotFound{Bucket: bucket}
}
return minio.BucketInfo{
Name: bi.Name,
Created: time.Now().UTC(),
}, nil
}
for _, bi := range buckets {
+8 -2
View File
@@ -159,8 +159,9 @@ var (
globalHTTPServerErrorCh = make(chan error)
globalOSSignalCh = make(chan os.Signal, 1)
// File to log HTTP request/response headers and body.
globalHTTPTraceFile *os.File
// global Trace system to send HTTP request/response logs to
// registered listeners
globalTrace *HTTPTraceSys
globalEndpoints EndpointList
@@ -260,6 +261,11 @@ var (
// GlobalGatewaySSE sse options
GlobalGatewaySSE gatewaySSE
// The always present healing routine ready to heal objects
globalBackgroundHealing *healRoutine
globalAllHealState *allHealState
globalSweepHealState *allHealState
// Add new variable global values here.
)
+14 -7
View File
@@ -30,7 +30,6 @@ import (
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/handlers"
httptracer "github.com/minio/minio/pkg/handlers"
)
// Parses location constraint from the incoming reader.
@@ -326,18 +325,26 @@ func extractPostPolicyFormValues(ctx context.Context, form *multipart.Form) (fil
// Log headers and body.
func httpTraceAll(f http.HandlerFunc) http.HandlerFunc {
if globalHTTPTraceFile == nil {
return f
return func(w http.ResponseWriter, r *http.Request) {
if !globalTrace.HasTraceListeners() {
f.ServeHTTP(w, r)
return
}
trace := Trace(f, true, w, r)
globalTrace.Publish(trace)
}
return httptracer.TraceReqHandlerFunc(f, globalHTTPTraceFile, true)
}
// Log only the headers.
func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc {
if globalHTTPTraceFile == nil {
return f
return func(w http.ResponseWriter, r *http.Request) {
if !globalTrace.HasTraceListeners() {
f.ServeHTTP(w, r)
return
}
trace := Trace(f, false, w, r)
globalTrace.Publish(trace)
}
return httptracer.TraceReqHandlerFunc(f, globalHTTPTraceFile, false)
}
// Returns "/bucketName/objectName" for path-style or virtual-host-style requests.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package handlers
package cmd
import (
"bytes"
@@ -24,8 +24,12 @@ import (
"net/http"
"reflect"
"runtime"
"strconv"
"strings"
"time"
xnet "github.com/minio/minio/pkg/net"
trace "github.com/minio/minio/pkg/trace"
)
// recordRequest - records the first recLen bytes
@@ -109,92 +113,64 @@ func (r *recordResponseWriter) Flush() {
r.ResponseWriter.(http.Flusher).Flush()
}
// Return response headers.
func (r *recordResponseWriter) Headers() []byte {
return r.headers.Bytes()
}
// Return response body.
func (r *recordResponseWriter) Body() []byte {
return r.body.Bytes()
}
// TraceReqHandlerFunc logs request/response headers and body.
func TraceReqHandlerFunc(f http.HandlerFunc, output io.Writer, logBody bool) http.HandlerFunc {
// Trace gets trace of http request
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info {
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
name = strings.TrimSuffix(name, "Handler-fm")
bodyPlaceHolder := []byte("<BODY>")
var reqBodyRecorder *recordRequest
return func(w http.ResponseWriter, r *http.Request) {
const timeFormat = "2006-01-02 15:04:05 -0700"
var reqBodyRecorder *recordRequest
t := trace.Info{FuncName: name}
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody}
r.Body = ioutil.NopCloser(reqBodyRecorder)
// Generate short random request ID
reqID := fmt.Sprintf("%f", float64(time.Now().UnixNano())/1e10)
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody}
r.Body = ioutil.NopCloser(reqBodyRecorder)
// Setup a http response body recorder
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody}
b := bytes.NewBuffer(nil)
fmt.Fprintf(b, "[REQUEST %s] [%s] [%s]\n", name, reqID, time.Now().Format(timeFormat))
f(respBodyRecorder, r)
// Build request log and write it to log file
fmt.Fprintf(b, "%s %s", r.Method, r.URL.Path)
if r.URL.RawQuery != "" {
fmt.Fprintf(b, "?%s", r.URL.RawQuery)
}
fmt.Fprintf(b, "\n")
fmt.Fprintf(b, "Host: %s\n", r.Host)
for k, v := range r.Header {
fmt.Fprintf(b, "%s: %s\n", k, v[0])
}
fmt.Fprintf(b, "\n")
if logBody {
bodyContents := reqBodyRecorder.Data()
if bodyContents != nil {
// If body logging is disabled then we print <BODY> as a placeholder
// for the actual body.
b.Write(bodyContents)
fmt.Fprintf(b, "\n")
}
} else {
b.Write(bodyPlaceHolder)
fmt.Fprintf(b, "\n")
}
fmt.Fprintf(b, "\n")
// Build response log and write it to log file
fmt.Fprintf(b, "[RESPONSE] [%s] [%s]\n", reqID, time.Now().Format(timeFormat))
b.Write(respBodyRecorder.Headers())
fmt.Fprintf(b, "\n")
// recordResponseWriter{} is configured to record only
// responses with http code != 200 & != 206, we don't
// have to check for logBody value here.
bodyContents := respBodyRecorder.Body()
if bodyContents != nil {
b.Write(bodyContents)
fmt.Fprintf(b, "\n")
} else {
if !logBody {
// If there was no error response and body logging is disabled
// then we print <BODY> as a placeholder for the actual body.
b.Write(bodyPlaceHolder)
fmt.Fprintf(b, "\n")
}
}
fmt.Fprintf(b, "\n")
// Write the contents in one shot so that logs don't get interspersed.
output.Write(b.Bytes())
host, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
if err == nil {
t.NodeName = host.Name
}
rq := trace.RequestInfo{Time: time.Now().UTC(), Method: r.Method, Path: r.URL.Path, RawQuery: r.URL.RawQuery}
rq.Headers = cloneHeader(r.Header)
rq.Headers.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
rq.Headers.Set("Host", r.Host)
for _, enc := range r.TransferEncoding {
rq.Headers.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
}
// 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
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
}
+115
View File
@@ -0,0 +1,115 @@
/*
* 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"
"context"
"encoding/json"
"strings"
"sync"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/pubsub"
"github.com/minio/minio/pkg/trace"
)
//HTTPTraceSys holds global trace state
type HTTPTraceSys struct {
peers []*peerRESTClient
pubsub *pubsub.PubSub
}
// NewTraceSys - creates new HTTPTraceSys with all nodes subscribed to
// the trace pub sub system
func NewTraceSys(ctx context.Context, endpoints EndpointList) *HTTPTraceSys {
remoteHosts := getRemoteHosts(endpoints)
remoteClients, err := getRestClients(remoteHosts)
if err != nil {
logger.FatalIf(err, "Unable to start httptrace sub system")
}
ps := pubsub.New()
return &HTTPTraceSys{
remoteClients, ps,
}
}
// HasTraceListeners returns true if trace listeners are registered
// for this node or peers
func (sys *HTTPTraceSys) HasTraceListeners() bool {
return sys != nil && sys.pubsub.HasSubscribers()
}
// Publish - publishes trace message to the http trace pubsub system
func (sys *HTTPTraceSys) Publish(traceMsg trace.Info) {
sys.pubsub.Publish(traceMsg)
}
// Trace writes http trace to writer
func (sys *HTTPTraceSys) Trace(doneCh chan struct{}, trcAll bool) chan []byte {
traceCh := make(chan []byte)
go func() {
defer close(traceCh)
var wg = &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
buf := &bytes.Buffer{}
ch := sys.pubsub.Subscribe()
defer sys.pubsub.Unsubscribe(ch)
for {
select {
case entry := <-ch:
trcInfo := entry.(trace.Info)
path := strings.TrimPrefix(trcInfo.ReqInfo.Path, "/")
// omit inter-node traffic if trcAll is false
if !trcAll && strings.HasPrefix(path, minioReservedBucket) {
continue
}
buf.Reset()
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(trcInfo); err != nil {
continue
}
traceCh <- buf.Bytes()
case <-doneCh:
return
}
}
}()
for _, peer := range sys.peers {
wg.Add(1)
go func(peer *peerRESTClient) {
defer wg.Done()
ch, err := peer.Trace(doneCh, trcAll)
if err != nil {
return
}
for entry := range ch {
traceCh <- entry
}
}(peer)
}
wg.Wait()
}()
return traceCh
}
+299 -168
View File
@@ -25,6 +25,7 @@ import (
"time"
etcd "github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb"
"github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
@@ -60,6 +61,45 @@ type IAMSys struct {
iamCannedPolicyMap map[string]iampolicy.Policy
}
// LoadPolicy - reloads a specific canned policy from backend disks or etcd.
func (sys *IAMSys) LoadPolicy(objAPI ObjectLayer, policyName string) error {
if objAPI == nil {
return errInvalidArgument
}
sys.Lock()
defer sys.Unlock()
prefix := iamConfigPoliciesPrefix
if globalEtcdClient == nil {
return reloadPolicy(context.Background(), objAPI, prefix, policyName, sys.iamCannedPolicyMap)
}
// When etcd is set, we use watch APIs so this code is not needed.
return nil
}
// LoadUser - reloads a specific user from backend disks or etcd.
func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, temp bool) error {
if objAPI == nil {
return errInvalidArgument
}
sys.Lock()
defer sys.Unlock()
prefix := iamConfigUsersPrefix
if temp {
prefix = iamConfigSTSPrefix
}
if globalEtcdClient == nil {
return reloadUser(context.Background(), objAPI, prefix, accessKey, sys.iamUsersMap, sys.iamPolicyMap)
}
// When etcd is set, we use watch APIs so this code is not needed.
return nil
}
// Load - loads iam subsystem
func (sys *IAMSys) Load(objAPI ObjectLayer) error {
if globalEtcdClient != nil {
@@ -68,6 +108,105 @@ func (sys *IAMSys) Load(objAPI ObjectLayer) error {
return sys.refresh(objAPI)
}
func (sys *IAMSys) reloadFromEvent(event *etcd.Event) {
eventCreate := event.IsModify() || event.IsCreate()
eventDelete := event.Type == etcd.EventTypeDelete
usersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigUsersPrefix)
stsPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigSTSPrefix)
policyPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPoliciesPrefix)
ctx, cancel := context.WithTimeout(context.Background(),
defaultContextTimeout)
defer cancel()
switch {
case eventCreate:
switch {
case usersPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigUsersPrefix))
reloadEtcdUser(ctx, iamConfigUsersPrefix, accessKey,
sys.iamUsersMap, sys.iamPolicyMap)
case stsPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigSTSPrefix))
reloadEtcdUser(ctx, iamConfigSTSPrefix, accessKey,
sys.iamUsersMap, sys.iamPolicyMap)
case policyPrefix:
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigPoliciesPrefix))
reloadEtcdPolicy(ctx, iamConfigPoliciesPrefix,
policyName, sys.iamCannedPolicyMap)
}
case eventDelete:
switch {
case usersPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigUsersPrefix))
delete(sys.iamUsersMap, accessKey)
delete(sys.iamPolicyMap, accessKey)
case stsPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigSTSPrefix))
delete(sys.iamUsersMap, accessKey)
delete(sys.iamPolicyMap, accessKey)
case policyPrefix:
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigPoliciesPrefix))
delete(sys.iamCannedPolicyMap, policyName)
}
}
}
// Watch etcd entries for IAM
func (sys *IAMSys) watchIAMEtcd() {
watchEtcd := func() {
// Refresh IAMSys with etcd watch.
for {
watchCh := globalEtcdClient.Watch(context.Background(),
iamConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
select {
case <-GlobalServiceDoneCh:
return
case watchResp, ok := <-watchCh:
if !ok {
time.Sleep(1 * time.Second)
continue
}
if err := watchResp.Err(); err != nil {
logger.LogIf(context.Background(), err)
// log and retry.
time.Sleep(1 * time.Second)
continue
}
for _, event := range watchResp.Events {
sys.Lock()
sys.reloadFromEvent(event)
sys.Unlock()
}
}
}
}
go watchEtcd()
}
func (sys *IAMSys) watchIAMDisk(objAPI ObjectLayer) {
watchDisk := func() {
ticker := time.NewTicker(globalRefreshIAMInterval)
defer ticker.Stop()
for {
select {
case <-GlobalServiceDoneCh:
return
case <-ticker.C:
sys.refresh(objAPI)
}
}
}
// Refresh IAMSys in background.
go watchDisk()
}
// Init - initializes config system from iam.json
func (sys *IAMSys) Init(objAPI ObjectLayer) error {
if objAPI == nil {
@@ -75,51 +214,9 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
}
if globalEtcdClient != nil {
defer func() {
go func() {
// Refresh IAMSys with etcd watch.
for {
watchCh := globalEtcdClient.Watch(context.Background(),
iamConfigPrefix, etcd.WithPrefix())
select {
case <-GlobalServiceDoneCh:
return
case watchResp, ok := <-watchCh:
if !ok {
time.Sleep(1 * time.Second)
continue
}
if err := watchResp.Err(); err != nil {
logger.LogIf(context.Background(), err)
// log and retry.
time.Sleep(1 * time.Second)
continue
}
for _, event := range watchResp.Events {
if event.IsModify() || event.IsCreate() || event.Type == etcd.EventTypeDelete {
sys.refreshEtcd()
}
}
}
}
}()
}()
defer sys.watchIAMEtcd()
} else {
defer func() {
// Refresh IAMSys in background.
go func() {
ticker := time.NewTicker(globalRefreshIAMInterval)
defer ticker.Stop()
for {
select {
case <-GlobalServiceDoneCh:
return
case <-ticker.C:
sys.refresh(objAPI)
}
}
}()
}()
defer sys.watchIAMDisk(objAPI)
}
doneCh := make(chan struct{})
@@ -148,8 +245,8 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
return nil
}
// DeleteCannedPolicy - deletes a canned policy.
func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
// DeletePolicy - deletes a canned policy from backend or etcd.
func (sys *IAMSys) DeletePolicy(policyName string) error {
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
@@ -160,11 +257,17 @@ func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
}
var err error
configFile := pathJoin(iamConfigPoliciesPrefix, policyName, iamPolicyFile)
pFile := pathJoin(iamConfigPoliciesPrefix, policyName, iamPolicyFile)
if globalEtcdClient != nil {
err = deleteConfigEtcd(context.Background(), globalEtcdClient, configFile)
err = deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
} else {
err = deleteConfig(context.Background(), objectAPI, configFile)
err = deleteConfig(context.Background(), objectAPI, pFile)
}
switch err.(type) {
case ObjectNotFound:
// Ignore error if policy is already deleted.
err = nil
}
sys.Lock()
@@ -174,8 +277,8 @@ func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
return err
}
// ListCannedPolicies - lists all canned policies.
func (sys *IAMSys) ListCannedPolicies() (map[string][]byte, error) {
// ListPolicies - lists all canned policies.
func (sys *IAMSys) ListPolicies() (map[string][]byte, error) {
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return nil, errServerNotInitialized
@@ -197,8 +300,8 @@ func (sys *IAMSys) ListCannedPolicies() (map[string][]byte, error) {
return cannedPolicyMap, nil
}
// SetCannedPolicy - sets a new canned policy.
func (sys *IAMSys) SetCannedPolicy(policyName string, p iampolicy.Policy) error {
// SetPolicy - sets a new canned policy.
func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
@@ -279,11 +382,11 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
pFile := pathJoin(iamConfigUsersPrefix, accessKey, iamPolicyFile)
iFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
if globalEtcdClient != nil {
// It is okay to ingnore errors when deleting policy.json for the user.
_ = deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
// It is okay to ignore errors when deleting policy.json for the user.
deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
err = deleteConfigEtcd(context.Background(), globalEtcdClient, iFile)
} else {
// It is okay to ingnore errors when deleting policy.json for the user.
// It is okay to ignore errors when deleting policy.json for the user.
_ = deleteConfig(context.Background(), objectAPI, pFile)
err = deleteConfig(context.Background(), objectAPI, iFile)
}
@@ -291,7 +394,8 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
//
switch err.(type) {
case ObjectNotFound:
err = errNoSuchUser
// ignore if user is already deleted.
err = nil
}
sys.Lock()
@@ -567,21 +671,9 @@ func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
var defaultContextTimeout = 30 * time.Second
// Similar to reloadUsers but updates users, policies maps from etcd server,
func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
r, err := globalEtcdClient.Get(ctx, prefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil {
return err
}
// No users are created yet.
if r.Count == 0 {
return nil
}
func etcdKvsToSet(prefix string, kvs []*mvccpb.KeyValue) set.StringSet {
users := set.NewStringSet()
for _, kv := range r.Kvs {
for _, kv := range kvs {
// Extract user by stripping off the `prefix` value as suffix,
// then strip off the remaining basename to obtain the prefix
// value, usually in the following form.
@@ -595,46 +687,48 @@ func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policy
users.Add(user)
}
}
return users
}
// Similar to reloadUsers but updates users, policies maps from etcd server,
func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
r, err := globalEtcdClient.Get(ctx, prefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil {
return err
}
// No users are created yet.
if r.Count == 0 {
return nil
}
users := etcdKvsToSet(prefix, r.Kvs)
// Reload config and policies for all users.
for _, user := range users.ToSlice() {
idFile := pathJoin(prefix, user, iamIdentityFile)
pFile := pathJoin(prefix, user, iamPolicyFile)
cdata, cerr := readConfigEtcd(ctx, globalEtcdClient, idFile)
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
continue
}
if cerr == nil {
var cred auth.Credentials
if err = json.Unmarshal(cdata, &cred); err != nil {
return err
}
cred.AccessKey = user
if cred.IsExpired() {
deleteConfigEtcd(ctx, globalEtcdClient, idFile)
deleteConfigEtcd(ctx, globalEtcdClient, pFile)
continue
}
usersMap[cred.AccessKey] = cred
}
if perr == nil {
var policyName string
if err = json.Unmarshal(pdata, &policyName); err != nil {
return err
}
policyMap[user] = policyName
if err = reloadEtcdUser(ctx, prefix, user, usersMap, policyMap); err != nil {
return err
}
}
return nil
}
func reloadEtcdPolicy(ctx context.Context, prefix string, policyName string,
cannedPolicyMap map[string]iampolicy.Policy) error {
pFile := pathJoin(prefix, policyName, iamPolicyFile)
pdata, err := readConfigEtcd(ctx, globalEtcdClient, pFile)
if err != nil {
return err
}
var p iampolicy.Policy
if err = json.Unmarshal(pdata, &p); err != nil {
return err
}
cannedPolicyMap[policyName] = p
return nil
}
func reloadEtcdPolicies(prefix string, cannedPolicyMap map[string]iampolicy.Policy) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
@@ -647,38 +741,32 @@ func reloadEtcdPolicies(prefix string, cannedPolicyMap map[string]iampolicy.Poli
return nil
}
policies := set.NewStringSet()
for _, kv := range r.Kvs {
// Extract policy by stripping off the `prefix` value as suffix,
// then strip off the remaining basename to obtain the prefix
// value, usually in the following form.
//
// key := "config/iam/policies/newpolicy/identity.json"
// prefix := "config/iam/policies/"
// v := trim(trim(key, prefix), base(key)) == "newpolicy"
//
policyName := path.Clean(strings.TrimSuffix(strings.TrimPrefix(string(kv.Key), prefix), path.Base(string(kv.Key))))
if !policies.Contains(policyName) {
policies.Add(policyName)
}
}
policies := etcdKvsToSet(prefix, r.Kvs)
// Reload config and policies for all policys.
for _, policyName := range policies.ToSlice() {
pFile := pathJoin(prefix, policyName, iamPolicyFile)
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
if perr != nil {
return perr
}
var p iampolicy.Policy
if err = json.Unmarshal(pdata, &p); err != nil {
if err = reloadEtcdPolicy(ctx, prefix, policyName, cannedPolicyMap); err != nil {
return err
}
cannedPolicyMap[policyName] = p
}
return nil
}
func reloadPolicy(ctx context.Context, objectAPI ObjectLayer, prefix string,
policyName string, cannedPolicyMap map[string]iampolicy.Policy) error {
pFile := pathJoin(prefix, policyName, iamPolicyFile)
pdata, err := readConfig(context.Background(), objectAPI, pFile)
if err != nil {
return err
}
var p iampolicy.Policy
if err = json.Unmarshal(pdata, &p); err != nil {
return err
}
cannedPolicyMap[policyName] = p
return nil
}
func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[string]iampolicy.Policy) error {
marker := ""
for {
@@ -690,16 +778,10 @@ func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[st
}
marker = lo.NextMarker
for _, prefix := range lo.Prefixes {
pFile := pathJoin(prefix, iamPolicyFile)
pdata, perr := readConfig(context.Background(), objectAPI, pFile)
if perr != nil {
return perr
}
var p iampolicy.Policy
if err = json.Unmarshal(pdata, &p); err != nil {
if err = reloadPolicy(context.Background(), objectAPI, iamConfigPoliciesPrefix,
path.Base(prefix), cannedPolicyMap); err != nil {
return err
}
cannedPolicyMap[path.Base(prefix)] = p
}
if !lo.IsTruncated {
break
@@ -709,6 +791,86 @@ func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[st
}
func reloadEtcdUser(ctx context.Context, prefix string, accessKey string,
usersMap map[string]auth.Credentials, policyMap map[string]string) error {
idFile := pathJoin(prefix, accessKey, iamIdentityFile)
pFile := pathJoin(prefix, accessKey, iamPolicyFile)
cdata, cerr := readConfigEtcd(ctx, globalEtcdClient, idFile)
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
return nil
}
if cerr == nil {
var cred auth.Credentials
if err := json.Unmarshal(cdata, &cred); err != nil {
return err
}
cred.AccessKey = path.Base(accessKey)
if cred.IsExpired() {
// Delete expired identity.
deleteConfigEtcd(ctx, globalEtcdClient, idFile)
// Delete expired identity policy.
deleteConfigEtcd(ctx, globalEtcdClient, pFile)
return nil
}
usersMap[cred.AccessKey] = cred
}
if perr == nil {
var policyName string
if err := json.Unmarshal(pdata, &policyName); err != nil {
return err
}
policyMap[path.Base(accessKey)] = policyName
}
return nil
}
func reloadUser(ctx context.Context, objectAPI ObjectLayer, prefix string, accessKey string,
usersMap map[string]auth.Credentials, policyMap map[string]string) error {
idFile := pathJoin(prefix, accessKey, iamIdentityFile)
pFile := pathJoin(prefix, accessKey, iamPolicyFile)
cdata, cerr := readConfig(ctx, objectAPI, idFile)
pdata, perr := readConfig(ctx, objectAPI, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
return nil
}
if cerr == nil {
var cred auth.Credentials
if err := json.Unmarshal(cdata, &cred); err != nil {
return err
}
cred.AccessKey = path.Base(accessKey)
if cred.IsExpired() {
// Delete expired identity.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, idFile)
// Delete expired identity policy.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, pFile)
return nil
}
usersMap[cred.AccessKey] = cred
}
if perr == nil {
var policyName string
if err := json.Unmarshal(pdata, &policyName); err != nil {
return err
}
policyMap[path.Base(accessKey)] = policyName
}
return nil
}
// reloadUsers reads an updates users, policies from object layer into user and policy maps.
func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
marker := ""
@@ -721,40 +883,9 @@ func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.
}
marker = lo.NextMarker
for _, prefix := range lo.Prefixes {
idFile := pathJoin(prefix, iamIdentityFile)
pFile := pathJoin(prefix, iamPolicyFile)
cdata, cerr := readConfig(context.Background(), objectAPI, idFile)
pdata, perr := readConfig(context.Background(), objectAPI, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
continue
}
if cerr == nil {
var cred auth.Credentials
if err = json.Unmarshal(cdata, &cred); err != nil {
return err
}
cred.AccessKey = path.Base(prefix)
if cred.IsExpired() {
// Delete expired identity.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, idFile)
// Delete expired identity policy.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, pFile)
continue
}
usersMap[cred.AccessKey] = cred
}
if perr == nil {
var policyName string
if err = json.Unmarshal(pdata, &policyName); err != nil {
return err
}
policyMap[path.Base(prefix)] = policyName
// Prefix is empty because prefix is already part of the List output.
if err = reloadUser(context.Background(), objectAPI, "", prefix, usersMap, policyMap); err != nil {
return err
}
}
if !lo.IsTruncated {
+3 -3
View File
@@ -27,8 +27,8 @@ import (
"github.com/minio/minio/pkg/words"
)
// global flags for minio.
var globalFlags = []cli.Flag{
// GlobalFlags - global flags for minio.
var GlobalFlags = []cli.Flag{
cli.StringFlag{
Name: "config-dir, C",
Value: defaultConfigDir.Get(),
@@ -131,7 +131,7 @@ func newApp(name string) *cli.App {
app.Version = Version
app.Usage = "Cloud Storage Server."
app.Description = `MinIO is an Amazon S3 compatible object storage server. Use it to store photos, videos, VMs, containers, log files, or any blob of data as objects.`
app.Flags = globalFlags
app.Flags = GlobalFlags
app.HideVersion = true // Hide `--version` flag, we already have `minio version`.
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
app.Commands = commands
+60
View File
@@ -157,6 +157,66 @@ func (sys *NotificationSys) ReloadFormat(dryRun bool) []NotificationPeerErr {
return ng.Wait()
}
// DeletePolicy - deletes policy across all peers.
func (sys *NotificationSys) DeletePolicy(policyName string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.DeletePolicy(policyName)
}, idx, *client.host)
}
return ng.Wait()
}
// LoadPolicy - reloads a specific modified policy across all peers
func (sys *NotificationSys) LoadPolicy(policyName string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.LoadPolicy(policyName)
}, idx, *client.host)
}
return ng.Wait()
}
// DeleteUser - deletes a specific user across all peers
func (sys *NotificationSys) DeleteUser(accessKey string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.DeleteUser(accessKey)
}, idx, *client.host)
}
return ng.Wait()
}
// LoadUser - reloads a specific user across all peers
func (sys *NotificationSys) LoadUser(accessKey string, temp bool) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.LoadUser(accessKey, temp)
}, idx, *client.host)
}
return ng.Wait()
}
// LoadUsers - calls LoadUsers RPC call on all peers.
func (sys *NotificationSys) LoadUsers() []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
+2 -2
View File
@@ -272,7 +272,7 @@ func listObjects(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, d
recursive = false
}
walkResultCh, endWalkCh := tpool.Release(listParams{bucket, recursive, marker, prefix})
walkResultCh, endWalkCh := tpool.Release(listParams{bucket, recursive, marker, prefix, false})
if walkResultCh == nil {
endWalkCh = make(chan struct{})
walkResultCh = startTreeWalk(ctx, bucket, prefix, marker, recursive, listDir, endWalkCh)
@@ -333,7 +333,7 @@ func listObjects(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, d
}
// Save list routine for the next marker if we haven't reached EOF.
params := listParams{bucket, recursive, nextMarker, prefix}
params := listParams{bucket, recursive, nextMarker, prefix, false}
if !eof {
tpool.Set(params, walkResultCh, endWalkCh)
}
+3 -1
View File
@@ -90,9 +90,11 @@ type ObjectLayer interface {
HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error)
HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error)
HealObject(ctx context.Context, bucket, object string, dryRun, remove bool, scanMode madmin.HealScanMode) (madmin.HealResultItem, error)
ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error)
HealObjects(ctx context.Context, bucket, prefix string, healObjectFn func(string, string) error) error
ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error)
ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error)
// Policy operations
SetBucketPolicy(context.Context, string, *policy.Policy) error
GetBucketPolicy(context.Context, string) (*policy.Policy, error)
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
* MinIO Cloud Storage, (C) 2018, 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.
+113 -2
View File
@@ -17,12 +17,15 @@
package cmd
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/gob"
"io"
"net/url"
"strconv"
"time"
"github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
@@ -51,9 +54,16 @@ func (client *peerRESTClient) reConnect() error {
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func (client *peerRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
return client.callWithContext(context.Background(), method, values, body, length)
}
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func (client *peerRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
if !client.connected {
err := client.reConnect()
logger.LogIf(context.Background(), err)
logger.LogIf(ctx, err)
if err != nil {
return nil, err
}
@@ -63,7 +73,7 @@ func (client *peerRESTClient) call(method string, values url.Values, body io.Rea
values = make(url.Values)
}
respBody, err = client.restClient.Call(method, values, body, length)
respBody, err = client.restClient.CallWithContext(ctx, method, values, body, length)
if err == nil {
return respBody, nil
}
@@ -337,6 +347,59 @@ func (client *peerRESTClient) PutBucketNotification(bucket string, rulesMap even
return nil
}
// DeletePolicy - delete a specific canned policy.
func (client *peerRESTClient) DeletePolicy(policyName string) (err error) {
values := make(url.Values)
values.Set(peerRESTPolicy, policyName)
respBody, err := client.call(peerRESTMethodDeletePolicy, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadPolicy - reload a specific canned policy.
func (client *peerRESTClient) LoadPolicy(policyName string) (err error) {
values := make(url.Values)
values.Set(peerRESTPolicy, policyName)
respBody, err := client.call(peerRESTMethodLoadPolicy, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// DeleteUser - delete a specific user.
func (client *peerRESTClient) DeleteUser(accessKey string) (err error) {
values := make(url.Values)
values.Set(peerRESTUser, accessKey)
respBody, err := client.call(peerRESTMethodDeleteUser, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadUser - reload a specific user.
func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error) {
values := make(url.Values)
values.Set(peerRESTUser, accessKey)
values.Set(peerRESTUserTemp, strconv.FormatBool(temp))
respBody, err := client.call(peerRESTMethodLoadUser, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadUsers - send load users command to peer nodes.
func (client *peerRESTClient) LoadUsers() (err error) {
respBody, err := client.call(peerRESTMethodLoadUsers, nil, nil, -1)
@@ -359,6 +422,54 @@ func (client *peerRESTClient) SignalService(sig serviceSignal) error {
return nil
}
// Trace - send http trace request to peer nodes
func (client *peerRESTClient) Trace(doneCh chan struct{}, trcAll bool) (chan []byte, error) {
ch := make(chan []byte)
go func() {
cleanupFn := func(cancel context.CancelFunc, ch chan []byte, respBody io.ReadCloser) {
close(ch)
if cancel != nil {
cancel()
}
http.DrainBody(respBody)
}
for {
values := make(url.Values)
values.Set(peerRESTTraceAll, strconv.FormatBool(trcAll))
// get cancellation context to properly unsubscribe peers
ctx, cancel := context.WithCancel(context.Background())
respBody, err := client.callWithContext(ctx, peerRESTMethodTrace, values, nil, -1)
if err != nil {
//retry
time.Sleep(5 * time.Second)
select {
case <-doneCh:
cleanupFn(cancel, ch, respBody)
return
default:
}
continue
}
bio := bufio.NewScanner(respBody)
go func() {
<-doneCh
cancel()
}()
// Unmarshal each line, returns marshaled values.
for bio.Scan() {
ch <- bio.Bytes()
}
select {
case <-doneCh:
cleanupFn(cancel, ch, respBody)
return
default:
}
}
}()
return ch, nil
}
func getRemoteHosts(endpoints EndpointList) []*xnet.Host {
var remoteHosts []*xnet.Host
for _, hostStr := range GetRemotePeers(endpoints) {
+10 -1
View File
@@ -16,7 +16,7 @@
package cmd
const peerRESTVersion = "v1"
const peerRESTVersion = "v2"
const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion
const (
@@ -28,6 +28,10 @@ const (
peerRESTMethodSignalService = "signalservice"
peerRESTMethodGetLocks = "getlocks"
peerRESTMethodBucketPolicyRemove = "removebucketpolicy"
peerRESTMethodLoadUser = "loaduser"
peerRESTMethodDeleteUser = "deleteuser"
peerRESTMethodLoadPolicy = "loadpolicy"
peerRESTMethodDeletePolicy = "deletepolicy"
peerRESTMethodLoadUsers = "loadusers"
peerRESTMethodStartProfiling = "startprofiling"
peerRESTMethodDownloadProfilingData = "downloadprofilingdata"
@@ -37,11 +41,16 @@ const (
peerRESTMethodReloadFormat = "reloadformat"
peerRESTMethodTargetExists = "targetexists"
peerRESTMethodSendEvent = "sendevent"
peerRESTMethodTrace = "trace"
)
const (
peerRESTBucket = "bucket"
peerRESTUser = "user"
peerRESTUserTemp = "user-temp"
peerRESTPolicy = "policy"
peerRESTSignal = "signal"
peerRESTProfiler = "profiler"
peerRESTDryRun = "dry-run"
peerRESTTraceAll = "all"
)
+169 -1
View File
@@ -19,10 +19,12 @@ package cmd
import (
"context"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
@@ -31,6 +33,7 @@ import (
"github.com/minio/minio/pkg/event"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/policy"
trace "github.com/minio/minio/pkg/trace"
)
// To abstract a node over network.
@@ -117,7 +120,125 @@ func (s *peerRESTServer) GetLocksHandler(w http.ResponseWriter, r *http.Request)
}
// LoadUsersHandler - returns server info.
// DeletePolicyHandler - deletes a policy on the server.
func (s *peerRESTServer) DeletePolicyHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
policyName := vars[peerRESTPolicy]
if policyName == "" {
s.writeErrorResponse(w, errors.New("policyName is missing"))
return
}
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// LoadPolicyHandler - reloads a policy on the server.
func (s *peerRESTServer) LoadPolicyHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
policyName := vars[peerRESTPolicy]
if policyName == "" {
s.writeErrorResponse(w, errors.New("policyName is missing"))
return
}
if err := globalIAMSys.LoadPolicy(objAPI, policyName); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// DeleteUserHandler - deletes a user on the server.
func (s *peerRESTServer) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
accessKey := vars[peerRESTUser]
if accessKey == "" {
s.writeErrorResponse(w, errors.New("username is missing"))
return
}
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// LoadUserHandler - reloads a user on the server.
func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
accessKey := vars[peerRESTUser]
if accessKey == "" {
s.writeErrorResponse(w, errors.New("username is missing"))
return
}
temp, err := strconv.ParseBool(vars[peerRESTUserTemp])
if err != nil {
s.writeErrorResponse(w, err)
return
}
if err = globalIAMSys.LoadUser(objAPI, accessKey, temp); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// LoadUsersHandler - reloads all users and canned policies.
func (s *peerRESTServer) LoadUsersHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
@@ -547,6 +668,47 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
}
}
// TraceHandler sends http trace messages back to peer rest client
func (s *peerRESTServer) TraceHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
trcAll := r.URL.Query().Get(peerRESTTraceAll) == "true"
w.Header().Set("Connection", "close")
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
ch := globalTrace.pubsub.Subscribe()
defer globalTrace.pubsub.Unsubscribe(ch)
enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)
for {
select {
case entry := <-ch:
trcInfo := entry.(trace.Info)
path := strings.TrimPrefix(trcInfo.ReqInfo.Path, "/")
// omit inter-node traffic if trcAll is false
if !trcAll && strings.HasPrefix(path, minioReservedBucket) {
continue
}
if err := enc.Encode(trcInfo); err != nil {
return
}
if _, err := w.Write([]byte("\n")); err != nil {
return
}
w.(http.Flusher).Flush()
case <-r.Context().Done():
return
}
}
}
func (s *peerRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(err.Error()))
@@ -576,6 +738,10 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDeletePolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser, peerRESTUserTemp)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUsers).HandlerFunc(httpTraceAll(server.LoadUsersHandler))
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodStartProfiling).HandlerFunc(httpTraceAll(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...)
@@ -588,5 +754,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodReloadFormat).HandlerFunc(httpTraceHdrs(server.ReloadFormatHandler)).Queries(restQueries(peerRESTDryRun)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodTrace).HandlerFunc(server.TraceHandler)
router.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
}
-1
View File
@@ -114,7 +114,6 @@ func (sys *PolicySys) IsAllowed(args policy.Args) bool {
func (sys *PolicySys) refresh(objAPI ObjectLayer) error {
buckets, err := objAPI.ListBuckets(context.Background())
if err != nil {
logger.LogIf(context.Background(), err)
return err
}
sys.removeDeletedBuckets(buckets)
+9 -3
View File
@@ -52,13 +52,13 @@ type Client struct {
newAuthToken func() string
}
// Call - make a REST call.
func (c *Client) Call(method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
// CallWithContext - make a REST call with context.
func (c *Client) CallWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
req, err := http.NewRequest(http.MethodPost, c.url.String()+"/"+method+"?"+values.Encode(), body)
if err != nil {
return nil, &NetworkError{err}
}
req = req.WithContext(ctx)
req.Header.Set("Authorization", "Bearer "+c.newAuthToken())
req.Header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
if length > 0 {
@@ -84,6 +84,12 @@ func (c *Client) Call(method string, values url.Values, body io.Reader, length i
return resp.Body, nil
}
// Call - make a REST call.
func (c *Client) Call(method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
ctx := context.Background()
return c.CallWithContext(ctx, method, values, body, length)
}
// Close closes all idle connections of the underlying http client
func (c *Client) Close() {
if c.httpIdleConnsCloser != nil {
+17 -4
View File
@@ -38,7 +38,8 @@ func init() {
logger.RegisterUIError(fmtError)
}
var serverFlags = []cli.Flag{
// ServerFlags - server command specific flags
var ServerFlags = []cli.Flag{
cli.StringFlag{
Name: "address",
Value: ":" + globalMinioDefaultPort,
@@ -49,7 +50,7 @@ var serverFlags = []cli.Flag{
var serverCmd = cli.Command{
Name: "server",
Usage: "start object storage server",
Flags: append(serverFlags, globalFlags...),
Flags: append(ServerFlags, GlobalFlags...),
Action: serverMain,
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
@@ -287,8 +288,14 @@ func serverMain(ctx *cli.Context) {
// Initialize name space lock.
initNSLock(globalIsDistXL)
// Init global heal state
initAllHealState(globalIsXL)
if globalIsXL {
// Init global heal state
globalAllHealState = initHealState()
globalSweepHealState = initHealState()
}
// initialize globalTrace system
globalTrace = NewTraceSys(context.Background(), globalEndpoints)
// Configure server.
var handler http.Handler
@@ -369,6 +376,12 @@ func serverMain(ctx *cli.Context) {
logger.Fatal(errors.New("Invalid KMS configuration"), "auto-encryption is enabled but server does not support encryption")
}
if globalIsXL {
initBackgroundHealing()
initDailyHeal()
initDailySweeper()
}
globalObjLayerMutex.Lock()
globalObjectAPI = newObject
globalObjLayerMutex.Unlock()
-3
View File
@@ -74,7 +74,6 @@ func handleSignals() {
exit(err == nil && oerr == nil)
case osSignal := <-globalOSSignalCh:
stopHTTPTrace()
logger.Info("Exiting on signal: %s", strings.ToUpper(osSignal.String()))
exit(stopProcess())
case signal := <-globalServiceSignalCh:
@@ -83,14 +82,12 @@ func handleSignals() {
// Ignore this at the moment.
case serviceRestart:
logger.Info("Restarting on service signal")
stopHTTPTrace()
stop := stopProcess()
rerr := restartProcess()
logger.LogIf(context.Background(), rerr)
exit(stop && rerr == nil)
case serviceStop:
logger.Info("Stopping on service signal")
stopHTTPTrace()
exit(stopProcess())
}
}
+2 -2
View File
@@ -189,7 +189,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
}
// Notify all other MinIO peers to reload temp users
for _, nerr := range globalNotificationSys.LoadUsers() {
for _, nerr := range globalNotificationSys.LoadUser(cred.AccessKey, true) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
@@ -306,7 +306,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
}
// Notify all other MinIO peers to reload temp users
for _, nerr := range globalNotificationSys.LoadUsers() {
for _, nerr := range globalNotificationSys.LoadUser(cred.AccessKey, true) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
+3
View File
@@ -470,6 +470,9 @@ func resetGlobalStorageEnvs() {
// reset global heal state
func resetGlobalHealState() {
if globalAllHealState == nil {
return
}
globalAllHealState.Lock()
defer globalAllHealState.Unlock()
for _, v := range globalAllHealState.healSeqMap {
+1
View File
@@ -34,6 +34,7 @@ type listParams struct {
recursive bool
marker string
prefix string
heal bool
}
// errWalkAbort - returned by doTreeWalk() if it returns prematurely.
-10
View File
@@ -59,16 +59,6 @@ func IsErr(err error, errs ...error) bool {
return false
}
// Close Http tracing file.
func stopHTTPTrace() {
if globalHTTPTraceFile != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("traceFile", globalHTTPTraceFile.Name())
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, globalHTTPTraceFile.Close())
globalHTTPTraceFile = nil
}
}
// make a copy of http.Header
func cloneHeader(h http.Header) http.Header {
h2 := make(http.Header, len(h))
-13
View File
@@ -54,19 +54,6 @@ func TestCloneHeader(t *testing.T) {
}
}
// Tests closing http tracing file.
func TestStopHTTPTrace(t *testing.T) {
var err error
globalHTTPTraceFile, err = ioutil.TempFile("", "")
if err != nil {
defer os.Remove(globalHTTPTraceFile.Name())
stopHTTPTrace()
if globalHTTPTraceFile != nil {
t.Errorf("globalHTTPTraceFile is not nil, it is expected to be nil")
}
}
}
// Tests maximum object size.
func TestMaxObjectSize(t *testing.T) {
sizes := []struct {
+24 -4
View File
@@ -844,6 +844,10 @@ func leastEntry(entriesCh []FileInfoCh, readQuorum int) (FileInfo, bool) {
entriesCh[i].Push(entries[i])
}
if readQuorum < 0 {
return lentry, isTruncated
}
quorum := lentry.Quorum
if quorum == 0 {
quorum = readQuorum
@@ -906,7 +910,7 @@ func (s *xlSets) startMergeWalks(ctx context.Context, bucket, prefix, marker str
// ListObjects - implements listing of objects across disks, each disk is indepenently
// walked and merged at this layer. Resulting value through the merge process sends
// the data in lexically sorted order.
func (s *xlSets) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, err error) {
func (s *xlSets) listObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int, heal bool) (loi ListObjectsInfo, err error) {
if err = checkListObjsArgs(ctx, bucket, prefix, marker, delimiter, s); err != nil {
return loi, err
}
@@ -944,13 +948,18 @@ func (s *xlSets) ListObjects(ctx context.Context, bucket, prefix, marker, delimi
recursive = false
}
entryChs, endWalkCh := s.pool.Release(listParams{bucket, recursive, marker, prefix})
entryChs, endWalkCh := s.pool.Release(listParams{bucket, recursive, marker, prefix, heal})
if entryChs == nil {
endWalkCh = make(chan struct{})
entryChs = s.startMergeWalks(context.Background(), bucket, prefix, marker, recursive, endWalkCh)
}
entries := mergeEntriesCh(entryChs, maxKeys, s.drivesPerSet/2)
readQuorum := s.drivesPerSet / 2
if heal {
readQuorum = -1
}
entries := mergeEntriesCh(entryChs, maxKeys, readQuorum)
if len(entries.Files) == 0 {
return loi, nil
}
@@ -1004,11 +1013,18 @@ func (s *xlSets) ListObjects(ctx context.Context, bucket, prefix, marker, delimi
loi.Objects = append(loi.Objects, objInfo)
}
if loi.IsTruncated {
s.pool.Set(listParams{bucket, recursive, loi.NextMarker, prefix}, entryChs, endWalkCh)
s.pool.Set(listParams{bucket, recursive, loi.NextMarker, prefix, heal}, entryChs, endWalkCh)
}
return loi, nil
}
// ListObjects - implements listing of objects across disks, each disk is indepenently
// walked and merged at this layer. Resulting value through the merge process sends
// the data in lexically sorted order.
func (s *xlSets) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, err error) {
return s.listObjects(ctx, bucket, prefix, marker, delimiter, maxKeys, false)
}
func (s *xlSets) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error) {
// In list multipart uploads we are going to treat input prefix as the object,
// this means that we are not supporting directory navigation.
@@ -1536,3 +1552,7 @@ func (s *xlSets) HealObjects(ctx context.Context, bucket, prefix string, healObj
return nil
}
func (s *xlSets) ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, err error) {
return s.listObjects(ctx, bucket, prefix, marker, delimiter, maxKeys, true)
}
+5
View File
@@ -23,6 +23,11 @@ func (xl xlObjects) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
return nil, nil
}
// This is not implemented, look for xl-sets.ListObjectsHeal()
func (xl xlObjects) ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, err error) {
return ListObjectsInfo{}, nil
}
// This is not implemented/needed anymore, look for xl-sets.HealObjects()
func (xl xlObjects) HealObjects(ctx context.Context, bucket, prefix string, healFn func(string, string) error) (e error) {
return nil
+2 -2
View File
@@ -67,7 +67,7 @@ func (xl xlObjects) listObjects(ctx context.Context, bucket, prefix, marker, del
recursive = false
}
walkResultCh, endWalkCh := xl.listPool.Release(listParams{bucket, recursive, marker, prefix})
walkResultCh, endWalkCh := xl.listPool.Release(listParams{bucket, recursive, marker, prefix, false})
if walkResultCh == nil {
endWalkCh = make(chan struct{})
listDir := listDirFactory(ctx, xl.getLoadBalancedDisks()...)
@@ -118,7 +118,7 @@ func (xl xlObjects) listObjects(ctx context.Context, bucket, prefix, marker, del
}
}
params := listParams{bucket, recursive, nextMarker, prefix}
params := listParams{bucket, recursive, nextMarker, prefix, false}
if !eof {
xl.listPool.Set(params, walkResultCh, endWalkCh)
}
+136
View File
@@ -0,0 +1,136 @@
// +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/exec"
"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) {
cmd := exec.Command("chown", "-R", defaultUserGroup, path)
if err = cmd.Run(); err != nil {
return "", err
}
return defaultUserGroup, nil
}
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)
}
}
+44 -9
View File
@@ -1,6 +1,6 @@
#!/bin/sh
#
# MinIO Cloud Storage, (C) 2017 MinIO, Inc.
# 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.
@@ -15,6 +15,9 @@
# 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
@@ -24,20 +27,52 @@ fi
## Look for docker secrets in default documented location.
docker_secrets_env() {
local ACCESS_KEY_FILE="/run/secrets/$MINIO_ACCESS_KEY_FILE"
local SECRET_KEY_FILE="/run/secrets/$MINIO_SECRET_KEY_FILE"
ACCESS_KEY_FILE="/run/secrets/$MINIO_ACCESS_KEY_FILE"
SECRET_KEY_FILE="/run/secrets/$MINIO_SECRET_KEY_FILE"
if [ -f $ACCESS_KEY_FILE -a -f $SECRET_KEY_FILE ]; then
if [ -f $ACCESS_KEY_FILE ]; then
export MINIO_ACCESS_KEY="$(cat "$ACCESS_KEY_FILE")"
if [ -f "$ACCESS_KEY_FILE" ] && [ -f "$SECRET_KEY_FILE" ]; then
if [ -f "$ACCESS_KEY_FILE" ]; then
MINIO_ACCESS_KEY="$(cat "$ACCESS_KEY_FILE")"
export MINIO_ACCESS_KEY
fi
if [ -f $SECRET_KEY_FILE ]; then
export MINIO_SECRET_KEY="$(cat "$SECRET_KEY_FILE")"
if [ -f "$SECRET_KEY_FILE" ]; then
MINIO_SECRET_KEY="$(cat "$SECRET_KEY_FILE")"
export MINIO_SECRET_KEY
fi
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
}
# su-exec to requested user, if user cannot be requested
# existing user is used automatically.
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
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
exec "$@"
## User Input UID and GID
docker_set_uid_gid
## Switch to user if applicable.
docker_switch_user "$@"
+12 -3
View File
@@ -1,3 +1,5 @@
// +build ignore
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
@@ -36,7 +38,7 @@ const (
initGraceTime = 300
healthPath = "/minio/health/live"
timeout = time.Duration(30 * time.Second)
minioProcess = "minio"
tcp = "tcp"
)
// returns container boot time by finding
@@ -66,9 +68,16 @@ func findEndpoint() (string, error) {
// split netstat output in rows
scanner := bufio.NewScanner(stdout)
scanner.Split(bufio.ScanLines)
// loop over the rows to find MinIO process
// MinIO works on TCP and it is supposed to be
// the only process listening on a port inside
// container. So we take the first row of netstat
// output (that has tcp) and assume that is the
// MinIO server port.
// Since MinIO is running as non-root user, we can
// no longer depend on the PID/Program name column
// of netstat output
for scanner.Scan() {
if strings.Contains(scanner.Text(), minioProcess) {
if strings.Contains(scanner.Text(), tcp) {
line := scanner.Text()
newLine := strings.Replace(line, ":::", "127.0.0.1:", 1)
fields := strings.Fields(newLine)
+3 -3
View File
@@ -585,7 +585,7 @@ MinIO server also supports [NATS Streaming mode](http://nats.io/documentation/st
},
```
Read more about sections `clusterID`, `clientID` on [NATS documentation](https://github.com/nats-io/nats-streaming-server/blob/master/README.md). Section `maxPubAcksInflight` is explained [here](https://github.com/nats-io/go-nats-streaming#publisher-rate-limiting).
Read more about sections `clusterID`, `clientID` on [NATS documentation](https://github.com/nats-io/nats-streaming-server/blob/master/README.md). Section `maxPubAcksInflight` is explained [here](https://github.com/nats-io/stan.go#publisher-rate-limiting).
### Step 2: Enable bucket notification using MinIO client
@@ -610,7 +610,7 @@ import (
"log"
"runtime"
"github.com/nats-io/nats"
"github.com/nats-io/nats.go"
)
func main() {
@@ -663,7 +663,7 @@ import (
"fmt"
"runtime"
"github.com/nats-io/go-nats-streaming"
"github.com/nats-io/stan.go"
)
func main() {
+16 -16
View File
@@ -14,23 +14,23 @@ Distributed MinIO provides protection against multiple node/drive failures and [
A stand-alone MinIO server would go down if the server hosting the disks goes offline. In contrast, a distributed MinIO setup with _n_ disks will have your data safe as long as _n/2_ or more disks are online. You'll need a minimum of _(n/2 + 1)_ [Quorum](https://github.com/minio/dsync#lock-process) disks to create new objects though.
For example, an 8-node distributed MinIO setup with 1 disk per node would continue serving files, even if up to 4 disks are offline. But, you'll need at least 5 disks online to create new objects.
For example, an 16-node distributed MinIO setup with 16 disks per node would continue serving files, even if up to 8 servers are offline. But, you'll need at least 9 servers online to create new objects.
### Limits
As with MinIO in stand-alone mode, distributed MinIO has a per tenant limit of minimum 2 and maximum 32 servers. There are no limits on number of disks shared across these servers. If you need a multiple tenant setup, you can easily spin up multiple MinIO instances managed by orchestration tools like Kubernetes.
As with MinIO in stand-alone mode, distributed MinIO has a per tenant limit of minimum of 2 and maximum of 32 servers. There are no limits on number of disks across these servers. If you need a multiple tenant setup, you can easily spin up multiple MinIO instances managed by orchestration tools like Kubernetes, Docker Swarm etc.
Note that with distributed MinIO you can play around with the number of nodes and drives as long as the limits are adhered to. For example, you can have 2 nodes with 4 drives each, 4 nodes with 4 drives each, 8 nodes with 2 drives each, 32 servers with 24 drives each and so on.
Note that with distributed MinIO you can play around with the number of nodes and drives as long as the limits are adhered to. For example, you can have 2 nodes with 4 drives each, 4 nodes with 4 drives each, 8 nodes with 2 drives each, 32 servers with 64 drives each and so on.
You can also use [storage classes](https://github.com/minio/minio/tree/master/docs/erasure/storage-class) to set custom data and parity distribution across total disks.
You can also use [storage classes](https://github.com/minio/minio/tree/master/docs/erasure/storage-class) to set custom data and parity distribution per object.
### Consistency Guarantees
MinIO follows strict **read-after-write** consistency model for all i/o operations both in distributed and standalone modes.
MinIO follows strict **read-after-write** and **list-after-write** consistency model for all i/o operations both in distributed and standalone modes.
# Get started
If you're aware of stand-alone MinIO set up, the process remains largely the same, as the MinIO server automatically switches to stand-alone or distributed mode, depending on the command line parameters.
If you're aware of stand-alone MinIO set up, the process remains largely the same. MinIO server automatically switches to stand-alone or distributed mode, depending on the command line parameters.
## 1. Prerequisites
@@ -43,25 +43,25 @@ To start a distributed MinIO instance, you just need to pass drive locations as
*Note*
- All the nodes running distributed MinIO need to have same access key and secret key for the nodes to connect. To achieve this, it is **mandatory** to export access key and secret key as environment variables, `MINIO_ACCESS_KEY` and `MINIO_SECRET_KEY`, on all the nodes before executing MinIO server command.
- All the nodes running distributed MinIO need to be in a homogeneous environment, i.e. same operating system, same number of disks and same interconnects.
- `MINIO_DOMAIN` environment variable should be defined and exported if domain is needed to be set.
- MinIO distributed mode requires fresh directories. If required, the drives can be shared with other applications. You can do this by using a sub-directory exclusive to minio. For example, if you have mounted your volume under `/export`, pass `/export/data` as arguments to MinIO server.
- All the nodes running distributed MinIO setup are recommended to be in homogeneous environment, i.e. same operating system, same number of disks and same network interconnects.
- MinIO distributed mode requires fresh directories. If required, the drives can be shared with other applications. You can do this by using a sub-directory exclusive to MinIO. For example, if you have mounted your volume under `/export`, pass `/export/data` as arguments to MinIO server.
- The IP addresses and drive paths below are for demonstration purposes only, you need to replace these with the actual IP addresses and drive paths/folders.
- Servers running distributed MinIO instances should be less than 3 seconds apart. You can use [NTP](http://www.ntp.org/) as a best practice to ensure consistent times across servers.
- Running Distributed MinIO on Windows is experimental as of now. Please proceed with caution.
- Servers running distributed MinIO instances should be less than 15 minutes apart. You can enable [NTP](http://www.ntp.org/) service as a best practice to ensure same times across servers.
- Running Distributed MinIO on Windows operating system is experimental. Please proceed with caution.
- `MINIO_DOMAIN` environment variable should be defined and exported if domain is needed to be set.
Example 1: Start distributed MinIO instance on 32 nodes with 32 drives each mounted at `/export1` to `/export32` (pictured below), by running this command on all the 32 nodes:
![Distributed MinIO, 32 nodes with 32 drives each](https://github.com/minio/minio/blob/master/docs/screenshots/Architecture-diagram_distributed_32.png?raw=true)
Example 1: Start distributed MinIO instance on 8 nodes with 1 disk each mounted at `/export1` (pictured below), by running this command on all the 8 nodes:
![Distributed MinIO, 8 nodes with 1 disk each](https://github.com/minio/minio/blob/master/docs/screenshots/Architecture-diagram_distributed_8.jpg?raw=true)
#### GNU/Linux and macOS
```sh
export MINIO_ACCESS_KEY=<ACCESS_KEY>
export MINIO_SECRET_KEY=<SECRET_KEY>
minio server http://192.168.1.1{1...8}/export1
minio server http://host{1...32}/export{1...32}
```
__NOTE:__ `{1...n}` shown have 3 dots! Using only 2 dots `{1..4}` will be interpreted by your shell and won't be passed to minio server, affecting the erasure coding order, which may impact performance and high availability. __Always use `{1...n}` (3 dots!) to allow minio server to optimally erasure-code data__
__NOTE:__ `{1...n}` shown have 3 dots! Using only 2 dots `{1..32}` will be interpreted by your shell and won't be passed to minio server, affecting the erasure coding order, which may impact performance and high availability. __Always use ellipses syntax `{1...n}` (3 dots!) for optimal erasure-code distribution__
## 3. Test your setup
To test this setup, access the MinIO server via browser or [`mc`](https://docs.min.io/docs/minio-client-quickstart-guide).
+29
View File
@@ -58,6 +58,35 @@ 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.
By default `minio` is username and groupname. Use environment variables `MINIO_USERNAME` and `MINIO_GROUPNAME` to override these default values.
#### 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" \
-v /mnt/data:/data \
-v /mnt/config:/root/.minio \
minio/minio server /data
```
#### Windows
```powershell
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" \
-v D:\data:/data \
-v D:\minio\config:/root/.minio \
minio/minio server /data
```
### MinIO Custom Access and Secret Keys using Docker secrets
To override MinIO's auto-generated keys, you may pass secret and access keys explicitly by creating access and secret keys as [Docker secrets](https://docs.docker.com/engine/swarm/secrets/). MinIO server also allows regular strings as access and secret keys.
+25
View File
@@ -34,6 +34,31 @@ MinIO gateway will automatically look for list of credential styles in following
- AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
- IAM profile based credentials. (performs an HTTP call to a pre-defined endpoint, only valid inside configured ec2 instances)
Minimum permissions required if you wish to provide restricted access with your AWS credentials, please make sure you have following IAM policies attached for your AWS user or roles.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "readonly",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::testbucket"
},
{
"Sid": "readonly",
"Effect": "Allow",
"Action": "s3:HeadBucket",
"Resource": "arn:aws:s3:::testbucket"
}
]
}
```
## Run MinIO Gateway for AWS S3 compatible services
As a prerequisite to run MinIO S3 gateway on an AWS S3 compatible service, you need valid access key, secret key and service endpoint.
-47
View File
@@ -1,47 +0,0 @@
# Large Bucket Support Quickstart Guide [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io) [![Go Report Card](https://goreportcard.com/badge/minio/minio)](https://goreportcard.com/report/minio/minio) [![Docker Pulls](https://img.shields.io/docker/pulls/minio/minio.svg?maxAge=604800)](https://hub.docker.com/r/minio/minio/)
Erasure code in MinIO is limited to 16 disks. This on its own allows storage space to hold a tenant's data. However, to cater to cases which may need larger number of disks or high storage space requirements, large bucket support was introduced.
We call it large-bucket as it allows a single MinIO bucket to expand over multiple erasure code deployment sets. Without any special configuration it helps you create peta-scale storage systems. With large bucket support, you can use more than 16 disks upfront while deploying the MinIO server. Internally MinIO creates multiple smaller erasure coded sets, and these sets are further combined into a single namespace. This document gives a brief introduction on how to get started with large bucket deployments. To explore further on advanced uses and limitations, refer to the [design document](https://github.com/minio/minio/blob/master/docs/large-bucket/DESIGN.md).
## Get started
If you're aware of distributed MinIO setup, the installation and configuration remains the same. Only new addition to the input syntax is `...` convention to abbreviate the drive arguments. Remote drives in a distributed setup are encoded as HTTP(s) URIs which can be similarly abbreviated as well.
### 1. Prerequisites
Install MinIO - [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide).
### 2. Run MinIO on many drives
We'll see examples on how to do this in the following sections.
*Note*
- All the nodes running distributed MinIO need to have same access key and secret key. To achieve this, we export access key and secret key as environment variables on all the nodes before executing MinIO server command.
- The drive paths below are for demonstration purposes only, you need to replace these with the actual drive paths/folders.
#### MinIO large bucket on multiple drives (standalone)
You'll need the path to the disks e.g. `/export1, /export2 .... /export24`. Then run the following commands on all the nodes you'd like to launch MinIO.
```sh
export MINIO_ACCESS_KEY=<ACCESS_KEY>
export MINIO_SECRET_KEY=<SECRET_KEY>
minio server /export{1...24}
```
#### MinIO large bucket on multiple servers (distributed)
You'll need the path to the disks e.g. `http://host1/export1, http://host2/export2 .... http://host4/export16`. Then run the following commands on all the nodes you'd like to launch MinIO.
```sh
export MINIO_ACCESS_KEY=<ACCESS_KEY>
export MINIO_SECRET_KEY=<SECRET_KEY>
minio server http://host{1...4}/export{1...16}
```
### 3. Test your setup
To test this setup, access the MinIO server via browser or [`mc`](https://docs.min.io/docs/minio-client-quickstart-guide). You’ll see the uploaded files are accessible from the all the MinIO endpoints.
## Explore Further
- [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide)
- [Use `aws-cli` with MinIO Server](https://docs.min.io/docs/aws-cli-with-minio)
- [Use `s3cmd` with MinIO Server](https://docs.min.io/docs/s3cmd-with-minio)
- [Use `minio-go` SDK with MinIO Server](https://docs.min.io/docs/golang-client-quickstart-guide)
- [The MinIO documentation website](https://docs.min.io)
@@ -5,7 +5,7 @@ version: '2'
# 9001 through 9004.
services:
minio1:
image: minio/minio:RELEASE.2019-06-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
volumes:
- data4:/data
ports:
@@ -2,7 +2,7 @@ version: '3.1'
services:
minio1:
image: minio/minio:RELEASE.2019-06-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
hostname: minio1
volumes:
- minio1-data:/export
@@ -24,7 +24,7 @@ services:
- access_key
minio2:
image: minio/minio:RELEASE.2019-06-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
hostname: minio2
volumes:
- minio2-data:/export
@@ -46,7 +46,7 @@ services:
- access_key
minio3:
image: minio/minio:RELEASE.2019-06-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
hostname: minio3
volumes:
- minio3-data:/export
@@ -68,7 +68,7 @@ services:
- access_key
minio4:
image: minio/minio:RELEASE.2019-06-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
hostname: minio4
volumes:
- minio4-data:/export
@@ -2,7 +2,7 @@ version: '3'
services:
minio1:
image: minio/minio:RELEASE.2019-06-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
# 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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
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-01T03-46-14Z
image: minio/minio:RELEASE.2019-06-11T00-44-33Z
args:
- server
- /data
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
version: '2'
services:
opa:
image: openpolicyagent/opa:0.9.1
image: openpolicyagent/opa:0.11.0
ports:
- 8181:8181
command:
+7 -6
View File
@@ -15,7 +15,7 @@ cat >docker-compose.yml <<EOF
version: '2'
services:
opa:
image: openpolicyagent/opa:0.9.1
image: openpolicyagent/opa:0.11.0
ports:
- 8181:8181
command:
@@ -45,11 +45,12 @@ package httpapi.authz
import input as http_api
allow {
input.action = "s3:PutObject"
input.owner = false
}
default allow = false
allow = true {
http_api.action = "s3:PutObject"
http_api.owner = false
}
EOF
```
@@ -62,7 +63,7 @@ curl -X PUT --data-binary @putobject.rego \
### 4. Setup MinIO with OPA
MinIO server expects environment variable for OPA http API url as `MINIO_IAM_OPA_URL`, this environment variable takes a single entry.
```
export MINIO_IAM_OPA_URL=http://localhost:8181/v1/data/httpapi/authz
export MINIO_IAM_OPA_URL=http://localhost:8181/v1/data/httpapi/authz/allow
minio server /mnt/data
```
+5 -3
View File
@@ -2,7 +2,9 @@ package httpapi.authz
import input as http_api
allow {
input.action = "s3:PutObject"
input.owner = false
default allow = false
allow = true {
http_api.action = "s3:PutObject"
http_api.owner = false
}
+3 -3
View File
@@ -526,7 +526,7 @@ MinIO服务也支持 [NATS Streaming mode](http://nats.io/documentation/streamin
}
},
```
更多关于 `clusterID`, `clientID` 的信息,请看 [NATS documentation](https://github.com/nats-io/nats-streaming-server/blob/master/README.md). 关于 `maxPubAcksInflight` ,请看 [这里](https://github.com/nats-io/go-nats-streaming#publisher-rate-limiting).
更多关于 `clusterID`, `clientID` 的信息,请看 [NATS documentation](https://github.com/nats-io/nats-streaming-server/blob/master/README.md). 关于 `maxPubAcksInflight` ,请看 [这里](https://github.com/nats-io/stan.go#publisher-rate-limiting).
### 第二步: 使用MinIO客户端启用bucket通知
@@ -551,7 +551,7 @@ import (
"log"
"runtime"
"github.com/nats-io/nats"
"github.com/nats-io/nats.go"
)
func main() {
@@ -604,7 +604,7 @@ import (
"fmt"
"runtime"
"github.com/nats-io/go-nats-streaming"
"github.com/nats-io/stan.go"
)
func main() {
+10 -8
View File
@@ -14,6 +14,7 @@ require (
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2
github.com/cheggaaa/pb v1.0.28
github.com/coredns/coredns v1.4.0
github.com/coreos/bbolt v1.3.3 // indirect
github.com/coreos/etcd v3.3.12+incompatible
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
@@ -31,6 +32,7 @@ require (
github.com/gorilla/mux v1.7.0
github.com/gorilla/rpc v1.2.0+incompatible
github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect
github.com/hashicorp/go-rootcerts v1.0.1 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.1 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
@@ -55,7 +57,7 @@ require (
github.com/minio/highwayhash v1.0.0
github.com/minio/lsync v0.1.0
github.com/minio/mc v0.0.0-20190529152718-f4bb0b8850cb
github.com/minio/minio-go/v6 v6.0.27
github.com/minio/minio-go/v6 v6.0.29
github.com/minio/parquet-go v0.0.0-20190318185229-9d767baf1679
github.com/minio/sha256-simd v0.1.0
github.com/minio/sio v0.2.0
@@ -63,8 +65,8 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/nats-io/gnatsd v1.4.1 // indirect
github.com/nats-io/go-nats-streaming v0.4.2
github.com/nats-io/nats v1.7.2
github.com/nats-io/go-nats-streaming v0.4.4 // indirect
github.com/nats-io/nats-server v1.4.1 // indirect
github.com/nats-io/nats-streaming-server v0.14.2 // indirect
github.com/nats-io/nats.go v1.8.0
github.com/nats-io/stan.go v0.4.5
@@ -81,14 +83,14 @@ require (
github.com/streadway/amqp v0.0.0-20190402114354-16ed540749f6
github.com/tidwall/gjson v1.2.1
github.com/tidwall/match v1.0.1 // indirect
github.com/tidwall/pretty v1.0.0 // indirect
github.com/tidwall/sjson v1.0.4
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-20190530122614-20be4c3c3ed5
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2
golang.org/x/tools v0.0.0-20190531223538-26e35f15edef // indirect
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae
google.golang.org/api v0.4.0
gopkg.in/Shopify/sarama.v1 v1.20.0
gopkg.in/olivere/elastic.v5 v5.0.80
+24 -12
View File
@@ -86,6 +86,8 @@ github.com/coredns/coredns v1.4.0 h1:RubBkYmkByUqZWWkjRHvNLnUHgkRVqAWgSMmRFvpE1A
github.com/coredns/coredns v1.4.0/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v0.0.0-20180622175804-8f6348a97d48/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v0.0.0-20180703215944-e4425ee79f2f/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.12+incompatible h1:pAWNwdf7QiT1zfaWyqCtNZQWCLByQyA3JrSQyuYAqnQ=
@@ -273,6 +275,8 @@ github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
@@ -429,8 +433,10 @@ github.com/minio/minio-go v0.0.0-20190327203652-5325257a208f h1:u+iNxfkLrfyWp7Kx
github.com/minio/minio-go v0.0.0-20190327203652-5325257a208f/go.mod h1:/haSOWG8hQNx2+JOfLJ9GKp61EAmgPwRVw/Sac0NzaM=
github.com/minio/minio-go/v6 v6.0.26 h1:nHLr1A+sJBv/sQu6zc5BrHLFAStCXxloC+jmZp4FtW0=
github.com/minio/minio-go/v6 v6.0.26/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
github.com/minio/minio-go/v6 v6.0.27 h1:UxEyhY10E9qog45XmUQyX4npPao0uwLHcoqZcF9gjOI=
github.com/minio/minio-go/v6 v6.0.27/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
github.com/minio/minio-go/v6 v6.0.28 h1:RIsoxMaljP2euGbu2r0gBC0UNn70l2gzHjifU6DhE0c=
github.com/minio/minio-go/v6 v6.0.28/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
github.com/minio/minio-go/v6 v6.0.29 h1:p4YPxK1beY13reFJjCE5QwCnXUMT9D5sV5wl0BSy5Xo=
github.com/minio/minio-go/v6 v6.0.29/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
github.com/minio/parquet-go v0.0.0-20190318185229-9d767baf1679 h1:OMKaN/82sBHUZPvjYNBFituHExa1OGY63eACDGtetKs=
github.com/minio/parquet-go v0.0.0-20190318185229-9d767baf1679/go.mod h1:J+goXSuzlte5imWMqb6cUWC/tbYYysUHctwmKXomYzM=
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
@@ -465,13 +471,19 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/nats-io/gnatsd v1.4.1 h1:RconcfDeWpKCD6QIIwiVFcvForlXpWeJP7i5/lDLy44=
github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ=
github.com/nats-io/go-nats v0.0.0-20161120202126-6b6bf392d34d/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
github.com/nats-io/go-nats v1.7.2 h1:cJujlwCYR8iMz5ofZSD/p2WLW8FabhkQ2lIEVbSvNSA=
github.com/nats-io/go-nats v1.7.2/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
github.com/nats-io/go-nats-streaming v0.0.0-20161216191029-077898146bfb/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo=
github.com/nats-io/go-nats-streaming v0.4.2/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo=
github.com/nats-io/go-nats-streaming v0.4.4 h1:1I3lkZDRdQYXb+holjdqZ2J6xyekrD06o9Fd8rWlgP4=
github.com/nats-io/go-nats-streaming v0.4.4/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo=
github.com/nats-io/nats v0.0.0-20160916181735-70b70be17b77/go.mod h1:PpmYZwlgTfBI56QypJLfIMOfLnMRuVs+VL6r8mQ2SoQ=
github.com/nats-io/nats v1.7.2/go.mod h1:PpmYZwlgTfBI56QypJLfIMOfLnMRuVs+VL6r8mQ2SoQ=
github.com/nats-io/nats-server v1.4.1 h1:Ul1oSOGNV/L8kjr4v6l2f9Yet6WY+LevH1/7cRZ/qyA=
github.com/nats-io/nats-server v1.4.1/go.mod h1:c8f/fHd2B6Hgms3LtCaI7y6pC4WD1f4SUxcCud5vhBc=
github.com/nats-io/nats-streaming-server v0.12.2/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c=
github.com/nats-io/nats-streaming-server v0.14.1/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c=
github.com/nats-io/nats-streaming-server v0.14.2 h1:WjQMDqVOwsI0Nb0E+XmEs1LY17CwHRbTCSTWKhw9fXs=
github.com/nats-io/nats-streaming-server v0.14.2/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c=
github.com/nats-io/nats.go v1.8.0 h1:PXePcr71qzI9MMvQFfV0OBuNItkRQyyZowPfXzvdmVI=
github.com/nats-io/nats.go v1.8.0/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
@@ -609,6 +621,8 @@ github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65 h1:rQ229MBgvW68s1/g6f1/63TgYwYxfF4E+bi/KC19P8g=
github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.0.4 h1:UcdIRXff12Lpnu3OLtZvnc03g4vH2suXDXhBwBqmzYg=
github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
@@ -627,6 +641,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.18.1-0.20181204023538-aab39bd6a98b/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -663,8 +679,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-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/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=
@@ -673,8 +689,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 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
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=
@@ -697,6 +711,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-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00=
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/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=
@@ -737,10 +753,8 @@ 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-20190522044717-8097e1b27ff5 h1:f005F/Jl5JLP036x7QIvUVhNTqxvSYwFIiyOh2q12iU=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 h1:T5DasATyLQfmbTpfEXx/IOL9vfjzW6up+ZDkmHvIf2s=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -761,8 +775,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-20190531223538-26e35f15edef h1:tvvUBWZ5+WFnfcokh8TavIRxYRxUtmB+gQgCytJckhs=
golang.org/x/tools v0.0.0-20190531223538-26e35f15edef/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=
-154
View File
@@ -1,154 +0,0 @@
/*
* MinIO Cloud Storage, (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.
*/
package handlers
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"regexp"
"testing"
)
func wsTestSuccessHandler(w http.ResponseWriter, r *http.Request) {
// A very simple health check.
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
ioutil.ReadAll(r.Body)
// In the future we could report back on the status of our DB, or our cache
// (e.g. Redis) by performing a simple PING, and include them in the response.
io.WriteString(w, `{"success": true}`)
}
func wsTest404Handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
func TestTraceHTTPHandler(t *testing.T) {
logOutput := bytes.NewBuffer([]byte(""))
testCases := []struct {
method string
path string
sentData string
headers map[string]string
handler http.HandlerFunc
expectedStatus int
expectedLogRegexp string
}{
{
method: "PUT",
path: "/test-log",
sentData: "sending data",
headers: map[string]string{"Test-Header": "TestHeaderValue"},
handler: TraceReqHandlerFunc(http.HandlerFunc(wsTestSuccessHandler), logOutput, true),
expectedStatus: http.StatusOK,
expectedLogRegexp: `\[REQUEST github.com/minio/minio/pkg/handlers.wsTestSuccessHandler\] \[[^\]]*\] \[[^\]]*\]
PUT /test-log
Host:\
Test-Header: TestHeaderValue
sending data
\[RESPONSE\] \[[^\]]*\] \[[^\]]*\]
200 OK
{"success": true}
`,
},
{
method: "POST",
path: "/test-log",
handler: TraceReqHandlerFunc(http.HandlerFunc(wsTestSuccessHandler), logOutput, false),
headers: map[string]string{"Test-Header": "TestHeaderValue"},
expectedStatus: http.StatusOK,
expectedLogRegexp: `\[REQUEST github.com/minio/minio/pkg/handlers.wsTestSuccessHandler\] \[[^\]]*\] \[[^\]]*\]
POST /test-log
Host:\
Test-Header: TestHeaderValue
<BODY>
\[RESPONSE\] \[[^\]]*\] \[[^\]]*\]
200 OK
<BODY>
`,
},
{
method: "POST",
path: "/test-log",
handler: TraceReqHandlerFunc(http.HandlerFunc(wsTest404Handler), logOutput, false),
headers: map[string]string{"Test-Header": "TestHeaderValue"},
expectedStatus: http.StatusNotFound,
expectedLogRegexp: `\[REQUEST github.com/minio/minio/pkg/handlers.wsTest404Handler\] \[[^\]]*\] \[[^\]]*\]
POST /test-log
Host:\
Test-Header: TestHeaderValue
<BODY>
\[RESPONSE\] \[[^\]]*\] \[[^\]]*\]
404 Not Found
<BODY>
`,
},
}
for i, testCase := range testCases {
logOutput.Reset()
req, err := http.NewRequest(testCase.method, testCase.path, bytes.NewBuffer([]byte(testCase.sentData)))
if err != nil {
t.Fatalf("Test %d: %v\n", i+1, err)
}
for k, v := range testCase.headers {
req.Header.Set(k, v)
}
rr := httptest.NewRecorder()
handler := testCase.handler
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != testCase.expectedStatus {
t.Errorf("Test %d: handler returned wrong status code: got %v want %v", i+1,
status, testCase.expectedStatus)
}
matched, err := regexp.MatchString(testCase.expectedLogRegexp, logOutput.String())
if err != nil {
t.Fatalf("Test %d: Incorrect regexp: %v", i+1, err)
}
if !matched {
t.Fatalf("Test %d: Unexpected log content, found: `%s`", i+1, logOutput.String())
}
}
}
+30 -7
View File
@@ -20,6 +20,7 @@ import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"os"
@@ -121,14 +122,36 @@ func (o *Opa) IsAllowed(args Args) bool {
}
defer o.args.CloseRespFn(resp.Body)
// Handle OPA response
type opaResponse struct {
Allow bool `json:"allow"`
}
var result opaResponse
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
// Read the body to be saved later.
opaRespBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false
}
return result.Allow
// Handle large OPA responses when OPA URL is of
// form http://localhost:8181/v1/data/httpapi/authz
type opaResultAllow struct {
Result struct {
Allow bool `json:"allow"`
} `json:"result"`
}
// Handle simpler OPA responses when OPA URL is of
// form http://localhost:8181/v1/data/httpapi/authz/allow
type opaResult struct {
Result bool `json:"result"`
}
respBody := bytes.NewReader(opaRespBytes)
var result opaResult
if err = json.NewDecoder(respBody).Decode(&result); err != nil {
respBody.Seek(0, 0)
var resultAllow opaResultAllow
if err = json.NewDecoder(respBody).Decode(&resultAllow); err != nil {
return false
}
return resultAllow.Result.Allow
}
return result.Result
}
+20 -1
View File
@@ -45,7 +45,7 @@ func main() {
|:------------------------------------------|:--------------------------------------------|:-------------------|:----------------------------------|:------------------------|:--------------------------------------|:--------------------------------------------------|
| [`ServiceStatus`](#ServiceStatus) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | |
| [`ServiceSendAction`](#ServiceSendAction) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) |
| | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) |
| [`Trace`](#Trace) | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) |
| | | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | |
@@ -537,3 +537,22 @@ __Example__
log.Println("Profiling data successfully downloaded.")
```
<a name="Trace"></a>
### Trace(allTrace bool,doneCh <-chan struct{}) <-chan TraceInfo
Enable HTTP request tracing on all nodes in a MinIO cluster
__Example__
``` go
doneCh := make(chan struct{})
defer close(doneCh)
// listen to all trace including internal API calls
allTrace := true
// Start listening on all trace activity.
traceCh := madmClnt.Trace(allTrace,doneCh)
for traceInfo := range traceCh {
fmt.Println(traceInfo.String())
}
log.Println("Success")
```
+92
View File
@@ -0,0 +1,92 @@
/*
* 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 madmin
import (
"bufio"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
trace "github.com/minio/minio/pkg/trace"
)
// TraceInfo holds http trace
type TraceInfo struct {
Trace trace.Info
Err error `json:"-"`
}
// Trace - listen on http trace notifications.
func (adm AdminClient) Trace(allTrace bool, doneCh <-chan struct{}) <-chan TraceInfo {
traceInfoCh := make(chan TraceInfo, 1)
// Only success, start a routine to start reading line by line.
go func(traceInfoCh chan<- TraceInfo) {
defer close(traceInfoCh)
for {
urlValues := make(url.Values)
urlValues.Set("all", strconv.FormatBool(allTrace))
reqData := requestData{
relPath: "/v1/trace",
queryValues: urlValues,
}
// Execute GET to call trace handler
resp, err := adm.executeMethod("GET", reqData)
if err != nil {
closeResponse(resp)
return
}
if resp.StatusCode != http.StatusOK {
traceInfoCh <- TraceInfo{Err: httpRespToErrorResponse(resp)}
return
}
// Initialize a new bufio scanner, to read line by line.
bio := bufio.NewScanner(resp.Body)
// Close the response body.
defer resp.Body.Close()
// Unmarshal each line, returns marshaled values.
for bio.Scan() {
var traceRec trace.Info
if err = json.Unmarshal(bio.Bytes(), &traceRec); err != nil {
continue
}
select {
case <-doneCh:
return
case traceInfoCh <- TraceInfo{Trace: traceRec}:
}
}
// Look for any underlying errors.
if err = bio.Err(); err != nil {
// For an unexpected connection drop from server, we close the body
// and re-connect.
if err == io.ErrUnexpectedEOF {
resp.Body.Close()
}
}
}
}(traceInfoCh)
// Returns the trace info channel, for caller to start reading from.
return traceInfoCh
}
+50
View File
@@ -0,0 +1,50 @@
// +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 (
"log"
"github.com/minio/minio/pkg/madmin"
)
func main() {
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
// dummy values, please replace them with original values.
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
// New returns an MinIO Admin client object.
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
if err != nil {
log.Fatalln(err)
}
doneCh := make(chan struct{})
defer close(doneCh)
// Start listening on all http trace activity from all servers
// in the minio cluster.
traceCh := madmClnt.Trace(false, doneCh)
for traceInfo := range traceCh {
if traceInfo.Err != nil {
fmt.Println(traceInfo.Err)
}
fmt.Println(traceInfo)
}
}
+2
View File
@@ -1,3 +1,5 @@
// +build linux
/*
* MinIO Cloud Storage, (C) 2017 MinIO, Inc.
*
+83
View File
@@ -0,0 +1,83 @@
/*
* 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 pubsub
import (
"sync"
)
// PubSub holds publishers and subscribers
type PubSub struct {
subs []chan interface{}
pub chan interface{}
mutex sync.Mutex
}
// process item to subscribers.
func (ps *PubSub) process() {
for item := range ps.pub {
ps.mutex.Lock()
for _, sub := range ps.subs {
go func(s chan interface{}) {
s <- item
}(sub)
}
ps.mutex.Unlock()
}
}
// Publish message to pubsub system
func (ps *PubSub) Publish(item interface{}) {
ps.pub <- item
}
// Subscribe - Adds a subscriber to pubsub system
func (ps *PubSub) Subscribe() chan interface{} {
ps.mutex.Lock()
defer ps.mutex.Unlock()
ch := make(chan interface{})
ps.subs = append(ps.subs, ch)
return ch
}
// Unsubscribe removes current subscriber
func (ps *PubSub) Unsubscribe(ch chan interface{}) {
ps.mutex.Lock()
defer ps.mutex.Unlock()
for i, sub := range ps.subs {
if sub == ch {
close(ch)
ps.subs = append(ps.subs[:i], ps.subs[i+1:]...)
}
}
}
// HasSubscribers returns true if pubsub system has subscribers
func (ps *PubSub) HasSubscribers() bool {
ps.mutex.Lock()
defer ps.mutex.Unlock()
return len(ps.subs) > 0
}
// New inits a PubSub system
func New() *PubSub {
ps := &PubSub{}
ps.pub = make(chan interface{})
go ps.process()
return ps
}
+66
View File
@@ -0,0 +1,66 @@
/*
* 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 pubsub
import (
"fmt"
"testing"
)
func TestSubscribe(t *testing.T) {
ps := New()
ps.Subscribe()
ps.Subscribe()
if len(ps.subs) != 2 {
t.Errorf("expected 2 subscribers")
}
}
func TestUnsubscribe(t *testing.T) {
ps := New()
c1 := ps.Subscribe()
ps.Subscribe()
ps.Unsubscribe(c1)
if len(ps.subs) != 1 {
t.Errorf("expected 1 subscriber")
}
}
func TestPubSub(t *testing.T) {
ps := New()
c1 := ps.Subscribe()
val := "hello"
ps.Publish(val)
msg := <-c1
if msg != "hello" {
t.Errorf(fmt.Sprintf("expected %s , found %s", val, msg))
}
}
func TestMultiPubSub(t *testing.T) {
ps := New()
c1 := ps.Subscribe()
c2 := ps.Subscribe()
val := "hello"
ps.Publish(val)
msg1 := <-c1
msg2 := <-c2
if msg1 != "hello" && msg2 != "hello" {
t.Errorf(fmt.Sprintf("expected both subscribers to have%s , found %s and %s", val, msg1, msg2))
}
}
+1 -3
View File
@@ -18,12 +18,10 @@ package s3select
import (
"bytes"
"go/build"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"reflect"
"testing"
)
@@ -174,7 +172,7 @@ func TestParquetInput(t *testing.T) {
`)
getReader := func(offset int64, length int64) (io.ReadCloser, error) {
testdataFile := path.Join(build.Default.GOPATH, "src/github.com/minio/minio/pkg/s3select/testdata.parquet")
testdataFile := "testdata.parquet"
file, err := os.Open(testdataFile)
if err != nil {
return nil, err
+49
View File
@@ -0,0 +1,49 @@
/*
* 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 trace
import (
"net/http"
"time"
)
// Info - represents a trace record, additionally
// also reports errors if any while listening on trace.
type Info struct {
NodeName string `json:"nodename"`
FuncName string `json:"funcname"`
ReqInfo RequestInfo `json:"request"`
RespInfo ResponseInfo `json:"response"`
}
// RequestInfo represents trace of http request
type RequestInfo struct {
Time time.Time `json:"time"`
Method string `json:"method"`
Path string `json:"path,omitempty"`
RawQuery string `json:"rawquery,omitempty"`
Headers http.Header `json:"headers,omitempty"`
Body []byte `json:"body,omitempty"`
}
// ResponseInfo represents trace of http request
type ResponseInfo struct {
Time time.Time `json:"time"`
Headers http.Header `json:"headers,omitempty"`
Body []byte `json:"body,omitempty"`
StatusCode int `json:"statuscode,omitempty"`
}