Compare commits

...

49 Commits

Author SHA1 Message Date
Harshavardhana b52a3e523c Avoid using fastjson parser pool, move back to jsoniter (#8190)
It looks like from implementation point of view fastjson
parser pool doesn't behave the same way as expected
when dealing many `xl.json` from multiple disks.

The fastjson parser pool usage ends up returning incorrect
xl.json entries for checksums, with references pointing
to older entries. This led to the subtle bug where checksum
info is duplicated from a previous xl.json read of a different
file from different disk.
2019-09-06 04:21:27 +05:30
Praveen raj Mani 428836d4e1 Ignore "connection reset" errors while initializing the targets with queue store enabled (#8185)
Fixes #8178
2019-09-05 13:55:48 -07:00
poornas 259a5d825b cache - fix corruption when client prematurely terminates request (#8155) 2019-09-05 23:33:32 +05:30
poornas 29f64355ce Allow caching on single PutObject (#8100) 2019-09-05 19:50:16 +05:30
Nitish Tiwari 496fba3e9a Return 200 OK for liveness checks while distributed cluster starts (#8176)
With this PR, liveness check responds with 200 OK with "server-not-
initialized" header while objectLayer gets initialized. The header
is removed as objectLayer is initialized. This is to allow
MinIO distributed cluster to get started when running on an
orchestration platforms like Docker Swarm.

This PR also updates sample Swarm yaml files to use correct values
for healthcheck fields.

Fixes #8140
2019-09-05 14:50:56 +05:30
Andreas Auernhammer 810a44e951 KMS Admin-API: add route and handler for KMS key info (#7955)
This commit adds an admin API route and handler for
requesting status information about a KMS key.

Therefore, the client specifies the KMS key ID (when
empty / not set the server takes the currently configured
default key-ID) and the server tries to perform a dummy encryption,
re-wrap and decryption operation. If all three succeed we know that
the server can access the KMS and has permissions to generate, re-wrap
and decrypt data keys (policy is set correctly).
2019-09-05 01:49:44 +05:30
Praveen raj Mani 341d61e3d8 Fix for web-uploads in federated mode (#8175)
Fixes #8173
2019-09-04 23:14:02 +05:30
poornas 8a71b0ec5a Add admin API to send console log messages (#7784)
Utilized by mc admin console command.
2019-09-03 23:40:48 +05:30
Harshavardhana 0772438125 Update orchestration docs to use ellipses syntax (#8153) 2019-09-01 10:02:15 -07:00
Anis Elleuch b3c19e2d4b storage: Expect empty param in REST requests (#8167)
Empty parameter was forgotten to be added to restQueries() function,
scanning with deep parameter wasn't working properly for distributed
setup.
2019-08-31 13:51:25 +05:30
Yao Zongyou ec9bfd3aef speed up the performance of s3select on csv (#7945) 2019-08-31 00:07:40 -07:00
Bala FA fa3546bb03 Add NetPerfInfo() API in madmin (#8112) 2019-08-31 08:27:53 +05:30
Harshavardhana 42e716a094 formatsToDrivesInfo should return drives with correct order (#8157)
This is a defensive change to avoid any future issues,
from this part of the code. New change also ensures
to populate UUID if present for the right disk.
2019-08-30 14:11:18 -07:00
Andreas Auernhammer 6b2ed0fc47 fix DownloadZIP for encrypted objects (#8159)
This commit fixes the web ZIP download handler for
encrypted objects. The decryption logic has moved into
`getObjectNInfo`. So trying to decrypt the (already decrypted)
content again in the ZIP handler obviously causes an error.

This commit fixes this by removing the decryption logic from the
the handler.

Fixes #7965
2019-08-30 10:46:09 -07:00
Harshavardhana 0cd0f6c255 Avoid error modification during IAM migration (#8156)
The underlying errors are important, for IAM
requirements and should wait appropriately at
the caller level, this allows for distributed
setups to run properly and not fail prematurely
during startup.

Also additionally fix the onlineDisk counting
2019-08-30 10:41:02 -07:00
poornas 4553db3872 mint: turn off progress for awscli sync test (#8158)
Bumping up to awscli 1.11.177 which supports --no-progress flag
2019-08-30 10:26:48 -07:00
Aditya Manthramurthy 847a3ea0a2 Add unit tests and refactor to improve coverage (#7617) 2019-08-29 13:53:27 -07:00
Aditya Manthramurthy 1f3d270de8 Fix delete policy routing (#8145) 2019-08-29 07:07:43 +05:30
Minio Trusted daa57e8e5a Update yaml files to latest version RELEASE.2019-08-29T00-25-01Z 2019-08-29 00:30:50 +00:00
Aditya Manthramurthy eb18c82976 Remove policy query param from being rejected for objects (#8144) 2019-08-28 16:58:40 -07:00
Krishna Srinivas 2ab0681c0c Do not ignore Lock()'s return value (#8142) 2019-08-28 16:12:57 -07:00
Harshavardhana 83d4c5763c Decouple ServiceUpdate to ServerUpdate to be more native (#8138)
The change now is to ensure that we take custom URL as
well for updating the deployment, this is required for
hotfix deliveries for certain deployments - other than
the community release.

This commit changes the previous work d65a2c6725
with newer set of requirements.

Also deprecates PeerUptime()
2019-08-28 15:04:43 -07:00
Ashish Kumar Sinha c6f86d35d3 Rectify Examples and README.MD (#8141) 2019-08-28 10:56:02 -07:00
Harshavardhana d65a2c6725 Implement cluster-wide in-place updates (#8070)
This PR is a breaking change and also deprecates
`minio update` command, from this release onwards
all users are advised to just use `mc admin update`
2019-08-27 11:37:47 -07:00
Harshavardhana 70136fb55b Look for network errors appropriately for RemoteStorageAPI (#8128)
net.Error is very unreliable in providing better error
handling, we need to ensure that we always have a fallback
option in case of network failures.

This fixes an important issue in our distributed server
setups when one of the servers is down, all deployments
out there are recommended to upgrade after this fix is
merged to ensure that availability is not lost.

Fixes #8127
Fixes #8016
Fixes #7964
2019-08-25 13:32:49 -07:00
Harshavardhana d6dd98e597 Avoid data-race in getDisksInfo call (#8126) 2019-08-23 17:03:15 -07:00
Krishna Srinivas c38ada1a26 write() to disk in 4MB blocks for better performance (#7888) 2019-08-23 15:36:46 -07:00
poornas 48bc3f1d53 Allow cached content to be encrypted (#8001)
If MINIO_CACHE_ENCRYPTION_MASTER_KEY is set,
automatically encrypt all cached content on disk.
2019-08-23 10:13:22 -07:00
Praveen raj Mani e211f6f52e Parallelize the DiskInfo calls in xl.StorageInfo() (#8115) 2019-08-22 20:02:40 -07:00
Harshavardhana f13f421e84 Allow CopyObject in pathStyle across federated instances (#8064)
Fixes #7976
2019-08-21 22:02:39 -10:00
Aditya Manthramurthy cd03bfb3cf Fix ignoring claims in list buckets call (#8118) 2019-08-21 19:20:11 -10:00
Harshavardhana add1b6cb6b Remove go mod cache due to buggy travis caching (#8113)
There are sporadic failures due to go-mod build
caches on travis, deprecate it since we moved to
using GOPROXY.
2019-08-21 13:38:52 -10:00
Dee Koder 4f3c9d7677 Update doc with Object Tagging not supported (#8116) 2019-08-21 16:10:59 -07:00
poornas 2e19619e79 browser: Avoid logging BucketNotEmpty error (#8110) 2019-08-21 10:01:46 -10:00
Minio Trusted de736f2864 Update yaml files to latest version RELEASE.2019-08-21T19-40-07Z 2019-08-21 19:46:01 +00:00
Harshavardhana 2fa98b1d6a Convert errAuthentication as AccessDenied appropriately (#8105)
Fixes #8062
2019-08-21 09:13:15 +05:30
kannappanr 99a4298938 Use a non-strict invalid bucket name check in Get and Delete object (#8073) 2019-08-20 17:40:52 -10:00
Harshavardhana 069badc7e9 Allow CopyObjectPart to work in federated setups (#8066)
Fixes #8065
2019-08-20 07:19:22 -10:00
Harshavardhana c601cb2f1e Add listBucketObjectsVersions implementation (#8093)
This API implementation simply behaves like listObjects()
but returns back single version for each object, this
implementation should be considered dummy it is only
meant for some applications which rely on this.
2019-08-19 11:02:54 -10:00
Harshavardhana 9ca7470ccc Avoid using jsoniter, move to fastjson (#8063)
This is to avoid using unsafe.Pointer type
code dependency for MinIO, this causes
crashes on ARM64 platforms

Refer #8005 collection of runtime crashes due
to unsafe.Pointer usage incorrectly. We have
seen issues like this before when using
jsoniter library in the past.

This PR hopes to fix this using fastjson
2019-08-19 08:35:52 -10:00
Harshavardhana b3ca304c01 Avoid excessive listing attempts in the daily sweep (#8081)
Add better dynamic timeouts for locks, also
add jitters before launching daily sweep to ensure
that not all the servers in distributed setup
are not trying to hold locks to begin the sweep
round.

Also, add enough delay for incoming requests based
on totalSetCount*totalDriveCount.

A possible fix for #8071
2019-08-19 08:22:32 -10:00
Bala FA 60f52f461f add network read performance collection support. (#8038)
ReST API on /minio/admin/v1/performance?perfType=net[?size=N] 
returns

```
{
  "PEER-1": [
             {
	       "addr": ADDR,
	       "readPerf": DURATION,
	       "error": ERROR,
	     },
	     ...
	   ],
  ...
  ...
  "PEER-N": [
             {
	       "addr": ADDR,
	       "readPerf": DURATION,
	       "error": ERROR,
	     },
	     ...
	   ]
}
```
2019-08-19 08:26:32 +05:30
Nitish Tiwari e5fb6294a7 Remove healthcheck script for Docker image (#8095)
There are multiple possibilities for running MinIO within
a container e.g. configurable address, non-root user etc.
This makes it difficult to identify actual IP / Port to
use to check healthcheck status from within a container.

It is simpler to use external healthcheck mechanisms
like healthcheck command in docker-compose to check
for MinIO health status. This is similar to how checks
work in Kubernetes as well.

This PR removes the healthcheck script used inside
Docker container and ad documentation on how to
use docker-compose based healthcheck mechanism.
2019-08-17 12:44:04 -07:00
Harshavardhana a15bb19d37 Allow audit logging to work while tracing (#8077)
It is observed that when `mc admin trace` is being
used due to ResponseWriter wrapper, we loose information
about statusCode,statusText for audit logging.

This PR fixes this behavior
2019-08-15 16:17:46 -07:00
poornas 7bf1caa0fe Fix broken link to go install docs (#8090) 2019-08-15 16:00:50 -07:00
Harshavardhana 6e7962bf35 Return if paths are empty in DeleteFileBulk (#8085)
This avoids a network call, also fixes an issue
when empty paths are passed the underlying call
fails with "405 Method Not Allowed".

This is reproducible when you are deleting a
non-existent object.

Fixes #8083
2019-08-15 13:15:49 -07:00
Aditya Manthramurthy 825e29f301 Check if user or group is disabled when evaluating policy (#8078) 2019-08-14 16:59:16 -07:00
Harshavardhana cebeca3075 Fix multi-user doc to reflect the latest change 2019-08-14 14:12:23 -07:00
Minio Trusted 26640033b0 Update yaml files to latest version RELEASE.2019-08-14T20-37-41Z 2019-08-14 20:42:16 +00:00
136 changed files with 3031 additions and 1622 deletions
-1
View File
@@ -22,4 +22,3 @@ prime/
stage/
.sia_temp/
config.json
dockerscripts/healthcheck
+2 -10
View File
@@ -27,15 +27,7 @@ matrix:
- CGO_ENABLED=0
- GO111MODULE=on
- GOPROXY=https://proxy.golang.org
# Enable build cache
# https://restic.net/blog/2018-09-02/travis-build-cache
cache:
directories:
- $HOME/.cache/go-build
- $HOME/gopath/pkg/mod
- $HOME/go/pkg/mod
go: 1.12.5
go: 1.12.x
script:
- make
- diff -au <(gofmt -s -d cmd) <(printf "")
@@ -54,7 +46,7 @@ matrix:
- CGO_ENABLED=0
- GO111MODULE=on
- GOPROXY=https://proxy.golang.org
go: 1.12.5
go: 1.12.x
script:
- go build --ldflags="$(go run buildscripts/gen-ldflags.go)" -o %GOPATH%\bin\minio.exe
- bash buildscripts/go-coverage.sh
+1 -5
View File
@@ -9,8 +9,7 @@ ENV GO111MODULE on
RUN \
apk add --no-cache git && \
git clone https://github.com/minio/minio && cd minio && \
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \
cd dockerscripts; go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
FROM alpine:3.9
@@ -22,7 +21,6 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
EXPOSE 9000
COPY --from=0 /go/bin/minio /usr/bin/minio
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
RUN \
@@ -33,6 +31,4 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"]
HEALTHCHECK --interval=1m CMD healthcheck
CMD ["minio"]
+2 -5
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 /usr/bin/
COPY minio /usr/bin/
ENV MINIO_UPDATE off
@@ -14,8 +14,7 @@ RUN \
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
chmod +x /usr/bin/minio && \
chmod +x /usr/bin/docker-entrypoint.sh && \
chmod +x /usr/bin/healthcheck
chmod +x /usr/bin/docker-entrypoint.sh
EXPOSE 9000
@@ -23,6 +22,4 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"]
HEALTHCHECK --interval=1m CMD healthcheck
CMD ["minio"]
+2 -7
View File
@@ -6,14 +6,12 @@ ENV GO111MODULE on
RUN \
apk add --no-cache git && \
git clone https://github.com/minio/minio && cd minio/dockerscripts && \
go build -tags kqueue -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
git clone https://github.com/minio/minio
FROM alpine:3.9
LABEL maintainer="MinIO Inc <dev@min.io>"
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
ENV MINIO_UPDATE off
@@ -26,8 +24,7 @@ RUN \
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/docker-entrypoint.sh
EXPOSE 9000
@@ -35,6 +32,4 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"]
HEALTHCHECK --interval=1m CMD healthcheck
CMD ["minio"]
-1
View File
@@ -72,7 +72,6 @@ coverage: build
build: checks
@echo "Building minio binary to './minio'"
@GOPROXY=https://proxy.golang.org GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio 1>/dev/null
@GOPROXY=https://proxy.golang.org GO111MODULE=on GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/dockerscripts/healthcheck $(PWD)/dockerscripts/healthcheck.go 1>/dev/null
docker: build
@docker build -t $(TAG) . -f Dockerfile.dev
+1 -1
View File
@@ -75,7 +75,7 @@ service minio start
## 使用源码安装
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://docs.min.io/docs/how-to-install-golang).
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install).
```sh
go get -u github.com/minio/minio
+1 -1
View File
@@ -13,7 +13,7 @@ nvm install stable
### Install `go-bindata` and `go-bindata-assetfs`
If you do not have a working Golang environment, please follow [Install Golang](https://docs.min.io/docs/how-to-install-golang)
If you do not have a working Golang environment, please follow [Install Golang](https://golang.org/doc/install)
```sh
go get github.com/go-bindata/go-bindata/go-bindata
+1 -1
View File
@@ -119,7 +119,7 @@ assert_is_supported_os() {
assert_check_golang_env() {
if ! which go >/dev/null 2>&1; then
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://docs.min.io/docs/how-to-install-golang"
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
exit 1
fi
+377 -93
View File
@@ -1,5 +1,5 @@
/*
* MinIO Cloud Storage, (C) 2016, 2017, 2018, 2019 MinIO, Inc.
* MinIO Cloud Storage, (C) 2016-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.
@@ -19,22 +19,29 @@ package cmd
import (
"bytes"
"context"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"runtime"
"sort"
"strconv"
"strings"
"time"
humanize "github.com/dustin/go-humanize"
"github.com/gorilla/mux"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/cpu"
@@ -50,6 +57,7 @@ import (
const (
maxEConfigJSONSize = 262272
defaultNetPerfSize = 100 * humanize.MiByte
)
// Type-safe query params.
@@ -64,107 +72,139 @@ const (
mgmtForceStop = "forceStop"
)
var (
// This struct literal represents the Admin API version that
// the server uses.
adminAPIVersionInfo = madmin.AdminAPIVersionInfo{
Version: "1",
func updateServer(updateURL, sha256Hex string, latestReleaseTime time.Time) (us madmin.ServerUpdateStatus, err error) {
minioMode := getMinioMode()
// No inputs provided we should try to update using the default URL.
if updateURL == "" && sha256Hex == "" && latestReleaseTime.IsZero() {
var updateMsg string
updateMsg, sha256Hex, _, latestReleaseTime, err = getUpdateInfo(updateTimeout, minioMode)
if err != nil {
return us, err
}
if updateMsg == "" {
us.CurrentVersion = Version
us.UpdatedVersion = Version
return us, nil
}
if runtime.GOOS == "windows" {
updateURL = minioReleaseURL + "minio.exe"
} else {
updateURL = minioReleaseURL + "minio"
}
}
)
if err = doUpdate(updateURL, sha256Hex, minioMode); err != nil {
return us, err
}
us.CurrentVersion = Version
us.UpdatedVersion = latestReleaseTime.Format(minioReleaseTagTimeLayout)
return us, nil
}
// VersionHandler - GET /minio/admin/version
// -----------
// Returns Administration API version
func (a adminAPIHandlers) VersionHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "Version")
// ServerUpdateHandler - POST /minio/admin/v1/update?updateURL={updateURL}
// ----------
// updates all minio servers and restarts them gracefully.
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ServerUpdate")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
jsonBytes, err := json.Marshal(adminAPIVersionInfo)
if globalInplaceUpdateDisabled {
// if MINIO_UPDATE=off - inplace update is disabled, mostly
// in containers.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
updateURL := vars[peerRESTUpdateURL]
mode := getMinioMode()
var sha256Hex string
var latestReleaseTime time.Time
if updateURL != "" {
u, err := url.Parse(updateURL)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
content, err := downloadReleaseURL(updateURL, updateTimeout, mode)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
sha256Hex, latestReleaseTime, err = parseReleaseData(content)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if runtime.GOOS == "windows" {
u.Path = path.Dir(u.Path) + "minio.exe"
} else {
u.Path = path.Dir(u.Path) + "minio"
}
updateURL = u.String()
}
for _, nerr := range globalNotificationSys.ServerUpdate(updateURL, sha256Hex, latestReleaseTime) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
updateStatus, err := updateServer(updateURL, sha256Hex, latestReleaseTime)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, jsonBytes)
}
// ServiceStatusHandler - GET /minio/admin/v1/service
// ----------
// Returns server version and uptime.
func (a adminAPIHandlers) ServiceStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ServiceStatus")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
// Fetch server version
serverVersion := madmin.ServerVersion{
Version: Version,
CommitID: CommitID,
}
// Fetch uptimes from all peers and pick the latest.
uptime := getPeerUptimes(globalNotificationSys.ServerInfo(ctx))
// Create API response
serverStatus := madmin.ServiceStatus{
ServerVersion: serverVersion,
Uptime: uptime,
}
// Marshal API response
jsonBytes, err := json.Marshal(serverStatus)
jsonBytes, err := json.Marshal(updateStatus)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Reply with storage information (across nodes in a
// distributed setup) as json.
writeSuccessResponseJSON(w, jsonBytes)
if updateStatus.CurrentVersion != updateStatus.UpdatedVersion {
// We did upgrade - restart all services.
globalServiceSignalCh <- serviceRestart
}
}
// ServiceStopNRestartHandler - POST /minio/admin/v1/service
// Body: {"action": <restart-action>}
// ServiceActionHandler - POST /minio/admin/v1/service?action={action}
// ----------
// Restarts/Stops minio server gracefully. In a distributed setup,
// restarts all the servers in the cluster.
func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ServiceStopNRestart")
// restarts/stops minio server gracefully. In a distributed setup,
func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ServiceAction")
vars := mux.Vars(r)
action := vars["action"]
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
var sa madmin.ServiceAction
err := json.NewDecoder(r.Body).Decode(&sa)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrRequestBodyParse), r.URL)
return
}
var serviceSig serviceSignal
switch sa.Action {
case madmin.ServiceActionValueRestart:
switch madmin.ServiceAction(action) {
case madmin.ServiceActionRestart:
serviceSig = serviceRestart
case madmin.ServiceActionValueStop:
case madmin.ServiceActionStop:
serviceSig = serviceStop
default:
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action))
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
logger.LogIf(ctx, errors.New("Invalid service action received"))
return
}
// Reply to the client before restarting minio server.
writeSuccessResponseHeadersOnly(w)
// Notify all other MinIO peers signal service.
for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
if nerr.Err != nil {
@@ -173,6 +213,9 @@ func (a adminAPIHandlers) ServiceStopNRestartHandler(w http.ResponseWriter, r *h
}
}
// Reply to the client before restarting, stopping MinIO server.
writeSuccessResponseHeadersOnly(w)
globalServiceSignalCh <- serviceSig
}
@@ -304,6 +347,13 @@ type ServerMemUsageInfo struct {
HistoricUsage []mem.Usage `json:"historicUsage"`
}
// ServerNetReadPerfInfo network read performance information.
type ServerNetReadPerfInfo struct {
Addr string `json:"addr"`
ReadPerf time.Duration `json:"readPerf"`
Error string `json:"error,omitempty"`
}
// PerfInfoHandler - GET /minio/admin/v1/performance?perfType={perfType}
// ----------
// Get all performance information based on input type
@@ -318,6 +368,43 @@ func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request
vars := mux.Vars(r)
switch perfType := vars["perfType"]; perfType {
case "net":
var size int64 = defaultNetPerfSize
if sizeStr, found := vars["size"]; found {
var err error
if size, err = strconv.ParseInt(sizeStr, 10, 64); err != nil || size < 0 {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
return
}
}
if !globalIsDistXL {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(globalEndpoints)
}
infos := map[string][]ServerNetReadPerfInfo{}
infos[addr] = globalNotificationSys.NetReadPerfInfo(size)
for peer, info := range globalNotificationSys.CollectNetPerfInfo(size) {
infos[peer] = info
}
// Marshal API response
jsonBytes, err := json.Marshal(infos)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Reply with performance information (across nodes in a
// distributed setup) as json.
writeSuccessResponseJSON(w, jsonBytes)
case "drive":
info := objectAPI.StorageInfo(ctx)
if !(info.Backend.Type == BackendFS || info.Backend.Type == BackendErasure) {
@@ -391,7 +478,6 @@ func newLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEntr
}
func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries {
const listCount int = 10
entryMap := make(map[string]*madmin.LockEntry)
for _, peerLock := range peerLocks {
if peerLock == nil {
@@ -412,6 +498,7 @@ func topLockEntries(peerLocks []*PeerLocks) madmin.LockEntries {
lockEntries = append(lockEntries, *v)
}
sort.Sort(lockEntries)
const listCount int = 10
if len(lockEntries) > listCount {
lockEntries = lockEntries[:listCount]
}
@@ -569,45 +656,60 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt
}
}
type healInitParams struct {
bucket, objPrefix string
hs madmin.HealOpts
clientToken string
forceStart, forceStop bool
}
// extractHealInitParams - Validates params for heal init API.
func extractHealInitParams(r *http.Request) (bucket, objPrefix string,
hs madmin.HealOpts, clientToken string, forceStart bool, forceStop bool,
err APIErrorCode) {
func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reader) (hip healInitParams, err APIErrorCode) {
hip.bucket = vars[string(mgmtBucket)]
hip.objPrefix = vars[string(mgmtPrefix)]
vars := mux.Vars(r)
bucket = vars[string(mgmtBucket)]
objPrefix = vars[string(mgmtPrefix)]
if bucket == "" {
if objPrefix != "" {
if hip.bucket == "" {
if hip.objPrefix != "" {
// Bucket is required if object-prefix is given
err = ErrHealMissingBucket
return
}
} else if isReservedOrInvalidBucket(bucket, false) {
} else if isReservedOrInvalidBucket(hip.bucket, false) {
err = ErrInvalidBucketName
return
}
// empty prefix is valid.
if !IsValidObjectPrefix(objPrefix) {
if !IsValidObjectPrefix(hip.objPrefix) {
err = ErrInvalidObjectName
return
}
qParms := r.URL.Query()
if len(qParms[string(mgmtClientToken)]) > 0 {
clientToken = qParms[string(mgmtClientToken)][0]
hip.clientToken = qParms[string(mgmtClientToken)][0]
}
if _, ok := qParms[string(mgmtForceStart)]; ok {
forceStart = true
hip.forceStart = true
}
if _, ok := qParms[string(mgmtForceStop)]; ok {
forceStop = true
hip.forceStop = true
}
// Invalid request conditions:
//
// Cannot have both forceStart and forceStop in the same
// request; If clientToken is provided, request can only be
// to continue receiving logs, so it cannot be start or
// stop;
if (hip.forceStart && hip.forceStop) ||
(hip.clientToken != "" && (hip.forceStart || hip.forceStop)) {
err = ErrInvalidRequest
return
}
// ignore body if clientToken is provided
if clientToken == "" {
jerr := json.NewDecoder(r.Body).Decode(&hs)
if hip.clientToken == "" {
jerr := json.NewDecoder(r).Decode(&hip.hs)
if jerr != nil {
logger.LogIf(context.Background(), jerr)
err = ErrRequestBodyParse
@@ -646,7 +748,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
return
}
bucket, objPrefix, hs, clientToken, forceStart, forceStop, errCode := extractHealInitParams(r)
hip, errCode := extractHealInitParams(mux.Vars(r), r.URL.Query(), r.Body)
if errCode != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL)
return
@@ -720,8 +822,8 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
info := objectAPI.StorageInfo(ctx)
numDisks := info.Backend.OfflineDisks + info.Backend.OnlineDisks
healPath := pathJoin(bucket, objPrefix)
if clientToken == "" && !forceStart && !forceStop {
healPath := pathJoin(hip.bucket, hip.objPrefix)
if hip.clientToken == "" && !hip.forceStart && !hip.forceStop {
nh, exists := globalAllHealState.getHealSequence(healPath)
if exists && !nh.hasEnded() && len(nh.currentStatus.Items) > 0 {
b, err := json.Marshal(madmin.HealStartSuccess{
@@ -740,11 +842,11 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
}
}
if clientToken != "" && !forceStart && !forceStop {
if hip.clientToken != "" && !hip.forceStart && !hip.forceStop {
// Since clientToken is given, fetch heal status from running
// heal sequence.
respBytes, errCode := globalAllHealState.PopHealStatusJSON(
healPath, clientToken)
healPath, hip.clientToken)
if errCode != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(errCode), r.URL)
} else {
@@ -755,14 +857,14 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
respCh := make(chan healResp)
switch {
case forceStop:
case hip.forceStop:
go func() {
respBytes, apiErr := globalAllHealState.stopHealSequence(healPath)
hr := healResp{respBytes: respBytes, apiErr: apiErr}
respCh <- hr
}()
case clientToken == "":
nh := newHealSequence(bucket, objPrefix, handlers.GetSourceIP(r), numDisks, hs, forceStart)
case hip.clientToken == "":
nh := newHealSequence(hip.bucket, hip.objPrefix, handlers.GetSourceIP(r), numDisks, hip.hs, hip.forceStart)
go func() {
respBytes, apiErr, errMsg := globalAllHealState.LaunchNewHealSequence(nh)
hr := healResp{respBytes, apiErr, errMsg}
@@ -942,6 +1044,24 @@ func (a adminAPIHandlers) GetConfigKeysHandler(w http.ResponseWriter, r *http.Re
writeSuccessResponseJSON(w, []byte(econfigData))
}
// AdminError - is a generic error for all admin APIs.
type AdminError struct {
Code string
Message string
StatusCode int
}
func (ae AdminError) Error() string {
return ae.Message
}
// Admin API errors
const (
AdminUpdateUnexpectedFailure = "XMinioAdminUpdateUnexpectedFailure"
AdminUpdateURLNotReachable = "XMinioAdminUpdateURLNotReachable"
AdminUpdateApplyFailure = "XMinioAdminUpdateApplyFailure"
)
// toAdminAPIErrCode - converts errXLWriteQuorum error to admin API
// specific error.
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
@@ -954,7 +1074,21 @@ func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
}
func toAdminAPIErr(ctx context.Context, err error) APIError {
return errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err))
if err == nil {
return noError
}
apiErr := errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err))
if apiErr.Code == "InternalError" {
switch e := err.(type) {
case AdminError:
apiErr = APIError{
Code: e.Code,
Description: e.Message,
HTTPStatusCode: e.StatusCode,
}
}
}
return apiErr
}
// RemoveUser - DELETE /minio/admin/v1/remove-user?accessKey=<access_key>
@@ -1492,7 +1626,10 @@ func convertValueType(elem []byte, jsonType gjson.Type) (interface{}, error) {
case gjson.False, gjson.True:
return strconv.ParseBool(str)
case gjson.JSON:
return gjson.Parse(str).Value(), nil
if gjson.Valid(str) {
return gjson.Parse(str).Value(), nil
}
return nil, errors.New("invalid json")
case gjson.String:
return str, nil
case gjson.Number:
@@ -1682,3 +1819,150 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
}
}
}
// The handler sends console logs to the connected HTTP client.
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ConsoleLog")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
node := r.URL.Query().Get("node")
// limit buffered console entries if client requested it.
limitStr := r.URL.Query().Get("limit")
limitLines, err := strconv.Atoi(limitStr)
if err != nil {
limitLines = 10
}
// 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")
w.Header().Set(xhttp.ContentType, "text/event-stream")
doneCh := make(chan struct{})
defer close(doneCh)
logCh := make(chan interface{}, 4000)
remoteHosts := getRemoteHosts(globalEndpoints)
peers, err := getRestClients(remoteHosts)
if err != nil {
return
}
globalConsoleSys.Subscribe(logCh, doneCh, node, limitLines, nil)
for _, peer := range peers {
if node == "" || strings.EqualFold(peer.host.Name, node) {
peer.ConsoleLog(logCh, doneCh)
}
}
enc := json.NewEncoder(w)
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
defer keepAliveTicker.Stop()
for {
select {
case entry := <-logCh:
log := entry.(madmin.LogInfo)
if log.SendLog(node) {
if err := enc.Encode(log); err != nil {
return
}
w.(http.Flusher).Flush()
}
case <-keepAliveTicker.C:
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
case <-GlobalServiceDoneCh:
return
}
}
}
// KMSKeyStatusHandler - GET /minio/admin/v1/kms/key/status?key-id=<master-key-id>
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "KMSKeyStatusHandler")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
if GlobalKMS == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
return
}
keyID := r.URL.Query().Get("key-id")
if keyID == "" {
keyID = globalKMSKeyID
}
var response = madmin.KMSKeyStatus{
KeyID: keyID,
}
kmsContext := crypto.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
// 1. Generate a new key using the KMS.
key, sealedKey, err := GlobalKMS.GenerateKey(keyID, kmsContext)
if err != nil {
response.EncryptionErr = err.Error()
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
// 2. Check whether we can update / re-wrap the sealed key.
sealedKey, err = GlobalKMS.UpdateKey(keyID, sealedKey, kmsContext)
if err != nil {
response.UpdateErr = err.Error()
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
// 3. Verify that we can indeed decrypt the (encrypted) key
decryptedKey, err := GlobalKMS.UnsealKey(keyID, sealedKey, kmsContext)
if err != nil {
response.DecryptionErr = err.Error()
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
// 4. Compare generated key with decrypted key
if subtle.ConstantTimeCompare(key[:], decryptedKey[:]) != 1 {
response.DecryptionErr = "The generated and the decrypted data key do not match"
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
return
}
resp, err := json.Marshal(response)
if err != nil {
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
return
}
writeSuccessResponseJSON(w, resp)
}
+162 -123
View File
@@ -1,5 +1,5 @@
/*
* MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc.
* MinIO Cloud Storage, (C) 2016-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.
@@ -25,13 +25,16 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"sync"
"testing"
"time"
"github.com/gorilla/mux"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/madmin"
"github.com/tidwall/gjson"
)
var (
@@ -344,117 +347,35 @@ func initTestXLObjLayer() (ObjectLayer, []string, error) {
return objLayer, xlDirs, nil
}
func TestAdminVersionHandler(t *testing.T) {
adminTestBed, err := prepareAdminXLTestBed()
if err != nil {
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
}
defer adminTestBed.TearDown()
req, err := newTestRequest("GET", "/minio/admin/version", 0, nil)
if err != nil {
t.Fatalf("Failed to construct request - %v", err)
}
cred := globalServerConfig.GetCredential()
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
if err != nil {
t.Fatalf("Failed to sign request - %v", err)
}
rec := httptest.NewRecorder()
adminTestBed.router.ServeHTTP(rec, req)
if http.StatusOK != rec.Code {
t.Errorf("Unexpected status code - got %d but expected %d",
rec.Code, http.StatusOK)
}
var result madmin.AdminAPIVersionInfo
err = json.NewDecoder(rec.Body).Decode(&result)
if err != nil {
t.Errorf("json parse err: %v", err)
}
if result != adminAPIVersionInfo {
t.Errorf("unexpected version: %v", result)
}
}
// cmdType - Represents different service subcomands like status, stop
// and restart.
type cmdType int
const (
statusCmd cmdType = iota
restartCmd
restartCmd cmdType = iota
stopCmd
setCreds
)
// String - String representation for cmdType
func (c cmdType) String() string {
switch c {
case statusCmd:
return "status"
case restartCmd:
return "restart"
case stopCmd:
return "stop"
case setCreds:
return "set-credentials"
}
return ""
}
// apiMethod - Returns the HTTP method corresponding to the admin REST
// API for a given cmdType value.
func (c cmdType) apiMethod() string {
switch c {
case statusCmd:
return "GET"
case restartCmd:
return "POST"
case stopCmd:
return "POST"
case setCreds:
return "PUT"
}
return "GET"
}
// apiEndpoint - Return endpoint for each admin REST API mapped to a
// command here.
func (c cmdType) apiEndpoint() string {
switch c {
case statusCmd, restartCmd, stopCmd:
return "/minio/admin/v1/service"
case setCreds:
return "/minio/admin/v1/config/credential"
}
return ""
}
// toServiceSignal - Helper function that translates a given cmdType
// value to its corresponding serviceSignal value.
func (c cmdType) toServiceSignal() serviceSignal {
switch c {
case statusCmd:
return serviceStatus
case restartCmd:
return serviceRestart
case stopCmd:
return serviceStop
}
return serviceStatus
return serviceRestart
}
func (c cmdType) toServiceActionValue() madmin.ServiceActionValue {
func (c cmdType) toServiceAction() madmin.ServiceAction {
switch c {
case restartCmd:
return madmin.ServiceActionValueRestart
return madmin.ServiceActionRestart
case stopCmd:
return madmin.ServiceActionValueStop
return madmin.ServiceActionStop
}
return madmin.ServiceActionValueStop
return madmin.ServiceActionRestart
}
// testServiceSignalReceiver - Helper function that simulates a
@@ -469,19 +390,15 @@ func testServiceSignalReceiver(cmd cmdType, t *testing.T) {
// getServiceCmdRequest - Constructs a management REST API request for service
// subcommands for a given cmdType value.
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials, body []byte) (*http.Request, error) {
req, err := newTestRequest(cmd.apiMethod(), cmd.apiEndpoint(), 0, nil)
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, error) {
queryVal := url.Values{}
queryVal.Set("action", string(cmd.toServiceAction()))
resource := "/minio/admin/v1/service?" + queryVal.Encode()
req, err := newTestRequest(http.MethodPost, resource, 0, nil)
if err != nil {
return nil, err
}
// Set body
req.Body = ioutil.NopCloser(bytes.NewReader(body))
req.ContentLength = int64(len(body))
// Set sha-sum header
req.Header.Set("X-Amz-Content-Sha256", getSHA256Hash(body))
// management REST API uses signature V4 for authentication.
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
if err != nil {
@@ -517,13 +434,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
}
credentials := globalServerConfig.GetCredential()
body, err := json.Marshal(madmin.ServiceAction{
Action: cmd.toServiceActionValue()})
if err != nil {
t.Fatalf("JSONify error: %v", err)
}
req, err := getServiceCmdRequest(cmd, credentials, body)
req, err := getServiceCmdRequest(cmd, credentials)
if err != nil {
t.Fatalf("Failed to build service status request %v", err)
}
@@ -531,19 +442,6 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
rec := httptest.NewRecorder()
adminTestBed.router.ServeHTTP(rec, req)
if cmd == statusCmd {
expectedInfo := madmin.ServiceStatus{
ServerVersion: madmin.ServerVersion{Version: Version, CommitID: CommitID},
}
receivedInfo := madmin.ServiceStatus{}
if jsonErr := json.Unmarshal(rec.Body.Bytes(), &receivedInfo); jsonErr != nil {
t.Errorf("Failed to unmarshal StorageInfo - %v", jsonErr)
}
if expectedInfo.ServerVersion != receivedInfo.ServerVersion {
t.Errorf("Expected storage info and received storage info differ, %v %v", expectedInfo, receivedInfo)
}
}
if rec.Code != http.StatusOK {
resp, _ := ioutil.ReadAll(rec.Body)
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
@@ -554,11 +452,6 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
wg.Wait()
}
// Test for service status management REST API.
func TestServiceStatusHandler(t *testing.T) {
testServicesCmdHandler(statusCmd, t)
}
// Test for service restart management REST API.
func TestServiceRestartHandler(t *testing.T) {
testServicesCmdHandler(restartCmd, t)
@@ -760,3 +653,149 @@ func TestToAdminAPIErrCode(t *testing.T) {
}
}
}
func TestTopLockEntries(t *testing.T) {
t1 := UTCNow()
t2 := UTCNow().Add(10 * time.Second)
peerLocks := []*PeerLocks{
{
Addr: "1",
Locks: map[string][]lockRequesterInfo{
"1": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
"2": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
},
},
{
Addr: "2",
Locks: map[string][]lockRequesterInfo{
"1": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
"2": {
{false, "node2", "ep2", "2", t2, t2, ""},
{true, "node1", "ep1", "1", t1, t1, ""},
},
},
},
}
les := topLockEntries(peerLocks)
if len(les) != 2 {
t.Fatalf("Did not get 2 results")
}
if les[0].Timestamp.After(les[1].Timestamp) {
t.Fatalf("Got wrong sorted value")
}
}
func TestExtractHealInitParams(t *testing.T) {
mkParams := func(clientToken string, forceStart, forceStop bool) url.Values {
v := url.Values{}
if clientToken != "" {
v.Add(string(mgmtClientToken), clientToken)
}
if forceStart {
v.Add(string(mgmtForceStart), "")
}
if forceStop {
v.Add(string(mgmtForceStop), "")
}
return v
}
qParmsArr := []url.Values{
// Invalid cases
mkParams("", true, true),
mkParams("111", true, true),
mkParams("111", true, false),
mkParams("111", false, true),
// Valid cases follow
mkParams("", true, false),
mkParams("", false, true),
mkParams("", false, false),
mkParams("111", false, false),
}
varsArr := []map[string]string{
// Invalid cases
{string(mgmtPrefix): "objprefix"},
// Valid cases
{},
{string(mgmtBucket): "bucket"},
{string(mgmtBucket): "bucket", string(mgmtPrefix): "objprefix"},
}
// Body is always valid - we do not test JSON decoding.
body := `{"recursive": false, "dryRun": true, "remove": false, "scanMode": 0}`
// Test all combinations!
for pIdx, parms := range qParmsArr {
for vIdx, vars := range varsArr {
_, err := extractHealInitParams(vars, parms, bytes.NewBuffer([]byte(body)))
isErrCase := false
if pIdx < 4 || vIdx < 1 {
isErrCase = true
}
if err != ErrNone && !isErrCase {
t.Errorf("Got unexpected error: %v %v %v", pIdx, vIdx, err)
} else if err == ErrNone && isErrCase {
t.Errorf("Got no error but expected one: %v %v", pIdx, vIdx)
}
}
}
}
func TestNormalizeJSONKey(t *testing.T) {
cases := []struct {
input string
correctResult string
}{
{"notify.webhook.1", "notify.webhook.:1"},
{"notify.webhook.ok", "notify.webhook.ok"},
{"notify.webhook.1.2", "notify.webhook.:1.:2"},
{"abc", "abc"},
}
for i, tcase := range cases {
res := normalizeJSONKey(tcase.input)
if res != tcase.correctResult {
t.Errorf("Case %d: failed to match", i)
}
}
}
func TestConvertValueType(t *testing.T) {
cases := []struct {
elem []byte
jt gjson.Type
correctResult interface{}
isError bool
}{
{[]byte(""), gjson.Null, nil, false},
{[]byte("t"), gjson.False, true, false},
{[]byte("f"), gjson.False, false, false},
{[]byte(`{"a": 1}`), gjson.JSON, map[string]interface{}{"a": float64(1)}, false},
{[]byte(`abc`), gjson.String, "abc", false},
{[]byte(`1`), gjson.Number, float64(1), false},
{[]byte(`a1`), gjson.Number, nil, true},
{[]byte(`{"A": `), gjson.JSON, nil, true},
{[]byte(`2`), gjson.False, nil, true},
}
for i, tc := range cases {
res, err := convertValueType(tc.elem, tc.jt)
if err != nil {
if !tc.isError {
t.Errorf("Case %d: got an error when none was expected", i)
}
} else if err == nil && tc.isError {
t.Errorf("Case %d: got no error though we expected one", i)
} else if !reflect.DeepEqual(res, tc.correctResult) {
t.Errorf("Case %d: result mismatch - got %#v", i, res)
}
}
}
+12 -7
View File
@@ -38,17 +38,14 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
adminRouter := router.PathPrefix(adminAPIPathPrefix).Subrouter()
// Version handler
adminRouter.Methods(http.MethodGet).Path("/version").HandlerFunc(httpTraceAll(adminAPI.VersionHandler))
adminV1Router := adminRouter.PathPrefix("/v1").Subrouter()
/// Service operations
// Service status
adminV1Router.Methods(http.MethodGet).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStatusHandler))
// Service restart and stop - TODO
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceStopNRestartHandler))
// Restart and stop MinIO service.
adminV1Router.Methods(http.MethodPost).Path("/service").HandlerFunc(httpTraceAll(adminAPI.ServiceActionHandler)).Queries("action", "{action:.*}")
// Update MinIO servers.
adminV1Router.Methods(http.MethodPost).Path("/update").HandlerFunc(httpTraceAll(adminAPI.ServerUpdateHandler)).Queries("updateURL", "{updateURL:.*}")
// Info operations
adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
@@ -138,6 +135,14 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
// HTTP Trace
adminV1Router.Methods(http.MethodGet).Path("/trace").HandlerFunc(adminAPI.TraceHandler)
// Console Logs
adminV1Router.Methods(http.MethodGet).Path("/log").HandlerFunc(httpTraceAll(adminAPI.ConsoleLogHandler))
// -- KMS APIs --
//
adminV1Router.Methods(http.MethodGet).Path("/kms/key/status").HandlerFunc(httpTraceAll(adminAPI.KMSKeyStatusHandler))
// If none of the routes match, return error.
adminV1Router.NotFoundHandler = http.HandlerFunc(httpTraceHdrs(notFoundHandlerJSON))
}
+2
View File
@@ -1526,6 +1526,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrEntityTooLarge
case errDataTooSmall:
apiErr = ErrEntityTooSmall
case errAuthentication:
apiErr = ErrAccessDenied
case auth.ErrInvalidAccessKeyLength:
apiErr = ErrAdminInvalidAccessKey
case auth.ErrInvalidSecretKeyLength:
+21
View File
@@ -42,6 +42,27 @@ func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string,
return
}
func getListBucketObjectVersionsArgs(values url.Values) (prefix, marker, delimiter string, maxkeys int, encodingType, versionIDMarker string, errCode APIErrorCode) {
errCode = ErrNone
if values.Get("max-keys") != "" {
var err error
if maxkeys, err = strconv.Atoi(values.Get("max-keys")); err != nil {
errCode = ErrInvalidMaxKeys
return
}
} else {
maxkeys = maxObjectList
}
prefix = values.Get("prefix")
marker = values.Get("key-marker")
delimiter = values.Get("delimiter")
encodingType = values.Get("encoding-type")
versionIDMarker = values.Get("version-id-marker")
return
}
// Parse bucket url queries for ListObjects V2.
func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimiter string, fetchOwner bool, maxkeys int, encodingType string, errCode APIErrorCode) {
errCode = ErrNone
+93
View File
@@ -44,6 +44,45 @@ type LocationResponse struct {
Location string `xml:",chardata"`
}
// ListVersionsResponse - format for list bucket versions response.
type ListVersionsResponse struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListVersionsResult" json:"-"`
Name string
Prefix string
KeyMarker string
// When response is truncated (the IsTruncated element value in the response
// is true), you can use the key name in this field as marker in the subsequent
// request to get next set of objects. Server lists objects in alphabetical
// order Note: This element is returned only if you have delimiter request parameter
// specified. If response does not include the NextMaker and it is truncated,
// you can use the value of the last Key in the response as the marker in the
// subsequent request to get the next set of object keys.
NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
// When the number of responses exceeds the value of MaxKeys,
// NextVersionIdMarker specifies the first object version not
// returned that satisfies the search criteria. Use this value
// for the version-id-marker request parameter in a subsequent request.
NextVersionIDMarker string `xml:"NextVersionIdMarker"`
// Marks the last version of the Key returned in a truncated response.
VersionIDMarker string `xml:"VersionIdMarker"`
MaxKeys int
Delimiter string
// A flag that indicates whether or not ListObjects returned all of the results
// that satisfied the search criteria.
IsTruncated bool
CommonPrefixes []CommonPrefix
Versions []ObjectVersion
// Encoding type used to encode object keys in the response.
EncodingType string `xml:"EncodingType,omitempty"`
}
// ListObjectsResponse - format for list objects response.
type ListObjectsResponse struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
@@ -191,6 +230,14 @@ type Bucket struct {
CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
}
// ObjectVersion container for object version metadata
type ObjectVersion struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Version" json:"-"`
Object
VersionID string `xml:"VersionId"`
IsLatest bool
}
// Object container for object metadata
type Object struct {
Key string
@@ -328,6 +375,52 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
return data
}
// generates an ListBucketVersions response for the said bucket with other enumerated options.
func generateListVersionsResponse(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListVersionsResponse {
var versions []ObjectVersion
var prefixes []CommonPrefix
var owner = Owner{}
var data = ListVersionsResponse{}
owner.ID = globalMinioDefaultOwnerID
for _, object := range resp.Objects {
var content = ObjectVersion{}
if object.Name == "" {
continue
}
content.Key = s3EncodeName(object.Name, encodingType)
content.LastModified = object.ModTime.UTC().Format(timeFormatAMZLong)
if object.ETag != "" {
content.ETag = "\"" + object.ETag + "\""
}
content.Size = object.Size
content.StorageClass = object.StorageClass
content.Owner = owner
content.VersionID = "null"
content.IsLatest = true
versions = append(versions, content)
}
data.Name = bucket
data.Versions = versions
data.EncodingType = encodingType
data.Prefix = s3EncodeName(prefix, encodingType)
data.KeyMarker = s3EncodeName(marker, encodingType)
data.Delimiter = s3EncodeName(delimiter, encodingType)
data.MaxKeys = maxKeys
data.NextKeyMarker = s3EncodeName(resp.NextMarker, encodingType)
data.IsTruncated = resp.IsTruncated
for _, prefix := range resp.Prefixes {
var prefixItem = CommonPrefix{}
prefixItem.Prefix = s3EncodeName(prefix, encodingType)
prefixes = append(prefixes, prefixItem)
}
data.CommonPrefixes = prefixes
return data
}
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
var contents []Object
+3 -1
View File
@@ -64,7 +64,7 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
// PutObjectPart
bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
// ListObjectPxarts
// ListObjectParts
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.ListObjectPartsHandler)).Queries("uploadId", "{uploadId:.*}")
// CompleteMultipartUpload
bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(httpTraceAll(api.CompleteMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
@@ -129,6 +129,8 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListMultipartUploadsHandler)).Queries("uploads", "")
// ListObjectsV2
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListObjectsV2Handler)).Queries("list-type", "2")
// ListBucketVersions
bucket.Methods(http.MethodGet).HandlerFunc(httpTraceAll(api.ListBucketObjectVersionsHandler)).Queries("versions", "")
// ListObjectsV1 (Legacy)
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV1Handler))
// PutBucketLifecycle
+4 -4
View File
@@ -62,11 +62,11 @@ func (h *healRoutine) run() {
break
}
if globalHTTPServer != nil {
// Wait at max 1 minute for an inprogress request
// before proceeding to heal
waitCount := 60
// Wait at max 10 minute for an inprogress request before proceeding to heal
waitCount := 600
// Any requests in progress, delay the heal.
for globalHTTPServer.GetRequestCount() > 2 && waitCount > 0 {
for (globalHTTPServer.GetRequestCount() >= int32(globalXLSetCount*globalXLSetDriveCount)) &&
waitCount > 0 {
waitCount--
time.Sleep(1 * time.Second)
}
+77
View File
@@ -49,6 +49,83 @@ func validateListObjectsArgs(prefix, marker, delimiter, encodingType string, max
return ErrNone
}
// ListBucketObjectVersions - GET Bucket Object versions
// You can use the versions subresource to list metadata about all
// of the versions of objects in a bucket.
func (api objectAPIHandlers) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListBucketObjectVersions")
defer logger.AuditLog(w, r, "ListBucketObjectVersions", mustGetClaimsFromToken(r))
vars := mux.Vars(r)
bucket := vars["bucket"]
objectAPI := api.ObjectAPI()
if objectAPI == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
urlValues := r.URL.Query()
// Extract all the listBucketVersions query params to their native values.
// versionIDMarker is ignored here.
prefix, marker, delimiter, maxkeys, encodingType, _, errCode := getListBucketObjectVersionsArgs(urlValues)
if errCode != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return
}
// Validate the query params before beginning to serve the request.
if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxkeys); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
listObjects := objectAPI.ListObjects
// Inititate a list objects operation based on the input params.
// On success would return back ListObjectsInfo object to be
// marshaled into S3 compatible XML header.
listObjectsInfo, err := listObjects(ctx, bucket, prefix, marker, delimiter, maxkeys)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
for i := range listObjectsInfo.Objects {
var actualSize int64
if listObjectsInfo.Objects[i].IsCompressed() {
// Read the decompressed size from the meta.json.
actualSize = listObjectsInfo.Objects[i].GetActualSize()
if actualSize < 0 {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidDecompressedSize),
r.URL, guessIsBrowserReq(r))
return
}
// Set the info.Size to the actualSize.
listObjectsInfo.Objects[i].Size = actualSize
} else if crypto.IsEncrypted(listObjectsInfo.Objects[i].UserDefined) {
listObjectsInfo.Objects[i].ETag = getDecryptedETag(r.Header, listObjectsInfo.Objects[i], false)
listObjectsInfo.Objects[i].Size, err = listObjectsInfo.Objects[i].DecryptedSize()
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
}
}
response := generateListVersionsResponse(bucket, prefix, marker, delimiter, encodingType, maxkeys, listObjectsInfo)
// Write success response.
writeSuccessResponseXML(w, encodeResponse(response))
}
// ListObjectsV2Handler - GET Bucket (List Objects) Version 2.
// --------------------------
// This implementation of the GET operation returns some or all (up to 1000)
+4
View File
@@ -288,6 +288,9 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
// Set delimiter value for "s3:delimiter" policy conditionals.
r.Header.Set("delimiter", SlashSeparator)
// err will be nil here as we already called this function
// earlier in this request.
claims, _ := getClaimsFromToken(r)
var newBucketsInfo []BucketInfo
for _, bucketInfo := range bucketsInfo {
if globalIAMSys.IsAllowed(iampolicy.Args{
@@ -297,6 +300,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
ConditionValues: getConditionValues(r, "", accessKey),
IsOwner: owner,
ObjectName: "",
Claims: claims,
}) {
newBucketsInfo = append(newBucketsInfo, bucketInfo)
}
+10 -3
View File
@@ -31,7 +31,6 @@ import (
"github.com/minio/cli"
"github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/logger/target/console"
"github.com/minio/minio/cmd/logger/target/http"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/dns"
@@ -67,7 +66,7 @@ func checkUpdate(mode string) {
if globalInplaceUpdateDisabled {
logger.StartupMessage(updateMsg)
} else {
logger.StartupMessage(prepareUpdateMessage("Run `minio update`", latestReleaseTime.Sub(currentReleaseTime)))
logger.StartupMessage(prepareUpdateMessage("Run `mc admin update`", latestReleaseTime.Sub(currentReleaseTime)))
}
}
}
@@ -97,7 +96,7 @@ func loadLoggers() {
if globalServerConfig.Logger.Console.Enabled {
// Enable console logging
logger.AddTarget(console.New())
logger.AddTarget(globalConsoleSys.Console())
}
}
@@ -359,6 +358,14 @@ func handleCommonEnvVars() {
globalCacheMaxUse = maxUse
}
}
var err error
if cacheEncKey := os.Getenv("MINIO_CACHE_ENCRYPTION_MASTER_KEY"); cacheEncKey != "" {
globalCacheKMSKeyID, globalCacheKMS, err = parseKMSMasterKey(cacheEncKey)
if err != nil {
logger.Fatal(uiErrInvalidCacheEncryptionKey(err), "Invalid cache encryption master key")
}
}
// In place update is true by default if the MINIO_UPDATE is not set
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
// in-place update is off.
+15 -9
View File
@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"path"
"runtime"
"strings"
@@ -136,18 +137,23 @@ func (sys *ConfigSys) Init(objAPI ObjectLayer) error {
// of the object layer.
// - Write quorum not met when upgrading configuration
// version is needed.
for range newRetryTimerSimple(doneCh) {
if err := initConfig(objAPI); err != nil {
if strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for configuration to be initialized..")
continue
retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
if err := initConfig(objAPI); err != nil {
if strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for configuration to be initialized..")
continue
}
return err
}
return err
return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing config sub-system gracefully stopped")
}
break
}
return nil
}
// NewConfigSys - creates new config system object.
+128
View File
@@ -0,0 +1,128 @@
/*
* 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 (
ring "container/ring"
"context"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/logger/message/log"
"github.com/minio/minio/cmd/logger/target/console"
"github.com/minio/minio/pkg/madmin"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/pubsub"
)
// number of log messages to buffer
const defaultLogBufferCount = 10000
//HTTPConsoleLoggerSys holds global console logger state
type HTTPConsoleLoggerSys struct {
pubsub *pubsub.PubSub
console *console.Target
nodeName string
logBuf *ring.Ring
}
// NewConsoleLogger - creates new HTTPConsoleLoggerSys with all nodes subscribed to
// the console logging pub sub system
func NewConsoleLogger(ctx context.Context, endpoints EndpointList) *HTTPConsoleLoggerSys {
host, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
if err != nil {
logger.FatalIf(err, "Unable to start console logging subsystem")
}
var nodeName string
if globalIsDistXL {
nodeName = host.Name
}
ps := pubsub.New()
return &HTTPConsoleLoggerSys{
ps, nil, nodeName, ring.New(defaultLogBufferCount),
}
}
// HasLogListeners returns true if console log listeners are registered
// for this node or peers
func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool {
return sys != nil && sys.pubsub.HasSubscribers()
}
// Subscribe starts console logging for this node.
func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan interface{}, doneCh chan struct{}, node string, last int, filter func(entry interface{}) bool) {
// Enable console logging for remote client even if local console logging is disabled in the config.
if !globalServerConfig.Logger.Console.Enabled && !sys.pubsub.HasSubscribers() {
logger.AddTarget(globalConsoleSys.Console())
}
cnt := 0
// by default send all console logs in the ring buffer unless node or limit query parameters
// are set.
var lastN []madmin.LogInfo
if last > defaultLogBufferCount || last <= 0 {
last = defaultLogBufferCount
}
lastN = make([]madmin.LogInfo, last)
r := sys.logBuf
r.Do(func(p interface{}) {
if p != nil && (p.(madmin.LogInfo)).SendLog(node) {
lastN[cnt%last] = p.(madmin.LogInfo)
cnt++
}
})
// send last n console log messages in order filtered by node
if cnt > 0 {
for i := 0; i < last; i++ {
entry := lastN[(cnt+i)%last]
if (entry == madmin.LogInfo{}) {
continue
}
select {
case subCh <- entry:
case <-doneCh:
return
}
}
}
sys.pubsub.Subscribe(subCh, doneCh, filter)
}
// Console returns a console target
func (sys *HTTPConsoleLoggerSys) Console() *HTTPConsoleLoggerSys {
if sys.console == nil {
sys.console = console.New()
}
return sys
}
// Send log message 'e' to console and publish to console
// log pubsub system
func (sys *HTTPConsoleLoggerSys) Send(e interface{}) error {
lg := madmin.LogInfo{}
lg.Entry = e.(log.Entry)
lg.NodeName = sys.nodeName
sys.pubsub.Publish(lg)
// add log to ring buffer
sys.logBuf.Value = lg
sys.logBuf = sys.logBuf.Next()
if globalServerConfig.Logger.Console.Enabled {
return sys.console.Send(e)
}
return nil
}
+3 -5
View File
@@ -103,14 +103,12 @@ func startDailyLifecycle() {
}
}
var lifecycleTimeout = newDynamicTimeout(60*time.Second, time.Second)
func lifecycleRound(ctx context.Context, objAPI ObjectLayer) error {
zeroDuration := time.Millisecond
zeroDynamicTimeout := newDynamicTimeout(zeroDuration, zeroDuration)
// Lock to avoid concurrent lifecycle ops from other nodes
sweepLock := globalNSMutex.NewNSLock(ctx, "system", "daily-lifecycle-ops")
if err := sweepLock.GetLock(zeroDynamicTimeout); err != nil {
if err := sweepLock.GetLock(lifecycleTimeout); err != nil {
return err
}
defer sweepLock.Unlock()
+18 -4
View File
@@ -18,6 +18,7 @@ package cmd
import (
"context"
"math/rand"
"sync"
"time"
@@ -48,15 +49,14 @@ func copyDailySweepListeners() []chan string {
return listenersCopy
}
var sweepTimeout = newDynamicTimeout(60*time.Second, time.Second)
// 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(ctx, "system", "daily-sweep")
if err := sweepLock.GetLock(zeroDynamicTimeout); err != nil {
if err := sweepLock.GetLock(sweepTimeout); err != nil {
return err
}
defer sweepLock.Unlock()
@@ -76,6 +76,17 @@ func sweepRound(ctx context.Context, objAPI ObjectLayer) error {
marker := ""
for {
if globalHTTPServer != nil {
// Wait at max 10 minute for an inprogress request before proceeding to heal
waitCount := 600
// Any requests in progress, delay the heal.
for (globalHTTPServer.GetRequestCount() >= int32(globalXLSetCount*globalXLSetDriveCount)) &&
waitCount > 0 {
waitCount--
time.Sleep(1 * time.Second)
}
}
res, err := objAPI.ListObjectsHeal(ctx, bucket.Name, "", marker, "", 1000)
if err != nil {
continue
@@ -119,6 +130,9 @@ func dailySweeper() {
break
}
// Start with random sleep time, so as to avoid "synchronous checks" between servers
time.Sleep(time.Duration(rand.Float64() * float64(time.Hour)))
// Perform a sweep round each month
for {
if time.Since(lastSweepTime) < 30*24*time.Hour {
+112 -12
View File
@@ -19,6 +19,7 @@ package cmd
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
@@ -33,8 +34,10 @@ import (
"time"
"github.com/djherbis/atime"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/disk"
"github.com/minio/sio"
"github.com/ncw/directio"
)
@@ -45,6 +48,10 @@ const (
cacheMetaVersion = "1.0.0"
cacheEnvDelimiter = ";"
// SSECacheEncrypted is the metadata key indicating that the object
// is a cache entry encrypted with cache KMS master key in globalCacheKMS.
SSECacheEncrypted = "X-Minio-Internal-Encrypted-Cache"
)
// CacheChecksumInfoV1 - carries checksums of individual blocks on disk.
@@ -223,6 +230,7 @@ func (c *diskCache) purge() {
continue
}
cc := cacheControlOpts(objInfo)
if atime.Get(fi).Before(expiry) ||
cc.isStale(objInfo.ModTime) {
if err = removeAll(pathJoin(c.dir, obj.Name())); err != nil {
@@ -273,6 +281,10 @@ func (c *diskCache) Stat(ctx context.Context, bucket, object string) (oi ObjectI
}
oi.Bucket = bucket
oi.Name = object
if err = decryptCacheObjectETag(&oi); err != nil {
return oi, err
}
return
}
@@ -321,9 +333,48 @@ func (c *diskCache) saveMetadata(ctx context.Context, bucket, object string, met
return err
}
// updates ETag in cache metadata file to the backend ETag.
func (c *diskCache) updateETag(ctx context.Context, bucket, object string, etag string) error {
metaPath := path.Join(getCacheSHADir(c.dir, bucket, object), cacheMetaJSONFile)
f, err := os.OpenFile(metaPath, os.O_RDWR, 0)
if err != nil {
return err
}
defer f.Close()
meta := &cacheMeta{Version: cacheMetaVersion}
if err := jsonLoad(f, meta); err != nil {
return err
}
meta.Meta["etag"] = etag
jsonData, err := json.Marshal(meta)
if err != nil {
return err
}
_, err = f.Write(jsonData)
return err
}
// Backend metadata could have changed through server side copy - reset cache metadata if that is the case
func (c *diskCache) updateMetadataIfChanged(ctx context.Context, bucket, object string, bkObjectInfo, cacheObjInfo ObjectInfo) error {
if !reflect.DeepEqual(bkObjectInfo.UserDefined, cacheObjInfo.UserDefined) ||
bkMeta := make(map[string]string)
cacheMeta := make(map[string]string)
for k, v := range bkObjectInfo.UserDefined {
if hasPrefix(k, ReservedMetadataPrefix) {
// Do not need to send any internal metadata
continue
}
bkMeta[http.CanonicalHeaderKey(k)] = v
}
for k, v := range cacheObjInfo.UserDefined {
if hasPrefix(k, ReservedMetadataPrefix) {
// Do not need to send any internal metadata
continue
}
cacheMeta[http.CanonicalHeaderKey(k)] = v
}
if !reflect.DeepEqual(bkMeta, cacheMeta) ||
bkObjectInfo.ETag != cacheObjInfo.ETag ||
bkObjectInfo.ContentType != cacheObjInfo.ContentType ||
bkObjectInfo.Expires != cacheObjInfo.Expires {
@@ -337,11 +388,11 @@ func getCacheSHADir(dir, bucket, object string) string {
}
// Cache data to disk with bitrot checksum added for each block of 1MB
func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, reader io.Reader, size int64) (int64, error) {
func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, reader io.Reader, size uint64) (int64, error) {
if err := os.MkdirAll(cachePath, 0777); err != nil {
return 0, err
}
bufSize := int64(readSizeV1)
bufSize := uint64(readSizeV1)
if size > 0 && bufSize > size {
bufSize = size
}
@@ -367,18 +418,19 @@ func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, re
bufp := c.pool.Get().(*[]byte)
defer c.pool.Put(bufp)
var n int
for {
n, err := io.ReadFull(reader, *bufp)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF && err != io.ErrClosedPipe {
n, err = io.ReadFull(reader, *bufp)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return 0, err
}
eof := err == io.EOF || err == io.ErrUnexpectedEOF || err == io.ErrClosedPipe
eof := err == io.EOF || err == io.ErrUnexpectedEOF
if n == 0 && size != 0 {
// Reached EOF, nothing more to be done.
break
}
h.Reset()
if _, err := h.Write((*bufp)[:n]); err != nil {
if _, err = h.Write((*bufp)[:n]); err != nil {
return 0, err
}
hashBytes := h.Sum(nil)
@@ -396,6 +448,39 @@ func (c *diskCache) bitrotWriteToCache(ctx context.Context, cachePath string, re
return bytesWritten, nil
}
func newCacheEncryptReader(content io.Reader, bucket, object string, metadata map[string]string) (r io.Reader, err error) {
objectEncryptionKey, err := newCacheEncryptMetadata(bucket, object, metadata)
if err != nil {
return nil, err
}
reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20})
if err != nil {
return nil, crypto.ErrInvalidCustomerKey
}
return reader, nil
}
func newCacheEncryptMetadata(bucket, object string, metadata map[string]string) ([]byte, error) {
var sealedKey crypto.SealedKey
if globalCacheKMS == nil {
return nil, errKMSNotConfigured
}
key, encKey, err := globalCacheKMS.GenerateKey(globalCacheKMSKeyID, crypto.Context{bucket: path.Join(bucket, object)})
if err != nil {
return nil, err
}
objectKey := crypto.GenerateKey(key, rand.Reader)
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
crypto.S3.CreateMetadata(metadata, globalCacheKMSKeyID, encKey, sealedKey)
if etag, ok := metadata["etag"]; ok {
metadata["etag"] = hex.EncodeToString(objectKey.SealETag([]byte(etag)))
}
metadata[SSECacheEncrypted] = ""
return objectKey[:], nil
}
// Caches the object to disk
func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Reader, size int64, opts ObjectOptions) error {
if c.diskUsageHigh() {
@@ -417,14 +502,31 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
bufSize = size
}
n, err := c.bitrotWriteToCache(ctx, cachePath, data, size)
var metadata = make(map[string]string)
for k, v := range opts.UserDefined {
metadata[k] = v
}
var reader = data
var actualSize = uint64(size)
var err error
if globalCacheKMS != nil {
reader, err = newCacheEncryptReader(data, bucket, object, metadata)
if err != nil {
return err
}
actualSize, _ = sio.EncryptedSize(uint64(size))
}
n, err := c.bitrotWriteToCache(ctx, cachePath, reader, actualSize)
if IsErr(err, baseErrs...) {
c.setOnline(false)
}
if err != nil {
return err
}
return c.saveMetadata(ctx, bucket, object, opts.UserDefined, n)
if actualSize != uint64(n) {
return IncompleteBody{}
}
return c.saveMetadata(ctx, bucket, object, metadata, n)
}
// checks streaming bitrot checksum of cached object before returning data
@@ -484,7 +586,6 @@ func (c *diskCache) bitrotReadFromCache(ctx context.Context, filePath string, of
if _, err := io.ReadFull(rc, checksumHash); err != nil {
return err
}
h.Reset()
n, err := io.ReadFull(rc, *bufp)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
@@ -527,7 +628,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
var objInfo ObjectInfo
cacheObjPath := getCacheSHADir(c.dir, bucket, object)
if objInfo, err = c.statCache(ctx, cacheObjPath); err != nil {
if objInfo, err = c.Stat(ctx, bucket, object); err != nil {
return nil, toObjectErr(err, bucket, object)
}
@@ -552,7 +653,6 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
// Cleanup function to cause the go routine above to exit, in
// case of incomplete read.
pipeCloser := func() { pr.Close() }
return fn(pr, h, opts.CheckCopyPrecondFn, pipeCloser)
}
+51 -1
View File
@@ -17,8 +17,10 @@
package cmd
import (
"encoding/hex"
"io"
"os"
"path"
"strconv"
"strings"
"time"
@@ -124,7 +126,7 @@ func backendDownError(err error) bool {
// IsCacheable returns if the object should be saved in the cache.
func (o ObjectInfo) IsCacheable() bool {
return !crypto.IsEncrypted(o.UserDefined)
return !crypto.IsEncrypted(o.UserDefined) || globalCacheKMS != nil
}
// reads file cached on disk from offset upto length
@@ -147,6 +149,10 @@ func readCacheFileStream(filePath string, offset, length int64) (io.ReadCloser,
return nil, err
}
if err = os.Chtimes(filePath, time.Now(), st.ModTime()); err != nil {
return nil, err
}
// Verify if its not a regular file, since subsequent Seek is undefined.
if !st.Mode().IsRegular() {
return nil, errIsNotRegular
@@ -168,3 +174,47 @@ func readCacheFileStream(filePath string, offset, length int64) (io.ReadCloser,
io.Closer
}{Reader: io.LimitReader(fr, length), Closer: fr}, nil
}
func isCacheEncrypted(meta map[string]string) bool {
_, ok := meta[SSECacheEncrypted]
return ok
}
// decryptCacheObjectETag tries to decrypt the ETag saved in encrypted format using the cache KMS
func decryptCacheObjectETag(info *ObjectInfo) error {
// Directories are never encrypted.
if info.IsDir {
return nil
}
encrypted := crypto.S3.IsEncrypted(info.UserDefined) && isCacheEncrypted(info.UserDefined)
switch {
case encrypted:
if globalCacheKMS == nil {
return errKMSNotConfigured
}
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(info.UserDefined)
if err != nil {
return err
}
extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{info.Bucket: path.Join(info.Bucket, info.Name)})
if err != nil {
return err
}
var objectKey crypto.ObjectKey
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), info.Bucket, info.Name); err != nil {
return err
}
etagStr := tryDecryptETag(objectKey[:], info.ETag, false)
// backend ETag was hex encoded before encrypting, so hex decode to get actual ETag
etag, err := hex.DecodeString(etagStr)
if err != nil {
return err
}
info.ETag = string(etag)
return nil
}
return nil
}
+87 -3
View File
@@ -14,6 +14,7 @@ import (
"github.com/djherbis/atime"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/wildcard"
)
@@ -35,6 +36,7 @@ type CacheObjectLayer interface {
GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObject(ctx context.Context, bucket, object string) error
DeleteObjects(ctx context.Context, bucket string, objects []string) ([]error, error)
PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
// Storage operations.
StorageInfo(ctx context.Context) CacheStorageInfo
}
@@ -58,6 +60,7 @@ type cacheObjects struct {
GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObjectFn func(ctx context.Context, bucket, object string) error
DeleteObjectsFn func(ctx context.Context, bucket string, objects []string) ([]error, error)
PutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
}
func (c *cacheObjects) delete(ctx context.Context, dcache *diskCache, bucket, object string) (err error) {
@@ -231,13 +234,13 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
pipeReader, pipeWriter := io.Pipe()
teeReader := io.TeeReader(bkReader, pipeWriter)
go func() {
putErr := dcache.Put(ctx, bucket, object, io.LimitReader(pipeReader, bkReader.ObjInfo.Size), bkReader.ObjInfo.Size, ObjectOptions{UserDefined: getMetadata(bkReader.ObjInfo)})
putErr := c.put(ctx, dcache, bucket, object, io.LimitReader(pipeReader, bkReader.ObjInfo.Size), bkReader.ObjInfo.Size, ObjectOptions{UserDefined: getMetadata(bkReader.ObjInfo)})
// close the write end of the pipe, so the error gets
// propagated to getObjReader
pipeWriter.CloseWithError(putErr)
}()
cleanupBackend := func() { bkReader.Close() }
cleanupPipe := func() { pipeReader.Close() }
cleanupPipe := func() { pipeWriter.Close() }
return NewGetObjectReaderFromReader(teeReader, bkReader.ObjInfo, opts.CheckCopyPrecondFn, cleanupBackend, cleanupPipe)
}
@@ -263,7 +266,6 @@ func (c *cacheObjects) GetObjectInfo(ctx context.Context, bucket, object string,
return cachedObjInfo, nil
}
}
objInfo, err := getObjectInfoFn(ctx, bucket, object, opts)
if err != nil {
if _, ok := err.(ObjectNotFound); ok {
@@ -446,6 +448,7 @@ func checkAtimeSupport(dir string) (err error) {
}
func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) {
logger.StartupMessage(colorBlue("Cache migration initiated ...."))
var wg = &sync.WaitGroup{}
errs := make([]error, len(c.cache))
for i, dc := range c.cache {
@@ -483,6 +486,84 @@ func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) {
logger.StartupMessage(colorBlue("Cache migration completed successfully."))
}
// PutObject - caches the uploaded object for single Put operations
func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
putObjectFn := c.PutObjectFn
data := r.rawReader
dcache, err := c.getCacheToLoc(ctx, bucket, object)
if err != nil {
// disk cache could not be located,execute backend call.
return putObjectFn(ctx, bucket, object, r, opts)
}
size := r.Size()
if c.skipCache() {
return putObjectFn(ctx, bucket, object, r, opts)
}
// fetch from backend if there is no space on cache drive
if !dcache.diskAvailable(size) {
return putObjectFn(ctx, bucket, object, r, opts)
}
if opts.ServerSideEncryption != nil {
return putObjectFn(ctx, bucket, object, r, opts)
}
// fetch from backend if cache exclude pattern or cache-control
// directive set to exclude
if c.isCacheExclude(bucket, object) {
dcache.Delete(ctx, bucket, object)
return putObjectFn(ctx, bucket, object, r, opts)
}
// Initialize pipe to stream data to backend
pipeReader, pipeWriter := io.Pipe()
hashReader, err := hash.NewReader(pipeReader, size, "", "", data.ActualSize(), globalCLIContext.StrictS3Compat)
if err != nil {
return ObjectInfo{}, err
}
// Initialize pipe to stream data to cache
rPipe, wPipe := io.Pipe()
oinfoCh := make(chan ObjectInfo)
errCh := make(chan error)
go func() {
oinfo, perr := putObjectFn(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), opts)
if perr != nil {
pipeWriter.CloseWithError(perr)
wPipe.CloseWithError(perr)
close(oinfoCh)
errCh <- perr
return
}
close(errCh)
oinfoCh <- oinfo
}()
// get a namespace lock on cache until cache is filled.
cLock := c.nsMutex.NewNSLock(ctx, bucket, object)
if err := cLock.GetLock(globalObjectTimeout); err != nil {
return ObjectInfo{}, err
}
defer cLock.Unlock()
go func() {
if err = dcache.Put(ctx, bucket, object, rPipe, data.Size(), opts); err != nil {
wPipe.CloseWithError(err)
return
}
}()
mwriter := io.MultiWriter(pipeWriter, wPipe)
_, err = io.Copy(mwriter, data)
if err != nil {
err = <-errCh
return objInfo, err
}
pipeWriter.Close()
wPipe.Close()
objInfo = <-oinfoCh
dcache.updateETag(ctx, bucket, object, objInfo.ETag)
return objInfo, err
}
// Returns cacheObjects for use by Server.
func newServerCacheObjects(ctx context.Context, config CacheConfig) (CacheObjectLayer, error) {
// list of disk caches for cache "drives" specified in config.json or MINIO_CACHE_DRIVES env var.
@@ -513,6 +594,9 @@ func newServerCacheObjects(ctx context.Context, config CacheConfig) (CacheObject
}
return errs, nil
},
PutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
return newObjectLayerFn().PutObject(ctx, bucket, object, data, opts)
},
}
if migrateSw {
go c.migrateCacheFromV1toV2(ctx)
+17
View File
@@ -279,6 +279,23 @@ func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]st
switch {
default:
return nil, errObjectTampered
case crypto.S3.IsEncrypted(metadata) && isCacheEncrypted(metadata):
if globalCacheKMS == nil {
return nil, errKMSNotConfigured
}
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata)
if err != nil {
return nil, err
}
extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)})
if err != nil {
return nil, err
}
var objectKey crypto.ObjectKey
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil {
return nil, err
}
return objectKey[:], nil
case crypto.S3.IsEncrypted(metadata):
if GlobalKMS == nil {
return nil, errKMSNotConfigured
+29 -15
View File
@@ -30,6 +30,7 @@ import (
"strings"
"github.com/minio/minio/cmd/logger"
"github.com/minio/sio"
)
const (
@@ -362,7 +363,7 @@ func loadAndValidateCacheFormat(ctx context.Context, drives []string) (formats [
// reads cached object on disk and writes it back after adding bitrot
// hashsum per block as per the new disk cache format.
func migrateData(ctx context.Context, c *diskCache, oldfile, destDir string) error {
func migrateCacheData(ctx context.Context, c *diskCache, bucket, object, oldfile, destDir string, metadata map[string]string) error {
st, err := os.Stat(oldfile)
if err != nil {
err = osErrToFSFileErr(err)
@@ -372,7 +373,18 @@ func migrateData(ctx context.Context, c *diskCache, oldfile, destDir string) err
if err != nil {
return err
}
_, err = c.bitrotWriteToCache(ctx, destDir, readCloser, st.Size())
var reader io.Reader = readCloser
actualSize := uint64(st.Size())
if globalCacheKMS != nil {
reader, err = newCacheEncryptReader(readCloser, bucket, object, metadata)
if err != nil {
return err
}
actualSize, _ = sio.EncryptedSize(uint64(st.Size()))
}
_, err = c.bitrotWriteToCache(ctx, destDir, reader, uint64(actualSize))
return err
}
@@ -401,6 +413,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
}
for _, bucket := range buckets {
bucket = strings.TrimSuffix(bucket, SlashSeparator)
var objMetaPaths []string
root := path.Join(oldCacheBucketsPath, bucket)
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
@@ -422,10 +435,23 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
return err
}
prevCachedPath := path.Join(c.dir, bucket, object)
// get old cached metadata
oldMetaPath := pathJoin(oldCacheBucketsPath, bucket, object, cacheMetaJSONFile)
metaPath := pathJoin(destdir, cacheMetaJSONFile)
metaBytes, err := ioutil.ReadFile(oldMetaPath)
if err != nil {
return err
}
// marshal cache metadata after adding version and stat info
meta := &cacheMeta{}
if err = json.Unmarshal(metaBytes, &meta); err != nil {
return err
}
// move cached object to new cache directory path
// migrate cache data and add bit-rot protection hash sum
// at the start of each block
if err := migrateData(ctx, c, prevCachedPath, destdir); err != nil {
if err := migrateCacheData(ctx, c, bucket, object, prevCachedPath, destdir, meta.Meta); err != nil {
continue
}
stat, err := os.Stat(prevCachedPath)
@@ -440,19 +466,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
if err := os.Remove(prevCachedPath); err != nil {
return err
}
// move cached metadata after changing cache metadata version
oldMetaPath := pathJoin(oldCacheBucketsPath, bucket, object, cacheMetaJSONFile)
metaPath := pathJoin(destdir, cacheMetaJSONFile)
metaBytes, err := ioutil.ReadFile(oldMetaPath)
if err != nil {
return err
}
// marshal cache metadata after adding version and stat info
meta := &cacheMeta{}
if err = json.Unmarshal(metaBytes, &meta); err != nil {
return err
}
meta.Checksum = CacheChecksumInfoV1{Algorithm: HighwayHash256S.String(), Blocksize: cacheBlkSize}
meta.Version = cacheMetaVersion
meta.Stat.Size = stat.Size()
+5 -47
View File
@@ -27,10 +27,10 @@ import (
pathutil "path"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/lock"
"github.com/minio/minio/pkg/mimedb"
"github.com/tidwall/gjson"
)
// FS format, and object metadata.
@@ -203,44 +203,6 @@ func (m *fsMetaV1) WriteTo(lk *lock.LockedFile) (n int64, err error) {
return fi.Size(), nil
}
func parseFSVersion(fsMetaBuf []byte) string {
return gjson.GetBytes(fsMetaBuf, "version").String()
}
func parseFSMetaMap(fsMetaBuf []byte) map[string]string {
// Get xlMetaV1.Meta map.
metaMapResult := gjson.GetBytes(fsMetaBuf, "meta").Map()
metaMap := make(map[string]string)
for key, valResult := range metaMapResult {
metaMap[key] = valResult.String()
}
return metaMap
}
func parseFSPartsArray(fsMetaBuf []byte) []ObjectPartInfo {
// Get xlMetaV1.Parts array
var partsArray []ObjectPartInfo
partsArrayResult := gjson.GetBytes(fsMetaBuf, "parts")
partsArrayResult.ForEach(func(key, part gjson.Result) bool {
partJSON := part.String()
number := gjson.Get(partJSON, "number").Int()
name := gjson.Get(partJSON, "name").String()
etag := gjson.Get(partJSON, "etag").String()
size := gjson.Get(partJSON, "size").Int()
actualSize := gjson.Get(partJSON, "actualSize").Int()
partsArray = append(partsArray, ObjectPartInfo{
Number: int(number),
Name: name,
ETag: etag,
Size: size,
ActualSize: int64(actualSize),
})
return true
})
return partsArray
}
func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64, err error) {
var fsMetaBuf []byte
fi, err := lk.Stat()
@@ -260,8 +222,10 @@ func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64,
return 0, io.EOF
}
// obtain version.
m.Version = parseFSVersion(fsMetaBuf)
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(fsMetaBuf, m); err != nil {
return 0, err
}
// Verify if the format is valid, return corrupted format
// for unrecognized formats.
@@ -271,12 +235,6 @@ func (m *fsMetaV1) ReadFrom(ctx context.Context, lk *lock.LockedFile) (n int64,
return 0, errCorruptedFormat
}
// obtain parts information
m.Parts = parseFSPartsArray(fsMetaBuf)
// obtain metadata.
m.Meta = parseFSMetaMap(fsMetaBuf)
// Success.
return int64(len(fsMetaBuf)), nil
}
+10 -2
View File
@@ -28,6 +28,7 @@ import (
"strings"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger"
mioutil "github.com/minio/minio/pkg/ioutil"
)
@@ -456,7 +457,8 @@ func (fs *FSObjects) ListObjectParts(ctx context.Context, bucket, object, upload
}
for i, part := range result.Parts {
var stat os.FileInfo
stat, err = fsStatFile(ctx, pathJoin(uploadIDDir, fs.encodePartFile(part.PartNumber, part.ETag, part.ActualSize)))
stat, err = fsStatFile(ctx, pathJoin(uploadIDDir,
fs.encodePartFile(part.PartNumber, part.ETag, part.ActualSize)))
if err != nil {
return result, toObjectErr(err)
}
@@ -470,7 +472,13 @@ func (fs *FSObjects) ListObjectParts(ctx context.Context, bucket, object, upload
return result, err
}
result.UserDefined = parseFSMetaMap(fsMetaBytes)
var fsMeta fsMetaV1
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(fsMetaBytes, &fsMeta); err != nil {
return result, err
}
result.UserDefined = fsMeta.Meta
return result, nil
}
+9 -2
View File
@@ -30,6 +30,7 @@ import (
"sync/atomic"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/lifecycle"
@@ -1092,13 +1093,19 @@ func (fs *FSObjects) getObjectETag(ctx context.Context, bucket, entry string, lo
return "", toObjectErr(err, bucket, entry)
}
var fsMeta fsMetaV1
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(fsMetaBuf, &fsMeta); err != nil {
return "", err
}
// Check if FS metadata is valid, if not return error.
if !isFSMetaValid(parseFSVersion(fsMetaBuf)) {
if !isFSMetaValid(fsMeta.Version) {
logger.LogIf(ctx, errCorruptedFormat)
return "", toObjectErr(errCorruptedFormat, bucket, entry)
}
return extractETag(parseFSMetaMap(fsMetaBuf)), nil
return extractETag(fsMeta.Meta), nil
}
// ListObjects - list all objects at prefix upto maxKeys., optionally delimited by '/'. Maintains the list pool
+3
View File
@@ -159,6 +159,9 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
registerSTSRouter(router)
}
// initialize globalConsoleSys system
globalConsoleSys = NewConsoleLogger(context.Background(), globalEndpoints)
enableConfigOps := gatewayName == "nas"
enableIAMOps := globalEtcdClient != nil
+6 -79
View File
@@ -24,10 +24,10 @@ import (
"net/http"
"time"
jsoniter "github.com/json-iterator/go"
minio "github.com/minio/minio/cmd"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash"
"github.com/tidwall/gjson"
)
var (
@@ -137,84 +137,11 @@ func (m gwMetaV1) ObjectToPartOffset(ctx context.Context, offset int64) (partInd
return 0, 0, minio.InvalidRange{}
}
// parses gateway metadata stat info from metadata json
func parseGWStat(gwMetaBuf []byte) (si minio.StatInfo, e error) {
// obtain stat info.
stat := minio.StatInfo{}
// fetching modTime.
modTime, err := time.Parse(time.RFC3339, gjson.GetBytes(gwMetaBuf, "stat.modTime").String())
if err != nil {
return si, err
}
stat.ModTime = modTime
// obtain Stat.Size .
stat.Size = gjson.GetBytes(gwMetaBuf, "stat.size").Int()
return stat, nil
}
// parses gateway metadata version from metadata json
func parseGWVersion(gwMetaBuf []byte) string {
return gjson.GetBytes(gwMetaBuf, "version").String()
}
// parses gateway ETag from metadata json
func parseGWETag(gwMetaBuf []byte) string {
return gjson.GetBytes(gwMetaBuf, "etag").String()
}
// parses gateway metadata format from metadata json
func parseGWFormat(gwMetaBuf []byte) string {
return gjson.GetBytes(gwMetaBuf, "format").String()
}
// parses gateway metadata json to get list of ObjectPartInfo
func parseGWParts(gwMetaBuf []byte) []minio.ObjectPartInfo {
// Parse the GW Parts.
partsResult := gjson.GetBytes(gwMetaBuf, "parts").Array()
partInfo := make([]minio.ObjectPartInfo, len(partsResult))
for i, p := range partsResult {
info := minio.ObjectPartInfo{}
info.Number = int(p.Get("number").Int())
info.Name = p.Get("name").String()
info.ETag = p.Get("etag").String()
info.Size = p.Get("size").Int()
partInfo[i] = info
}
return partInfo
}
// parses gateway metadata json to get the metadata map
func parseGWMetaMap(gwMetaBuf []byte) map[string]string {
// Get gwMetaV1.Meta map.
metaMapResult := gjson.GetBytes(gwMetaBuf, "meta").Map()
metaMap := make(map[string]string)
for key, valResult := range metaMapResult {
metaMap[key] = valResult.String()
}
return metaMap
}
// Constructs GWMetaV1 using `gjson` lib to retrieve each field.
func gwMetaUnmarshalJSON(ctx context.Context, gwMetaBuf []byte) (gwMeta gwMetaV1, e error) {
// obtain version.
gwMeta.Version = parseGWVersion(gwMetaBuf)
// obtain format.
gwMeta.Format = parseGWFormat(gwMetaBuf)
// Parse gwMetaV1.Stat .
stat, err := parseGWStat(gwMetaBuf)
if err != nil {
logger.LogIf(ctx, err)
return gwMeta, err
}
gwMeta.ETag = parseGWETag(gwMetaBuf)
gwMeta.Stat = stat
// Parse the GW Parts.
gwMeta.Parts = parseGWParts(gwMetaBuf)
// parse gwMetaV1.
gwMeta.Meta = parseGWMetaMap(gwMetaBuf)
return gwMeta, nil
// Constructs GWMetaV1 using `jsoniter` lib to retrieve each field.
func gwMetaUnmarshalJSON(ctx context.Context, gwMetaBuf []byte) (gwMeta gwMetaV1, err error) {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
err = json.Unmarshal(gwMetaBuf, &gwMeta)
return gwMeta, err
}
// readGWMeta reads `dare.meta` and returns back GW metadata structure.
+8 -8
View File
@@ -53,25 +53,25 @@ func TestReadGWMetadata(t *testing.T) {
metaStr string
pass bool
}{
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "132", "modTime": "2018-08-31T22:25:39.23626461Z" }}}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "132", "modTime": "0000-00-00T00:00:00.00000000Z" }}}`, false},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "5242880", "modTime": "2018-08-31T22:25:39.23626461Z" }},"meta":{"content-type":"application/octet-stream","etag":"57c743902b2fc8eea6ba3bb4fc58c8e8"},"parts":[{"number":1,"name":"part.1","etag":"","size":5242880}]}}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "68190720", "modTime": "2018-08-31T22:25:39.23626461Z" }},"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":[{"number":1,"name":"part.1","etag":"c5cac075eefdab801a5198812f51b36e","size":67141632},{"number":2,"name":"part.2","etag":"ccdf4b774bc3be8eef9a8987309e8171","size":1049088}]}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", {"stat": {"size": "68190720", "modTime": "2018-08-31T22:25:39.23626461Z" }},"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":"123"}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 132, "modTime": "2018-08-31T22:25:39.23626461Z" }}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 132, "modTime": "0000-00-00T00:00:00.00000000Z" }}`, false},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 5242880, "modTime": "2018-08-31T22:25:39.23626461Z" },"meta":{"content-type":"application/octet-stream","etag":"57c743902b2fc8eea6ba3bb4fc58c8e8"},"parts":[{"number":1,"name":"part.1","etag":"","size":5242880}]}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": 68190720, "modTime": "2018-08-31T22:25:39.23626461Z" },"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":[{"number":1,"name":"part.1","etag":"c5cac075eefdab801a5198812f51b36e","size":67141632},{"number":2,"name":"part.2","etag":"ccdf4b774bc3be8eef9a8987309e8171","size":1049088}]}`, true},
{`{"version": "` + gwMetaVersion + `", "format":"` + gwMetaFormat + `", "stat": {"size": "68190720", "modTime": "2018-08-31T22:25:39.23626461Z" },"meta":{"X-Minio-Internal-Encrypted-Multipart":"","X-Minio-Internal-Server-Side-Encryption-Iv":"kdbOcKdXD3Sew8tOiHe5eI9xkX1oQ2W9JURz0oslCZA=","X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm":"DAREv2-HMAC-SHA256","X-Minio-Internal-Server-Side-Encryption-Sealed-Key":"IAAfAMfqKrxMXC9LuiI7ENP+p0xArepzAiIeB/MftFp7Xmq2OzDkKlmNbj5RKI89RrjiAbOVLSSEMvqQsrIrTQ==","content-type":"text/plain; charset=utf-8","etag":"2b137fa4ab80126af54623b010c98de6-2"},"parts":"123"}`, false},
}
for i, tt := range tests {
buf := bytes.NewBufferString(tt.metaStr)
m, err := readGWMetadata(context.Background(), *buf)
if err != nil && tt.pass {
t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed", i)
t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed, %s", i+1, err)
}
if err == nil && !tt.pass {
t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed", i)
t.Errorf("Test %d: Expected parse gw metadata to succeed, but failed", i+1)
}
if err == nil {
if m.Version != gwMetaVersion {
t.Errorf("Test %d: Expected version %s, but failed with %s", i, gwMetaVersion, m.Version)
t.Errorf("Test %d: Expected version %s, but failed with %s", i+1, gwMetaVersion, m.Version)
}
}
}
+27 -22
View File
@@ -481,14 +481,12 @@ var notimplementedBucketResourceNames = map[string]bool{
"requestPayment": true,
"tagging": true,
"versioning": true,
"versions": true,
"website": true,
}
// List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{
"acl": true,
"policy": true,
"restore": true,
"tagging": true,
"torrent": true,
@@ -674,25 +672,29 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
bucket, _ = urlPath2BucketObjectName(r.URL.Path)
}
}
if bucket != "" {
sr, err := globalDNSConfig.Get(bucket)
if err != nil {
if err == dns.ErrNoEntriesFound {
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrNoSuchBucket), r.URL, guessIsBrowserReq(r))
} else {
writeErrorResponse(context.Background(), w, toAPIError(context.Background(), err), r.URL, guessIsBrowserReq(r))
}
return
if bucket == "" {
f.handler.ServeHTTP(w, r)
return
}
sr, err := globalDNSConfig.Get(bucket)
if err != nil {
if err == dns.ErrNoEntriesFound {
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrNoSuchBucket),
r.URL, guessIsBrowserReq(r))
} else {
writeErrorResponse(context.Background(), w, toAPIError(context.Background(), err),
r.URL, guessIsBrowserReq(r))
}
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
r.URL.Scheme = "http"
if globalIsSSL {
r.URL.Scheme = "https"
}
r.URL.Host = getHostFromSrv(sr)
f.fwd.ServeHTTP(w, r)
return
return
}
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
r.URL.Scheme = "http"
if globalIsSSL {
r.URL.Scheme = "https"
}
r.URL.Host = getHostFromSrv(sr)
f.fwd.ServeHTTP(w, r)
return
}
f.handler.ServeHTTP(w, r)
return
@@ -716,9 +718,12 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
// CopyObject requests should be handled at current endpoint as path style
// requests have target bucket and object in URI and source details are in
// header fields
if r.Method == http.MethodPut && r.Header.Get("X-Amz-Copy-Source") != "" {
f.handler.ServeHTTP(w, r)
return
if r.Method == http.MethodPut && r.Header.Get(xhttp.AmzCopySource) != "" {
bucket, object = urlPath2BucketObjectName(r.Header.Get(xhttp.AmzCopySource))
if bucket == "" || object == "" {
f.handler.ServeHTTP(w, r)
return
}
}
sr, err := globalDNSConfig.Get(bucket)
if err != nil {
+8 -13
View File
@@ -174,6 +174,10 @@ var (
// registered listeners
globalHTTPTrace = pubsub.New()
// global console system to send console logs to
// registered listeners
globalConsoleSys *HTTPConsoleLoggerSys
globalEndpoints EndpointList
// Global server's network statistics
@@ -221,7 +225,10 @@ var (
globalCacheExpiry = 90
// Max allowed disk cache percentage
globalCacheMaxUse = 80
// Disk cache KMS Key
globalCacheKMSKeyID string
// Initialized KMS configuration for disk cache
globalCacheKMS crypto.KMS
// Allocated etcd endpoint for config and bucket DNS.
globalEtcdClient *etcd.Client
@@ -336,18 +343,6 @@ var (
}
return fmt.Sprintf
}()
colorGreenBold = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgGreen, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
colorRedBold = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgRed, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
)
// Returns minio global information, as a key value map.
+7 -1
View File
@@ -22,6 +22,7 @@ import (
"os"
"runtime"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
)
@@ -54,7 +55,12 @@ func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
objLayer := newObjectLayerFn()
// Service not initialized yet
if objLayer == nil {
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
// Respond with 200 OK while server initializes to ensure a distributed cluster
// is able to start on orchestration platforms like Docker Swarm.
// Refer https://github.com/minio/minio/issues/8140 for more details.
// Make sure to add server not initialized status in header
w.Header().Set(xhttp.MinIOServerStatus, "Server-not-initialized")
writeSuccessResponseHeadersOnly(w)
return
}
+9 -4
View File
@@ -29,6 +29,7 @@ import (
"strings"
"time"
"github.com/minio/minio/cmd/logger"
trace "github.com/minio/minio/pkg/trace"
)
@@ -126,7 +127,7 @@ func (r *recordResponseWriter) Write(p []byte) (n int, err error) {
r.writeHeaders(&r.headers, http.StatusOK, r.ResponseWriter.Header())
r.headersLogged = true
}
if (r.statusCode != http.StatusOK && r.statusCode != http.StatusPartialContent && r.statusCode != 0) || r.logBody {
if r.statusCode >= http.StatusBadRequest || r.logBody {
// Always logging error responses.
r.body.Write(p)
}
@@ -146,7 +147,7 @@ func (r *recordResponseWriter) Flush() {
func (r *recordResponseWriter) Body() []byte {
// If there was an error response or body logging is enabled
// then we return the body contents
if r.statusCode >= 400 || r.logBody {
if r.statusCode >= http.StatusBadRequest || r.logBody {
return r.body.Bytes()
}
// ... otherwise we return the <BODY> place holder
@@ -206,7 +207,7 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
// Setup a http response body recorder
respBodyRecorder := &recordResponseWriter{ResponseWriter: w, logBody: logBody}
f(respBodyRecorder, r)
f(logger.NewResponseWriter(respBodyRecorder), r)
rs := trace.ResponseInfo{
Time: time.Now().UTC(),
@@ -222,6 +223,10 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
t.ReqInfo = rq
t.RespInfo = rs
t.CallStats = trace.CallStats{Latency: rs.Time.Sub(rq.Time), InputBytes: reqBodyRecorder.Size(), OutputBytes: respBodyRecorder.Size()}
t.CallStats = trace.CallStats{
Latency: rs.Time.Sub(rq.Time),
InputBytes: reqBodyRecorder.Size(),
OutputBytes: respBodyRecorder.Size(),
}
return t
}
+3
View File
@@ -78,4 +78,7 @@ const (
// Deployment id.
MinioDeploymentID = "x-minio-deployment-id"
// Server-Status
MinIOServerStatus = "x-minio-server-status"
)
+1 -1
View File
@@ -213,7 +213,7 @@ func (ies *IAMEtcdStore) migrateToV1() error {
case errConfigNotFound:
// Need to migrate to V1.
default:
return errors.New("corrupt IAM format file")
return err
}
} else {
if iamFmt.Version >= iamFormatVersion1 {
+3 -3
View File
@@ -127,8 +127,8 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(isSTS bool) error {
identityPath := pathJoin(basePrefix, user, iamIdentityFile)
var cred auth.Credentials
if err := iamOS.loadIAMConfig(&cred, identityPath); err != nil {
switch err.(type) {
case ObjectNotFound:
switch err {
case errConfigNotFound:
// This should not happen.
default:
// File may be corrupt or network error
@@ -169,7 +169,7 @@ func (iamOS *IAMObjectStore) migrateToV1() error {
case errConfigNotFound:
// Need to migrate to V1.
default:
return errors.New("corrupt IAM format file")
return err
}
} else {
if iamFmt.Version >= iamFormatVersion1 {
+13 -1
View File
@@ -945,8 +945,14 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
return []string{policy.Policy}, nil
}
if _, ok := sys.iamUsersMap[name]; !ok {
// When looking for a user's policies, we also check if the
// user and the groups they are member of are enabled.
if u, ok := sys.iamUsersMap[name]; !ok {
return nil, errNoSuchUser
} else if u.Status == statusDisabled {
// User is disabled, so we return no policy - this
// ensures the request is denied.
return nil, nil
}
result := []string{}
@@ -956,6 +962,12 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
result = append(result, policy.Policy)
}
for _, group := range sys.iamUserGroupMemberships[name].ToSlice() {
// Skip missing or disabled groups
gi, ok := sys.iamGroupsMap[group]
if !ok || gi.Status == statusDisabled {
continue
}
p, ok := sys.iamGroupPolicyMap[group]
if ok && p.Policy != "" {
result = append(result, p.Policy)
+17 -11
View File
@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/xml"
"fmt"
"path"
"strings"
"sync"
@@ -134,20 +135,25 @@ func (sys *LifecycleSys) Init(objAPI ObjectLayer) error {
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
// Load LifecycleSys once during boot.
if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for lifecycle subsystem to be initialized..")
continue
retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
// Load LifecycleSys once during boot.
if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for lifecycle subsystem to be initialized..")
continue
}
return err
}
return err
return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing Lifecycle sub-system gracefully stopped")
}
break
}
return nil
}
// Refresh LifecycleSys.
+23 -4
View File
@@ -43,6 +43,20 @@ type lockRESTClient struct {
timer *time.Timer
}
func toLockError(err error) error {
if err == nil {
return nil
}
switch err.Error() {
case errLockConflict.Error():
return errLockConflict
case errLockNotExpired.Error():
return errLockNotExpired
}
return err
}
// ServerAddr - dsync.NetLocker interface compatible method.
func (client *lockRESTClient) ServerAddr() string {
return client.serverURL.Host
@@ -88,7 +102,6 @@ func (client *lockRESTClient) markHostDown() {
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func (client *lockRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
if !client.isHostUp() {
return nil, errors.New("Lock rest server node is down")
}
@@ -98,7 +111,6 @@ func (client *lockRESTClient) call(method string, values url.Values, body io.Rea
}
respBody, err = client.restClient.Call(method, values, body, length)
if err == nil {
return respBody, nil
}
@@ -107,7 +119,7 @@ func (client *lockRESTClient) call(method string, values url.Values, body io.Rea
client.markHostDown()
}
return nil, err
return nil, toLockError(err)
}
// Stringer provides a canonicalized representation of node.
@@ -138,7 +150,14 @@ func (client *lockRESTClient) restCall(call string, args dsync.LockArgs) (reply
respBody, err := client.call(call, values, nil, -1)
defer http.DrainBody(respBody)
return err == nil, err
switch err {
case nil:
return true, nil
case errLockConflict, errLockNotExpired:
return false, nil
default:
return false, err
}
}
// RLock calls read lock REST API.
+4
View File
@@ -17,6 +17,7 @@
package cmd
import (
"errors"
"path"
"time"
)
@@ -53,6 +54,9 @@ type nameLockRequesterInfoPair struct {
lri lockRequesterInfo
}
var errLockConflict = errors.New("lock conflict")
var errLockNotExpired = errors.New("lock not expired")
// Similar to removeEntry but only removes an entry only if the lock entry exists in map.
func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) {
// Check if entry is still in map (could have been removed altogether by 'concurrent' (R)Unlock of last entry)
+1 -1
View File
@@ -35,7 +35,7 @@ func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
}
locker := &lockRESTServer{
ll: localLocker{
ll: &localLocker{
mutex: sync.Mutex{},
serviceEndpoint: "rpc-path",
lockMap: make(map[string][]lockRequesterInfo),
+29 -13
View File
@@ -42,7 +42,7 @@ const (
// To abstract a node over network.
type lockRESTServer struct {
ll localLocker
ll *localLocker
}
func (l *lockRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
@@ -62,6 +62,7 @@ func (l *lockRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
func getLockArgs(r *http.Request) dsync.LockArgs {
return dsync.LockArgs{
UID: r.URL.Query().Get(lockRESTUID),
Source: r.URL.Query().Get(lockRESTSource),
Resource: r.URL.Query().Get(lockRESTResource),
ServerAddr: r.URL.Query().Get(lockRESTServerAddr),
ServiceEndpoint: r.URL.Query().Get(lockRESTServerEndpoint),
@@ -75,7 +76,11 @@ func (l *lockRESTServer) LockHandler(w http.ResponseWriter, r *http.Request) {
return
}
if _, err := l.ll.Lock(getLockArgs(r)); err != nil {
success, err := l.ll.Lock(getLockArgs(r))
if err == nil && !success {
err = errLockConflict
}
if err != nil {
l.writeErrorResponse(w, err)
return
}
@@ -88,7 +93,10 @@ func (l *lockRESTServer) UnlockHandler(w http.ResponseWriter, r *http.Request) {
return
}
if _, err := l.ll.Unlock(getLockArgs(r)); err != nil {
_, err := l.ll.Unlock(getLockArgs(r))
// Ignore the Unlock() "reply" return value because if err == nil, "reply" is always true
// Consequently, if err != nil, reply is always false
if err != nil {
l.writeErrorResponse(w, err)
return
}
@@ -101,7 +109,11 @@ func (l *lockRESTServer) RLockHandler(w http.ResponseWriter, r *http.Request) {
return
}
if _, err := l.ll.RLock(getLockArgs(r)); err != nil {
success, err := l.ll.RLock(getLockArgs(r))
if err == nil && !success {
err = errLockConflict
}
if err != nil {
l.writeErrorResponse(w, err)
return
}
@@ -114,7 +126,10 @@ func (l *lockRESTServer) RUnlockHandler(w http.ResponseWriter, r *http.Request)
return
}
if _, err := l.ll.RUnlock(getLockArgs(r)); err != nil {
// Ignore the RUnlock() "reply" return value because if err == nil, "reply" is always true.
// Consequently, if err != nil, reply is always false
_, err := l.ll.RUnlock(getLockArgs(r))
if err != nil {
l.writeErrorResponse(w, err)
return
}
@@ -127,6 +142,8 @@ func (l *lockRESTServer) ForceUnlockHandler(w http.ResponseWriter, r *http.Reque
return
}
// Ignore the ForceUnlock() "reply" return value because if err == nil, "reply" is always true
// Consequently, if err != nil, reply is always false
if _, err := l.ll.ForceUnlock(getLockArgs(r)); err != nil {
l.writeErrorResponse(w, err)
return
@@ -142,7 +159,6 @@ func (l *lockRESTServer) ExpiredHandler(w http.ResponseWriter, r *http.Request)
lockArgs := getLockArgs(r)
success := true
l.ll.mutex.Lock()
defer l.ll.mutex.Unlock()
// Lock found, proceed to verify if belongs to given uid.
@@ -150,15 +166,11 @@ func (l *lockRESTServer) ExpiredHandler(w http.ResponseWriter, r *http.Request)
// Check whether uid is still active
for _, entry := range lri {
if entry.UID == lockArgs.UID {
success = false // When uid found, lock is still active so return not expired.
break
l.writeErrorResponse(w, errLockNotExpired)
return
}
}
}
if !success {
l.writeErrorResponse(w, errors.New("lock already expired"))
return
}
}
// lockMaintenance loops over locks that have been active for some time and checks back
@@ -189,11 +201,15 @@ func (l *lockRESTServer) lockMaintenance(interval time.Duration) {
}
// Call back to original server verify whether the lock is still active (based on name & uid)
expired, _ := c.Expired(dsync.LockArgs{
expired, err := c.Expired(dsync.LockArgs{
UID: nlrip.lri.UID,
Resource: nlrip.name,
})
if err != nil {
continue
}
// For successful response, verify if lock was indeed active or stale.
if expired {
// The lock is no longer active at server that originated
-1
View File
@@ -116,7 +116,6 @@ func newApp(name string) *cli.App {
// Register all commands.
registerCommand(serverCmd)
registerCommand(gatewayCmd)
registerCommand(updateCmd)
registerCommand(versionCmd)
// Set up app.
+2 -2
View File
@@ -67,7 +67,7 @@ func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int)
myNode = len(clnts)
receiver := &lockRESTServer{
ll: localLocker{
ll: &localLocker{
serverAddr: endpoint.Host,
serviceEndpoint: lockServicePath,
lockMap: make(map[string][]lockRequesterInfo),
@@ -75,7 +75,7 @@ func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int)
}
globalLockServer = receiver
locker = &(receiver.ll)
locker = receiver.ll
} else {
host, err := xnet.ParseHost(endpoint.Host)
logger.FatalIf(err, "Unable to parse Lock RPC Host")
+79 -10
View File
@@ -405,6 +405,21 @@ func (sys *NotificationSys) DownloadProfilingData(ctx context.Context, writer io
return profilingDataFound
}
// ServerUpdate - updates remote peers.
func (sys *NotificationSys) ServerUpdate(updateURL, sha256Hex string, latestReleaseTime time.Time) []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.ServerUpdate(updateURL, sha256Hex, latestReleaseTime)
}, idx, *client.host)
}
return ng.Wait()
}
// SignalService - calls signal service RPC call on all peers.
func (sys *NotificationSys) SignalService(sig serviceSignal) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
@@ -793,19 +808,24 @@ func (sys *NotificationSys) Init(objAPI ObjectLayer) error {
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for notification subsystem to be initialized..")
continue
retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for notification subsystem to be initialized..")
continue
}
return err
}
return err
return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing Notification sub-system gracefully stopped")
}
break
}
return nil
}
// AddRulesMap - adds rules map for bucket name.
@@ -907,6 +927,55 @@ func (sys *NotificationSys) Send(args eventArgs) []event.TargetIDErr {
return sys.send(args.BucketName, args.ToEvent(), targetIDs...)
}
// NetReadPerfInfo - Network read performance information.
func (sys *NotificationSys) NetReadPerfInfo(size int64) []ServerNetReadPerfInfo {
reply := make([]ServerNetReadPerfInfo, len(sys.peerClients))
// Execution is done serially.
for i, client := range sys.peerClients {
if client == nil {
continue
}
info, err := client.NetReadPerfInfo(size)
if err != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("remotePeer", client.host.String())
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err)
info.Addr = client.host.String()
info.Error = err.Error()
}
reply[i] = info
}
return reply
}
// CollectNetPerfInfo - Collect network performance information of all peers.
func (sys *NotificationSys) CollectNetPerfInfo(size int64) map[string][]ServerNetReadPerfInfo {
reply := map[string][]ServerNetReadPerfInfo{}
// Execution is done serially.
for _, client := range sys.peerClients {
if client == nil {
continue
}
info, err := client.CollectNetPerfInfo(size)
if err != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("remotePeer", client.host.String())
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err)
}
reply[client.host.String()] = info
}
return reply
}
// DrivePerfInfo - Drive speed (read and write) information
func (sys *NotificationSys) DrivePerfInfo() []ServerDrivesPerfInfo {
reply := make([]ServerDrivesPerfInfo, len(sys.peerClients))
-1
View File
@@ -64,7 +64,6 @@ func testGetObjectInfo(obj ObjectLayer, instanceType string, t TestErrHandler) {
}{
// Test cases with invalid bucket names ( Test number 1-4 ).
{".test", "", ObjectInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
{"Test", "", ObjectInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
{"---", "", ObjectInfo{}, BucketNameInvalid{Bucket: "---"}, false},
{"ad", "", ObjectInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
// Test cases with valid but non-existing bucket names (Test number 5-6).
+2 -1
View File
@@ -19,6 +19,7 @@ package cmd
import (
"context"
"github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio/cmd/logger"
"github.com/skyrings/skyring-common/tools/uuid"
)
@@ -36,7 +37,7 @@ func checkDelObjArgs(ctx context.Context, bucket, object string) error {
// Checks bucket and object name validity, returns nil if both are valid.
func checkBucketAndObjectNames(ctx context.Context, bucket, object string) error {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
if !isMinioMetaBucketName(bucket) && s3utils.CheckValidBucketName(bucket) != nil {
logger.LogIf(ctx, BucketNameInvalid{Bucket: bucket})
return BucketNameInvalid{Bucket: bucket}
}
+52 -21
View File
@@ -601,8 +601,21 @@ var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core,
return core, nil
}
// Check if the destination bucket is on a remote site, this code only gets executed
// when federation is enabled, ie when globalDNSConfig is non 'nil'.
//
// This function is similar to isRemoteCallRequired but specifically for COPY object API
// if destination and source are same we do not need to check for destnation bucket
// to exist locally.
func isRemoteCopyRequired(ctx context.Context, srcBucket, dstBucket string, objAPI ObjectLayer) bool {
if srcBucket == dstBucket {
return false
}
return isRemoteCallRequired(ctx, dstBucket, objAPI)
}
// Check if the bucket is on a remote site, this code only gets executed when federation is enabled.
var isRemoteCallRequired = func(ctx context.Context, bucket string, objAPI ObjectLayer) bool {
func isRemoteCallRequired(ctx context.Context, bucket string, objAPI ObjectLayer) bool {
if globalDNSConfig == nil {
return false
}
@@ -787,23 +800,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
length = actualSize
}
// Check if the destination bucket is on a remote site, this code only gets executed
// when federation is enabled, ie when globalDNSConfig is non 'nil'.
//
// This function is similar to isRemoteCallRequired but specifically for COPY object API
// if destination and source are same we do not need to check for destnation bucket
// to exist locally.
var isRemoteCopyRequired = func(ctx context.Context, srcBucket, dstBucket string, objAPI ObjectLayer) bool {
if globalDNSConfig == nil {
return false
}
if srcBucket == dstBucket {
return false
}
_, err := objAPI.GetBucketInfo(ctx, dstBucket)
return err == toObjectErr(errVolumeNotFound, dstBucket)
}
var compressMetadata map[string]string
// No need to compress for remote etcd calls
// Pass the decompressed stream to such calls.
@@ -1067,7 +1063,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
r.Body = &detectDisconnect{r.Body, r.Context().Done()}
// X-Amz-Copy-Source shouldn't be set for this call.
if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
if _, ok := r.Header[xhttp.AmzCopySource]; ok {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r))
return
}
@@ -1090,7 +1086,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
size := r.ContentLength
rAuthType := getRequestAuthType(r)
if rAuthType == authTypeStreamingSigned {
if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok {
if sizeStr, ok := r.Header[xhttp.AmzDecodedContentLength]; ok {
if sizeStr[0] == "" {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
return
@@ -1209,6 +1205,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
getObjectInfo := objectAPI.GetObjectInfo
if api.CacheAPI() != nil {
getObjectInfo = api.CacheAPI().GetObjectInfo
putObject = api.CacheAPI().PutObject
}
rawReader := hashReader
pReader := NewPutObjReader(rawReader, nil, nil)
@@ -1223,7 +1224,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
// Deny if WORM is enabled
if globalWORMEnabled {
if _, err = objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil {
if _, err = getObjectInfo(ctx, bucket, object, opts); err == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return
}
@@ -1591,6 +1592,36 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
return
}
if isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) {
var dstRecords []dns.SrvRecord
dstRecords, err = globalDNSConfig.Get(dstBucket)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
// Send PutObject request to appropriate instance (in federated deployment)
client, rerr := getRemoteInstanceClient(r, getHostFromSrv(dstRecords))
if rerr != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, rerr), r.URL, guessIsBrowserReq(r))
return
}
partInfo, err := client.PutObjectPart(dstBucket, dstObject, uploadID, partID,
srcInfo.Reader, srcInfo.Size, "", "", dstOpts.ServerSideEncryption)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
response := generateCopyObjectPartResponse(partInfo.ETag, partInfo.LastModified)
encodedSuccessResponse := encodeResponse(response)
// Write success response.
writeSuccessResponseXML(w, encodedSuccessResponse)
return
}
actualPartSize = length
var reader io.Reader
+93 -1
View File
@@ -22,6 +22,7 @@ import (
"crypto/tls"
"encoding/gob"
"io"
"math/rand"
"net/url"
"strconv"
"time"
@@ -107,6 +108,37 @@ func (client *peerRESTClient) Close() error {
// GetLocksResp stores various info from the client for each lock that is requested.
type GetLocksResp map[string][]lockRequesterInfo
// NetReadPerfInfo - fetch network read performance information for a remote node.
func (client *peerRESTClient) NetReadPerfInfo(size int64) (info ServerNetReadPerfInfo, err error) {
params := make(url.Values)
params.Set(peerRESTNetPerfSize, strconv.FormatInt(size, 10))
respBody, err := client.call(
peerRESTMethodNetReadPerfInfo,
params,
rand.New(rand.NewSource(time.Now().UnixNano())),
size,
)
if err != nil {
return
}
defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&info)
return info, err
}
// CollectNetPerfInfo - collect network performance information of other peers.
func (client *peerRESTClient) CollectNetPerfInfo(size int64) (info []ServerNetReadPerfInfo, err error) {
params := make(url.Values)
params.Set(peerRESTNetPerfSize, strconv.FormatInt(size, 10))
respBody, err := client.call(peerRESTMethodCollectNetPerfInfo, params, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&info)
return info, err
}
// GetLocks - fetch older locks for a remote node.
func (client *peerRESTClient) GetLocks() (locks GetLocksResp, err error) {
respBody, err := client.call(peerRESTMethodGetLocks, nil, nil, -1)
@@ -471,10 +503,28 @@ func (client *peerRESTClient) LoadGroup(group string) error {
return nil
}
// ServerUpdate - sends server update message to remote peers.
func (client *peerRESTClient) ServerUpdate(updateURL, sha256Hex string, latestReleaseTime time.Time) error {
values := make(url.Values)
values.Set(peerRESTUpdateURL, updateURL)
values.Set(peerRESTSha256Hex, sha256Hex)
if !latestReleaseTime.IsZero() {
values.Set(peerRESTLatestRelease, latestReleaseTime.Format(time.RFC3339))
} else {
values.Set(peerRESTLatestRelease, "")
}
respBody, err := client.call(peerRESTMethodServerUpdate, values, nil, -1)
if err != nil {
return err
}
defer http.DrainBody(respBody)
return nil
}
// SignalService - sends signal to peer nodes.
func (client *peerRESTClient) SignalService(sig serviceSignal) error {
values := make(url.Values)
values.Set(peerRESTSignal, string(sig))
values.Set(peerRESTSignal, strconv.Itoa(int(sig)))
respBody, err := client.call(peerRESTMethodSignalService, values, nil, -1)
if err != nil {
return err
@@ -579,6 +629,48 @@ func (client *peerRESTClient) Trace(traceCh chan interface{}, doneCh chan struct
}()
}
// ConsoleLog - sends request to peer nodes to get console logs
func (client *peerRESTClient) ConsoleLog(logCh chan interface{}, doneCh chan struct{}) {
go func() {
for {
// get cancellation context to properly unsubscribe peers
ctx, cancel := context.WithCancel(context.Background())
respBody, err := client.callWithContext(ctx, peerRESTMethodLog, nil, nil, -1)
if err != nil {
// Retry the failed request.
time.Sleep(5 * time.Second)
} else {
dec := gob.NewDecoder(respBody)
go func() {
<-doneCh
cancel()
}()
for {
var log madmin.LogInfo
if err = dec.Decode(&log); err != nil {
break
}
select {
case logCh <- log:
default:
}
}
}
select {
case <-doneCh:
cancel()
http.DrainBody(respBody)
return
default:
// There was error in the REST request, retry.
}
}
}()
}
func getRemoteHosts(endpoints EndpointList) []*xnet.Host {
var remoteHosts []*xnet.Host
for _, hostStr := range GetRemotePeers(endpoints) {
+20 -12
View File
@@ -20,11 +20,14 @@ const peerRESTVersion = "v4"
const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion
const (
peerRESTMethodNetReadPerfInfo = "netreadperfinfo"
peerRESTMethodCollectNetPerfInfo = "collectnetperfinfo"
peerRESTMethodServerInfo = "serverinfo"
peerRESTMethodCPULoadInfo = "cpuloadinfo"
peerRESTMethodMemUsageInfo = "memusageinfo"
peerRESTMethodDrivePerfInfo = "driveperfinfo"
peerRESTMethodDeleteBucket = "deletebucket"
peerRESTMethodServerUpdate = "serverupdate"
peerRESTMethodSignalService = "signalservice"
peerRESTMethodBackgroundHealStatus = "backgroundhealstatus"
peerRESTMethodBackgroundOpsStatus = "backgroundopsstatus"
@@ -48,19 +51,24 @@ const (
peerRESTMethodTrace = "trace"
peerRESTMethodBucketLifecycleSet = "setbucketlifecycle"
peerRESTMethodBucketLifecycleRemove = "removebucketlifecycle"
peerRESTMethodLog = "log"
)
const (
peerRESTBucket = "bucket"
peerRESTUser = "user"
peerRESTGroup = "group"
peerRESTUserTemp = "user-temp"
peerRESTPolicy = "policy"
peerRESTUserOrGroup = "user-or-group"
peerRESTIsGroup = "is-group"
peerRESTSignal = "signal"
peerRESTProfiler = "profiler"
peerRESTDryRun = "dry-run"
peerRESTTraceAll = "all"
peerRESTTraceErr = "err"
peerRESTNetPerfSize = "netperfsize"
peerRESTBucket = "bucket"
peerRESTUser = "user"
peerRESTGroup = "group"
peerRESTUserTemp = "user-temp"
peerRESTPolicy = "policy"
peerRESTUserOrGroup = "user-or-group"
peerRESTIsGroup = "is-group"
peerRESTUpdateURL = "updateURL"
peerRESTSha256Hex = "sha256Hex"
peerRESTLatestRelease = "latestReleaseTime"
peerRESTSignal = "signal"
peerRESTProfiler = "profiler"
peerRESTDryRun = "dry-run"
peerRESTTraceAll = "all"
peerRESTTraceErr = "err"
)
+147 -36
View File
@@ -21,8 +21,9 @@ import (
"encoding/gob"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"sort"
"strconv"
"strings"
"time"
@@ -31,6 +32,7 @@ import (
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/lifecycle"
"github.com/minio/minio/pkg/madmin"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/policy"
trace "github.com/minio/minio/pkg/trace"
@@ -65,44 +67,81 @@ func getServerInfo() (*ServerInfoData, error) {
}, nil
}
// uptimes - used to sort uptimes in chronological order.
type uptimes []time.Duration
func (ts uptimes) Len() int {
return len(ts)
}
func (ts uptimes) Less(i, j int) bool {
return ts[i] < ts[j]
}
func (ts uptimes) Swap(i, j int) {
ts[i], ts[j] = ts[j], ts[i]
}
// getPeerUptimes - returns the uptime.
func getPeerUptimes(serverInfo []ServerInfo) time.Duration {
// In a single node Erasure or FS backend setup the uptime of
// the setup is the uptime of the single minio server
// instance.
if !globalIsDistXL {
return UTCNow().Sub(globalBootTime)
// NetReadPerfInfoHandler - returns network read performance information.
func (s *peerRESTServer) NetReadPerfInfoHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
var times []time.Duration
params := mux.Vars(r)
for _, info := range serverInfo {
if info.Error != "" {
continue
}
times = append(times, info.Data.Properties.Uptime)
sizeStr, found := params[peerRESTNetPerfSize]
if !found {
s.writeErrorResponse(w, errors.New("size is missing"))
return
}
// Sort uptimes in chronological order.
sort.Sort(uptimes(times))
size, err := strconv.ParseInt(sizeStr, 10, 64)
if err != nil || size < 0 {
s.writeErrorResponse(w, errInvalidArgument)
return
}
// Return the latest time as the uptime.
return times[0]
start := time.Now()
n, err := io.CopyN(ioutil.Discard, r.Body, size)
end := time.Now()
if err != nil {
s.writeErrorResponse(w, err)
return
}
if n != size {
s.writeErrorResponse(w, fmt.Errorf("short read; expected: %v, got: %v", size, n))
return
}
addr := r.Host
if globalIsDistXL {
addr = GetLocalPeer(globalEndpoints)
}
info := ServerNetReadPerfInfo{
Addr: addr,
ReadPerf: end.Sub(start),
}
ctx := newContext(r, w, "NetReadPerfInfo")
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
w.(http.Flusher).Flush()
}
// CollectNetPerfInfoHandler - returns network performance information collected from other peers.
func (s *peerRESTServer) CollectNetPerfInfoHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
params := mux.Vars(r)
sizeStr, found := params[peerRESTNetPerfSize]
if !found {
s.writeErrorResponse(w, errors.New("size is missing"))
return
}
size, err := strconv.ParseInt(sizeStr, 10, 64)
if err != nil || size < 0 {
s.writeErrorResponse(w, errInvalidArgument)
return
}
info := globalNotificationSys.NetReadPerfInfo(size)
ctx := newContext(r, w, "CollectNetPerfInfo")
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
w.(http.Flusher).Flush()
}
// GetLocksHandler - returns list of older lock from the server.
@@ -730,6 +769,35 @@ func (s *peerRESTServer) ListenBucketNotificationHandler(w http.ResponseWriter,
w.(http.Flusher).Flush()
}
// ServerUpdateHandler - updates the current server.
func (s *peerRESTServer) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
vars := mux.Vars(r)
updateURL := vars[peerRESTUpdateURL]
sha256Hex := vars[peerRESTSha256Hex]
var latestReleaseTime time.Time
var err error
if latestRelease := vars[peerRESTLatestRelease]; latestRelease != "" {
latestReleaseTime, err = time.Parse(latestRelease, time.RFC3339)
if err != nil {
s.writeErrorResponse(w, err)
return
}
}
us, err := updateServer(updateURL, sha256Hex, latestReleaseTime)
if err != nil {
s.writeErrorResponse(w, err)
return
}
if us.CurrentVersion != us.UpdatedVersion {
globalServiceSignalCh <- serviceRestart
}
}
var errUnsupportedSignal = fmt.Errorf("unsupported signal: only restart and stop signals are supported")
// SignalServiceHandler - signal service handler.
@@ -745,10 +813,17 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
s.writeErrorResponse(w, errors.New("signal name is missing"))
return
}
signal := serviceSignal(signalString)
si, err := strconv.Atoi(signalString)
if err != nil {
s.writeErrorResponse(w, err)
return
}
signal := serviceSignal(si)
defer w.(http.Flusher).Flush()
switch signal {
case serviceRestart, serviceStop:
case serviceRestart:
globalServiceSignalCh <- signal
case serviceStop:
globalServiceSignalCh <- signal
default:
s.writeErrorResponse(w, errUnsupportedSignal)
@@ -829,6 +904,38 @@ func (s *peerRESTServer) BackgroundOpsStatusHandler(w http.ResponseWriter, r *ht
logger.LogIf(ctx, gob.NewEncoder(w).Encode(state))
}
// ConsoleLogHandler sends console logs of this node back to peer rest client
func (s *peerRESTServer) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
w.Header().Set("Connection", "close")
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
doneCh := make(chan struct{})
defer close(doneCh)
ch := make(chan interface{}, 2000)
globalConsoleSys.Subscribe(ch, doneCh, "", 0, nil)
enc := gob.NewEncoder(w)
for {
select {
case entry := <-ch:
log := entry.(madmin.LogInfo)
if err := enc.Encode(log); 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()))
@@ -847,6 +954,8 @@ func (s *peerRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
func registerPeerRESTHandlers(router *mux.Router) {
server := &peerRESTServer{}
subrouter := router.PathPrefix(peerRESTPath).Subrouter()
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodNetReadPerfInfo).HandlerFunc(httpTraceHdrs(server.NetReadPerfInfoHandler)).Queries(restQueries(peerRESTNetPerfSize)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodCollectNetPerfInfo).HandlerFunc(httpTraceHdrs(server.CollectNetPerfInfoHandler)).Queries(restQueries(peerRESTNetPerfSize)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodGetLocks).HandlerFunc(httpTraceHdrs(server.GetLocksHandler))
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodServerInfo).HandlerFunc(httpTraceHdrs(server.ServerInfoHandler))
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodCPULoadInfo).HandlerFunc(httpTraceHdrs(server.CPULoadInfoHandler))
@@ -854,11 +963,12 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDrivePerfInfo).HandlerFunc(httpTraceHdrs(server.DrivePerfInfoHandler))
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteBucket).HandlerFunc(httpTraceHdrs(server.DeleteBucketHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodSignalService).HandlerFunc(httpTraceHdrs(server.SignalServiceHandler)).Queries(restQueries(peerRESTSignal)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodServerUpdate).HandlerFunc(httpTraceHdrs(server.ServerUpdateHandler)).Queries(restQueries(peerRESTUpdateURL, peerRESTSha256Hex, peerRESTLatestRelease)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeletePolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeletePolicy).HandlerFunc(httpTraceAll(server.DeletePolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLoadPolicyMapping).HandlerFunc(httpTraceAll(server.LoadPolicyMappingHandler)).Queries(restQueries(peerRESTUserOrGroup)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser)...)
@@ -881,6 +991,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodTrace).HandlerFunc(server.TraceHandler)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodBackgroundHealStatus).HandlerFunc(server.BackgroundHealStatusHandler)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodLog).HandlerFunc(server.ConsoleLogHandler)
router.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
}
+16 -11
View File
@@ -163,20 +163,25 @@ func (sys *PolicySys) Init(objAPI ObjectLayer) error {
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
// Load PolicySys once during boot.
if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for policy subsystem to be initialized..")
continue
retryTimerCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryTimerCh:
// Load PolicySys once during boot.
if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for policy subsystem to be initialized..")
continue
}
return err
}
return err
return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing Policy sub-system gracefully stopped")
}
break
}
return nil
}
// NewPolicySys - creates new policy system.
+2 -2
View File
@@ -47,7 +47,7 @@ const (
diskMinFreeSpace = 900 * humanize.MiByte // Min 900MiB free space.
diskMinTotalSpace = diskMinFreeSpace // Min 900MiB total space.
maxAllowedIOError = 5
readBlockSize = humanize.KiByte * 32 // Default read block size 32KiB.
readBlockSize = 4 * humanize.MiByte // Default read block size 4MiB.
)
// isValidVolname verifies a volname name in accordance with object
@@ -1230,7 +1230,7 @@ func (s *posix) CreateFile(volume, path string, fileSize int64, r io.Reader) (er
bufp := s.pool.Get().(*[]byte)
defer s.pool.Put(bufp)
written, err := xioutil.CopyAligned(w, r, *bufp)
written, err := xioutil.CopyAligned(w, r, *bufp, fileSize)
if err != nil {
return err
}
+2
View File
@@ -289,6 +289,8 @@ func serverMain(ctx *cli.Context) {
globalSweepHealState = initHealState()
}
// initialize globalConsoleSys system
globalConsoleSys = NewConsoleLogger(context.Background(), globalEndpoints)
// Configure server.
var handler http.Handler
handler, err = configureServerHandler(globalEndpoints)
-15
View File
@@ -87,7 +87,6 @@ func runAllTests(suite *TestSuiteCommon, c *check) {
suite.TestObjectGetAnonymous(c)
suite.TestObjectGet(c)
suite.TestMultipleObjects(c)
suite.TestNotImplemented(c)
suite.TestHeader(c)
suite.TestPutBucket(c)
suite.TestCopyObject(c)
@@ -996,20 +995,6 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *check) {
c.Assert(true, bytes.Equal(responseBody, []byte("hello three")))
}
// TestNotImplemented - validates if object policy is implemented, should return 'NotImplemented'.
func (s *TestSuiteCommon) TestNotImplemented(c *check) {
// Generate a random bucket name.
bucketName := getRandomBucketName()
request, err := newTestSignedRequest("GET", s.endPoint+SlashSeparator+bucketName+"/object?policy",
0, nil, s.accessKey, s.secretKey, s.signer)
c.Assert(err, nil)
client := http.Client{Transport: s.transport}
response, err := client.Do(request)
c.Assert(err, nil)
c.Assert(response.StatusCode, http.StatusNotImplemented)
}
// TestHeader - Validates the error response for an attempt to fetch non-existent object.
func (s *TestSuiteCommon) TestHeader(c *check) {
// generate a random bucket name.
+3 -4
View File
@@ -23,12 +23,11 @@ import (
)
// Type of service signals currently supported.
type serviceSignal string
type serviceSignal int
const (
serviceStatus serviceSignal = "serviceStatus" // Gets status about the service.
serviceRestart = "serviceRestart" // Restarts the service.
serviceStop = "serviceStop" // Stops the server.
serviceRestart serviceSignal = iota // Restarts the server.
serviceStop // Stops the server.
// Add new service requests here.
)
-2
View File
@@ -78,8 +78,6 @@ func handleSignals() {
exit(stopProcess())
case signal := <-globalServiceSignalCh:
switch signal {
case serviceStatus:
// Ignore this at the moment.
case serviceRestart:
logger.Info("Restarting on service signal")
stop := stopProcess()
+3
View File
@@ -374,6 +374,9 @@ func (client *storageRESTClient) DeleteFile(volume, path string) error {
// DeleteFileBulk - deletes files in bulk.
func (client *storageRESTClient) DeleteFileBulk(volume string, paths []string) (errs []error, err error) {
if len(paths) == 0 {
return errs, err
}
errs = make([]error, len(paths))
values := make(url.Values)
values.Set(storageRESTVolume, volume)
+1 -1
View File
@@ -600,7 +600,7 @@ func registerStorageRESTHandlers(router *mux.Router, endpoints EndpointList) {
subrouter.Methods(http.MethodPost).Path(SlashSeparator + storageRESTMethodRenameFile).HandlerFunc(httpTraceHdrs(server.RenameFileHandler)).
Queries(restQueries(storageRESTSrcVolume, storageRESTSrcPath, storageRESTDstVolume, storageRESTDstPath)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + storageRESTMethodVerifyFile).HandlerFunc(httpTraceHdrs(server.VerifyFile)).
Queries(restQueries(storageRESTVolume, storageRESTFilePath, storageRESTBitrotAlgo, storageRESTLength)...)
Queries(restQueries(storageRESTVolume, storageRESTFilePath, storageRESTBitrotAlgo, storageRESTLength, storageRESTEmpty)...)
subrouter.Methods(http.MethodPost).Path(SlashSeparator + storageRESTMethodGetInstanceID).HandlerFunc(httpTraceAll(server.GetInstanceID))
}
+6
View File
@@ -71,6 +71,12 @@ var (
"MINIO_CACHE_MAXUSE: Valid cache max-use value between 0-100",
)
uiErrInvalidCacheEncryptionKey = newUIErrFn(
"Invalid cache encryption master key value",
"Please check the passed value",
"MINIO_CACHE_ENCRYPTION_MASTER_KEY: For more information, please refer to https://docs.min.io/docs/minio-disk-cache-guide",
)
uiErrInvalidCredentials = newUIErrFn(
"Invalid credentials",
"Please provide correct credentials",
+125 -133
View File
@@ -23,6 +23,7 @@ import (
"encoding/hex"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
@@ -30,45 +31,12 @@ import (
"strings"
"time"
"github.com/fatih/color"
"github.com/inconshreveable/go-update"
"github.com/minio/cli"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
_ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier.
)
// Check for new software updates.
var updateCmd = cli.Command{
Name: "update",
Usage: "update minio to latest release",
Action: mainUpdate,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "quiet",
Usage: "disable any update prompt message",
},
},
CustomHelpTemplate: `Name:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}}{{if .VisibleFlags}} [FLAGS]{{end}}
{{if .VisibleFlags}}
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXIT STATUS:
0 - You are already running the most recent version.
1 - New update was applied successfully.
-1 - Error in getting update information.
EXAMPLES:
1. Check and update minio:
{{.Prompt}} {{.HelpName}}
`,
}
const (
minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
@@ -109,11 +77,14 @@ func releaseTimeToReleaseTag(releaseTime time.Time) string {
// releaseTagToReleaseTime - reverse of `releaseTimeToReleaseTag()`
func releaseTagToReleaseTime(releaseTag string) (releaseTime time.Time, err error) {
tagTimePart := strings.TrimPrefix(releaseTag, "RELEASE.")
if tagTimePart == releaseTag {
fields := strings.Split(releaseTag, ".")
if len(fields) < 2 || len(fields) > 3 {
return releaseTime, fmt.Errorf("%s is not a valid release tag", releaseTag)
}
return time.Parse(minioReleaseTagTimeLayout, tagTimePart)
if fields[0] != "RELEASE" {
return releaseTime, fmt.Errorf("%s is not a valid release tag", releaseTag)
}
return time.Parse(minioReleaseTagTimeLayout, fields[1])
}
// getModTime - get the file modification time of `path`
@@ -298,38 +269,58 @@ func getUserAgent(mode string) string {
}
func downloadReleaseURL(releaseChecksumURL string, timeout time.Duration, mode string) (content string, err error) {
req, err := http.NewRequest("GET", releaseChecksumURL, nil)
req, err := http.NewRequest(http.MethodGet, releaseChecksumURL, nil)
if err != nil {
return content, err
return content, AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
req.Header.Set("User-Agent", getUserAgent(mode))
client := &http.Client{
Timeout: timeout,
Transport: &http.Transport{
// need to close connection after usage.
DisableKeepAlives: true,
},
}
client := &http.Client{Transport: getUpdateTransport(timeout)}
resp, err := client.Do(req)
if err != nil {
return content, err
if isNetworkOrHostDown(err) {
return content, AdminError{
Code: AdminUpdateURLNotReachable,
Message: err.Error(),
StatusCode: http.StatusServiceUnavailable,
}
}
return content, AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
if resp == nil {
return content, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL)
return content, AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: fmt.Sprintf("No response from server to download URL %s", releaseChecksumURL),
StatusCode: http.StatusInternalServerError,
}
}
defer xhttp.DrainBody(resp.Body)
if resp.StatusCode != http.StatusOK {
return content, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status)
return content, AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: fmt.Sprintf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status),
StatusCode: resp.StatusCode,
}
}
contentBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return content, fmt.Errorf("Error reading response. %s", err)
return content, AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: fmt.Sprintf("Error reading response. %s", err),
StatusCode: http.StatusInternalServerError,
}
}
return string(contentBytes), err
return string(contentBytes), nil
}
// DownloadReleaseData - downloads release data from minio official server.
@@ -338,6 +329,7 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
if runtime.GOOS == globalWindowsOSName {
releaseURLs = minioReleaseWindowsInfoURLs
}
return func() (data string, err error) {
for _, url := range releaseURLs {
data, err = downloadReleaseURL(url, timeout, mode)
@@ -345,7 +337,7 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
return data, nil
}
}
return data, fmt.Errorf("Failed to fetch release URL - last error: %s", err)
return data, err
}()
}
@@ -354,10 +346,20 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e
//
// The expected format is a single line with two words like:
//
// fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z
// fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z.<hotfix_optional>
//
// The second word must be `minio.` appended to a standard release tag.
func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err error) {
defer func() {
if err != nil {
err = AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
}()
fields := strings.Fields(data)
if len(fields) != 2 {
err = fmt.Errorf("Unknown release data `%s`", data)
@@ -367,17 +369,18 @@ func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err
sha256Hex = fields[0]
releaseInfo := fields[1]
fields = strings.SplitN(releaseInfo, ".", 2)
if len(fields) != 2 {
// Split release of style minio.RELEASE.2019-08-21T19-40-07Z.<hotfix>
nfields := strings.SplitN(releaseInfo, ".", 2)
if len(nfields) != 2 {
err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
return sha256Hex, releaseTime, err
}
if fields[0] != "minio" {
if nfields[0] != "minio" {
err = fmt.Errorf("Unknown release `%s`", releaseInfo)
return sha256Hex, releaseTime, err
}
releaseTime, err = releaseTagToReleaseTime(fields[1])
releaseTime, err = releaseTagToReleaseTime(nfields[1])
if err != nil {
err = fmt.Errorf("Unknown release tag format. %s", err)
}
@@ -385,6 +388,27 @@ func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err
return sha256Hex, releaseTime, err
}
const updateTimeout = 10 * time.Second
func getUpdateTransport(timeout time.Duration) http.RoundTripper {
var updateTransport http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dialer := &net.Dialer{
Timeout: timeout,
KeepAlive: timeout,
DualStack: true,
}
return dialer.DialContext(ctx, network, addr)
},
IdleConnTimeout: timeout,
TLSHandshakeTimeout: timeout,
ExpectContinueTimeout: timeout,
DisableCompression: true,
}
return updateTransport
}
func getLatestReleaseTime(timeout time.Duration, mode string) (sha256Hex string, releaseTime time.Time, err error) {
data, err := DownloadReleaseData(timeout, mode)
if err != nil {
@@ -450,21 +474,43 @@ func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256
return prepareUpdateMessage(downloadURL, older), sha256Hex, currentReleaseTime, latestReleaseTime, nil
}
func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateStatusMsg string, err error) {
if !ok {
updateStatusMsg = colorRedBold("MinIO update to version RELEASE.%s canceled.",
latestReleaseTime.Format(minioReleaseTagTimeLayout))
return updateStatusMsg, nil
}
func doUpdate(updateURL, sha256Hex, mode string) (err error) {
var sha256Sum []byte
sha256Sum, err = hex.DecodeString(sha256Hex)
if err != nil {
return updateStatusMsg, err
return AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
resp, err := http.Get(getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime)))
clnt := &http.Client{Transport: getUpdateTransport(30 * time.Second)}
req, err := http.NewRequest(http.MethodGet, updateURL, nil)
if err != nil {
return updateStatusMsg, err
return AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
req.Header.Set("User-Agent", getUserAgent(mode))
resp, err := clnt.Do(req)
if err != nil {
if isNetworkOrHostDown(err) {
return AdminError{
Code: AdminUpdateURLNotReachable,
Message: err.Error(),
StatusCode: http.StatusServiceUnavailable,
}
}
return AdminError{
Code: AdminUpdateUnexpectedFailure,
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
defer xhttp.DrainBody(resp.Body)
@@ -475,73 +521,19 @@ func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateSta
Checksum: sha256Sum,
},
); err != nil {
return updateStatusMsg, err
}
return colorGreenBold("MinIO updated to version RELEASE.%s successfully.",
latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil
}
// Confirm continues prompting until the input is boolean-ish.
func confirm(prompt string, args ...interface{}) bool {
for {
var s string
fmt.Fprintf(color.Output, prompt+": ", args...)
fmt.Scanln(&s)
switch s {
case "Yes", "yes", "y", "Y":
return true
case "No", "no", "n", "N":
return false
if os.IsPermission(err) {
return AdminError{
Code: AdminUpdateApplyFailure,
Message: err.Error(),
StatusCode: http.StatusForbidden,
}
}
return AdminError{
Code: AdminUpdateApplyFailure,
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
}
func shouldUpdate(quiet bool, sha256Hex string, latestReleaseTime time.Time) (ok bool) {
ok = true
if !quiet {
ok = confirm(colorGreenBold("Update to RELEASE.%s ? [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "y/n"))
}
return ok
}
func mainUpdate(ctx *cli.Context) {
if len(ctx.Args()) != 0 {
cli.ShowCommandHelpAndExit(ctx, "update", -1)
}
handleCommonEnvVars()
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
if quiet {
logger.EnableQuiet()
}
minioMode := ""
updateMsg, sha256Hex, _, latestReleaseTime, err := getUpdateInfo(10*time.Second, minioMode)
if err != nil {
logger.Info(err.Error())
os.Exit(-1)
}
// Nothing to update running the latest release.
if updateMsg == "" {
logger.Info(colorGreenBold("You are already running the most recent version of ‘minio’."))
os.Exit(0)
}
logger.Info(updateMsg)
// if the in-place update is disabled then we shouldn't ask the
// user to update the binaries.
if strings.Contains(updateMsg, minioReleaseURL) && !globalInplaceUpdateDisabled {
var updateStatusMsg string
updateStatusMsg, err = doUpdate(sha256Hex, latestReleaseTime, shouldUpdate(quiet, sha256Hex, latestReleaseTime))
if err != nil {
logger.Info(colorRedBold("Unable to update ‘minio’."))
logger.Info(err.Error())
os.Exit(-1)
}
logger.Info(updateStatusMsg)
os.Exit(1)
}
return nil
}
@@ -285,6 +285,7 @@ func TestParseReleaseData(t *testing.T) {
{"more minio.RELEASE.fields", time.Time{}, "", fmt.Errorf(`Unknown release tag format. parsing time "fields" as "2006-01-02T15-04-05Z": cannot parse "fields" as "2006"`)},
{"more minio.RELEASE.2016-10-07T01-16-39Z", releaseTime, "more", nil},
{"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", releaseTime, "fbe246edbd382902db9a4035df7dce8cb441357d", nil},
{"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z.customer-hotfix\n", releaseTime, "fbe246edbd382902db9a4035df7dce8cb441357d", nil},
}
for i, testCase := range testCases {
+18 -2
View File
@@ -29,6 +29,7 @@ import (
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
@@ -446,10 +447,25 @@ func isNetworkOrHostDown(err error) bool {
// or a non-temporary error.
e, ok := err.(net.Error)
if ok {
return e.Timeout()
urlErr, ok := e.(*url.Error)
if ok {
switch urlErr.Err.(type) {
case *net.DNSError, *net.OpError, net.UnknownNetworkError:
return true
}
}
if e.Timeout() {
return true
}
}
ok = false
// Fallback to other mechanisms.
if strings.Contains(err.Error(), "i/o timeout") {
if strings.Contains(err.Error(), "Connection closed by foreign host") {
ok = true
} else if strings.Contains(err.Error(), "TLS handshake timeout") {
// If error is - tlsHandshakeTimeoutError.
ok = true
} else if strings.Contains(err.Error(), "i/o timeout") {
// If error is - tcp timeoutError.
ok = true
} else if strings.Contains(err.Error(), "connection timed out") {
+6 -28
View File
@@ -1053,6 +1053,9 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
}
putObject := objectAPI.PutObject
if web.CacheAPI() != nil {
putObject = web.CacheAPI().PutObject
}
objInfo, err := putObject(context.Background(), bucket, object, pReader, opts)
if err != nil {
@@ -1316,17 +1319,6 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
info := gr.ObjInfo
length = info.Size
if objectAPI.IsEncryptionSupported() {
if _, err = DecryptObjectInfo(&info, r.Header); err != nil {
writeWebErrorResponse(w, err)
return err
}
if crypto.IsEncrypted(info.UserDefined) {
length, _ = info.DecryptedSize()
}
}
length = info.Size
var actualSize int64
if info.IsCompressed() {
// Read the decompressed size from the meta.json.
@@ -1345,21 +1337,16 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
writeWebErrorResponse(w, errUnexpected)
return err
}
var startOffset int64
var writer io.Writer
if info.IsCompressed() {
// The decompress metrics are set.
snappyStartOffset := 0
snappyLength := actualSize
// Open a pipe for compression
// Where compressWriter is actually passed to the getObject
decompressReader, compressWriter := io.Pipe()
snappyReader := snappy.NewReader(decompressReader)
// The limit is set to the actual size.
responseWriter := ioutil.LimitedWriter(zipWriter, int64(snappyStartOffset), snappyLength)
responseWriter := ioutil.LimitedWriter(zipWriter, 0, actualSize)
wg.Add(1) //For closures.
go func() {
defer wg.Done()
@@ -1374,17 +1361,6 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
} else {
writer = zipWriter
}
if objectAPI.IsEncryptionSupported() && crypto.S3.IsEncrypted(info.UserDefined) {
// Response writer should be limited early on for decryption upto required length,
// additionally also skipping mod(offset)64KiB boundaries.
writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length)
writer, _, length, err = DecryptBlocksRequest(writer, r,
args.BucketName, objectName, startOffset, length, info, false)
if err != nil {
writeWebErrorResponse(w, err)
return err
}
}
httpWriter := ioutil.WriteOnClose(writer)
// Write object content to response body
@@ -1972,6 +1948,8 @@ func toWebAPIError(ctx context.Context, err error) APIError {
return getAPIError(ErrStorageFull)
case BucketNotFound:
return getAPIError(ErrNoSuchBucket)
case BucketNotEmpty:
return getAPIError(ErrBucketNotEmpty)
case BucketExists:
return getAPIError(ErrBucketAlreadyOwnedByYou)
case BucketNameInvalid:
+1 -1
View File
@@ -338,7 +338,7 @@ func testDeleteBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrH
{"minio", false, "false token", "Authentication failed"},
{"minio", false, token, "The specified bucket is not valid"},
{bucketName, false, token, ""},
{bucketName, true, token, "Bucket not empty"},
{bucketName, true, token, "The bucket you tried to delete is not empty"},
{bucketName, false, "", "JWT token missing"},
}
+34 -28
View File
@@ -304,14 +304,26 @@ func newXLSets(endpoints EndpointList, format *formatXLV3, setCount int, drivesP
// StorageInfo - combines output of StorageInfo across all erasure coded object sets.
func (s *xlSets) StorageInfo(ctx context.Context) StorageInfo {
var storageInfo StorageInfo
var wg sync.WaitGroup
storageInfos := make([]StorageInfo, len(s.sets))
storageInfo.Backend.Type = BackendErasure
for _, set := range s.sets {
lstorageInfo := set.StorageInfo(ctx)
storageInfo.Used = storageInfo.Used + lstorageInfo.Used
storageInfo.Total = storageInfo.Total + lstorageInfo.Total
storageInfo.Available = storageInfo.Available + lstorageInfo.Available
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks + lstorageInfo.Backend.OnlineDisks
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks + lstorageInfo.Backend.OfflineDisks
for index, set := range s.sets {
wg.Add(1)
go func(id int, set *xlObjects) {
defer wg.Done()
storageInfos[id] = set.StorageInfo(ctx)
}(index, set)
}
// Wait for the go routines.
wg.Wait()
for _, lstorageInfo := range storageInfos {
storageInfo.Used += lstorageInfo.Used
storageInfo.Total += lstorageInfo.Total
storageInfo.Available += lstorageInfo.Available
storageInfo.Backend.OnlineDisks += lstorageInfo.Backend.OnlineDisks
storageInfo.Backend.OfflineDisks += lstorageInfo.Backend.OfflineDisks
}
scData, scParity := getRedundancyCount(standardStorageClass, s.drivesPerSet)
@@ -1239,35 +1251,29 @@ fi
*/
func formatsToDrivesInfo(endpoints EndpointList, formats []*formatXLV3, sErrs []error) (beforeDrives []madmin.DriveInfo) {
beforeDrives = make([]madmin.DriveInfo, len(endpoints))
// Existing formats are available (i.e. ok), so save it in
// result, also populate disks to be healed.
for i, format := range formats {
drive := endpoints.GetString(i)
var state = madmin.DriveStateCorrupt
switch {
case format != nil:
beforeDrives = append(beforeDrives, madmin.DriveInfo{
UUID: format.XL.This,
Endpoint: drive,
State: madmin.DriveStateOk,
})
state = madmin.DriveStateOk
case sErrs[i] == errUnformattedDisk:
beforeDrives = append(beforeDrives, madmin.DriveInfo{
UUID: "",
Endpoint: drive,
State: madmin.DriveStateMissing,
})
state = madmin.DriveStateMissing
case sErrs[i] == errDiskNotFound:
beforeDrives = append(beforeDrives, madmin.DriveInfo{
UUID: "",
Endpoint: drive,
State: madmin.DriveStateOffline,
})
default:
beforeDrives = append(beforeDrives, madmin.DriveInfo{
UUID: "",
Endpoint: drive,
State: madmin.DriveStateCorrupt,
})
state = madmin.DriveStateOffline
}
beforeDrives[i] = madmin.DriveInfo{
UUID: func() string {
if format != nil {
return format.XL.This
}
return ""
}(),
Endpoint: drive,
State: state,
}
}
-2
View File
@@ -28,7 +28,6 @@ import (
"sync"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger"
)
@@ -77,7 +76,6 @@ func (c ChecksumInfo) MarshalJSON() ([]byte, error) {
// UnmarshalJSON - should never be called, instead xlMetaV1UnmarshalJSON() should be used.
func (c *ChecksumInfo) UnmarshalJSON(data []byte) error {
var info checksumInfoJSON
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err := json.Unmarshal(data, &info); err != nil {
return err
}
+18 -83
View File
@@ -1,5 +1,5 @@
/*
* MinIO Cloud Storage, (C) 2016 MinIO, Inc.
* MinIO Cloud Storage, (C) 2016-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.
@@ -22,11 +22,9 @@ import (
"hash/crc32"
"path"
"sync"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger"
"github.com/tidwall/gjson"
)
// Returns number of errors that occurred the most (incl. nil) and the
@@ -117,59 +115,11 @@ func hashOrder(key string, cardinality int) []int {
return nums
}
func parseXLStat(xlMetaBuf []byte) (si statInfo, e error) {
// obtain stat info.
stat := statInfo{}
// fetching modTime.
modTime, err := time.Parse(time.RFC3339, gjson.GetBytes(xlMetaBuf, "stat.modTime").String())
if err != nil {
return si, err
}
stat.ModTime = modTime
// obtain Stat.Size .
stat.Size = gjson.GetBytes(xlMetaBuf, "stat.size").Int()
return stat, nil
}
func parseXLVersion(xlMetaBuf []byte) string {
return gjson.GetBytes(xlMetaBuf, "version").String()
}
func parseXLFormat(xlMetaBuf []byte) string {
return gjson.GetBytes(xlMetaBuf, "format").String()
}
func parseXLParts(xlMetaBuf []byte) []ObjectPartInfo {
// Parse the XL Parts.
partsResult := gjson.GetBytes(xlMetaBuf, "parts").Array()
partInfo := make([]ObjectPartInfo, len(partsResult))
for i, p := range partsResult {
info := ObjectPartInfo{}
info.Number = int(p.Get("number").Int())
info.Name = p.Get("name").String()
info.ETag = p.Get("etag").String()
info.Size = p.Get("size").Int()
info.ActualSize = p.Get("actualSize").Int()
partInfo[i] = info
}
return partInfo
}
func parseXLMetaMap(xlMetaBuf []byte) map[string]string {
// Get xlMetaV1.Meta map.
metaMapResult := gjson.GetBytes(xlMetaBuf, "meta").Map()
metaMap := make(map[string]string)
for key, valResult := range metaMapResult {
metaMap[key] = valResult.String()
}
return metaMap
}
// Constructs XLMetaV1 using `gjson` lib to retrieve each field.
func xlMetaV1UnmarshalJSON(ctx context.Context, xlMetaBuf []byte) (xlMeta xlMetaV1, e error) {
// Constructs xlMetaV1 using `jsoniter` lib.
func xlMetaV1UnmarshalJSON(ctx context.Context, xlMetaBuf []byte) (xlMeta xlMetaV1, err error) {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
e = json.Unmarshal(xlMetaBuf, &xlMeta)
return xlMeta, e
err = json.Unmarshal(xlMetaBuf, &xlMeta)
return xlMeta, err
}
// read xl.json from the given disk, parse and return xlV1MetaV1.Parts.
@@ -181,15 +131,18 @@ func readXLMetaParts(ctx context.Context, disk StorageAPI, bucket string, object
return nil, nil, err
}
// obtain xlMetaV1{}.Partsusing `github.com/tidwall/gjson`.
xlMetaParts := parseXLParts(xlMetaBuf)
xlMetaMap := parseXLMetaMap(xlMetaBuf)
var xlMeta xlMetaV1
xlMeta, err = xlMetaV1UnmarshalJSON(ctx, xlMetaBuf)
if err != nil {
return nil, nil, err
}
return xlMetaParts, xlMetaMap, nil
return xlMeta.Parts, xlMeta.Meta, nil
}
// read xl.json from the given disk and parse xlV1Meta.Stat and xlV1Meta.Meta using gjson.
func readXLMetaStat(ctx context.Context, disk StorageAPI, bucket string, object string) (si statInfo, mp map[string]string, e error) {
// read xl.json from the given disk and parse xlV1Meta.Stat and xlV1Meta.Meta using jsoniter.
func readXLMetaStat(ctx context.Context, disk StorageAPI, bucket string, object string) (si statInfo,
mp map[string]string, e error) {
// Reads entire `xl.json`.
xlMetaBuf, err := disk.ReadAll(bucket, path.Join(object, xlMetaJSONFile))
if err != nil {
@@ -197,31 +150,14 @@ func readXLMetaStat(ctx context.Context, disk StorageAPI, bucket string, object
return si, nil, err
}
// obtain version.
xlVersion := parseXLVersion(xlMetaBuf)
// obtain format.
xlFormat := parseXLFormat(xlMetaBuf)
// Validate if the xl.json we read is sane, return corrupted format.
if !isXLMetaFormatValid(xlVersion, xlFormat) {
// For version mismatchs and unrecognized format, return corrupted format.
logger.LogIf(ctx, errCorruptedFormat)
return si, nil, errCorruptedFormat
}
// obtain xlMetaV1{}.Meta using `github.com/tidwall/gjson`.
xlMetaMap := parseXLMetaMap(xlMetaBuf)
// obtain xlMetaV1{}.Stat using `github.com/tidwall/gjson`.
xlStat, err := parseXLStat(xlMetaBuf)
var xlMeta xlMetaV1
xlMeta, err = xlMetaV1UnmarshalJSON(ctx, xlMetaBuf)
if err != nil {
logger.LogIf(ctx, err)
return si, nil, err
return si, mp, err
}
// Return structured `xl.json`.
return xlStat, xlMetaMap, nil
return xlMeta.Stat, xlMeta.Meta, nil
}
// readXLMeta reads `xl.json` and returns back XL metadata structure.
@@ -238,7 +174,6 @@ func readXLMeta(ctx context.Context, disk StorageAPI, bucket string, object stri
if len(xlMetaBuf) == 0 {
return xlMetaV1{}, errFileNotFound
}
// obtain xlMetaV1{} using `github.com/tidwall/gjson`.
xlMeta, err = xlMetaV1UnmarshalJSON(ctx, xlMetaBuf)
if err != nil {
logger.GetReqInfo(ctx).AppendTags("disk", disk.String())
+58 -58
View File
@@ -212,99 +212,99 @@ func getSampleXLMeta(totalParts int) xlMetaV1 {
return xlMeta
}
// Compare the unmarshaled XLMetaV1 with the one obtained from gjson parsing.
func compareXLMetaV1(t *testing.T, unMarshalXLMeta, gjsonXLMeta xlMetaV1) {
// Start comparing the fields of xlMetaV1 obtained from gjson parsing with one parsed using json unmarshaling.
if unMarshalXLMeta.Version != gjsonXLMeta.Version {
t.Errorf("Expected the Version to be \"%s\", but got \"%s\".", unMarshalXLMeta.Version, gjsonXLMeta.Version)
// Compare the unmarshaled XLMetaV1 with the one obtained from jsoniter parsing.
func compareXLMetaV1(t *testing.T, unMarshalXLMeta, jsoniterXLMeta xlMetaV1) {
// Start comparing the fields of xlMetaV1 obtained from jsoniter parsing with one parsed using json unmarshaling.
if unMarshalXLMeta.Version != jsoniterXLMeta.Version {
t.Errorf("Expected the Version to be \"%s\", but got \"%s\".", unMarshalXLMeta.Version, jsoniterXLMeta.Version)
}
if unMarshalXLMeta.Format != gjsonXLMeta.Format {
t.Errorf("Expected the format to be \"%s\", but got \"%s\".", unMarshalXLMeta.Format, gjsonXLMeta.Format)
if unMarshalXLMeta.Format != jsoniterXLMeta.Format {
t.Errorf("Expected the format to be \"%s\", but got \"%s\".", unMarshalXLMeta.Format, jsoniterXLMeta.Format)
}
if unMarshalXLMeta.Stat.Size != gjsonXLMeta.Stat.Size {
t.Errorf("Expected the stat size to be %v, but got %v.", unMarshalXLMeta.Stat.Size, gjsonXLMeta.Stat.Size)
if unMarshalXLMeta.Stat.Size != jsoniterXLMeta.Stat.Size {
t.Errorf("Expected the stat size to be %v, but got %v.", unMarshalXLMeta.Stat.Size, jsoniterXLMeta.Stat.Size)
}
if !unMarshalXLMeta.Stat.ModTime.Equal(gjsonXLMeta.Stat.ModTime) {
t.Errorf("Expected the modTime to be \"%v\", but got \"%v\".", unMarshalXLMeta.Stat.ModTime, gjsonXLMeta.Stat.ModTime)
if !unMarshalXLMeta.Stat.ModTime.Equal(jsoniterXLMeta.Stat.ModTime) {
t.Errorf("Expected the modTime to be \"%v\", but got \"%v\".", unMarshalXLMeta.Stat.ModTime, jsoniterXLMeta.Stat.ModTime)
}
if unMarshalXLMeta.Erasure.Algorithm != gjsonXLMeta.Erasure.Algorithm {
t.Errorf("Expected the erasure algorithm to be \"%v\", but got \"%v\".", unMarshalXLMeta.Erasure.Algorithm, gjsonXLMeta.Erasure.Algorithm)
if unMarshalXLMeta.Erasure.Algorithm != jsoniterXLMeta.Erasure.Algorithm {
t.Errorf("Expected the erasure algorithm to be \"%v\", but got \"%v\".", unMarshalXLMeta.Erasure.Algorithm, jsoniterXLMeta.Erasure.Algorithm)
}
if unMarshalXLMeta.Erasure.DataBlocks != gjsonXLMeta.Erasure.DataBlocks {
t.Errorf("Expected the erasure data blocks to be %v, but got %v.", unMarshalXLMeta.Erasure.DataBlocks, gjsonXLMeta.Erasure.DataBlocks)
if unMarshalXLMeta.Erasure.DataBlocks != jsoniterXLMeta.Erasure.DataBlocks {
t.Errorf("Expected the erasure data blocks to be %v, but got %v.", unMarshalXLMeta.Erasure.DataBlocks, jsoniterXLMeta.Erasure.DataBlocks)
}
if unMarshalXLMeta.Erasure.ParityBlocks != gjsonXLMeta.Erasure.ParityBlocks {
t.Errorf("Expected the erasure parity blocks to be %v, but got %v.", unMarshalXLMeta.Erasure.ParityBlocks, gjsonXLMeta.Erasure.ParityBlocks)
if unMarshalXLMeta.Erasure.ParityBlocks != jsoniterXLMeta.Erasure.ParityBlocks {
t.Errorf("Expected the erasure parity blocks to be %v, but got %v.", unMarshalXLMeta.Erasure.ParityBlocks, jsoniterXLMeta.Erasure.ParityBlocks)
}
if unMarshalXLMeta.Erasure.BlockSize != gjsonXLMeta.Erasure.BlockSize {
t.Errorf("Expected the erasure block size to be %v, but got %v.", unMarshalXLMeta.Erasure.BlockSize, gjsonXLMeta.Erasure.BlockSize)
if unMarshalXLMeta.Erasure.BlockSize != jsoniterXLMeta.Erasure.BlockSize {
t.Errorf("Expected the erasure block size to be %v, but got %v.", unMarshalXLMeta.Erasure.BlockSize, jsoniterXLMeta.Erasure.BlockSize)
}
if unMarshalXLMeta.Erasure.Index != gjsonXLMeta.Erasure.Index {
t.Errorf("Expected the erasure index to be %v, but got %v.", unMarshalXLMeta.Erasure.Index, gjsonXLMeta.Erasure.Index)
if unMarshalXLMeta.Erasure.Index != jsoniterXLMeta.Erasure.Index {
t.Errorf("Expected the erasure index to be %v, but got %v.", unMarshalXLMeta.Erasure.Index, jsoniterXLMeta.Erasure.Index)
}
if len(unMarshalXLMeta.Erasure.Distribution) != len(gjsonXLMeta.Erasure.Distribution) {
t.Errorf("Expected the size of Erasure Distribution to be %d, but got %d.", len(unMarshalXLMeta.Erasure.Distribution), len(gjsonXLMeta.Erasure.Distribution))
if len(unMarshalXLMeta.Erasure.Distribution) != len(jsoniterXLMeta.Erasure.Distribution) {
t.Errorf("Expected the size of Erasure Distribution to be %d, but got %d.", len(unMarshalXLMeta.Erasure.Distribution), len(jsoniterXLMeta.Erasure.Distribution))
} else {
for i := 0; i < len(unMarshalXLMeta.Erasure.Distribution); i++ {
if unMarshalXLMeta.Erasure.Distribution[i] != gjsonXLMeta.Erasure.Distribution[i] {
t.Errorf("Expected the Erasure Distribution to be %d, got %d.", unMarshalXLMeta.Erasure.Distribution[i], gjsonXLMeta.Erasure.Distribution[i])
if unMarshalXLMeta.Erasure.Distribution[i] != jsoniterXLMeta.Erasure.Distribution[i] {
t.Errorf("Expected the Erasure Distribution to be %d, got %d.", unMarshalXLMeta.Erasure.Distribution[i], jsoniterXLMeta.Erasure.Distribution[i])
}
}
}
if len(unMarshalXLMeta.Erasure.Checksums) != len(gjsonXLMeta.Erasure.Checksums) {
t.Errorf("Expected the size of Erasure Checksums to be %d, but got %d.", len(unMarshalXLMeta.Erasure.Checksums), len(gjsonXLMeta.Erasure.Checksums))
if len(unMarshalXLMeta.Erasure.Checksums) != len(jsoniterXLMeta.Erasure.Checksums) {
t.Errorf("Expected the size of Erasure Checksums to be %d, but got %d.", len(unMarshalXLMeta.Erasure.Checksums), len(jsoniterXLMeta.Erasure.Checksums))
} else {
for i := 0; i < len(unMarshalXLMeta.Erasure.Checksums); i++ {
if unMarshalXLMeta.Erasure.Checksums[i].Name != gjsonXLMeta.Erasure.Checksums[i].Name {
t.Errorf("Expected the Erasure Checksum Name to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Name, gjsonXLMeta.Erasure.Checksums[i].Name)
if unMarshalXLMeta.Erasure.Checksums[i].Name != jsoniterXLMeta.Erasure.Checksums[i].Name {
t.Errorf("Expected the Erasure Checksum Name to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Name, jsoniterXLMeta.Erasure.Checksums[i].Name)
}
if unMarshalXLMeta.Erasure.Checksums[i].Algorithm != gjsonXLMeta.Erasure.Checksums[i].Algorithm {
t.Errorf("Expected the Erasure Checksum Algorithm to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Algorithm, gjsonXLMeta.Erasure.Checksums[i].Algorithm)
if unMarshalXLMeta.Erasure.Checksums[i].Algorithm != jsoniterXLMeta.Erasure.Checksums[i].Algorithm {
t.Errorf("Expected the Erasure Checksum Algorithm to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Algorithm, jsoniterXLMeta.Erasure.Checksums[i].Algorithm)
}
if !bytes.Equal(unMarshalXLMeta.Erasure.Checksums[i].Hash, gjsonXLMeta.Erasure.Checksums[i].Hash) {
t.Errorf("Expected the Erasure Checksum Hash to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Hash, gjsonXLMeta.Erasure.Checksums[i].Hash)
if !bytes.Equal(unMarshalXLMeta.Erasure.Checksums[i].Hash, jsoniterXLMeta.Erasure.Checksums[i].Hash) {
t.Errorf("Expected the Erasure Checksum Hash to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Hash, jsoniterXLMeta.Erasure.Checksums[i].Hash)
}
}
}
if unMarshalXLMeta.Minio.Release != gjsonXLMeta.Minio.Release {
t.Errorf("Expected the Release string to be \"%s\", but got \"%s\".", unMarshalXLMeta.Minio.Release, gjsonXLMeta.Minio.Release)
if unMarshalXLMeta.Minio.Release != jsoniterXLMeta.Minio.Release {
t.Errorf("Expected the Release string to be \"%s\", but got \"%s\".", unMarshalXLMeta.Minio.Release, jsoniterXLMeta.Minio.Release)
}
if len(unMarshalXLMeta.Parts) != len(gjsonXLMeta.Parts) {
t.Errorf("Expected info of %d parts to be present, but got %d instead.", len(unMarshalXLMeta.Parts), len(gjsonXLMeta.Parts))
if len(unMarshalXLMeta.Parts) != len(jsoniterXLMeta.Parts) {
t.Errorf("Expected info of %d parts to be present, but got %d instead.", len(unMarshalXLMeta.Parts), len(jsoniterXLMeta.Parts))
} else {
for i := 0; i < len(unMarshalXLMeta.Parts); i++ {
if unMarshalXLMeta.Parts[i].Name != gjsonXLMeta.Parts[i].Name {
t.Errorf("Expected the name of part %d to be \"%s\", got \"%s\".", i+1, unMarshalXLMeta.Parts[i].Name, gjsonXLMeta.Parts[i].Name)
if unMarshalXLMeta.Parts[i].Name != jsoniterXLMeta.Parts[i].Name {
t.Errorf("Expected the name of part %d to be \"%s\", got \"%s\".", i+1, unMarshalXLMeta.Parts[i].Name, jsoniterXLMeta.Parts[i].Name)
}
if unMarshalXLMeta.Parts[i].ETag != gjsonXLMeta.Parts[i].ETag {
t.Errorf("Expected the ETag of part %d to be \"%s\", got \"%s\".", i+1, unMarshalXLMeta.Parts[i].ETag, gjsonXLMeta.Parts[i].ETag)
if unMarshalXLMeta.Parts[i].ETag != jsoniterXLMeta.Parts[i].ETag {
t.Errorf("Expected the ETag of part %d to be \"%s\", got \"%s\".", i+1, unMarshalXLMeta.Parts[i].ETag, jsoniterXLMeta.Parts[i].ETag)
}
if unMarshalXLMeta.Parts[i].Number != gjsonXLMeta.Parts[i].Number {
t.Errorf("Expected the number of part %d to be \"%d\", got \"%d\".", i+1, unMarshalXLMeta.Parts[i].Number, gjsonXLMeta.Parts[i].Number)
if unMarshalXLMeta.Parts[i].Number != jsoniterXLMeta.Parts[i].Number {
t.Errorf("Expected the number of part %d to be \"%d\", got \"%d\".", i+1, unMarshalXLMeta.Parts[i].Number, jsoniterXLMeta.Parts[i].Number)
}
if unMarshalXLMeta.Parts[i].Size != gjsonXLMeta.Parts[i].Size {
t.Errorf("Expected the size of part %d to be %v, got %v.", i+1, unMarshalXLMeta.Parts[i].Size, gjsonXLMeta.Parts[i].Size)
if unMarshalXLMeta.Parts[i].Size != jsoniterXLMeta.Parts[i].Size {
t.Errorf("Expected the size of part %d to be %v, got %v.", i+1, unMarshalXLMeta.Parts[i].Size, jsoniterXLMeta.Parts[i].Size)
}
}
}
for key, val := range unMarshalXLMeta.Meta {
gjsonVal, exists := gjsonXLMeta.Meta[key]
jsoniterVal, exists := jsoniterXLMeta.Meta[key]
if !exists {
t.Errorf("No meta data entry for Key \"%s\" exists.", key)
}
if val != gjsonVal {
t.Errorf("Expected the value for Meta data key \"%s\" to be \"%s\", but got \"%s\".", key, val, gjsonVal)
if val != jsoniterVal {
t.Errorf("Expected the value for Meta data key \"%s\" to be \"%s\", but got \"%s\".", key, val, jsoniterVal)
}
}
}
// Tests the correctness of constructing XLMetaV1 using gjson lib.
// Tests the correctness of constructing XLMetaV1 using jsoniter lib.
// The result will be compared with the result obtained from json.unMarshal of the byte data.
func TestGetXLMetaV1GJson1(t *testing.T) {
func TestGetXLMetaV1Jsoniter1(t *testing.T) {
xlMetaJSON := getXLMetaBytes(1)
var unMarshalXLMeta xlMetaV1
@@ -312,16 +312,16 @@ func TestGetXLMetaV1GJson1(t *testing.T) {
t.Errorf("Unmarshalling failed: %v", err)
}
gjsonXLMeta, err := xlMetaV1UnmarshalJSON(context.Background(), xlMetaJSON)
jsoniterXLMeta, err := xlMetaV1UnmarshalJSON(context.Background(), xlMetaJSON)
if err != nil {
t.Errorf("gjson parsing of XLMeta failed: %v", err)
t.Errorf("jsoniter parsing of XLMeta failed: %v", err)
}
compareXLMetaV1(t, unMarshalXLMeta, gjsonXLMeta)
compareXLMetaV1(t, unMarshalXLMeta, jsoniterXLMeta)
}
// Tests the correctness of constructing XLMetaV1 using gjson lib for XLMetaV1 of size 10 parts.
// Tests the correctness of constructing XLMetaV1 using jsoniter lib for XLMetaV1 of size 10 parts.
// The result will be compared with the result obtained from json.unMarshal of the byte data.
func TestGetXLMetaV1GJson10(t *testing.T) {
func TestGetXLMetaV1Jsoniter10(t *testing.T) {
xlMetaJSON := getXLMetaBytes(10)
@@ -329,11 +329,11 @@ func TestGetXLMetaV1GJson10(t *testing.T) {
if err := json.Unmarshal(xlMetaJSON, &unMarshalXLMeta); err != nil {
t.Errorf("Unmarshalling failed: %v", err)
}
gjsonXLMeta, err := xlMetaV1UnmarshalJSON(context.Background(), xlMetaJSON)
jsoniterXLMeta, err := xlMetaV1UnmarshalJSON(context.Background(), xlMetaJSON)
if err != nil {
t.Errorf("gjson parsing of XLMeta failed: %v", err)
t.Errorf("jsoniter parsing of XLMeta failed: %v", err)
}
compareXLMetaV1(t, unMarshalXLMeta, gjsonXLMeta)
compareXLMetaV1(t, unMarshalXLMeta, jsoniterXLMeta)
}
// Test the predicted part size from the part index
+27 -11
View File
@@ -19,6 +19,7 @@ package cmd
import (
"context"
"sort"
"sync"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bpool"
@@ -70,24 +71,39 @@ func (d byDiskTotal) Less(i, j int) bool {
// getDisksInfo - fetch disks info across all other storage API.
func getDisksInfo(disks []StorageAPI) (disksInfo []DiskInfo, onlineDisks int, offlineDisks int) {
disksInfo = make([]DiskInfo, len(disks))
errs := make([]error, len(disks))
var wg sync.WaitGroup
for i, storageDisk := range disks {
if storageDisk == nil {
// Storage disk is empty, perhaps ignored disk or not available.
errs[i] = errDiskNotFound
continue
}
wg.Add(1)
go func(id int, sDisk StorageAPI) {
defer wg.Done()
info, err := sDisk.DiskInfo()
if err != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("disk", sDisk.String())
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err)
if IsErr(err, baseErrs...) {
errs[id] = err
return
}
}
disksInfo[id] = info
}(i, storageDisk)
}
// Wait for the routines.
wg.Wait()
for _, err := range errs {
if err != nil {
offlineDisks++
continue
}
info, err := storageDisk.DiskInfo()
if err != nil {
ctx := context.Background()
logger.GetReqInfo(ctx).AppendTags("disk", storageDisk.String())
logger.LogIf(ctx, err)
if IsErr(err, baseErrs...) {
offlineDisks++
continue
}
}
onlineDisks++
disksInfo[i] = info
}
// Success.
-181
View File
@@ -1,181 +0,0 @@
// +build ignore
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"bufio"
"crypto/tls"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"time"
xhttp "github.com/minio/minio/cmd/http"
)
const (
initGraceTime = 300
healthPath = "/minio/health/live"
timeout = time.Duration(30 * time.Second)
tcp = "tcp"
anyIPv6 = ":::"
anyIPv4 = "0.0.0.0"
)
// returns container boot time by finding
// modtime of /proc/1 directory
func getStartTime() time.Time {
di, err := os.Stat("/proc/1")
if err != nil {
// Cant stat proc dir successfully, exit with error
log.Fatalln(err)
}
return di.ModTime()
}
// Returns the ip:port of the MinIO process
// running in the container
func findEndpoint() (string, error) {
cmd := exec.Command("netstat", "-ntlp")
stdout, err := cmd.StdoutPipe()
if err != nil {
// error getting stdout pipe
return "", err
}
if err = cmd.Start(); err != nil {
// error executing the command.
return "", err
}
// split netstat output in rows
scanner := bufio.NewScanner(stdout)
scanner.Split(bufio.ScanLines)
// MinIO works on TCP and it is supposed to be
// the only process listening on a port on any IP address
// (on ::: or 0.0.0.0) inside container.
// Since MinIO may run as non-root user, we can
// not depend on the PID/Program name column
// of netstat output
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, tcp) && (strings.Contains(line, anyIPv6) || strings.Contains(line, anyIPv4)) {
newLine := strings.Replace(line, anyIPv4, "127.0.0.1:", 1)
if strings.Contains(line, anyIPv6) {
newLine = strings.Replace(line, anyIPv6, "::1:", 1)
}
fields := strings.Fields(newLine)
// index 3 in the row has the Local address
// find the last index of ":" - address will
// have port number after this index
i := strings.LastIndex(fields[3], ":")
// split address and port
addr := fields[3][:i]
port := fields[3][i+1:]
// add surrounding [] for ip6 address
if strings.Count(addr, ":") > 0 {
addr = strings.Join([]string{"[", addr, "]"}, "")
}
// wait for cmd to complete before return
if err = cmd.Wait(); err != nil {
return "", err
}
// return joint address and port
return strings.Join([]string{addr, port}, ":"), nil
}
}
if err = scanner.Err(); err != nil {
return "", err
}
if err = cmd.Wait(); err != nil {
// command failed to run
return "", err
}
// minio process not found, exit with error
return "", errors.New("no minio process found")
}
func main() {
startTime := getStartTime()
// In distributed environment like Swarm, traffic is routed
// to a container only when it reports a `healthy` status. So, we exit
// with 0 to ensure healthy status till distributed MinIO starts (120s).
// Refer: https://github.com/moby/moby/pull/28938#issuecomment-301753272
if (time.Now().Sub(startTime) / time.Second) > initGraceTime {
endPoint, err := findEndpoint()
if err != nil {
log.Fatalln(err)
}
u, err := url.Parse(fmt.Sprintf("http://%s%s", endPoint, healthPath))
if err != nil {
// Could not parse URL successfully
log.Fatalln(err)
}
// MinIO server may be using self-signed or CA certificates. To avoid
// making Docker setup complicated, we skip verifying certificates here.
// This is because, following request tests for health status within
// containerized environment, i.e. requests are always made to the MinIO
// server running on the same host.
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr, Timeout: timeout}
resp, err := client.Get(u.String())
if err != nil {
// GET failed exit
log.Fatalln(err)
}
if resp.StatusCode == http.StatusOK {
// Drain any response.
xhttp.DrainBody(resp.Body)
// exit with success
os.Exit(0)
}
// Drain any response.
xhttp.DrainBody(resp.Body)
// 400 response may mean sever is configured with https
if resp.StatusCode == http.StatusBadRequest {
// Try with https
u.Scheme = "https"
resp, err = client.Get(u.String())
if err != nil {
// GET failed exit
log.Fatalln(err)
}
if resp.StatusCode == http.StatusOK {
// Drain any response.
xhttp.DrainBody(resp.Body)
// exit with success
os.Exit(0)
}
// Drain any response.
xhttp.DrainBody(resp.Body)
}
// Execution reaching here means none of
// the success cases were satisfied
os.Exit(1)
}
os.Exit(0)
}
+6 -3
View File
@@ -40,7 +40,12 @@ Disk caching caches objects for **downloaded** objects i.e
- Bitrot protection is added to cached content and verified when object is served from cache.
- When an object is deleted, corresponding entry in cache if any is deleted as well.
- Cache continues to work for read-only operations such as GET, HEAD when backend is offline.
- Cache-Control and Expires headers can be used to control how long objects stay in the cache
- Cache-Control and Expires headers can be used to control how long objects stay in the cache. ETag of cached objects are not validated with backend until expiry time as per the Cache-Control or Expires header is met.
- To ensure security guarantees, encrypted objects are normally not cached. However, if you wish to encrypt cached content on disk, you can set MINIO_CACHE_ENCRYPTION_MASTER_KEY environment variable to set a cache KMS
master key to automatically encrypt all cached content.
Note that cache KMS master key is not recommended for use in production deployments. If the MinIO server/gateway machine is ever compromised, the cache KMS master key must also be treated as compromised.
Support for external KMS to manage cache KMS keys is on the roadmap,and would be ideal for production use cases.
> NOTE: Expiration happens automatically based on the configured interval as explained above, frequently accessed objects stay alive in cache for a significantly longer time.
@@ -51,5 +56,3 @@ Upon restart of minio server after a running minio process is killed or crashes,
- Bucket policies are not cached, so anonymous operations are not supported when backend is offline.
- Objects are distributed using deterministic hashing among the list of configured cache drives. If one or more drives go offline, or cache drive configuration is altered in any way, performance may degrade to a linear lookup time depending on the number of disks in cache.
## TODO
- Encrypt cached objects automatically with a cache encryption master key
+1
View File
@@ -52,6 +52,7 @@ We found the following APIs to be redundant or less useful outside of AWS S3. If
- ObjectACL (Use [bucket policies](https://docs.min.io/docs/minio-client-complete-guide#policy) instead)
- ObjectTorrent
- ObjectVersions
- ObjectTagging
### Object name restrictions on MinIO
Object names that contain characters `^*|\/&";` are unsupported on Windows and other file systems which do not support filenames with these characters. Note that this list is not exhaustive, and depends on the maintainers of the filesystem itself.
+48 -8
View File
@@ -39,36 +39,76 @@ Create new canned policy by name `getonly` using `getonly.json` policy file.
mc admin policy add myminio getonly getonly.json
```
Create a new user `newuser` on MinIO use `mc admin user`, specify `getonly` canned policy for this `newuser`.
Create a new user `newuser` on MinIO use `mc admin user`.
```
mc admin user add myminio newuser newuser123 getonly
mc admin user add myminio newuser newuser123
```
### 3. Disable user
Once the user is successfully created you can now apply the `getonly` policy for this user.
```
mc admin policy set myminio getonly user=newuser
```
### 3. Create a new group
```
mc admin group add myminio newgroup newuser
```
Once the group is successfully created you can now apply the `getonly` policy for this group.
```
mc admin policy set myminio getonly group=newgroup
```
### 4. Disable user
Disable user `newuser`.
```
mc admin user disable myminio newuser
```
Disable group `newgroup`.
```
mc admin group disable myminio newgroup
```
### 4. Remove user
Remove the user `newuser`.
```
mc admin user remove myminio newuser
```
### 5. Change user policy
Change the policy for user `newuser` to `putonly` canned policy.
Remove the user `newuser` from a group.
```
mc admin user set-policy myminio newuser putonly
mc admin group remove myminio newgroup newuser
```
### 5. List all users
Remove the group `newgroup`.
```
mc admin group remove myminio newgroup
```
### 5. Change user or group policy
Change the policy for user `newuser` to `putonly` canned policy.
```
mc admin policy set myminio putonly user=newuser
```
Change the policy for group `newgroup` to `putonly` canned policy.
```
mc admin policy set myminio putonly group=newgroup
```
### 6. List all users or groups
List all enabled and disabled users.
```
mc admin user list myminio
```
### 6. Configure `mc`
List all enabled or disabled groups.
```
mc admin group list myminio
```
### 7. Configure `mc`
```
mc config host add myminio-newuser http://localhost:9000 newuser newuser123 --api s3v4
mc cat myminio-newuser/my-bucketname/my-objectname
@@ -1,55 +1,86 @@
version: '2'
version: '3.7'
# starts 4 docker containers running minio server instances. Each
# minio server's web interface will be accessible on the host at port
# 9001 through 9004.
services:
minio1:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
volumes:
- data1:/data
ports:
- "9001:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio2:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
volumes:
- data2:/data
ports:
- "9002:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio3:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
volumes:
- data3:/data
ports:
- "9003:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio4:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
volumes:
- data4:/data
ports:
- "9004:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio1:
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
volumes:
- data1-1:/data1
- data1-2:/data2
ports:
- "9001:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio2:
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
volumes:
- data2-1:/data1
- data2-2:/data2
ports:
- "9002:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio3:
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
volumes:
- data3-1:/data1
- data3-2:/data2
ports:
- "9003:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio4:
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
volumes:
- data4-1:/data1
- data4-2:/data2
ports:
- "9004:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
volumes:
data1:
data2:
data3:
data4:
data1-1:
data1-2:
data2-1:
data2-2:
data3-1:
data3-2:
data4-1:
data4-2:
@@ -1,8 +1,8 @@
version: '3.1'
version: '3.7'
services:
minio1:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio1
volumes:
- minio1-data:/export
@@ -18,13 +18,18 @@ services:
placement:
constraints:
- node.labels.minio1==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
secrets:
- secret_key
- access_key
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio2:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio2
volumes:
- minio2-data:/export
@@ -40,13 +45,18 @@ services:
placement:
constraints:
- node.labels.minio2==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
secrets:
- secret_key
- access_key
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio3:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio3
volumes:
- minio3-data:/export
@@ -62,13 +72,18 @@ services:
placement:
constraints:
- node.labels.minio3==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
secrets:
- secret_key
- access_key
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio4:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio4
volumes:
- minio4-data:/export
@@ -84,10 +99,15 @@ services:
placement:
constraints:
- node.labels.minio4==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
secrets:
- secret_key
- access_key
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
minio1-data:
@@ -2,7 +2,7 @@ version: '3.7'
services:
minio1:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio1
volumes:
- minio1-data:/export
@@ -25,10 +25,15 @@ services:
placement:
constraints:
- node.labels.minio1==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio2:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio2
volumes:
- minio2-data:/export
@@ -51,10 +56,15 @@ services:
placement:
constraints:
- node.labels.minio2==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio3:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio3
volumes:
- minio3-data:/export
@@ -77,10 +87,15 @@ services:
placement:
constraints:
- node.labels.minio3==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 1m30s
timeout: 20s
retries: 3
minio4:
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
hostname: minio4
volumes:
- minio4-data:/export
@@ -103,7 +118,12 @@ services:
placement:
constraints:
- node.labels.minio4==true
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
command: server http://minio{1...4}/export
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
minio1-data:
+1 -4
View File
@@ -207,10 +207,7 @@ Specifically the hostpath:
And the list of hosts:
```yaml
- http://hostname1:9000/data/minio
- http://hostname2:9000/data/minio
- http://hostname3:9000/data/minio
- http://hostname4:9000/data/minio
- http://hostname{1...4}/data/minio
```
Once deployed, tag the defined host with the `minio-server=true` label:
@@ -30,14 +30,11 @@ spec:
value: "minio"
- name: MINIO_SECRET_KEY
value: "minio123"
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
# Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future.
args:
- server
- http://hostname1:9000/data/minio
- http://hostname2:9000/data/minio
- http://hostname3:9000/data/minio
- http://hostname4:9000/data/minio
- http://hostname{1...4}/data/minio
ports:
- containerPort: 9000
volumeMounts:
@@ -21,13 +21,10 @@ spec:
value: "minio"
- name: MINIO_SECRET_KEY
value: "minio123"
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
args:
- server
- http://minio-0.minio.default.svc.cluster.local/data
- http://minio-1.minio.default.svc.cluster.local/data
- http://minio-2.minio.default.svc.cluster.local/data
- http://minio-3.minio.default.svc.cluster.local/data
- http://minio-{0...3}.minio.default.svc.cluster.local/data
ports:
- containerPort: 9000
# These volume mounts are persistent. Each pod in the PetSet
@@ -21,7 +21,7 @@ spec:
containers:
- name: minio
# Pulls the default Minio image from Docker Hub
image: minio/minio:RELEASE.2019-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
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-08-07T01-59-21Z
image: minio/minio:RELEASE.2019-08-29T00-25-01Z
args:
- server
- /data
+16 -4
View File
@@ -34,6 +34,7 @@ require (
github.com/gorilla/handlers v1.4.0
github.com/gorilla/mux v1.7.0
github.com/gorilla/rpc v1.2.0+incompatible
github.com/gorilla/websocket v1.4.1 // indirect
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
@@ -42,7 +43,7 @@ require (
github.com/hashicorp/raft v1.1.0 // indirect
github.com/hashicorp/vault v1.1.0
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/json-iterator/go v1.1.6
github.com/json-iterator/go v1.1.7
github.com/klauspost/compress v1.5.0 // indirect
github.com/klauspost/cpuid v1.2.1 // indirect
github.com/klauspost/pgzip v1.2.1
@@ -94,9 +95,9 @@ require (
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
go.etcd.io/bbolt v1.3.3 // indirect
go.uber.org/atomic v1.3.2
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456
google.golang.org/api v0.4.0
gopkg.in/Shopify/sarama.v1 v1.20.0
gopkg.in/olivere/elastic.v5 v5.0.80
@@ -105,3 +106,14 @@ require (
// Added for go1.13 migration https://github.com/golang/go/issues/32805
replace github.com/gorilla/rpc v1.2.0+incompatible => github.com/gorilla/rpc v1.2.0
// Allow this for offline builds
replace github.com/eapache/go-xerial-snappy => github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21
replace github.com/eapache/queue => github.com/eapache/queue v1.1.0
replace github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.4
replace github.com/mitchellh/mapstructure => github.com/mitchellh/mapstructure v1.1.2
replace git.apache.org/thrift.git => github.com/apache/thrift v0.12.0
+14 -6
View File
@@ -44,6 +44,7 @@ github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170925032315-6fe16293d6b7/go.mod h1
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 h1:nWDRPCyCltiTsANwC/n3QZH7Vww33Npq9MKqlwRzI/c=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/araddon/gou v0.0.0-20190110011759-c797efecbb61/go.mod h1:ikc1XA58M+Rx7SEbf0bLJCfBkwayZ8T5jBo5FXK8Uz8=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -198,6 +199,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -226,6 +228,8 @@ github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
@@ -326,6 +330,8 @@ github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -448,8 +454,10 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/pointerstructure v0.0.0-20170205204203-f2329fcfa9e2/go.mod h1:KMNPMpc0BU/kZEgyDhBplsDn/mjnJMhyMjq4MWboN20=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -649,8 +657,8 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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=
@@ -678,8 +686,8 @@ golang.org/x/net v0.0.0-20190324223953-e3b2ff56ed87/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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=
@@ -719,8 +727,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-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/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/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+1 -1
View File
@@ -15,6 +15,6 @@
# limitations under the License.
#
AWS_CLI_VERSION="1.11.112"
AWS_CLI_VERSION="1.11.177"
python -m pip install awscli==$AWS_CLI_VERSION
+1 -1
View File
@@ -863,7 +863,7 @@ function test_aws_s3_sync() {
# if make bucket succeeds sync all the files in a directory
if [ $rv -eq 0 ]; then
function="${AWS} s3 sync $MINT_DATA_DIR s3://${bucket_name}/"
function="${AWS} s3 sync --no-progress $MINT_DATA_DIR s3://${bucket_name}/"
test_function=${function}
out=$($function 2>&1)
rv=$?
+1 -1
View File
@@ -231,7 +231,7 @@ func NewAMQPTarget(id string, args AMQPArgs, doneCh <-chan struct{}) (*AMQPTarge
conn, err = amqp.Dial(args.URL.String())
if err != nil {
if store == nil || !IsConnRefusedErr(err) {
if store == nil || !(IsConnRefusedErr(err) || IsConnResetErr(err)) {
return nil, err
}
}
+1 -1
View File
@@ -351,7 +351,7 @@ func NewMySQLTarget(id string, args MySQLArgs, doneCh <-chan struct{}) (*MySQLTa
err = target.db.Ping()
if err != nil {
if target.store == nil || !IsConnRefusedErr(err) {
if target.store == nil || !(IsConnRefusedErr(err) || IsConnResetErr(err)) {
return nil, err
}
} else {
+1 -1
View File
@@ -182,7 +182,7 @@ func NewNSQTarget(id string, args NSQArgs, doneCh <-chan struct{}) (*NSQTarget,
if err := target.producer.Ping(); err != nil {
// To treat "connection refused" errors as errNotConnected.
if target.store == nil || !IsConnRefusedErr(err) {
if target.store == nil || !(IsConnRefusedErr(err) || IsConnResetErr(err)) {
return nil, err
}
}
+1 -1
View File
@@ -358,7 +358,7 @@ func NewPostgreSQLTarget(id string, args PostgreSQLArgs, doneCh <-chan struct{})
err = target.db.Ping()
if err != nil {
if target.store == nil || !IsConnRefusedErr(err) {
if target.store == nil || !(IsConnRefusedErr(err) || IsConnResetErr(err)) {
return nil, err
}
} else {
+1 -1
View File
@@ -276,7 +276,7 @@ func NewRedisTarget(id string, args RedisArgs, doneCh <-chan struct{}) (*RedisTa
_, pingErr := conn.Do("PING")
if pingErr != nil {
if target.store == nil || !IsConnRefusedErr(pingErr) {
if target.store == nil || !(IsConnRefusedErr(pingErr) || IsConnResetErr(pingErr)) {
return nil, pingErr
}
} else {
+7 -3
View File
@@ -93,8 +93,12 @@ func IsConnRefusedErr(err error) bool {
return false
}
// isConnResetErr - Checks for connection reset errors.
func isConnResetErr(err error) bool {
// IsConnResetErr - Checks for connection reset errors.
func IsConnResetErr(err error) bool {
if strings.Contains(err.Error(), "connection reset by peer") {
return true
}
// incase if error message is wrapped.
if opErr, ok := err.(*net.OpError); ok {
if syscallErr, ok := opErr.Err.(*os.SyscallError); ok {
if syscallErr.Err == syscall.ECONNRESET {
@@ -117,7 +121,7 @@ func sendEvents(target event.Target, eventKeyCh <-chan string, doneCh <-chan str
break
}
if err != errNotConnected && !isConnResetErr(err) {
if err != errNotConnected && !IsConnResetErr(err) {
panic(fmt.Errorf("target.Send() failed with '%v'", err))
}
+36 -38
View File
@@ -173,7 +173,7 @@ const directioAlignSize = 4096
// used with DIRECT I/O based file descriptor and it is expected that
// input writer *os.File not a generic io.Writer. Make sure to have
// the file opened for writes with syscall.O_DIRECT flag.
func CopyAligned(w *os.File, r io.Reader, alignedBuf []byte) (int64, error) {
func CopyAligned(w *os.File, r io.Reader, alignedBuf []byte, totalSize int64) (int64, error) {
// Writes remaining bytes in the buffer.
writeUnaligned := func(w *os.File, buf []byte) (remainingWritten int, err error) {
var n int
@@ -205,47 +205,45 @@ func CopyAligned(w *os.File, r io.Reader, alignedBuf []byte) (int64, error) {
}
var written int64
var err error
for {
nr, er := r.Read(alignedBuf)
if nr == len(alignedBuf) {
// Buffer read is aligned with input buffer, proceed to write.
nw, ew := w.Write(alignedBuf)
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
} else if nr > 0 {
// Buffer read is not aligned with input buffer, proceed to write
// whatever possible as aligned and turn off direct I/O.
nw, ew := writeUnaligned(w, alignedBuf[:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
buf := alignedBuf
if totalSize != -1 {
remaining := totalSize - written
if remaining < int64(len(buf)) {
buf = buf[:remaining]
}
}
// For any read errors break out and return error.
if er != nil {
if er != io.EOF {
err = er
nr, err := io.ReadFull(r, buf)
eof := err == io.EOF || err == io.ErrUnexpectedEOF
if err != nil && !eof {
return written, err
}
buf = buf[:nr]
var nw int
if len(buf)%directioAlignSize == 0 {
// buf is aligned for directio write()
nw, err = w.Write(buf)
} else {
// buf is not aligned, hence use writeUnaligned()
nw, err = writeUnaligned(w, buf)
}
if nw > 0 {
written += int64(nw)
}
if err != nil {
return written, err
}
if nw != len(buf) {
return written, io.ErrShortWrite
}
if totalSize != -1 {
if written == totalSize {
return written, nil
}
break
}
if eof {
return written, nil
}
}
return written, err
}
+118 -68
View File
@@ -3,7 +3,7 @@ The MinIO Admin Golang Client SDK provides APIs to manage MinIO services.
This quickstart guide will show you how to install the MinIO Admin client SDK, connect to MinIO admin service, and provide a walkthrough of a simple file uploader.
This document assumes that you have a working [Golang setup](https://docs.min.io/docs/how-to-install-golang).
This document assumes that you have a working [Golang setup](https://golang.org/doc/install).
## Initialize MinIO Admin Client object.
@@ -31,24 +31,25 @@ func main() {
}
// Fetch service status.
st, err := mdmClnt.ServiceStatus()
st, err := mdmClnt.ServerInfo()
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", st)
for _, peerInfo := range serversInfo {
log.Printf("Node: %s, Info: %v\n", peerInfo.Addr, peerInfo.Data)
}
}
```
| Service operations | Info operations | Healing operations | Config operations | Top operations | IAM operations | Misc |
|:------------------------------------------|:--------------------------------------------|:-------------------|:----------------------------------|:------------------------|:--------------------------------------|:--------------------------------------------------|
| [`ServiceStatus`](#ServiceStatus) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | |
| [`ServiceSendAction`](#ServiceSendAction) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) |
| [`Trace`](#Trace) | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) |
| | | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | |
| Service operations | Info operations | Healing operations | Config operations | Top operations | IAM operations | Misc | KMS |
|:------------------------------------|:------------------------------------------------|:-------------------|:----------------------------------|:------------------------|:--------------------------------------|:--------------------------------------------------|:----------------------------------|
| [`ServiceRestart`](#ServiceRestart) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | | [`GetKeyStatus`](#GetKeyStatus) |
| [`ServiceStop`](#ServiceStop) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) | |
| | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | [`GetConfigKeys`](#GetConfigKeys) | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) | |
| [`ServiceTrace`](#ServiceTrace) | [`ServerDrivesPerfInfo`](#ServerDrivesPerfInfo) | | [`SetConfigKeys`](#SetConfigKeys) | | [`AddCannedPolicy`](#AddCannedPolicy) | [`ServerUpdate`](#ServerUpdate) | |
| | [`NetPerfInfo`](#NetPerfInfo) | | | | | | |
## 1. Constructor
<a name="MinIO"></a>
@@ -64,25 +65,7 @@ __Parameters__
| `secretAccessKey` | _string_ | Secret key for the object storage endpoint. |
| `ssl` | _bool_ | Set this value to 'true' to enable secure (HTTPS) access. |
## 2. Admin API Version
<a name="VersionInfo"></a>
### VersionInfo() (AdminAPIVersionInfo, error)
Fetch server's supported Administrative API version.
__Example__
``` go
info, err := madmClnt.VersionInfo()
if err != nil {
log.Fatalln(err)
}
log.Printf("%s\n", info.Version)
```
## 3. Service operations
## 2. Service operations
<a name="ServiceStatus"></a>
### ServiceStatus() (ServiceStatusMetadata, error)
@@ -111,25 +94,55 @@ Fetch service status, replies disk space used, backend type and total disks offl
```
<a name="ServiceSendAction"></a>
### ServiceSendAction(act ServiceActionValue) (error)
Sends a service action command to service - possible actions are restarting and stopping the server.
<a name="ServiceRestart"></a>
### ServiceRestart() error
Sends a service action restart command to MinIO server.
__Example__
```go
// To restart the service, restarts all servers in the cluster.
err := madmClnt.ServiceRestart()
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
```
```go
// to restart
st, err := madmClnt.ServiceSendAction(ServiceActionValueRestart)
// or to stop
// st, err := madmClnt.ServiceSendAction(ServiceActionValueStop)
if err != nil {
log.Fatalln(err)
}
log.Printf("Success")
```
<a name="ServiceStop"></a>
### ServiceStop() error
Sends a service action stop command to MinIO server.
## 4. Info operations
__Example__
```go
// To stop the service, stops all servers in the cluster.
err := madmClnt.ServiceStop()
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
```
<a name="ServiceTrace"></a>
### ServiceTrace(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.ServiceTrace(allTrace, doneCh)
for traceInfo := range traceCh {
fmt.Println(traceInfo.String())
}
```
## 3. Info operations
<a name="ServerInfo"></a>
### ServerInfo() ([]ServerInfo, error)
@@ -213,7 +226,7 @@ Fetches information for all cluster nodes, such as server properties, storage in
<a name="ServerDrivesPerfInfo"></a>
### ServerDrivesPerfInfo() ([]ServerDrivesPerfInfo, error)
Fetches drive performance information for all cluster nodes. Returned value is in Bytes/s.
Fetches drive performance information for all cluster nodes.
| Param | Type | Description |
|-----------------|--------------------|--------------------------------------------------------------------|
@@ -231,7 +244,7 @@ Fetches drive performance information for all cluster nodes. Returned value is i
<a name="ServerCPULoadInfo"></a>
### ServerCPULoadInfo() ([]ServerCPULoadInfo, error)
Fetches CPU utilization for all cluster nodes. Returned value is in Bytes.
Fetches CPU utilization for all cluster nodes.
| Param | Type | Description |
|----------------|------------|---------------------------------------------------------------------|
@@ -244,12 +257,12 @@ Fetches CPU utilization for all cluster nodes. Returned value is in Bytes.
| `cpu.Load.Avg` | _float64_ | The average utilization of the CPU measured in a 200ms interval |
| `cpu.Load.Min` | _float64_ | The minimum utilization of the CPU measured in a 200ms interval |
| `cpu.Load.Max` | _float64_ | The maximum utilization of the CPU measured in a 200ms interval |
| `cpu.Load.Error` | _string_ | Error (if any) encountered while accesing the CPU info |
| `cpu.Load.Error` | _string_ | Error (if any) encountered while accessing the CPU info |
<a name="ServerMemUsageInfo"></a>
### ServerMemUsageInfo() ([]ServerMemUsageInfo, error)
Fetches Mem utilization for all cluster nodes. Returned value is in Bytes.
Fetches Mem utilization for all cluster nodes.
| Param | Type | Description |
|-----------------|-------------|---------------------------------------------------------------------|
@@ -260,9 +273,20 @@ Fetches Mem utilization for all cluster nodes. Returned value is in Bytes.
| Param | Type | Description |
|-------------------|----------|--------------------------------------------------------|
| `mem.Usage.Mem` | _uint64_ | The total number of bytes obtained from the OS |
| `mem.Usage.Error` | _string_ | Error (if any) encountered while accesing the CPU info |
| `mem.Usage.Error` | _string_ | Error (if any) encountered while accessing the CPU info |
## 6. Heal operations
<a name="NetPerfInfo"></a>
### NetPerfInfo(int size) (map[string][]NetPerfInfo, error)
Fetches network performance of all cluster nodes using given sized payload. Returned value is a map containing each node indexed list of performance of other nodes.
| Param | Type | Description |
|------------|------------------|--------------------------------------------------------------------|
| `Addr` | _string_ | Address of the server the following information is retrieved from. |
| `Error` | _string_ | Errors (if any) encountered while reaching this node |
| `ReadPerf` | _time.Duration_ | Network read performance of the server |
## 5. Heal operations
<a name="Heal"></a>
### Heal(bucket, prefix string, healOpts HealOpts, clientToken string, forceStart bool, forceStop bool) (start HealStartSuccess, status HealTaskStatus, err error)
@@ -327,7 +351,7 @@ __Example__
| `DiskInfo.AvailableOn` | _[]int_ | List of disks on which the healed entity is present and healthy |
| `DiskInfo.HealedOn` | _[]int_ | List of disks on which the healed entity was restored |
## 7. Config operations
## 6. Config operations
<a name="GetConfig"></a>
### GetConfig() ([]byte, error)
@@ -405,7 +429,7 @@ __Example__
log.Println("New configuration successfully set")
```
## 8. Top operations
## 7. Top operations
<a name="TopLocks"></a>
### TopLocks() (LockEntries, error)
@@ -427,7 +451,7 @@ __Example__
log.Println("TopLocks received successfully: ", string(out))
```
## 9. IAM operations
## 8. IAM operations
<a name="AddCannedPolicy"></a>
### AddCannedPolicy(policyName string, policy string) error
@@ -483,7 +507,25 @@ __Example__
}
```
## 10. Misc operations
## 9. Misc operations
<a name="ServerUpdate"></a>
### ServerUpdate(updateURL string) (ServerUpdateStatus, error)
Sends a update command to MinIO server, to update MinIO server to latest release. In distributed setup it updates all servers atomically.
__Example__
```go
// Updates all servers and restarts all the servers in the cluster.
// optionally takes an updateURL, which is used to update the binary.
us, err := madmClnt.ServerUpdate(updateURL)
if err != nil {
log.Fatalln(err)
}
if us.CurrentVersion != us.UpdatedVersion {
log.Printf("Updated server version from %s to %s successfully", us.CurrentVersion, us.UpdatedVersion)
}
```
<a name="StartProfiling"></a>
### StartProfiling(profiler string) error
@@ -538,21 +580,29 @@ __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
## 11. KMS
<a name="GetKeyStatus"></a>
### GetKeyStatus(keyID string) (*KMSKeyStatus, error)
Requests status information about one particular KMS master key
from a MinIO server. The keyID is optional and the server will
use the default master key (configured via `MINIO_SSE_VAULT_KEY_NAME`
or `MINIO_SSE_MASTER_KEY`) if the keyID is empty.
__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())
keyInfo, err := madmClnt.GetKeyStatus("my-minio-key")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
```
if keyInfo.EncryptionErr != "" {
log.Fatalf("Failed to perform encryption operation using '%s': %v\n", keyInfo.KeyID, keyInfo.EncryptionErr)
}
if keyInfo.UpdateErr != "" {
log.Fatalf("Failed to perform key re-wrap operation using '%s': %v\n", keyInfo.KeyID, keyInfo.UpdateErr)
}
if keyInfo.DecryptionErr != "" {
log.Fatalf("Failed to perform decryption operation using '%s': %v\n", keyInfo.KeyID, keyInfo.DecryptionErr)
}
```

Some files were not shown because too many files have changed in this diff Show More