mirror of
https://github.com/pgsty/minio.git
synced 2026-11-04 14:52:02 +03:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 545a9e4a82 | |||
| f25ec31565 | |||
| 374feda237 | |||
| 6ef0161835 | |||
| 1a3dbbc9dd | |||
| f1355da72e | |||
| bbe521ffec | |||
| 7d75d61621 | |||
| 1f77708a30 | |||
| 84fc78d60f | |||
| a182fe8c15 | |||
| 819d1e80c6 | |||
| ffdf115bf2 | |||
| eb7c690ea9 | |||
| 8c08571cd9 | |||
| 2853fa1882 | |||
| 1672c73988 | |||
| 5476415016 | |||
| 67ac74471d | |||
| a1c1a18dc5 | |||
| eb2894233c | |||
| 52e382b697 | |||
| 0d435e11b1 | |||
| 2755a0b763 | |||
| 043e030a4a | |||
| 490c30f853 | |||
| 59749a2b85 | |||
| fc3cf97b81 | |||
| d45a8784fc | |||
| 6d7319380c | |||
| 14e6c5ec08 | |||
| 98d07210e7 | |||
| 6923630389 | |||
| 0c73c81919 | |||
| a46b640da3 | |||
| 71f9d2beff | |||
| cf414a6053 | |||
| 05b395e81d | |||
| 6a2d7ae808 | |||
| 685afb6749 | |||
| 8efa82126b | |||
| 6e6aeb6a9e | |||
| c9e00ae0a5 | |||
| 08e0698b7e | |||
| 135a6a7bb4 | |||
| 8b4d7048f8 | |||
| f4d4ea5c36 |
+2
-1
@@ -9,7 +9,7 @@ site/
|
||||
/.idea/
|
||||
/Minio.iml
|
||||
**/access.log
|
||||
build
|
||||
build/
|
||||
vendor/**/*.js
|
||||
vendor/**/*.json
|
||||
release
|
||||
@@ -23,3 +23,4 @@ parts/
|
||||
prime/
|
||||
snap/.snapcraft/
|
||||
stage/
|
||||
.sia_temp/
|
||||
|
||||
@@ -5,6 +5,7 @@ MAINTAINER Minio Inc <dev@minio.io>
|
||||
ENV GOPATH /go
|
||||
ENV PATH $PATH:$GOPATH/bin
|
||||
ENV CGO_ENABLED 0
|
||||
ENV MINIO_UPDATE off
|
||||
|
||||
WORKDIR /go/src/github.com/minio/
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ MAINTAINER Minio Inc <dev@minio.io>
|
||||
ENV GOPATH /go
|
||||
ENV PATH $PATH:$GOPATH/bin
|
||||
ENV CGO_ENABLED 0
|
||||
ENV MINIO_UPDATE off
|
||||
|
||||
WORKDIR /go/src/github.com/minio/
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ MAINTAINER Minio Inc <dev@minio.io>
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||
|
||||
ENV MINIO_UPDATE off
|
||||
|
||||
RUN \
|
||||
apk add --no-cache ca-certificates && \
|
||||
apk add --no-cache --virtual .build-deps curl && \
|
||||
|
||||
@@ -58,15 +58,13 @@ spelling:
|
||||
check: test
|
||||
test: verifiers build
|
||||
@echo "Running unit tests"
|
||||
@go test $(GOFLAGS) .
|
||||
@go test $(GOFLAGS) github.com/minio/minio/cmd...
|
||||
@go test $(GOFLAGS) github.com/minio/minio/pkg...
|
||||
@go test $(GOFLAGS) ./...
|
||||
@echo "Verifying build"
|
||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||
|
||||
coverage: build
|
||||
@echo "Running all coverage for minio"
|
||||
@./buildscripts/go-coverage.sh
|
||||
@(env bash $(PWD)/buildscripts/go-coverage.sh)
|
||||
|
||||
# Builds minio locally.
|
||||
build:
|
||||
|
||||
@@ -26,8 +26,8 @@ Install minio packages using [Homebrew](http://brew.sh/)
|
||||
brew install minio/stable/minio
|
||||
minio server /data
|
||||
```
|
||||
#### Note
|
||||
If you previously installed minio using `brew install minio` then it is recommended that you reinstall minio from `minio/stable/minio` official repo instead.
|
||||
|
||||
> NOTE: If you previously installed minio using `brew install minio` then it is recommended that you reinstall minio from `minio/stable/minio` official repo instead.
|
||||
```sh
|
||||
brew uninstall minio
|
||||
brew install minio/stable/minio
|
||||
@@ -53,18 +53,15 @@ chmod +x minio
|
||||
```
|
||||
|
||||
### Snap
|
||||
You can install the latest `minio` [snap](https://snapcraft.io), and help testing the most recent changes of the master branch in [all the supported Linux distros](https://snapcraft.io/docs/core/install) with:
|
||||
|
||||
Install minio using [Snap](https://snapcraft.io)
|
||||
```sh
|
||||
sudo snap install minio --edge
|
||||
```
|
||||
|
||||
Every time a new version of `minio` is pushed to the store, you will get it updated automatically.
|
||||
|
||||
You will need to allow the minio snap to observe mounts in the system:
|
||||
|
||||
Start minio using `snap run` command
|
||||
```sh
|
||||
sudo snap connect minio:mount-observe
|
||||
sudo snap run minio server /data
|
||||
```
|
||||
|
||||
## Microsoft Windows
|
||||
@@ -88,7 +85,6 @@ service minio start
|
||||
```
|
||||
|
||||
## Install from Source
|
||||
|
||||
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://docs.minio.io/docs/how-to-install-golang).
|
||||
|
||||
```sh
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
# Minio Quickstart Guide [](https://slack.minio.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/) [](https://codecov.io/gh/minio/minio)
|
||||
|
||||
Minio 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
|
||||
|
||||
Minio是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
|
||||
|
||||
## Docker 容器
|
||||
### 稳定版
|
||||
```
|
||||
docker pull minio/minio
|
||||
docker run -p 9000:9000 minio/minio server /data
|
||||
```
|
||||
|
||||
### 尝鲜版
|
||||
```
|
||||
docker pull minio/minio:edge
|
||||
docker run -p 9000:9000 minio/minio:edge server /data
|
||||
```
|
||||
更多Docker部署信息请访问 [这里](https://docs.minio.io/docs/minio-docker-quickstart-guide)
|
||||
|
||||
## macOS
|
||||
### Homebrew
|
||||
使用 [Homebrew](http://brew.sh/)安装minio
|
||||
|
||||
```sh
|
||||
brew install minio/stable/minio
|
||||
minio server /data
|
||||
```
|
||||
#### Note
|
||||
如果你之前使用 `brew install minio`安装过minio, 可以用 `minio/stable/minio` 官方镜像进行重装. 由于golang 1.8的bug,homebrew版本不太稳定。
|
||||
|
||||
```
|
||||
brew uninstall minio
|
||||
brew install minio/stable/minio
|
||||
```
|
||||
|
||||
### 下载二进制文件
|
||||
| 操作系统| CPU架构 | 地址|
|
||||
| ----------| -------- | ------|
|
||||
|Apple macOS|64-bit Intel|https://dl.minio.io/server/minio/release/darwin-amd64/minio |
|
||||
```sh
|
||||
chmod 755 minio
|
||||
./minio server /data
|
||||
```
|
||||
|
||||
## GNU/Linux
|
||||
### 下载二进制文件
|
||||
| 操作系统| CPU架构 | 地址|
|
||||
| ----------| -------- | ------|
|
||||
|GNU/Linux|64-bit Intel|https://dl.minio.io/server/minio/release/linux-amd64/minio |
|
||||
```sh
|
||||
chmod +x minio
|
||||
./minio server /data
|
||||
```
|
||||
|
||||
### 快照版
|
||||
你可以下载最新版 `minio` [快照](https://snapcraft.io), 并且帮助我们一起验证master分支上[所有支持的Linux发行版](https://snapcraft.io/docs/core/install) 的一些最新修改:
|
||||
|
||||
```sh
|
||||
sudo snap install minio --edge
|
||||
```
|
||||
|
||||
每次有最新的 `minio` 被推送到服务器,你都会自动更新下来.
|
||||
|
||||
你需要允许minio snap来观察其安装:
|
||||
|
||||
```sh
|
||||
sudo snap connect minio:mount-observe
|
||||
```
|
||||
|
||||
## 微软Windows系统
|
||||
### 下载二进制文件
|
||||
| 操作系统| CPU架构 | 地址|
|
||||
| ----------| -------- | ------|
|
||||
|微软Windows系统|64位|https://dl.minio.io/server/minio/release/windows-amd64/minio.exe |
|
||||
```sh
|
||||
minio.exe server D:\Photos
|
||||
```
|
||||
|
||||
## FreeBSD
|
||||
### Port
|
||||
使用 [pkg](https://github.com/freebsd/pkg)进行安装。
|
||||
|
||||
```sh
|
||||
pkg install minio
|
||||
sysrc minio_enable=yes
|
||||
sysrc minio_disks=/home/user/Photos
|
||||
service minio start
|
||||
```
|
||||
|
||||
## 使用源码安装
|
||||
|
||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://docs.minio.io/docs/how-to-install-golang).
|
||||
|
||||
```sh
|
||||
go get -u github.com/minio/minio
|
||||
```
|
||||
|
||||
## 使用Minio浏览器进行验证
|
||||
安装后使用浏览器访问[http://127.0.0.1:9000](http://127.0.0.1:9000),如果可以访问,则表示minio已经安装成功。
|
||||
|
||||

|
||||
|
||||
## 使用Minio客户端 `mc`进行验证
|
||||
`mc` 提供了一些UNIX常用命令的替代品,像ls, cat, cp, mirror, diff这些。 它支持文件系统和亚马逊S3云存储服务。 更多信息请参考 [mc快速入门](https://docs.minio.io/docs/minio-client-quickstart-guide) 。
|
||||
|
||||
## 了解更多
|
||||
- [Minio纠删码入门](https://docs.minio.io/docs/minio-erasure-code-quickstart-guide)
|
||||
- [`mc`快速入门](https://docs.minio.io/docs/minio-client-quickstart-guide)
|
||||
- [使用 `aws-cli`](https://docs.minio.io/docs/aws-cli-with-minio)
|
||||
- [使用 `s3cmd`](https://docs.minio.io/docs/s3cmd-with-minio)
|
||||
- [使用 `minio-go` SDK](https://docs.minio.io/docs/golang-client-quickstart-guide)
|
||||
- [Minio文档](https://docs.minio.io)
|
||||
|
||||
## 如何参与到Minio项目
|
||||
请参考 [贡献者指南](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)。欢迎各位中国程序员加到Minio项目中。
|
||||
+8
-11
@@ -38,21 +38,18 @@ test_script:
|
||||
# Unit tests
|
||||
- ps: Add-AppveyorTest "Unit Tests" -Outcome Running
|
||||
- mkdir build\coverage
|
||||
- go test -v -timeout 17m -race github.com/minio/minio/cmd...
|
||||
- go test -v -race github.com/minio/minio/pkg...
|
||||
# FIXME(aead): enable codecov after issue https://github.com/golang/go/issues/18468 is solved.
|
||||
# - go test -v -timeout 17m -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd
|
||||
- for /f "" %%G in ('go list github.com/minio/minio/... ^| find /i /v "browser/"') do ( go test -v -timeout 20m -race %%G )
|
||||
- go test -v -timeout 20m -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd
|
||||
- ps: Update-AppveyorTest "Unit Tests" -Outcome Passed
|
||||
|
||||
after_test:
|
||||
# FIXME(aead): enable codecov after issue https://github.com/golang/go/issues/18468 is solved.
|
||||
# - go tool cover -html=build\coverage\coverage.txt -o build\coverage\coverage.html
|
||||
# - ps: Push-AppveyorArtifact build\coverage\coverage.txt
|
||||
# - ps: Push-AppveyorArtifact build\coverage\coverage.html
|
||||
- go tool cover -html=build\coverage\coverage.txt -o build\coverage\coverage.html
|
||||
- ps: Push-AppveyorArtifact build\coverage\coverage.txt
|
||||
- ps: Push-AppveyorArtifact build\coverage\coverage.html
|
||||
# Upload coverage report.
|
||||
# - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
|
||||
# - pip install codecov
|
||||
# - codecov -X gcov -f "build\coverage\coverage.txt"
|
||||
- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
|
||||
- pip install codecov
|
||||
- codecov -X gcov -f "build\coverage\coverage.txt"
|
||||
|
||||
# to disable deployment
|
||||
deploy: off
|
||||
|
||||
@@ -316,8 +316,8 @@ export const selectBucket = (newCurrentBucket, prefix) => {
|
||||
|
||||
export const listObjects = () => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, currentPath, marker, objects, istruncated, web} = getState()
|
||||
if (!istruncated) return
|
||||
const {buckets, currentBucket, currentPath, marker, objects, istruncated, web} = getState()
|
||||
if (!istruncated || buckets.length === 0) return
|
||||
web.ListObjects({
|
||||
bucketName: currentBucket,
|
||||
prefix: currentPath,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
.fe-body {
|
||||
@media(min-width: @screen-md-min) {
|
||||
padding: 0 0 40px @fe-sidebar-width;
|
||||
padding: 0 0 80px @fe-sidebar-width;
|
||||
}
|
||||
|
||||
@media(max-width: @screen-sm-max) {
|
||||
@@ -79,7 +79,7 @@
|
||||
text-align: center;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
|
||||
@@ -159,7 +159,7 @@ div.fesl-row {
|
||||
top: 0;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
z-index: 20;
|
||||
z-index: 8;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -223,7 +223,7 @@ div.fesl-row {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 2px solid @white;
|
||||
z-index: 10;
|
||||
z-index: 7;
|
||||
border-radius: 2px;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
|
||||
+43
-40
File diff suppressed because one or more lines are too long
@@ -3,8 +3,8 @@
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -coverprofile=profile.out -covermode=atomic $d
|
||||
for d in $(go list ./... | grep -v browser); do
|
||||
go test -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
|
||||
@@ -183,12 +183,12 @@ func (adminAPI adminAPIHandlers) ServiceCredentialsHandler(w http.ResponseWriter
|
||||
}
|
||||
|
||||
// Update local credentials in memory.
|
||||
prevCred := serverConfig.SetCredential(creds)
|
||||
prevCred := globalServerConfig.SetCredential(creds)
|
||||
|
||||
// Save credentials to config file
|
||||
if err = serverConfig.Save(); err != nil {
|
||||
if err = globalServerConfig.Save(); err != nil {
|
||||
// Save the current creds when failed to update.
|
||||
serverConfig.SetCredential(prevCred)
|
||||
globalServerConfig.SetCredential(prevCred)
|
||||
|
||||
errorIf(err, "Unable to update the config with new credentials.")
|
||||
writeErrorResponse(w, ErrInternalError, r.URL)
|
||||
@@ -842,7 +842,7 @@ func (adminAPI adminAPIHandlers) HealFormatHandler(w http.ResponseWriter, r *htt
|
||||
|
||||
// Wrap into retrying disks
|
||||
retryingDisks := initRetryableStorageDisks(bootstrapDisks,
|
||||
time.Millisecond, time.Millisecond*5)
|
||||
time.Millisecond, time.Millisecond*5, globalStorageHealthCheckInterval, globalStorageRetryThreshold)
|
||||
|
||||
// Heal format.json on available storage.
|
||||
err = healFormatXL(retryingDisks)
|
||||
@@ -985,7 +985,7 @@ func (adminAPI adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
var config serverConfigV20
|
||||
var config serverConfig
|
||||
err = json.Unmarshal(configBytes, &config)
|
||||
|
||||
if err != nil {
|
||||
@@ -995,7 +995,7 @@ func (adminAPI adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
if globalIsEnvCreds {
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
if config.Credential.AccessKey != creds.AccessKey ||
|
||||
config.Credential.SecretKey != creds.SecretKey {
|
||||
writeErrorResponse(w, ErrAdminCredentialsMismatch, r.URL)
|
||||
|
||||
+12
-11
@@ -33,6 +33,7 @@ import (
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
var configJSON = []byte(`{
|
||||
@@ -306,7 +307,7 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
||||
if cmd == restartCmd {
|
||||
go testServiceSignalReceiver(cmd, t)
|
||||
}
|
||||
credentials := serverConfig.GetCredential()
|
||||
credentials := globalServerConfig.GetCredential()
|
||||
var body []byte
|
||||
|
||||
req, err := getServiceCmdRequest(cmd, credentials, body)
|
||||
@@ -361,7 +362,7 @@ func TestServiceSetCreds(t *testing.T) {
|
||||
globalMinioAddr = "127.0.0.1:9000"
|
||||
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||
|
||||
credentials := serverConfig.GetCredential()
|
||||
credentials := globalServerConfig.GetCredential()
|
||||
var body []byte
|
||||
|
||||
testCases := []struct {
|
||||
@@ -410,7 +411,7 @@ func TestServiceSetCreds(t *testing.T) {
|
||||
|
||||
// If we got 200 OK, check if new credentials are really set
|
||||
if rec.Code == http.StatusOK {
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
if cred.AccessKey != testCase.Username {
|
||||
t.Errorf("Test %d: Wrong access key, expected = %s, found = %s", i+1, testCase.Username, cred.AccessKey)
|
||||
}
|
||||
@@ -487,7 +488,7 @@ func TestListLocksHandler(t *testing.T) {
|
||||
}
|
||||
req.Header.Set(minioAdminOpHeader, "list")
|
||||
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d - Failed to sign list locks request - %v", i+1, err)
|
||||
@@ -555,7 +556,7 @@ func TestClearLocksHandler(t *testing.T) {
|
||||
}
|
||||
req.Header.Set(minioAdminOpHeader, "clear")
|
||||
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d - Failed to sign clear locks request - %v", i+1, err)
|
||||
@@ -840,7 +841,7 @@ func TestListObjectsHealHandler(t *testing.T) {
|
||||
}
|
||||
req.Header.Set(minioAdminOpHeader, "list-objects")
|
||||
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d - Failed to sign list objects needing heal request - %v", i+1, err)
|
||||
@@ -911,7 +912,7 @@ func TestHealBucketHandler(t *testing.T) {
|
||||
|
||||
req.Header.Set(minioAdminOpHeader, "bucket")
|
||||
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d - Failed to sign heal bucket request - %v",
|
||||
@@ -1014,7 +1015,7 @@ func TestHealObjectHandler(t *testing.T) {
|
||||
|
||||
req.Header.Set(minioAdminOpHeader, "object")
|
||||
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d - Failed to sign heal object request - %v", i+1, err)
|
||||
@@ -1033,15 +1034,15 @@ func buildAdminRequest(queryVal url.Values, opHdr, method string,
|
||||
contentLength int64, bodySeeker io.ReadSeeker) (*http.Request, error) {
|
||||
req, err := newTestRequest(method, "/?"+queryVal.Encode(), contentLength, bodySeeker)
|
||||
if err != nil {
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
req.Header.Set(minioAdminOpHeader, opHdr)
|
||||
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
err = signRequestV4(req, cred.AccessKey, cred.SecretKey)
|
||||
if err != nil {
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
|
||||
@@ -18,7 +18,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -139,7 +139,7 @@ func (lc localAdminClient) ServerInfoData() (sid ServerInfoData, e error) {
|
||||
Version: Version,
|
||||
CommitID: CommitID,
|
||||
SQSARN: arns,
|
||||
Region: serverConfig.GetRegion(),
|
||||
Region: globalServerConfig.GetRegion(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -158,11 +158,11 @@ func (rc remoteAdminClient) ServerInfoData() (sid ServerInfoData, e error) {
|
||||
|
||||
// GetConfig - returns config.json of the local server.
|
||||
func (lc localAdminClient) GetConfig() ([]byte, error) {
|
||||
if serverConfig == nil {
|
||||
return nil, errors.New("config not present")
|
||||
if globalServerConfig == nil {
|
||||
return nil, fmt.Errorf("config not present")
|
||||
}
|
||||
|
||||
return json.Marshal(serverConfig)
|
||||
return json.Marshal(globalServerConfig)
|
||||
}
|
||||
|
||||
// GetConfig - returns config.json of the remote server.
|
||||
@@ -246,7 +246,7 @@ func makeAdminPeers(endpoints EndpointList) (adminPeerList adminPeers) {
|
||||
})
|
||||
|
||||
hostSet := set.CreateStringSet(globalMinioAddr)
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
serviceEndpoint := path.Join(minioReservedBucketPath, adminPath)
|
||||
for _, host := range GetRemotePeers(endpoints) {
|
||||
if hostSet.Contains(host) {
|
||||
@@ -483,7 +483,7 @@ func getPeerConfig(peers adminPeers) ([]byte, error) {
|
||||
configJSON, err := getValidServerConfig(serverConfigs, errs)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to find a valid server config")
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Return the config.json that was present quorum or more
|
||||
|
||||
+12
-8
@@ -18,7 +18,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -26,11 +25,12 @@ import (
|
||||
"time"
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
const adminPath = "/admin"
|
||||
|
||||
var errUnsupportedBackend = errors.New("not supported for non erasure-code backend")
|
||||
var errUnsupportedBackend = fmt.Errorf("not supported for non erasure-code backend")
|
||||
|
||||
// adminCmd - exports RPC methods for service status, stop and
|
||||
// restart commands.
|
||||
@@ -104,8 +104,12 @@ func (s *adminCmd) ReInitDisks(args *AuthRPCArgs, reply *AuthRPCReply) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrap into retrying disks
|
||||
retryingDisks := initRetryableStorageDisks(bootstrapDisks,
|
||||
time.Millisecond, time.Millisecond*5, globalStorageHealthCheckInterval, globalStorageRetryThreshold)
|
||||
|
||||
// Initialize new object layer with newly formatted disks.
|
||||
newObjectAPI, err := newXLObjects(bootstrapDisks)
|
||||
newObjectAPI, err := newXLObjects(retryingDisks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,7 +152,7 @@ func (s *adminCmd) ServerInfoData(args *AuthRPCArgs, reply *ServerInfoDataReply)
|
||||
Uptime: UTCNow().Sub(globalBootTime),
|
||||
Version: Version,
|
||||
CommitID: CommitID,
|
||||
Region: serverConfig.GetRegion(),
|
||||
Region: globalServerConfig.GetRegion(),
|
||||
SQSARN: arns,
|
||||
},
|
||||
StorageInfo: storageInfo,
|
||||
@@ -165,11 +169,11 @@ func (s *adminCmd) GetConfig(args *AuthRPCArgs, reply *ConfigReply) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if serverConfig == nil {
|
||||
return errors.New("config not present")
|
||||
if globalServerConfig == nil {
|
||||
return fmt.Errorf("config not present")
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(serverConfig)
|
||||
jsonBytes, err := json.Marshal(globalServerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -238,7 +242,7 @@ func registerAdminRPCRouter(mux *router.Router) error {
|
||||
adminRPCServer := newRPCServer()
|
||||
err := adminRPCServer.RegisterName("Admin", adminRPCHandler)
|
||||
if err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
adminRouter := mux.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter()
|
||||
adminRouter.Path(adminPath).Handler(adminRPCServer)
|
||||
|
||||
@@ -33,7 +33,7 @@ func testAdminCmd(cmd cmdType, t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(rootPath)
|
||||
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -95,7 +95,7 @@ func TestReInitDisks(t *testing.T) {
|
||||
globalIsXL = true
|
||||
adminServer := adminCmd{}
|
||||
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -163,7 +163,7 @@ func TestGetConfig(t *testing.T) {
|
||||
defer os.RemoveAll(rootPath)
|
||||
|
||||
adminServer := adminCmd{}
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
|
||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
@@ -212,7 +212,7 @@ func TestWriteAndCommitConfig(t *testing.T) {
|
||||
defer os.RemoveAll(rootPath)
|
||||
|
||||
adminServer := adminCmd{}
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
+17
-2
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
@@ -121,10 +122,10 @@ const (
|
||||
ErrMetadataTooLarge
|
||||
ErrUnsupportedMetadata
|
||||
ErrMaximumExpires
|
||||
ErrSlowDown
|
||||
// Add new error codes here.
|
||||
|
||||
// Server-Side-Encryption (with Customer provided key) related API errors.
|
||||
|
||||
ErrInsecureSSECustomerRequest
|
||||
ErrSSEEncryptedObject
|
||||
ErrInvalidEncryptionParameters
|
||||
@@ -163,6 +164,10 @@ const (
|
||||
ErrOperationTimedOut
|
||||
ErrPartsSizeUnequal
|
||||
ErrInvalidRequest
|
||||
|
||||
// Minio storage class error codes
|
||||
ErrInvalidStorageClass
|
||||
|
||||
// Add new extended error codes here.
|
||||
// Please open a https://github.com/minio/minio/issues before adding
|
||||
// new error codes here.
|
||||
@@ -193,6 +198,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{
|
||||
Description: "Unknown metadata directive.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidStorageClass: {
|
||||
Code: "InvalidStorageClass",
|
||||
Description: "Invalid storage class.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRequestBody: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Body shouldn't be set for this request.",
|
||||
@@ -505,6 +515,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{
|
||||
Description: "Request is not valid yet",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrSlowDown: {
|
||||
Code: "SlowDown",
|
||||
Description: "Please reduce your request",
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
// FIXME: Actual XML error response also contains the header which missed in list of signed header parameters.
|
||||
ErrUnsignedHeaders: {
|
||||
Code: "AccessDenied",
|
||||
@@ -752,7 +767,7 @@ func toAPIErrorCode(err error) (apiErr APIErrorCode) {
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
// Verify if the underlying error is signature mismatch.
|
||||
switch err {
|
||||
case errSignatureMismatch:
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ func setCommonHeaders(w http.ResponseWriter) {
|
||||
w.Header().Set("Server", globalServerUserAgent)
|
||||
// Set `x-amz-bucket-region` only if region is set on the server
|
||||
// by default minio uses an empty region.
|
||||
if region := serverConfig.GetRegion(); region != "" {
|
||||
if region := globalServerConfig.GetRegion(); region != "" {
|
||||
w.Header().Set("X-Amz-Bucket-Region", region)
|
||||
}
|
||||
w.Header().Set("Accept-Ranges", "bytes")
|
||||
|
||||
@@ -567,6 +567,12 @@ func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
|
||||
|
||||
// writeErrorRespone writes error headers
|
||||
func writeErrorResponse(w http.ResponseWriter, errorCode APIErrorCode, reqURL *url.URL) {
|
||||
switch errorCode {
|
||||
case ErrSlowDown, ErrServerNotInitialized, ErrReadQuorum, ErrWriteQuorum:
|
||||
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||
w.Header().Set("Retry-After", "120")
|
||||
}
|
||||
apiError := getAPIError(errorCode)
|
||||
// Generate error response.
|
||||
errorResponse := getAPIErrorResponse(apiError, reqURL.Path)
|
||||
|
||||
@@ -304,7 +304,7 @@ func mustNewRequest(method string, urlStr string, contentLength int64, body io.R
|
||||
// is signed with AWS Signature V4, fails if not able to do so.
|
||||
func mustNewSignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
|
||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
}
|
||||
@@ -314,7 +314,7 @@ func mustNewSignedRequest(method string, urlStr string, contentLength int64, bod
|
||||
func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
|
||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||
req.Header.Set("Content-Md5", "YWFhYWFhYWFhYWFhYWFhCg==")
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {
|
||||
t.Fatalf("Unable to initialized new signed http request %s", err)
|
||||
}
|
||||
@@ -334,7 +334,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
t.Fatalf("unable create credential, %s", err)
|
||||
}
|
||||
|
||||
serverConfig.SetCredential(creds)
|
||||
globalServerConfig.SetCredential(creds)
|
||||
|
||||
// List of test cases for validating http request authentication.
|
||||
testCases := []struct {
|
||||
@@ -353,7 +353,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||
|
||||
// Validates all testcases.
|
||||
for _, testCase := range testCases {
|
||||
if s3Error := isReqAuthenticated(testCase.req, serverConfig.GetRegion()); s3Error != testCase.s3Error {
|
||||
if s3Error := isReqAuthenticated(testCase.req, globalServerConfig.GetRegion()); s3Error != testCase.s3Error {
|
||||
t.Fatalf("Unexpected s3error returned wanted %d, got %d", testCase.s3Error, s3Error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestLogin(t *testing.T) {
|
||||
t.Fatalf("Failed to create test config - %v", err)
|
||||
}
|
||||
defer os.RemoveAll(rootPath)
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -51,12 +51,12 @@ func (br *browserPeerAPIHandlers) SetAuthPeer(args SetAuthPeerArgs, reply *AuthR
|
||||
}
|
||||
|
||||
// Update credentials in memory
|
||||
prevCred := serverConfig.SetCredential(args.Creds)
|
||||
prevCred := globalServerConfig.SetCredential(args.Creds)
|
||||
|
||||
// Save credentials to config file
|
||||
if err := serverConfig.Save(); err != nil {
|
||||
if err := globalServerConfig.Save(); err != nil {
|
||||
// Save the current creds when failed to update.
|
||||
serverConfig.SetCredential(prevCred)
|
||||
globalServerConfig.SetCredential(prevCred)
|
||||
|
||||
errorIf(err, "Unable to update the config with new credentials sent from browser RPC.")
|
||||
return err
|
||||
@@ -77,7 +77,7 @@ func updateCredsOnPeers(creds auth.Credentials) map[string]error {
|
||||
errs := make([]error, len(peers))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
serverCred := serverConfig.GetCredential()
|
||||
serverCred := globalServerConfig.GetCredential()
|
||||
// Launch go routines to send request to each peer in parallel.
|
||||
for ix := range peers {
|
||||
wg.Add(1)
|
||||
|
||||
@@ -18,6 +18,8 @@ package cmd
|
||||
|
||||
import (
|
||||
router "github.com/gorilla/mux"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// Set up an RPC endpoint that receives browser related calls. The
|
||||
@@ -40,7 +42,7 @@ func registerBrowserPeerRPCRouter(mux *router.Router) error {
|
||||
bpRPCServer := newRPCServer()
|
||||
err := bpRPCServer.RegisterName("BrowserPeer", bpHandlers)
|
||||
if err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
bpRouter := mux.NewRoute().PathPrefix(minioReservedBucketPath).Subrouter()
|
||||
|
||||
@@ -89,7 +89,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucket", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucket", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucket", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucket", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
+11
-10
@@ -32,6 +32,7 @@ import (
|
||||
mux "github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ import (
|
||||
func enforceBucketPolicy(bucket, action, resource, referer, sourceIP string, queryParams url.Values) (s3Error APIErrorCode) {
|
||||
// Verify if bucket actually exists
|
||||
if err := checkBucketExist(bucket, newObjectLayerFn()); err != nil {
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
switch err.(type) {
|
||||
case BucketNameInvalid:
|
||||
// Return error for invalid bucket name.
|
||||
@@ -119,7 +120,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
||||
s3Error := checkRequestAuthType(r, bucket, "s3:GetBucketLocation", globalMinioDefaultRegion)
|
||||
if s3Error == ErrInvalidRegion {
|
||||
// Clients like boto3 send getBucketLocation() call signed with region that is configured.
|
||||
s3Error = checkRequestAuthType(r, "", "s3:GetBucketLocation", serverConfig.GetRegion())
|
||||
s3Error = checkRequestAuthType(r, "", "s3:GetBucketLocation", globalServerConfig.GetRegion())
|
||||
}
|
||||
if s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
@@ -135,7 +136,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
||||
// Generate response.
|
||||
encodedSuccessResponse := encodeResponse(LocationResponse{})
|
||||
// Get current region.
|
||||
region := serverConfig.GetRegion()
|
||||
region := globalServerConfig.GetRegion()
|
||||
if region != globalMinioDefaultRegion {
|
||||
encodedSuccessResponse = encodeResponse(LocationResponse{
|
||||
Location: region,
|
||||
@@ -164,7 +165,7 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucketMultipartUploads", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucketMultipartUploads", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -211,7 +212,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
||||
s3Error := checkRequestAuthType(r, "", "", globalMinioDefaultRegion)
|
||||
if s3Error == ErrInvalidRegion {
|
||||
// Clients like boto3 send listBuckets() call signed with region that is configured.
|
||||
s3Error = checkRequestAuthType(r, "", "", serverConfig.GetRegion())
|
||||
s3Error = checkRequestAuthType(r, "", "", globalServerConfig.GetRegion())
|
||||
}
|
||||
if s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
@@ -245,7 +246,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
}
|
||||
|
||||
var authError APIErrorCode
|
||||
if authError = checkRequestAuthType(r, bucket, "s3:DeleteObject", serverConfig.GetRegion()); authError != ErrNone {
|
||||
if authError = checkRequestAuthType(r, bucket, "s3:DeleteObject", globalServerConfig.GetRegion()); authError != ErrNone {
|
||||
// In the event access is denied, a 200 response should still be returned
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||
if authError != ErrAccessDenied {
|
||||
@@ -328,7 +329,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
deletedObjects = append(deletedObjects, object)
|
||||
continue
|
||||
}
|
||||
if _, ok := errorCause(err).(ObjectNotFound); ok {
|
||||
if _, ok := errors.Cause(err).(ObjectNotFound); ok {
|
||||
// If the object is not found it should be
|
||||
// accounted as deleted as per S3 spec.
|
||||
deletedObjects = append(deletedObjects, object)
|
||||
@@ -384,7 +385,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
// PutBucket does not have any bucket action.
|
||||
s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion())
|
||||
s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion())
|
||||
if s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
@@ -648,7 +649,7 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucket", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, bucket, "s3:ListBucket", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponseHeadersOnly(w, s3Error)
|
||||
return
|
||||
}
|
||||
@@ -678,7 +679,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
// DeleteBucket does not have any bucket action.
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -209,5 +209,5 @@ type arnSQS struct {
|
||||
|
||||
// Stringer for constructing AWS ARN compatible string.
|
||||
func (m arnSQS) String() string {
|
||||
return minioSqs + serverConfig.GetRegion() + ":" + m.AccountID + ":" + m.Type
|
||||
return minioSqs + globalServerConfig.GetRegion() + ":" + m.AccountID + ":" + m.Type
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -48,7 +49,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -65,13 +66,13 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
||||
|
||||
// Attempt to successfully load notification config.
|
||||
nConfig, err := loadNotificationConfig(bucket, objAPI)
|
||||
if err != nil && errorCause(err) != errNoSuchNotifications {
|
||||
if err != nil && errors.Cause(err) != errNoSuchNotifications {
|
||||
errorIf(err, "Unable to read notification configuration.")
|
||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||
return
|
||||
}
|
||||
// For no notifications we write a dummy XML.
|
||||
if errorCause(err) == errNoSuchNotifications {
|
||||
if errors.Cause(err) == errNoSuchNotifications {
|
||||
// Complies with the s3 behavior in this regard.
|
||||
nConfig = ¬ificationConfig{}
|
||||
}
|
||||
@@ -102,7 +103,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -296,7 +297,7 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -337,7 +338,7 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
|
||||
accountARN := fmt.Sprintf(
|
||||
"%s:%s:%s:%s-%s",
|
||||
minioTopic,
|
||||
serverConfig.GetRegion(),
|
||||
globalServerConfig.GetRegion(),
|
||||
accountID,
|
||||
snsTypeMinio,
|
||||
targetServer,
|
||||
|
||||
@@ -120,7 +120,7 @@ func checkARN(arn, arnType string) APIErrorCode {
|
||||
// Server region is allowed to be empty by default,
|
||||
// in such a scenario ARN region is not validating
|
||||
// allowing all regions.
|
||||
if sregion := serverConfig.GetRegion(); sregion != "" {
|
||||
if sregion := globalServerConfig.GetRegion(); sregion != "" {
|
||||
region := strs[3]
|
||||
if region != sregion {
|
||||
return ErrRegionNotification
|
||||
@@ -146,34 +146,34 @@ func isValidQueueID(queueARN string) bool {
|
||||
// Is Queue identifier valid?.
|
||||
|
||||
if isAMQPQueue(sqsARN) { // AMQP eueue.
|
||||
amqpN := serverConfig.Notify.GetAMQPByID(sqsARN.AccountID)
|
||||
amqpN := globalServerConfig.Notify.GetAMQPByID(sqsARN.AccountID)
|
||||
return amqpN.Enable && amqpN.URL != ""
|
||||
} else if isMQTTQueue(sqsARN) {
|
||||
mqttN := serverConfig.Notify.GetMQTTByID(sqsARN.AccountID)
|
||||
mqttN := globalServerConfig.Notify.GetMQTTByID(sqsARN.AccountID)
|
||||
return mqttN.Enable && mqttN.Broker != ""
|
||||
} else if isNATSQueue(sqsARN) {
|
||||
natsN := serverConfig.Notify.GetNATSByID(sqsARN.AccountID)
|
||||
natsN := globalServerConfig.Notify.GetNATSByID(sqsARN.AccountID)
|
||||
return natsN.Enable && natsN.Address != ""
|
||||
} else if isElasticQueue(sqsARN) { // Elastic queue.
|
||||
elasticN := serverConfig.Notify.GetElasticSearchByID(sqsARN.AccountID)
|
||||
elasticN := globalServerConfig.Notify.GetElasticSearchByID(sqsARN.AccountID)
|
||||
return elasticN.Enable && elasticN.URL != ""
|
||||
} else if isRedisQueue(sqsARN) { // Redis queue.
|
||||
redisN := serverConfig.Notify.GetRedisByID(sqsARN.AccountID)
|
||||
redisN := globalServerConfig.Notify.GetRedisByID(sqsARN.AccountID)
|
||||
return redisN.Enable && redisN.Addr != ""
|
||||
} else if isPostgreSQLQueue(sqsARN) {
|
||||
pgN := serverConfig.Notify.GetPostgreSQLByID(sqsARN.AccountID)
|
||||
pgN := globalServerConfig.Notify.GetPostgreSQLByID(sqsARN.AccountID)
|
||||
// Postgres can work with only default conn. info.
|
||||
return pgN.Enable
|
||||
} else if isMySQLQueue(sqsARN) {
|
||||
msqlN := serverConfig.Notify.GetMySQLByID(sqsARN.AccountID)
|
||||
msqlN := globalServerConfig.Notify.GetMySQLByID(sqsARN.AccountID)
|
||||
// Mysql can work with only default conn. info.
|
||||
return msqlN.Enable
|
||||
} else if isKafkaQueue(sqsARN) {
|
||||
kafkaN := serverConfig.Notify.GetKafkaByID(sqsARN.AccountID)
|
||||
kafkaN := globalServerConfig.Notify.GetKafkaByID(sqsARN.AccountID)
|
||||
return (kafkaN.Enable && len(kafkaN.Brokers) > 0 &&
|
||||
kafkaN.Topic != "")
|
||||
} else if isWebhookQueue(sqsARN) {
|
||||
webhookN := serverConfig.Notify.GetWebhookByID(sqsARN.AccountID)
|
||||
webhookN := globalServerConfig.Notify.GetWebhookByID(sqsARN.AccountID)
|
||||
return webhookN.Enable && webhookN.Endpoint != ""
|
||||
}
|
||||
return false
|
||||
@@ -281,7 +281,7 @@ func unmarshalSqsARN(queueARN string) (mSqs arnSQS) {
|
||||
// Server region is allowed to be empty by default,
|
||||
// in such a scenario ARN region is not validating
|
||||
// allowing all regions.
|
||||
if sregion := serverConfig.GetRegion(); sregion != "" {
|
||||
if sregion := globalServerConfig.GetRegion(); sregion != "" {
|
||||
region := strs[3]
|
||||
if region != sregion {
|
||||
return
|
||||
|
||||
@@ -225,7 +225,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -284,7 +284,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -325,7 +325,7 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
@@ -88,7 +89,7 @@ func loadAllBucketPolicies(objAPI ObjectLayer) (policies map[string]policy.Bucke
|
||||
buckets, err := objAPI.ListBuckets()
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to list buckets.")
|
||||
return nil, errorCause(err)
|
||||
return nil, errors.Cause(err)
|
||||
}
|
||||
|
||||
policies = make(map[string]policy.BucketAccessPolicy)
|
||||
@@ -99,7 +100,7 @@ func loadAllBucketPolicies(objAPI ObjectLayer) (policies map[string]policy.Bucke
|
||||
if pErr != nil {
|
||||
// net.Dial fails for rpc client or any
|
||||
// other unexpected errors during net.Dial.
|
||||
if !isErrIgnored(pErr, errDiskNotFound) {
|
||||
if !errors.IsErrIgnored(pErr, errDiskNotFound) {
|
||||
if !isErrBucketPolicyNotFound(pErr) {
|
||||
pErrs = append(pErrs, pErr)
|
||||
}
|
||||
@@ -162,7 +163,7 @@ func readBucketPolicyJSON(bucket string, objAPI ObjectLayer) (bucketPolicyReader
|
||||
return nil, BucketPolicyNotFound{Bucket: bucket}
|
||||
}
|
||||
errorIf(err, "Unable to load policy for the bucket %s.", bucket)
|
||||
return nil, errorCause(err)
|
||||
return nil, errors.Cause(err)
|
||||
}
|
||||
|
||||
return &buffer, nil
|
||||
@@ -199,7 +200,7 @@ func removeBucketPolicy(bucket string, objAPI ObjectLayer) error {
|
||||
err := objAPI.DeleteObject(minioMetaBucket, policyPath)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to remove bucket-policy on bucket %s.", bucket)
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
if _, ok := err.(ObjectNotFound); ok {
|
||||
return BucketPolicyNotFound{Bucket: bucket}
|
||||
}
|
||||
@@ -226,12 +227,12 @@ func writeBucketPolicy(bucket string, objAPI ObjectLayer, bpy policy.BucketAcces
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf))
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to set policy for the bucket %s", bucket)
|
||||
return errorCause(err)
|
||||
return errors.Cause(err)
|
||||
}
|
||||
|
||||
if _, err = objAPI.PutObject(minioMetaBucket, policyPath, hashReader, nil); err != nil {
|
||||
errorIf(err, "Unable to set policy for the bucket %s", bucket)
|
||||
return errorCause(err)
|
||||
return errors.Cause(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
package cmd
|
||||
|
||||
import "go/build"
|
||||
|
||||
// DO NOT EDIT THIS FILE DIRECTLY. These are build-time constants
|
||||
// set through ‘buildscripts/gen-ldflags.go’.
|
||||
var (
|
||||
// GOPATH - GOPATH value at the time of build.
|
||||
GOPATH = ""
|
||||
GOPATH = build.Default.GOPATH
|
||||
|
||||
// Go get development tag.
|
||||
goGetTag = "DEVELOPMENT.GOGET"
|
||||
|
||||
+41
-20
@@ -29,35 +29,21 @@ import (
|
||||
|
||||
// Check for updates and print a notification message
|
||||
func checkUpdate(mode string) {
|
||||
// Its OK to ignore any errors during getUpdateInfo() here.
|
||||
if older, downloadURL, err := getUpdateInfo(1*time.Second, mode); err == nil {
|
||||
if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" {
|
||||
// Its OK to ignore any errors during doUpdate() here.
|
||||
if updateMsg, _, currentReleaseTime, latestReleaseTime, err := getUpdateInfo(2*time.Second, mode); err == nil {
|
||||
if globalInplaceUpdateDisabled {
|
||||
log.Println(updateMsg)
|
||||
} else {
|
||||
log.Println(prepareUpdateMessage("Run `minio update`", latestReleaseTime.Sub(currentReleaseTime)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func enableLoggers() {
|
||||
fileLogTarget := serverConfig.Logger.GetFile()
|
||||
if fileLogTarget.Enable {
|
||||
err := InitFileLogger(&fileLogTarget)
|
||||
fatalIf(err, "Unable to initialize file logger")
|
||||
log.AddTarget(fileLogTarget)
|
||||
}
|
||||
|
||||
consoleLogTarget := serverConfig.Logger.GetConsole()
|
||||
if consoleLogTarget.Enable {
|
||||
InitConsoleLogger(&consoleLogTarget)
|
||||
}
|
||||
|
||||
log.SetConsoleTarget(consoleLogTarget)
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
// Config file does not exist, we create it fresh and return upon success.
|
||||
if isFile(getConfigFile()) {
|
||||
fatalIf(migrateConfig(), "Config migration failed.")
|
||||
fatalIf(loadConfig(), "Unable to load config version: '%s'.", v20)
|
||||
fatalIf(loadConfig(), "Unable to load config version: '%s'.", serverConfigVersion)
|
||||
} else {
|
||||
fatalIf(newConfig(), "Unable to initialize minio config for the first time.")
|
||||
log.Println("Created minio configuration file successfully at " + getConfigDir())
|
||||
@@ -122,4 +108,39 @@ func handleCommonEnvVars() {
|
||||
if globalDomainName != "" {
|
||||
globalIsEnvDomainName = true
|
||||
}
|
||||
|
||||
// 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.
|
||||
globalInplaceUpdateDisabled = strings.EqualFold(os.Getenv("MINIO_UPDATE"), "off")
|
||||
|
||||
// Validate and store the storage class env variables only for XL/Dist XL setups
|
||||
if globalIsXL {
|
||||
var err error
|
||||
|
||||
// Check for environment variables and parse into storageClass struct
|
||||
if ssc := os.Getenv(standardStorageClassEnv); ssc != "" {
|
||||
globalStandardStorageClass, err = parseStorageClass(ssc)
|
||||
fatalIf(err, "Invalid value set in environment variable %s.", standardStorageClassEnv)
|
||||
}
|
||||
|
||||
if rrsc := os.Getenv(reducedRedundancyStorageClassEnv); rrsc != "" {
|
||||
globalRRStorageClass, err = parseStorageClass(rrsc)
|
||||
fatalIf(err, "Invalid value set in environment variable %s.", reducedRedundancyStorageClassEnv)
|
||||
}
|
||||
|
||||
// Validation is done after parsing both the storage classes. This is needed because we need one
|
||||
// storage class value to deduce the correct value of the other storage class.
|
||||
if globalRRStorageClass.Scheme != "" {
|
||||
err := validateRRSParity(globalRRStorageClass.Parity, globalStandardStorageClass.Parity)
|
||||
fatalIf(err, "Invalid value set in environment variable %s.", reducedRedundancyStorageClassEnv)
|
||||
globalIsStorageClass = true
|
||||
}
|
||||
|
||||
if globalStandardStorageClass.Scheme != "" {
|
||||
err := validateSSParity(globalStandardStorageClass.Parity, globalRRStorageClass.Parity)
|
||||
fatalIf(err, "Invalid value set in environment variable %s.", standardStorageClassEnv)
|
||||
globalIsStorageClass = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
@@ -27,36 +28,27 @@ import (
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// Steps to move from version N to version N+1
|
||||
// 1. Add new struct serverConfigVN+1 in config-versions.go
|
||||
// 2. Set serverConfigVersion to "N+1"
|
||||
// 3. Set serverConfig to serverConfigVN+1
|
||||
// 4. Add new migration function (ex. func migrateVNToVN+1()) in config-migrate.go
|
||||
// 5. Call migrateVNToVN+1() from migrateConfig() in config-migrate.go
|
||||
// 6. Make changes in config-current_test.go for any test change
|
||||
|
||||
// Config version
|
||||
const v20 = "20"
|
||||
const serverConfigVersion = "22"
|
||||
|
||||
type serverConfig = serverConfigV22
|
||||
|
||||
var (
|
||||
// serverConfig server config.
|
||||
serverConfig *serverConfigV20
|
||||
serverConfigMu sync.RWMutex
|
||||
// globalServerConfig server config.
|
||||
globalServerConfig *serverConfig
|
||||
globalServerConfigMu sync.RWMutex
|
||||
)
|
||||
|
||||
// serverConfigV20 server configuration version '20' which is like
|
||||
// version '19' except it adds support for VirtualHostDomain
|
||||
type serverConfigV20 struct {
|
||||
sync.RWMutex
|
||||
Version string `json:"version"`
|
||||
|
||||
// S3 API configuration.
|
||||
Credential auth.Credentials `json:"credential"`
|
||||
Region string `json:"region"`
|
||||
Browser BrowserFlag `json:"browser"`
|
||||
Domain string `json:"domain"`
|
||||
|
||||
// Additional error logging configuration.
|
||||
Logger *loggers `json:"logger"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify *notifier `json:"notify"`
|
||||
}
|
||||
|
||||
// GetVersion get current config version.
|
||||
func (s *serverConfigV20) GetVersion() string {
|
||||
func (s *serverConfig) GetVersion() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
@@ -64,7 +56,7 @@ func (s *serverConfigV20) GetVersion() string {
|
||||
}
|
||||
|
||||
// SetRegion set a new region.
|
||||
func (s *serverConfigV20) SetRegion(region string) {
|
||||
func (s *serverConfig) SetRegion(region string) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
@@ -73,15 +65,15 @@ func (s *serverConfigV20) SetRegion(region string) {
|
||||
}
|
||||
|
||||
// GetRegion get current region.
|
||||
func (s *serverConfigV20) GetRegion() string {
|
||||
func (s *serverConfig) GetRegion() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.Region
|
||||
}
|
||||
|
||||
// SetCredentials set new credentials. SetCredential returns the previous credential.
|
||||
func (s *serverConfigV20) SetCredential(creds auth.Credentials) (prevCred auth.Credentials) {
|
||||
// SetCredential sets new credential and returns the previous credential.
|
||||
func (s *serverConfig) SetCredential(creds auth.Credentials) (prevCred auth.Credentials) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
@@ -96,7 +88,7 @@ func (s *serverConfigV20) SetCredential(creds auth.Credentials) (prevCred auth.C
|
||||
}
|
||||
|
||||
// GetCredentials get current credentials.
|
||||
func (s *serverConfigV20) GetCredential() auth.Credentials {
|
||||
func (s *serverConfig) GetCredential() auth.Credentials {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
@@ -104,7 +96,7 @@ func (s *serverConfigV20) GetCredential() auth.Credentials {
|
||||
}
|
||||
|
||||
// SetBrowser set if browser is enabled.
|
||||
func (s *serverConfigV20) SetBrowser(b bool) {
|
||||
func (s *serverConfig) SetBrowser(b bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
@@ -112,8 +104,54 @@ func (s *serverConfigV20) SetBrowser(b bool) {
|
||||
s.Browser = BrowserFlag(b)
|
||||
}
|
||||
|
||||
func (s *serverConfig) SetStorageClass(standardClass, rrsClass storageClass) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// Set the values
|
||||
s.StorageClass.Standard = standardClass.Scheme + strconv.Itoa(standardClass.Parity)
|
||||
s.StorageClass.RRS = rrsClass.Scheme + strconv.Itoa(rrsClass.Parity)
|
||||
}
|
||||
|
||||
func (s *serverConfig) GetStorageClass() (standardStorageClass, rrsStorageClass storageClass) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
var err error
|
||||
var ssc storageClass
|
||||
var rrsc storageClass
|
||||
|
||||
if s.StorageClass.Standard != "" {
|
||||
// Parse the values read from config file into storageClass struct
|
||||
ssc, err = parseStorageClass(s.StorageClass.Standard)
|
||||
fatalIf(err, "Invalid value %s set in config.json", s.StorageClass.Standard)
|
||||
}
|
||||
|
||||
if s.StorageClass.RRS != "" {
|
||||
// Parse the values read from config file into storageClass struct
|
||||
rrsc, err = parseStorageClass(s.StorageClass.RRS)
|
||||
fatalIf(err, "Invalid value %s set in config.json", s.StorageClass.RRS)
|
||||
}
|
||||
|
||||
// Validation is done after parsing both the storage classes. This is needed because we need one
|
||||
// storage class value to deduce the correct value of the other storage class.
|
||||
if rrsc.Scheme != "" {
|
||||
err := validateRRSParity(rrsc.Parity, ssc.Parity)
|
||||
fatalIf(err, "Invalid value %s set in config.json", s.StorageClass.RRS)
|
||||
globalIsStorageClass = true
|
||||
}
|
||||
|
||||
if ssc.Scheme != "" {
|
||||
err := validateSSParity(ssc.Parity, rrsc.Parity)
|
||||
fatalIf(err, "Invalid value %s set in config.json", s.StorageClass.Standard)
|
||||
globalIsStorageClass = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetCredentials get current credentials.
|
||||
func (s *serverConfigV20) GetBrowser() bool {
|
||||
func (s *serverConfig) GetBrowser() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
@@ -121,7 +159,7 @@ func (s *serverConfigV20) GetBrowser() bool {
|
||||
}
|
||||
|
||||
// Save config.
|
||||
func (s *serverConfigV20) Save() error {
|
||||
func (s *serverConfig) Save() error {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
@@ -129,19 +167,15 @@ func (s *serverConfigV20) Save() error {
|
||||
return quick.Save(getConfigFile(), s)
|
||||
}
|
||||
|
||||
func newServerConfigV20() *serverConfigV20 {
|
||||
srvCfg := &serverConfigV20{
|
||||
Version: v20,
|
||||
func newServerConfig() *serverConfig {
|
||||
srvCfg := &serverConfig{
|
||||
Version: serverConfigVersion,
|
||||
Credential: auth.MustGetNewCredentials(),
|
||||
Region: globalMinioDefaultRegion,
|
||||
Browser: true,
|
||||
Logger: &loggers{},
|
||||
Notify: ¬ifier{},
|
||||
}
|
||||
|
||||
// Enable console logger by default on a fresh run.
|
||||
srvCfg.Logger.Console = NewConsoleLogger()
|
||||
|
||||
// Make sure to initialize notification configs.
|
||||
srvCfg.Notify.AMQP = make(map[string]amqpNotify)
|
||||
srvCfg.Notify.AMQP["1"] = amqpNotify{}
|
||||
@@ -169,7 +203,7 @@ func newServerConfigV20() *serverConfigV20 {
|
||||
// found, otherwise use default parameters
|
||||
func newConfig() error {
|
||||
// Initialize server config.
|
||||
srvCfg := newServerConfigV20()
|
||||
srvCfg := newServerConfig()
|
||||
|
||||
// If env is set override the credentials from config file.
|
||||
if globalIsEnvCreds {
|
||||
@@ -188,15 +222,19 @@ func newConfig() error {
|
||||
srvCfg.Domain = globalDomainName
|
||||
}
|
||||
|
||||
if globalIsStorageClass {
|
||||
srvCfg.SetStorageClass(globalStandardStorageClass, globalRRStorageClass)
|
||||
}
|
||||
|
||||
// hold the mutex lock before a new config is assigned.
|
||||
// Save the new config globally.
|
||||
// unlock the mutex.
|
||||
serverConfigMu.Lock()
|
||||
serverConfig = srvCfg
|
||||
serverConfigMu.Unlock()
|
||||
globalServerConfigMu.Lock()
|
||||
globalServerConfig = srvCfg
|
||||
globalServerConfigMu.Unlock()
|
||||
|
||||
// Save config into file.
|
||||
return serverConfig.Save()
|
||||
return globalServerConfig.Save()
|
||||
}
|
||||
|
||||
// doCheckDupJSONKeys recursively detects duplicate json keys
|
||||
@@ -251,8 +289,8 @@ func checkDupJSONKeys(json string) error {
|
||||
}
|
||||
|
||||
// getValidConfig - returns valid server configuration
|
||||
func getValidConfig() (*serverConfigV20, error) {
|
||||
srvCfg := &serverConfigV20{
|
||||
func getValidConfig() (*serverConfig, error) {
|
||||
srvCfg := &serverConfig{
|
||||
Region: globalMinioDefaultRegion,
|
||||
Browser: true,
|
||||
}
|
||||
@@ -262,8 +300,8 @@ func getValidConfig() (*serverConfigV20, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if srvCfg.Version != v20 {
|
||||
return nil, fmt.Errorf("configuration version mismatch. Expected: ‘%s’, Got: ‘%s’", v20, srvCfg.Version)
|
||||
if srvCfg.Version != serverConfigVersion {
|
||||
return nil, fmt.Errorf("configuration version mismatch. Expected: ‘%s’, Got: ‘%s’", serverConfigVersion, srvCfg.Version)
|
||||
}
|
||||
|
||||
// Load config file json and check for duplication json keys
|
||||
@@ -283,11 +321,6 @@ func getValidConfig() (*serverConfigV20, error) {
|
||||
return nil, errors.New("invalid credential in config file " + configFile)
|
||||
}
|
||||
|
||||
// Validate logger field
|
||||
if err = srvCfg.Logger.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate notify field
|
||||
if err = srvCfg.Notify.Validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -321,22 +354,29 @@ func loadConfig() error {
|
||||
srvCfg.Domain = globalDomainName
|
||||
}
|
||||
|
||||
if globalIsStorageClass {
|
||||
srvCfg.SetStorageClass(globalStandardStorageClass, globalRRStorageClass)
|
||||
}
|
||||
|
||||
// hold the mutex lock before a new config is assigned.
|
||||
serverConfigMu.Lock()
|
||||
serverConfig = srvCfg
|
||||
globalServerConfigMu.Lock()
|
||||
globalServerConfig = srvCfg
|
||||
if !globalIsEnvCreds {
|
||||
globalActiveCred = serverConfig.GetCredential()
|
||||
globalActiveCred = globalServerConfig.GetCredential()
|
||||
}
|
||||
if !globalIsEnvBrowser {
|
||||
globalIsBrowserEnabled = serverConfig.GetBrowser()
|
||||
globalIsBrowserEnabled = globalServerConfig.GetBrowser()
|
||||
}
|
||||
if !globalIsEnvRegion {
|
||||
globalServerRegion = serverConfig.GetRegion()
|
||||
globalServerRegion = globalServerConfig.GetRegion()
|
||||
}
|
||||
if !globalIsEnvDomainName {
|
||||
globalDomainName = serverConfig.Domain
|
||||
globalDomainName = globalServerConfig.Domain
|
||||
}
|
||||
serverConfigMu.Unlock()
|
||||
if !globalIsStorageClass {
|
||||
globalStandardStorageClass, globalRRStorageClass = globalServerConfig.GetStorageClass()
|
||||
}
|
||||
globalServerConfigMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -34,95 +34,72 @@ func TestServerConfig(t *testing.T) {
|
||||
// remove the root directory after the test ends.
|
||||
defer os.RemoveAll(rootPath)
|
||||
|
||||
if serverConfig.GetRegion() != globalMinioDefaultRegion {
|
||||
t.Errorf("Expecting region `us-east-1` found %s", serverConfig.GetRegion())
|
||||
if globalServerConfig.GetRegion() != globalMinioDefaultRegion {
|
||||
t.Errorf("Expecting region `us-east-1` found %s", globalServerConfig.GetRegion())
|
||||
}
|
||||
|
||||
// Set new region and verify.
|
||||
serverConfig.SetRegion("us-west-1")
|
||||
if serverConfig.GetRegion() != "us-west-1" {
|
||||
t.Errorf("Expecting region `us-west-1` found %s", serverConfig.GetRegion())
|
||||
globalServerConfig.SetRegion("us-west-1")
|
||||
if globalServerConfig.GetRegion() != "us-west-1" {
|
||||
t.Errorf("Expecting region `us-west-1` found %s", globalServerConfig.GetRegion())
|
||||
}
|
||||
|
||||
// Set new amqp notification id.
|
||||
serverConfig.Notify.SetAMQPByID("2", amqpNotify{})
|
||||
savedNotifyCfg1 := serverConfig.Notify.GetAMQPByID("2")
|
||||
globalServerConfig.Notify.SetAMQPByID("2", amqpNotify{})
|
||||
savedNotifyCfg1 := globalServerConfig.Notify.GetAMQPByID("2")
|
||||
if !reflect.DeepEqual(savedNotifyCfg1, amqpNotify{}) {
|
||||
t.Errorf("Expecting AMQP config %#v found %#v", amqpNotify{}, savedNotifyCfg1)
|
||||
}
|
||||
|
||||
// Set new elastic search notification id.
|
||||
serverConfig.Notify.SetElasticSearchByID("2", elasticSearchNotify{})
|
||||
savedNotifyCfg2 := serverConfig.Notify.GetElasticSearchByID("2")
|
||||
globalServerConfig.Notify.SetElasticSearchByID("2", elasticSearchNotify{})
|
||||
savedNotifyCfg2 := globalServerConfig.Notify.GetElasticSearchByID("2")
|
||||
if !reflect.DeepEqual(savedNotifyCfg2, elasticSearchNotify{}) {
|
||||
t.Errorf("Expecting Elasticsearch config %#v found %#v", elasticSearchNotify{}, savedNotifyCfg2)
|
||||
}
|
||||
|
||||
// Set new redis notification id.
|
||||
serverConfig.Notify.SetRedisByID("2", redisNotify{})
|
||||
savedNotifyCfg3 := serverConfig.Notify.GetRedisByID("2")
|
||||
globalServerConfig.Notify.SetRedisByID("2", redisNotify{})
|
||||
savedNotifyCfg3 := globalServerConfig.Notify.GetRedisByID("2")
|
||||
if !reflect.DeepEqual(savedNotifyCfg3, redisNotify{}) {
|
||||
t.Errorf("Expecting Redis config %#v found %#v", redisNotify{}, savedNotifyCfg3)
|
||||
}
|
||||
|
||||
// Set new kafka notification id.
|
||||
serverConfig.Notify.SetKafkaByID("2", kafkaNotify{})
|
||||
savedNotifyCfg4 := serverConfig.Notify.GetKafkaByID("2")
|
||||
globalServerConfig.Notify.SetKafkaByID("2", kafkaNotify{})
|
||||
savedNotifyCfg4 := globalServerConfig.Notify.GetKafkaByID("2")
|
||||
if !reflect.DeepEqual(savedNotifyCfg4, kafkaNotify{}) {
|
||||
t.Errorf("Expecting Kafka config %#v found %#v", kafkaNotify{}, savedNotifyCfg4)
|
||||
}
|
||||
|
||||
// Set new Webhook notification id.
|
||||
serverConfig.Notify.SetWebhookByID("2", webhookNotify{})
|
||||
savedNotifyCfg5 := serverConfig.Notify.GetWebhookByID("2")
|
||||
globalServerConfig.Notify.SetWebhookByID("2", webhookNotify{})
|
||||
savedNotifyCfg5 := globalServerConfig.Notify.GetWebhookByID("2")
|
||||
if !reflect.DeepEqual(savedNotifyCfg5, webhookNotify{}) {
|
||||
t.Errorf("Expecting Webhook config %#v found %#v", webhookNotify{}, savedNotifyCfg5)
|
||||
}
|
||||
|
||||
// Set new console logger.
|
||||
// Set new MySQL notification id.
|
||||
serverConfig.Notify.SetMySQLByID("2", mySQLNotify{})
|
||||
savedNotifyCfg6 := serverConfig.Notify.GetMySQLByID("2")
|
||||
globalServerConfig.Notify.SetMySQLByID("2", mySQLNotify{})
|
||||
savedNotifyCfg6 := globalServerConfig.Notify.GetMySQLByID("2")
|
||||
if !reflect.DeepEqual(savedNotifyCfg6, mySQLNotify{}) {
|
||||
t.Errorf("Expecting Webhook config %#v found %#v", mySQLNotify{}, savedNotifyCfg6)
|
||||
}
|
||||
|
||||
// Set new console logger.
|
||||
// Set new MQTT notification id.
|
||||
serverConfig.Notify.SetMQTTByID("2", mqttNotify{})
|
||||
savedNotifyCfg7 := serverConfig.Notify.GetMQTTByID("2")
|
||||
globalServerConfig.Notify.SetMQTTByID("2", mqttNotify{})
|
||||
savedNotifyCfg7 := globalServerConfig.Notify.GetMQTTByID("2")
|
||||
if !reflect.DeepEqual(savedNotifyCfg7, mqttNotify{}) {
|
||||
t.Errorf("Expecting Webhook config %#v found %#v", mqttNotify{}, savedNotifyCfg7)
|
||||
}
|
||||
|
||||
consoleLogger := NewConsoleLogger()
|
||||
serverConfig.Logger.SetConsole(consoleLogger)
|
||||
consoleCfg := serverConfig.Logger.GetConsole()
|
||||
if !reflect.DeepEqual(consoleCfg, consoleLogger) {
|
||||
t.Errorf("Expecting console logger config %#v found %#v", consoleLogger, consoleCfg)
|
||||
}
|
||||
// Set new console logger.
|
||||
consoleLogger.Enable = false
|
||||
serverConfig.Logger.SetConsole(consoleLogger)
|
||||
|
||||
// Set new file logger.
|
||||
fileLogger := NewFileLogger("test-log-file")
|
||||
serverConfig.Logger.SetFile(fileLogger)
|
||||
fileCfg := serverConfig.Logger.GetFile()
|
||||
if !reflect.DeepEqual(fileCfg, fileLogger) {
|
||||
t.Errorf("Expecting file logger config %#v found %#v", fileLogger, fileCfg)
|
||||
}
|
||||
// Set new file logger.
|
||||
fileLogger.Enable = false
|
||||
serverConfig.Logger.SetFile(fileLogger)
|
||||
|
||||
// Match version.
|
||||
if serverConfig.GetVersion() != v20 {
|
||||
t.Errorf("Expecting version %s found %s", serverConfig.GetVersion(), v20)
|
||||
if globalServerConfig.GetVersion() != serverConfigVersion {
|
||||
t.Errorf("Expecting version %s found %s", globalServerConfig.GetVersion(), serverConfigVersion)
|
||||
}
|
||||
|
||||
// Attempt to save.
|
||||
if err := serverConfig.Save(); err != nil {
|
||||
if err := globalServerConfig.Save(); err != nil {
|
||||
t.Fatalf("Unable to save updated config file %s", err)
|
||||
}
|
||||
|
||||
@@ -172,17 +149,17 @@ func TestServerConfigWithEnvs(t *testing.T) {
|
||||
defer os.RemoveAll(rootPath)
|
||||
|
||||
// Check if serverConfig has
|
||||
if serverConfig.GetBrowser() {
|
||||
t.Errorf("Expecting browser is set to false found %v", serverConfig.GetBrowser())
|
||||
if globalServerConfig.GetBrowser() {
|
||||
t.Errorf("Expecting browser is set to false found %v", globalServerConfig.GetBrowser())
|
||||
}
|
||||
|
||||
// Check if serverConfig has
|
||||
if serverConfig.GetRegion() != "us-west-1" {
|
||||
t.Errorf("Expecting region to be \"us-west-1\" found %v", serverConfig.GetRegion())
|
||||
if globalServerConfig.GetRegion() != "us-west-1" {
|
||||
t.Errorf("Expecting region to be \"us-west-1\" found %v", globalServerConfig.GetRegion())
|
||||
}
|
||||
|
||||
// Check if serverConfig has
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
|
||||
if cred.AccessKey != "minio" {
|
||||
t.Errorf("Expecting access key to be `minio` found %s", cred.AccessKey)
|
||||
@@ -192,8 +169,8 @@ func TestServerConfigWithEnvs(t *testing.T) {
|
||||
t.Errorf("Expecting access key to be `minio123` found %s", cred.SecretKey)
|
||||
}
|
||||
|
||||
if serverConfig.Domain != "domain.com" {
|
||||
t.Errorf("Expecting Domain to be `domain.com` found " + serverConfig.Domain)
|
||||
if globalServerConfig.Domain != "domain.com" {
|
||||
t.Errorf("Expecting Domain to be `domain.com` found " + globalServerConfig.Domain)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +214,7 @@ func TestValidateConfig(t *testing.T) {
|
||||
|
||||
configPath := filepath.Join(rootPath, minioConfigFile)
|
||||
|
||||
v := v20
|
||||
v := serverConfigVersion
|
||||
|
||||
testCases := []struct {
|
||||
configData string
|
||||
@@ -273,58 +250,55 @@ func TestValidateConfig(t *testing.T) {
|
||||
// Test 10 - duplicated json keys
|
||||
{`{"version": "` + v + `", "browser": "on", "browser": "on", "region":"us-east-1", "credential" : {"accessKey":"minio", "secretKey":"minio123"}}`, false},
|
||||
|
||||
// Test 11 - empty filename field in File
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "logger": { "file": { "enable": true, "filename": "" } }}`, false},
|
||||
|
||||
// Test 12 - Test AMQP
|
||||
// Test 11 - Test AMQP
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "amqp": { "1": { "enable": true, "url": "", "exchange": "", "routingKey": "", "exchangeType": "", "mandatory": false, "immediate": false, "durable": false, "internal": false, "noWait": false, "autoDeleted": false }}}}`, false},
|
||||
|
||||
// Test 13 - Test NATS
|
||||
// Test 12 - Test NATS
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "nats": { "1": { "enable": true, "address": "", "subject": "", "username": "", "password": "", "token": "", "secure": false, "pingInterval": 0, "streaming": { "enable": false, "clusterID": "", "clientID": "", "async": false, "maxPubAcksInflight": 0 } } }}}`, false},
|
||||
|
||||
// Test 14 - Test ElasticSearch
|
||||
// Test 13 - Test ElasticSearch
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "url": "", "index": "" } }}}`, false},
|
||||
|
||||
// Test 15 - Test Redis
|
||||
// Test 14 - Test Redis
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "redis": { "1": { "enable": true, "address": "", "password": "", "key": "" } }}}`, false},
|
||||
|
||||
// Test 16 - Test PostgreSQL
|
||||
// Test 15 - Test PostgreSQL
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "postgresql": { "1": { "enable": true, "connectionString": "", "table": "", "host": "", "port": "", "user": "", "password": "", "database": "" }}}}`, false},
|
||||
|
||||
// Test 17 - Test Kafka
|
||||
// Test 16 - Test Kafka
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "kafka": { "1": { "enable": true, "brokers": null, "topic": "" } }}}`, false},
|
||||
|
||||
// Test 18 - Test Webhook
|
||||
// Test 17 - Test Webhook
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "webhook": { "1": { "enable": true, "endpoint": "" } }}}`, false},
|
||||
|
||||
// Test 20 - Test MySQL
|
||||
// Test 18 - Test MySQL
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mysql": { "1": { "enable": true, "dsnString": "", "table": "", "host": "", "port": "", "user": "", "password": "", "database": "" }}}}`, false},
|
||||
|
||||
// Test 21 - Test Format for MySQL
|
||||
// Test 19 - Test Format for MySQL
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mysql": { "1": { "enable": true, "dsnString": "", "format": "invalid", "table": "xxx", "host": "10.0.0.1", "port": "3306", "user": "abc", "password": "pqr", "database": "test1" }}}}`, false},
|
||||
|
||||
// Test 22 - Test valid Format for MySQL
|
||||
// Test 20 - Test valid Format for MySQL
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mysql": { "1": { "enable": true, "dsnString": "", "format": "namespace", "table": "xxx", "host": "10.0.0.1", "port": "3306", "user": "abc", "password": "pqr", "database": "test1" }}}}`, true},
|
||||
|
||||
// Test 23 - Test Format for PostgreSQL
|
||||
// Test 21 - Test Format for PostgreSQL
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "postgresql": { "1": { "enable": true, "connectionString": "", "format": "invalid", "table": "xxx", "host": "myhost", "port": "5432", "user": "abc", "password": "pqr", "database": "test1" }}}}`, false},
|
||||
|
||||
// Test 24 - Test valid Format for PostgreSQL
|
||||
// Test 22 - Test valid Format for PostgreSQL
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "postgresql": { "1": { "enable": true, "connectionString": "", "format": "namespace", "table": "xxx", "host": "myhost", "port": "5432", "user": "abc", "password": "pqr", "database": "test1" }}}}`, true},
|
||||
|
||||
// Test 25 - Test Format for ElasticSearch
|
||||
// Test 23 - Test Format for ElasticSearch
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "invalid", "url": "example.com", "index": "myindex" } }}}`, false},
|
||||
|
||||
// Test 26 - Test valid Format for ElasticSearch
|
||||
// Test 24 - Test valid Format for ElasticSearch
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "elasticsearch": { "1": { "enable": true, "format": "namespace", "url": "example.com", "index": "myindex" } }}}`, true},
|
||||
|
||||
// Test 27 - Test Format for Redis
|
||||
// Test 25 - Test Format for Redis
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "redis": { "1": { "enable": true, "format": "invalid", "address": "example.com:80", "password": "xxx", "key": "key1" } }}}`, false},
|
||||
|
||||
// Test 28 - Test valid Format for Redis
|
||||
// Test 26 - Test valid Format for Redis
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "redis": { "1": { "enable": true, "format": "namespace", "address": "example.com:80", "password": "xxx", "key": "key1" } }}}`, true},
|
||||
|
||||
// Test 29 - Test MQTT
|
||||
// Test 27 - Test MQTT
|
||||
{`{"version": "` + v + `", "credential": { "accessKey": "minio", "secretKey": "minio123" }, "region": "us-east-1", "browser": "on", "notify": { "mqtt": { "1": { "enable": true, "broker": "", "topic": "", "qos": 0, "clientId": "", "username": "", "password": ""}}}}`, false},
|
||||
}
|
||||
|
||||
@@ -337,7 +311,7 @@ func TestValidateConfig(t *testing.T) {
|
||||
t.Errorf("Test %d, should pass but it failed with err = %v", i+1, verr)
|
||||
}
|
||||
if !testCase.shouldPass && verr == nil {
|
||||
t.Errorf("Test %d, should fail but it succeed.", i+1)
|
||||
t.Errorf("Test %d, should fail but it succeeded.", i+1)
|
||||
}
|
||||
}
|
||||
|
||||
+224
-1
@@ -143,7 +143,7 @@ func migrateConfig() error {
|
||||
}
|
||||
fallthrough
|
||||
case "18":
|
||||
// Migrate version '17' to '18'.
|
||||
// Migrate version '18' to '19'.
|
||||
if err = migrateV18ToV19(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -154,6 +154,15 @@ func migrateConfig() error {
|
||||
}
|
||||
fallthrough
|
||||
case "20":
|
||||
if err = migrateV20ToV21(); err != nil {
|
||||
return err
|
||||
}
|
||||
fallthrough
|
||||
case "21":
|
||||
if err = migrateV21ToV22(); err != nil {
|
||||
return err
|
||||
}
|
||||
case serverConfigVersion:
|
||||
// No migration needed. this always points to current version.
|
||||
err = nil
|
||||
}
|
||||
@@ -1592,3 +1601,217 @@ func migrateV19ToV20() error {
|
||||
log.Printf(configMigrateMSGTemplate, configFile, cv19.Version, srvConfig.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateV20ToV21() error {
|
||||
configFile := getConfigFile()
|
||||
|
||||
cv20 := &serverConfigV20{}
|
||||
_, err := quick.Load(configFile, cv20)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("Unable to load config version ‘20’. %v", err)
|
||||
}
|
||||
if cv20.Version != "20" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy over fields from V20 into V21 config struct
|
||||
srvConfig := &serverConfigV21{
|
||||
Notify: ¬ifier{},
|
||||
}
|
||||
srvConfig.Version = "21"
|
||||
srvConfig.Credential = cv20.Credential
|
||||
srvConfig.Region = cv20.Region
|
||||
if srvConfig.Region == "" {
|
||||
// Region needs to be set for AWS Signature Version 4.
|
||||
srvConfig.Region = globalMinioDefaultRegion
|
||||
}
|
||||
|
||||
// check and set notifiers config
|
||||
if len(cv20.Notify.AMQP) == 0 {
|
||||
srvConfig.Notify.AMQP = make(map[string]amqpNotify)
|
||||
srvConfig.Notify.AMQP["1"] = amqpNotify{}
|
||||
} else {
|
||||
// New deliveryMode parameter is added for AMQP,
|
||||
// default value is already 0, so nothing to
|
||||
// explicitly migrate here.
|
||||
srvConfig.Notify.AMQP = cv20.Notify.AMQP
|
||||
}
|
||||
if len(cv20.Notify.ElasticSearch) == 0 {
|
||||
srvConfig.Notify.ElasticSearch = make(map[string]elasticSearchNotify)
|
||||
srvConfig.Notify.ElasticSearch["1"] = elasticSearchNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.ElasticSearch = cv20.Notify.ElasticSearch
|
||||
}
|
||||
if len(cv20.Notify.Redis) == 0 {
|
||||
srvConfig.Notify.Redis = make(map[string]redisNotify)
|
||||
srvConfig.Notify.Redis["1"] = redisNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.Redis = cv20.Notify.Redis
|
||||
}
|
||||
if len(cv20.Notify.PostgreSQL) == 0 {
|
||||
srvConfig.Notify.PostgreSQL = make(map[string]postgreSQLNotify)
|
||||
srvConfig.Notify.PostgreSQL["1"] = postgreSQLNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.PostgreSQL = cv20.Notify.PostgreSQL
|
||||
}
|
||||
if len(cv20.Notify.Kafka) == 0 {
|
||||
srvConfig.Notify.Kafka = make(map[string]kafkaNotify)
|
||||
srvConfig.Notify.Kafka["1"] = kafkaNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.Kafka = cv20.Notify.Kafka
|
||||
}
|
||||
if len(cv20.Notify.NATS) == 0 {
|
||||
srvConfig.Notify.NATS = make(map[string]natsNotify)
|
||||
srvConfig.Notify.NATS["1"] = natsNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.NATS = cv20.Notify.NATS
|
||||
}
|
||||
if len(cv20.Notify.Webhook) == 0 {
|
||||
srvConfig.Notify.Webhook = make(map[string]webhookNotify)
|
||||
srvConfig.Notify.Webhook["1"] = webhookNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.Webhook = cv20.Notify.Webhook
|
||||
}
|
||||
if len(cv20.Notify.MySQL) == 0 {
|
||||
srvConfig.Notify.MySQL = make(map[string]mySQLNotify)
|
||||
srvConfig.Notify.MySQL["1"] = mySQLNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.MySQL = cv20.Notify.MySQL
|
||||
}
|
||||
if len(cv20.Notify.MQTT) == 0 {
|
||||
srvConfig.Notify.MQTT = make(map[string]mqttNotify)
|
||||
srvConfig.Notify.MQTT["1"] = mqttNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.MQTT = cv20.Notify.MQTT
|
||||
}
|
||||
|
||||
// Load browser config from existing config in the file.
|
||||
srvConfig.Browser = cv20.Browser
|
||||
|
||||
// Load domain config from existing config in the file.
|
||||
srvConfig.Domain = cv20.Domain
|
||||
|
||||
if err = quick.Save(configFile, srvConfig); err != nil {
|
||||
return fmt.Errorf("Failed to migrate config from ‘%s’ to ‘%s’. %v", cv20.Version, srvConfig.Version, err)
|
||||
}
|
||||
|
||||
log.Printf(configMigrateMSGTemplate, configFile, cv20.Version, srvConfig.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateV21ToV22() error {
|
||||
configFile := getConfigFile()
|
||||
|
||||
cv21 := &serverConfigV21{}
|
||||
_, err := quick.Load(configFile, cv21)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("Unable to load config version ‘21’. %v", err)
|
||||
}
|
||||
if cv21.Version != "21" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy over fields from V21 into V22 config struct
|
||||
srvConfig := &serverConfigV22{
|
||||
Notify: ¬ifier{},
|
||||
}
|
||||
srvConfig.Version = serverConfigVersion
|
||||
srvConfig.Credential = cv21.Credential
|
||||
srvConfig.Region = cv21.Region
|
||||
if srvConfig.Region == "" {
|
||||
// Region needs to be set for AWS Signature Version 4.
|
||||
srvConfig.Region = globalMinioDefaultRegion
|
||||
}
|
||||
|
||||
// check and set notifiers config
|
||||
if len(cv21.Notify.AMQP) == 0 {
|
||||
srvConfig.Notify.AMQP = make(map[string]amqpNotify)
|
||||
srvConfig.Notify.AMQP["1"] = amqpNotify{}
|
||||
} else {
|
||||
// New deliveryMode parameter is added for AMQP,
|
||||
// default value is already 0, so nothing to
|
||||
// explicitly migrate here.
|
||||
srvConfig.Notify.AMQP = cv21.Notify.AMQP
|
||||
}
|
||||
if len(cv21.Notify.ElasticSearch) == 0 {
|
||||
srvConfig.Notify.ElasticSearch = make(map[string]elasticSearchNotify)
|
||||
srvConfig.Notify.ElasticSearch["1"] = elasticSearchNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.ElasticSearch = cv21.Notify.ElasticSearch
|
||||
}
|
||||
if len(cv21.Notify.Redis) == 0 {
|
||||
srvConfig.Notify.Redis = make(map[string]redisNotify)
|
||||
srvConfig.Notify.Redis["1"] = redisNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.Redis = cv21.Notify.Redis
|
||||
}
|
||||
if len(cv21.Notify.PostgreSQL) == 0 {
|
||||
srvConfig.Notify.PostgreSQL = make(map[string]postgreSQLNotify)
|
||||
srvConfig.Notify.PostgreSQL["1"] = postgreSQLNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.PostgreSQL = cv21.Notify.PostgreSQL
|
||||
}
|
||||
if len(cv21.Notify.Kafka) == 0 {
|
||||
srvConfig.Notify.Kafka = make(map[string]kafkaNotify)
|
||||
srvConfig.Notify.Kafka["1"] = kafkaNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.Kafka = cv21.Notify.Kafka
|
||||
}
|
||||
if len(cv21.Notify.NATS) == 0 {
|
||||
srvConfig.Notify.NATS = make(map[string]natsNotify)
|
||||
srvConfig.Notify.NATS["1"] = natsNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.NATS = cv21.Notify.NATS
|
||||
}
|
||||
if len(cv21.Notify.Webhook) == 0 {
|
||||
srvConfig.Notify.Webhook = make(map[string]webhookNotify)
|
||||
srvConfig.Notify.Webhook["1"] = webhookNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.Webhook = cv21.Notify.Webhook
|
||||
}
|
||||
if len(cv21.Notify.MySQL) == 0 {
|
||||
srvConfig.Notify.MySQL = make(map[string]mySQLNotify)
|
||||
srvConfig.Notify.MySQL["1"] = mySQLNotify{
|
||||
Format: formatNamespace,
|
||||
}
|
||||
} else {
|
||||
srvConfig.Notify.MySQL = cv21.Notify.MySQL
|
||||
}
|
||||
if len(cv21.Notify.MQTT) == 0 {
|
||||
srvConfig.Notify.MQTT = make(map[string]mqttNotify)
|
||||
srvConfig.Notify.MQTT["1"] = mqttNotify{}
|
||||
} else {
|
||||
srvConfig.Notify.MQTT = cv21.Notify.MQTT
|
||||
}
|
||||
|
||||
// Load browser config from existing config in the file.
|
||||
srvConfig.Browser = cv21.Browser
|
||||
|
||||
// Load domain config from existing config in the file.
|
||||
srvConfig.Domain = cv21.Domain
|
||||
|
||||
if err = quick.Save(configFile, srvConfig); err != nil {
|
||||
return fmt.Errorf("Failed to migrate config from ‘%s’ to ‘%s’. %v", cv21.Version, srvConfig.Version, err)
|
||||
}
|
||||
|
||||
log.Printf(configMigrateMSGTemplate, configFile, cv21.Version, srvConfig.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
+21
-10
@@ -125,11 +125,16 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) {
|
||||
if err := migrateV18ToV19(); err != nil {
|
||||
t.Fatal("migrate v18 to v19 should succeed when no config file is found")
|
||||
}
|
||||
|
||||
if err := migrateV19ToV20(); err != nil {
|
||||
t.Fatal("migrate v19 to v20 should succeed when no config file is found")
|
||||
}
|
||||
if err := migrateV20ToV21(); err != nil {
|
||||
t.Fatal("migrate v20 to v21 should succeed when no config file is found")
|
||||
}
|
||||
}
|
||||
|
||||
// Test if a config migration from v2 to v19 is successfully done
|
||||
func TestServerConfigMigrateV2toV19(t *testing.T) {
|
||||
// Test if a config migration from v2 to v21 is successfully done
|
||||
func TestServerConfigMigrateV2toV21(t *testing.T) {
|
||||
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
||||
if err != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
@@ -169,17 +174,17 @@ func TestServerConfigMigrateV2toV19(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check the version number in the upgraded config file
|
||||
expectedVersion := v20
|
||||
if serverConfig.Version != expectedVersion {
|
||||
t.Fatalf("Expect version "+expectedVersion+", found: %v", serverConfig.Version)
|
||||
expectedVersion := serverConfigVersion
|
||||
if globalServerConfig.Version != expectedVersion {
|
||||
t.Fatalf("Expect version "+expectedVersion+", found: %v", globalServerConfig.Version)
|
||||
}
|
||||
|
||||
// Check if accessKey and secretKey are not altered during migration
|
||||
if serverConfig.Credential.AccessKey != accessKey {
|
||||
t.Fatalf("Access key lost during migration, expected: %v, found:%v", accessKey, serverConfig.Credential.AccessKey)
|
||||
if globalServerConfig.Credential.AccessKey != accessKey {
|
||||
t.Fatalf("Access key lost during migration, expected: %v, found:%v", accessKey, globalServerConfig.Credential.AccessKey)
|
||||
}
|
||||
if serverConfig.Credential.SecretKey != secretKey {
|
||||
t.Fatalf("Secret key lost during migration, expected: %v, found: %v", secretKey, serverConfig.Credential.SecretKey)
|
||||
if globalServerConfig.Credential.SecretKey != secretKey {
|
||||
t.Fatalf("Secret key lost during migration, expected: %v, found: %v", secretKey, globalServerConfig.Credential.SecretKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +257,12 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) {
|
||||
if err := migrateV18ToV19(); err == nil {
|
||||
t.Fatal("migrateConfigV18ToV19() should fail with a corrupted json")
|
||||
}
|
||||
if err := migrateV19ToV20(); err == nil {
|
||||
t.Fatal("migrateConfigV19ToV20() should fail with a corrupted json")
|
||||
}
|
||||
if err := migrateV20ToV21(); err == nil {
|
||||
t.Fatal("migrateConfigV20ToV21() should fail with a corrupted json")
|
||||
}
|
||||
}
|
||||
|
||||
// Test if all migrate code returns error with corrupted config files
|
||||
|
||||
@@ -418,6 +418,12 @@ type serverConfigV15 struct {
|
||||
Notify *notifier `json:"notify"`
|
||||
}
|
||||
|
||||
type loggers struct {
|
||||
sync.RWMutex
|
||||
Console ConsoleLogger `json:"console"`
|
||||
File FileLogger `json:"file"`
|
||||
}
|
||||
|
||||
// serverConfigV16 server configuration version '16' which is like
|
||||
// version '15' except it makes a change to logging configuration.
|
||||
type serverConfigV16 struct {
|
||||
@@ -490,3 +496,56 @@ type serverConfigV19 struct {
|
||||
// Notification queue configuration.
|
||||
Notify *notifier `json:"notify"`
|
||||
}
|
||||
|
||||
// serverConfigV20 server configuration version '20' which is like
|
||||
// version '19' except it adds support for VirtualHostDomain
|
||||
type serverConfigV20 struct {
|
||||
sync.RWMutex
|
||||
Version string `json:"version"`
|
||||
|
||||
// S3 API configuration.
|
||||
Credential auth.Credentials `json:"credential"`
|
||||
Region string `json:"region"`
|
||||
Browser BrowserFlag `json:"browser"`
|
||||
Domain string `json:"domain"`
|
||||
|
||||
// Additional error logging configuration.
|
||||
Logger *loggers `json:"logger"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify *notifier `json:"notify"`
|
||||
}
|
||||
|
||||
// serverConfigV21 is just like version '20' without logger field
|
||||
type serverConfigV21 struct {
|
||||
sync.RWMutex
|
||||
Version string `json:"version"`
|
||||
|
||||
// S3 API configuration.
|
||||
Credential auth.Credentials `json:"credential"`
|
||||
Region string `json:"region"`
|
||||
Browser BrowserFlag `json:"browser"`
|
||||
Domain string `json:"domain"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify *notifier `json:"notify"`
|
||||
}
|
||||
|
||||
// serverConfigV22 is just like version '21' with added support
|
||||
// for StorageClass
|
||||
type serverConfigV22 struct {
|
||||
sync.RWMutex
|
||||
Version string `json:"version"`
|
||||
|
||||
// S3 API configuration.
|
||||
Credential auth.Credentials `json:"credential"`
|
||||
Region string `json:"region"`
|
||||
Browser BrowserFlag `json:"browser"`
|
||||
Domain string `json:"domain"`
|
||||
|
||||
// Storage class configuration
|
||||
StorageClass storageClassConfig `json:"storageclass"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify *notifier `json:"notify"`
|
||||
}
|
||||
+6
-24
@@ -65,18 +65,9 @@ func (endpoint Endpoint) Type() EndpointType {
|
||||
return URLEndpointType
|
||||
}
|
||||
|
||||
// SetHTTPS - sets secure http for URLEndpointType.
|
||||
func (endpoint Endpoint) SetHTTPS() {
|
||||
if endpoint.Host != "" {
|
||||
endpoint.Scheme = "https"
|
||||
}
|
||||
}
|
||||
|
||||
// SetHTTP - sets insecure http for URLEndpointType.
|
||||
func (endpoint Endpoint) SetHTTP() {
|
||||
if endpoint.Host != "" {
|
||||
endpoint.Scheme = "http"
|
||||
}
|
||||
// IsHTTPS - returns true if secure for URLEndpointType.
|
||||
func (endpoint Endpoint) IsHTTPS() bool {
|
||||
return endpoint.Scheme == "https"
|
||||
}
|
||||
|
||||
// NewEndpoint - returns new endpoint based on given arguments.
|
||||
@@ -190,18 +181,9 @@ func (endpoints EndpointList) Less(i, j int) bool {
|
||||
return endpoints[i].String() < endpoints[j].String()
|
||||
}
|
||||
|
||||
// SetHTTPS - sets secure http for URLEndpointType.
|
||||
func (endpoints EndpointList) SetHTTPS() {
|
||||
for i := range endpoints {
|
||||
endpoints[i].SetHTTPS()
|
||||
}
|
||||
}
|
||||
|
||||
// SetHTTP - sets insecure http for URLEndpointType.
|
||||
func (endpoints EndpointList) SetHTTP() {
|
||||
for i := range endpoints {
|
||||
endpoints[i].SetHTTP()
|
||||
}
|
||||
// IsHTTPS - returns true if secure for URLEndpointType.
|
||||
func (endpoints EndpointList) IsHTTPS() bool {
|
||||
return endpoints[0].IsHTTPS()
|
||||
}
|
||||
|
||||
// NewEndpointList - returns new endpoint list based on input args.
|
||||
|
||||
@@ -19,6 +19,8 @@ package cmd
|
||||
import (
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// CreateFile creates a new bitrot encoded file spread over all available disks. CreateFile will create
|
||||
@@ -26,14 +28,14 @@ import (
|
||||
// be used to protect the erasure encoded file.
|
||||
func (s *ErasureStorage) CreateFile(src io.Reader, volume, path string, buffer []byte, algorithm BitrotAlgorithm, writeQuorum int) (f ErasureFileInfo, err error) {
|
||||
if !algorithm.Available() {
|
||||
return f, traceError(errBitrotHashAlgoInvalid)
|
||||
return f, errors.Trace(errBitrotHashAlgoInvalid)
|
||||
}
|
||||
f.Checksums = make([][]byte, len(s.disks))
|
||||
hashers := make([]hash.Hash, len(s.disks))
|
||||
for i := range hashers {
|
||||
hashers[i] = algorithm.New()
|
||||
}
|
||||
errChans, errors := make([]chan error, len(s.disks)), make([]error, len(s.disks))
|
||||
errChans, errs := make([]chan error, len(s.disks)), make([]error, len(s.disks))
|
||||
for i := range errChans {
|
||||
errChans[i] = make(chan error, 1) // create buffered channel to let finished go-routines die early
|
||||
}
|
||||
@@ -53,19 +55,19 @@ func (s *ErasureStorage) CreateFile(src io.Reader, volume, path string, buffer [
|
||||
return f, err
|
||||
}
|
||||
} else {
|
||||
return f, traceError(err)
|
||||
return f, errors.Trace(err)
|
||||
}
|
||||
|
||||
for i := range errChans { // span workers
|
||||
go erasureAppendFile(s.disks[i], volume, path, hashers[i], blocks[i], errChans[i])
|
||||
}
|
||||
for i := range errChans { // what until all workers are finished
|
||||
errors[i] = <-errChans[i]
|
||||
errs[i] = <-errChans[i]
|
||||
}
|
||||
if err = reduceWriteQuorumErrs(errors, objectOpIgnoredErrs, writeQuorum); err != nil {
|
||||
if err = reduceWriteQuorumErrs(errs, objectOpIgnoredErrs, writeQuorum); err != nil {
|
||||
return f, err
|
||||
}
|
||||
s.disks = evalDisks(s.disks, errors)
|
||||
s.disks = evalDisks(s.disks, errs)
|
||||
f.Size += int64(n)
|
||||
}
|
||||
|
||||
@@ -83,7 +85,7 @@ func (s *ErasureStorage) CreateFile(src io.Reader, volume, path string, buffer [
|
||||
// the hash of the written data. It sends the write error (or nil) over the error channel.
|
||||
func erasureAppendFile(disk StorageAPI, volume, path string, hash hash.Hash, buf []byte, errChan chan<- error) {
|
||||
if disk == OfflineDisk {
|
||||
errChan <- traceError(errDiskNotFound)
|
||||
errChan <- errors.Trace(errDiskNotFound)
|
||||
return
|
||||
}
|
||||
err := disk.AppendFile(volume, path, buf)
|
||||
|
||||
+16
-8
@@ -20,6 +20,8 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// HealFile tries to reconstruct an erasure-coded file spread over all
|
||||
@@ -48,7 +50,7 @@ func (s ErasureStorage) HealFile(staleDisks []StorageAPI, volume, path string, b
|
||||
f ErasureFileInfo, err error) {
|
||||
|
||||
if !alg.Available() {
|
||||
return f, traceError(errBitrotHashAlgoInvalid)
|
||||
return f, errors.Trace(errBitrotHashAlgoInvalid)
|
||||
}
|
||||
|
||||
// Initialization
|
||||
@@ -76,12 +78,14 @@ func (s ErasureStorage) HealFile(staleDisks []StorageAPI, volume, path string, b
|
||||
blocks[i] = make([]byte, chunksize)
|
||||
}
|
||||
var chunkOffset, blockOffset int64
|
||||
for ; blockOffset < size; blockOffset += blocksize {
|
||||
|
||||
// The for loop below is entered when size == 0 and
|
||||
// blockOffset == 0 to allow for reconstructing empty files.
|
||||
for ; blockOffset == 0 || blockOffset < size; blockOffset += blocksize {
|
||||
// last iteration may have less than blocksize data
|
||||
// left, so chunksize needs to be recomputed.
|
||||
if size < blockOffset+blocksize {
|
||||
blocksize = size - blockOffset
|
||||
chunksize = getChunkSize(blocksize, s.dataBlocks)
|
||||
chunksize = getChunkSize(size-blockOffset, s.dataBlocks)
|
||||
for i := range blocks {
|
||||
blocks[i] = blocks[i][:chunksize]
|
||||
}
|
||||
@@ -119,9 +123,13 @@ func (s ErasureStorage) HealFile(staleDisks []StorageAPI, volume, path string, b
|
||||
// iteration
|
||||
chunkOffset += chunksize
|
||||
|
||||
// reconstruct data - this computes all data and parity shards
|
||||
if err = s.ErasureDecodeDataAndParityBlocks(blocks); err != nil {
|
||||
return f, err
|
||||
// reconstruct data - this computes all data and
|
||||
// parity shards - but we skip this step if we are
|
||||
// reconstructing an empty file.
|
||||
if chunksize > 0 {
|
||||
if err = s.ErasureDecodeDataAndParityBlocks(blocks); err != nil {
|
||||
return f, err
|
||||
}
|
||||
}
|
||||
|
||||
// write computed shards as chunks on file in each
|
||||
@@ -144,7 +152,7 @@ func (s ErasureStorage) HealFile(staleDisks []StorageAPI, volume, path string, b
|
||||
// If all disks had write errors we quit.
|
||||
if !writeSucceeded {
|
||||
// build error from all write errors
|
||||
return f, traceError(joinWriteErrors(writeErrors))
|
||||
return f, errors.Trace(joinWriteErrors(writeErrors))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// ReadFile reads as much data as requested from the file under the given volume and path and writes the data to the provided writer.
|
||||
@@ -25,13 +27,13 @@ import (
|
||||
// up to the given length. If parts of the file are corrupted ReadFile tries to reconstruct the data.
|
||||
func (s ErasureStorage) ReadFile(writer io.Writer, volume, path string, offset, length int64, totalLength int64, checksums [][]byte, algorithm BitrotAlgorithm, blocksize int64) (f ErasureFileInfo, err error) {
|
||||
if offset < 0 || length < 0 {
|
||||
return f, traceError(errUnexpected)
|
||||
return f, errors.Trace(errUnexpected)
|
||||
}
|
||||
if offset+length > totalLength {
|
||||
return f, traceError(errUnexpected)
|
||||
return f, errors.Trace(errUnexpected)
|
||||
}
|
||||
if !algorithm.Available() {
|
||||
return f, traceError(errBitrotHashAlgoInvalid)
|
||||
return f, errors.Trace(errBitrotHashAlgoInvalid)
|
||||
}
|
||||
|
||||
f.Checksums = make([][]byte, len(s.disks))
|
||||
@@ -66,7 +68,7 @@ func (s ErasureStorage) ReadFile(writer io.Writer, volume, path string, offset,
|
||||
}
|
||||
err = s.readConcurrent(volume, path, blockOffset, blocks, verifiers, errChans)
|
||||
if err != nil {
|
||||
return f, traceError(errXLReadQuorum)
|
||||
return f, errors.Trace(errXLReadQuorum)
|
||||
}
|
||||
|
||||
writeLength := blocksize - startOffset
|
||||
@@ -150,7 +152,7 @@ func erasureReadBlocksConcurrent(disks []StorageAPI, volume, path string, offset
|
||||
// It sends the returned error through the error channel.
|
||||
func erasureReadFromFile(disk StorageAPI, volume, path string, offset int64, buffer []byte, verifier *BitrotVerifier, errChan chan<- error) {
|
||||
if disk == OfflineDisk {
|
||||
errChan <- traceError(errDiskNotFound)
|
||||
errChan <- errors.Trace(errDiskNotFound)
|
||||
return
|
||||
}
|
||||
_, err := disk.ReadFile(volume, path, offset, buffer, verifier)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/klauspost/reedsolomon"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// getDataBlockLen - get length of data blocks from encoded blocks.
|
||||
@@ -38,17 +39,17 @@ func getDataBlockLen(enBlocks [][]byte, dataBlocks int) int {
|
||||
func writeDataBlocks(dst io.Writer, enBlocks [][]byte, dataBlocks int, offset int64, length int64) (int64, error) {
|
||||
// Offset and out size cannot be negative.
|
||||
if offset < 0 || length < 0 {
|
||||
return 0, traceError(errUnexpected)
|
||||
return 0, errors.Trace(errUnexpected)
|
||||
}
|
||||
|
||||
// Do we have enough blocks?
|
||||
if len(enBlocks) < dataBlocks {
|
||||
return 0, traceError(reedsolomon.ErrTooFewShards)
|
||||
return 0, errors.Trace(reedsolomon.ErrTooFewShards)
|
||||
}
|
||||
|
||||
// Do we have enough data?
|
||||
if int64(getDataBlockLen(enBlocks, dataBlocks)) < length {
|
||||
return 0, traceError(reedsolomon.ErrShortData)
|
||||
return 0, errors.Trace(reedsolomon.ErrShortData)
|
||||
}
|
||||
|
||||
// Counter to decrement total left to write.
|
||||
@@ -76,7 +77,7 @@ func writeDataBlocks(dst io.Writer, enBlocks [][]byte, dataBlocks int, offset in
|
||||
if write < int64(len(block)) {
|
||||
n, err := io.Copy(dst, bytes.NewReader(block[:write]))
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
totalWritten += n
|
||||
break
|
||||
@@ -84,7 +85,7 @@ func writeDataBlocks(dst io.Writer, enBlocks [][]byte, dataBlocks int, offset in
|
||||
// Copy the block.
|
||||
n, err := io.Copy(dst, bytes.NewReader(block))
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Decrement output size.
|
||||
|
||||
+6
-5
@@ -21,6 +21,7 @@ import (
|
||||
"hash"
|
||||
|
||||
"github.com/klauspost/reedsolomon"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// OfflineDisk represents an unavailable disk.
|
||||
@@ -46,7 +47,7 @@ type ErasureStorage struct {
|
||||
func NewErasureStorage(disks []StorageAPI, dataBlocks, parityBlocks int) (s ErasureStorage, err error) {
|
||||
erasure, err := reedsolomon.New(dataBlocks, parityBlocks)
|
||||
if err != nil {
|
||||
return s, traceErrorf("failed to create erasure coding: %v", err)
|
||||
return s, errors.Tracef("failed to create erasure coding: %v", err)
|
||||
}
|
||||
s = ErasureStorage{
|
||||
disks: make([]StorageAPI, len(disks)),
|
||||
@@ -63,10 +64,10 @@ func NewErasureStorage(disks []StorageAPI, dataBlocks, parityBlocks int) (s Eras
|
||||
func (s *ErasureStorage) ErasureEncode(data []byte) ([][]byte, error) {
|
||||
encoded, err := s.erasure.Split(data)
|
||||
if err != nil {
|
||||
return nil, traceErrorf("failed to split data: %v", err)
|
||||
return nil, errors.Tracef("failed to split data: %v", err)
|
||||
}
|
||||
if err = s.erasure.Encode(encoded); err != nil {
|
||||
return nil, traceErrorf("failed to encode data: %v", err)
|
||||
return nil, errors.Tracef("failed to encode data: %v", err)
|
||||
}
|
||||
return encoded, nil
|
||||
}
|
||||
@@ -76,7 +77,7 @@ func (s *ErasureStorage) ErasureEncode(data []byte) ([][]byte, error) {
|
||||
// It returns an error if the decoding failed.
|
||||
func (s *ErasureStorage) ErasureDecodeDataBlocks(data [][]byte) error {
|
||||
if err := s.erasure.ReconstructData(data); err != nil {
|
||||
return traceErrorf("failed to reconstruct data: %v", err)
|
||||
return errors.Tracef("failed to reconstruct data: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -85,7 +86,7 @@ func (s *ErasureStorage) ErasureDecodeDataBlocks(data [][]byte) error {
|
||||
// It returns an error if the decoding failed.
|
||||
func (s *ErasureStorage) ErasureDecodeDataAndParityBlocks(data [][]byte) error {
|
||||
if err := s.erasure.Reconstruct(data); err != nil {
|
||||
return traceErrorf("failed to reconstruct data: %v", err)
|
||||
return errors.Tracef("failed to reconstruct data: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Holds the current directory path. Used for trimming path in traceError()
|
||||
var rootPath string
|
||||
|
||||
// Figure out the rootPath
|
||||
func initError() {
|
||||
// Root path is automatically determined from the calling function's source file location.
|
||||
// Catch the calling function's source file path.
|
||||
_, file, _, _ := runtime.Caller(1)
|
||||
// Save the directory alone.
|
||||
rootPath = filepath.Dir(file)
|
||||
}
|
||||
|
||||
// Represents a stack frame in the stack trace.
|
||||
type traceInfo struct {
|
||||
file string // File where error occurred
|
||||
line int // Line where error occurred
|
||||
name string // Name of the function where error occurred
|
||||
}
|
||||
|
||||
// Error - error type containing cause and the stack trace.
|
||||
type Error struct {
|
||||
e error // Holds the cause error
|
||||
trace []traceInfo // stack trace
|
||||
errs []error // Useful for XL to hold errors from all disks
|
||||
}
|
||||
|
||||
// Implement error interface.
|
||||
func (e Error) Error() string {
|
||||
return e.e.Error()
|
||||
}
|
||||
|
||||
// Trace - returns stack trace.
|
||||
func (e Error) Trace() []string {
|
||||
var traceArr []string
|
||||
for _, info := range e.trace {
|
||||
traceArr = append(traceArr, fmt.Sprintf("%s:%d:%s",
|
||||
info.file, info.line, info.name))
|
||||
}
|
||||
return traceArr
|
||||
}
|
||||
|
||||
// NewStorageError - return new Error type.
|
||||
func traceError(e error, errs ...error) error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
err := &Error{}
|
||||
err.e = e
|
||||
err.errs = errs
|
||||
|
||||
stack := make([]uintptr, 40)
|
||||
length := runtime.Callers(2, stack)
|
||||
if length > len(stack) {
|
||||
length = len(stack)
|
||||
}
|
||||
stack = stack[:length]
|
||||
|
||||
for _, pc := range stack {
|
||||
pc = pc - 1
|
||||
fn := runtime.FuncForPC(pc)
|
||||
file, line := fn.FileLine(pc)
|
||||
name := fn.Name()
|
||||
if hasSuffix(name, "ServeHTTP") {
|
||||
break
|
||||
}
|
||||
if hasSuffix(name, "runtime.") {
|
||||
break
|
||||
}
|
||||
|
||||
file = strings.TrimPrefix(file, rootPath+string(os.PathSeparator))
|
||||
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
|
||||
err.trace = append(err.trace, traceInfo{file, line, name})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Returns the underlying cause error.
|
||||
func errorCause(err error) error {
|
||||
if e, ok := err.(*Error); ok {
|
||||
err = e.e
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Returns slice of underlying cause error.
|
||||
func errorsCause(errs []error) []error {
|
||||
cerrs := make([]error, len(errs))
|
||||
for i, err := range errs {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
cerrs[i] = errorCause(err)
|
||||
}
|
||||
return cerrs
|
||||
}
|
||||
|
||||
// Collection of basic errors.
|
||||
var baseErrs = []error{
|
||||
errDiskNotFound,
|
||||
errFaultyDisk,
|
||||
errFaultyRemoteDisk,
|
||||
}
|
||||
|
||||
var baseIgnoredErrs = baseErrs
|
||||
|
||||
// isErrIgnored returns whether given error is ignored or not.
|
||||
func isErrIgnored(err error, ignoredErrs ...error) bool {
|
||||
err = errorCause(err)
|
||||
for _, ignoredErr := range ignoredErrs {
|
||||
if ignoredErr == err {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isErr returns whether given error is exact error.
|
||||
func isErr(err error, errs ...error) bool {
|
||||
err = errorCause(err)
|
||||
for _, exactErr := range errs {
|
||||
if err == exactErr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// traceErrorf behaves like fmt.traceErrorf but also traces the returned error.
|
||||
func traceErrorf(format string, args ...interface{}) error {
|
||||
return traceError(fmt.Errorf(format, args...))
|
||||
}
|
||||
+21
-20
@@ -27,6 +27,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
@@ -106,10 +107,10 @@ func newNotificationEvent(event eventData) NotificationEvent {
|
||||
}
|
||||
|
||||
// Fetch the region.
|
||||
region := serverConfig.GetRegion()
|
||||
region := globalServerConfig.GetRegion()
|
||||
|
||||
// Fetch the credentials.
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
|
||||
// Time when Minio finished processing the request.
|
||||
eventTime := UTCNow()
|
||||
@@ -378,7 +379,7 @@ func loadNotificationConfig(bucket string, objAPI ObjectLayer) (*notificationCon
|
||||
// 'errNoSuchNotifications'. This is default when no
|
||||
// bucket notifications are found on the bucket.
|
||||
if isErrObjectNotFound(err) || isErrIncompleteBody(err) {
|
||||
return nil, traceError(errNoSuchNotifications)
|
||||
return nil, errors.Trace(errNoSuchNotifications)
|
||||
}
|
||||
errorIf(err, "Unable to load bucket-notification for bucket %s", bucket)
|
||||
// Returns error for other errors.
|
||||
@@ -387,7 +388,7 @@ func loadNotificationConfig(bucket string, objAPI ObjectLayer) (*notificationCon
|
||||
|
||||
// if `notifications.xml` is empty we should return NoSuchNotifications.
|
||||
if buffer.Len() == 0 {
|
||||
return nil, traceError(errNoSuchNotifications)
|
||||
return nil, errors.Trace(errNoSuchNotifications)
|
||||
}
|
||||
|
||||
// Unmarshal notification bytes.
|
||||
@@ -395,7 +396,7 @@ func loadNotificationConfig(bucket string, objAPI ObjectLayer) (*notificationCon
|
||||
notificationCfg := ¬ificationConfig{}
|
||||
// Unmarshal notification bytes only if we read data.
|
||||
if err = xml.Unmarshal(notificationConfigBytes, notificationCfg); err != nil {
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Return success.
|
||||
@@ -429,7 +430,7 @@ func loadListenerConfig(bucket string, objAPI ObjectLayer) ([]listenerConfig, er
|
||||
// 'errNoSuchNotifications'. This is default when no
|
||||
// bucket listeners are found on the bucket
|
||||
if isErrObjectNotFound(err) || isErrIncompleteBody(err) {
|
||||
return nil, traceError(errNoSuchNotifications)
|
||||
return nil, errors.Trace(errNoSuchNotifications)
|
||||
}
|
||||
errorIf(err, "Unable to load bucket-listeners for bucket %s", bucket)
|
||||
// Returns error for other errors.
|
||||
@@ -438,14 +439,14 @@ func loadListenerConfig(bucket string, objAPI ObjectLayer) ([]listenerConfig, er
|
||||
|
||||
// if `listener.json` is empty we should return NoSuchNotifications.
|
||||
if buffer.Len() == 0 {
|
||||
return nil, traceError(errNoSuchNotifications)
|
||||
return nil, errors.Trace(errNoSuchNotifications)
|
||||
}
|
||||
|
||||
var lCfg []listenerConfig
|
||||
lConfigBytes := buffer.Bytes()
|
||||
if err = json.Unmarshal(lConfigBytes, &lCfg); err != nil {
|
||||
errorIf(err, "Unable to unmarshal listener config from JSON.")
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Return success.
|
||||
@@ -552,13 +553,13 @@ func removeListenerConfig(bucket string, objAPI ObjectLayer) error {
|
||||
func loadNotificationAndListenerConfig(bucketName string, objAPI ObjectLayer) (nCfg *notificationConfig, lCfg []listenerConfig, err error) {
|
||||
// Loads notification config if any.
|
||||
nCfg, err = loadNotificationConfig(bucketName, objAPI)
|
||||
if err != nil && !isErrIgnored(err, errDiskNotFound, errNoSuchNotifications) {
|
||||
if err != nil && !errors.IsErrIgnored(err, errDiskNotFound, errNoSuchNotifications) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Loads listener config if any.
|
||||
lCfg, err = loadListenerConfig(bucketName, objAPI)
|
||||
if err != nil && !isErrIgnored(err, errDiskNotFound, errNoSuchNotifications) {
|
||||
if err != nil && !errors.IsErrIgnored(err, errDiskNotFound, errNoSuchNotifications) {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nCfg, lCfg, nil
|
||||
@@ -595,7 +596,7 @@ func addQueueTarget(queueTargets map[string]*logrus.Logger,
|
||||
newTargetFunc func(string) (*logrus.Logger, error)) (string, error) {
|
||||
|
||||
// Construct the queue ARN for AMQP.
|
||||
queueARN := minioSqs + serverConfig.GetRegion() + ":" + accountID + ":" + queueType
|
||||
queueARN := minioSqs + globalServerConfig.GetRegion() + ":" + accountID + ":" + queueType
|
||||
|
||||
// Queue target if already initialized we move to the next ARN.
|
||||
if _, ok := queueTargets[queueARN]; ok {
|
||||
@@ -618,7 +619,7 @@ func addQueueTarget(queueTargets map[string]*logrus.Logger,
|
||||
func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
queueTargets := make(map[string]*logrus.Logger)
|
||||
// Load all amqp targets, initialize their respective loggers.
|
||||
for accountID, amqpN := range serverConfig.Notify.GetAMQP() {
|
||||
for accountID, amqpN := range globalServerConfig.Notify.GetAMQP() {
|
||||
if !amqpN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -637,7 +638,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load all mqtt targets, initialize their respective loggers.
|
||||
for accountID, mqttN := range serverConfig.Notify.GetMQTT() {
|
||||
for accountID, mqttN := range globalServerConfig.Notify.GetMQTT() {
|
||||
if !mqttN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -656,7 +657,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load all nats targets, initialize their respective loggers.
|
||||
for accountID, natsN := range serverConfig.Notify.GetNATS() {
|
||||
for accountID, natsN := range globalServerConfig.Notify.GetNATS() {
|
||||
if !natsN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -675,7 +676,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load redis targets, initialize their respective loggers.
|
||||
for accountID, redisN := range serverConfig.Notify.GetRedis() {
|
||||
for accountID, redisN := range globalServerConfig.Notify.GetRedis() {
|
||||
if !redisN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -694,7 +695,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load Webhook targets, initialize their respective loggers.
|
||||
for accountID, webhookN := range serverConfig.Notify.GetWebhook() {
|
||||
for accountID, webhookN := range globalServerConfig.Notify.GetWebhook() {
|
||||
if !webhookN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -704,7 +705,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load elastic targets, initialize their respective loggers.
|
||||
for accountID, elasticN := range serverConfig.Notify.GetElasticSearch() {
|
||||
for accountID, elasticN := range globalServerConfig.Notify.GetElasticSearch() {
|
||||
if !elasticN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -723,7 +724,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load PostgreSQL targets, initialize their respective loggers.
|
||||
for accountID, pgN := range serverConfig.Notify.GetPostgreSQL() {
|
||||
for accountID, pgN := range globalServerConfig.Notify.GetPostgreSQL() {
|
||||
if !pgN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -742,7 +743,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load MySQL targets, initialize their respective loggers.
|
||||
for accountID, msqlN := range serverConfig.Notify.GetMySQL() {
|
||||
for accountID, msqlN := range globalServerConfig.Notify.GetMySQL() {
|
||||
if !msqlN.Enable {
|
||||
continue
|
||||
}
|
||||
@@ -761,7 +762,7 @@ func loadAllQueueTargets() (map[string]*logrus.Logger, error) {
|
||||
}
|
||||
|
||||
// Load Kafka targets, initialize their respective loggers.
|
||||
for accountID, kafkaN := range serverConfig.Notify.GetKafka() {
|
||||
for accountID, kafkaN := range globalServerConfig.Notify.GetKafka() {
|
||||
if !kafkaN.Enable {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// Test InitEventNotifier with faulty disks
|
||||
@@ -71,7 +73,7 @@ func TestInitEventNotifierFaultyDisks(t *testing.T) {
|
||||
}
|
||||
// Test initEventNotifier() with faulty disks
|
||||
for i := 1; i <= 3; i++ {
|
||||
if err := initEventNotifier(xl); errorCause(err) != errFaultyDisk {
|
||||
if err := initEventNotifier(xl); errors.Cause(err) != errFaultyDisk {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
}
|
||||
@@ -98,7 +100,7 @@ func TestInitEventNotifierWithPostgreSQL(t *testing.T) {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
|
||||
serverConfig.Notify.SetPostgreSQLByID("1", postgreSQLNotify{Enable: true})
|
||||
globalServerConfig.Notify.SetPostgreSQLByID("1", postgreSQLNotify{Enable: true})
|
||||
if err := initEventNotifier(fs); err == nil {
|
||||
t.Fatal("PostgreSQL config didn't fail.")
|
||||
}
|
||||
@@ -125,7 +127,7 @@ func TestInitEventNotifierWithNATS(t *testing.T) {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
|
||||
serverConfig.Notify.SetNATSByID("1", natsNotify{Enable: true})
|
||||
globalServerConfig.Notify.SetNATSByID("1", natsNotify{Enable: true})
|
||||
if err := initEventNotifier(fs); err == nil {
|
||||
t.Fatal("NATS config didn't fail.")
|
||||
}
|
||||
@@ -152,7 +154,7 @@ func TestInitEventNotifierWithWebHook(t *testing.T) {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
|
||||
serverConfig.Notify.SetWebhookByID("1", webhookNotify{Enable: true})
|
||||
globalServerConfig.Notify.SetWebhookByID("1", webhookNotify{Enable: true})
|
||||
if err := initEventNotifier(fs); err == nil {
|
||||
t.Fatal("WebHook config didn't fail.")
|
||||
}
|
||||
@@ -179,7 +181,7 @@ func TestInitEventNotifierWithAMQP(t *testing.T) {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
|
||||
serverConfig.Notify.SetAMQPByID("1", amqpNotify{Enable: true})
|
||||
globalServerConfig.Notify.SetAMQPByID("1", amqpNotify{Enable: true})
|
||||
if err := initEventNotifier(fs); err == nil {
|
||||
t.Fatal("AMQP config didn't fail.")
|
||||
}
|
||||
@@ -206,7 +208,7 @@ func TestInitEventNotifierWithElasticSearch(t *testing.T) {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
|
||||
serverConfig.Notify.SetElasticSearchByID("1", elasticSearchNotify{Enable: true})
|
||||
globalServerConfig.Notify.SetElasticSearchByID("1", elasticSearchNotify{Enable: true})
|
||||
if err := initEventNotifier(fs); err == nil {
|
||||
t.Fatal("ElasticSearch config didn't fail.")
|
||||
}
|
||||
@@ -233,7 +235,7 @@ func TestInitEventNotifierWithRedis(t *testing.T) {
|
||||
t.Fatal("Unable to initialize FS backend.", err)
|
||||
}
|
||||
|
||||
serverConfig.Notify.SetRedisByID("1", redisNotify{Enable: true})
|
||||
globalServerConfig.Notify.SetRedisByID("1", redisNotify{Enable: true})
|
||||
if err := initEventNotifier(fs); err == nil {
|
||||
t.Fatal("Redis config didn't fail.")
|
||||
}
|
||||
@@ -415,7 +417,7 @@ func TestListenBucketNotification(t *testing.T) {
|
||||
|
||||
listenARN := fmt.Sprintf("%s:%s:1:%s-%s",
|
||||
minioTopic,
|
||||
serverConfig.GetRegion(),
|
||||
globalServerConfig.GetRegion(),
|
||||
snsTypeMinio,
|
||||
s.testServer.Server.Listener.Addr(),
|
||||
)
|
||||
@@ -523,7 +525,7 @@ func TestAddRemoveBucketListenerConfig(t *testing.T) {
|
||||
accountID := fmt.Sprintf("%d", UTCNow().UnixNano())
|
||||
accountARN := fmt.Sprintf(
|
||||
"arn:minio:sqs:%s:%s:listen-%s",
|
||||
serverConfig.GetRegion(),
|
||||
globalServerConfig.GetRegion(),
|
||||
accountID,
|
||||
globalMinioAddr,
|
||||
)
|
||||
|
||||
+10
-9
@@ -25,6 +25,7 @@ import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
errors2 "github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
)
|
||||
|
||||
@@ -123,10 +124,10 @@ func (f *formatConfigV1) CheckFS() error {
|
||||
// if reading format.json fails with io.EOF.
|
||||
func (f *formatConfigV1) LoadFormat(lk *lock.LockedFile) error {
|
||||
_, err := f.ReadFrom(lk)
|
||||
if errorCause(err) == io.EOF {
|
||||
if errors2.Cause(err) == io.EOF {
|
||||
// No data on disk `format.json` still empty
|
||||
// treat it as unformatted disk.
|
||||
return traceError(errUnformattedDisk)
|
||||
return errors2.Trace(errUnformattedDisk)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -136,14 +137,14 @@ func (f *formatConfigV1) WriteTo(lk *lock.LockedFile) (n int64, err error) {
|
||||
var fbytes []byte
|
||||
fbytes, err = json.Marshal(f)
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors2.Trace(err)
|
||||
}
|
||||
if err = lk.Truncate(0); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors2.Trace(err)
|
||||
}
|
||||
_, err = lk.Write(fbytes)
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors2.Trace(err)
|
||||
}
|
||||
return int64(len(fbytes)), nil
|
||||
}
|
||||
@@ -152,18 +153,18 @@ func (f *formatConfigV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) {
|
||||
var fbytes []byte
|
||||
fi, err := lk.Stat()
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors2.Trace(err)
|
||||
}
|
||||
fbytes, err = ioutil.ReadAll(io.NewSectionReader(lk, 0, fi.Size()))
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors2.Trace(err)
|
||||
}
|
||||
if len(fbytes) == 0 {
|
||||
return 0, traceError(io.EOF)
|
||||
return 0, errors2.Trace(io.EOF)
|
||||
}
|
||||
// Decode `format.json`.
|
||||
if err = json.Unmarshal(fbytes, f); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors2.Trace(err)
|
||||
}
|
||||
return int64(len(fbytes)), nil
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
errors2 "github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
)
|
||||
@@ -756,7 +757,7 @@ func TestFSCheckFormatFSErr(t *testing.T) {
|
||||
t.Errorf("Test %d: Should fail with expected %s, got nil", i+1, testCase.formatCheckErr)
|
||||
}
|
||||
if err != nil && !testCase.shouldPass {
|
||||
if errorCause(err).Error() != testCase.formatCheckErr.Error() {
|
||||
if errors2.Cause(err).Error() != testCase.formatCheckErr.Error() {
|
||||
t.Errorf("Test %d: Should fail with expected %s, got %s", i+1, testCase.formatCheckErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
+63
-61
@@ -22,6 +22,8 @@ import (
|
||||
"os"
|
||||
pathutil "path"
|
||||
"runtime"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// Removes only the file at given path does not remove
|
||||
@@ -29,11 +31,11 @@ import (
|
||||
// windows automatically.
|
||||
func fsRemoveFile(filePath string) (err error) {
|
||||
if filePath == "" {
|
||||
return traceError(errInvalidArgument)
|
||||
return errors.Trace(errInvalidArgument)
|
||||
}
|
||||
|
||||
if err = checkPathLength(filePath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
if err = os.Remove((filePath)); err != nil {
|
||||
@@ -47,20 +49,20 @@ func fsRemoveFile(filePath string) (err error) {
|
||||
// long paths for windows automatically.
|
||||
func fsRemoveAll(dirPath string) (err error) {
|
||||
if dirPath == "" {
|
||||
return traceError(errInvalidArgument)
|
||||
return errors.Trace(errInvalidArgument)
|
||||
}
|
||||
|
||||
if err = checkPathLength(dirPath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
if err = os.RemoveAll(dirPath); err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return traceError(errVolumeAccessDenied)
|
||||
return errors.Trace(errVolumeAccessDenied)
|
||||
} else if isSysErrNotEmpty(err) {
|
||||
return traceError(errVolumeNotEmpty)
|
||||
return errors.Trace(errVolumeNotEmpty)
|
||||
}
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -70,20 +72,20 @@ func fsRemoveAll(dirPath string) (err error) {
|
||||
// paths for windows automatically.
|
||||
func fsRemoveDir(dirPath string) (err error) {
|
||||
if dirPath == "" {
|
||||
return traceError(errInvalidArgument)
|
||||
return errors.Trace(errInvalidArgument)
|
||||
}
|
||||
|
||||
if err = checkPathLength(dirPath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
if err = os.Remove((dirPath)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return traceError(errVolumeNotFound)
|
||||
return errors.Trace(errVolumeNotFound)
|
||||
} else if isSysErrNotEmpty(err) {
|
||||
return traceError(errVolumeNotEmpty)
|
||||
return errors.Trace(errVolumeNotEmpty)
|
||||
}
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -93,15 +95,15 @@ func fsRemoveDir(dirPath string) (err error) {
|
||||
// if it doesn't exist.
|
||||
func fsMkdirAll(dirPath string) (err error) {
|
||||
if dirPath == "" {
|
||||
return traceError(errInvalidArgument)
|
||||
return errors.Trace(errInvalidArgument)
|
||||
}
|
||||
|
||||
if err = checkPathLength(dirPath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dirPath, 0777); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -113,27 +115,27 @@ func fsMkdirAll(dirPath string) (err error) {
|
||||
// are handled automatically.
|
||||
func fsMkdir(dirPath string) (err error) {
|
||||
if dirPath == "" {
|
||||
return traceError(errInvalidArgument)
|
||||
return errors.Trace(errInvalidArgument)
|
||||
}
|
||||
|
||||
if err = checkPathLength(dirPath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
if err = os.Mkdir((dirPath), 0777); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return traceError(errVolumeExists)
|
||||
return errors.Trace(errVolumeExists)
|
||||
} else if os.IsPermission(err) {
|
||||
return traceError(errDiskAccessDenied)
|
||||
return errors.Trace(errDiskAccessDenied)
|
||||
} else if isSysErrNotDir(err) {
|
||||
// File path cannot be verified since
|
||||
// one of the parents is a file.
|
||||
return traceError(errDiskAccessDenied)
|
||||
return errors.Trace(errDiskAccessDenied)
|
||||
} else if isSysErrPathNotFound(err) {
|
||||
// Add specific case for windows.
|
||||
return traceError(errDiskAccessDenied)
|
||||
return errors.Trace(errDiskAccessDenied)
|
||||
}
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -146,14 +148,14 @@ func fsMkdir(dirPath string) (err error) {
|
||||
// fsStatFileDir, fsStatFile, fsStatDir.
|
||||
func fsStat(statLoc string) (os.FileInfo, error) {
|
||||
if statLoc == "" {
|
||||
return nil, traceError(errInvalidArgument)
|
||||
return nil, errors.Trace(errInvalidArgument)
|
||||
}
|
||||
if err := checkPathLength(statLoc); err != nil {
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
fi, err := os.Stat((statLoc))
|
||||
if err != nil {
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return fi, nil
|
||||
@@ -163,17 +165,17 @@ func fsStat(statLoc string) (os.FileInfo, error) {
|
||||
func fsStatVolume(volume string) (os.FileInfo, error) {
|
||||
fi, err := fsStat(volume)
|
||||
if err != nil {
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, traceError(errVolumeNotFound)
|
||||
return nil, errors.Trace(errVolumeNotFound)
|
||||
} else if os.IsPermission(err) {
|
||||
return nil, traceError(errVolumeAccessDenied)
|
||||
return nil, errors.Trace(errVolumeAccessDenied)
|
||||
}
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return nil, traceError(errVolumeAccessDenied)
|
||||
return nil, errors.Trace(errVolumeAccessDenied)
|
||||
}
|
||||
|
||||
return fi, nil
|
||||
@@ -187,18 +189,18 @@ func osErrToFSFileErr(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
if os.IsNotExist(err) {
|
||||
return traceError(errFileNotFound)
|
||||
return errors.Trace(errFileNotFound)
|
||||
}
|
||||
if os.IsPermission(err) {
|
||||
return traceError(errFileAccessDenied)
|
||||
return errors.Trace(errFileAccessDenied)
|
||||
}
|
||||
if isSysErrNotDir(err) {
|
||||
return traceError(errFileAccessDenied)
|
||||
return errors.Trace(errFileAccessDenied)
|
||||
}
|
||||
if isSysErrPathNotFound(err) {
|
||||
return traceError(errFileNotFound)
|
||||
return errors.Trace(errFileNotFound)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -210,7 +212,7 @@ func fsStatDir(statDir string) (os.FileInfo, error) {
|
||||
return nil, osErrToFSFileErr(err)
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return nil, traceError(errFileAccessDenied)
|
||||
return nil, errors.Trace(errFileAccessDenied)
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
@@ -222,7 +224,7 @@ func fsStatFile(statFile string) (os.FileInfo, error) {
|
||||
return nil, osErrToFSFileErr(err)
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return nil, traceError(errFileAccessDenied)
|
||||
return nil, errors.Trace(errFileAccessDenied)
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
@@ -231,10 +233,10 @@ func fsStatFile(statFile string) (os.FileInfo, error) {
|
||||
// a readable stream and the size of the readable stream.
|
||||
func fsOpenFile(readPath string, offset int64) (io.ReadCloser, int64, error) {
|
||||
if readPath == "" || offset < 0 {
|
||||
return nil, 0, traceError(errInvalidArgument)
|
||||
return nil, 0, errors.Trace(errInvalidArgument)
|
||||
}
|
||||
if err := checkPathLength(readPath); err != nil {
|
||||
return nil, 0, traceError(err)
|
||||
return nil, 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
fr, err := os.Open((readPath))
|
||||
@@ -245,19 +247,19 @@ func fsOpenFile(readPath string, offset int64) (io.ReadCloser, int64, error) {
|
||||
// Stat to get the size of the file at path.
|
||||
st, err := os.Stat((readPath))
|
||||
if err != nil {
|
||||
return nil, 0, traceError(err)
|
||||
return nil, 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Verify if its not a regular file, since subsequent Seek is undefined.
|
||||
if !st.Mode().IsRegular() {
|
||||
return nil, 0, traceError(errIsNotRegular)
|
||||
return nil, 0, errors.Trace(errIsNotRegular)
|
||||
}
|
||||
|
||||
// Seek to the requested offset.
|
||||
if offset > 0 {
|
||||
_, err = fr.Seek(offset, os.SEEK_SET)
|
||||
if err != nil {
|
||||
return nil, 0, traceError(err)
|
||||
return nil, 0, errors.Trace(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,19 +270,19 @@ func fsOpenFile(readPath string, offset int64) (io.ReadCloser, int64, error) {
|
||||
// Creates a file and copies data from incoming reader. Staging buffer is used by io.CopyBuffer.
|
||||
func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int64) (int64, error) {
|
||||
if filePath == "" || reader == nil {
|
||||
return 0, traceError(errInvalidArgument)
|
||||
return 0, errors.Trace(errInvalidArgument)
|
||||
}
|
||||
|
||||
if err := checkPathLength(filePath); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(pathutil.Dir(filePath), 0777); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
if err := checkDiskFree(pathutil.Dir(filePath), fallocSize); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
writer, err := os.OpenFile((filePath), os.O_CREATE|os.O_WRONLY, 0666)
|
||||
@@ -292,7 +294,7 @@ func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int6
|
||||
// Fallocate only if the size is final object is known.
|
||||
if fallocSize > 0 {
|
||||
if err = fsFAllocate(int(writer.Fd()), 0, fallocSize); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,12 +302,12 @@ func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int6
|
||||
if buf != nil {
|
||||
bytesWritten, err = io.CopyBuffer(writer, reader, buf)
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
} else {
|
||||
bytesWritten, err = io.Copy(writer, reader)
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
}
|
||||
return bytesWritten, nil
|
||||
@@ -314,26 +316,26 @@ func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int6
|
||||
// Removes uploadID at destination path.
|
||||
func fsRemoveUploadIDPath(basePath, uploadIDPath string) error {
|
||||
if basePath == "" || uploadIDPath == "" {
|
||||
return traceError(errInvalidArgument)
|
||||
return errors.Trace(errInvalidArgument)
|
||||
}
|
||||
if err := checkPathLength(basePath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
if err := checkPathLength(uploadIDPath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
// List all the entries in uploadID.
|
||||
entries, err := readDir(uploadIDPath)
|
||||
if err != nil && err != errFileNotFound {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
// Delete all the entries obtained from previous readdir.
|
||||
for _, entryPath := range entries {
|
||||
err = fsDeleteFile(basePath, pathJoin(uploadIDPath, entryPath))
|
||||
if err != nil && err != errFileNotFound {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,23 +369,23 @@ func fsFAllocate(fd int, offset int64, len int64) (err error) {
|
||||
// missing parents if they don't exist.
|
||||
func fsRenameFile(sourcePath, destPath string) error {
|
||||
if err := checkPathLength(sourcePath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
if err := checkPathLength(destPath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
// Verify if source path exists.
|
||||
if _, err := os.Stat((sourcePath)); err != nil {
|
||||
return osErrToFSFileErr(err)
|
||||
}
|
||||
if err := os.MkdirAll(pathutil.Dir(destPath), 0777); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
if err := os.Rename((sourcePath), (destPath)); err != nil {
|
||||
if isSysErrCrossDevice(err) {
|
||||
return traceError(fmt.Errorf("%s (%s)->(%s)", errCrossDeviceLink, sourcePath, destPath))
|
||||
return errors.Trace(fmt.Errorf("%s (%s)->(%s)", errCrossDeviceLink, sourcePath, destPath))
|
||||
}
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -391,11 +393,11 @@ func fsRenameFile(sourcePath, destPath string) error {
|
||||
// fsDeleteFile is a wrapper for deleteFile(), after checking the path length.
|
||||
func fsDeleteFile(basePath, deletePath string) error {
|
||||
if err := checkPathLength(basePath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
if err := checkPathLength(deletePath); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
return deleteFile(basePath, deletePath)
|
||||
|
||||
+21
-20
@@ -24,6 +24,7 @@ import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
)
|
||||
|
||||
@@ -36,11 +37,11 @@ func TestFSMkdirAll(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
if err = fsMkdirAll(""); errorCause(err) != errInvalidArgument {
|
||||
if err = fsMkdirAll(""); errors.Cause(err) != errInvalidArgument {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
if err = fsMkdirAll(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errorCause(err) != errFileNameTooLong {
|
||||
if err = fsMkdirAll(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errors.Cause(err) != errFileNameTooLong {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
@@ -63,13 +64,13 @@ func TestFSRenameFile(t *testing.T) {
|
||||
if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); errorCause(err) != errFileNotFound {
|
||||
if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); errors.Cause(err) != errFileNotFound {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = fsRenameFile(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), pathJoin(path, "testvolume2")); errorCause(err) != errFileNameTooLong {
|
||||
if err = fsRenameFile(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), pathJoin(path, "testvolume2")); errors.Cause(err) != errFileNameTooLong {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errorCause(err) != errFileNameTooLong {
|
||||
if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errors.Cause(err) != errFileNameTooLong {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
}
|
||||
@@ -84,11 +85,11 @@ func TestFSStats(t *testing.T) {
|
||||
|
||||
// Setup test environment.
|
||||
|
||||
if err = fsMkdir(""); errorCause(err) != errInvalidArgument {
|
||||
if err = fsMkdir(""); errors.Cause(err) != errInvalidArgument {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
if err = fsMkdir(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errorCause(err) != errFileNameTooLong {
|
||||
if err = fsMkdir(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errors.Cause(err) != errFileNameTooLong {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ func TestFSStats(t *testing.T) {
|
||||
// Seek back.
|
||||
reader.Seek(0, 0)
|
||||
|
||||
if err = fsMkdir(pathJoin(path, "success-vol", "success-file")); errorCause(err) != errVolumeExists {
|
||||
if err = fsMkdir(pathJoin(path, "success-vol", "success-file")); errors.Cause(err) != errVolumeExists {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
@@ -191,11 +192,11 @@ func TestFSStats(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
if testCase.srcPath != "" {
|
||||
if _, err := fsStatFile(pathJoin(testCase.srcFSPath, testCase.srcVol,
|
||||
testCase.srcPath)); errorCause(err) != testCase.expectedErr {
|
||||
testCase.srcPath)); errors.Cause(err) != testCase.expectedErr {
|
||||
t.Fatalf("TestPosix case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
} else {
|
||||
if _, err := fsStatVolume(pathJoin(testCase.srcFSPath, testCase.srcVol)); errorCause(err) != testCase.expectedErr {
|
||||
if _, err := fsStatVolume(pathJoin(testCase.srcFSPath, testCase.srcVol)); errors.Cause(err) != testCase.expectedErr {
|
||||
t.Fatalf("TestPosix case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
@@ -214,11 +215,11 @@ func TestFSCreateAndOpen(t *testing.T) {
|
||||
t.Fatalf("Unable to create directory, %s", err)
|
||||
}
|
||||
|
||||
if _, err = fsCreateFile("", nil, nil, 0); errorCause(err) != errInvalidArgument {
|
||||
if _, err = fsCreateFile("", nil, nil, 0); errors.Cause(err) != errInvalidArgument {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
if _, _, err = fsOpenFile("", -1); errorCause(err) != errInvalidArgument {
|
||||
if _, _, err = fsOpenFile("", -1); errors.Cause(err) != errInvalidArgument {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
@@ -252,17 +253,17 @@ func TestFSCreateAndOpen(t *testing.T) {
|
||||
|
||||
for i, testCase := range testCases {
|
||||
_, err = fsCreateFile(pathJoin(path, testCase.srcVol, testCase.srcPath), reader, nil, 0)
|
||||
if errorCause(err) != testCase.expectedErr {
|
||||
if errors.Cause(err) != testCase.expectedErr {
|
||||
t.Errorf("Test case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
_, _, err = fsOpenFile(pathJoin(path, testCase.srcVol, testCase.srcPath), 0)
|
||||
if errorCause(err) != testCase.expectedErr {
|
||||
if errors.Cause(err) != testCase.expectedErr {
|
||||
t.Errorf("Test case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to open a directory.
|
||||
if _, _, err = fsOpenFile(pathJoin(path), 0); errorCause(err) != errIsNotRegular {
|
||||
if _, _, err = fsOpenFile(pathJoin(path), 0); errors.Cause(err) != errIsNotRegular {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
}
|
||||
@@ -364,7 +365,7 @@ func TestFSDeletes(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
if err = fsDeleteFile(testCase.basePath, pathJoin(testCase.basePath, testCase.srcVol, testCase.srcPath)); errorCause(err) != testCase.expectedErr {
|
||||
if err = fsDeleteFile(testCase.basePath, pathJoin(testCase.basePath, testCase.srcVol, testCase.srcPath)); errors.Cause(err) != testCase.expectedErr {
|
||||
t.Errorf("Test case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
@@ -498,11 +499,11 @@ func TestFSRemoves(t *testing.T) {
|
||||
|
||||
for i, testCase := range testCases {
|
||||
if testCase.srcPath != "" {
|
||||
if err = fsRemoveFile(pathJoin(testCase.srcFSPath, testCase.srcVol, testCase.srcPath)); errorCause(err) != testCase.expectedErr {
|
||||
if err = fsRemoveFile(pathJoin(testCase.srcFSPath, testCase.srcVol, testCase.srcPath)); errors.Cause(err) != testCase.expectedErr {
|
||||
t.Errorf("Test case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
} else {
|
||||
if err = fsRemoveDir(pathJoin(testCase.srcFSPath, testCase.srcVol, testCase.srcPath)); errorCause(err) != testCase.expectedErr {
|
||||
if err = fsRemoveDir(pathJoin(testCase.srcFSPath, testCase.srcVol, testCase.srcPath)); errors.Cause(err) != testCase.expectedErr {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -512,11 +513,11 @@ func TestFSRemoves(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = fsRemoveAll(""); errorCause(err) != errInvalidArgument {
|
||||
if err = fsRemoveAll(""); errors.Cause(err) != errInvalidArgument {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = fsRemoveAll("my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); errorCause(err) != errFileNameTooLong {
|
||||
if err = fsRemoveAll("my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); errors.Cause(err) != errFileNameTooLong {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
+15
-14
@@ -26,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
"github.com/minio/minio/pkg/mimedb"
|
||||
"github.com/tidwall/gjson"
|
||||
@@ -166,15 +167,15 @@ func (m *fsMetaV1) WriteTo(lk *lock.LockedFile) (n int64, err error) {
|
||||
var metadataBytes []byte
|
||||
metadataBytes, err = json.Marshal(m)
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
if err = lk.Truncate(0); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
if _, err = lk.Write(metadataBytes); err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Success.
|
||||
@@ -222,16 +223,16 @@ func (m *fsMetaV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) {
|
||||
var fsMetaBuf []byte
|
||||
fi, err := lk.Stat()
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
fsMetaBuf, err = ioutil.ReadAll(io.NewSectionReader(lk, 0, fi.Size()))
|
||||
if err != nil {
|
||||
return 0, traceError(err)
|
||||
return 0, errors.Trace(err)
|
||||
}
|
||||
|
||||
if len(fsMetaBuf) == 0 {
|
||||
return 0, traceError(io.EOF)
|
||||
return 0, errors.Trace(io.EOF)
|
||||
}
|
||||
|
||||
// obtain version.
|
||||
@@ -243,7 +244,7 @@ func (m *fsMetaV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) {
|
||||
// Verify if the format is valid, return corrupted format
|
||||
// for unrecognized formats.
|
||||
if !isFSMetaValid(m.Version, m.Format) {
|
||||
return 0, traceError(errCorruptedFormat)
|
||||
return 0, errors.Trace(errCorruptedFormat)
|
||||
}
|
||||
|
||||
// obtain metadata.
|
||||
@@ -278,9 +279,9 @@ func checkLockedValidFormatFS(fsPath string) (*lock.RLockedFile, error) {
|
||||
if os.IsNotExist(err) {
|
||||
// If format.json not found then
|
||||
// its an unformatted disk.
|
||||
return nil, traceError(errUnformattedDisk)
|
||||
return nil, errors.Trace(errUnformattedDisk)
|
||||
}
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
var format = &formatConfigV1{}
|
||||
@@ -296,7 +297,7 @@ func checkLockedValidFormatFS(fsPath string) (*lock.RLockedFile, error) {
|
||||
}
|
||||
|
||||
// Always return read lock here and should be closed by the caller.
|
||||
return rlk, traceError(err)
|
||||
return rlk, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Creates a new format.json if unformatted.
|
||||
@@ -307,7 +308,7 @@ func createFormatFS(fsPath string) error {
|
||||
// file stored in minioMetaBucket(.minio.sys) directory.
|
||||
lk, err := lock.TryLockedOpenFile((fsFormatPath), os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
// Close the locked file upon return.
|
||||
defer lk.Close()
|
||||
@@ -316,7 +317,7 @@ func createFormatFS(fsPath string) error {
|
||||
// writes the new format.json
|
||||
var format = &formatConfigV1{}
|
||||
err = format.LoadFormat(lk)
|
||||
if errorCause(err) == errUnformattedDisk {
|
||||
if errors.Cause(err) == errUnformattedDisk {
|
||||
_, err = newFSFormat().WriteTo(lk)
|
||||
return err
|
||||
}
|
||||
@@ -338,10 +339,10 @@ func initFormatFS(fsPath string) (rlk *lock.RLockedFile, err error) {
|
||||
// is blocked if attempted in-turn avoiding corruption on
|
||||
// the backend disk.
|
||||
return rlk, nil
|
||||
case errorCause(err) == errUnformattedDisk:
|
||||
case errors.Cause(err) == errUnformattedDisk:
|
||||
if err = createFormatFS(fsPath); err != nil {
|
||||
// Existing write locks detected.
|
||||
if errorCause(err) == lock.ErrAlreadyLocked {
|
||||
if errors.Cause(err) == lock.ErrAlreadyLocked {
|
||||
// Lock already present, sleep and attempt again.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
|
||||
+41
-103
@@ -25,23 +25,17 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
)
|
||||
|
||||
const (
|
||||
// Expiry duration after which the multipart uploads are deemed stale.
|
||||
fsMultipartExpiry = time.Hour * 24 * 14 // 2 weeks.
|
||||
// Cleanup interval when the stale multipart cleanup is initiated.
|
||||
fsMultipartCleanupInterval = time.Hour * 24 // 24 hrs.
|
||||
)
|
||||
|
||||
// Returns if the prefix is a multipart upload.
|
||||
func (fs fsObjects) isMultipartUpload(bucket, prefix string) bool {
|
||||
uploadsIDPath := pathJoin(fs.fsPath, bucket, prefix, uploadsJSONFile)
|
||||
_, err := fsStatFile(uploadsIDPath)
|
||||
if err != nil {
|
||||
if errorCause(err) == errFileNotFound {
|
||||
if errors.Cause(err) == errFileNotFound {
|
||||
return false
|
||||
}
|
||||
errorIf(err, "Unable to access uploads.json "+uploadsIDPath)
|
||||
@@ -91,13 +85,13 @@ func (fs fsObjects) addUploadID(bucket, object, uploadID string, initiated time.
|
||||
|
||||
_, err := uploadIDs.ReadFrom(rwlk)
|
||||
// For all unexpected errors, we return.
|
||||
if err != nil && errorCause(err) != io.EOF {
|
||||
if err != nil && errors.Cause(err) != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we couldn't read anything, we assume a default
|
||||
// (empty) upload info.
|
||||
if errorCause(err) == io.EOF {
|
||||
if errors.Cause(err) == io.EOF {
|
||||
uploadIDs = newUploadsV1("fs")
|
||||
}
|
||||
|
||||
@@ -117,7 +111,7 @@ func (fs fsObjects) listMultipartUploadIDs(bucketName, objectName, uploadIDMarke
|
||||
// do not leave a stale uploads.json behind.
|
||||
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucketName, objectName))
|
||||
if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil {
|
||||
return nil, false, traceError(err)
|
||||
return nil, false, errors.Trace(err)
|
||||
}
|
||||
defer objectMPartPathLock.RUnlock()
|
||||
|
||||
@@ -127,7 +121,7 @@ func (fs fsObjects) listMultipartUploadIDs(bucketName, objectName, uploadIDMarke
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
return nil, true, nil
|
||||
}
|
||||
return nil, false, traceError(err)
|
||||
return nil, false, errors.Trace(err)
|
||||
}
|
||||
defer fs.rwPool.Close(pathJoin(fs.fsPath, minioMetaMultipartBucket, uploadsPath))
|
||||
|
||||
@@ -235,7 +229,7 @@ func (fs fsObjects) listMultipartUploadsCleanup(bucket, prefix, keyMarker, uploa
|
||||
// For any walk error return right away.
|
||||
if walkResult.err != nil {
|
||||
// File not found or Disk not found is a valid case.
|
||||
if isErrIgnored(walkResult.err, fsTreeWalkIgnoredErrs...) {
|
||||
if errors.IsErrIgnored(walkResult.err, fsTreeWalkIgnoredErrs...) {
|
||||
eof = true
|
||||
break
|
||||
}
|
||||
@@ -372,7 +366,7 @@ func (fs fsObjects) newMultipartUpload(bucket string, object string, meta map[st
|
||||
uploadsPath := pathJoin(bucket, object, uploadsJSONFile)
|
||||
rwlk, err := fs.rwPool.Create(pathJoin(fs.fsPath, minioMetaMultipartBucket, uploadsPath))
|
||||
if err != nil {
|
||||
return "", toObjectErr(traceError(err), bucket, object)
|
||||
return "", toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer rwlk.Close()
|
||||
|
||||
@@ -380,7 +374,7 @@ func (fs fsObjects) newMultipartUpload(bucket string, object string, meta map[st
|
||||
fsMetaPath := pathJoin(fs.fsPath, minioMetaMultipartBucket, uploadIDPath, fsMetaJSONFile)
|
||||
metaFile, err := fs.rwPool.Create(fsMetaPath)
|
||||
if err != nil {
|
||||
return "", toObjectErr(traceError(err), bucket, object)
|
||||
return "", toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer metaFile.Close()
|
||||
|
||||
@@ -442,7 +436,9 @@ func partToAppend(fsMeta fsMetaV1, fsAppendMeta fsMetaV1) (part objectPartInfo,
|
||||
// CopyObjectPart - similar to PutObjectPart but reads data from an existing
|
||||
// object. Internally incoming data is written to '.minio.sys/tmp' location
|
||||
// and safely renamed to '.minio.sys/multipart' for reach parts.
|
||||
func (fs fsObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int, startOffset int64, length int64) (pi PartInfo, e error) {
|
||||
func (fs fsObjects) CopyObjectPart(srcBucket, srcObject, dstBucket, dstObject, uploadID string, partID int,
|
||||
startOffset int64, length int64, metadata map[string]string) (pi PartInfo, e error) {
|
||||
|
||||
if err := checkNewMultipartArgs(srcBucket, srcObject, fs); err != nil {
|
||||
return pi, err
|
||||
}
|
||||
@@ -490,7 +486,7 @@ func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
|
||||
|
||||
// Validate input data size and it can never be less than zero.
|
||||
if data.Size() < 0 {
|
||||
return pi, toObjectErr(traceError(errInvalidArgument))
|
||||
return pi, toObjectErr(errors.Trace(errInvalidArgument))
|
||||
}
|
||||
|
||||
// Hold the lock so that two parallel complete-multipart-uploads
|
||||
@@ -505,9 +501,9 @@ func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
|
||||
uploadsPath := pathJoin(fs.fsPath, minioMetaMultipartBucket, bucket, object, uploadsJSONFile)
|
||||
if _, err := fs.rwPool.Open(uploadsPath); err != nil {
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
return pi, traceError(InvalidUploadID{UploadID: uploadID})
|
||||
return pi, errors.Trace(InvalidUploadID{UploadID: uploadID})
|
||||
}
|
||||
return pi, toObjectErr(traceError(err), bucket, object)
|
||||
return pi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer fs.rwPool.Close(uploadsPath)
|
||||
|
||||
@@ -518,9 +514,9 @@ func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
|
||||
rwlk, err := fs.rwPool.Write(fsMetaPath)
|
||||
if err != nil {
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
return pi, traceError(InvalidUploadID{UploadID: uploadID})
|
||||
return pi, errors.Trace(InvalidUploadID{UploadID: uploadID})
|
||||
}
|
||||
return pi, toObjectErr(traceError(err), bucket, object)
|
||||
return pi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer rwlk.Close()
|
||||
|
||||
@@ -550,7 +546,7 @@ func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, d
|
||||
// bytes than specified in request header.
|
||||
if bytesWritten < data.Size() {
|
||||
fsRemoveFile(fsPartPath)
|
||||
return pi, traceError(IncompleteBody{})
|
||||
return pi, errors.Trace(IncompleteBody{})
|
||||
}
|
||||
|
||||
// Delete temporary part in case of failure. If
|
||||
@@ -618,9 +614,9 @@ func (fs fsObjects) listObjectParts(bucket, object, uploadID string, partNumberM
|
||||
if err != nil {
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
// On windows oddly this is returned.
|
||||
return lpi, traceError(InvalidUploadID{UploadID: uploadID})
|
||||
return lpi, errors.Trace(InvalidUploadID{UploadID: uploadID})
|
||||
}
|
||||
return lpi, toObjectErr(traceError(err), bucket, object)
|
||||
return lpi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer fs.rwPool.Close(fsMetaPath)
|
||||
|
||||
@@ -695,7 +691,7 @@ func (fs fsObjects) ListObjectParts(bucket, object, uploadID string, partNumberM
|
||||
// do not leave a stale uploads.json behind.
|
||||
objectMPartPathLock := globalNSMutex.NewNSLock(minioMetaMultipartBucket, pathJoin(bucket, object))
|
||||
if err := objectMPartPathLock.GetRLock(globalListingTimeout); err != nil {
|
||||
return lpi, traceError(err)
|
||||
return lpi, errors.Trace(err)
|
||||
}
|
||||
defer objectMPartPathLock.RUnlock()
|
||||
|
||||
@@ -720,7 +716,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
|
||||
// Check if an object is present as one of the parent dir.
|
||||
if fs.parentDirIsObject(bucket, pathutil.Dir(object)) {
|
||||
return oi, toObjectErr(traceError(errFileAccessDenied), bucket, object)
|
||||
return oi, toObjectErr(errors.Trace(errFileAccessDenied), bucket, object)
|
||||
}
|
||||
|
||||
if _, err := fs.statBucketDir(bucket); err != nil {
|
||||
@@ -747,7 +743,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
if removeObjectDir {
|
||||
basePath := pathJoin(fs.fsPath, minioMetaMultipartBucket, bucket)
|
||||
derr := fsDeleteFile(basePath, pathJoin(basePath, object))
|
||||
if derr = errorCause(derr); derr != nil {
|
||||
if derr = errors.Cause(derr); derr != nil {
|
||||
// In parallel execution, CompleteMultipartUpload could have deleted temporary
|
||||
// state files/directory, it is safe to ignore errFileNotFound
|
||||
if derr != errFileNotFound {
|
||||
@@ -762,9 +758,9 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
rlk, err := fs.rwPool.Open(fsMetaPathMultipart)
|
||||
if err != nil {
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
return oi, traceError(InvalidUploadID{UploadID: uploadID})
|
||||
return oi, errors.Trace(InvalidUploadID{UploadID: uploadID})
|
||||
}
|
||||
return oi, toObjectErr(traceError(err), bucket, object)
|
||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Disallow any parallel abort or complete multipart operations.
|
||||
@@ -772,9 +768,9 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
if err != nil {
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
return oi, traceError(InvalidUploadID{UploadID: uploadID})
|
||||
return oi, errors.Trace(InvalidUploadID{UploadID: uploadID})
|
||||
}
|
||||
return oi, toObjectErr(traceError(err), bucket, object)
|
||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer rwlk.Close()
|
||||
|
||||
@@ -792,18 +788,18 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
partIdx := fsMeta.ObjectPartIndex(part.PartNumber)
|
||||
if partIdx == -1 {
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
return oi, traceError(InvalidPart{})
|
||||
return oi, errors.Trace(InvalidPart{})
|
||||
}
|
||||
|
||||
if fsMeta.Parts[partIdx].ETag != part.ETag {
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
return oi, traceError(InvalidPart{})
|
||||
return oi, errors.Trace(InvalidPart{})
|
||||
}
|
||||
|
||||
// All parts except the last part has to be atleast 5MB.
|
||||
if (i < len(parts)-1) && !isMinAllowedPartSize(fsMeta.Parts[partIdx].Size) {
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
return oi, traceError(PartTooSmall{
|
||||
return oi, errors.Trace(PartTooSmall{
|
||||
PartNumber: part.PartNumber,
|
||||
PartSize: fsMeta.Parts[partIdx].Size,
|
||||
PartETag: part.ETag,
|
||||
@@ -821,7 +817,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
// renamed to the main name-space.
|
||||
if (i < len(parts)-1) && partSize != fsMeta.Parts[partIdx].Size {
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
return oi, traceError(PartsSizeUnequal{})
|
||||
return oi, errors.Trace(PartsSizeUnequal{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,7 +827,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
metaFile, err := fs.rwPool.Create(fsMetaPath)
|
||||
if err != nil {
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
return oi, toObjectErr(traceError(err), bucket, object)
|
||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer metaFile.Close()
|
||||
|
||||
@@ -877,9 +873,9 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
if err != nil {
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
if err == errFileNotFound {
|
||||
return oi, traceError(InvalidPart{})
|
||||
return oi, errors.Trace(InvalidPart{})
|
||||
}
|
||||
return oi, toObjectErr(traceError(err), minioMetaMultipartBucket, partSuffix)
|
||||
return oi, toObjectErr(errors.Trace(err), minioMetaMultipartBucket, partSuffix)
|
||||
}
|
||||
|
||||
// No need to hold a lock, this is a unique file and will be only written
|
||||
@@ -889,7 +885,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
if err != nil {
|
||||
reader.Close()
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
return oi, toObjectErr(traceError(err), bucket, object)
|
||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
_, err = io.CopyBuffer(wfile, reader, buf)
|
||||
@@ -897,7 +893,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
wfile.Close()
|
||||
reader.Close()
|
||||
fs.rwPool.Close(fsMetaPathMultipart)
|
||||
return oi, toObjectErr(traceError(err), bucket, object)
|
||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
wfile.Close()
|
||||
@@ -988,7 +984,7 @@ func (fs fsObjects) AbortMultipartUpload(bucket, object, uploadID string) error
|
||||
if removeObjectDir {
|
||||
basePath := pathJoin(fs.fsPath, minioMetaMultipartBucket, bucket)
|
||||
derr := fsDeleteFile(basePath, pathJoin(basePath, object))
|
||||
if derr = errorCause(derr); derr != nil {
|
||||
if derr = errors.Cause(derr); derr != nil {
|
||||
// In parallel execution, AbortMultipartUpload could have deleted temporary
|
||||
// state files/directory, it is safe to ignore errFileNotFound
|
||||
if derr != errFileNotFound {
|
||||
@@ -1002,9 +998,9 @@ func (fs fsObjects) AbortMultipartUpload(bucket, object, uploadID string) error
|
||||
fsMetaPath := pathJoin(fs.fsPath, minioMetaMultipartBucket, uploadIDPath, fsMetaJSONFile)
|
||||
if _, err := fs.rwPool.Open(fsMetaPath); err != nil {
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
return traceError(InvalidUploadID{UploadID: uploadID})
|
||||
return errors.Trace(InvalidUploadID{UploadID: uploadID})
|
||||
}
|
||||
return toObjectErr(traceError(err), bucket, object)
|
||||
return toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
uploadsPath := pathJoin(bucket, object, uploadsJSONFile)
|
||||
@@ -1012,9 +1008,9 @@ func (fs fsObjects) AbortMultipartUpload(bucket, object, uploadID string) error
|
||||
if err != nil {
|
||||
fs.rwPool.Close(fsMetaPath)
|
||||
if err == errFileNotFound || err == errFileAccessDenied {
|
||||
return traceError(InvalidUploadID{UploadID: uploadID})
|
||||
return errors.Trace(InvalidUploadID{UploadID: uploadID})
|
||||
}
|
||||
return toObjectErr(traceError(err), bucket, object)
|
||||
return toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer rwlk.Close()
|
||||
|
||||
@@ -1041,61 +1037,3 @@ func (fs fsObjects) AbortMultipartUpload(bucket, object, uploadID string) error
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removes multipart uploads if any older than `expiry` duration in a given bucket.
|
||||
func (fs fsObjects) cleanupStaleMultipartUpload(bucket string, expiry time.Duration) (err error) {
|
||||
var lmi ListMultipartsInfo
|
||||
var st os.FileInfo
|
||||
for {
|
||||
// List multipart uploads in a bucket 1000 at a time
|
||||
prefix := ""
|
||||
lmi, err = fs.listMultipartUploadsCleanup(bucket, prefix, lmi.KeyMarker, lmi.UploadIDMarker, "", 1000)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to list uploads")
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove uploads (and its parts) older than expiry duration.
|
||||
for _, upload := range lmi.Uploads {
|
||||
uploadIDPath := pathJoin(fs.fsPath, minioMetaMultipartBucket, bucket, upload.Object, upload.UploadID)
|
||||
if st, err = fsStatDir(uploadIDPath); err != nil {
|
||||
errorIf(err, "Failed to lookup uploads directory path %s", uploadIDPath)
|
||||
continue
|
||||
}
|
||||
if time.Since(st.ModTime()) > expiry {
|
||||
fs.AbortMultipartUpload(bucket, upload.Object, upload.UploadID)
|
||||
}
|
||||
}
|
||||
|
||||
// No more incomplete uploads remain, break and return.
|
||||
if !lmi.IsTruncated {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removes multipart uploads if any older than `expiry` duration
|
||||
// on all buckets for every `cleanupInterval`, this function is
|
||||
// blocking and should be run in a go-routine.
|
||||
func (fs fsObjects) cleanupStaleMultipartUploads(cleanupInterval, expiry time.Duration, doneCh chan struct{}) {
|
||||
ticker := time.NewTicker(cleanupInterval)
|
||||
for {
|
||||
select {
|
||||
case <-doneCh:
|
||||
// Stop the timer.
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
bucketInfos, err := fs.ListBuckets()
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to list buckets")
|
||||
continue
|
||||
}
|
||||
for _, bucketInfo := range bucketInfos {
|
||||
fs.cleanupStaleMultipartUpload(bucketInfo.Name, expiry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,11 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// Tests cleanup multipart uploads for filesystem backend.
|
||||
func TestFSCleanupMultipartUploadsInRoutine(t *testing.T) {
|
||||
// Prepare for tests
|
||||
disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
@@ -45,7 +48,7 @@ func TestFSCleanupMultipartUploadsInRoutine(t *testing.T) {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
go fs.cleanupStaleMultipartUploads(20*time.Millisecond, 0, globalServiceDoneCh)
|
||||
go cleanupStaleMultipartUploads(20*time.Millisecond, 0, obj, fs.listMultipartUploadsCleanup, globalServiceDoneCh)
|
||||
|
||||
// Wait for 40ms such that - we have given enough time for
|
||||
// cleanup routine to kick in.
|
||||
@@ -56,7 +59,7 @@ func TestFSCleanupMultipartUploadsInRoutine(t *testing.T) {
|
||||
|
||||
// Check if upload id was already purged.
|
||||
if err = obj.AbortMultipartUpload(bucketName, objectName, uploadID); err != nil {
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
if _, ok := err.(InvalidUploadID); !ok {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
@@ -87,13 +90,13 @@ func TestFSCleanupMultipartUpload(t *testing.T) {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
if err = fs.cleanupStaleMultipartUpload(bucketName, 0); err != nil {
|
||||
if err = cleanupStaleMultipartUpload(bucketName, 0, obj, fs.listMultipartUploadsCleanup); err != nil {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
|
||||
// Check if upload id was already purged.
|
||||
if err = obj.AbortMultipartUpload(bucketName, objectName, uploadID); err != nil {
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
if _, ok := err.(InvalidUploadID); !ok {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
@@ -122,7 +125,7 @@ func TestFSWriteUploadJSON(t *testing.T) {
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
_, err = obj.NewMultipartUpload(bucketName, objectName, nil)
|
||||
if err != nil {
|
||||
if _, ok := errorCause(err).(BucketNotFound); !ok {
|
||||
if _, ok := errors.Cause(err).(BucketNotFound); !ok {
|
||||
t.Fatal("Unexpected err: ", err)
|
||||
}
|
||||
}
|
||||
@@ -146,7 +149,7 @@ func TestNewMultipartUploadFaultyDisk(t *testing.T) {
|
||||
// Test with disk removed.
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
if _, err := fs.NewMultipartUpload(bucketName, objectName, map[string]string{"X-Amz-Meta-xid": "3f"}); err != nil {
|
||||
if !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error ", err)
|
||||
}
|
||||
}
|
||||
@@ -184,7 +187,7 @@ func TestPutObjectPartFaultyDisk(t *testing.T) {
|
||||
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
_, err = fs.PutObjectPart(bucketName, objectName, uploadID, 1, mustGetHashReader(t, bytes.NewReader(data), dataLen, md5Hex, sha256sum))
|
||||
if !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error ", err)
|
||||
}
|
||||
}
|
||||
@@ -220,7 +223,7 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) {
|
||||
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
if _, err := fs.CompleteMultipartUpload(bucketName, objectName, uploadID, parts); err != nil {
|
||||
if !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error ", err)
|
||||
}
|
||||
}
|
||||
@@ -323,7 +326,7 @@ func TestListMultipartUploadsFaultyDisk(t *testing.T) {
|
||||
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
if _, err := fs.ListMultipartUploads(bucketName, objectName, "", "", "", 1000); err != nil {
|
||||
if !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error ", err)
|
||||
}
|
||||
}
|
||||
|
||||
+36
-35
@@ -27,6 +27,7 @@ import (
|
||||
"sort"
|
||||
"syscall"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
)
|
||||
@@ -155,8 +156,8 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) {
|
||||
return nil, fmt.Errorf("Unable to initialize event notification. %s", err)
|
||||
}
|
||||
|
||||
// Start background process to cleanup old files in `.minio.sys`.
|
||||
go fs.cleanupStaleMultipartUploads(fsMultipartCleanupInterval, fsMultipartExpiry, globalServiceDoneCh)
|
||||
// Start background process to cleanup old multipart objects in `.minio.sys`.
|
||||
go cleanupStaleMultipartUploads(multipartCleanupInterval, multipartExpiry, fs, fs.listMultipartUploadsCleanup, globalServiceDoneCh)
|
||||
|
||||
// Return successfully initialized object layer.
|
||||
return fs, nil
|
||||
@@ -190,7 +191,7 @@ func (fs fsObjects) StorageInfo() StorageInfo {
|
||||
func (fs fsObjects) getBucketDir(bucket string) (string, error) {
|
||||
// Verify if bucket is valid.
|
||||
if !IsValidBucketName(bucket) {
|
||||
return "", traceError(BucketNameInvalid{Bucket: bucket})
|
||||
return "", errors.Trace(BucketNameInvalid{Bucket: bucket})
|
||||
}
|
||||
|
||||
bucketDir := pathJoin(fs.fsPath, bucket)
|
||||
@@ -242,12 +243,12 @@ func (fs fsObjects) GetBucketInfo(bucket string) (bi BucketInfo, e error) {
|
||||
// ListBuckets - list all s3 compatible buckets (directories) at fsPath.
|
||||
func (fs fsObjects) ListBuckets() ([]BucketInfo, error) {
|
||||
if err := checkPathLength(fs.fsPath); err != nil {
|
||||
return nil, traceError(err)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
var bucketInfos []BucketInfo
|
||||
entries, err := readDir((fs.fsPath))
|
||||
if err != nil {
|
||||
return nil, toObjectErr(traceError(errDiskNotFound))
|
||||
return nil, toObjectErr(errors.Trace(errDiskNotFound))
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
@@ -330,7 +331,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string
|
||||
var wlk *lock.LockedFile
|
||||
wlk, err = fs.rwPool.Write(fsMetaPath)
|
||||
if err != nil {
|
||||
return oi, toObjectErr(traceError(err), srcBucket, srcObject)
|
||||
return oi, toObjectErr(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
// This close will allow for locks to be synchronized on `fs.json`.
|
||||
defer wlk.Close()
|
||||
@@ -395,25 +396,25 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64,
|
||||
|
||||
// Offset cannot be negative.
|
||||
if offset < 0 {
|
||||
return toObjectErr(traceError(errUnexpected), bucket, object)
|
||||
return toObjectErr(errors.Trace(errUnexpected), bucket, object)
|
||||
}
|
||||
|
||||
// Writer cannot be nil.
|
||||
if writer == nil {
|
||||
return toObjectErr(traceError(errUnexpected), bucket, object)
|
||||
return toObjectErr(errors.Trace(errUnexpected), bucket, object)
|
||||
}
|
||||
|
||||
// If its a directory request, we return an empty body.
|
||||
if hasSuffix(object, slashSeparator) {
|
||||
_, err = writer.Write([]byte(""))
|
||||
return toObjectErr(traceError(err), bucket, object)
|
||||
return toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
if bucket != minioMetaBucket {
|
||||
fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile)
|
||||
_, err = fs.rwPool.Open(fsMetaPath)
|
||||
if err != nil && err != errFileNotFound {
|
||||
return toObjectErr(traceError(err), bucket, object)
|
||||
return toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer fs.rwPool.Close(fsMetaPath)
|
||||
}
|
||||
@@ -438,7 +439,7 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64,
|
||||
|
||||
// Reply back invalid range if the input offset and length fall out of range.
|
||||
if offset > size || offset+length > size {
|
||||
return traceError(InvalidRange{offset, length, size})
|
||||
return errors.Trace(InvalidRange{offset, length, size})
|
||||
}
|
||||
|
||||
// Allocate a staging buffer.
|
||||
@@ -446,14 +447,14 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64,
|
||||
|
||||
_, err = io.CopyBuffer(writer, io.LimitReader(reader, length), buf)
|
||||
|
||||
return toObjectErr(traceError(err), bucket, object)
|
||||
return toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// getObjectInfo - wrapper for reading object metadata and constructs ObjectInfo.
|
||||
func (fs fsObjects) getObjectInfo(bucket, object string) (oi ObjectInfo, e error) {
|
||||
fsMeta := fsMetaV1{}
|
||||
fi, err := fsStatDir(pathJoin(fs.fsPath, bucket, object))
|
||||
if err != nil && errorCause(err) != errFileAccessDenied {
|
||||
if err != nil && errors.Cause(err) != errFileAccessDenied {
|
||||
return oi, toObjectErr(err, bucket, object)
|
||||
}
|
||||
if fi != nil {
|
||||
@@ -477,7 +478,7 @@ func (fs fsObjects) getObjectInfo(bucket, object string) (oi ObjectInfo, e error
|
||||
// `fs.json` can be empty due to previously failed
|
||||
// PutObject() transaction, if we arrive at such
|
||||
// a situation we just ignore and continue.
|
||||
if errorCause(rerr) != io.EOF {
|
||||
if errors.Cause(rerr) != io.EOF {
|
||||
return oi, toObjectErr(rerr, bucket, object)
|
||||
}
|
||||
}
|
||||
@@ -485,7 +486,7 @@ func (fs fsObjects) getObjectInfo(bucket, object string) (oi ObjectInfo, e error
|
||||
|
||||
// Ignore if `fs.json` is not available, this is true for pre-existing data.
|
||||
if err != nil && err != errFileNotFound {
|
||||
return oi, toObjectErr(traceError(err), bucket, object)
|
||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Stat the file to get file size.
|
||||
@@ -501,14 +502,14 @@ func (fs fsObjects) getObjectInfo(bucket, object string) (oi ObjectInfo, e error
|
||||
func checkBucketAndObjectNamesFS(bucket, object string) error {
|
||||
// Verify if bucket is valid.
|
||||
if !IsValidBucketName(bucket) {
|
||||
return traceError(BucketNameInvalid{Bucket: bucket})
|
||||
return errors.Trace(BucketNameInvalid{Bucket: bucket})
|
||||
}
|
||||
// Verify if object is valid.
|
||||
if len(object) == 0 {
|
||||
return traceError(ObjectNameInvalid{Bucket: bucket, Object: object})
|
||||
return errors.Trace(ObjectNameInvalid{Bucket: bucket, Object: object})
|
||||
}
|
||||
if !IsValidObjectPrefix(object) {
|
||||
return traceError(ObjectNameInvalid{Bucket: bucket, Object: object})
|
||||
return errors.Trace(ObjectNameInvalid{Bucket: bucket, Object: object})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -572,7 +573,7 @@ func (fs fsObjects) PutObject(bucket string, object string, data *hash.Reader, m
|
||||
if isObjectDir(object, data.Size()) {
|
||||
// Check if an object is present as one of the parent dir.
|
||||
if fs.parentDirIsObject(bucket, path.Dir(object)) {
|
||||
return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object)
|
||||
return ObjectInfo{}, toObjectErr(errors.Trace(errFileAccessDenied), bucket, object)
|
||||
}
|
||||
if err = fsMkdirAll(pathJoin(fs.fsPath, bucket, object)); err != nil {
|
||||
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||
@@ -590,12 +591,12 @@ func (fs fsObjects) PutObject(bucket string, object string, data *hash.Reader, m
|
||||
|
||||
// Check if an object is present as one of the parent dir.
|
||||
if fs.parentDirIsObject(bucket, path.Dir(object)) {
|
||||
return ObjectInfo{}, toObjectErr(traceError(errFileAccessDenied), bucket, object)
|
||||
return ObjectInfo{}, toObjectErr(errors.Trace(errFileAccessDenied), bucket, object)
|
||||
}
|
||||
|
||||
// Validate input data size and it can never be less than zero.
|
||||
if data.Size() < 0 {
|
||||
return ObjectInfo{}, traceError(errInvalidArgument)
|
||||
return ObjectInfo{}, errors.Trace(errInvalidArgument)
|
||||
}
|
||||
|
||||
var wlk *lock.LockedFile
|
||||
@@ -604,7 +605,7 @@ func (fs fsObjects) PutObject(bucket string, object string, data *hash.Reader, m
|
||||
fsMetaPath := pathJoin(bucketMetaDir, bucket, object, fsMetaJSONFile)
|
||||
wlk, err = fs.rwPool.Create(fsMetaPath)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, toObjectErr(traceError(err), bucket, object)
|
||||
return ObjectInfo{}, toObjectErr(errors.Trace(err), bucket, object)
|
||||
}
|
||||
// This close will allow for locks to be synchronized on `fs.json`.
|
||||
defer wlk.Close()
|
||||
@@ -643,7 +644,7 @@ func (fs fsObjects) PutObject(bucket string, object string, data *hash.Reader, m
|
||||
// bytes than specified in request header.
|
||||
if bytesWritten < data.Size() {
|
||||
fsRemoveFile(fsTmpObjPath)
|
||||
return ObjectInfo{}, traceError(IncompleteBody{})
|
||||
return ObjectInfo{}, errors.Trace(IncompleteBody{})
|
||||
}
|
||||
|
||||
// Delete the temporary object in the case of a
|
||||
@@ -694,7 +695,7 @@ func (fs fsObjects) DeleteObject(bucket, object string) error {
|
||||
defer rwlk.Close()
|
||||
}
|
||||
if lerr != nil && lerr != errFileNotFound {
|
||||
return toObjectErr(traceError(lerr), bucket, object)
|
||||
return toObjectErr(errors.Trace(lerr), bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +707,7 @@ func (fs fsObjects) DeleteObject(bucket, object string) error {
|
||||
if bucket != minioMetaBucket {
|
||||
// Delete the metadata object.
|
||||
err := fsDeleteFile(minioMetaBucketDir, fsMetaPath)
|
||||
if err != nil && errorCause(err) != errFileNotFound {
|
||||
if err != nil && errors.Cause(err) != errFileNotFound {
|
||||
return toObjectErr(err, bucket, object)
|
||||
}
|
||||
}
|
||||
@@ -747,7 +748,7 @@ func (fs fsObjects) getObjectETag(bucket, entry string) (string, error) {
|
||||
rlk, err := fs.rwPool.Open(fsMetaPath)
|
||||
// Ignore if `fs.json` is not available, this is true for pre-existing data.
|
||||
if err != nil && err != errFileNotFound {
|
||||
return "", toObjectErr(traceError(err), bucket, entry)
|
||||
return "", toObjectErr(errors.Trace(err), bucket, entry)
|
||||
}
|
||||
|
||||
// If file is not found, we don't need to proceed forward.
|
||||
@@ -761,7 +762,7 @@ func (fs fsObjects) getObjectETag(bucket, entry string) (string, error) {
|
||||
// Fetch the size of the underlying file.
|
||||
fi, err := rlk.LockedFile.Stat()
|
||||
if err != nil {
|
||||
return "", toObjectErr(traceError(err), bucket, entry)
|
||||
return "", toObjectErr(errors.Trace(err), bucket, entry)
|
||||
}
|
||||
|
||||
// `fs.json` can be empty due to previously failed
|
||||
@@ -775,12 +776,12 @@ func (fs fsObjects) getObjectETag(bucket, entry string) (string, error) {
|
||||
// make sure the underlying offsets don't move.
|
||||
fsMetaBuf, err := ioutil.ReadAll(io.NewSectionReader(rlk.LockedFile, 0, fi.Size()))
|
||||
if err != nil {
|
||||
return "", traceError(err)
|
||||
return "", errors.Trace(err)
|
||||
}
|
||||
|
||||
// Check if FS metadata is valid, if not return error.
|
||||
if !isFSMetaValid(parseFSVersion(fsMetaBuf), parseFSFormat(fsMetaBuf)) {
|
||||
return "", toObjectErr(traceError(errCorruptedFormat), bucket, entry)
|
||||
return "", toObjectErr(errors.Trace(errCorruptedFormat), bucket, entry)
|
||||
}
|
||||
|
||||
return extractETag(parseFSMetaMap(fsMetaBuf)), nil
|
||||
@@ -902,7 +903,7 @@ func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKey
|
||||
// For any walk error return right away.
|
||||
if walkResult.err != nil {
|
||||
// File not found is a valid case.
|
||||
if errorCause(walkResult.err) == errFileNotFound {
|
||||
if errors.Cause(walkResult.err) == errFileNotFound {
|
||||
return loi, nil
|
||||
}
|
||||
return loi, toObjectErr(walkResult.err, bucket, prefix)
|
||||
@@ -943,25 +944,25 @@ func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKey
|
||||
|
||||
// HealObject - no-op for fs. Valid only for XL.
|
||||
func (fs fsObjects) HealObject(bucket, object string) (int, int, error) {
|
||||
return 0, 0, traceError(NotImplemented{})
|
||||
return 0, 0, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// HealBucket - no-op for fs, Valid only for XL.
|
||||
func (fs fsObjects) HealBucket(bucket string) error {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListObjectsHeal - list all objects to be healed. Valid only for XL
|
||||
func (fs fsObjects) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, e error) {
|
||||
return loi, traceError(NotImplemented{})
|
||||
return loi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListBucketsHeal - list all buckets to be healed. Valid only for XL
|
||||
func (fs fsObjects) ListBucketsHeal() ([]BucketInfo, error) {
|
||||
return []BucketInfo{}, traceError(NotImplemented{})
|
||||
return []BucketInfo{}, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
func (fs fsObjects) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker,
|
||||
delimiter string, maxUploads int) (lmi ListMultipartsInfo, e error) {
|
||||
return lmi, traceError(NotImplemented{})
|
||||
return lmi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
+20
-18
@@ -22,6 +22,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// Tests for if parent directory is object
|
||||
@@ -178,7 +180,7 @@ func TestFSGetBucketInfo(t *testing.T) {
|
||||
|
||||
// Test with inexistant bucket
|
||||
_, err = fs.GetBucketInfo("a")
|
||||
if !isSameType(errorCause(err), BucketNameInvalid{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNameInvalid{}) {
|
||||
t.Fatal("BucketNameInvalid error not returned")
|
||||
}
|
||||
|
||||
@@ -186,7 +188,7 @@ func TestFSGetBucketInfo(t *testing.T) {
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
|
||||
_, err = fs.GetBucketInfo(bucketName)
|
||||
if !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("BucketNotFound error not returned")
|
||||
}
|
||||
}
|
||||
@@ -209,7 +211,7 @@ func TestFSPutObject(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected should fail here, bucket doesn't exist")
|
||||
}
|
||||
if _, ok := errorCause(err).(BucketNotFound); !ok {
|
||||
if _, ok := errors.Cause(err).(BucketNotFound); !ok {
|
||||
t.Fatalf("Expected error type BucketNotFound, got %#v", err)
|
||||
}
|
||||
|
||||
@@ -218,7 +220,7 @@ func TestFSPutObject(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected should fail here, bucket doesn't exist")
|
||||
}
|
||||
if _, ok := errorCause(err).(BucketNotFound); !ok {
|
||||
if _, ok := errors.Cause(err).(BucketNotFound); !ok {
|
||||
t.Fatalf("Expected error type BucketNotFound, got %#v", err)
|
||||
}
|
||||
|
||||
@@ -230,7 +232,7 @@ func TestFSPutObject(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected should fail here, backend corruption occurred")
|
||||
}
|
||||
if nerr, ok := errorCause(err).(PrefixAccessDenied); !ok {
|
||||
if nerr, ok := errors.Cause(err).(PrefixAccessDenied); !ok {
|
||||
t.Fatalf("Expected PrefixAccessDenied, got %#v", err)
|
||||
} else {
|
||||
if nerr.Bucket != "bucket" {
|
||||
@@ -245,7 +247,7 @@ func TestFSPutObject(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected should fail here, backned corruption occurred")
|
||||
}
|
||||
if nerr, ok := errorCause(err).(PrefixAccessDenied); !ok {
|
||||
if nerr, ok := errors.Cause(err).(PrefixAccessDenied); !ok {
|
||||
t.Fatalf("Expected PrefixAccessDenied, got %#v", err)
|
||||
} else {
|
||||
if nerr.Bucket != "bucket" {
|
||||
@@ -272,19 +274,19 @@ func TestFSDeleteObject(t *testing.T) {
|
||||
obj.PutObject(bucketName, objectName, mustGetHashReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil)
|
||||
|
||||
// Test with invalid bucket name
|
||||
if err := fs.DeleteObject("fo", objectName); !isSameType(errorCause(err), BucketNameInvalid{}) {
|
||||
if err := fs.DeleteObject("fo", objectName); !isSameType(errors.Cause(err), BucketNameInvalid{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
// Test with bucket does not exist
|
||||
if err := fs.DeleteObject("foobucket", "fooobject"); !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if err := fs.DeleteObject("foobucket", "fooobject"); !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
// Test with invalid object name
|
||||
if err := fs.DeleteObject(bucketName, "\\"); !isSameType(errorCause(err), ObjectNameInvalid{}) {
|
||||
if err := fs.DeleteObject(bucketName, "\\"); !isSameType(errors.Cause(err), ObjectNameInvalid{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
// Test with object does not exist.
|
||||
if err := fs.DeleteObject(bucketName, "foooobject"); !isSameType(errorCause(err), ObjectNotFound{}) {
|
||||
if err := fs.DeleteObject(bucketName, "foooobject"); !isSameType(errors.Cause(err), ObjectNotFound{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
// Test with valid condition
|
||||
@@ -295,7 +297,7 @@ func TestFSDeleteObject(t *testing.T) {
|
||||
// Delete object should err disk not found.
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
if err := fs.DeleteObject(bucketName, objectName); err != nil {
|
||||
if !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -318,11 +320,11 @@ func TestFSDeleteBucket(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test with an invalid bucket name
|
||||
if err = fs.DeleteBucket("fo"); !isSameType(errorCause(err), BucketNameInvalid{}) {
|
||||
if err = fs.DeleteBucket("fo"); !isSameType(errors.Cause(err), BucketNameInvalid{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
// Test with an inexistant bucket
|
||||
if err = fs.DeleteBucket("foobucket"); !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if err = fs.DeleteBucket("foobucket"); !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
// Test with a valid case
|
||||
@@ -335,7 +337,7 @@ func TestFSDeleteBucket(t *testing.T) {
|
||||
// Delete bucket should get error disk not found.
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
if err = fs.DeleteBucket(bucketName); err != nil {
|
||||
if !isSameType(errorCause(err), BucketNotFound{}) {
|
||||
if !isSameType(errors.Cause(err), BucketNotFound{}) {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -378,7 +380,7 @@ func TestFSListBuckets(t *testing.T) {
|
||||
fs.fsPath = filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||
|
||||
if _, err := fs.ListBuckets(); err != nil {
|
||||
if errorCause(err) != errDiskNotFound {
|
||||
if errors.Cause(err) != errDiskNotFound {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -386,7 +388,7 @@ func TestFSListBuckets(t *testing.T) {
|
||||
longPath := fmt.Sprintf("%0256d", 1)
|
||||
fs.fsPath = longPath
|
||||
if _, err := fs.ListBuckets(); err != nil {
|
||||
if errorCause(err) != errFileNameTooLong {
|
||||
if errors.Cause(err) != errFileNameTooLong {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -399,7 +401,7 @@ func TestFSHealObject(t *testing.T) {
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
_, _, err := obj.HealObject("bucket", "object")
|
||||
if err == nil || !isSameType(errorCause(err), NotImplemented{}) {
|
||||
if err == nil || !isSameType(errors.Cause(err), NotImplemented{}) {
|
||||
t.Fatalf("Heal Object should return NotImplemented error ")
|
||||
}
|
||||
}
|
||||
@@ -411,7 +413,7 @@ func TestFSListObjectsHeal(t *testing.T) {
|
||||
|
||||
obj := initFSObjects(disk, t)
|
||||
_, err := obj.ListObjectsHeal("bucket", "prefix", "marker", "delimiter", 1000)
|
||||
if err == nil || !isSameType(errorCause(err), NotImplemented{}) {
|
||||
if err == nil || !isSameType(errors.Cause(err), NotImplemented{}) {
|
||||
t.Fatalf("Heal Object should return NotImplemented error ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func anonErrToObjectErr(statusCode int, params ...string) error {
|
||||
bucket := ""
|
||||
object := ""
|
||||
if len(params) >= 1 {
|
||||
bucket = params[0]
|
||||
}
|
||||
if len(params) == 2 {
|
||||
object = params[1]
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusNotFound:
|
||||
if object != "" {
|
||||
return ObjectNotFound{bucket, object}
|
||||
}
|
||||
return BucketNotFound{Bucket: bucket}
|
||||
case http.StatusBadRequest:
|
||||
if object != "" {
|
||||
return ObjectNameInvalid{bucket, object}
|
||||
}
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
case http.StatusForbidden:
|
||||
fallthrough
|
||||
case http.StatusUnauthorized:
|
||||
return AllAccessDisabled{bucket, object}
|
||||
}
|
||||
|
||||
return errUnexpected
|
||||
}
|
||||
|
||||
// newCustomHTTPTransport returns a new http configuration
|
||||
// used while communicating with the cloud backends.
|
||||
// This sets the value for MaxIdleConns from 2 (go default) to
|
||||
// 100.
|
||||
func newCustomHTTPTransport() http.RoundTripper {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{RootCAs: globalRootCAs},
|
||||
DisableCompression: true,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
|
||||
minio "github.com/minio/minio-go"
|
||||
)
|
||||
|
||||
var (
|
||||
// CanonicalizeETag provides canonicalizeETag function alias.
|
||||
CanonicalizeETag = canonicalizeETag
|
||||
|
||||
// MustGetUUID function alias.
|
||||
MustGetUUID = mustGetUUID
|
||||
|
||||
// ErrorIf provides errorIf function alias.
|
||||
ErrorIf = errorIf
|
||||
|
||||
// FatalIf provides fatalIf function alias.
|
||||
FatalIf = fatalIf
|
||||
)
|
||||
|
||||
// AnonErrToObjectErr - converts standard http codes into meaningful object layer errors.
|
||||
func AnonErrToObjectErr(statusCode int, params ...string) error {
|
||||
bucket := ""
|
||||
object := ""
|
||||
if len(params) >= 1 {
|
||||
bucket = params[0]
|
||||
}
|
||||
if len(params) == 2 {
|
||||
object = params[1]
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusNotFound:
|
||||
if object != "" {
|
||||
return ObjectNotFound{bucket, object}
|
||||
}
|
||||
return BucketNotFound{Bucket: bucket}
|
||||
case http.StatusBadRequest:
|
||||
if object != "" {
|
||||
return ObjectNameInvalid{bucket, object}
|
||||
}
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
case http.StatusForbidden:
|
||||
fallthrough
|
||||
case http.StatusUnauthorized:
|
||||
return AllAccessDisabled{bucket, object}
|
||||
}
|
||||
|
||||
return errUnexpected
|
||||
}
|
||||
|
||||
// FromMinioClientMetadata converts minio metadata to map[string]string
|
||||
func FromMinioClientMetadata(metadata map[string][]string) map[string]string {
|
||||
mm := map[string]string{}
|
||||
for k, v := range metadata {
|
||||
mm[http.CanonicalHeaderKey(k)] = v[0]
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
||||
// FromMinioClientObjectPart converts minio ObjectPart to PartInfo
|
||||
func FromMinioClientObjectPart(op minio.ObjectPart) PartInfo {
|
||||
return PartInfo{
|
||||
Size: op.Size,
|
||||
ETag: canonicalizeETag(op.ETag),
|
||||
LastModified: op.LastModified,
|
||||
PartNumber: op.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// FromMinioClientListPartsInfo converts minio ListObjectPartsResult to ListPartsInfo
|
||||
func FromMinioClientListPartsInfo(lopr minio.ListObjectPartsResult) ListPartsInfo {
|
||||
// Convert minio ObjectPart to PartInfo
|
||||
fromMinioClientObjectParts := func(parts []minio.ObjectPart) []PartInfo {
|
||||
toParts := make([]PartInfo, len(parts))
|
||||
for i, part := range parts {
|
||||
toParts[i] = FromMinioClientObjectPart(part)
|
||||
}
|
||||
return toParts
|
||||
}
|
||||
|
||||
return ListPartsInfo{
|
||||
UploadID: lopr.UploadID,
|
||||
Bucket: lopr.Bucket,
|
||||
Object: lopr.Key,
|
||||
StorageClass: "",
|
||||
PartNumberMarker: lopr.PartNumberMarker,
|
||||
NextPartNumberMarker: lopr.NextPartNumberMarker,
|
||||
MaxParts: lopr.MaxParts,
|
||||
IsTruncated: lopr.IsTruncated,
|
||||
EncodingType: lopr.EncodingType,
|
||||
Parts: fromMinioClientObjectParts(lopr.ObjectParts),
|
||||
}
|
||||
}
|
||||
|
||||
// FromMinioClientListMultipartsInfo converts minio ListMultipartUploadsResult to ListMultipartsInfo
|
||||
func FromMinioClientListMultipartsInfo(lmur minio.ListMultipartUploadsResult) ListMultipartsInfo {
|
||||
uploads := make([]MultipartInfo, len(lmur.Uploads))
|
||||
|
||||
for i, um := range lmur.Uploads {
|
||||
uploads[i] = MultipartInfo{
|
||||
Object: um.Key,
|
||||
UploadID: um.UploadID,
|
||||
Initiated: um.Initiated,
|
||||
}
|
||||
}
|
||||
|
||||
commonPrefixes := make([]string, len(lmur.CommonPrefixes))
|
||||
for i, cp := range lmur.CommonPrefixes {
|
||||
commonPrefixes[i] = cp.Prefix
|
||||
}
|
||||
|
||||
return ListMultipartsInfo{
|
||||
KeyMarker: lmur.KeyMarker,
|
||||
UploadIDMarker: lmur.UploadIDMarker,
|
||||
NextKeyMarker: lmur.NextKeyMarker,
|
||||
NextUploadIDMarker: lmur.NextUploadIDMarker,
|
||||
MaxUploads: int(lmur.MaxUploads),
|
||||
IsTruncated: lmur.IsTruncated,
|
||||
Uploads: uploads,
|
||||
Prefix: lmur.Prefix,
|
||||
Delimiter: lmur.Delimiter,
|
||||
CommonPrefixes: commonPrefixes,
|
||||
EncodingType: lmur.EncodingType,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FromMinioClientObjectInfo converts minio ObjectInfo to gateway ObjectInfo
|
||||
func FromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo {
|
||||
userDefined := FromMinioClientMetadata(oi.Metadata)
|
||||
userDefined["Content-Type"] = oi.ContentType
|
||||
|
||||
return ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: oi.Key,
|
||||
ModTime: oi.LastModified,
|
||||
Size: oi.Size,
|
||||
ETag: canonicalizeETag(oi.ETag),
|
||||
UserDefined: userDefined,
|
||||
ContentType: oi.ContentType,
|
||||
ContentEncoding: oi.Metadata.Get("Content-Encoding"),
|
||||
}
|
||||
}
|
||||
|
||||
// FromMinioClientListBucketV2Result converts minio ListBucketResult to ListObjectsInfo
|
||||
func FromMinioClientListBucketV2Result(bucket string, result minio.ListBucketV2Result) ListObjectsV2Info {
|
||||
objects := make([]ObjectInfo, len(result.Contents))
|
||||
|
||||
for i, oi := range result.Contents {
|
||||
objects[i] = FromMinioClientObjectInfo(bucket, oi)
|
||||
}
|
||||
|
||||
prefixes := make([]string, len(result.CommonPrefixes))
|
||||
for i, p := range result.CommonPrefixes {
|
||||
prefixes[i] = p.Prefix
|
||||
}
|
||||
|
||||
return ListObjectsV2Info{
|
||||
IsTruncated: result.IsTruncated,
|
||||
Prefixes: prefixes,
|
||||
Objects: objects,
|
||||
|
||||
ContinuationToken: result.ContinuationToken,
|
||||
NextContinuationToken: result.NextContinuationToken,
|
||||
}
|
||||
}
|
||||
|
||||
// FromMinioClientListBucketResult converts minio ListBucketResult to ListObjectsInfo
|
||||
func FromMinioClientListBucketResult(bucket string, result minio.ListBucketResult) ListObjectsInfo {
|
||||
objects := make([]ObjectInfo, len(result.Contents))
|
||||
|
||||
for i, oi := range result.Contents {
|
||||
objects[i] = FromMinioClientObjectInfo(bucket, oi)
|
||||
}
|
||||
|
||||
prefixes := make([]string, len(result.CommonPrefixes))
|
||||
for i, p := range result.CommonPrefixes {
|
||||
prefixes[i] = p.Prefix
|
||||
}
|
||||
|
||||
return ListObjectsInfo{
|
||||
IsTruncated: result.IsTruncated,
|
||||
NextMarker: result.NextMarker,
|
||||
Prefixes: prefixes,
|
||||
Objects: objects,
|
||||
}
|
||||
}
|
||||
|
||||
// FromMinioClientListBucketResultToV2Info converts minio ListBucketResult to ListObjectsV2Info
|
||||
func FromMinioClientListBucketResultToV2Info(bucket string, result minio.ListBucketResult) ListObjectsV2Info {
|
||||
objects := make([]ObjectInfo, len(result.Contents))
|
||||
|
||||
for i, oi := range result.Contents {
|
||||
objects[i] = FromMinioClientObjectInfo(bucket, oi)
|
||||
}
|
||||
|
||||
prefixes := make([]string, len(result.CommonPrefixes))
|
||||
for i, p := range result.CommonPrefixes {
|
||||
prefixes[i] = p.Prefix
|
||||
}
|
||||
|
||||
return ListObjectsV2Info{
|
||||
IsTruncated: result.IsTruncated,
|
||||
Prefixes: prefixes,
|
||||
Objects: objects,
|
||||
ContinuationToken: result.Marker,
|
||||
NextContinuationToken: result.NextMarker,
|
||||
}
|
||||
}
|
||||
|
||||
// ToMinioClientMetadata converts metadata to map[string][]string
|
||||
func ToMinioClientMetadata(metadata map[string]string) map[string]string {
|
||||
mm := make(map[string]string)
|
||||
for k, v := range metadata {
|
||||
mm[http.CanonicalHeaderKey(k)] = v
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
||||
// ToMinioClientCompletePart converts CompletePart to minio CompletePart
|
||||
func ToMinioClientCompletePart(part CompletePart) minio.CompletePart {
|
||||
return minio.CompletePart{
|
||||
ETag: part.ETag,
|
||||
PartNumber: part.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// ToMinioClientCompleteParts converts []CompletePart to minio []CompletePart
|
||||
func ToMinioClientCompleteParts(parts []CompletePart) []minio.CompletePart {
|
||||
mparts := make([]minio.CompletePart, len(parts))
|
||||
for i, part := range parts {
|
||||
mparts[i] = ToMinioClientCompletePart(part)
|
||||
}
|
||||
return mparts
|
||||
}
|
||||
|
||||
// ErrorRespToObjectError converts Minio errors to minio object layer errors.
|
||||
func ErrorRespToObjectError(err error, params ...string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, ok := err.(*errors.Error)
|
||||
if !ok {
|
||||
// Code should be fixed if this function is called without doing traceError()
|
||||
// Else handling different situations in this function makes this function complicated.
|
||||
errorIf(err, "Expected type *Error")
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.Cause
|
||||
|
||||
bucket := ""
|
||||
object := ""
|
||||
if len(params) >= 1 {
|
||||
bucket = params[0]
|
||||
}
|
||||
if len(params) == 2 {
|
||||
object = params[1]
|
||||
}
|
||||
|
||||
minioErr, ok := err.(minio.ErrorResponse)
|
||||
if !ok {
|
||||
// We don't interpret non Minio errors. As minio errors will
|
||||
// have StatusCode to help to convert to object errors.
|
||||
return e
|
||||
}
|
||||
|
||||
switch minioErr.Code {
|
||||
case "BucketAlreadyOwnedByYou":
|
||||
err = BucketAlreadyOwnedByYou{}
|
||||
case "BucketNotEmpty":
|
||||
err = BucketNotEmpty{}
|
||||
case "NoSuchBucketPolicy":
|
||||
err = PolicyNotFound{}
|
||||
case "InvalidBucketName":
|
||||
err = BucketNameInvalid{Bucket: bucket}
|
||||
case "NoSuchBucket":
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
case "NoSuchKey":
|
||||
if object != "" {
|
||||
err = ObjectNotFound{Bucket: bucket, Object: object}
|
||||
} else {
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
}
|
||||
case "XMinioInvalidObjectName":
|
||||
err = ObjectNameInvalid{}
|
||||
case "AccessDenied":
|
||||
err = PrefixAccessDenied{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
case "XAmzContentSHA256Mismatch":
|
||||
err = hash.SHA256Mismatch{}
|
||||
case "NoSuchUpload":
|
||||
err = InvalidUploadID{}
|
||||
case "EntityTooSmall":
|
||||
err = PartTooSmall{}
|
||||
}
|
||||
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
+21
-13
@@ -61,7 +61,7 @@ func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
|
||||
s3Error := isReqAuthenticated(r, globalServerConfig.GetRegion())
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, "%s", dumpRequest(r))
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
@@ -186,6 +186,14 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
bucket = vars["bucket"]
|
||||
object = vars["object"]
|
||||
|
||||
// Validate storage class metadata if present
|
||||
if _, ok := r.Header[amzStorageClassCanonical]; ok {
|
||||
if !isValidStorageClassMeta(r.Header.Get(amzStorageClassCanonical)) {
|
||||
writeErrorResponse(w, ErrInvalidStorageClass, r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we should validate the object name here
|
||||
|
||||
// Get Content-Md5 sent by client and verify if valid
|
||||
@@ -286,7 +294,7 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := reqSignatureV4Verify(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := reqSignatureV4Verify(r, globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, "%s", dumpRequest(r))
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
@@ -357,7 +365,7 @@ func (api gatewayAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.R
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
|
||||
s3Error := isReqAuthenticated(r, globalServerConfig.GetRegion())
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, "%s", dumpRequest(r))
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
@@ -429,7 +437,7 @@ func (api gatewayAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -492,7 +500,7 @@ func (api gatewayAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -526,7 +534,7 @@ func (api gatewayAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -596,7 +604,7 @@ func (api gatewayAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// PutBucket does not have any bucket action.
|
||||
s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion())
|
||||
s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion())
|
||||
if s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
@@ -643,7 +651,7 @@ func (api gatewayAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
// DeleteBucket does not have any bucket action.
|
||||
if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthType(r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
return
|
||||
}
|
||||
@@ -690,7 +698,7 @@ func (api gatewayAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
|
||||
s3Error := isReqAuthenticated(r, globalServerConfig.GetRegion())
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, "%s", dumpRequest(r))
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
@@ -764,7 +772,7 @@ func (api gatewayAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
|
||||
s3Error := isReqAuthenticated(r, globalServerConfig.GetRegion())
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
@@ -843,7 +851,7 @@ func (api gatewayAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.R
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
|
||||
s3Error := isReqAuthenticated(r, globalServerConfig.GetRegion())
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, "%s", dumpRequest(r))
|
||||
writeErrorResponse(w, s3Error, r.URL)
|
||||
@@ -898,7 +906,7 @@ func (api gatewayAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r
|
||||
s3Error := isReqAuthenticated(r, globalMinioDefaultRegion)
|
||||
if s3Error == ErrInvalidRegion {
|
||||
// Clients like boto3 send getBucketLocation() call signed with region that is configured.
|
||||
s3Error = isReqAuthenticated(r, serverConfig.GetRegion())
|
||||
s3Error = isReqAuthenticated(r, globalServerConfig.GetRegion())
|
||||
}
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, "%s", dumpRequest(r))
|
||||
@@ -927,7 +935,7 @@ func (api gatewayAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r
|
||||
// Generate response.
|
||||
encodedSuccessResponse := encodeResponse(LocationResponse{})
|
||||
// Get current region.
|
||||
region := serverConfig.GetRegion()
|
||||
region := globalServerConfig.GetRegion()
|
||||
if region != globalMinioDefaultRegion {
|
||||
encodedSuccessResponse = encodeResponse(LocationResponse{
|
||||
Location: region,
|
||||
|
||||
+30
-41
@@ -17,7 +17,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -28,6 +27,7 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
miniohttp "github.com/minio/minio/pkg/http"
|
||||
)
|
||||
|
||||
@@ -40,36 +40,15 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// Gateway represents a gateway backend.
|
||||
type Gateway interface {
|
||||
// Name returns the unique name of the gateway.
|
||||
Name() string
|
||||
// NewGatewayLayer returns a new gateway layer.
|
||||
NewGatewayLayer() (GatewayLayer, error)
|
||||
}
|
||||
|
||||
// RegisterGatewayCommand registers a new command for gateway.
|
||||
func RegisterGatewayCommand(cmd cli.Command) error {
|
||||
// We should not have multiple subcommands with same name.
|
||||
for _, c := range gatewayCmd.Subcommands {
|
||||
if c.Name == cmd.Name {
|
||||
return fmt.Errorf("duplicate gateway: %s", cmd.Name)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Flags = append(append(cmd.Flags, append(cmd.Flags, serverFlags...)...), globalFlags...)
|
||||
gatewayCmd.Subcommands = append(gatewayCmd.Subcommands, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustRegisterGatewayCommand is like RegisterGatewayCommand but panics instead of returning error.
|
||||
func MustRegisterGatewayCommand(cmd cli.Command) {
|
||||
if err := RegisterGatewayCommand(cmd); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Return endpoint.
|
||||
func parseGatewayEndpoint(arg string) (endPoint string, secure bool, err error) {
|
||||
// ParseGatewayEndpoint - Return endpoint.
|
||||
func ParseGatewayEndpoint(arg string) (endPoint string, secure bool, err error) {
|
||||
schemeSpecified := len(strings.Split(arg, "://")) > 1
|
||||
if !schemeSpecified {
|
||||
// Default connection will be "secure".
|
||||
@@ -91,8 +70,8 @@ func parseGatewayEndpoint(arg string) (endPoint string, secure bool, err error)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate gateway arguments.
|
||||
func validateGatewayArguments(serverAddr, endpointAddr string) error {
|
||||
// ValidateGatewayArguments - Validate gateway arguments.
|
||||
func ValidateGatewayArguments(serverAddr, endpointAddr string) error {
|
||||
if err := CheckLocalServerAddr(serverAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -115,14 +94,24 @@ func validateGatewayArguments(serverAddr, endpointAddr string) error {
|
||||
return err
|
||||
}
|
||||
if sameTarget {
|
||||
return errors.New("endpoint points to the local gateway")
|
||||
return fmt.Errorf("endpoint points to the local gateway")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handler for 'minio gateway <name>'.
|
||||
func startGateway(ctx *cli.Context, gw Gateway) {
|
||||
// StartGateway - handler for 'minio gateway <name>'.
|
||||
func StartGateway(ctx *cli.Context, gw Gateway) {
|
||||
if gw == nil {
|
||||
fatalIf(errUnexpected, "Gateway implementation not initialized, exiting.")
|
||||
}
|
||||
|
||||
// Validate if we have access, secret set through environment.
|
||||
gatewayName := gw.Name()
|
||||
if ctx.Args().First() == "help" {
|
||||
cli.ShowCommandHelpAndExit(ctx, gatewayName, 1)
|
||||
}
|
||||
|
||||
// Get quiet flag from command line argument.
|
||||
quietFlag := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
|
||||
if quietFlag {
|
||||
@@ -142,9 +131,8 @@ func startGateway(ctx *cli.Context, gw Gateway) {
|
||||
handleCommonEnvVars()
|
||||
|
||||
// Validate if we have access, secret set through environment.
|
||||
gatewayName := gw.Name()
|
||||
if !globalIsEnvCreds {
|
||||
errorIf(errors.New("Access and secret keys not set"), "Access and Secret keys should be set through ENVs for backend [%s]", gatewayName)
|
||||
errorIf(fmt.Errorf("Access and secret keys not set"), "Access and Secret keys should be set through ENVs for backend [%s]", gatewayName)
|
||||
cli.ShowCommandHelpAndExit(ctx, gatewayName, 1)
|
||||
}
|
||||
|
||||
@@ -154,24 +142,20 @@ func startGateway(ctx *cli.Context, gw Gateway) {
|
||||
// Initialize gateway config.
|
||||
initConfig()
|
||||
|
||||
// Enable loggers as per configuration file.
|
||||
enableLoggers()
|
||||
|
||||
// Init the error tracing module.
|
||||
initError()
|
||||
errors.Init(GOPATH, "github.com/minio/minio")
|
||||
|
||||
// Check and load SSL certificates.
|
||||
var err error
|
||||
globalPublicCerts, globalRootCAs, globalTLSCertificate, globalIsSSL, err = getSSLConfig()
|
||||
fatalIf(err, "Invalid SSL certificate file")
|
||||
|
||||
// Set system resources to maximum.
|
||||
errorIf(setMaxResources(), "Unable to change resource limit")
|
||||
|
||||
initNSLock(false) // Enable local namespace lock.
|
||||
|
||||
if ctx.Args().First() == "help" {
|
||||
cli.ShowCommandHelpAndExit(ctx, gatewayName, 1)
|
||||
}
|
||||
|
||||
newObject, err := gw.NewGatewayLayer()
|
||||
newObject, err := gw.NewGatewayLayer(globalServerConfig.GetCredential())
|
||||
fatalIf(err, "Unable to initialize gateway layer")
|
||||
|
||||
router := mux.NewRouter().SkipClean(true)
|
||||
@@ -230,6 +214,11 @@ func startGateway(ctx *cli.Context, gw Gateway) {
|
||||
// Check update mode.
|
||||
checkUpdate(mode)
|
||||
|
||||
// Print a warning message if gateway is not ready for production before the startup banner.
|
||||
if !gw.Production() {
|
||||
log.Println(colorYellow("\n *** Warning: Not Ready for Production ***"))
|
||||
}
|
||||
|
||||
// Print gateway startup message.
|
||||
printGatewayStartupMessage(getAPIEndpoints(gatewayAddr), gatewayName)
|
||||
}
|
||||
|
||||
@@ -32,16 +32,6 @@ func TestRegisterGatewayCommand(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("RegisterGatewayCommand got unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Should returns 'duplicated' error
|
||||
err = RegisterGatewayCommand(cmd)
|
||||
if err == nil {
|
||||
t.Errorf("RegisterGatewayCommand twice with same name should return error")
|
||||
} else {
|
||||
if err.Error() != "duplicate gateway: test" {
|
||||
t.Errorf("RegisterGatewayCommand got unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test parseGatewayEndpoint
|
||||
@@ -62,7 +52,7 @@ func TestParseGatewayEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, test := range testCases {
|
||||
endPoint, secure, err := parseGatewayEndpoint(test.arg)
|
||||
endPoint, secure, err := ParseGatewayEndpoint(test.arg)
|
||||
errReturned := err != nil
|
||||
|
||||
if endPoint != test.endPoint ||
|
||||
@@ -97,7 +87,7 @@ func TestValidateGatewayArguments(t *testing.T) {
|
||||
{":9000", nonLoopBackIP + ":9000", false},
|
||||
}
|
||||
for i, test := range testCases {
|
||||
err := validateGatewayArguments(test.serverAddr, test.endpointAddr)
|
||||
err := ValidateGatewayArguments(test.serverAddr, test.endpointAddr)
|
||||
if test.valid && err != nil {
|
||||
t.Errorf("Test %d expected not to return error but got %s", i+1, err)
|
||||
}
|
||||
|
||||
+17
-1
@@ -22,10 +22,26 @@ import (
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
// GatewayLayer - Interface to implement gateway mode.
|
||||
// GatewayMinioSysTmp prefix is used in Azure/GCS gateway for save metadata sent by Initialize Multipart Upload API.
|
||||
const GatewayMinioSysTmp = "minio.sys.tmp/"
|
||||
|
||||
// Gateway represents a gateway backend.
|
||||
type Gateway interface {
|
||||
// Name returns the unique name of the gateway.
|
||||
Name() string
|
||||
|
||||
// NewGatewayLayer returns a new gateway layer.
|
||||
NewGatewayLayer(creds auth.Credentials) (GatewayLayer, error)
|
||||
|
||||
// Returns true if gateway is ready for production.
|
||||
Production() bool
|
||||
}
|
||||
|
||||
// GatewayLayer - interface to implement gateway mode.
|
||||
type GatewayLayer interface {
|
||||
ObjectLayer
|
||||
|
||||
|
||||
@@ -1,639 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/cli"
|
||||
minio "github.com/minio/minio-go"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio-go/pkg/s3utils"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
s3Backend = "s3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const s3GatewayTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [ENDPOINT]
|
||||
{{if .VisibleFlags}}
|
||||
FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
ENDPOINT:
|
||||
S3 server endpoint. Default ENDPOINT is https://s3.amazonaws.com
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
ACCESS:
|
||||
MINIO_ACCESS_KEY: Username or access key of S3 storage.
|
||||
MINIO_SECRET_KEY: Password or secret key of S3 storage.
|
||||
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for AWS S3 backend.
|
||||
$ export MINIO_ACCESS_KEY=accesskey
|
||||
$ export MINIO_SECRET_KEY=secretkey
|
||||
$ {{.HelpName}}
|
||||
|
||||
2. Start minio gateway server for S3 backend on custom endpoint.
|
||||
$ export MINIO_ACCESS_KEY=Q3AM3UQ867SPQQA43P2F
|
||||
$ export MINIO_SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
$ {{.HelpName}} https://play.minio.io:9000
|
||||
`
|
||||
|
||||
MustRegisterGatewayCommand(cli.Command{
|
||||
Name: s3Backend,
|
||||
Usage: "Amazon Simple Storage Service (S3).",
|
||||
Action: s3GatewayMain,
|
||||
CustomHelpTemplate: s3GatewayTemplate,
|
||||
Flags: append(serverFlags, globalFlags...),
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for 'minio gateway s3' command line.
|
||||
func s3GatewayMain(ctx *cli.Context) {
|
||||
// Validate gateway arguments.
|
||||
host := ctx.Args().First()
|
||||
// Validate gateway arguments.
|
||||
fatalIf(validateGatewayArguments(ctx.GlobalString("address"), host), "Invalid argument")
|
||||
|
||||
startGateway(ctx, &S3Gateway{host})
|
||||
}
|
||||
|
||||
// S3Gateway implements Gateway.
|
||||
type S3Gateway struct {
|
||||
host string
|
||||
}
|
||||
|
||||
// Name implements Gateway interface.
|
||||
func (g *S3Gateway) Name() string {
|
||||
return s3Backend
|
||||
}
|
||||
|
||||
// NewGatewayLayer returns s3 gatewaylayer.
|
||||
func (g *S3Gateway) NewGatewayLayer() (GatewayLayer, error) {
|
||||
return newS3GatewayLayer(g.host)
|
||||
}
|
||||
|
||||
// s3ToObjectError converts Minio errors to minio object layer errors.
|
||||
func s3ToObjectError(err error, params ...string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, ok := err.(*Error)
|
||||
if !ok {
|
||||
// Code should be fixed if this function is called without doing traceError()
|
||||
// Else handling different situations in this function makes this function complicated.
|
||||
errorIf(err, "Expected type *Error")
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.e
|
||||
|
||||
bucket := ""
|
||||
object := ""
|
||||
if len(params) >= 1 {
|
||||
bucket = params[0]
|
||||
}
|
||||
if len(params) == 2 {
|
||||
object = params[1]
|
||||
}
|
||||
|
||||
minioErr, ok := err.(minio.ErrorResponse)
|
||||
if !ok {
|
||||
// We don't interpret non Minio errors. As minio errors will
|
||||
// have StatusCode to help to convert to object errors.
|
||||
return e
|
||||
}
|
||||
|
||||
switch minioErr.Code {
|
||||
case "BucketAlreadyOwnedByYou":
|
||||
err = BucketAlreadyOwnedByYou{}
|
||||
case "BucketNotEmpty":
|
||||
err = BucketNotEmpty{}
|
||||
case "NoSuchBucketPolicy":
|
||||
err = PolicyNotFound{}
|
||||
case "InvalidBucketName":
|
||||
err = BucketNameInvalid{Bucket: bucket}
|
||||
case "NoSuchBucket":
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
case "NoSuchKey":
|
||||
if object != "" {
|
||||
err = ObjectNotFound{Bucket: bucket, Object: object}
|
||||
} else {
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
}
|
||||
case "XMinioInvalidObjectName":
|
||||
err = ObjectNameInvalid{}
|
||||
case "AccessDenied":
|
||||
err = PrefixAccessDenied{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
case "XAmzContentSHA256Mismatch":
|
||||
err = hash.SHA256Mismatch{}
|
||||
case "NoSuchUpload":
|
||||
err = InvalidUploadID{}
|
||||
case "EntityTooSmall":
|
||||
err = PartTooSmall{}
|
||||
}
|
||||
|
||||
e.e = err
|
||||
return e
|
||||
}
|
||||
|
||||
// s3Objects implements gateway for Minio and S3 compatible object storage servers.
|
||||
type s3Objects struct {
|
||||
gatewayUnsupported
|
||||
Client *minio.Core
|
||||
anonClient *minio.Core
|
||||
}
|
||||
|
||||
// newS3GatewayLayer returns s3 gatewaylayer
|
||||
func newS3GatewayLayer(host string) (GatewayLayer, error) {
|
||||
var err error
|
||||
var endpoint string
|
||||
var secure = true
|
||||
|
||||
// Validate host parameters.
|
||||
if host != "" {
|
||||
// Override default params if the host is provided
|
||||
endpoint, secure, err = parseGatewayEndpoint(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Default endpoint parameters
|
||||
if endpoint == "" {
|
||||
endpoint = "s3.amazonaws.com"
|
||||
}
|
||||
|
||||
creds := serverConfig.GetCredential()
|
||||
|
||||
// Initialize minio client object.
|
||||
client, err := minio.NewCore(endpoint, creds.AccessKey, creds.SecretKey, secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anonClient, err := minio.NewCore(endpoint, "", "", secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
anonClient.SetCustomTransport(newCustomHTTPTransport())
|
||||
|
||||
return &s3Objects{
|
||||
Client: client,
|
||||
anonClient: anonClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Shutdown saves any gateway metadata to disk
|
||||
// if necessary and reload upon next restart.
|
||||
func (l *s3Objects) Shutdown() error {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageInfo is not relevant to S3 backend.
|
||||
func (l *s3Objects) StorageInfo() (si StorageInfo) {
|
||||
return si
|
||||
}
|
||||
|
||||
// MakeBucket creates a new container on S3 backend.
|
||||
func (l *s3Objects) MakeBucketWithLocation(bucket, location string) error {
|
||||
err := l.Client.MakeBucket(bucket, location)
|
||||
if err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetBucketInfo gets bucket metadata..
|
||||
func (l *s3Objects) GetBucketInfo(bucket string) (bi BucketInfo, e error) {
|
||||
// Verify if bucket name is valid.
|
||||
// We are using a separate helper function here to validate bucket
|
||||
// names instead of IsValidBucketName() because there is a possibility
|
||||
// that certains users might have buckets which are non-DNS compliant
|
||||
// in us-east-1 and we might severely restrict them by not allowing
|
||||
// access to these buckets.
|
||||
// Ref - http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
|
||||
if s3utils.CheckValidBucketName(bucket) != nil {
|
||||
return bi, traceError(BucketNameInvalid{Bucket: bucket})
|
||||
}
|
||||
|
||||
buckets, err := l.Client.ListBuckets()
|
||||
if err != nil {
|
||||
return bi, s3ToObjectError(traceError(err), bucket)
|
||||
}
|
||||
|
||||
for _, bi := range buckets {
|
||||
if bi.Name != bucket {
|
||||
continue
|
||||
}
|
||||
|
||||
return BucketInfo{
|
||||
Name: bi.Name,
|
||||
Created: bi.CreationDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return bi, traceError(BucketNotFound{Bucket: bucket})
|
||||
}
|
||||
|
||||
// ListBuckets lists all S3 buckets
|
||||
func (l *s3Objects) ListBuckets() ([]BucketInfo, error) {
|
||||
buckets, err := l.Client.ListBuckets()
|
||||
if err != nil {
|
||||
return nil, s3ToObjectError(traceError(err))
|
||||
}
|
||||
|
||||
b := make([]BucketInfo, len(buckets))
|
||||
for i, bi := range buckets {
|
||||
b[i] = BucketInfo{
|
||||
Name: bi.Name,
|
||||
Created: bi.CreationDate,
|
||||
}
|
||||
}
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket on S3
|
||||
func (l *s3Objects) DeleteBucket(bucket string) error {
|
||||
err := l.Client.RemoveBucket(bucket)
|
||||
if err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListObjects lists all blobs in S3 bucket filtered by prefix
|
||||
func (l *s3Objects) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi ListObjectsInfo, e error) {
|
||||
result, err := l.Client.ListObjects(bucket, prefix, marker, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, s3ToObjectError(traceError(err), bucket)
|
||||
}
|
||||
|
||||
return fromMinioClientListBucketResult(bucket, result), nil
|
||||
}
|
||||
|
||||
// ListObjectsV2 lists all blobs in S3 bucket filtered by prefix
|
||||
func (l *s3Objects) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi ListObjectsV2Info, e error) {
|
||||
result, err := l.Client.ListObjectsV2(bucket, prefix, continuationToken, fetchOwner, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, s3ToObjectError(traceError(err), bucket)
|
||||
}
|
||||
|
||||
return fromMinioClientListBucketV2Result(bucket, result), nil
|
||||
}
|
||||
|
||||
// fromMinioClientListBucketV2Result converts minio ListBucketResult to ListObjectsInfo
|
||||
func fromMinioClientListBucketV2Result(bucket string, result minio.ListBucketV2Result) ListObjectsV2Info {
|
||||
objects := make([]ObjectInfo, len(result.Contents))
|
||||
|
||||
for i, oi := range result.Contents {
|
||||
objects[i] = fromMinioClientObjectInfo(bucket, oi)
|
||||
}
|
||||
|
||||
prefixes := make([]string, len(result.CommonPrefixes))
|
||||
for i, p := range result.CommonPrefixes {
|
||||
prefixes[i] = p.Prefix
|
||||
}
|
||||
|
||||
return ListObjectsV2Info{
|
||||
IsTruncated: result.IsTruncated,
|
||||
Prefixes: prefixes,
|
||||
Objects: objects,
|
||||
|
||||
ContinuationToken: result.ContinuationToken,
|
||||
NextContinuationToken: result.NextContinuationToken,
|
||||
}
|
||||
}
|
||||
|
||||
// fromMinioClientListBucketResult converts minio ListBucketResult to ListObjectsInfo
|
||||
func fromMinioClientListBucketResult(bucket string, result minio.ListBucketResult) ListObjectsInfo {
|
||||
objects := make([]ObjectInfo, len(result.Contents))
|
||||
|
||||
for i, oi := range result.Contents {
|
||||
objects[i] = fromMinioClientObjectInfo(bucket, oi)
|
||||
}
|
||||
|
||||
prefixes := make([]string, len(result.CommonPrefixes))
|
||||
for i, p := range result.CommonPrefixes {
|
||||
prefixes[i] = p.Prefix
|
||||
}
|
||||
|
||||
return ListObjectsInfo{
|
||||
IsTruncated: result.IsTruncated,
|
||||
NextMarker: result.NextMarker,
|
||||
Prefixes: prefixes,
|
||||
Objects: objects,
|
||||
}
|
||||
}
|
||||
|
||||
// GetObject reads an object from S3. Supports additional
|
||||
// parameters like offset and length which are synonymous with
|
||||
// HTTP Range requests.
|
||||
//
|
||||
// startOffset indicates the starting read location of the object.
|
||||
// length indicates the total length of the object.
|
||||
func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error {
|
||||
if length < 0 && length != -1 {
|
||||
return s3ToObjectError(traceError(errInvalidArgument), bucket, key)
|
||||
}
|
||||
|
||||
opts := minio.GetObjectOptions{}
|
||||
if startOffset >= 0 && length >= 0 {
|
||||
if err := opts.SetRange(startOffset, startOffset+length-1); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
}
|
||||
}
|
||||
object, _, err := l.Client.GetObject(bucket, key, opts)
|
||||
if err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
}
|
||||
defer object.Close()
|
||||
|
||||
if _, err := io.Copy(writer, object); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fromMinioClientObjectInfo converts minio ObjectInfo to gateway ObjectInfo
|
||||
func fromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo {
|
||||
userDefined := fromMinioClientMetadata(oi.Metadata)
|
||||
userDefined["Content-Type"] = oi.ContentType
|
||||
|
||||
return ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: oi.Key,
|
||||
ModTime: oi.LastModified,
|
||||
Size: oi.Size,
|
||||
ETag: canonicalizeETag(oi.ETag),
|
||||
UserDefined: userDefined,
|
||||
ContentType: oi.ContentType,
|
||||
ContentEncoding: oi.Metadata.Get("Content-Encoding"),
|
||||
}
|
||||
}
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo
|
||||
func (l *s3Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) {
|
||||
oi, err := l.Client.StatObject(bucket, object, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
|
||||
return fromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// PutObject creates a new object with the incoming data,
|
||||
func (l *s3Objects) PutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, err error) {
|
||||
oi, err := l.Client.PutObject(bucket, object, data, data.Size(), data.MD5(), data.SHA256(), toMinioClientMetadata(metadata))
|
||||
if err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
|
||||
return fromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// CopyObject copies an object from source bucket to a destination bucket.
|
||||
func (l *s3Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, dstObject string, metadata map[string]string) (objInfo ObjectInfo, err error) {
|
||||
// Set this header such that following CopyObject() always sets the right metadata on the destination.
|
||||
// metadata input is already a trickled down value from interpreting x-amz-metadata-directive at
|
||||
// handler layer. So what we have right now is supposed to be applied on the destination object anyways.
|
||||
// So preserve it by adding "REPLACE" directive to save all the metadata set by CopyObject API.
|
||||
metadata["x-amz-metadata-directive"] = "REPLACE"
|
||||
if _, err = l.Client.CopyObject(srcBucket, srcObject, dstBucket, dstObject, metadata); err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), srcBucket, srcObject)
|
||||
}
|
||||
return l.GetObjectInfo(dstBucket, dstObject)
|
||||
}
|
||||
|
||||
// DeleteObject deletes a blob in bucket
|
||||
func (l *s3Objects) DeleteObject(bucket string, object string) error {
|
||||
err := l.Client.RemoveObject(bucket, object)
|
||||
if err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fromMinioClientMultipartInfo converts ObjectMultipartInfo to MultipartInfo
|
||||
func fromMinioClientMultipartInfo(omi minio.ObjectMultipartInfo) MultipartInfo {
|
||||
return MultipartInfo{
|
||||
Object: omi.Key,
|
||||
UploadID: omi.UploadID,
|
||||
Initiated: omi.Initiated,
|
||||
}
|
||||
}
|
||||
|
||||
// fromMinioClientListMultipartsInfo converts minio ListMultipartUploadsResult to ListMultipartsInfo
|
||||
func fromMinioClientListMultipartsInfo(lmur minio.ListMultipartUploadsResult) ListMultipartsInfo {
|
||||
uploads := make([]MultipartInfo, len(lmur.Uploads))
|
||||
|
||||
for i, um := range lmur.Uploads {
|
||||
uploads[i] = fromMinioClientMultipartInfo(um)
|
||||
}
|
||||
|
||||
commonPrefixes := make([]string, len(lmur.CommonPrefixes))
|
||||
for i, cp := range lmur.CommonPrefixes {
|
||||
commonPrefixes[i] = cp.Prefix
|
||||
}
|
||||
|
||||
return ListMultipartsInfo{
|
||||
KeyMarker: lmur.KeyMarker,
|
||||
UploadIDMarker: lmur.UploadIDMarker,
|
||||
NextKeyMarker: lmur.NextKeyMarker,
|
||||
NextUploadIDMarker: lmur.NextUploadIDMarker,
|
||||
MaxUploads: int(lmur.MaxUploads),
|
||||
IsTruncated: lmur.IsTruncated,
|
||||
Uploads: uploads,
|
||||
Prefix: lmur.Prefix,
|
||||
Delimiter: lmur.Delimiter,
|
||||
CommonPrefixes: commonPrefixes,
|
||||
EncodingType: lmur.EncodingType,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ListMultipartUploads lists all multipart uploads.
|
||||
func (l *s3Objects) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (lmi ListMultipartsInfo, e error) {
|
||||
result, err := l.Client.ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
|
||||
if err != nil {
|
||||
return lmi, err
|
||||
}
|
||||
|
||||
return fromMinioClientListMultipartsInfo(result), nil
|
||||
}
|
||||
|
||||
// fromMinioClientMetadata converts minio metadata to map[string]string
|
||||
func fromMinioClientMetadata(metadata map[string][]string) map[string]string {
|
||||
mm := map[string]string{}
|
||||
for k, v := range metadata {
|
||||
mm[http.CanonicalHeaderKey(k)] = v[0]
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
||||
// toMinioClientMetadata converts metadata to map[string][]string
|
||||
func toMinioClientMetadata(metadata map[string]string) map[string]string {
|
||||
mm := map[string]string{}
|
||||
for k, v := range metadata {
|
||||
mm[http.CanonicalHeaderKey(k)] = v
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
||||
// NewMultipartUpload upload object in multiple parts
|
||||
func (l *s3Objects) NewMultipartUpload(bucket string, object string, metadata map[string]string) (uploadID string, err error) {
|
||||
// Create PutObject options
|
||||
opts := minio.PutObjectOptions{UserMetadata: metadata}
|
||||
uploadID, err = l.Client.NewMultipartUpload(bucket, object, opts)
|
||||
if err != nil {
|
||||
return uploadID, s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
return uploadID, nil
|
||||
}
|
||||
|
||||
// fromMinioClientObjectPart converts minio ObjectPart to PartInfo
|
||||
func fromMinioClientObjectPart(op minio.ObjectPart) PartInfo {
|
||||
return PartInfo{
|
||||
Size: op.Size,
|
||||
ETag: canonicalizeETag(op.ETag),
|
||||
LastModified: op.LastModified,
|
||||
PartNumber: op.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// PutObjectPart puts a part of object in bucket
|
||||
func (l *s3Objects) PutObjectPart(bucket string, object string, uploadID string, partID int, data *hash.Reader) (pi PartInfo, e error) {
|
||||
info, err := l.Client.PutObjectPart(bucket, object, uploadID, partID, data, data.Size(), data.MD5(), data.SHA256())
|
||||
if err != nil {
|
||||
return pi, s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
|
||||
return fromMinioClientObjectPart(info), nil
|
||||
}
|
||||
|
||||
// fromMinioClientObjectParts converts minio ObjectPart to PartInfo
|
||||
func fromMinioClientObjectParts(parts []minio.ObjectPart) []PartInfo {
|
||||
toParts := make([]PartInfo, len(parts))
|
||||
for i, part := range parts {
|
||||
toParts[i] = fromMinioClientObjectPart(part)
|
||||
}
|
||||
return toParts
|
||||
}
|
||||
|
||||
// fromMinioClientListPartsInfo converts minio ListObjectPartsResult to ListPartsInfo
|
||||
func fromMinioClientListPartsInfo(lopr minio.ListObjectPartsResult) ListPartsInfo {
|
||||
return ListPartsInfo{
|
||||
UploadID: lopr.UploadID,
|
||||
Bucket: lopr.Bucket,
|
||||
Object: lopr.Key,
|
||||
StorageClass: "",
|
||||
PartNumberMarker: lopr.PartNumberMarker,
|
||||
NextPartNumberMarker: lopr.NextPartNumberMarker,
|
||||
MaxParts: lopr.MaxParts,
|
||||
IsTruncated: lopr.IsTruncated,
|
||||
EncodingType: lopr.EncodingType,
|
||||
Parts: fromMinioClientObjectParts(lopr.ObjectParts),
|
||||
}
|
||||
}
|
||||
|
||||
// ListObjectParts returns all object parts for specified object in specified bucket
|
||||
func (l *s3Objects) ListObjectParts(bucket string, object string, uploadID string, partNumberMarker int, maxParts int) (lpi ListPartsInfo, e error) {
|
||||
result, err := l.Client.ListObjectParts(bucket, object, uploadID, partNumberMarker, maxParts)
|
||||
if err != nil {
|
||||
return lpi, err
|
||||
}
|
||||
|
||||
return fromMinioClientListPartsInfo(result), nil
|
||||
}
|
||||
|
||||
// AbortMultipartUpload aborts a ongoing multipart upload
|
||||
func (l *s3Objects) AbortMultipartUpload(bucket string, object string, uploadID string) error {
|
||||
err := l.Client.AbortMultipartUpload(bucket, object, uploadID)
|
||||
return s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
|
||||
// toMinioClientCompletePart converts CompletePart to minio CompletePart
|
||||
func toMinioClientCompletePart(part CompletePart) minio.CompletePart {
|
||||
return minio.CompletePart{
|
||||
ETag: part.ETag,
|
||||
PartNumber: part.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// toMinioClientCompleteParts converts []CompletePart to minio []CompletePart
|
||||
func toMinioClientCompleteParts(parts []CompletePart) []minio.CompletePart {
|
||||
mparts := make([]minio.CompletePart, len(parts))
|
||||
for i, part := range parts {
|
||||
mparts[i] = toMinioClientCompletePart(part)
|
||||
}
|
||||
return mparts
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload completes ongoing multipart upload and finalizes object
|
||||
func (l *s3Objects) CompleteMultipartUpload(bucket string, object string, uploadID string, uploadedParts []CompletePart) (oi ObjectInfo, e error) {
|
||||
err := l.Client.CompleteMultipartUpload(bucket, object, uploadID, toMinioClientCompleteParts(uploadedParts))
|
||||
if err != nil {
|
||||
return oi, s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
|
||||
return l.GetObjectInfo(bucket, object)
|
||||
}
|
||||
|
||||
// SetBucketPolicies sets policy on bucket
|
||||
func (l *s3Objects) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error {
|
||||
if err := l.Client.PutBucketPolicy(bucket, policyInfo); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBucketPolicies will get policy on bucket
|
||||
func (l *s3Objects) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) {
|
||||
policyInfo, err := l.Client.GetBucketPolicy(bucket)
|
||||
if err != nil {
|
||||
return policy.BucketAccessPolicy{}, s3ToObjectError(traceError(err), bucket, "")
|
||||
}
|
||||
return policyInfo, nil
|
||||
}
|
||||
|
||||
// DeleteBucketPolicies deletes all policies on bucket
|
||||
func (l *s3Objects) DeleteBucketPolicies(bucket string) error {
|
||||
if err := l.Client.PutBucketPolicy(bucket, policy.BucketAccessPolicy{}); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func printGatewayStartupMessage(apiEndPoints []string, backendType string) {
|
||||
// Prints common server startup message. Prints credential, region and browser access.
|
||||
func printGatewayCommonMsg(apiEndpoints []string) {
|
||||
// Get saved credentials.
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
|
||||
apiEndpointStr := strings.Join(apiEndpoints, " ")
|
||||
// Colorize the message and print.
|
||||
|
||||
+98
-28
@@ -17,62 +17,132 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
type gatewayUnsupported struct{}
|
||||
// GatewayUnsupported list of unsupported call stubs for gateway.
|
||||
type GatewayUnsupported struct{}
|
||||
|
||||
// CopyObjectPart - Not implemented.
|
||||
func (a gatewayUnsupported) CopyObjectPart(srcBucket, srcObject, destBucket, destObject string, uploadID string,
|
||||
partID int, startOffset int64, length int64) (info PartInfo, err error) {
|
||||
return info, traceError(NotImplemented{})
|
||||
// ListMultipartUploads lists all multipart uploads.
|
||||
func (a GatewayUnsupported) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (lmi ListMultipartsInfo, err error) {
|
||||
return lmi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// HealBucket - Not relevant.
|
||||
func (a gatewayUnsupported) HealBucket(bucket string) error {
|
||||
return traceError(NotImplemented{})
|
||||
// NewMultipartUpload upload object in multiple parts
|
||||
func (a GatewayUnsupported) NewMultipartUpload(bucket string, object string, metadata map[string]string) (uploadID string, err error) {
|
||||
return "", errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListBucketsHeal - Not relevant.
|
||||
func (a gatewayUnsupported) ListBucketsHeal() (buckets []BucketInfo, err error) {
|
||||
return nil, traceError(NotImplemented{})
|
||||
// CopyObjectPart copy part of object to other bucket and object
|
||||
func (a GatewayUnsupported) CopyObjectPart(srcBucket string, srcObject string, destBucket string, destObject string, uploadID string, partID int, startOffset int64, length int64, metadata map[string]string) (pi PartInfo, err error) {
|
||||
return pi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// HealObject - Not relevant.
|
||||
func (a gatewayUnsupported) HealObject(bucket, object string) (int, int, error) {
|
||||
return 0, 0, traceError(NotImplemented{})
|
||||
// PutObjectPart puts a part of object in bucket
|
||||
func (a GatewayUnsupported) PutObjectPart(bucket string, object string, uploadID string, partID int, data *hash.Reader) (pi PartInfo, err error) {
|
||||
return pi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListObjectsHeal - Not relevant.
|
||||
func (a gatewayUnsupported) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, e error) {
|
||||
return loi, traceError(NotImplemented{})
|
||||
// ListObjectParts returns all object parts for specified object in specified bucket
|
||||
func (a GatewayUnsupported) ListObjectParts(bucket string, object string, uploadID string, partNumberMarker int, maxParts int) (lpi ListPartsInfo, err error) {
|
||||
return lpi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListUploadsHeal - Not relevant.
|
||||
func (a gatewayUnsupported) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker,
|
||||
// AbortMultipartUpload aborts a ongoing multipart upload
|
||||
func (a GatewayUnsupported) AbortMultipartUpload(bucket string, object string, uploadID string) error {
|
||||
return errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload completes ongoing multipart upload and finalizes object
|
||||
func (a GatewayUnsupported) CompleteMultipartUpload(bucket string, object string, uploadID string, uploadedParts []CompletePart) (oi ObjectInfo, err error) {
|
||||
return oi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// SetBucketPolicies sets policy on bucket
|
||||
func (a GatewayUnsupported) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error {
|
||||
return errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// GetBucketPolicies will get policy on bucket
|
||||
func (a GatewayUnsupported) GetBucketPolicies(bucket string) (bal policy.BucketAccessPolicy, err error) {
|
||||
return bal, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// DeleteBucketPolicies deletes all policies on bucket
|
||||
func (a GatewayUnsupported) DeleteBucketPolicies(bucket string) error {
|
||||
return errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// HealBucket - Not implemented stub
|
||||
func (a GatewayUnsupported) HealBucket(bucket string) error {
|
||||
return errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListBucketsHeal - Not implemented stub
|
||||
func (a GatewayUnsupported) ListBucketsHeal() (buckets []BucketInfo, err error) {
|
||||
return nil, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// HealObject - Not implemented stub
|
||||
func (a GatewayUnsupported) HealObject(bucket, object string) (int, int, error) {
|
||||
return 0, 0, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListObjectsV2 - Not implemented stub
|
||||
func (a GatewayUnsupported) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result ListObjectsV2Info, err error) {
|
||||
return result, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListObjectsHeal - Not implemented stub
|
||||
func (a GatewayUnsupported) ListObjectsHeal(bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, e error) {
|
||||
return loi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// ListUploadsHeal - Not implemented stub
|
||||
func (a GatewayUnsupported) ListUploadsHeal(bucket, prefix, marker, uploadIDMarker,
|
||||
delimiter string, maxUploads int) (lmi ListMultipartsInfo, e error) {
|
||||
return lmi, traceError(NotImplemented{})
|
||||
return lmi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// AnonListObjects - List objects anonymously
|
||||
func (a gatewayUnsupported) AnonListObjects(bucket string, prefix string, marker string, delimiter string,
|
||||
func (a GatewayUnsupported) AnonListObjects(bucket string, prefix string, marker string, delimiter string,
|
||||
maxKeys int) (loi ListObjectsInfo, err error) {
|
||||
return loi, traceError(NotImplemented{})
|
||||
return loi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// AnonListObjectsV2 - List objects in V2 mode, anonymously
|
||||
func (a gatewayUnsupported) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int,
|
||||
func (a GatewayUnsupported) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int,
|
||||
fetchOwner bool, startAfter string) (loi ListObjectsV2Info, err error) {
|
||||
return loi, traceError(NotImplemented{})
|
||||
return loi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// AnonGetBucketInfo - Get bucket metadata anonymously.
|
||||
func (a gatewayUnsupported) AnonGetBucketInfo(bucket string) (bi BucketInfo, err error) {
|
||||
return bi, traceError(NotImplemented{})
|
||||
func (a GatewayUnsupported) AnonGetBucketInfo(bucket string) (bi BucketInfo, err error) {
|
||||
return bi, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// AnonPutObject creates a new object anonymously with the incoming data,
|
||||
func (a gatewayUnsupported) AnonPutObject(bucket, object string, data *hash.Reader,
|
||||
func (a GatewayUnsupported) AnonPutObject(bucket, object string, data *hash.Reader,
|
||||
metadata map[string]string) (ObjectInfo, error) {
|
||||
return ObjectInfo{}, traceError(NotImplemented{})
|
||||
return ObjectInfo{}, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// AnonGetObject downloads object anonymously.
|
||||
func (a GatewayUnsupported) AnonGetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error) {
|
||||
return errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// AnonGetObjectInfo returns stat information about an object anonymously.
|
||||
func (a GatewayUnsupported) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) {
|
||||
return objInfo, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
// CopyObject copies a blob from source container to destination container.
|
||||
func (a GatewayUnsupported) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string,
|
||||
metadata map[string]string) (objInfo ObjectInfo, err error) {
|
||||
return objInfo, errors.Trace(NotImplemented{})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
@@ -28,6 +28,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
// Copied from github.com/Azure/azure-sdk-for-go/storage/container.go
|
||||
@@ -112,34 +115,32 @@ func azureAnonRequest(verb, urlStr string, header http.Header) (*http.Response,
|
||||
}
|
||||
|
||||
// AnonGetBucketInfo - Get bucket metadata from azure anonymously.
|
||||
func (a *azureObjects) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, err error) {
|
||||
func (a *azureObjects) AnonGetBucketInfo(bucket string) (bucketInfo minio.BucketInfo, err error) {
|
||||
blobURL := a.client.GetContainerReference(bucket).GetBlobReference("").GetURL()
|
||||
url, err := url.Parse(blobURL)
|
||||
if err != nil {
|
||||
return bucketInfo, azureToObjectError(traceError(err))
|
||||
return bucketInfo, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
url.RawQuery = "restype=container"
|
||||
resp, err := azureAnonRequest(httpHEAD, url.String(), nil)
|
||||
resp, err := azureAnonRequest(http.MethodHead, url.String(), nil)
|
||||
if err != nil {
|
||||
return bucketInfo, azureToObjectError(traceError(err), bucket)
|
||||
return bucketInfo, azureToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return bucketInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket)), bucket)
|
||||
return bucketInfo, azureToObjectError(errors.Trace(minio.AnonErrToObjectErr(resp.StatusCode, bucket)), bucket)
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return bucketInfo, traceError(err)
|
||||
return bucketInfo, errors.Trace(err)
|
||||
}
|
||||
|
||||
bucketInfo = BucketInfo{
|
||||
return minio.BucketInfo{
|
||||
Name: bucket,
|
||||
Created: t,
|
||||
}
|
||||
|
||||
return bucketInfo, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AnonGetObject - SendGET request without authentication.
|
||||
@@ -153,32 +154,32 @@ func (a *azureObjects) AnonGetObject(bucket, object string, startOffset int64, l
|
||||
}
|
||||
|
||||
blobURL := a.client.GetContainerReference(bucket).GetBlobReference(object).GetURL()
|
||||
resp, err := azureAnonRequest(httpGET, blobURL, h)
|
||||
resp, err := azureAnonRequest(http.MethodGet, blobURL, h)
|
||||
if err != nil {
|
||||
return azureToObjectError(traceError(err), bucket, object)
|
||||
return azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent && resp.StatusCode != http.StatusOK {
|
||||
return azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
return azureToObjectError(errors.Trace(minio.AnonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
}
|
||||
|
||||
_, err = io.Copy(writer, resp.Body)
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
// AnonGetObjectInfo - Send HEAD request without authentication and convert the
|
||||
// result to ObjectInfo.
|
||||
func (a *azureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) {
|
||||
func (a *azureObjects) AnonGetObjectInfo(bucket, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
blobURL := a.client.GetContainerReference(bucket).GetBlobReference(object).GetURL()
|
||||
resp, err := azureAnonRequest(httpHEAD, blobURL, nil)
|
||||
resp, err := azureAnonRequest(http.MethodHead, blobURL, nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return objInfo, azureToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(minio.AnonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
}
|
||||
|
||||
var contentLength int64
|
||||
@@ -186,13 +187,13 @@ func (a *azureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectI
|
||||
if contentLengthStr != "" {
|
||||
contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(errUnexpected), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(fmt.Errorf("Unexpected error")), bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return objInfo, traceError(err)
|
||||
return objInfo, errors.Trace(err)
|
||||
}
|
||||
|
||||
objInfo.ModTime = t
|
||||
@@ -210,7 +211,7 @@ func (a *azureObjects) AnonGetObjectInfo(bucket, object string) (objInfo ObjectI
|
||||
}
|
||||
|
||||
// AnonListObjects - Use Azure equivalent ListBlobs.
|
||||
func (a *azureObjects) AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) {
|
||||
func (a *azureObjects) AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result minio.ListObjectsInfo, err error) {
|
||||
params := storage.ListBlobsParameters{
|
||||
Prefix: prefix,
|
||||
Marker: marker,
|
||||
@@ -225,13 +226,13 @@ func (a *azureObjects) AnonListObjects(bucket, prefix, marker, delimiter string,
|
||||
blobURL := a.client.GetContainerReference(bucket).GetBlobReference("").GetURL()
|
||||
url, err := url.Parse(blobURL)
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
resp, err := azureAnonRequest(httpGET, url.String(), nil)
|
||||
resp, err := azureAnonRequest(http.MethodGet, url.String(), nil)
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
@@ -239,17 +240,17 @@ func (a *azureObjects) AnonListObjects(bucket, prefix, marker, delimiter string,
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
err = xml.Unmarshal(data, &listResp)
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
|
||||
result.IsTruncated = listResp.NextMarker != ""
|
||||
result.NextMarker = listResp.NextMarker
|
||||
for _, object := range listResp.Blobs {
|
||||
result.Objects = append(result.Objects, ObjectInfo{
|
||||
result.Objects = append(result.Objects, minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object.Name,
|
||||
ModTime: time.Time(object.Properties.LastModified),
|
||||
@@ -264,7 +265,7 @@ func (a *azureObjects) AnonListObjects(bucket, prefix, marker, delimiter string,
|
||||
}
|
||||
|
||||
// AnonListObjectsV2 - List objects in V2 mode, anonymously
|
||||
func (a *azureObjects) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result ListObjectsV2Info, err error) {
|
||||
func (a *azureObjects) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result minio.ListObjectsV2Info, err error) {
|
||||
params := storage.ListBlobsParameters{
|
||||
Prefix: prefix,
|
||||
Marker: continuationToken,
|
||||
@@ -279,13 +280,13 @@ func (a *azureObjects) AnonListObjectsV2(bucket, prefix, continuationToken, deli
|
||||
blobURL := a.client.GetContainerReference(bucket).GetBlobReference("").GetURL()
|
||||
url, err := url.Parse(blobURL)
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
resp, err := http.Get(url.String())
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
@@ -293,11 +294,11 @@ func (a *azureObjects) AnonListObjectsV2(bucket, prefix, continuationToken, deli
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
err = xml.Unmarshal(data, &listResp)
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err))
|
||||
return result, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
|
||||
// If NextMarker is not empty, this means response is truncated and NextContinuationToken should be set
|
||||
@@ -306,12 +307,12 @@ func (a *azureObjects) AnonListObjectsV2(bucket, prefix, continuationToken, deli
|
||||
result.NextContinuationToken = listResp.NextMarker
|
||||
}
|
||||
for _, object := range listResp.Blobs {
|
||||
result.Objects = append(result.Objects, ObjectInfo{
|
||||
result.Objects = append(result.Objects, minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object.Name,
|
||||
ModTime: time.Time(object.Properties.LastModified),
|
||||
Size: object.Properties.ContentLength,
|
||||
ETag: canonicalizeETag(object.Properties.Etag),
|
||||
ETag: minio.CanonicalizeETag(object.Properties.Etag),
|
||||
ContentType: object.Properties.ContentType,
|
||||
ContentEncoding: object.Properties.ContentEncoding,
|
||||
})
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -36,13 +35,18 @@ import (
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
globalAzureAPIVersion = "2016-05-31"
|
||||
azureBlockSize = 100 * humanize.MiByte
|
||||
metadataObjectNameTemplate = globalMinioSysTmp + "multipart/v1/%s.%x/azure.json"
|
||||
azureS3MinPartSize = 5 * humanize.MiByte
|
||||
metadataObjectNameTemplate = minio.GatewayMinioSysTmp + "multipart/v1/%s.%x/azure.json"
|
||||
azureBackend = "azure"
|
||||
)
|
||||
|
||||
@@ -67,6 +71,9 @@ ENVIRONMENT VARIABLES:
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
UPDATE:
|
||||
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for Azure Blob Storage backend.
|
||||
$ export MINIO_ACCESS_KEY=azureaccountname
|
||||
@@ -80,12 +87,11 @@ EXAMPLES:
|
||||
|
||||
`
|
||||
|
||||
MustRegisterGatewayCommand(cli.Command{
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: azureBackend,
|
||||
Usage: "Microsoft Azure Blob Storage.",
|
||||
Action: azureGatewayMain,
|
||||
CustomHelpTemplate: azureGatewayTemplate,
|
||||
Flags: append(serverFlags, globalFlags...),
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
@@ -95,24 +101,49 @@ func azureGatewayMain(ctx *cli.Context) {
|
||||
// Validate gateway arguments.
|
||||
host := ctx.Args().First()
|
||||
// Validate gateway arguments.
|
||||
fatalIf(validateGatewayArguments(ctx.GlobalString("address"), host), "Invalid argument")
|
||||
minio.FatalIf(minio.ValidateGatewayArguments(ctx.GlobalString("address"), host), "Invalid argument")
|
||||
|
||||
startGateway(ctx, &AzureGateway{host})
|
||||
minio.StartGateway(ctx, &Azure{host})
|
||||
}
|
||||
|
||||
// AzureGateway implements Gateway.
|
||||
type AzureGateway struct {
|
||||
// Azure implements Gateway.
|
||||
type Azure struct {
|
||||
host string
|
||||
}
|
||||
|
||||
// Name implements Gateway interface.
|
||||
func (g *AzureGateway) Name() string {
|
||||
func (g *Azure) Name() string {
|
||||
return azureBackend
|
||||
}
|
||||
|
||||
// NewGatewayLayer initializes azure blob storage client and returns AzureObjects.
|
||||
func (g *AzureGateway) NewGatewayLayer() (GatewayLayer, error) {
|
||||
return newAzureLayer(g.host)
|
||||
func (g *Azure) NewGatewayLayer(creds auth.Credentials) (minio.GatewayLayer, error) {
|
||||
var err error
|
||||
var endpoint = storage.DefaultBaseURL
|
||||
var secure = true
|
||||
|
||||
// If user provided some parameters
|
||||
if g.host != "" {
|
||||
endpoint, secure, err = minio.ParseGatewayEndpoint(g.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c, err := storage.NewClient(creds.AccessKey, creds.SecretKey, endpoint, globalAzureAPIVersion, secure)
|
||||
if err != nil {
|
||||
return &azureObjects{}, err
|
||||
}
|
||||
c.HTTPClient = &http.Client{Transport: minio.NewCustomHTTPTransport()}
|
||||
|
||||
return &azureObjects{
|
||||
client: c.GetBlobService(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Production - Azure gateway is production ready.
|
||||
func (g *Azure) Production() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// s3MetaToAzureProperties converts metadata meant for S3 PUT/COPY
|
||||
@@ -133,7 +164,7 @@ func s3MetaToAzureProperties(s3Metadata map[string]string) (storage.BlobMetadata
|
||||
storage.BlobProperties, error) {
|
||||
for k := range s3Metadata {
|
||||
if strings.Contains(k, "--") {
|
||||
return storage.BlobMetadata{}, storage.BlobProperties{}, traceError(UnsupportedMetadata{})
|
||||
return storage.BlobMetadata{}, storage.BlobProperties{}, errors.Trace(minio.UnsupportedMetadata{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +269,7 @@ func azurePropertiesToS3Meta(meta storage.BlobMetadata, props storage.BlobProper
|
||||
|
||||
// azureObjects - Implements Object layer for Azure blob storage.
|
||||
type azureObjects struct {
|
||||
gatewayUnsupported
|
||||
minio.GatewayUnsupported
|
||||
client storage.BlobStorageClient // Azure sdk client
|
||||
}
|
||||
|
||||
@@ -248,15 +279,15 @@ func azureToObjectError(err error, params ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, ok := err.(*Error)
|
||||
e, ok := err.(*errors.Error)
|
||||
if !ok {
|
||||
// Code should be fixed if this function is called without doing traceError()
|
||||
// Code should be fixed if this function is called without doing errors.Trace()
|
||||
// Else handling different situations in this function makes this function complicated.
|
||||
errorIf(err, "Expected type *Error")
|
||||
minio.ErrorIf(err, "Expected type *Error")
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.e
|
||||
err = e.Cause
|
||||
bucket := ""
|
||||
object := ""
|
||||
if len(params) >= 1 {
|
||||
@@ -275,26 +306,29 @@ func azureToObjectError(err error, params ...string) error {
|
||||
|
||||
switch azureErr.Code {
|
||||
case "ContainerAlreadyExists":
|
||||
err = BucketExists{Bucket: bucket}
|
||||
err = minio.BucketExists{Bucket: bucket}
|
||||
case "InvalidResourceName":
|
||||
err = BucketNameInvalid{Bucket: bucket}
|
||||
err = minio.BucketNameInvalid{Bucket: bucket}
|
||||
case "RequestBodyTooLarge":
|
||||
err = PartTooBig{}
|
||||
err = minio.PartTooBig{}
|
||||
case "InvalidMetadata":
|
||||
err = UnsupportedMetadata{}
|
||||
err = minio.UnsupportedMetadata{}
|
||||
default:
|
||||
switch azureErr.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
if object != "" {
|
||||
err = ObjectNotFound{bucket, object}
|
||||
err = minio.ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
} else {
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
err = minio.BucketNotFound{Bucket: bucket}
|
||||
}
|
||||
case http.StatusBadRequest:
|
||||
err = BucketNameInvalid{Bucket: bucket}
|
||||
err = minio.BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
}
|
||||
e.e = err
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -316,11 +350,15 @@ func mustGetAzureUploadID() string {
|
||||
// checkAzureUploadID - returns error in case of given string is upload ID.
|
||||
func checkAzureUploadID(uploadID string) (err error) {
|
||||
if len(uploadID) != 16 {
|
||||
return traceError(MalformedUploadID{uploadID})
|
||||
return errors.Trace(minio.MalformedUploadID{
|
||||
UploadID: uploadID,
|
||||
})
|
||||
}
|
||||
|
||||
if _, err = hex.DecodeString(uploadID); err != nil {
|
||||
return traceError(MalformedUploadID{uploadID})
|
||||
return errors.Trace(minio.MalformedUploadID{
|
||||
UploadID: uploadID,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -360,32 +398,6 @@ func azureParseBlockID(blockID string) (partID, subPartNumber int, uploadID, md5
|
||||
return
|
||||
}
|
||||
|
||||
// Inits azure blob storage client and returns AzureObjects.
|
||||
func newAzureLayer(host string) (GatewayLayer, error) {
|
||||
var err error
|
||||
var endpoint = storage.DefaultBaseURL
|
||||
var secure = true
|
||||
|
||||
// If user provided some parameters
|
||||
if host != "" {
|
||||
endpoint, secure, err = parseGatewayEndpoint(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
creds := serverConfig.GetCredential()
|
||||
c, err := storage.NewClient(creds.AccessKey, creds.SecretKey, endpoint, globalAzureAPIVersion, secure)
|
||||
if err != nil {
|
||||
return &azureObjects{}, err
|
||||
}
|
||||
c.HTTPClient = &http.Client{Transport: newCustomHTTPTransport()}
|
||||
|
||||
return &azureObjects{
|
||||
client: c.GetBlobService(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Shutdown - save any gateway metadata to disk
|
||||
// if necessary and reload upon next restart.
|
||||
func (a *azureObjects) Shutdown() error {
|
||||
@@ -393,7 +405,7 @@ func (a *azureObjects) Shutdown() error {
|
||||
}
|
||||
|
||||
// StorageInfo - Not relevant to Azure backend.
|
||||
func (a *azureObjects) StorageInfo() (si StorageInfo) {
|
||||
func (a *azureObjects) StorageInfo() (si minio.StorageInfo) {
|
||||
return si
|
||||
}
|
||||
|
||||
@@ -403,17 +415,17 @@ func (a *azureObjects) MakeBucketWithLocation(bucket, location string) error {
|
||||
err := container.Create(&storage.CreateContainerOptions{
|
||||
Access: storage.ContainerAccessTypePrivate,
|
||||
})
|
||||
return azureToObjectError(traceError(err), bucket)
|
||||
return azureToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
// GetBucketInfo - Get bucket metadata..
|
||||
func (a *azureObjects) GetBucketInfo(bucket string) (bi BucketInfo, e error) {
|
||||
func (a *azureObjects) GetBucketInfo(bucket string) (bi minio.BucketInfo, e error) {
|
||||
// Verify if bucket (container-name) is valid.
|
||||
// IsValidBucketName has same restrictions as container names mentioned
|
||||
// in azure documentation, so we will simply use the same function here.
|
||||
// Ref - https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata
|
||||
if !IsValidBucketName(bucket) {
|
||||
return bi, traceError(BucketNameInvalid{Bucket: bucket})
|
||||
if !minio.IsValidBucketName(bucket) {
|
||||
return bi, errors.Trace(minio.BucketNameInvalid{Bucket: bucket})
|
||||
}
|
||||
|
||||
// Azure does not have an equivalent call, hence use
|
||||
@@ -422,34 +434,34 @@ func (a *azureObjects) GetBucketInfo(bucket string) (bi BucketInfo, e error) {
|
||||
Prefix: bucket,
|
||||
})
|
||||
if err != nil {
|
||||
return bi, azureToObjectError(traceError(err), bucket)
|
||||
return bi, azureToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
for _, container := range resp.Containers {
|
||||
if container.Name == bucket {
|
||||
t, e := time.Parse(time.RFC1123, container.Properties.LastModified)
|
||||
if e == nil {
|
||||
return BucketInfo{
|
||||
return minio.BucketInfo{
|
||||
Name: bucket,
|
||||
Created: t,
|
||||
}, nil
|
||||
} // else continue
|
||||
}
|
||||
}
|
||||
return bi, traceError(BucketNotFound{Bucket: bucket})
|
||||
return bi, errors.Trace(minio.BucketNotFound{Bucket: bucket})
|
||||
}
|
||||
|
||||
// ListBuckets - Lists all azure containers, uses Azure equivalent ListContainers.
|
||||
func (a *azureObjects) ListBuckets() (buckets []BucketInfo, err error) {
|
||||
func (a *azureObjects) ListBuckets() (buckets []minio.BucketInfo, err error) {
|
||||
resp, err := a.client.ListContainers(storage.ListContainersParameters{})
|
||||
if err != nil {
|
||||
return nil, azureToObjectError(traceError(err))
|
||||
return nil, azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
for _, container := range resp.Containers {
|
||||
t, e := time.Parse(time.RFC1123, container.Properties.LastModified)
|
||||
if e != nil {
|
||||
return nil, traceError(e)
|
||||
return nil, errors.Trace(e)
|
||||
}
|
||||
buckets = append(buckets, BucketInfo{
|
||||
buckets = append(buckets, minio.BucketInfo{
|
||||
Name: container.Name,
|
||||
Created: t,
|
||||
})
|
||||
@@ -460,13 +472,13 @@ func (a *azureObjects) ListBuckets() (buckets []BucketInfo, err error) {
|
||||
// DeleteBucket - delete a container on azure, uses Azure equivalent DeleteContainer.
|
||||
func (a *azureObjects) DeleteBucket(bucket string) error {
|
||||
container := a.client.GetContainerReference(bucket)
|
||||
return azureToObjectError(traceError(container.Delete(nil)), bucket)
|
||||
return azureToObjectError(errors.Trace(container.Delete(nil)), bucket)
|
||||
}
|
||||
|
||||
// ListObjects - lists all blobs on azure with in a container filtered by prefix
|
||||
// and marker, uses Azure equivalent ListBlobs.
|
||||
func (a *azureObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) {
|
||||
var objects []ObjectInfo
|
||||
func (a *azureObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result minio.ListObjectsInfo, err error) {
|
||||
var objects []minio.ObjectInfo
|
||||
var prefixes []string
|
||||
container := a.client.GetContainerReference(bucket)
|
||||
for len(objects) == 0 && len(prefixes) == 0 {
|
||||
@@ -477,19 +489,19 @@ func (a *azureObjects) ListObjects(bucket, prefix, marker, delimiter string, max
|
||||
MaxResults: uint(maxKeys),
|
||||
})
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err), bucket, prefix)
|
||||
return result, azureToObjectError(errors.Trace(err), bucket, prefix)
|
||||
}
|
||||
|
||||
for _, object := range resp.Blobs {
|
||||
if strings.HasPrefix(object.Name, globalMinioSysTmp) {
|
||||
if strings.HasPrefix(object.Name, minio.GatewayMinioSysTmp) {
|
||||
continue
|
||||
}
|
||||
objects = append(objects, ObjectInfo{
|
||||
objects = append(objects, minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object.Name,
|
||||
ModTime: time.Time(object.Properties.LastModified),
|
||||
Size: object.Properties.ContentLength,
|
||||
ETag: toS3ETag(object.Properties.Etag),
|
||||
ETag: minio.ToS3ETag(object.Properties.Etag),
|
||||
ContentType: object.Properties.ContentType,
|
||||
ContentEncoding: object.Properties.ContentEncoding,
|
||||
})
|
||||
@@ -497,7 +509,7 @@ func (a *azureObjects) ListObjects(bucket, prefix, marker, delimiter string, max
|
||||
|
||||
// Remove minio.sys.tmp prefix.
|
||||
for _, prefix := range resp.BlobPrefixes {
|
||||
if prefix != globalMinioSysTmp {
|
||||
if prefix != minio.GatewayMinioSysTmp {
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
}
|
||||
@@ -516,13 +528,13 @@ func (a *azureObjects) ListObjects(bucket, prefix, marker, delimiter string, max
|
||||
}
|
||||
|
||||
// ListObjectsV2 - list all blobs in Azure bucket filtered by prefix
|
||||
func (a *azureObjects) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result ListObjectsV2Info, err error) {
|
||||
func (a *azureObjects) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result minio.ListObjectsV2Info, err error) {
|
||||
marker := continuationToken
|
||||
if startAfter != "" {
|
||||
marker = startAfter
|
||||
}
|
||||
|
||||
var resultV1 ListObjectsInfo
|
||||
var resultV1 minio.ListObjectsInfo
|
||||
resultV1, err = a.ListObjects(bucket, prefix, marker, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return result, err
|
||||
@@ -545,7 +557,7 @@ func (a *azureObjects) ListObjectsV2(bucket, prefix, continuationToken, delimite
|
||||
func (a *azureObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) error {
|
||||
// startOffset cannot be negative.
|
||||
if startOffset < 0 {
|
||||
return toObjectErr(traceError(errUnexpected), bucket, object)
|
||||
return azureToObjectError(errors.Trace(minio.InvalidRange{}), bucket, object)
|
||||
}
|
||||
|
||||
blobRange := &storage.BlobRange{Start: uint64(startOffset)}
|
||||
@@ -564,39 +576,37 @@ func (a *azureObjects) GetObject(bucket, object string, startOffset int64, lengt
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return azureToObjectError(traceError(err), bucket, object)
|
||||
return azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
_, err = io.Copy(writer, rc)
|
||||
rc.Close()
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
// GetObjectInfo - reads blob metadata properties and replies back ObjectInfo,
|
||||
// GetObjectInfo - reads blob metadata properties and replies back minio.ObjectInfo,
|
||||
// uses zure equivalent GetBlobProperties.
|
||||
func (a *azureObjects) GetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error) {
|
||||
func (a *azureObjects) GetObjectInfo(bucket, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
blob := a.client.GetContainerReference(bucket).GetBlobReference(object)
|
||||
err = blob.GetProperties(nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
meta := azurePropertiesToS3Meta(blob.Metadata, blob.Properties)
|
||||
objInfo = ObjectInfo{
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
UserDefined: meta,
|
||||
ETag: toS3ETag(blob.Properties.Etag),
|
||||
UserDefined: azurePropertiesToS3Meta(blob.Metadata, blob.Properties),
|
||||
ETag: minio.ToS3ETag(blob.Properties.Etag),
|
||||
ModTime: time.Time(blob.Properties.LastModified),
|
||||
Name: object,
|
||||
Size: blob.Properties.ContentLength,
|
||||
ContentType: blob.Properties.ContentType,
|
||||
ContentEncoding: blob.Properties.ContentEncoding,
|
||||
}
|
||||
return objInfo, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PutObject - Create a new blob with the incoming data,
|
||||
// uses Azure equivalent CreateBlockBlobFromReader.
|
||||
func (a *azureObjects) PutObject(bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, err error) {
|
||||
func (a *azureObjects) PutObject(bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
blob := a.client.GetContainerReference(bucket).GetBlobReference(object)
|
||||
blob.Metadata, blob.Properties, err = s3MetaToAzureProperties(metadata)
|
||||
if err != nil {
|
||||
@@ -604,14 +614,14 @@ func (a *azureObjects) PutObject(bucket, object string, data *hash.Reader, metad
|
||||
}
|
||||
err = blob.CreateBlockBlobFromReader(data, nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
return a.GetObjectInfo(bucket, object)
|
||||
}
|
||||
|
||||
// CopyObject - Copies a blob from source container to destination container.
|
||||
// Uses Azure equivalent CopyBlob API.
|
||||
func (a *azureObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string) (objInfo ObjectInfo, err error) {
|
||||
func (a *azureObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
srcBlobURL := a.client.GetContainerReference(srcBucket).GetBlobReference(srcObject).GetURL()
|
||||
destBlob := a.client.GetContainerReference(destBucket).GetBlobReference(destObject)
|
||||
azureMeta, props, err := s3MetaToAzureProperties(metadata)
|
||||
@@ -621,12 +631,12 @@ func (a *azureObjects) CopyObject(srcBucket, srcObject, destBucket, destObject s
|
||||
destBlob.Metadata = azureMeta
|
||||
err = destBlob.Copy(srcBlobURL, nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), srcBucket, srcObject)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
destBlob.Properties = props
|
||||
err = destBlob.SetProperties(nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), srcBucket, srcObject)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
return a.GetObjectInfo(destBucket, destObject)
|
||||
}
|
||||
@@ -637,13 +647,13 @@ func (a *azureObjects) DeleteObject(bucket, object string) error {
|
||||
blob := a.client.GetContainerReference(bucket).GetBlobReference(object)
|
||||
err := blob.Delete(nil)
|
||||
if err != nil {
|
||||
return azureToObjectError(traceError(err), bucket, object)
|
||||
return azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListMultipartUploads - It's decided not to support List Multipart Uploads, hence returning empty result.
|
||||
func (a *azureObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error) {
|
||||
func (a *azureObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result minio.ListMultipartsInfo, err error) {
|
||||
// It's decided not to support List Multipart Uploads, hence returning empty result.
|
||||
return result, nil
|
||||
}
|
||||
@@ -661,10 +671,15 @@ func (a *azureObjects) checkUploadIDExists(bucketName, objectName, uploadID stri
|
||||
blob := a.client.GetContainerReference(bucketName).GetBlobReference(
|
||||
getAzureMetadataObjectName(objectName, uploadID))
|
||||
err = blob.GetMetadata(nil)
|
||||
err = azureToObjectError(traceError(err), bucketName, objectName)
|
||||
oerr := ObjectNotFound{bucketName, objectName}
|
||||
if errorCause(err) == oerr {
|
||||
err = traceError(InvalidUploadID{})
|
||||
err = azureToObjectError(errors.Trace(err), bucketName, objectName)
|
||||
oerr := minio.ObjectNotFound{
|
||||
Bucket: bucketName,
|
||||
Object: objectName,
|
||||
}
|
||||
if errors.Cause(err) == oerr {
|
||||
err = errors.Trace(minio.InvalidUploadID{
|
||||
UploadID: uploadID,
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -673,26 +688,26 @@ func (a *azureObjects) checkUploadIDExists(bucketName, objectName, uploadID stri
|
||||
func (a *azureObjects) NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error) {
|
||||
uploadID = mustGetAzureUploadID()
|
||||
if err = a.checkUploadIDExists(bucket, object, uploadID); err == nil {
|
||||
return "", traceError(errors.New("Upload ID name collision"))
|
||||
return "", errors.Trace(fmt.Errorf("Upload ID name collision"))
|
||||
}
|
||||
metadataObject := getAzureMetadataObjectName(object, uploadID)
|
||||
|
||||
var jsonData []byte
|
||||
if jsonData, err = json.Marshal(azureMultipartMetadata{Name: object, Metadata: metadata}); err != nil {
|
||||
return "", traceError(err)
|
||||
return "", errors.Trace(err)
|
||||
}
|
||||
|
||||
blob := a.client.GetContainerReference(bucket).GetBlobReference(metadataObject)
|
||||
err = blob.CreateBlockBlobFromReader(bytes.NewBuffer(jsonData), nil)
|
||||
if err != nil {
|
||||
return "", azureToObjectError(traceError(err), bucket, metadataObject)
|
||||
return "", azureToObjectError(errors.Trace(err), bucket, metadataObject)
|
||||
}
|
||||
|
||||
return uploadID, nil
|
||||
}
|
||||
|
||||
// PutObjectPart - Use Azure equivalent PutBlockWithLength.
|
||||
func (a *azureObjects) PutObjectPart(bucket, object, uploadID string, partID int, data *hash.Reader) (info PartInfo, err error) {
|
||||
func (a *azureObjects) PutObjectPart(bucket, object, uploadID string, partID int, data *hash.Reader) (info minio.PartInfo, err error) {
|
||||
if err = a.checkUploadIDExists(bucket, object, uploadID); err != nil {
|
||||
return info, err
|
||||
}
|
||||
@@ -703,7 +718,7 @@ func (a *azureObjects) PutObjectPart(bucket, object, uploadID string, partID int
|
||||
|
||||
etag := data.MD5HexString()
|
||||
if etag == "" {
|
||||
etag = genETag()
|
||||
etag = minio.GenETag()
|
||||
}
|
||||
|
||||
subPartSize, subPartNumber := int64(azureBlockSize), 1
|
||||
@@ -721,20 +736,20 @@ func (a *azureObjects) PutObjectPart(bucket, object, uploadID string, partID int
|
||||
blob := a.client.GetContainerReference(bucket).GetBlobReference(object)
|
||||
err = blob.PutBlockWithLength(id, uint64(subPartSize), io.LimitReader(data, subPartSize), nil)
|
||||
if err != nil {
|
||||
return info, azureToObjectError(traceError(err), bucket, object)
|
||||
return info, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
subPartNumber++
|
||||
}
|
||||
|
||||
info.PartNumber = partID
|
||||
info.ETag = etag
|
||||
info.LastModified = UTCNow()
|
||||
info.LastModified = minio.UTCNow()
|
||||
info.Size = data.Size()
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ListObjectParts - Use Azure equivalent GetBlockList.
|
||||
func (a *azureObjects) ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListPartsInfo, err error) {
|
||||
func (a *azureObjects) ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result minio.ListPartsInfo, err error) {
|
||||
if err = a.checkUploadIDExists(bucket, object, uploadID); err != nil {
|
||||
return result, err
|
||||
}
|
||||
@@ -746,24 +761,29 @@ func (a *azureObjects) ListObjectParts(bucket, object, uploadID string, partNumb
|
||||
|
||||
objBlob := a.client.GetContainerReference(bucket).GetBlobReference(object)
|
||||
resp, err := objBlob.GetBlockList(storage.BlockListTypeUncommitted, nil)
|
||||
azureErr, ok := err.(storage.AzureStorageServiceError)
|
||||
if ok && azureErr.StatusCode == http.StatusNotFound {
|
||||
// If no parts are uploaded yet then we return empty list.
|
||||
return result, nil
|
||||
}
|
||||
if err != nil {
|
||||
return result, azureToObjectError(traceError(err), bucket, object)
|
||||
return result, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
// Build a sorted list of parts and return the requested entries.
|
||||
partsMap := make(map[int]PartInfo)
|
||||
partsMap := make(map[int]minio.PartInfo)
|
||||
for _, block := range resp.UncommittedBlocks {
|
||||
var partNumber int
|
||||
var parsedUploadID string
|
||||
var md5Hex string
|
||||
if partNumber, _, parsedUploadID, md5Hex, err = azureParseBlockID(block.Name); err != nil {
|
||||
return result, azureToObjectError(traceError(errUnexpected), bucket, object)
|
||||
return result, azureToObjectError(errors.Trace(fmt.Errorf("Unexpected error")), bucket, object)
|
||||
}
|
||||
if parsedUploadID != uploadID {
|
||||
continue
|
||||
}
|
||||
part, ok := partsMap[partNumber]
|
||||
if !ok {
|
||||
partsMap[partNumber] = PartInfo{
|
||||
partsMap[partNumber] = minio.PartInfo{
|
||||
PartNumber: partNumber,
|
||||
Size: block.Size,
|
||||
ETag: md5Hex,
|
||||
@@ -773,12 +793,12 @@ func (a *azureObjects) ListObjectParts(bucket, object, uploadID string, partNumb
|
||||
if part.ETag != md5Hex {
|
||||
// If two parts of same partNumber were uploaded with different contents
|
||||
// return error as we won't be able to decide which the latest part is.
|
||||
return result, azureToObjectError(traceError(errUnexpected), bucket, object)
|
||||
return result, azureToObjectError(errors.Trace(fmt.Errorf("Unexpected error")), bucket, object)
|
||||
}
|
||||
part.Size += block.Size
|
||||
partsMap[partNumber] = part
|
||||
}
|
||||
var parts []PartInfo
|
||||
var parts []minio.PartInfo
|
||||
for _, part := range partsMap {
|
||||
parts = append(parts, part)
|
||||
}
|
||||
@@ -826,7 +846,7 @@ func (a *azureObjects) AbortMultipartUpload(bucket, object, uploadID string) (er
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload - Use Azure equivalent PutBlockList.
|
||||
func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string, uploadedParts []CompletePart) (objInfo ObjectInfo, err error) {
|
||||
func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string, uploadedParts []minio.CompletePart) (objInfo minio.ObjectInfo, err error) {
|
||||
metadataObject := getAzureMetadataObjectName(object, uploadID)
|
||||
if err = a.checkUploadIDExists(bucket, object, uploadID); err != nil {
|
||||
return objInfo, err
|
||||
@@ -839,12 +859,12 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
var metadataReader io.Reader
|
||||
blob := a.client.GetContainerReference(bucket).GetBlobReference(metadataObject)
|
||||
if metadataReader, err = blob.Get(nil); err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, metadataObject)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, metadataObject)
|
||||
}
|
||||
|
||||
var metadata azureMultipartMetadata
|
||||
if err = json.NewDecoder(metadataReader).Decode(&metadata); err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, metadataObject)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, metadataObject)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -854,13 +874,13 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
|
||||
blob := a.client.GetContainerReference(bucket).GetBlobReference(metadataObject)
|
||||
derr := blob.Delete(nil)
|
||||
errorIf(derr, "unable to remove meta data object for upload ID %s", uploadID)
|
||||
minio.ErrorIf(derr, "unable to remove meta data object for upload ID %s", uploadID)
|
||||
}()
|
||||
|
||||
objBlob := a.client.GetContainerReference(bucket).GetBlobReference(object)
|
||||
resp, err := objBlob.GetBlockList(storage.BlockListTypeUncommitted, nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
getBlocks := func(partNumber int, etag string) (blocks []storage.Block, size int64, err error) {
|
||||
@@ -883,7 +903,7 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
}
|
||||
|
||||
if len(blocks) == 0 {
|
||||
return nil, 0, InvalidPart{}
|
||||
return nil, 0, minio.InvalidPart{}
|
||||
}
|
||||
|
||||
return blocks, size, nil
|
||||
@@ -896,7 +916,7 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
var size int64
|
||||
blocks, size, err = getBlocks(part.PartNumber, part.ETag)
|
||||
if err != nil {
|
||||
return objInfo, traceError(err)
|
||||
return objInfo, errors.Trace(err)
|
||||
}
|
||||
|
||||
allBlocks = append(allBlocks, blocks...)
|
||||
@@ -905,8 +925,8 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
|
||||
// Error out if parts except last part sizing < 5MiB.
|
||||
for i, size := range partSizes[:len(partSizes)-1] {
|
||||
if size < globalMinPartSize {
|
||||
return objInfo, traceError(PartTooSmall{
|
||||
if size < azureS3MinPartSize {
|
||||
return objInfo, errors.Trace(minio.PartTooSmall{
|
||||
PartNumber: uploadedParts[i].PartNumber,
|
||||
PartSize: size,
|
||||
PartETag: uploadedParts[i].ETag,
|
||||
@@ -916,7 +936,7 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
|
||||
err = objBlob.PutBlockList(allBlocks, nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
if len(metadata.Metadata) > 0 {
|
||||
objBlob.Metadata, objBlob.Properties, err = s3MetaToAzureProperties(metadata.Metadata)
|
||||
@@ -925,11 +945,11 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
}
|
||||
err = objBlob.SetProperties(nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
err = objBlob.SetMetadata(nil)
|
||||
if err != nil {
|
||||
return objInfo, azureToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, azureToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
}
|
||||
return a.GetObjectInfo(bucket, object)
|
||||
@@ -942,23 +962,23 @@ func (a *azureObjects) CompleteMultipartUpload(bucket, object, uploadID string,
|
||||
// As the common denominator for minio and azure is readonly and none, we support
|
||||
// these two policies at the bucket level.
|
||||
func (a *azureObjects) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error {
|
||||
var policies []BucketAccessPolicy
|
||||
var policies []minio.BucketAccessPolicy
|
||||
|
||||
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket) {
|
||||
policies = append(policies, BucketAccessPolicy{
|
||||
policies = append(policies, minio.BucketAccessPolicy{
|
||||
Prefix: prefix,
|
||||
Policy: policy,
|
||||
})
|
||||
}
|
||||
prefix := bucket + "/*" // For all objects inside the bucket.
|
||||
if len(policies) != 1 {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
if policies[0].Prefix != prefix {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
if policies[0].Policy != policy.BucketPolicyReadOnly {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
perm := storage.ContainerPermissions{
|
||||
AccessType: storage.ContainerAccessTypeContainer,
|
||||
@@ -966,7 +986,7 @@ func (a *azureObjects) SetBucketPolicies(bucket string, policyInfo policy.Bucket
|
||||
}
|
||||
container := a.client.GetContainerReference(bucket)
|
||||
err := container.SetPermissions(perm, nil)
|
||||
return azureToObjectError(traceError(err), bucket)
|
||||
return azureToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
// GetBucketPolicies - Get the container ACL and convert it to canonical []bucketAccessPolicy
|
||||
@@ -975,15 +995,15 @@ func (a *azureObjects) GetBucketPolicies(bucket string) (policy.BucketAccessPoli
|
||||
container := a.client.GetContainerReference(bucket)
|
||||
perm, err := container.GetPermissions(nil)
|
||||
if err != nil {
|
||||
return policy.BucketAccessPolicy{}, azureToObjectError(traceError(err), bucket)
|
||||
return policy.BucketAccessPolicy{}, azureToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
switch perm.AccessType {
|
||||
case storage.ContainerAccessTypePrivate:
|
||||
return policy.BucketAccessPolicy{}, traceError(PolicyNotFound{Bucket: bucket})
|
||||
return policy.BucketAccessPolicy{}, errors.Trace(minio.PolicyNotFound{Bucket: bucket})
|
||||
case storage.ContainerAccessTypeContainer:
|
||||
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadOnly, bucket, "")
|
||||
default:
|
||||
return policy.BucketAccessPolicy{}, azureToObjectError(traceError(NotImplemented{}))
|
||||
return policy.BucketAccessPolicy{}, azureToObjectError(errors.Trace(minio.NotImplemented{}))
|
||||
}
|
||||
return policyInfo, nil
|
||||
}
|
||||
@@ -996,5 +1016,5 @@ func (a *azureObjects) DeleteBucketPolicies(bucket string) error {
|
||||
}
|
||||
container := a.client.GetContainerReference(bucket)
|
||||
err := container.SetPermissions(perm, nil)
|
||||
return azureToObjectError(traceError(err))
|
||||
return azureToObjectError(errors.Trace(err))
|
||||
}
|
||||
@@ -14,15 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// Test canonical metadata.
|
||||
@@ -30,6 +33,10 @@ func TestS3MetaToAzureProperties(t *testing.T) {
|
||||
headers := map[string]string{
|
||||
"accept-encoding": "gzip",
|
||||
"content-encoding": "gzip",
|
||||
"cache-control": "age: 3600",
|
||||
"content-disposition": "dummy",
|
||||
"content-length": "10",
|
||||
"content-type": "application/javascript",
|
||||
"X-Amz-Meta-Hdr": "value",
|
||||
"X-Amz-Meta-X_test_key": "value",
|
||||
"X-Amz-Meta-X__test__key": "value",
|
||||
@@ -60,8 +67,8 @@ func TestS3MetaToAzureProperties(t *testing.T) {
|
||||
"invalid--meta": "value",
|
||||
}
|
||||
_, _, err = s3MetaToAzureProperties(headers)
|
||||
if err = errorCause(err); err != nil {
|
||||
if _, ok := err.(UnsupportedMetadata); !ok {
|
||||
if err = errors.Cause(err); err != nil {
|
||||
if _, ok := err.(minio.UnsupportedMetadata); !ok {
|
||||
t.Fatalf("Test failed with unexpected error %s, expected UnsupportedMetadata", err)
|
||||
}
|
||||
}
|
||||
@@ -100,8 +107,21 @@ func TestAzurePropertiesToS3Meta(t *testing.T) {
|
||||
"X-Amz-Meta-X-Amz-Key": "hu3ZSqtqwn+aL4V2VhAeov4i+bG3KyCtRMSXQFRHXOk=",
|
||||
"X-Amz-Meta-X-Amz-Matdesc": "{}",
|
||||
"X-Amz-Meta-X-Amz-Iv": "eWmyryl8kq+EVnnsE7jpOg==",
|
||||
"Cache-Control": "max-age: 3600",
|
||||
"Content-Disposition": "dummy",
|
||||
"Content-Encoding": "gzip",
|
||||
"Content-Length": "10",
|
||||
"Content-MD5": "base64-md5",
|
||||
"Content-Type": "application/javascript",
|
||||
}
|
||||
actualMeta := azurePropertiesToS3Meta(metadata, storage.BlobProperties{})
|
||||
actualMeta := azurePropertiesToS3Meta(metadata, storage.BlobProperties{
|
||||
CacheControl: "max-age: 3600",
|
||||
ContentDisposition: "dummy",
|
||||
ContentEncoding: "gzip",
|
||||
ContentLength: 10,
|
||||
ContentMD5: "base64-md5",
|
||||
ContentType: "application/javascript",
|
||||
})
|
||||
if !reflect.DeepEqual(actualMeta, expectedMeta) {
|
||||
t.Fatalf("Test failed, expected %#v, got %#v", expectedMeta, actualMeta)
|
||||
}
|
||||
@@ -118,43 +138,57 @@ func TestAzureToObjectError(t *testing.T) {
|
||||
nil, nil, "", "",
|
||||
},
|
||||
{
|
||||
traceError(errUnexpected), errUnexpected, "", "",
|
||||
errors.Trace(fmt.Errorf("Non azure error")),
|
||||
fmt.Errorf("Non azure error"), "", "",
|
||||
},
|
||||
{
|
||||
traceError(errUnexpected), traceError(errUnexpected), "", "",
|
||||
},
|
||||
{
|
||||
traceError(storage.AzureStorageServiceError{
|
||||
storage.AzureStorageServiceError{
|
||||
Code: "ContainerAlreadyExists",
|
||||
}), BucketExists{Bucket: "bucket"}, "bucket", "",
|
||||
}, storage.AzureStorageServiceError{
|
||||
Code: "ContainerAlreadyExists",
|
||||
}, "bucket", "",
|
||||
},
|
||||
{
|
||||
traceError(storage.AzureStorageServiceError{
|
||||
errors.Trace(storage.AzureStorageServiceError{
|
||||
Code: "ContainerAlreadyExists",
|
||||
}), minio.BucketExists{Bucket: "bucket"}, "bucket", "",
|
||||
},
|
||||
{
|
||||
errors.Trace(storage.AzureStorageServiceError{
|
||||
Code: "InvalidResourceName",
|
||||
}), BucketNameInvalid{Bucket: "bucket."}, "bucket.", "",
|
||||
}), minio.BucketNameInvalid{Bucket: "bucket."}, "bucket.", "",
|
||||
},
|
||||
{
|
||||
traceError(storage.AzureStorageServiceError{
|
||||
errors.Trace(storage.AzureStorageServiceError{
|
||||
Code: "RequestBodyTooLarge",
|
||||
}), minio.PartTooBig{}, "", "",
|
||||
},
|
||||
{
|
||||
errors.Trace(storage.AzureStorageServiceError{
|
||||
Code: "InvalidMetadata",
|
||||
}), minio.UnsupportedMetadata{}, "", "",
|
||||
},
|
||||
{
|
||||
errors.Trace(storage.AzureStorageServiceError{
|
||||
StatusCode: http.StatusNotFound,
|
||||
}), ObjectNotFound{
|
||||
}), minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
}, "bucket", "object",
|
||||
},
|
||||
{
|
||||
traceError(storage.AzureStorageServiceError{
|
||||
errors.Trace(storage.AzureStorageServiceError{
|
||||
StatusCode: http.StatusNotFound,
|
||||
}), BucketNotFound{Bucket: "bucket"}, "bucket", "",
|
||||
}), minio.BucketNotFound{Bucket: "bucket"}, "bucket", "",
|
||||
},
|
||||
{
|
||||
traceError(storage.AzureStorageServiceError{
|
||||
errors.Trace(storage.AzureStorageServiceError{
|
||||
StatusCode: http.StatusBadRequest,
|
||||
}), BucketNameInvalid{Bucket: "bucket."}, "bucket.", "",
|
||||
}), minio.BucketNameInvalid{Bucket: "bucket."}, "bucket.", "",
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
err := azureToObjectError(testCase.actualErr, testCase.bucket, testCase.object)
|
||||
if err != nil {
|
||||
if err := azureToObjectError(testCase.actualErr, testCase.bucket, testCase.object); err != nil {
|
||||
if err.Error() != testCase.expectedErr.Error() {
|
||||
t.Errorf("Test %d: Expected error %s, got %s", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
@@ -190,32 +224,41 @@ func TestAzureParseBlockID(t *testing.T) {
|
||||
subPartNumber int
|
||||
uploadID string
|
||||
md5 string
|
||||
success bool
|
||||
}{
|
||||
{"MDAwMDEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQ==", 1, 7, "f328c35cad938137", "d41d8cd98f00b204e9800998ecf8427e"},
|
||||
{"MDAwMDIuMTkuYWJjZGMzNWNhZDkzODEzNy5hN2ZiNmI3YjM2ZWU0ZWQ2NmI1NTQ2ZmFjNDY5MDI3Mw==", 2, 19, "abcdc35cad938137", "a7fb6b7b36ee4ed66b5546fac4690273"},
|
||||
// Invalid base64.
|
||||
{"MDAwMDEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQ=", 0, 0, "", "", false},
|
||||
// Invalid number of tokens.
|
||||
{"MDAwMDEuQUEuZjMyOGMzNWNhZDkzODEzNwo=", 0, 0, "", "", false},
|
||||
// Invalid encoded part ID.
|
||||
{"MDAwMGEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQo=", 0, 0, "", "", false},
|
||||
// Invalid sub part ID.
|
||||
{"MDAwMDEuQUEuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQo=", 0, 0, "", "", false},
|
||||
{"MDAwMDEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQ==", 1, 7, "f328c35cad938137", "d41d8cd98f00b204e9800998ecf8427e", true},
|
||||
{"MDAwMDIuMTkuYWJjZGMzNWNhZDkzODEzNy5hN2ZiNmI3YjM2ZWU0ZWQ2NmI1NTQ2ZmFjNDY5MDI3Mw==", 2, 19, "abcdc35cad938137", "a7fb6b7b36ee4ed66b5546fac4690273", true},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
for i, test := range testCases {
|
||||
partID, subPartNumber, uploadID, md5, err := azureParseBlockID(test.blockID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if err != nil && test.success {
|
||||
t.Errorf("Test %d: Expected success but failed %s", i+1, err)
|
||||
}
|
||||
if partID != test.partID {
|
||||
t.Fatalf("%d not equal to %d", partID, test.partID)
|
||||
if err == nil && !test.success {
|
||||
t.Errorf("Test %d: Expected to fail but succeeeded insteadl", i+1)
|
||||
}
|
||||
if subPartNumber != test.subPartNumber {
|
||||
t.Fatalf("%d not equal to %d", subPartNumber, test.subPartNumber)
|
||||
if err == nil {
|
||||
if partID != test.partID {
|
||||
t.Errorf("Test %d: %d not equal to %d", i+1, partID, test.partID)
|
||||
}
|
||||
if subPartNumber != test.subPartNumber {
|
||||
t.Errorf("Test %d: %d not equal to %d", i+1, subPartNumber, test.subPartNumber)
|
||||
}
|
||||
if uploadID != test.uploadID {
|
||||
t.Errorf("Test %d: %s not equal to %s", i+1, uploadID, test.uploadID)
|
||||
}
|
||||
if md5 != test.md5 {
|
||||
t.Errorf("Test %d: %s not equal to %s", i+1, md5, test.md5)
|
||||
}
|
||||
}
|
||||
if uploadID != test.uploadID {
|
||||
t.Fatalf("%s not equal to %s", uploadID, test.uploadID)
|
||||
}
|
||||
if md5 != test.md5 {
|
||||
t.Fatalf("%s not equal to %s", md5, test.md5)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, _, _, err := azureParseBlockID("junk")
|
||||
if err == nil {
|
||||
t.Fatal("Expected azureParseBlockID() to return error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,32 +322,27 @@ func TestAnonErrToObjectErr(t *testing.T) {
|
||||
{"ObjectNotFound",
|
||||
http.StatusNotFound,
|
||||
[]string{"testBucket", "testObject"},
|
||||
ObjectNotFound{Bucket: "testBucket", Object: "testObject"},
|
||||
minio.ObjectNotFound{Bucket: "testBucket", Object: "testObject"},
|
||||
},
|
||||
{"BucketNotFound",
|
||||
http.StatusNotFound,
|
||||
[]string{"testBucket", ""},
|
||||
BucketNotFound{Bucket: "testBucket"},
|
||||
minio.BucketNotFound{Bucket: "testBucket"},
|
||||
},
|
||||
{"ObjectNameInvalid",
|
||||
http.StatusBadRequest,
|
||||
[]string{"testBucket", "testObject"},
|
||||
ObjectNameInvalid{Bucket: "testBucket", Object: "testObject"},
|
||||
minio.ObjectNameInvalid{Bucket: "testBucket", Object: "testObject"},
|
||||
},
|
||||
{"BucketNameInvalid",
|
||||
http.StatusBadRequest,
|
||||
[]string{"testBucket", ""},
|
||||
BucketNameInvalid{Bucket: "testBucket"},
|
||||
},
|
||||
{"UnexpectedError",
|
||||
http.StatusBadGateway,
|
||||
[]string{"testBucket", "testObject"},
|
||||
errUnexpected,
|
||||
minio.BucketNameInvalid{Bucket: "testBucket"},
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if err := anonErrToObjectErr(test.statusCode, test.params...); !reflect.DeepEqual(err, test.wantErr) {
|
||||
if err := minio.AnonErrToObjectErr(test.statusCode, test.params...); !reflect.DeepEqual(err, test.wantErr) {
|
||||
t.Errorf("anonErrToObjectErr() error = %v, wantErr %v", err, test.wantErr)
|
||||
}
|
||||
})
|
||||
@@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package b2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -25,6 +24,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
// mkRange converts offset, size into Range header equivalent.
|
||||
@@ -33,9 +36,9 @@ func mkRange(offset, size int64) string {
|
||||
return ""
|
||||
}
|
||||
if size == 0 {
|
||||
return fmt.Sprintf("%s%d-", byteRangePrefix, offset)
|
||||
return fmt.Sprintf("bytes=%d-", offset)
|
||||
}
|
||||
return fmt.Sprintf("%s%d-%d", byteRangePrefix, offset, offset+size-1)
|
||||
return fmt.Sprintf("bytes=%d-%d", offset, offset+size-1)
|
||||
}
|
||||
|
||||
// AnonGetObject - performs a plain http GET request on a public resource,
|
||||
@@ -44,7 +47,7 @@ func (l *b2Objects) AnonGetObject(bucket string, object string, startOffset int6
|
||||
uri := fmt.Sprintf("%s/file/%s/%s", l.b2Client.DownloadURI, bucket, object)
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return b2ToObjectError(traceError(err), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
rng := mkRange(startOffset, length)
|
||||
if rng != "" {
|
||||
@@ -52,14 +55,14 @@ func (l *b2Objects) AnonGetObject(bucket string, object string, startOffset int6
|
||||
}
|
||||
resp, err := l.anonClient.Do(req)
|
||||
if err != nil {
|
||||
return b2ToObjectError(traceError(err), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return b2ToObjectError(traceError(errors.New(resp.Status)), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(fmt.Errorf(resp.Status)), bucket, object)
|
||||
}
|
||||
_, err = io.Copy(writer, resp.Body)
|
||||
return b2ToObjectError(traceError(err), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Converts http Header into ObjectInfo. This function looks for all the
|
||||
@@ -70,16 +73,16 @@ func (l *b2Objects) AnonGetObject(bucket string, object string, startOffset int6
|
||||
// X-Bz-Info-<header>:<value> is converted to <header>:<value>
|
||||
// Content-Type is converted to ContentType.
|
||||
// X-Bz-Content-Sha1 is converted to ETag.
|
||||
func headerToObjectInfo(bucket, object string, header http.Header) (objInfo ObjectInfo, err error) {
|
||||
func headerToObjectInfo(bucket, object string, header http.Header) (objInfo minio.ObjectInfo, err error) {
|
||||
clen, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Converting upload timestamp in milliseconds to a time.Time value for ObjectInfo.ModTime.
|
||||
timeStamp, err := strconv.ParseInt(header.Get("X-Bz-Upload-Timestamp"), 10, 64)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Populate user metadata by looking for all the X-Bz-Info-<name>
|
||||
@@ -91,18 +94,18 @@ func headerToObjectInfo(bucket, object string, header http.Header) (objInfo Obje
|
||||
var name string
|
||||
name, err = url.QueryUnescape(strings.TrimPrefix(key, "X-Bz-Info-"))
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
var val string
|
||||
val, err = url.QueryUnescape(header.Get(key))
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
userMetadata[name] = val
|
||||
}
|
||||
}
|
||||
|
||||
return ObjectInfo{
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
ContentType: header.Get("Content-Type"),
|
||||
@@ -115,19 +118,19 @@ func headerToObjectInfo(bucket, object string, header http.Header) (objInfo Obje
|
||||
|
||||
// AnonGetObjectInfo - performs a plain http HEAD request on a public resource,
|
||||
// fails if the resource is not public.
|
||||
func (l *b2Objects) AnonGetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) {
|
||||
func (l *b2Objects) AnonGetObjectInfo(bucket string, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
uri := fmt.Sprintf("%s/file/%s/%s", l.b2Client.DownloadURI, bucket, object)
|
||||
req, err := http.NewRequest("HEAD", uri, nil)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
resp, err := l.anonClient.Do(req)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return objInfo, b2ToObjectError(traceError(errors.New(resp.Status)), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(fmt.Errorf(resp.Status)), bucket, object)
|
||||
}
|
||||
return headerToObjectInfo(bucket, object, resp.Header)
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package b2
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -32,7 +33,10 @@ import (
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
h2 "github.com/minio/minio/pkg/hash"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
// Supported bucket types by B2 backend.
|
||||
@@ -60,6 +64,9 @@ ENVIRONMENT VARIABLES:
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
UPDATE:
|
||||
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for B2 backend.
|
||||
$ export MINIO_ACCESS_KEY=accountID
|
||||
@@ -67,53 +74,33 @@ EXAMPLES:
|
||||
$ {{.HelpName}}
|
||||
|
||||
`
|
||||
|
||||
MustRegisterGatewayCommand(cli.Command{
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: b2Backend,
|
||||
Usage: "Backblaze B2.",
|
||||
Action: b2GatewayMain,
|
||||
CustomHelpTemplate: b2GatewayTemplate,
|
||||
Flags: append(serverFlags, globalFlags...),
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for 'minio gateway b2' command line.
|
||||
func b2GatewayMain(ctx *cli.Context) {
|
||||
startGateway(ctx, &B2Gateway{})
|
||||
minio.StartGateway(ctx, &B2{})
|
||||
}
|
||||
|
||||
// B2Gateway implements Gateway.
|
||||
type B2Gateway struct{}
|
||||
// B2 implements Minio Gateway
|
||||
type B2 struct{}
|
||||
|
||||
// Name implements Gateway interface.
|
||||
func (g *B2Gateway) Name() string {
|
||||
func (g *B2) Name() string {
|
||||
return b2Backend
|
||||
}
|
||||
|
||||
// NewGatewayLayer returns b2 gateway layer, implements GatewayLayer interface to
|
||||
// talk to B2 remote backend.
|
||||
func (g *B2Gateway) NewGatewayLayer() (GatewayLayer, error) {
|
||||
log.Println(colorYellow("\n *** Warning: Not Ready for Production ***"))
|
||||
return newB2GatewayLayer()
|
||||
}
|
||||
|
||||
// b2Object implements gateway for Minio and BackBlaze B2 compatible object storage servers.
|
||||
type b2Objects struct {
|
||||
gatewayUnsupported
|
||||
mu sync.Mutex
|
||||
creds auth.Credentials
|
||||
b2Client *b2.B2
|
||||
anonClient *http.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// newB2GatewayLayer returns b2 gateway layer.
|
||||
func newB2GatewayLayer() (GatewayLayer, error) {
|
||||
func (g *B2) NewGatewayLayer(creds auth.Credentials) (minio.GatewayLayer, error) {
|
||||
ctx := context.Background()
|
||||
creds := serverConfig.GetCredential()
|
||||
|
||||
client, err := b2.AuthorizeAccount(ctx, creds.AccessKey, creds.SecretKey, b2.Transport(newCustomHTTPTransport()))
|
||||
client, err := b2.AuthorizeAccount(ctx, creds.AccessKey, creds.SecretKey, b2.Transport(minio.NewCustomHTTPTransport()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -122,27 +109,43 @@ func newB2GatewayLayer() (GatewayLayer, error) {
|
||||
creds: creds,
|
||||
b2Client: client,
|
||||
anonClient: &http.Client{
|
||||
Transport: newCustomHTTPTransport(),
|
||||
Transport: minio.NewCustomHTTPTransport(),
|
||||
},
|
||||
ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Production - Ready for production use?
|
||||
func (g *B2) Production() bool {
|
||||
// Not ready for production use just yet.
|
||||
return false
|
||||
}
|
||||
|
||||
// b2Object implements gateway for Minio and BackBlaze B2 compatible object storage servers.
|
||||
type b2Objects struct {
|
||||
minio.GatewayUnsupported
|
||||
mu sync.Mutex
|
||||
creds auth.Credentials
|
||||
b2Client *b2.B2
|
||||
anonClient *http.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Convert B2 errors to minio object layer errors.
|
||||
func b2ToObjectError(err error, params ...string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, ok := err.(*Error)
|
||||
e, ok := err.(*errors.Error)
|
||||
if !ok {
|
||||
// Code should be fixed if this function is called without doing traceError()
|
||||
// Code should be fixed if this function is called without doing errors.Trace()
|
||||
// Else handling different situations in this function makes this function complicated.
|
||||
errorIf(err, "Expected type *Error")
|
||||
minio.ErrorIf(err, "Expected type *Error")
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.e
|
||||
err = e.Cause
|
||||
bucket := ""
|
||||
object := ""
|
||||
uploadID := ""
|
||||
@@ -169,27 +172,33 @@ func b2ToObjectError(err error, params ...string) error {
|
||||
|
||||
switch code {
|
||||
case "duplicate_bucket_name":
|
||||
err = BucketAlreadyOwnedByYou{Bucket: bucket}
|
||||
err = minio.BucketAlreadyOwnedByYou{Bucket: bucket}
|
||||
case "bad_request":
|
||||
if object != "" {
|
||||
err = ObjectNameInvalid{bucket, object}
|
||||
err = minio.ObjectNameInvalid{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
} else if bucket != "" {
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
err = minio.BucketNotFound{Bucket: bucket}
|
||||
}
|
||||
case "bad_bucket_id":
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
err = minio.BucketNotFound{Bucket: bucket}
|
||||
case "file_not_present", "not_found":
|
||||
err = ObjectNotFound{bucket, object}
|
||||
err = minio.ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
case "cannot_delete_non_empty_bucket":
|
||||
err = BucketNotEmpty{bucket, ""}
|
||||
err = minio.BucketNotEmpty{Bucket: bucket}
|
||||
}
|
||||
|
||||
// Special interpretation like this is required for Multipart sessions.
|
||||
if strings.Contains(msg, "No active upload for") && uploadID != "" {
|
||||
err = InvalidUploadID{uploadID}
|
||||
err = minio.InvalidUploadID{UploadID: uploadID}
|
||||
}
|
||||
|
||||
e.e = err
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -201,7 +210,7 @@ func (l *b2Objects) Shutdown() error {
|
||||
}
|
||||
|
||||
// StorageInfo is not relevant to B2 backend.
|
||||
func (l *b2Objects) StorageInfo() (si StorageInfo) {
|
||||
func (l *b2Objects) StorageInfo() (si minio.StorageInfo) {
|
||||
return si
|
||||
}
|
||||
|
||||
@@ -211,11 +220,11 @@ func (l *b2Objects) MakeBucketWithLocation(bucket, location string) error {
|
||||
|
||||
// All buckets are set to private by default.
|
||||
_, err := l.b2Client.CreateBucket(l.ctx, bucket, bucketTypePrivate, nil, nil)
|
||||
return b2ToObjectError(traceError(err), bucket)
|
||||
return b2ToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
func (l *b2Objects) reAuthorizeAccount() error {
|
||||
client, err := b2.AuthorizeAccount(l.ctx, l.creds.AccessKey, l.creds.SecretKey, b2.Transport(newCustomHTTPTransport()))
|
||||
client, err := b2.AuthorizeAccount(l.ctx, l.creds.AccessKey, l.creds.SecretKey, b2.Transport(minio.NewCustomHTTPTransport()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -252,36 +261,36 @@ func (l *b2Objects) listBuckets(err error) ([]*b2.Bucket, error) {
|
||||
func (l *b2Objects) Bucket(bucket string) (*b2.Bucket, error) {
|
||||
bktList, err := l.listBuckets(nil)
|
||||
if err != nil {
|
||||
return nil, b2ToObjectError(traceError(err), bucket)
|
||||
return nil, b2ToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
for _, bkt := range bktList {
|
||||
if bkt.Name == bucket {
|
||||
return bkt, nil
|
||||
}
|
||||
}
|
||||
return nil, traceError(BucketNotFound{Bucket: bucket})
|
||||
return nil, errors.Trace(minio.BucketNotFound{Bucket: bucket})
|
||||
}
|
||||
|
||||
// GetBucketInfo gets bucket metadata..
|
||||
func (l *b2Objects) GetBucketInfo(bucket string) (bi BucketInfo, err error) {
|
||||
func (l *b2Objects) GetBucketInfo(bucket string) (bi minio.BucketInfo, err error) {
|
||||
if _, err = l.Bucket(bucket); err != nil {
|
||||
return bi, err
|
||||
}
|
||||
return BucketInfo{
|
||||
return minio.BucketInfo{
|
||||
Name: bucket,
|
||||
Created: time.Unix(0, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListBuckets lists all B2 buckets
|
||||
func (l *b2Objects) ListBuckets() ([]BucketInfo, error) {
|
||||
func (l *b2Objects) ListBuckets() ([]minio.BucketInfo, error) {
|
||||
bktList, err := l.listBuckets(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var bktInfo []BucketInfo
|
||||
var bktInfo []minio.BucketInfo
|
||||
for _, bkt := range bktList {
|
||||
bktInfo = append(bktInfo, BucketInfo{
|
||||
bktInfo = append(bktInfo, minio.BucketInfo{
|
||||
Name: bkt.Name,
|
||||
Created: time.Unix(0, 0),
|
||||
})
|
||||
@@ -296,19 +305,18 @@ func (l *b2Objects) DeleteBucket(bucket string) error {
|
||||
return err
|
||||
}
|
||||
err = bkt.DeleteBucket(l.ctx)
|
||||
return b2ToObjectError(traceError(err), bucket)
|
||||
return b2ToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
// ListObjects lists all objects in B2 bucket filtered by prefix, returns upto at max 1000 entries at a time.
|
||||
func (l *b2Objects) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi ListObjectsInfo, err error) {
|
||||
func (l *b2Objects) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, err error) {
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
return loi, err
|
||||
}
|
||||
loi = ListObjectsInfo{}
|
||||
files, next, lerr := bkt.ListFileNames(l.ctx, maxKeys, marker, prefix, delimiter)
|
||||
if lerr != nil {
|
||||
return loi, b2ToObjectError(traceError(lerr), bucket)
|
||||
return loi, b2ToObjectError(errors.Trace(lerr), bucket)
|
||||
}
|
||||
loi.IsTruncated = next != ""
|
||||
loi.NextMarker = next
|
||||
@@ -317,12 +325,12 @@ func (l *b2Objects) ListObjects(bucket string, prefix string, marker string, del
|
||||
case "folder":
|
||||
loi.Prefixes = append(loi.Prefixes, file.Name)
|
||||
case "upload":
|
||||
loi.Objects = append(loi.Objects, ObjectInfo{
|
||||
loi.Objects = append(loi.Objects, minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: file.Name,
|
||||
ModTime: file.Timestamp,
|
||||
Size: file.Size,
|
||||
ETag: toS3ETag(file.Info.ID),
|
||||
ETag: minio.ToS3ETag(file.Info.ID),
|
||||
ContentType: file.Info.ContentType,
|
||||
UserDefined: file.Info.Info,
|
||||
})
|
||||
@@ -333,16 +341,15 @@ func (l *b2Objects) ListObjects(bucket string, prefix string, marker string, del
|
||||
|
||||
// ListObjectsV2 lists all objects in B2 bucket filtered by prefix, returns upto max 1000 entries at a time.
|
||||
func (l *b2Objects) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int,
|
||||
fetchOwner bool, startAfter string) (loi ListObjectsV2Info, err error) {
|
||||
fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, err error) {
|
||||
// fetchOwner, startAfter are not supported and unused.
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
return loi, err
|
||||
}
|
||||
loi = ListObjectsV2Info{}
|
||||
files, next, lerr := bkt.ListFileNames(l.ctx, maxKeys, continuationToken, prefix, delimiter)
|
||||
if lerr != nil {
|
||||
return loi, b2ToObjectError(traceError(lerr), bucket)
|
||||
return loi, b2ToObjectError(errors.Trace(lerr), bucket)
|
||||
}
|
||||
loi.IsTruncated = next != ""
|
||||
loi.ContinuationToken = continuationToken
|
||||
@@ -352,12 +359,12 @@ func (l *b2Objects) ListObjectsV2(bucket, prefix, continuationToken, delimiter s
|
||||
case "folder":
|
||||
loi.Prefixes = append(loi.Prefixes, file.Name)
|
||||
case "upload":
|
||||
loi.Objects = append(loi.Objects, ObjectInfo{
|
||||
loi.Objects = append(loi.Objects, minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: file.Name,
|
||||
ModTime: file.Timestamp,
|
||||
Size: file.Size,
|
||||
ETag: toS3ETag(file.Info.ID),
|
||||
ETag: minio.ToS3ETag(file.Info.ID),
|
||||
ContentType: file.Info.ContentType,
|
||||
UserDefined: file.Info.Info,
|
||||
})
|
||||
@@ -379,38 +386,37 @@ func (l *b2Objects) GetObject(bucket string, object string, startOffset int64, l
|
||||
}
|
||||
reader, err := bkt.DownloadFileByName(l.ctx, object, startOffset, length)
|
||||
if err != nil {
|
||||
return b2ToObjectError(traceError(err), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer reader.Close()
|
||||
_, err = io.Copy(writer, reader)
|
||||
return b2ToObjectError(traceError(err), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo
|
||||
func (l *b2Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) {
|
||||
func (l *b2Objects) GetObjectInfo(bucket string, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
return objInfo, err
|
||||
}
|
||||
f, err := bkt.DownloadFileByName(l.ctx, object, 0, 1)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
f.Close()
|
||||
fi, err := bkt.File(f.ID, object).GetFileInfo(l.ctx)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
objInfo = ObjectInfo{
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
ETag: toS3ETag(fi.ID),
|
||||
ETag: minio.ToS3ETag(fi.ID),
|
||||
Size: fi.Size,
|
||||
ModTime: fi.Timestamp,
|
||||
ContentType: fi.ContentType,
|
||||
UserDefined: fi.Info,
|
||||
}
|
||||
return objInfo, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// In B2 - You must always include the X-Bz-Content-Sha1 header with
|
||||
@@ -420,10 +426,8 @@ func (l *b2Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectI
|
||||
// (3) the string do_not_verify.
|
||||
// For more reference - https://www.backblaze.com/b2/docs/uploading.html
|
||||
//
|
||||
const (
|
||||
sha1NoVerify = "do_not_verify"
|
||||
sha1AtEOF = "hex_digits_at_end"
|
||||
)
|
||||
// In our case we are going to use (2) option
|
||||
const sha1AtEOF = "hex_digits_at_end"
|
||||
|
||||
// With the second option mentioned above, you append the 40-character hex sha1
|
||||
// to the end of the request body, immediately after the contents of the file
|
||||
@@ -436,20 +440,20 @@ const (
|
||||
// Additionally this reader also verifies Hash encapsulated inside hash.Reader
|
||||
// at io.EOF if the verification failed we return an error and do not send
|
||||
// the content to server.
|
||||
func newB2Reader(r *h2.Reader, size int64) *B2Reader {
|
||||
return &B2Reader{
|
||||
func newB2Reader(r *h2.Reader, size int64) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
size: size,
|
||||
sha1Hash: sha1.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// B2Reader - is a Reader wraps the hash.Reader which will emit out the sha1
|
||||
// Reader - is a Reader wraps the hash.Reader which will emit out the sha1
|
||||
// hex digits at io.EOF. It also means that your overall content size is
|
||||
// now original size + 40 bytes. Additionally this reader also verifies
|
||||
// Hash encapsulated inside hash.Reader at io.EOF if the verification
|
||||
// failed we return an error and do not send the content to server.
|
||||
type B2Reader struct {
|
||||
type Reader struct {
|
||||
r *h2.Reader
|
||||
size int64
|
||||
sha1Hash hash.Hash
|
||||
@@ -459,8 +463,8 @@ type B2Reader struct {
|
||||
}
|
||||
|
||||
// Size - Returns the total size of Reader.
|
||||
func (nb *B2Reader) Size() int64 { return nb.size + 40 }
|
||||
func (nb *B2Reader) Read(p []byte) (int, error) {
|
||||
func (nb *Reader) Size() int64 { return nb.size + 40 }
|
||||
func (nb *Reader) Read(p []byte) (int, error) {
|
||||
if nb.isEOF {
|
||||
return nb.buf.Read(p)
|
||||
}
|
||||
@@ -479,8 +483,7 @@ func (nb *B2Reader) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
// PutObject uploads the single upload to B2 backend by using *b2_upload_file* API, uploads upto 5GiB.
|
||||
func (l *b2Objects) PutObject(bucket string, object string, data *h2.Reader, metadata map[string]string) (ObjectInfo, error) {
|
||||
var objInfo ObjectInfo
|
||||
func (l *b2Objects) PutObject(bucket string, object string, data *h2.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
return objInfo, err
|
||||
@@ -491,26 +494,26 @@ func (l *b2Objects) PutObject(bucket string, object string, data *h2.Reader, met
|
||||
var u *b2.URL
|
||||
u, err = bkt.GetUploadURL(l.ctx)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
hr := newB2Reader(data, data.Size())
|
||||
var f *b2.File
|
||||
f, err = u.UploadFile(l.ctx, hr, int(hr.Size()), object, contentType, sha1AtEOF, metadata)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
var fi *b2.FileInfo
|
||||
fi, err = f.GetFileInfo(l.ctx)
|
||||
if err != nil {
|
||||
return objInfo, b2ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return ObjectInfo{
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
ETag: toS3ETag(fi.ID),
|
||||
ETag: minio.ToS3ETag(fi.ID),
|
||||
Size: fi.Size,
|
||||
ModTime: fi.Timestamp,
|
||||
ContentType: fi.ContentType,
|
||||
@@ -520,8 +523,8 @@ func (l *b2Objects) PutObject(bucket string, object string, data *h2.Reader, met
|
||||
|
||||
// CopyObject copies a blob from source container to destination container.
|
||||
func (l *b2Objects) CopyObject(srcBucket string, srcObject string, dstBucket string,
|
||||
dstObject string, metadata map[string]string) (objInfo ObjectInfo, err error) {
|
||||
return objInfo, traceError(NotImplemented{})
|
||||
dstObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
return objInfo, errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
|
||||
// DeleteObject deletes a blob in bucket
|
||||
@@ -532,17 +535,17 @@ func (l *b2Objects) DeleteObject(bucket string, object string) error {
|
||||
}
|
||||
reader, err := bkt.DownloadFileByName(l.ctx, object, 0, 1)
|
||||
if err != nil {
|
||||
return b2ToObjectError(traceError(err), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
io.Copy(ioutil.Discard, reader)
|
||||
reader.Close()
|
||||
err = bkt.File(reader.ID, object).DeleteFileVersion(l.ctx)
|
||||
return b2ToObjectError(traceError(err), bucket, object)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// ListMultipartUploads lists all multipart uploads.
|
||||
func (l *b2Objects) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string,
|
||||
delimiter string, maxUploads int) (lmi ListMultipartsInfo, err error) {
|
||||
delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, err error) {
|
||||
// keyMarker, prefix, delimiter are all ignored, Backblaze B2 doesn't support any
|
||||
// of these parameters only equivalent parameter is uploadIDMarker.
|
||||
bkt, err := l.Bucket(bucket)
|
||||
@@ -556,9 +559,9 @@ func (l *b2Objects) ListMultipartUploads(bucket string, prefix string, keyMarker
|
||||
}
|
||||
largeFiles, nextMarker, err := bkt.ListUnfinishedLargeFiles(l.ctx, uploadIDMarker, maxUploads)
|
||||
if err != nil {
|
||||
return lmi, b2ToObjectError(traceError(err), bucket)
|
||||
return lmi, b2ToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
lmi = ListMultipartsInfo{
|
||||
lmi = minio.ListMultipartsInfo{
|
||||
MaxUploads: maxUploads,
|
||||
}
|
||||
if nextMarker != "" {
|
||||
@@ -566,7 +569,7 @@ func (l *b2Objects) ListMultipartUploads(bucket string, prefix string, keyMarker
|
||||
lmi.NextUploadIDMarker = nextMarker
|
||||
}
|
||||
for _, largeFile := range largeFiles {
|
||||
lmi.Uploads = append(lmi.Uploads, MultipartInfo{
|
||||
lmi.Uploads = append(lmi.Uploads, minio.MultipartInfo{
|
||||
Object: largeFile.Name,
|
||||
UploadID: largeFile.ID,
|
||||
Initiated: largeFile.Timestamp,
|
||||
@@ -591,20 +594,14 @@ func (l *b2Objects) NewMultipartUpload(bucket string, object string, metadata ma
|
||||
delete(metadata, "content-type")
|
||||
lf, err := bkt.StartLargeFile(l.ctx, object, contentType, metadata)
|
||||
if err != nil {
|
||||
return uploadID, b2ToObjectError(traceError(err), bucket, object)
|
||||
return uploadID, b2ToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return lf.ID, nil
|
||||
}
|
||||
|
||||
// CopyObjectPart copy part of object to other bucket and object.
|
||||
func (l *b2Objects) CopyObjectPart(srcBucket string, srcObject string, destBucket string, destObject string,
|
||||
uploadID string, partID int, startOffset int64, length int64) (info PartInfo, err error) {
|
||||
return PartInfo{}, traceError(NotImplemented{})
|
||||
}
|
||||
|
||||
// PutObjectPart puts a part of object in bucket, uses B2's LargeFile upload API.
|
||||
func (l *b2Objects) PutObjectPart(bucket string, object string, uploadID string, partID int, data *h2.Reader) (pi PartInfo, err error) {
|
||||
func (l *b2Objects) PutObjectPart(bucket string, object string, uploadID string, partID int, data *h2.Reader) (pi minio.PartInfo, err error) {
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
return pi, err
|
||||
@@ -612,30 +609,30 @@ func (l *b2Objects) PutObjectPart(bucket string, object string, uploadID string,
|
||||
|
||||
fc, err := bkt.File(uploadID, object).CompileParts(0, nil).GetUploadPartURL(l.ctx)
|
||||
if err != nil {
|
||||
return pi, b2ToObjectError(traceError(err), bucket, object, uploadID)
|
||||
return pi, b2ToObjectError(errors.Trace(err), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
hr := newB2Reader(data, data.Size())
|
||||
sha1, err := fc.UploadPart(l.ctx, hr, sha1AtEOF, int(hr.Size()), partID)
|
||||
if err != nil {
|
||||
return pi, b2ToObjectError(traceError(err), bucket, object, uploadID)
|
||||
return pi, b2ToObjectError(errors.Trace(err), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
return PartInfo{
|
||||
return minio.PartInfo{
|
||||
PartNumber: partID,
|
||||
LastModified: UTCNow(),
|
||||
ETag: toS3ETag(sha1),
|
||||
LastModified: minio.UTCNow(),
|
||||
ETag: minio.ToS3ETag(sha1),
|
||||
Size: data.Size(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListObjectParts returns all object parts for specified object in specified bucket, uses B2's LargeFile upload API.
|
||||
func (l *b2Objects) ListObjectParts(bucket string, object string, uploadID string, partNumberMarker int, maxParts int) (lpi ListPartsInfo, err error) {
|
||||
func (l *b2Objects) ListObjectParts(bucket string, object string, uploadID string, partNumberMarker int, maxParts int) (lpi minio.ListPartsInfo, err error) {
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
return lpi, err
|
||||
}
|
||||
lpi = ListPartsInfo{
|
||||
lpi = minio.ListPartsInfo{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
UploadID: uploadID,
|
||||
@@ -646,16 +643,16 @@ func (l *b2Objects) ListObjectParts(bucket string, object string, uploadID strin
|
||||
partNumberMarker++
|
||||
partsList, next, err := bkt.File(uploadID, object).ListParts(l.ctx, partNumberMarker, maxParts)
|
||||
if err != nil {
|
||||
return lpi, b2ToObjectError(traceError(err), bucket, object, uploadID)
|
||||
return lpi, b2ToObjectError(errors.Trace(err), bucket, object, uploadID)
|
||||
}
|
||||
if next != 0 {
|
||||
lpi.IsTruncated = true
|
||||
lpi.NextPartNumberMarker = next
|
||||
}
|
||||
for _, part := range partsList {
|
||||
lpi.Parts = append(lpi.Parts, PartInfo{
|
||||
lpi.Parts = append(lpi.Parts, minio.PartInfo{
|
||||
PartNumber: part.Number,
|
||||
ETag: toS3ETag(part.SHA1),
|
||||
ETag: minio.ToS3ETag(part.SHA1),
|
||||
Size: part.Size,
|
||||
})
|
||||
}
|
||||
@@ -669,11 +666,11 @@ func (l *b2Objects) AbortMultipartUpload(bucket string, object string, uploadID
|
||||
return err
|
||||
}
|
||||
err = bkt.File(uploadID, object).CompileParts(0, nil).CancelLargeFile(l.ctx)
|
||||
return b2ToObjectError(traceError(err), bucket, object, uploadID)
|
||||
return b2ToObjectError(errors.Trace(err), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload completes ongoing multipart upload and finalizes object, uses B2's LargeFile upload API.
|
||||
func (l *b2Objects) CompleteMultipartUpload(bucket string, object string, uploadID string, uploadedParts []CompletePart) (oi ObjectInfo, err error) {
|
||||
func (l *b2Objects) CompleteMultipartUpload(bucket string, object string, uploadID string, uploadedParts []minio.CompletePart) (oi minio.ObjectInfo, err error) {
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
return oi, err
|
||||
@@ -683,7 +680,7 @@ func (l *b2Objects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
// B2 requires contigous part numbers starting with 1, they do not support
|
||||
// hand picking part numbers, we return an S3 compatible error instead.
|
||||
if i+1 != uploadedPart.PartNumber {
|
||||
return oi, b2ToObjectError(traceError(InvalidPart{}), bucket, object, uploadID)
|
||||
return oi, b2ToObjectError(errors.Trace(minio.InvalidPart{}), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
// Trim "-1" suffix in ETag as PutObjectPart() treats B2 returned SHA1 as ETag.
|
||||
@@ -691,7 +688,7 @@ func (l *b2Objects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
}
|
||||
|
||||
if _, err = bkt.File(uploadID, object).CompileParts(0, hashes).FinishLargeFile(l.ctx); err != nil {
|
||||
return oi, b2ToObjectError(traceError(err), bucket, object, uploadID)
|
||||
return oi, b2ToObjectError(errors.Trace(err), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
return l.GetObjectInfo(bucket, object)
|
||||
@@ -702,23 +699,23 @@ func (l *b2Objects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
// bucketType.AllPrivate - bucketTypePrivate means that you need an authorization token to download them.
|
||||
// Default is AllPrivate for all buckets.
|
||||
func (l *b2Objects) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error {
|
||||
var policies []BucketAccessPolicy
|
||||
var policies []minio.BucketAccessPolicy
|
||||
|
||||
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket) {
|
||||
policies = append(policies, BucketAccessPolicy{
|
||||
policies = append(policies, minio.BucketAccessPolicy{
|
||||
Prefix: prefix,
|
||||
Policy: policy,
|
||||
})
|
||||
}
|
||||
prefix := bucket + "/*" // For all objects inside the bucket.
|
||||
if len(policies) != 1 {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
if policies[0].Prefix != prefix {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
if policies[0].Policy != policy.BucketPolicyReadOnly {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
bkt, err := l.Bucket(bucket)
|
||||
if err != nil {
|
||||
@@ -726,7 +723,7 @@ func (l *b2Objects) SetBucketPolicies(bucket string, policyInfo policy.BucketAcc
|
||||
}
|
||||
bkt.Type = bucketTypeReadOnly
|
||||
_, err = bkt.Update(l.ctx)
|
||||
return b2ToObjectError(traceError(err))
|
||||
return b2ToObjectError(errors.Trace(err))
|
||||
}
|
||||
|
||||
// GetBucketPolicies, returns the current bucketType from B2 backend and convert
|
||||
@@ -744,7 +741,7 @@ func (l *b2Objects) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy,
|
||||
// bkt.Type can also be snapshot, but it is only allowed through B2 browser console,
|
||||
// just return back as policy not found for all cases.
|
||||
// CreateBucket always sets the value to allPrivate by default.
|
||||
return policy.BucketAccessPolicy{}, traceError(PolicyNotFound{Bucket: bucket})
|
||||
return policy.BucketAccessPolicy{}, errors.Trace(minio.PolicyNotFound{Bucket: bucket})
|
||||
}
|
||||
|
||||
// DeleteBucketPolicies - resets the bucketType of bucket on B2 to 'allPrivate'.
|
||||
@@ -755,5 +752,5 @@ func (l *b2Objects) DeleteBucketPolicies(bucket string) error {
|
||||
}
|
||||
bkt.Type = bucketTypePrivate
|
||||
_, err = bkt.Update(l.ctx)
|
||||
return b2ToObjectError(traceError(err))
|
||||
return b2ToObjectError(errors.Trace(err))
|
||||
}
|
||||
@@ -14,11 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package b2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
b2 "github.com/minio/blazer/base"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
// Tests headerToObjectInfo
|
||||
@@ -26,7 +32,7 @@ func TestHeaderToObjectInfo(t *testing.T) {
|
||||
testCases := []struct {
|
||||
bucket, object string
|
||||
header http.Header
|
||||
objInfo ObjectInfo
|
||||
objInfo minio.ObjectInfo
|
||||
}{
|
||||
{
|
||||
bucket: "bucket",
|
||||
@@ -38,7 +44,7 @@ func TestHeaderToObjectInfo(t *testing.T) {
|
||||
"X-Bz-Info-X-Amz-Meta-1": []string{"test1"},
|
||||
"X-Bz-File-Id": []string{"xxxxx"},
|
||||
},
|
||||
objInfo: ObjectInfo{
|
||||
objInfo: minio.ObjectInfo{
|
||||
Bucket: "bucket",
|
||||
Name: "object",
|
||||
ContentType: "application/javascript",
|
||||
@@ -102,3 +108,96 @@ func TestMkRange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test b2 object error.
|
||||
func TestB2ObjectError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
params []string
|
||||
b2Err error
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
[]string{}, nil, nil,
|
||||
},
|
||||
{
|
||||
[]string{}, fmt.Errorf("Not *Error"), fmt.Errorf("Not *Error"),
|
||||
},
|
||||
{
|
||||
[]string{}, errors.Trace(fmt.Errorf("Non B2 Error")), fmt.Errorf("Non B2 Error"),
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "duplicate_bucket_name",
|
||||
}), minio.BucketAlreadyOwnedByYou{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "bad_request",
|
||||
}), minio.BucketNotFound{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "bad_request",
|
||||
}), minio.ObjectNameInvalid{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "bad_bucket_id",
|
||||
}), minio.BucketNotFound{Bucket: "bucket"},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "file_not_present",
|
||||
}), minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "not_found",
|
||||
}), minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Code: "cannot_delete_non_empty_bucket",
|
||||
}), minio.BucketNotEmpty{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object", "uploadID"}, errors.Trace(b2.Error{
|
||||
StatusCode: 1,
|
||||
Message: "No active upload for",
|
||||
}), minio.InvalidUploadID{
|
||||
UploadID: "uploadID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
actualErr := b2ToObjectError(testCase.b2Err, testCase.params...)
|
||||
if actualErr != nil {
|
||||
if actualErr.Error() != testCase.expectedErr.Error() {
|
||||
t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package gateway
|
||||
|
||||
import (
|
||||
// Import all gateways.
|
||||
_ "github.com/minio/minio/cmd/gateway/azure"
|
||||
_ "github.com/minio/minio/cmd/gateway/b2"
|
||||
_ "github.com/minio/minio/cmd/gateway/gcs"
|
||||
_ "github.com/minio/minio/cmd/gateway/manta"
|
||||
_ "github.com/minio/minio/cmd/gateway/oss"
|
||||
_ "github.com/minio/minio/cmd/gateway/s3"
|
||||
_ "github.com/minio/minio/cmd/gateway/sia"
|
||||
// Add your gateway here.
|
||||
)
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package gcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -22,6 +22,10 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
func toGCSPublicURL(bucket, object string) string {
|
||||
@@ -32,7 +36,7 @@ func toGCSPublicURL(bucket, object string) string {
|
||||
func (l *gcsGateway) AnonGetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer) error {
|
||||
req, err := http.NewRequest("GET", toGCSPublicURL(bucket, object), nil)
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket, object)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
if length > 0 && startOffset > 0 {
|
||||
@@ -43,28 +47,28 @@ func (l *gcsGateway) AnonGetObject(bucket string, object string, startOffset int
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket, object)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent && resp.StatusCode != http.StatusOK {
|
||||
return gcsToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
return gcsToObjectError(errors.Trace(minio.AnonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
}
|
||||
|
||||
_, err = io.Copy(writer, resp.Body)
|
||||
return gcsToObjectError(traceError(err), bucket, object)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// AnonGetObjectInfo - Get object info anonymously
|
||||
func (l *gcsGateway) AnonGetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) {
|
||||
func (l *gcsGateway) AnonGetObjectInfo(bucket string, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
resp, err := http.Head(toGCSPublicURL(bucket, object))
|
||||
if err != nil {
|
||||
return objInfo, gcsToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, gcsToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return objInfo, gcsToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
return objInfo, gcsToObjectError(errors.Trace(minio.AnonErrToObjectErr(resp.StatusCode, bucket, object)), bucket, object)
|
||||
}
|
||||
|
||||
var contentLength int64
|
||||
@@ -72,13 +76,13 @@ func (l *gcsGateway) AnonGetObjectInfo(bucket string, object string) (objInfo Ob
|
||||
if contentLengthStr != "" {
|
||||
contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64)
|
||||
if err != nil {
|
||||
return objInfo, gcsToObjectError(traceError(errUnexpected), bucket, object)
|
||||
return objInfo, gcsToObjectError(errors.Trace(fmt.Errorf("Unexpected error")), bucket, object)
|
||||
}
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return objInfo, traceError(err)
|
||||
return objInfo, errors.Trace(err)
|
||||
}
|
||||
|
||||
objInfo.ModTime = t
|
||||
@@ -96,46 +100,46 @@ func (l *gcsGateway) AnonGetObjectInfo(bucket string, object string) (objInfo Ob
|
||||
}
|
||||
|
||||
// AnonListObjects - List objects anonymously
|
||||
func (l *gcsGateway) AnonListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||
func (l *gcsGateway) AnonListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (minio.ListObjectsInfo, error) {
|
||||
result, err := l.anonClient.ListObjects(bucket, prefix, marker, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return ListObjectsInfo{}, s3ToObjectError(traceError(err), bucket)
|
||||
return minio.ListObjectsInfo{}, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return fromMinioClientListBucketResult(bucket, result), nil
|
||||
return minio.FromMinioClientListBucketResult(bucket, result), nil
|
||||
}
|
||||
|
||||
// AnonListObjectsV2 - List objects in V2 mode, anonymously
|
||||
func (l *gcsGateway) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (ListObjectsV2Info, error) {
|
||||
func (l *gcsGateway) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (minio.ListObjectsV2Info, error) {
|
||||
// Request V1 List Object to the backend
|
||||
result, err := l.anonClient.ListObjects(bucket, prefix, continuationToken, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return ListObjectsV2Info{}, s3ToObjectError(traceError(err), bucket)
|
||||
return minio.ListObjectsV2Info{}, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
// translate V1 Result to V2Info
|
||||
return fromMinioClientListBucketResultToV2Info(bucket, result), nil
|
||||
return minio.FromMinioClientListBucketResultToV2Info(bucket, result), nil
|
||||
}
|
||||
|
||||
// AnonGetBucketInfo - Get bucket metadata anonymously.
|
||||
func (l *gcsGateway) AnonGetBucketInfo(bucket string) (bucketInfo BucketInfo, err error) {
|
||||
func (l *gcsGateway) AnonGetBucketInfo(bucket string) (bucketInfo minio.BucketInfo, err error) {
|
||||
resp, err := http.Head(toGCSPublicURL(bucket, ""))
|
||||
if err != nil {
|
||||
return bucketInfo, gcsToObjectError(traceError(err))
|
||||
return bucketInfo, gcsToObjectError(errors.Trace(err))
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return bucketInfo, gcsToObjectError(traceError(anonErrToObjectErr(resp.StatusCode, bucket)), bucket)
|
||||
return bucketInfo, gcsToObjectError(errors.Trace(minio.AnonErrToObjectErr(resp.StatusCode, bucket)), bucket)
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return bucketInfo, traceError(err)
|
||||
return bucketInfo, errors.Trace(err)
|
||||
}
|
||||
|
||||
// Last-Modified date being returned by GCS
|
||||
return BucketInfo{
|
||||
return minio.BucketInfo{
|
||||
Name: bucket,
|
||||
Created: t,
|
||||
}, nil
|
||||
@@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package gcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"math"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -31,28 +31,37 @@ import (
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/cli"
|
||||
minio "github.com/minio/minio-go"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
|
||||
miniogo "github.com/minio/minio-go"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
var (
|
||||
// Project ID format is not valid.
|
||||
errGCSInvalidProjectID = errors.New("GCS project id is either empty or invalid")
|
||||
errGCSInvalidProjectID = fmt.Errorf("GCS project id is either empty or invalid")
|
||||
|
||||
// Project ID not found
|
||||
errGCSProjectIDNotFound = errors.New("unknown project id")
|
||||
errGCSProjectIDNotFound = fmt.Errorf("Unknown project id")
|
||||
|
||||
// Invalid format.
|
||||
errGCSFormat = fmt.Errorf("Unknown format")
|
||||
)
|
||||
|
||||
const (
|
||||
// Path where multipart objects are saved.
|
||||
// If we change the backend format we will use a different url path like /multipart/v2
|
||||
// but we will not migrate old data.
|
||||
gcsMinioMultipartPathV1 = globalMinioSysTmp + "multipart/v1"
|
||||
gcsMinioMultipartPathV1 = minio.GatewayMinioSysTmp + "multipart/v1"
|
||||
|
||||
// Multipart meta file.
|
||||
gcsMinioMultipartMeta = "gcs.json"
|
||||
@@ -101,6 +110,9 @@ ENVIRONMENT VARIABLES:
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
UPDATE:
|
||||
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||
|
||||
GCS credentials file:
|
||||
GOOGLE_APPLICATION_CREDENTIALS: Path to credentials.json
|
||||
|
||||
@@ -114,12 +126,11 @@ EXAMPLES:
|
||||
|
||||
`
|
||||
|
||||
MustRegisterGatewayCommand(cli.Command{
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: gcsBackend,
|
||||
Usage: "Google Cloud Storage.",
|
||||
Action: gcsGatewayMain,
|
||||
CustomHelpTemplate: gcsGatewayTemplate,
|
||||
Flags: append(serverFlags, globalFlags...),
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
@@ -128,31 +139,71 @@ EXAMPLES:
|
||||
func gcsGatewayMain(ctx *cli.Context) {
|
||||
projectID := ctx.Args().First()
|
||||
if projectID == "" && os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
|
||||
errorIf(errGCSProjectIDNotFound, "project-id should be provided as argument or GOOGLE_APPLICATION_CREDENTIALS should be set with path to credentials.json")
|
||||
minio.ErrorIf(errGCSProjectIDNotFound, "project-id should be provided as argument or GOOGLE_APPLICATION_CREDENTIALS should be set with path to credentials.json")
|
||||
cli.ShowCommandHelpAndExit(ctx, "gcs", 1)
|
||||
}
|
||||
if projectID != "" && !isValidGCSProjectIDFormat(projectID) {
|
||||
errorIf(errGCSInvalidProjectID, "Unable to start GCS gateway with %s", ctx.Args().First())
|
||||
minio.ErrorIf(errGCSInvalidProjectID, "Unable to start GCS gateway with %s", ctx.Args().First())
|
||||
cli.ShowCommandHelpAndExit(ctx, "gcs", 1)
|
||||
}
|
||||
|
||||
startGateway(ctx, &GCSGateway{projectID})
|
||||
minio.StartGateway(ctx, &GCS{projectID})
|
||||
}
|
||||
|
||||
// GCSGateway implements Gateway.
|
||||
type GCSGateway struct {
|
||||
// GCS implements Azure.
|
||||
type GCS struct {
|
||||
projectID string
|
||||
}
|
||||
|
||||
// Name returns the name of gcs gatewaylayer.
|
||||
func (g *GCSGateway) Name() string {
|
||||
func (g *GCS) Name() string {
|
||||
return gcsBackend
|
||||
}
|
||||
|
||||
// NewGatewayLayer returns gcs gatewaylayer.
|
||||
func (g *GCSGateway) NewGatewayLayer() (GatewayLayer, error) {
|
||||
log.Println(colorYellow("\n *** Warning: Not Ready for Production ***"))
|
||||
return newGCSGatewayLayer(g.projectID)
|
||||
func (g *GCS) NewGatewayLayer(creds auth.Credentials) (minio.GatewayLayer, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
var err error
|
||||
if g.projectID == "" {
|
||||
// If project ID is not provided on command line, we figure it out
|
||||
// from the credentials.json file.
|
||||
g.projectID, err = gcsParseProjectID(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize a GCS client.
|
||||
// Send user-agent in this format for Google to obtain usage insights while participating in the
|
||||
// Google Cloud Technology Partners (https://cloud.google.com/partners/)
|
||||
client, err := storage.NewClient(ctx, option.WithUserAgent(fmt.Sprintf("Minio/%s (GPN:Minio;)", minio.Version)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize a anonymous client with minio core APIs.
|
||||
anonClient, err := miniogo.NewCore(googleStorageEndpoint, "", "", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
anonClient.SetCustomTransport(minio.NewCustomHTTPTransport())
|
||||
|
||||
gcs := &gcsGateway{
|
||||
client: client,
|
||||
projectID: g.projectID,
|
||||
ctx: ctx,
|
||||
anonClient: anonClient,
|
||||
}
|
||||
|
||||
// Start background process to cleanup old files in minio.sys.tmp
|
||||
go gcs.CleanupGCSMinioSysTmp()
|
||||
return gcs, nil
|
||||
}
|
||||
|
||||
// Production - FIXME: GCS is not production ready yet.
|
||||
func (g *GCS) Production() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Stored in gcs.json - Contents of this file is not used anywhere. It can be
|
||||
@@ -179,15 +230,15 @@ func gcsToObjectError(err error, params ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, ok := err.(*Error)
|
||||
e, ok := err.(*errors.Error)
|
||||
if !ok {
|
||||
// Code should be fixed if this function is called without doing traceError()
|
||||
// Code should be fixed if this function is called without doing errors.Trace()
|
||||
// Else handling different situations in this function makes this function complicated.
|
||||
errorIf(err, "Expected type *Error")
|
||||
minio.ErrorIf(err, "Expected type *Error")
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.e
|
||||
err = e.Cause
|
||||
|
||||
bucket := ""
|
||||
object := ""
|
||||
@@ -205,23 +256,23 @@ func gcsToObjectError(err error, params ...string) error {
|
||||
// in some cases just a plain error is being returned
|
||||
switch err.Error() {
|
||||
case "storage: bucket doesn't exist":
|
||||
err = BucketNotFound{
|
||||
err = minio.BucketNotFound{
|
||||
Bucket: bucket,
|
||||
}
|
||||
e.e = err
|
||||
e.Cause = err
|
||||
return e
|
||||
case "storage: object doesn't exist":
|
||||
if uploadID != "" {
|
||||
err = InvalidUploadID{
|
||||
err = minio.InvalidUploadID{
|
||||
UploadID: uploadID,
|
||||
}
|
||||
} else {
|
||||
err = ObjectNotFound{
|
||||
err = minio.ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
}
|
||||
e.e = err
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -229,12 +280,12 @@ func gcsToObjectError(err error, params ...string) error {
|
||||
if !ok {
|
||||
// We don't interpret non Minio errors. As minio errors will
|
||||
// have StatusCode to help to convert to object errors.
|
||||
e.e = err
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
if len(googleAPIErr.Errors) == 0 {
|
||||
e.e = err
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -248,38 +299,38 @@ func gcsToObjectError(err error, params ...string) error {
|
||||
case "keyInvalid":
|
||||
fallthrough
|
||||
case "forbidden":
|
||||
err = PrefixAccessDenied{
|
||||
err = minio.PrefixAccessDenied{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
case "invalid":
|
||||
err = BucketNameInvalid{
|
||||
err = minio.BucketNameInvalid{
|
||||
Bucket: bucket,
|
||||
}
|
||||
case "notFound":
|
||||
if object != "" {
|
||||
err = ObjectNotFound{
|
||||
err = minio.ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
break
|
||||
}
|
||||
err = BucketNotFound{Bucket: bucket}
|
||||
err = minio.BucketNotFound{Bucket: bucket}
|
||||
case "conflict":
|
||||
if message == "You already own this bucket. Please select another name." {
|
||||
err = BucketAlreadyOwnedByYou{Bucket: bucket}
|
||||
err = minio.BucketAlreadyOwnedByYou{Bucket: bucket}
|
||||
break
|
||||
}
|
||||
if message == "Sorry, that name is not available. Please try a different one." {
|
||||
err = BucketAlreadyExists{Bucket: bucket}
|
||||
err = minio.BucketAlreadyExists{Bucket: bucket}
|
||||
break
|
||||
}
|
||||
err = BucketNotEmpty{Bucket: bucket}
|
||||
err = minio.BucketNotEmpty{Bucket: bucket}
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported error reason: %s", reason)
|
||||
}
|
||||
|
||||
e.e = err
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -297,9 +348,9 @@ func isValidGCSProjectIDFormat(projectID string) bool {
|
||||
|
||||
// gcsGateway - Implements gateway for Minio and GCS compatible object storage servers.
|
||||
type gcsGateway struct {
|
||||
gatewayUnsupported
|
||||
minio.GatewayUnsupported
|
||||
client *storage.Client
|
||||
anonClient *minio.Core
|
||||
anonClient *miniogo.Core
|
||||
projectID string
|
||||
ctx context.Context
|
||||
}
|
||||
@@ -319,54 +370,14 @@ func gcsParseProjectID(credsFile string) (projectID string, err error) {
|
||||
return googleCreds[gcsProjectIDKey], err
|
||||
}
|
||||
|
||||
// newGCSGatewayLayer returns gcs gatewaylayer
|
||||
func newGCSGatewayLayer(projectID string) (GatewayLayer, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
var err error
|
||||
if projectID == "" {
|
||||
// If project ID is not provided on command line, we figure it out
|
||||
// from the credentials.json file.
|
||||
projectID, err = gcsParseProjectID(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize a GCS client.
|
||||
// Send user-agent in this format for Google to obtain usage insights while participating in the
|
||||
// Google Cloud Technology Partners (https://cloud.google.com/partners/)
|
||||
client, err := storage.NewClient(ctx, option.WithUserAgent(fmt.Sprintf("Minio/%s (GPN:Minio;)", Version)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize a anonymous client with minio core APIs.
|
||||
anonClient, err := minio.NewCore(googleStorageEndpoint, "", "", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
anonClient.SetCustomTransport(newCustomHTTPTransport())
|
||||
|
||||
gateway := &gcsGateway{
|
||||
client: client,
|
||||
projectID: projectID,
|
||||
ctx: ctx,
|
||||
anonClient: anonClient,
|
||||
}
|
||||
// Start background process to cleanup old files in minio.sys.tmp
|
||||
go gateway.CleanupGCSMinioSysTmp()
|
||||
return gateway, nil
|
||||
}
|
||||
|
||||
// Cleanup old files in minio.sys.tmp of the given bucket.
|
||||
func (l *gcsGateway) CleanupGCSMinioSysTmpBucket(bucket string) {
|
||||
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{Prefix: globalMinioSysTmp, Versions: false})
|
||||
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{Prefix: minio.GatewayMinioSysTmp, Versions: false})
|
||||
for {
|
||||
attrs, err := it.Next()
|
||||
if err != nil {
|
||||
if err != iterator.Done {
|
||||
errorIf(err, "Object listing error on bucket %s during purging of old files in minio.sys.tmp", bucket)
|
||||
minio.ErrorIf(err, "Object listing error on bucket %s during purging of old files in minio.sys.tmp", bucket)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -374,7 +385,7 @@ func (l *gcsGateway) CleanupGCSMinioSysTmpBucket(bucket string) {
|
||||
// Delete files older than 2 weeks.
|
||||
err := l.client.Bucket(bucket).Object(attrs.Name).Delete(l.ctx)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to delete %s/%s during purging of old files in minio.sys.tmp", bucket, attrs.Name)
|
||||
minio.ErrorIf(err, "Unable to delete %s/%s during purging of old files in minio.sys.tmp", bucket, attrs.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -389,7 +400,7 @@ func (l *gcsGateway) CleanupGCSMinioSysTmp() {
|
||||
attrs, err := it.Next()
|
||||
if err != nil {
|
||||
if err != iterator.Done {
|
||||
errorIf(err, "Bucket listing error during purging of old files in minio.sys.tmp")
|
||||
minio.ErrorIf(err, "Bucket listing error during purging of old files in minio.sys.tmp")
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -407,8 +418,8 @@ func (l *gcsGateway) Shutdown() error {
|
||||
}
|
||||
|
||||
// StorageInfo - Not relevant to GCS backend.
|
||||
func (l *gcsGateway) StorageInfo() StorageInfo {
|
||||
return StorageInfo{}
|
||||
func (l *gcsGateway) StorageInfo() minio.StorageInfo {
|
||||
return minio.StorageInfo{}
|
||||
}
|
||||
|
||||
// MakeBucketWithLocation - Create a new container on GCS backend.
|
||||
@@ -424,24 +435,24 @@ func (l *gcsGateway) MakeBucketWithLocation(bucket, location string) error {
|
||||
Location: location,
|
||||
})
|
||||
|
||||
return gcsToObjectError(traceError(err), bucket)
|
||||
return gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
// GetBucketInfo - Get bucket metadata..
|
||||
func (l *gcsGateway) GetBucketInfo(bucket string) (BucketInfo, error) {
|
||||
func (l *gcsGateway) GetBucketInfo(bucket string) (minio.BucketInfo, error) {
|
||||
attrs, err := l.client.Bucket(bucket).Attrs(l.ctx)
|
||||
if err != nil {
|
||||
return BucketInfo{}, gcsToObjectError(traceError(err), bucket)
|
||||
return minio.BucketInfo{}, gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return BucketInfo{
|
||||
return minio.BucketInfo{
|
||||
Name: attrs.Name,
|
||||
Created: attrs.Created,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListBuckets lists all buckets under your project-id on GCS.
|
||||
func (l *gcsGateway) ListBuckets() (buckets []BucketInfo, err error) {
|
||||
func (l *gcsGateway) ListBuckets() (buckets []minio.BucketInfo, err error) {
|
||||
it := l.client.Buckets(l.ctx, l.projectID)
|
||||
|
||||
// Iterate and capture all the buckets.
|
||||
@@ -452,10 +463,10 @@ func (l *gcsGateway) ListBuckets() (buckets []BucketInfo, err error) {
|
||||
}
|
||||
|
||||
if ierr != nil {
|
||||
return buckets, gcsToObjectError(traceError(ierr))
|
||||
return buckets, gcsToObjectError(errors.Trace(ierr))
|
||||
}
|
||||
|
||||
buckets = append(buckets, BucketInfo{
|
||||
buckets = append(buckets, minio.BucketInfo{
|
||||
Name: attrs.Name,
|
||||
Created: attrs.Created,
|
||||
})
|
||||
@@ -466,7 +477,10 @@ func (l *gcsGateway) ListBuckets() (buckets []BucketInfo, err error) {
|
||||
|
||||
// DeleteBucket delete a bucket on GCS.
|
||||
func (l *gcsGateway) DeleteBucket(bucket string) error {
|
||||
itObject := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{Delimiter: slashSeparator, Versions: false})
|
||||
itObject := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
|
||||
Delimiter: "/",
|
||||
Versions: false,
|
||||
})
|
||||
// We list the bucket and if we find any objects we return BucketNotEmpty error. If we
|
||||
// find only "minio.sys.tmp/" then we remove it before deleting the bucket.
|
||||
gcsMinioPathFound := false
|
||||
@@ -477,9 +491,9 @@ func (l *gcsGateway) DeleteBucket(bucket string) error {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err))
|
||||
return gcsToObjectError(errors.Trace(err))
|
||||
}
|
||||
if objAttrs.Prefix == globalMinioSysTmp {
|
||||
if objAttrs.Prefix == minio.GatewayMinioSysTmp {
|
||||
gcsMinioPathFound = true
|
||||
continue
|
||||
}
|
||||
@@ -487,27 +501,27 @@ func (l *gcsGateway) DeleteBucket(bucket string) error {
|
||||
break
|
||||
}
|
||||
if nonGCSMinioPathFound {
|
||||
return gcsToObjectError(traceError(BucketNotEmpty{}))
|
||||
return gcsToObjectError(errors.Trace(minio.BucketNotEmpty{}))
|
||||
}
|
||||
if gcsMinioPathFound {
|
||||
// Remove minio.sys.tmp before deleting the bucket.
|
||||
itObject = l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{Versions: false, Prefix: globalMinioSysTmp})
|
||||
itObject = l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{Versions: false, Prefix: minio.GatewayMinioSysTmp})
|
||||
for {
|
||||
objAttrs, err := itObject.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err))
|
||||
return gcsToObjectError(errors.Trace(err))
|
||||
}
|
||||
err = l.client.Bucket(bucket).Object(objAttrs.Name).Delete(l.ctx)
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err))
|
||||
return gcsToObjectError(errors.Trace(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
err := l.client.Bucket(bucket).Delete(l.ctx)
|
||||
return gcsToObjectError(traceError(err), bucket)
|
||||
return gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
func toGCSPageToken(name string) string {
|
||||
@@ -529,13 +543,13 @@ func toGCSPageToken(name string) string {
|
||||
}
|
||||
|
||||
// Returns true if marker was returned by GCS, i.e prefixed with
|
||||
// ##minio by minio gcs gateway.
|
||||
// ##minio by minio gcs minio.
|
||||
func isGCSMarker(marker string) bool {
|
||||
return strings.HasPrefix(marker, gcsTokenPrefix)
|
||||
}
|
||||
|
||||
// ListObjects - lists all blobs in GCS bucket filtered by prefix
|
||||
func (l *gcsGateway) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||
func (l *gcsGateway) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (minio.ListObjectsInfo, error) {
|
||||
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
|
||||
Delimiter: delimiter,
|
||||
Prefix: prefix,
|
||||
@@ -568,7 +582,7 @@ func (l *gcsGateway) ListObjects(bucket string, prefix string, marker string, de
|
||||
|
||||
it.PageInfo().MaxSize = maxKeys
|
||||
|
||||
objects := []ObjectInfo{}
|
||||
objects := []minio.ObjectInfo{}
|
||||
for {
|
||||
if len(objects) >= maxKeys {
|
||||
// check if there is one next object and
|
||||
@@ -576,7 +590,7 @@ func (l *gcsGateway) ListObjects(bucket string, prefix string, marker string, de
|
||||
// metadata folder, then just break
|
||||
// otherwise we've truncated the output
|
||||
attrs, _ := it.Next()
|
||||
if attrs != nil && attrs.Prefix == globalMinioSysTmp {
|
||||
if attrs != nil && attrs.Prefix == minio.GatewayMinioSysTmp {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -589,21 +603,21 @@ func (l *gcsGateway) ListObjects(bucket string, prefix string, marker string, de
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return ListObjectsInfo{}, gcsToObjectError(traceError(err), bucket, prefix)
|
||||
return minio.ListObjectsInfo{}, gcsToObjectError(errors.Trace(err), bucket, prefix)
|
||||
}
|
||||
|
||||
nextMarker = toGCSPageToken(attrs.Name)
|
||||
|
||||
if attrs.Prefix == globalMinioSysTmp {
|
||||
if attrs.Prefix == minio.GatewayMinioSysTmp {
|
||||
// We don't return our metadata prefix.
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(prefix, globalMinioSysTmp) {
|
||||
if !strings.HasPrefix(prefix, minio.GatewayMinioSysTmp) {
|
||||
// If client lists outside gcsMinioPath then we filter out gcsMinioPath/* entries.
|
||||
// But if the client lists inside gcsMinioPath then we return the entries in gcsMinioPath/
|
||||
// which will be helpful to observe the "directory structure" for debugging purposes.
|
||||
if strings.HasPrefix(attrs.Prefix, globalMinioSysTmp) ||
|
||||
strings.HasPrefix(attrs.Name, globalMinioSysTmp) {
|
||||
if strings.HasPrefix(attrs.Prefix, minio.GatewayMinioSysTmp) ||
|
||||
strings.HasPrefix(attrs.Name, minio.GatewayMinioSysTmp) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -617,19 +631,19 @@ func (l *gcsGateway) ListObjects(bucket string, prefix string, marker string, de
|
||||
continue
|
||||
}
|
||||
|
||||
objects = append(objects, ObjectInfo{
|
||||
objects = append(objects, minio.ObjectInfo{
|
||||
Name: attrs.Name,
|
||||
Bucket: attrs.Bucket,
|
||||
ModTime: attrs.Updated,
|
||||
Size: attrs.Size,
|
||||
ETag: toS3ETag(fmt.Sprintf("%d", attrs.CRC32C)),
|
||||
ETag: minio.ToS3ETag(fmt.Sprintf("%d", attrs.CRC32C)),
|
||||
UserDefined: attrs.Metadata,
|
||||
ContentType: attrs.ContentType,
|
||||
ContentEncoding: attrs.ContentEncoding,
|
||||
})
|
||||
}
|
||||
|
||||
return ListObjectsInfo{
|
||||
return minio.ListObjectsInfo{
|
||||
IsTruncated: isTruncated,
|
||||
NextMarker: gcsTokenPrefix + nextMarker,
|
||||
Prefixes: prefixes,
|
||||
@@ -638,8 +652,7 @@ func (l *gcsGateway) ListObjects(bucket string, prefix string, marker string, de
|
||||
}
|
||||
|
||||
// ListObjectsV2 - lists all blobs in GCS bucket filtered by prefix
|
||||
func (l *gcsGateway) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (ListObjectsV2Info, error) {
|
||||
|
||||
func (l *gcsGateway) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (minio.ListObjectsV2Info, error) {
|
||||
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
|
||||
Delimiter: delimiter,
|
||||
Prefix: prefix,
|
||||
@@ -662,8 +675,8 @@ func (l *gcsGateway) ListObjectsV2(bucket, prefix, continuationToken, delimiter
|
||||
}
|
||||
}
|
||||
|
||||
prefixes := []string{}
|
||||
objects := []ObjectInfo{}
|
||||
var prefixes []string
|
||||
var objects []minio.ObjectInfo
|
||||
|
||||
for {
|
||||
attrs, err := it.Next()
|
||||
@@ -672,19 +685,19 @@ func (l *gcsGateway) ListObjectsV2(bucket, prefix, continuationToken, delimiter
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return ListObjectsV2Info{}, gcsToObjectError(traceError(err), bucket, prefix)
|
||||
return minio.ListObjectsV2Info{}, gcsToObjectError(errors.Trace(err), bucket, prefix)
|
||||
}
|
||||
|
||||
if attrs.Prefix == globalMinioSysTmp {
|
||||
if attrs.Prefix == minio.GatewayMinioSysTmp {
|
||||
// We don't return our metadata prefix.
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(prefix, globalMinioSysTmp) {
|
||||
if !strings.HasPrefix(prefix, minio.GatewayMinioSysTmp) {
|
||||
// If client lists outside gcsMinioPath then we filter out gcsMinioPath/* entries.
|
||||
// But if the client lists inside gcsMinioPath then we return the entries in gcsMinioPath/
|
||||
// which will be helpful to observe the "directory structure" for debugging purposes.
|
||||
if strings.HasPrefix(attrs.Prefix, globalMinioSysTmp) ||
|
||||
strings.HasPrefix(attrs.Name, globalMinioSysTmp) {
|
||||
if strings.HasPrefix(attrs.Prefix, minio.GatewayMinioSysTmp) ||
|
||||
strings.HasPrefix(attrs.Name, minio.GatewayMinioSysTmp) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -697,7 +710,7 @@ func (l *gcsGateway) ListObjectsV2(bucket, prefix, continuationToken, delimiter
|
||||
objects = append(objects, fromGCSAttrsToObjectInfo(attrs))
|
||||
}
|
||||
|
||||
return ListObjectsV2Info{
|
||||
return minio.ListObjectsV2Info{
|
||||
IsTruncated: isTruncated,
|
||||
ContinuationToken: continuationToken,
|
||||
NextContinuationToken: continuationToken,
|
||||
@@ -716,55 +729,33 @@ func (l *gcsGateway) GetObject(bucket string, key string, startOffset int64, len
|
||||
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
|
||||
// otherwise gcs will just return object not exist in case of non-existing bucket
|
||||
if _, err := l.client.Bucket(bucket).Attrs(l.ctx); err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket)
|
||||
return gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
object := l.client.Bucket(bucket).Object(key)
|
||||
r, err := object.NewRangeReader(l.ctx, startOffset, length)
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket, key)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
if _, err := io.Copy(writer, r); err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket, key)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fromMinioClientListBucketResultToV2Info converts minio ListBucketResult to ListObjectsV2Info
|
||||
func fromMinioClientListBucketResultToV2Info(bucket string, result minio.ListBucketResult) ListObjectsV2Info {
|
||||
objects := make([]ObjectInfo, len(result.Contents))
|
||||
|
||||
for i, oi := range result.Contents {
|
||||
objects[i] = fromMinioClientObjectInfo(bucket, oi)
|
||||
}
|
||||
|
||||
prefixes := make([]string, len(result.CommonPrefixes))
|
||||
for i, p := range result.CommonPrefixes {
|
||||
prefixes[i] = p.Prefix
|
||||
}
|
||||
|
||||
return ListObjectsV2Info{
|
||||
IsTruncated: result.IsTruncated,
|
||||
Prefixes: prefixes,
|
||||
Objects: objects,
|
||||
ContinuationToken: result.Marker,
|
||||
NextContinuationToken: result.NextMarker,
|
||||
}
|
||||
}
|
||||
|
||||
// fromGCSAttrsToObjectInfo converts GCS BucketAttrs to gateway ObjectInfo
|
||||
func fromGCSAttrsToObjectInfo(attrs *storage.ObjectAttrs) ObjectInfo {
|
||||
func fromGCSAttrsToObjectInfo(attrs *storage.ObjectAttrs) minio.ObjectInfo {
|
||||
// All google cloud storage objects have a CRC32c hash, whereas composite objects may not have a MD5 hash
|
||||
// Refer https://cloud.google.com/storage/docs/hashes-etags. Use CRC32C for ETag
|
||||
return ObjectInfo{
|
||||
return minio.ObjectInfo{
|
||||
Name: attrs.Name,
|
||||
Bucket: attrs.Bucket,
|
||||
ModTime: attrs.Updated,
|
||||
Size: attrs.Size,
|
||||
ETag: toS3ETag(fmt.Sprintf("%d", attrs.CRC32C)),
|
||||
ETag: minio.ToS3ETag(fmt.Sprintf("%d", attrs.CRC32C)),
|
||||
UserDefined: attrs.Metadata,
|
||||
ContentType: attrs.ContentType,
|
||||
ContentEncoding: attrs.ContentEncoding,
|
||||
@@ -772,27 +763,27 @@ func fromGCSAttrsToObjectInfo(attrs *storage.ObjectAttrs) ObjectInfo {
|
||||
}
|
||||
|
||||
// GetObjectInfo - reads object info and replies back ObjectInfo
|
||||
func (l *gcsGateway) GetObjectInfo(bucket string, object string) (ObjectInfo, error) {
|
||||
func (l *gcsGateway) GetObjectInfo(bucket string, object string) (minio.ObjectInfo, error) {
|
||||
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
|
||||
// otherwise gcs will just return object not exist in case of non-existing bucket
|
||||
if _, err := l.client.Bucket(bucket).Attrs(l.ctx); err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
attrs, err := l.client.Bucket(bucket).Object(object).Attrs(l.ctx)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, object)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return fromGCSAttrsToObjectInfo(attrs), nil
|
||||
}
|
||||
|
||||
// PutObject - Create a new object with the incoming data,
|
||||
func (l *gcsGateway) PutObject(bucket string, key string, data *hash.Reader, metadata map[string]string) (ObjectInfo, error) {
|
||||
func (l *gcsGateway) PutObject(bucket string, key string, data *hash.Reader, metadata map[string]string) (minio.ObjectInfo, error) {
|
||||
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
|
||||
// otherwise gcs will just return object not exist in case of non-existing bucket
|
||||
if _, err := l.client.Bucket(bucket).Attrs(l.ctx); err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
object := l.client.Bucket(bucket).Object(key)
|
||||
@@ -806,7 +797,7 @@ func (l *gcsGateway) PutObject(bucket string, key string, data *hash.Reader, met
|
||||
if _, err := io.Copy(w, data); err != nil {
|
||||
// Close the object writer upon error.
|
||||
w.CloseWithError(err)
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
// Close the object writer upon success.
|
||||
@@ -814,7 +805,7 @@ func (l *gcsGateway) PutObject(bucket string, key string, data *hash.Reader, met
|
||||
|
||||
attrs, err := object.Attrs(l.ctx)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
return fromGCSAttrsToObjectInfo(attrs), nil
|
||||
@@ -822,7 +813,7 @@ func (l *gcsGateway) PutObject(bucket string, key string, data *hash.Reader, met
|
||||
|
||||
// CopyObject - Copies a blob from source container to destination container.
|
||||
func (l *gcsGateway) CopyObject(srcBucket string, srcObject string, destBucket string, destObject string,
|
||||
metadata map[string]string) (ObjectInfo, error) {
|
||||
metadata map[string]string) (minio.ObjectInfo, error) {
|
||||
|
||||
src := l.client.Bucket(srcBucket).Object(srcObject)
|
||||
dst := l.client.Bucket(destBucket).Object(destObject)
|
||||
@@ -832,7 +823,7 @@ func (l *gcsGateway) CopyObject(srcBucket string, srcObject string, destBucket s
|
||||
|
||||
attrs, err := copier.Run(l.ctx)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), destBucket, destObject)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), destBucket, destObject)
|
||||
}
|
||||
|
||||
return fromGCSAttrsToObjectInfo(attrs), nil
|
||||
@@ -842,7 +833,7 @@ func (l *gcsGateway) CopyObject(srcBucket string, srcObject string, destBucket s
|
||||
func (l *gcsGateway) DeleteObject(bucket string, object string) error {
|
||||
err := l.client.Bucket(bucket).Object(object).Delete(l.ctx)
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket, object)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -851,7 +842,7 @@ func (l *gcsGateway) DeleteObject(bucket string, object string) error {
|
||||
// NewMultipartUpload - upload object in multiple parts
|
||||
func (l *gcsGateway) NewMultipartUpload(bucket string, key string, metadata map[string]string) (uploadID string, err error) {
|
||||
// generate new uploadid
|
||||
uploadID = mustGetUUID()
|
||||
uploadID = minio.MustGetUUID()
|
||||
|
||||
// generate name for part zero
|
||||
meta := gcsMultipartMetaName(uploadID)
|
||||
@@ -868,14 +859,14 @@ func (l *gcsGateway) NewMultipartUpload(bucket string, key string, metadata map[
|
||||
bucket,
|
||||
key,
|
||||
}); err != nil {
|
||||
return "", gcsToObjectError(traceError(err), bucket, key)
|
||||
return "", gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
return uploadID, nil
|
||||
}
|
||||
|
||||
// ListMultipartUploads - lists all multipart uploads.
|
||||
func (l *gcsGateway) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (ListMultipartsInfo, error) {
|
||||
return ListMultipartsInfo{
|
||||
func (l *gcsGateway) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (minio.ListMultipartsInfo, error) {
|
||||
return minio.ListMultipartsInfo{
|
||||
KeyMarker: keyMarker,
|
||||
UploadIDMarker: uploadIDMarker,
|
||||
MaxUploads: maxUploads,
|
||||
@@ -888,18 +879,18 @@ func (l *gcsGateway) ListMultipartUploads(bucket string, prefix string, keyMarke
|
||||
// an object layer compatible error upon any error.
|
||||
func (l *gcsGateway) checkUploadIDExists(bucket string, key string, uploadID string) error {
|
||||
_, err := l.client.Bucket(bucket).Object(gcsMultipartMetaName(uploadID)).Attrs(l.ctx)
|
||||
return gcsToObjectError(traceError(err), bucket, key, uploadID)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, key, uploadID)
|
||||
}
|
||||
|
||||
// PutObjectPart puts a part of object in bucket
|
||||
func (l *gcsGateway) PutObjectPart(bucket string, key string, uploadID string, partNumber int, data *hash.Reader) (PartInfo, error) {
|
||||
func (l *gcsGateway) PutObjectPart(bucket string, key string, uploadID string, partNumber int, data *hash.Reader) (minio.PartInfo, error) {
|
||||
if err := l.checkUploadIDExists(bucket, key, uploadID); err != nil {
|
||||
return PartInfo{}, err
|
||||
return minio.PartInfo{}, err
|
||||
}
|
||||
etag := data.MD5HexString()
|
||||
if etag == "" {
|
||||
// Generate random ETag.
|
||||
etag = genETag()
|
||||
etag = minio.GenETag()
|
||||
}
|
||||
object := l.client.Bucket(bucket).Object(gcsMultipartDataName(uploadID, partNumber, etag))
|
||||
w := object.NewWriter(l.ctx)
|
||||
@@ -909,22 +900,22 @@ func (l *gcsGateway) PutObjectPart(bucket string, key string, uploadID string, p
|
||||
if _, err := io.Copy(w, data); err != nil {
|
||||
// Make sure to close object writer upon error.
|
||||
w.Close()
|
||||
return PartInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.PartInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
// Make sure to close the object writer upon success.
|
||||
w.Close()
|
||||
return PartInfo{
|
||||
return minio.PartInfo{
|
||||
PartNumber: partNumber,
|
||||
ETag: etag,
|
||||
LastModified: UTCNow(),
|
||||
LastModified: minio.UTCNow(),
|
||||
Size: data.Size(),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// ListObjectParts returns all object parts for specified object in specified bucket
|
||||
func (l *gcsGateway) ListObjectParts(bucket string, key string, uploadID string, partNumberMarker int, maxParts int) (ListPartsInfo, error) {
|
||||
return ListPartsInfo{}, l.checkUploadIDExists(bucket, key, uploadID)
|
||||
func (l *gcsGateway) ListObjectParts(bucket string, key string, uploadID string, partNumberMarker int, maxParts int) (minio.ListPartsInfo, error) {
|
||||
return minio.ListPartsInfo{}, l.checkUploadIDExists(bucket, key, uploadID)
|
||||
}
|
||||
|
||||
// Called by AbortMultipartUpload and CompleteMultipartUpload for cleaning up.
|
||||
@@ -940,7 +931,7 @@ func (l *gcsGateway) cleanupMultipartUpload(bucket, key, uploadID string) error
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket, key)
|
||||
return gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
object := l.client.Bucket(bucket).Object(attrs.Name)
|
||||
@@ -967,34 +958,34 @@ func (l *gcsGateway) AbortMultipartUpload(bucket string, key string, uploadID st
|
||||
// to the number of components you can compose per second. This rate counts both the
|
||||
// components being appended to a composite object as well as the components being
|
||||
// copied when the composite object of which they are a part is copied.
|
||||
func (l *gcsGateway) CompleteMultipartUpload(bucket string, key string, uploadID string, uploadedParts []CompletePart) (ObjectInfo, error) {
|
||||
func (l *gcsGateway) CompleteMultipartUpload(bucket string, key string, uploadID string, uploadedParts []minio.CompletePart) (minio.ObjectInfo, error) {
|
||||
meta := gcsMultipartMetaName(uploadID)
|
||||
object := l.client.Bucket(bucket).Object(meta)
|
||||
|
||||
partZeroAttrs, err := object.Attrs(l.ctx)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key, uploadID)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key, uploadID)
|
||||
}
|
||||
|
||||
r, err := object.NewReader(l.ctx)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Check version compatibility of the meta file before compose()
|
||||
multipartMeta := gcsMultipartMetaV1{}
|
||||
if err = json.NewDecoder(r).Decode(&multipartMeta); err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
if multipartMeta.Version != gcsMinioMultipartMetaCurrentVersion {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(errFormatNotSupported), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(errGCSFormat), bucket, key)
|
||||
}
|
||||
|
||||
// Validate if the gcs.json stores valid entries for the bucket and key.
|
||||
if multipartMeta.Bucket != bucket || multipartMeta.Object != key {
|
||||
return ObjectInfo{}, gcsToObjectError(InvalidUploadID{
|
||||
return minio.ObjectInfo{}, gcsToObjectError(minio.InvalidUploadID{
|
||||
UploadID: uploadID,
|
||||
}, bucket, key)
|
||||
}
|
||||
@@ -1006,15 +997,15 @@ func (l *gcsGateway) CompleteMultipartUpload(bucket string, key string, uploadID
|
||||
uploadedPart.PartNumber, uploadedPart.ETag)))
|
||||
partAttr, pErr := l.client.Bucket(bucket).Object(gcsMultipartDataName(uploadID, uploadedPart.PartNumber, uploadedPart.ETag)).Attrs(l.ctx)
|
||||
if pErr != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(pErr), bucket, key, uploadID)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(pErr), bucket, key, uploadID)
|
||||
}
|
||||
partSizes[i] = partAttr.Size
|
||||
}
|
||||
|
||||
// Error out if parts except last part sizing < 5MiB.
|
||||
for i, size := range partSizes[:len(partSizes)-1] {
|
||||
if size < globalMinPartSize {
|
||||
return ObjectInfo{}, traceError(PartTooSmall{
|
||||
if size < 5*humanize.MiByte {
|
||||
return minio.ObjectInfo{}, errors.Trace(minio.PartTooSmall{
|
||||
PartNumber: uploadedParts[i].PartNumber,
|
||||
PartSize: size,
|
||||
PartETag: uploadedParts[i].ETag,
|
||||
@@ -1024,7 +1015,7 @@ func (l *gcsGateway) CompleteMultipartUpload(bucket string, key string, uploadID
|
||||
|
||||
// Returns name of the composed object.
|
||||
gcsMultipartComposeName := func(uploadID string, composeNumber int) string {
|
||||
return fmt.Sprintf("%s/tmp/%s/composed-object-%05d", globalMinioSysTmp, uploadID, composeNumber)
|
||||
return fmt.Sprintf("%s/tmp/%s/composed-object-%05d", minio.GatewayMinioSysTmp, uploadID, composeNumber)
|
||||
}
|
||||
|
||||
composeCount := int(math.Ceil(float64(len(parts)) / float64(gcsMaxComponents)))
|
||||
@@ -1045,7 +1036,7 @@ func (l *gcsGateway) CompleteMultipartUpload(bucket string, key string, uploadID
|
||||
composer.Metadata = partZeroAttrs.Metadata
|
||||
|
||||
if _, err = composer.Run(l.ctx); err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1058,20 +1049,20 @@ func (l *gcsGateway) CompleteMultipartUpload(bucket string, key string, uploadID
|
||||
composer.Metadata = partZeroAttrs.Metadata
|
||||
attrs, err := composer.Run(l.ctx)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
if err = l.cleanupMultipartUpload(bucket, key, uploadID); err != nil {
|
||||
return ObjectInfo{}, gcsToObjectError(traceError(err), bucket, key)
|
||||
return minio.ObjectInfo{}, gcsToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
return fromGCSAttrsToObjectInfo(attrs), nil
|
||||
}
|
||||
|
||||
// SetBucketPolicies - Set policy on bucket
|
||||
func (l *gcsGateway) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error {
|
||||
var policies []BucketAccessPolicy
|
||||
var policies []minio.BucketAccessPolicy
|
||||
|
||||
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket) {
|
||||
policies = append(policies, BucketAccessPolicy{
|
||||
policies = append(policies, minio.BucketAccessPolicy{
|
||||
Prefix: prefix,
|
||||
Policy: policy,
|
||||
})
|
||||
@@ -1080,16 +1071,16 @@ func (l *gcsGateway) SetBucketPolicies(bucket string, policyInfo policy.BucketAc
|
||||
prefix := bucket + "/*" // For all objects inside the bucket.
|
||||
|
||||
if len(policies) != 1 {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
if policies[0].Prefix != prefix {
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
|
||||
acl := l.client.Bucket(bucket).ACL()
|
||||
if policies[0].Policy == policy.BucketPolicyNone {
|
||||
if err := acl.Delete(l.ctx, storage.AllUsers); err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket)
|
||||
return gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1101,11 +1092,11 @@ func (l *gcsGateway) SetBucketPolicies(bucket string, policyInfo policy.BucketAc
|
||||
case policy.BucketPolicyWriteOnly:
|
||||
role = storage.RoleWriter
|
||||
default:
|
||||
return traceError(NotImplemented{})
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
|
||||
if err := acl.Set(l.ctx, storage.AllUsers, role); err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket)
|
||||
return gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1115,7 +1106,7 @@ func (l *gcsGateway) SetBucketPolicies(bucket string, policyInfo policy.BucketAc
|
||||
func (l *gcsGateway) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) {
|
||||
rules, err := l.client.Bucket(bucket).ACL().List(l.ctx)
|
||||
if err != nil {
|
||||
return policy.BucketAccessPolicy{}, gcsToObjectError(traceError(err), bucket)
|
||||
return policy.BucketAccessPolicy{}, gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"}
|
||||
for _, r := range rules {
|
||||
@@ -1131,7 +1122,7 @@ func (l *gcsGateway) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy
|
||||
}
|
||||
// Return NoSuchBucketPolicy error, when policy is not set
|
||||
if len(policyInfo.Statements) == 0 {
|
||||
return policy.BucketAccessPolicy{}, gcsToObjectError(traceError(PolicyNotFound{}), bucket)
|
||||
return policy.BucketAccessPolicy{}, gcsToObjectError(errors.Trace(minio.PolicyNotFound{}), bucket)
|
||||
}
|
||||
return policyInfo, nil
|
||||
}
|
||||
@@ -1140,7 +1131,7 @@ func (l *gcsGateway) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy
|
||||
func (l *gcsGateway) DeleteBucketPolicies(bucket string) error {
|
||||
// This only removes the storage.AllUsers policies
|
||||
if err := l.client.Bucket(bucket).ACL().Delete(l.ctx, storage.AllUsers); err != nil {
|
||||
return gcsToObjectError(traceError(err), bucket)
|
||||
return gcsToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,16 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package gcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
minio "github.com/minio/minio-go"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"google.golang.org/api/googleapi"
|
||||
|
||||
miniogo "github.com/minio/minio-go"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
func TestToGCSPageToken(t *testing.T) {
|
||||
@@ -138,7 +143,7 @@ func TestIsGCSMarker(t *testing.T) {
|
||||
// Test for gcsMultipartMetaName.
|
||||
func TestGCSMultipartMetaName(t *testing.T) {
|
||||
uploadID := "a"
|
||||
expected := pathJoin(gcsMinioMultipartPathV1, uploadID, gcsMinioMultipartMeta)
|
||||
expected := path.Join(gcsMinioMultipartPathV1, uploadID, gcsMinioMultipartMeta)
|
||||
got := gcsMultipartMetaName(uploadID)
|
||||
if expected != got {
|
||||
t.Errorf("expected: %s, got: %s", expected, got)
|
||||
@@ -152,7 +157,7 @@ func TestGCSMultipartDataName(t *testing.T) {
|
||||
etag = "b"
|
||||
partNumber = 1
|
||||
)
|
||||
expected := pathJoin(gcsMinioMultipartPathV1, uploadID, fmt.Sprintf("%05d.%s", partNumber, etag))
|
||||
expected := path.Join(gcsMinioMultipartPathV1, uploadID, fmt.Sprintf("%05d.%s", partNumber, etag))
|
||||
got := gcsMultipartDataName(uploadID, partNumber, etag)
|
||||
if expected != got {
|
||||
t.Errorf("expected: %s, got: %s", expected, got)
|
||||
@@ -161,23 +166,23 @@ func TestGCSMultipartDataName(t *testing.T) {
|
||||
|
||||
func TestFromMinioClientListBucketResultToV2Info(t *testing.T) {
|
||||
|
||||
listBucketResult := minio.ListBucketResult{
|
||||
listBucketResult := miniogo.ListBucketResult{
|
||||
IsTruncated: false,
|
||||
Marker: "testMarker",
|
||||
NextMarker: "testMarker2",
|
||||
CommonPrefixes: []minio.CommonPrefix{{Prefix: "one"}, {Prefix: "two"}},
|
||||
Contents: []minio.ObjectInfo{{Key: "testobj", ContentType: ""}},
|
||||
CommonPrefixes: []miniogo.CommonPrefix{{Prefix: "one"}, {Prefix: "two"}},
|
||||
Contents: []miniogo.ObjectInfo{{Key: "testobj", ContentType: ""}},
|
||||
}
|
||||
|
||||
listBucketV2Info := ListObjectsV2Info{
|
||||
listBucketV2Info := minio.ListObjectsV2Info{
|
||||
Prefixes: []string{"one", "two"},
|
||||
Objects: []ObjectInfo{{Name: "testobj", Bucket: "testbucket", UserDefined: map[string]string{"Content-Type": ""}}},
|
||||
Objects: []minio.ObjectInfo{{Name: "testobj", Bucket: "testbucket", UserDefined: map[string]string{"Content-Type": ""}}},
|
||||
IsTruncated: false,
|
||||
ContinuationToken: "testMarker",
|
||||
NextContinuationToken: "testMarker2",
|
||||
}
|
||||
|
||||
if got := fromMinioClientListBucketResultToV2Info("testbucket", listBucketResult); !reflect.DeepEqual(got, listBucketV2Info) {
|
||||
if got := minio.FromMinioClientListBucketResultToV2Info("testbucket", listBucketResult); !reflect.DeepEqual(got, listBucketV2Info) {
|
||||
t.Errorf("fromMinioClientListBucketResultToV2Info() = %v, want %v", got, listBucketV2Info)
|
||||
}
|
||||
}
|
||||
@@ -201,10 +206,198 @@ func TestGCSParseProjectID(t *testing.T) {
|
||||
f.WriteString(contents)
|
||||
projectID, err := gcsParseProjectID(f.Name())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
if projectID != "miniotesting" {
|
||||
t.Errorf(`Expected projectID value to be "miniotesting"`)
|
||||
}
|
||||
|
||||
if _, err = gcsParseProjectID("non-existent"); err == nil {
|
||||
t.Errorf(`Expected to fail but succeeded reading "non-existent"`)
|
||||
}
|
||||
|
||||
f.WriteString(`,}`)
|
||||
|
||||
if _, err := gcsParseProjectID(f.Name()); err == nil {
|
||||
t.Errorf(`Expected to fail reading corrupted credentials file`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGCSPublicURL(t *testing.T) {
|
||||
gcsURL := toGCSPublicURL("bucket", "testing")
|
||||
if gcsURL != "https://storage.googleapis.com/bucket/testing" {
|
||||
t.Errorf(`Expected "https://storage.googleapis.com/bucket/testing", got %s"`, gcsURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGCSToObjectError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
params []string
|
||||
gcsErr error
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
[]string{}, nil, nil,
|
||||
},
|
||||
{
|
||||
[]string{}, fmt.Errorf("Not *Error"), fmt.Errorf("Not *Error"),
|
||||
},
|
||||
{
|
||||
[]string{"bucket"},
|
||||
errors.Trace(fmt.Errorf("storage: bucket doesn't exist")),
|
||||
minio.BucketNotFound{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(fmt.Errorf("storage: object doesn't exist")),
|
||||
minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object", "uploadID"},
|
||||
errors.Trace(fmt.Errorf("storage: object doesn't exist")),
|
||||
minio.InvalidUploadID{
|
||||
UploadID: "uploadID",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{},
|
||||
errors.Trace(fmt.Errorf("Unknown error")),
|
||||
fmt.Errorf("Unknown error"),
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Message: "No list of errors",
|
||||
}),
|
||||
&googleapi.Error{
|
||||
Message: "No list of errors",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "conflict",
|
||||
Message: "You already own this bucket. Please select another name.",
|
||||
}},
|
||||
}),
|
||||
minio.BucketAlreadyOwnedByYou{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "conflict",
|
||||
Message: "Sorry, that name is not available. Please try a different one.",
|
||||
}},
|
||||
}),
|
||||
minio.BucketAlreadyExists{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "conflict",
|
||||
}},
|
||||
}),
|
||||
minio.BucketNotEmpty{Bucket: "bucket"},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "notFound",
|
||||
}},
|
||||
}),
|
||||
minio.BucketNotFound{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "notFound",
|
||||
}},
|
||||
}),
|
||||
minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "invalid",
|
||||
}},
|
||||
}),
|
||||
minio.BucketNameInvalid{
|
||||
Bucket: "bucket",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "forbidden",
|
||||
}},
|
||||
}),
|
||||
minio.PrefixAccessDenied{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "keyInvalid",
|
||||
}},
|
||||
}),
|
||||
minio.PrefixAccessDenied{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "required",
|
||||
}},
|
||||
}),
|
||||
minio.PrefixAccessDenied{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{"bucket", "object"},
|
||||
errors.Trace(&googleapi.Error{
|
||||
Errors: []googleapi.ErrorItem{{
|
||||
Reason: "unknown",
|
||||
}},
|
||||
}),
|
||||
fmt.Errorf("Unsupported error reason: unknown"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
actualErr := gcsToObjectError(testCase.gcsErr, testCase.params...)
|
||||
if actualErr != nil {
|
||||
if actualErr.Error() != testCase.expectedErr.Error() {
|
||||
t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package manta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
triton "github.com/joyent/triton-go"
|
||||
"github.com/joyent/triton-go/authentication"
|
||||
tclient "github.com/joyent/triton-go/client"
|
||||
"github.com/joyent/triton-go/storage"
|
||||
|
||||
"github.com/minio/cli"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
// stor is a namespace within manta where you store any documents that are deemed as private
|
||||
// and require access credentials to read them. Within the stor namespace, you can create any
|
||||
// number of directories and objects.
|
||||
const (
|
||||
mantaBackend = "manta"
|
||||
defaultMantaRoot = "/stor"
|
||||
defaultMantaURL = "https://us-east.manta.joyent.com"
|
||||
)
|
||||
|
||||
var mantaRoot = defaultMantaRoot
|
||||
|
||||
func init() {
|
||||
const mantaGatewayTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [ENDPOINT]
|
||||
{{if .VisibleFlags}}
|
||||
FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
ENDPOINT:
|
||||
Manta server endpoint. Default ENDPOINT is https://us-east.manta.joyent.com
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
ACCESS:
|
||||
MINIO_ACCESS_KEY: The Manta account name.
|
||||
MINIO_SECRET_KEY: A KeyID associated with the Manta account.
|
||||
MANTA_KEY_MATERIAL: The path to the SSH Key associated with the Manta account if the MINIO_SECRET_KEY is not in SSH Agent.
|
||||
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for Manta Object Storage backend.
|
||||
$ export MINIO_ACCESS_KEY=manta_account_name
|
||||
$ export MINIO_SECRET_KEY=manta_key_id
|
||||
$ {{.HelpName}}
|
||||
|
||||
2. Start minio gateway server for Manta Object Storage backend on custom endpoint.
|
||||
$ export MINIO_ACCESS_KEY=manta_account_name
|
||||
$ export MINIO_SECRET_KEY=manta_key_id
|
||||
$ {{.HelpName}} https://us-west.manta.joyent.com
|
||||
|
||||
3. Start minio gateway server for Manta Object Storage backend without using SSH Agent.
|
||||
$ export MINIO_ACCESS_KEY=manta_account_name
|
||||
$ export MINIO_SECRET_KEY=manta_key_id
|
||||
$ export MANTA_KEY_MATERIAL=~/.ssh/custom_rsa
|
||||
$ {{.HelpName}}
|
||||
|
||||
`
|
||||
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: mantaBackend,
|
||||
Usage: "Manta Object Storage.",
|
||||
Action: mantaGatewayMain,
|
||||
CustomHelpTemplate: mantaGatewayTemplate,
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
|
||||
func mantaGatewayMain(ctx *cli.Context) {
|
||||
// Validate gateway arguments.
|
||||
host := ctx.Args().First()
|
||||
// Validate gateway arguments.
|
||||
minio.FatalIf(minio.ValidateGatewayArguments(ctx.GlobalString("address"), host), "Invalid argument")
|
||||
|
||||
minio.StartGateway(ctx, &Manta{host})
|
||||
}
|
||||
|
||||
// Manta implements Gateway.
|
||||
type Manta struct {
|
||||
host string
|
||||
}
|
||||
|
||||
// Name implements Gateway interface.
|
||||
func (g *Manta) Name() string {
|
||||
return mantaBackend
|
||||
}
|
||||
|
||||
// NewGatewayLayer returns manta gateway layer, implements GatewayLayer interface to
|
||||
// talk to manta remote backend.
|
||||
func (g *Manta) NewGatewayLayer(creds auth.Credentials) (minio.GatewayLayer, error) {
|
||||
var err error
|
||||
var signer authentication.Signer
|
||||
var endpoint = defaultMantaURL
|
||||
|
||||
if g.host != "" {
|
||||
endpoint, _, err = minio.ParseGatewayEndpoint(g.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if overrideRoot, ok := os.LookupEnv("MANTA_ROOT"); ok {
|
||||
mantaRoot = overrideRoot
|
||||
}
|
||||
|
||||
keyMaterial := os.Getenv("MANTA_KEY_MATERIAL")
|
||||
|
||||
if keyMaterial == "" {
|
||||
signer, err = authentication.NewSSHAgentSigner(creds.SecretKey, creds.AccessKey)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
} else {
|
||||
var keyBytes []byte
|
||||
if _, err = os.Stat(keyMaterial); err == nil {
|
||||
keyBytes, err = ioutil.ReadFile(keyMaterial)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading key material from %s: %s",
|
||||
keyMaterial, err)
|
||||
}
|
||||
block, _ := pem.Decode(keyBytes)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to read key material '%s': no key found", keyMaterial)
|
||||
}
|
||||
|
||||
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to read key '%s': password protected keys are\n"+
|
||||
"not currently supported. Please decrypt the key prior to use.", keyMaterial)
|
||||
}
|
||||
|
||||
} else {
|
||||
keyBytes = []byte(keyMaterial)
|
||||
}
|
||||
|
||||
signer, err = authentication.NewPrivateKeySigner(creds.SecretKey, keyBytes, creds.AccessKey)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
}
|
||||
|
||||
tc, err := storage.NewClient(&triton.ClientConfig{
|
||||
MantaURL: endpoint,
|
||||
AccountName: creds.AccessKey,
|
||||
Signers: []authentication.Signer{signer},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tc.Client.HTTPClient = &http.Client{
|
||||
Transport: minio.NewCustomHTTPTransport(),
|
||||
}
|
||||
|
||||
return &tritonObjects{
|
||||
client: tc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Production - Manta is not production ready.
|
||||
func (g *Manta) Production() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// tritonObjects - Implements Object layer for Triton Manta storage
|
||||
type tritonObjects struct {
|
||||
minio.GatewayUnsupported
|
||||
client *storage.StorageClient
|
||||
}
|
||||
|
||||
// Shutdown - save any gateway metadata to disk
|
||||
// if necessary and reload upon next restart.
|
||||
func (t *tritonObjects) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageInfo - Not relevant to Triton backend.
|
||||
func (t *tritonObjects) StorageInfo() (si minio.StorageInfo) {
|
||||
return si
|
||||
}
|
||||
|
||||
//
|
||||
// ~~~ Buckets ~~~
|
||||
//
|
||||
|
||||
// MakeBucketWithLocation - Create a new directory within manta.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#PutDirectory
|
||||
func (t *tritonObjects) MakeBucketWithLocation(bucket, location string) error {
|
||||
ctx := context.Background()
|
||||
err := t.client.Dir().Put(ctx, &storage.PutDirectoryInput{
|
||||
DirectoryName: path.Join(mantaRoot, bucket),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBucketInfo - Get directory metadata..
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#GetObject
|
||||
func (t *tritonObjects) GetBucketInfo(bucket string) (bi minio.BucketInfo, e error) {
|
||||
var info minio.BucketInfo
|
||||
ctx := context.Background()
|
||||
resp, err := t.client.Objects().Get(ctx, &storage.GetObjectInput{
|
||||
ObjectPath: path.Join(mantaRoot, bucket),
|
||||
})
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
return minio.BucketInfo{
|
||||
Name: bucket,
|
||||
Created: resp.LastModified,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListBuckets - Lists all Manta directories, uses Manta equivalent
|
||||
// ListDirectories.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#ListDirectory
|
||||
func (t *tritonObjects) ListBuckets() (buckets []minio.BucketInfo, err error) {
|
||||
ctx := context.Background()
|
||||
dirs, err := t.client.Dir().List(ctx, &storage.ListDirectoryInput{
|
||||
DirectoryName: path.Join(mantaRoot),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, dir := range dirs.Entries {
|
||||
if dir.Type == "directory" {
|
||||
buckets = append(buckets, minio.BucketInfo{
|
||||
Name: dir.Name,
|
||||
Created: dir.ModifiedTime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return buckets, nil
|
||||
}
|
||||
|
||||
// DeleteBucket - Delete a directory in Manta, uses Manta equivalent
|
||||
// DeleteDirectory.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#DeleteDirectory
|
||||
func (t *tritonObjects) DeleteBucket(bucket string) error {
|
||||
ctx := context.Background()
|
||||
return t.client.Dir().Delete(ctx, &storage.DeleteDirectoryInput{
|
||||
DirectoryName: path.Join(mantaRoot, bucket),
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// ~~~ Objects ~~~
|
||||
//
|
||||
|
||||
// ListObjects - Lists all objects in Manta with a container filtered by prefix
|
||||
// and marker, uses Manta equivalent ListDirectory.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#ListDirectory
|
||||
func (t *tritonObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result minio.ListObjectsInfo, err error) {
|
||||
var (
|
||||
dirName string
|
||||
objs *storage.ListDirectoryOutput
|
||||
input *storage.ListDirectoryInput
|
||||
|
||||
ctx = context.Background()
|
||||
pathBase = path.Base(prefix)
|
||||
)
|
||||
|
||||
// Make sure to only request a Dir.List for the parent "directory" for a
|
||||
// given prefix first. We don't know if our prefix is referencing a
|
||||
// directory or file name and can't send file names into Dir.List because
|
||||
// that'll cause Manta to return file content in the response body. Dir.List
|
||||
// expects to parse out directory entries in JSON. So, try the first
|
||||
// directory name of the prefix path provided.
|
||||
if pathDir := path.Dir(prefix); pathDir == "." {
|
||||
dirName = path.Join(mantaRoot, bucket)
|
||||
} else {
|
||||
dirName = path.Join(mantaRoot, bucket, pathDir)
|
||||
}
|
||||
|
||||
input = &storage.ListDirectoryInput{
|
||||
DirectoryName: dirName,
|
||||
Limit: uint64(maxKeys),
|
||||
Marker: marker,
|
||||
}
|
||||
objs, err = t.client.Dir().List(ctx, input)
|
||||
if err != nil {
|
||||
if tclient.IsResourceNotFoundError(err) {
|
||||
return result, nil
|
||||
}
|
||||
return result, errors.Trace(err)
|
||||
}
|
||||
|
||||
for _, obj := range objs.Entries {
|
||||
// If the base name of our prefix was found to be of type "directory"
|
||||
// than we need to pull the directory entries for that instead.
|
||||
if obj.Name == pathBase && obj.Type == "directory" {
|
||||
input.DirectoryName = path.Join(mantaRoot, bucket, prefix)
|
||||
objs, err = t.client.Dir().List(ctx, input)
|
||||
if err != nil {
|
||||
return result, errors.Trace(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
isTruncated := true // Always send a second request.
|
||||
if marker == "" && len(objs.Entries) < maxKeys {
|
||||
isTruncated = false
|
||||
} else if marker != "" && len(objs.Entries) < maxKeys {
|
||||
isTruncated = false
|
||||
}
|
||||
|
||||
for _, obj := range objs.Entries {
|
||||
if obj.Type == "directory" {
|
||||
result.Prefixes = append(result.Prefixes, obj.Name+delimiter)
|
||||
} else {
|
||||
result.Objects = append(result.Objects, minio.ObjectInfo{
|
||||
Name: obj.Name,
|
||||
Size: int64(obj.Size),
|
||||
ModTime: obj.ModifiedTime,
|
||||
ETag: obj.ETag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result.IsTruncated = isTruncated
|
||||
if isTruncated {
|
||||
result.NextMarker = result.Objects[len(result.Objects)-1].Name
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
//
|
||||
// ~~~ Objects ~~~
|
||||
//
|
||||
|
||||
// ListObjectsV2 - Lists all objects in Manta with a container filtered by prefix
|
||||
// and continuationToken, uses Manta equivalent ListDirectory.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#ListDirectory
|
||||
func (t *tritonObjects) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result minio.ListObjectsV2Info, err error) {
|
||||
var (
|
||||
dirName string
|
||||
objs *storage.ListDirectoryOutput
|
||||
input *storage.ListDirectoryInput
|
||||
|
||||
ctx = context.Background()
|
||||
pathBase = path.Base(prefix)
|
||||
)
|
||||
|
||||
if pathDir := path.Dir(prefix); pathDir == "." {
|
||||
dirName = path.Join(mantaRoot, bucket)
|
||||
} else {
|
||||
dirName = path.Join(mantaRoot, bucket, pathDir)
|
||||
}
|
||||
|
||||
input = &storage.ListDirectoryInput{
|
||||
DirectoryName: dirName,
|
||||
Limit: uint64(maxKeys),
|
||||
Marker: continuationToken,
|
||||
}
|
||||
objs, err = t.client.Dir().List(ctx, input)
|
||||
if err != nil {
|
||||
if tclient.IsResourceNotFoundError(err) {
|
||||
return result, nil
|
||||
}
|
||||
return result, errors.Trace(err)
|
||||
}
|
||||
|
||||
for _, obj := range objs.Entries {
|
||||
if obj.Name == pathBase && obj.Type == "directory" {
|
||||
input.DirectoryName = path.Join(mantaRoot, bucket, prefix)
|
||||
objs, err = t.client.Dir().List(ctx, input)
|
||||
if err != nil {
|
||||
return result, errors.Trace(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
isTruncated := true // Always send a second request.
|
||||
if continuationToken == "" && len(objs.Entries) < maxKeys {
|
||||
isTruncated = false
|
||||
} else if continuationToken != "" && len(objs.Entries) < maxKeys {
|
||||
isTruncated = false
|
||||
}
|
||||
|
||||
for _, obj := range objs.Entries {
|
||||
if obj.Type == "directory" {
|
||||
result.Prefixes = append(result.Prefixes, obj.Name+delimiter)
|
||||
} else {
|
||||
result.Objects = append(result.Objects, minio.ObjectInfo{
|
||||
Name: obj.Name,
|
||||
Size: int64(obj.Size),
|
||||
ModTime: obj.ModifiedTime,
|
||||
ETag: obj.ETag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result.IsTruncated = isTruncated
|
||||
if isTruncated {
|
||||
result.NextContinuationToken = result.Objects[len(result.Objects)-1].Name
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetObject - Reads an object from Manta. Supports additional parameters like
|
||||
// offset and length which are synonymous with HTTP Range requests.
|
||||
//
|
||||
// startOffset indicates the starting read location of the object. length
|
||||
// indicates the total length of the object.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#GetObject
|
||||
func (t *tritonObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) error {
|
||||
// Start offset cannot be negative.
|
||||
if startOffset < 0 {
|
||||
return errors.Trace(fmt.Errorf("Unexpected error"))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
output, err := t.client.Objects().Get(ctx, &storage.GetObjectInput{
|
||||
ObjectPath: path.Join(mantaRoot, bucket, object),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.ObjectReader.Close()
|
||||
|
||||
// Read until startOffset and discard, Manta object storage doesn't support range GET requests yet.
|
||||
if _, err = io.CopyN(ioutil.Discard, output.ObjectReader, startOffset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
_, err = io.Copy(writer, io.LimitReader(output.ObjectReader, length))
|
||||
} else {
|
||||
_, err = io.Copy(writer, output.ObjectReader)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetObjectInfo - reads blob metadata properties and replies back minio.ObjectInfo,
|
||||
// uses Triton equivalent GetBlobProperties.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#GetObject
|
||||
func (t *tritonObjects) GetObjectInfo(bucket, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
ctx := context.Background()
|
||||
info, err := t.client.Objects().GetInfo(ctx, &storage.GetInfoInput{
|
||||
ObjectPath: path.Join(mantaRoot, bucket, object),
|
||||
})
|
||||
if err != nil {
|
||||
errType := &tclient.MantaError{}
|
||||
if errwrap.ContainsType(err, errType) {
|
||||
mantaErr := errwrap.GetType(err, errType).(*tclient.MantaError)
|
||||
if mantaErr.StatusCode == http.StatusNotFound {
|
||||
return objInfo, minio.ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objInfo, err
|
||||
}
|
||||
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
ContentType: info.ContentType,
|
||||
Size: int64(info.ContentLength),
|
||||
ETag: info.ETag,
|
||||
ModTime: info.LastModified,
|
||||
UserDefined: info.Metadata,
|
||||
IsDir: strings.HasSuffix(info.ContentType, "type=directory"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dummySeeker struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (d dummySeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// PutObject - Create a new blob with the incoming data, uses Triton equivalent
|
||||
// CreateBlockBlobFromReader.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#PutObject
|
||||
func (t *tritonObjects) PutObject(bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
ctx := context.Background()
|
||||
if err = t.client.Objects().Put(ctx, &storage.PutObjectInput{
|
||||
ContentLength: uint64(data.Size()),
|
||||
ObjectPath: path.Join(mantaRoot, bucket, object),
|
||||
ContentType: metadata["content-type"],
|
||||
// TODO: Change to `string(data.md5sum)` if/when that becomes an exported field
|
||||
ContentMD5: metadata["content-md5"],
|
||||
ObjectReader: dummySeeker{data},
|
||||
ForceInsert: true,
|
||||
}); err != nil {
|
||||
return objInfo, errors.Trace(err)
|
||||
}
|
||||
if err = data.Verify(); err != nil {
|
||||
t.DeleteObject(bucket, object)
|
||||
return objInfo, errors.Trace(err)
|
||||
}
|
||||
|
||||
return t.GetObjectInfo(bucket, object)
|
||||
}
|
||||
|
||||
// CopyObject - Copies a blob from source container to destination container.
|
||||
// Uses Manta Snaplinks API.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#PutSnapLink
|
||||
func (t *tritonObjects) CopyObject(srcBucket, srcObject, destBucket, destObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
ctx := context.Background()
|
||||
if err = t.client.SnapLinks().Put(ctx, &storage.PutSnapLinkInput{
|
||||
SourcePath: path.Join(mantaRoot, srcBucket, srcObject),
|
||||
LinkPath: path.Join(mantaRoot, destBucket, destObject),
|
||||
}); err != nil {
|
||||
return objInfo, errors.Trace(err)
|
||||
}
|
||||
|
||||
return t.GetObjectInfo(destBucket, destObject)
|
||||
}
|
||||
|
||||
// DeleteObject - Delete a blob in Manta, uses Triton equivalent DeleteBlob API.
|
||||
//
|
||||
// https://apidocs.joyent.com/manta/api.html#DeleteObject
|
||||
func (t *tritonObjects) DeleteObject(bucket, object string) error {
|
||||
ctx := context.Background()
|
||||
if err := t.client.Objects().Delete(ctx, &storage.DeleteObjectInput{
|
||||
ObjectPath: path.Join(mantaRoot, bucket, object),
|
||||
}); err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package oss
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
// AnonPutObject creates a new object anonymously with the incoming data,
|
||||
func (l *ossObjects) AnonPutObject(bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
return ossPutObject(l.anonClient, bucket, object, data, metadata)
|
||||
}
|
||||
|
||||
// AnonGetObject - Get object anonymously
|
||||
func (l *ossObjects) AnonGetObject(bucket, key string, startOffset, length int64, writer io.Writer) error {
|
||||
return ossGetObject(l.anonClient, bucket, key, startOffset, length, writer)
|
||||
}
|
||||
|
||||
// AnonGetObjectInfo - Get object info anonymously
|
||||
func (l *ossObjects) AnonGetObjectInfo(bucket, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
return ossGetObjectInfo(l.anonClient, bucket, object)
|
||||
}
|
||||
|
||||
// AnonListObjects lists objects anonymously.
|
||||
func (l *ossObjects) AnonListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, err error) {
|
||||
return ossListObjects(l.anonClient, bucket, prefix, marker, delimiter, maxKeys)
|
||||
}
|
||||
|
||||
// AnonListObjectsV2 lists objects in V2 mode, anonymously.
|
||||
func (l *ossObjects) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, err error) {
|
||||
return ossListObjectsV2(l.anonClient, bucket, prefix, continuationToken, delimiter, maxKeys, fetchOwner, startAfter)
|
||||
}
|
||||
|
||||
// AnonGetBucketInfo gets bucket metadata anonymously.
|
||||
func (l *ossObjects) AnonGetBucketInfo(bucket string) (bi minio.BucketInfo, err error) {
|
||||
return ossGeBucketInfo(l.anonClient, bucket)
|
||||
}
|
||||
@@ -0,0 +1,991 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package oss
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
ossS3MinPartSize = 5 * humanize.MiByte
|
||||
ossMaxParts = 1000
|
||||
ossMaxKeys = 1000
|
||||
ossBackend = "oss"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const ossGatewayTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [ENDPOINT]
|
||||
{{if .VisibleFlags}}
|
||||
FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
ENDPOINT:
|
||||
OSS server endpoint. Default ENDPOINT is https://oss.aliyuncs.com
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
ACCESS:
|
||||
MINIO_ACCESS_KEY: Username or access key of OSS storage.
|
||||
MINIO_SECRET_KEY: Password or secret key of OSS storage.
|
||||
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for Aliyun OSS backend.
|
||||
$ export MINIO_ACCESS_KEY=accesskey
|
||||
$ export MINIO_SECRET_KEY=secretkey
|
||||
$ {{.HelpName}}
|
||||
|
||||
2. Start minio gateway server for Aliyun OSS backend on custom endpoint.
|
||||
$ export MINIO_ACCESS_KEY=Q3AM3UQ867SPQQA43P2F
|
||||
$ export MINIO_SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
$ {{.HelpName}} https://oss.example.com
|
||||
|
||||
`
|
||||
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: "oss",
|
||||
Usage: "Alibaba Cloud (Aliyun) Object Storage Service (OSS).",
|
||||
Action: ossGatewayMain,
|
||||
CustomHelpTemplate: ossGatewayTemplate,
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for 'minio gateway oss' command line.
|
||||
func ossGatewayMain(ctx *cli.Context) {
|
||||
if ctx.Args().First() == "help" {
|
||||
cli.ShowCommandHelpAndExit(ctx, ossBackend, 1)
|
||||
}
|
||||
|
||||
// Validate gateway arguments.
|
||||
host := ctx.Args().First()
|
||||
minio.FatalIf(minio.ValidateGatewayArguments(ctx.GlobalString("address"), host), "Invalid argument")
|
||||
|
||||
minio.StartGateway(ctx, &OSS{host})
|
||||
}
|
||||
|
||||
// OSS implements Gateway.
|
||||
type OSS struct {
|
||||
host string
|
||||
}
|
||||
|
||||
// Name implements Gateway interface.
|
||||
func (g *OSS) Name() string {
|
||||
return ossBackend
|
||||
}
|
||||
|
||||
// NewGatewayLayer implements Gateway interface and returns OSS GatewayLayer.
|
||||
func (g *OSS) NewGatewayLayer(creds auth.Credentials) (minio.GatewayLayer, error) {
|
||||
var err error
|
||||
|
||||
// Regions and endpoints
|
||||
// https://www.alibabacloud.com/help/doc-detail/31837.htm
|
||||
if g.host == "" {
|
||||
g.host = "https://oss.aliyuncs.com"
|
||||
}
|
||||
|
||||
// Initialize oss client object.
|
||||
client, err := oss.New(g.host, creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anonClient, err := oss.New(g.host, "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ossObjects{
|
||||
Client: client,
|
||||
anonClient: anonClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Production - oss is not production ready yet.
|
||||
func (g *OSS) Production() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// appendS3MetaToOSSOptions converts metadata meant for S3 PUT/COPY
|
||||
// object into oss.Option.
|
||||
//
|
||||
// S3 user-metadata is translated to OSS metadata by removing the
|
||||
// `X-Amz-Meta-` prefix and converted into `X-Oss-Meta-`.
|
||||
//
|
||||
// Header names are canonicalized as in http.Header.
|
||||
func appendS3MetaToOSSOptions(opts []oss.Option, s3Metadata map[string]string) ([]oss.Option, error) {
|
||||
if opts == nil {
|
||||
opts = make([]oss.Option, 0, len(s3Metadata))
|
||||
}
|
||||
for k, v := range s3Metadata {
|
||||
k = http.CanonicalHeaderKey(k)
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(k, "X-Amz-Meta-"):
|
||||
metaKey := k[len("X-Amz-Meta-"):]
|
||||
// NOTE(timonwong): OSS won't allow headers with underscore(_).
|
||||
if strings.Contains(metaKey, "_") {
|
||||
return nil, errors.Trace(minio.UnsupportedMetadata{})
|
||||
}
|
||||
opts = append(opts, oss.Meta(metaKey, v))
|
||||
case k == "X-Amz-Acl":
|
||||
// Valid values: public-read, private, and public-read-write
|
||||
opts = append(opts, oss.ObjectACL(oss.ACLType(v)))
|
||||
case k == "X-Amz-Server-Side-Encryption":
|
||||
opts = append(opts, oss.ServerSideEncryption(v))
|
||||
case k == "X-Amz-Copy-Source-If-Match":
|
||||
opts = append(opts, oss.CopySourceIfMatch(v))
|
||||
case k == "X-Amz-Copy-Source-If-None-Match":
|
||||
opts = append(opts, oss.CopySourceIfNoneMatch(v))
|
||||
case k == "X-Amz-Copy-Source-If-Unmodified-Since":
|
||||
if v, err := http.ParseTime(v); err == nil {
|
||||
opts = append(opts, oss.CopySourceIfUnmodifiedSince(v))
|
||||
}
|
||||
case k == "X-Amz-Copy-Source-If-Modified-Since":
|
||||
if v, err := http.ParseTime(v); err == nil {
|
||||
opts = append(opts, oss.CopySourceIfModifiedSince(v))
|
||||
}
|
||||
case k == "Accept-Encoding":
|
||||
opts = append(opts, oss.AcceptEncoding(v))
|
||||
case k == "Cache-Control":
|
||||
opts = append(opts, oss.CacheControl(v))
|
||||
case k == "Content-Disposition":
|
||||
opts = append(opts, oss.ContentDisposition(v))
|
||||
case k == "Content-Encoding":
|
||||
opts = append(opts, oss.ContentEncoding(v))
|
||||
case k == "Content-Length":
|
||||
if v, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||
opts = append(opts, oss.ContentLength(v))
|
||||
}
|
||||
case k == "Content-MD5":
|
||||
opts = append(opts, oss.ContentMD5(v))
|
||||
case k == "Content-Type":
|
||||
opts = append(opts, oss.ContentType(v))
|
||||
case k == "Expires":
|
||||
if v, err := http.ParseTime(v); err == nil {
|
||||
opts = append(opts, oss.Expires(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// ossMetaToS3Meta converts OSS metadata to S3 metadata.
|
||||
// It is the reverse of appendS3MetaToOSSOptions.
|
||||
func ossHeaderToS3Meta(header http.Header) map[string]string {
|
||||
// Decoding technique for each key is used here is as follows
|
||||
// Each '_' is converted to '-'
|
||||
// Each '__' is converted to '_'
|
||||
// With this basic assumption here are some of the expected
|
||||
// translations for these keys.
|
||||
// i: 'x_s3cmd__attrs' -> o: 'x-s3cmd_attrs' (mixed)
|
||||
// i: 'x____test____value' -> o: 'x__test__value' (double '_')
|
||||
decodeKey := func(key string) string {
|
||||
tokens := strings.Split(key, "__")
|
||||
for i := range tokens {
|
||||
tokens[i] = strings.Replace(tokens[i], "_", "-", -1)
|
||||
}
|
||||
return strings.Join(tokens, "_")
|
||||
}
|
||||
|
||||
s3Metadata := make(map[string]string)
|
||||
for k := range header {
|
||||
k = http.CanonicalHeaderKey(k)
|
||||
switch {
|
||||
case strings.HasPrefix(k, oss.HTTPHeaderOssMetaPrefix):
|
||||
// Add amazon s3 meta prefix
|
||||
metaKey := k[len(oss.HTTPHeaderOssMetaPrefix):]
|
||||
metaKey = "X-Amz-Meta-" + decodeKey(metaKey)
|
||||
metaKey = http.CanonicalHeaderKey(metaKey)
|
||||
s3Metadata[metaKey] = header.Get(k)
|
||||
case k == "Cache-Control":
|
||||
fallthrough
|
||||
case k == "Content-Encoding":
|
||||
fallthrough
|
||||
case k == "Content-Disposition":
|
||||
fallthrough
|
||||
case k == "Content-Length":
|
||||
fallthrough
|
||||
case k == "Content-MD5":
|
||||
fallthrough
|
||||
case k == "Content-Type":
|
||||
s3Metadata[k] = header.Get(k)
|
||||
}
|
||||
}
|
||||
|
||||
return s3Metadata
|
||||
}
|
||||
|
||||
// ossToObjectError converts OSS errors to minio object layer errors.
|
||||
func ossToObjectError(err error, params ...string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, ok := err.(*errors.Error)
|
||||
if !ok {
|
||||
// Code should be fixed if this function is called without doing errors.Trace()
|
||||
// Else handling different situations in this function makes this function complicated.
|
||||
minio.ErrorIf(err, "Expected type *Error")
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.Cause
|
||||
bucket := ""
|
||||
object := ""
|
||||
uploadID := ""
|
||||
switch len(params) {
|
||||
case 3:
|
||||
uploadID = params[2]
|
||||
fallthrough
|
||||
case 2:
|
||||
object = params[1]
|
||||
fallthrough
|
||||
case 1:
|
||||
bucket = params[0]
|
||||
}
|
||||
|
||||
ossErr, ok := err.(oss.ServiceError)
|
||||
if !ok {
|
||||
// We don't interpret non OSS errors. As oss errors will
|
||||
// have StatusCode to help to convert to object errors.
|
||||
return e
|
||||
}
|
||||
|
||||
switch ossErr.Code {
|
||||
case "BucketAlreadyExists":
|
||||
err = minio.BucketAlreadyOwnedByYou{Bucket: bucket}
|
||||
case "BucketNotEmpty":
|
||||
err = minio.BucketNotEmpty{Bucket: bucket}
|
||||
case "InvalidBucketName":
|
||||
err = minio.BucketNameInvalid{Bucket: bucket}
|
||||
case "NoSuchBucket":
|
||||
err = minio.BucketNotFound{Bucket: bucket}
|
||||
case "NoSuchKey":
|
||||
if object != "" {
|
||||
err = minio.ObjectNotFound{Bucket: bucket, Object: object}
|
||||
} else {
|
||||
err = minio.BucketNotFound{Bucket: bucket}
|
||||
}
|
||||
case "InvalidObjectName":
|
||||
err = minio.ObjectNameInvalid{Bucket: bucket, Object: object}
|
||||
case "AccessDenied":
|
||||
err = minio.PrefixAccessDenied{Bucket: bucket, Object: object}
|
||||
case "NoSuchUpload":
|
||||
err = minio.InvalidUploadID{UploadID: uploadID}
|
||||
case "EntityTooSmall":
|
||||
err = minio.PartTooSmall{}
|
||||
case "SignatureDoesNotMatch":
|
||||
err = minio.SignatureDoesNotMatch{}
|
||||
case "InvalidPart":
|
||||
err = minio.InvalidPart{}
|
||||
}
|
||||
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
// ossObjects implements gateway for Aliyun Object Storage Service.
|
||||
type ossObjects struct {
|
||||
minio.GatewayUnsupported
|
||||
Client *oss.Client
|
||||
anonClient *oss.Client
|
||||
}
|
||||
|
||||
// Shutdown saves any gateway metadata to disk
|
||||
// if necessary and reload upon next restart.
|
||||
func (l *ossObjects) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageInfo is not relevant to OSS backend.
|
||||
func (l *ossObjects) StorageInfo() (si minio.StorageInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
// ossIsValidBucketName verifies whether a bucket name is valid.
|
||||
func ossIsValidBucketName(bucket string) bool {
|
||||
// dot is not allowed in bucket name
|
||||
if strings.Contains(bucket, ".") {
|
||||
return false
|
||||
}
|
||||
if !minio.IsValidBucketName(bucket) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MakeBucketWithLocation creates a new container on OSS backend.
|
||||
func (l *ossObjects) MakeBucketWithLocation(bucket, location string) error {
|
||||
if !ossIsValidBucketName(bucket) {
|
||||
return errors.Trace(minio.BucketNameInvalid{Bucket: bucket})
|
||||
}
|
||||
|
||||
err := l.Client.CreateBucket(bucket)
|
||||
return ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
// ossGeBucketInfo gets bucket metadata.
|
||||
func ossGeBucketInfo(client *oss.Client, bucket string) (bi minio.BucketInfo, err error) {
|
||||
if !ossIsValidBucketName(bucket) {
|
||||
return bi, errors.Trace(minio.BucketNameInvalid{Bucket: bucket})
|
||||
}
|
||||
|
||||
bgir, err := client.GetBucketInfo(bucket)
|
||||
if err != nil {
|
||||
return bi, ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return minio.BucketInfo{
|
||||
Name: bgir.BucketInfo.Name,
|
||||
Created: bgir.BucketInfo.CreationDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetBucketInfo gets bucket metadata.
|
||||
func (l *ossObjects) GetBucketInfo(bucket string) (bi minio.BucketInfo, err error) {
|
||||
return ossGeBucketInfo(l.Client, bucket)
|
||||
}
|
||||
|
||||
// ListBuckets lists all OSS buckets.
|
||||
func (l *ossObjects) ListBuckets() (buckets []minio.BucketInfo, err error) {
|
||||
marker := oss.Marker("")
|
||||
for {
|
||||
lbr, err := l.Client.ListBuckets(marker)
|
||||
if err != nil {
|
||||
return nil, ossToObjectError(errors.Trace(err))
|
||||
}
|
||||
|
||||
for _, bi := range lbr.Buckets {
|
||||
buckets = append(buckets, minio.BucketInfo{
|
||||
Name: bi.Name,
|
||||
Created: bi.CreationDate,
|
||||
})
|
||||
}
|
||||
|
||||
marker = oss.Marker(lbr.NextMarker)
|
||||
if !lbr.IsTruncated {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return buckets, nil
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket on OSS.
|
||||
func (l *ossObjects) DeleteBucket(bucket string) error {
|
||||
err := l.Client.DeleteBucket(bucket)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fromOSSClientObjectProperties converts oss ObjectProperties to ObjectInfo.
|
||||
func fromOSSClientObjectProperties(bucket string, o oss.ObjectProperties) minio.ObjectInfo {
|
||||
// NOTE(timonwong): No Content-Type and user defined metadata.
|
||||
// https://www.alibabacloud.com/help/doc-detail/31965.htm
|
||||
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: o.Key,
|
||||
ModTime: o.LastModified,
|
||||
Size: o.Size,
|
||||
ETag: minio.ToS3ETag(o.ETag),
|
||||
}
|
||||
}
|
||||
|
||||
// fromOSSClientListObjectsResult converts oss ListBucketResult to ListObjectsInfo.
|
||||
func fromOSSClientListObjectsResult(bucket string, lor oss.ListObjectsResult) minio.ListObjectsInfo {
|
||||
objects := make([]minio.ObjectInfo, len(lor.Objects))
|
||||
for i, oi := range lor.Objects {
|
||||
objects[i] = fromOSSClientObjectProperties(bucket, oi)
|
||||
}
|
||||
|
||||
prefixes := make([]string, len(lor.CommonPrefixes))
|
||||
copy(prefixes, lor.CommonPrefixes)
|
||||
|
||||
return minio.ListObjectsInfo{
|
||||
IsTruncated: lor.IsTruncated,
|
||||
NextMarker: lor.NextMarker,
|
||||
Objects: objects,
|
||||
Prefixes: prefixes,
|
||||
}
|
||||
}
|
||||
|
||||
// ossListObjects lists all blobs in OSS bucket filtered by prefix.
|
||||
func ossListObjects(client *oss.Client, bucket, prefix, marker, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, err error) {
|
||||
buck, err := client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return loi, ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
// maxKeys should default to 1000 or less.
|
||||
if maxKeys == 0 || maxKeys > ossMaxKeys {
|
||||
maxKeys = ossMaxKeys
|
||||
}
|
||||
|
||||
lor, err := buck.ListObjects(oss.Prefix(prefix), oss.Marker(marker), oss.Delimiter(delimiter), oss.MaxKeys(maxKeys))
|
||||
if err != nil {
|
||||
return loi, ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return fromOSSClientListObjectsResult(bucket, lor), nil
|
||||
}
|
||||
|
||||
// ossListObjectsV2 lists all blobs in OSS bucket filtered by prefix.
|
||||
func ossListObjectsV2(client *oss.Client, bucket, prefix, continuationToken, delimiter string, maxKeys int,
|
||||
fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, err error) {
|
||||
// fetchOwner and startAfter are not supported and unused.
|
||||
marker := continuationToken
|
||||
|
||||
resultV1, err := ossListObjects(client, bucket, prefix, marker, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, err
|
||||
}
|
||||
|
||||
return minio.ListObjectsV2Info{
|
||||
Objects: resultV1.Objects,
|
||||
Prefixes: resultV1.Prefixes,
|
||||
ContinuationToken: continuationToken,
|
||||
NextContinuationToken: resultV1.NextMarker,
|
||||
IsTruncated: resultV1.IsTruncated,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListObjects lists all blobs in OSS bucket filtered by prefix.
|
||||
func (l *ossObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, err error) {
|
||||
return ossListObjects(l.Client, bucket, prefix, marker, delimiter, maxKeys)
|
||||
}
|
||||
|
||||
// ListObjectsV2 lists all blobs in OSS bucket filtered by prefix
|
||||
func (l *ossObjects) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int,
|
||||
fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, err error) {
|
||||
return ossListObjectsV2(l.Client, bucket, prefix, continuationToken, delimiter, maxKeys, fetchOwner, startAfter)
|
||||
}
|
||||
|
||||
// ossGetObject reads an object on OSS. Supports additional
|
||||
// parameters like offset and length which are synonymous with
|
||||
// HTTP Range requests.
|
||||
//
|
||||
// startOffset indicates the starting read location of the object.
|
||||
// length indicates the total length of the object.
|
||||
func ossGetObject(client *oss.Client, bucket, key string, startOffset, length int64, writer io.Writer) error {
|
||||
if length < 0 && length != -1 {
|
||||
return ossToObjectError(errors.Trace(fmt.Errorf("Invalid argument")), bucket, key)
|
||||
}
|
||||
|
||||
bkt, err := client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
var opts []oss.Option
|
||||
if startOffset >= 0 && length >= 0 {
|
||||
opts = append(opts, oss.Range(startOffset, startOffset+length-1))
|
||||
}
|
||||
|
||||
object, err := bkt.GetObject(key, opts...)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
defer object.Close()
|
||||
|
||||
if _, err := io.Copy(writer, object); err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetObject reads an object on OSS. Supports additional
|
||||
// parameters like offset and length which are synonymous with
|
||||
// HTTP Range requests.
|
||||
//
|
||||
// startOffset indicates the starting read location of the object.
|
||||
// length indicates the total length of the object.
|
||||
func (l *ossObjects) GetObject(bucket, key string, startOffset, length int64, writer io.Writer) error {
|
||||
return ossGetObject(l.Client, bucket, key, startOffset, length, writer)
|
||||
}
|
||||
|
||||
func translatePlainError(err error) error {
|
||||
errString := err.Error()
|
||||
|
||||
switch errString {
|
||||
case "oss: service returned without a response body (404 Not Found)":
|
||||
return oss.ServiceError{Code: "NoSuchKey"}
|
||||
case "oss: service returned without a response body (400 Bad Request)":
|
||||
return oss.ServiceError{Code: "AccessDenied"}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ossGetObjectInfo reads object info and replies back ObjectInfo.
|
||||
func ossGetObjectInfo(client *oss.Client, bucket, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
bkt, err := client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return objInfo, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
header, err := bkt.GetObjectDetailedMeta(object)
|
||||
if err != nil {
|
||||
return objInfo, ossToObjectError(errors.Trace(translatePlainError(err)), bucket, object)
|
||||
}
|
||||
|
||||
// Build S3 metadata from OSS metadata
|
||||
userDefined := ossHeaderToS3Meta(header)
|
||||
|
||||
modTime, _ := http.ParseTime(header.Get("Last-Modified"))
|
||||
size, _ := strconv.ParseInt(header.Get("Content-Length"), 10, 64)
|
||||
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
ModTime: modTime,
|
||||
Size: size,
|
||||
ETag: minio.ToS3ETag(header.Get("ETag")),
|
||||
UserDefined: userDefined,
|
||||
ContentType: header.Get("Content-Type"),
|
||||
ContentEncoding: header.Get("Content-Encoding"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo.
|
||||
func (l *ossObjects) GetObjectInfo(bucket, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
return ossGetObjectInfo(l.Client, bucket, object)
|
||||
}
|
||||
|
||||
// ossPutObject creates a new object with the incoming data.
|
||||
func ossPutObject(client *oss.Client, bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
bkt, err := client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return objInfo, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Build OSS metadata
|
||||
opts, err := appendS3MetaToOSSOptions(nil, metadata)
|
||||
if err != nil {
|
||||
return objInfo, ossToObjectError(err, bucket, object)
|
||||
}
|
||||
|
||||
err = bkt.PutObject(object, data, opts...)
|
||||
if err != nil {
|
||||
return objInfo, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return ossGetObjectInfo(client, bucket, object)
|
||||
}
|
||||
|
||||
// PutObject creates a new object with the incoming data.
|
||||
func (l *ossObjects) PutObject(bucket, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
return ossPutObject(l.Client, bucket, object, data, metadata)
|
||||
}
|
||||
|
||||
// CopyObject copies an object from source bucket to a destination bucket.
|
||||
func (l *ossObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
bkt, err := l.Client.Bucket(srcBucket)
|
||||
if err != nil {
|
||||
return objInfo, ossToObjectError(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
|
||||
opts := make([]oss.Option, 0, len(metadata)+1)
|
||||
// Set this header such that following CopyObject() always sets the right metadata on the destination.
|
||||
// metadata input is already a trickled down value from interpreting x-oss-metadata-directive at
|
||||
// handler layer. So what we have right now is supposed to be applied on the destination object anyways.
|
||||
// So preserve it by adding "REPLACE" directive to save all the metadata set by CopyObject API.
|
||||
opts = append(opts, oss.MetadataDirective(oss.MetaReplace))
|
||||
|
||||
// Build OSS metadata
|
||||
opts, err = appendS3MetaToOSSOptions(opts, metadata)
|
||||
if err != nil {
|
||||
return objInfo, ossToObjectError(err, srcBucket, srcObject)
|
||||
}
|
||||
|
||||
if _, err = bkt.CopyObjectTo(dstBucket, dstObject, srcObject, opts...); err != nil {
|
||||
return objInfo, ossToObjectError(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
return l.GetObjectInfo(dstBucket, dstObject)
|
||||
}
|
||||
|
||||
// DeleteObject deletes a blob in bucket.
|
||||
func (l *ossObjects) DeleteObject(bucket, object string) error {
|
||||
bkt, err := l.Client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
err = bkt.DeleteObject(object)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fromOSSClientListMultipartsInfo converts oss ListMultipartUploadResult to ListMultipartsInfo
|
||||
func fromOSSClientListMultipartsInfo(lmur oss.ListMultipartUploadResult) minio.ListMultipartsInfo {
|
||||
uploads := make([]minio.MultipartInfo, len(lmur.Uploads))
|
||||
for i, um := range lmur.Uploads {
|
||||
uploads[i] = minio.MultipartInfo{
|
||||
Object: um.Key,
|
||||
UploadID: um.UploadID,
|
||||
Initiated: um.Initiated,
|
||||
}
|
||||
}
|
||||
|
||||
commonPrefixes := make([]string, len(lmur.CommonPrefixes))
|
||||
copy(commonPrefixes, lmur.CommonPrefixes)
|
||||
|
||||
return minio.ListMultipartsInfo{
|
||||
KeyMarker: lmur.KeyMarker,
|
||||
UploadIDMarker: lmur.UploadIDMarker,
|
||||
NextKeyMarker: lmur.NextKeyMarker,
|
||||
NextUploadIDMarker: lmur.NextUploadIDMarker,
|
||||
MaxUploads: lmur.MaxUploads,
|
||||
IsTruncated: lmur.IsTruncated,
|
||||
Uploads: uploads,
|
||||
Prefix: lmur.Prefix,
|
||||
Delimiter: lmur.Delimiter,
|
||||
CommonPrefixes: commonPrefixes,
|
||||
}
|
||||
}
|
||||
|
||||
// ListMultipartUploads lists all multipart uploads.
|
||||
func (l *ossObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, err error) {
|
||||
bkt, err := l.Client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return lmi, ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
lmur, err := bkt.ListMultipartUploads(oss.Prefix(prefix), oss.KeyMarker(keyMarker), oss.UploadIDMarker(uploadIDMarker),
|
||||
oss.Delimiter(delimiter), oss.MaxUploads(maxUploads))
|
||||
if err != nil {
|
||||
return lmi, ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return fromOSSClientListMultipartsInfo(lmur), nil
|
||||
}
|
||||
|
||||
// NewMultipartUpload upload object in multiple parts.
|
||||
func (l *ossObjects) NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error) {
|
||||
bkt, err := l.Client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return uploadID, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Build OSS metadata
|
||||
opts, err := appendS3MetaToOSSOptions(nil, metadata)
|
||||
if err != nil {
|
||||
return uploadID, ossToObjectError(err, bucket, object)
|
||||
}
|
||||
|
||||
lmur, err := bkt.InitiateMultipartUpload(object, opts...)
|
||||
if err != nil {
|
||||
return uploadID, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return lmur.UploadID, nil
|
||||
}
|
||||
|
||||
// PutObjectPart puts a part of object in bucket.
|
||||
func (l *ossObjects) PutObjectPart(bucket, object, uploadID string, partID int, data *hash.Reader) (pi minio.PartInfo, err error) {
|
||||
bkt, err := l.Client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return pi, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
imur := oss.InitiateMultipartUploadResult{
|
||||
Bucket: bucket,
|
||||
Key: object,
|
||||
UploadID: uploadID,
|
||||
}
|
||||
size := data.Size()
|
||||
up, err := bkt.UploadPart(imur, data, size, partID)
|
||||
if err != nil {
|
||||
return pi, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return minio.PartInfo{
|
||||
Size: size,
|
||||
ETag: minio.ToS3ETag(up.ETag),
|
||||
// NOTE(timonwong): LastModified is not supported
|
||||
PartNumber: up.PartNumber,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ossBuildListObjectPartsParams(uploadID string, partNumberMarker, maxParts int) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"uploadId": uploadID,
|
||||
"part-number-marker": strconv.Itoa(partNumberMarker),
|
||||
"max-parts": strconv.Itoa(maxParts),
|
||||
}
|
||||
}
|
||||
|
||||
// fromOSSClientListPartsInfo converts OSS ListUploadedPartsResult to ListPartsInfo
|
||||
func fromOSSClientListPartsInfo(lupr oss.ListUploadedPartsResult, partNumberMarker int) minio.ListPartsInfo {
|
||||
parts := make([]minio.PartInfo, len(lupr.UploadedParts))
|
||||
for i, up := range lupr.UploadedParts {
|
||||
parts[i] = minio.PartInfo{
|
||||
PartNumber: up.PartNumber,
|
||||
LastModified: up.LastModified,
|
||||
ETag: minio.ToS3ETag(up.ETag),
|
||||
Size: int64(up.Size),
|
||||
}
|
||||
}
|
||||
|
||||
nextPartNumberMarker, _ := strconv.Atoi(lupr.NextPartNumberMarker)
|
||||
return minio.ListPartsInfo{
|
||||
Bucket: lupr.Bucket,
|
||||
Object: lupr.Key,
|
||||
UploadID: lupr.UploadID,
|
||||
PartNumberMarker: partNumberMarker,
|
||||
NextPartNumberMarker: nextPartNumberMarker,
|
||||
MaxParts: lupr.MaxParts,
|
||||
IsTruncated: lupr.IsTruncated,
|
||||
Parts: parts,
|
||||
}
|
||||
}
|
||||
|
||||
func ossListObjectParts(client *oss.Client, bucket, object, uploadID string, partNumberMarker, maxParts int) (lupr oss.ListUploadedPartsResult, err error) {
|
||||
params := ossBuildListObjectPartsParams(uploadID, partNumberMarker, maxParts)
|
||||
resp, err := client.Conn.Do("GET", bucket, object, params, nil, nil, 0, nil)
|
||||
if err != nil {
|
||||
return lupr, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// always drain output (response body)
|
||||
io.CopyN(ioutil.Discard, resp.Body, 512)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
err = xml.NewDecoder(resp.Body).Decode(&lupr)
|
||||
if err != nil {
|
||||
return lupr, err
|
||||
}
|
||||
return lupr, nil
|
||||
}
|
||||
|
||||
// CopyObjectPart creates a part in a multipart upload by copying
|
||||
// existing object or a part of it.
|
||||
func (l *ossObjects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject, uploadID string,
|
||||
partID int, startOffset, length int64, metadata map[string]string) (p minio.PartInfo, err error) {
|
||||
|
||||
bkt, err := l.Client.Bucket(destBucket)
|
||||
if err != nil {
|
||||
return p, ossToObjectError(errors.Trace(err), destBucket)
|
||||
}
|
||||
|
||||
// Build OSS metadata
|
||||
opts, err := appendS3MetaToOSSOptions(nil, metadata)
|
||||
if err != nil {
|
||||
return p, ossToObjectError(err, srcBucket, srcObject)
|
||||
}
|
||||
|
||||
completePart, err := bkt.UploadPartCopy(oss.InitiateMultipartUploadResult{
|
||||
Key: destObject,
|
||||
UploadID: uploadID,
|
||||
}, srcBucket, srcObject, startOffset, length, partID, opts...)
|
||||
|
||||
if err != nil {
|
||||
return p, ossToObjectError(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
|
||||
p.PartNumber = completePart.PartNumber
|
||||
p.ETag = minio.ToS3ETag(completePart.ETag)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ListObjectParts returns all object parts for specified object in specified bucket
|
||||
func (l *ossObjects) ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (lpi minio.ListPartsInfo, err error) {
|
||||
lupr, err := ossListObjectParts(l.Client, bucket, object, uploadID, partNumberMarker, maxParts)
|
||||
if err != nil {
|
||||
return lpi, ossToObjectError(errors.Trace(err), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
return fromOSSClientListPartsInfo(lupr, partNumberMarker), nil
|
||||
}
|
||||
|
||||
// AbortMultipartUpload aborts a ongoing multipart upload.
|
||||
func (l *ossObjects) AbortMultipartUpload(bucket, object, uploadID string) error {
|
||||
bkt, err := l.Client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
err = bkt.AbortMultipartUpload(oss.InitiateMultipartUploadResult{
|
||||
Bucket: bucket,
|
||||
Key: object,
|
||||
UploadID: uploadID,
|
||||
})
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload completes ongoing multipart upload and finalizes object.
|
||||
func (l *ossObjects) CompleteMultipartUpload(bucket, object, uploadID string, uploadedParts []minio.CompletePart) (oi minio.ObjectInfo, err error) {
|
||||
client := l.Client
|
||||
bkt, err := client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return oi, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// Error out if uploadedParts except last part sizing < 5MiB.
|
||||
// NOTE(timonwong): Actually, OSS wont't throw EntityTooSmall error, doing this check just for mint :(
|
||||
var partNumberMarker int
|
||||
lupr := oss.ListUploadedPartsResult{IsTruncated: true}
|
||||
for lupr.IsTruncated {
|
||||
lupr, err = ossListObjectParts(client, bucket, object, uploadID, partNumberMarker, ossMaxParts)
|
||||
if err != nil {
|
||||
return oi, ossToObjectError(errors.Trace(err), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
uploadedParts := lupr.UploadedParts
|
||||
if !lupr.IsTruncated {
|
||||
if len(uploadedParts) < 1 {
|
||||
uploadedParts = nil
|
||||
} else {
|
||||
uploadedParts = uploadedParts[:len(uploadedParts)-1]
|
||||
}
|
||||
}
|
||||
|
||||
for _, part := range uploadedParts {
|
||||
if part.Size < ossS3MinPartSize {
|
||||
return oi, errors.Trace(minio.PartTooSmall{
|
||||
PartNumber: part.PartNumber,
|
||||
PartSize: int64(part.Size),
|
||||
PartETag: minio.ToS3ETag(part.ETag),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
partNumberMarker, _ = strconv.Atoi(lupr.NextPartNumberMarker)
|
||||
}
|
||||
|
||||
imur := oss.InitiateMultipartUploadResult{
|
||||
Bucket: bucket,
|
||||
Key: object,
|
||||
UploadID: uploadID,
|
||||
}
|
||||
parts := make([]oss.UploadPart, len(uploadedParts))
|
||||
for i, up := range uploadedParts {
|
||||
parts[i] = oss.UploadPart{
|
||||
PartNumber: up.PartNumber,
|
||||
ETag: strings.TrimSuffix(up.ETag, "-1"), // Trim "-1" suffix in ETag as PutObjectPart().
|
||||
}
|
||||
}
|
||||
|
||||
_, err = bkt.CompleteMultipartUpload(imur, parts)
|
||||
if err != nil {
|
||||
return oi, ossToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return l.GetObjectInfo(bucket, object)
|
||||
}
|
||||
|
||||
// SetBucketPolicies sets policy on bucket.
|
||||
// OSS supports three types of bucket policies:
|
||||
// oss.ACLPublicReadWrite: readwrite in minio terminology
|
||||
// oss.ACLPublicRead: readonly in minio terminology
|
||||
// oss.ACLPrivate: none in minio terminology
|
||||
func (l *ossObjects) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error {
|
||||
bucketPolicies := policy.GetPolicies(policyInfo.Statements, bucket)
|
||||
if len(bucketPolicies) != 1 {
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
|
||||
prefix := bucket + "/*" // For all objects inside the bucket.
|
||||
for policyPrefix, bucketPolicy := range bucketPolicies {
|
||||
if policyPrefix != prefix {
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
|
||||
var acl oss.ACLType
|
||||
switch bucketPolicy {
|
||||
case policy.BucketPolicyNone:
|
||||
acl = oss.ACLPrivate
|
||||
case policy.BucketPolicyReadOnly:
|
||||
acl = oss.ACLPublicRead
|
||||
case policy.BucketPolicyReadWrite:
|
||||
acl = oss.ACLPublicReadWrite
|
||||
default:
|
||||
return errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
|
||||
err := l.Client.SetBucketACL(bucket, acl)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBucketPolicies will get policy on bucket.
|
||||
func (l *ossObjects) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) {
|
||||
result, err := l.Client.GetBucketACL(bucket)
|
||||
if err != nil {
|
||||
return policy.BucketAccessPolicy{}, ossToObjectError(errors.Trace(err))
|
||||
}
|
||||
|
||||
policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"}
|
||||
switch result.ACL {
|
||||
case string(oss.ACLPrivate):
|
||||
// By default, all buckets starts with a "private" policy.
|
||||
return policy.BucketAccessPolicy{}, ossToObjectError(errors.Trace(minio.PolicyNotFound{}), bucket)
|
||||
case string(oss.ACLPublicRead):
|
||||
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadOnly, bucket, "")
|
||||
case string(oss.ACLPublicReadWrite):
|
||||
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadWrite, bucket, "")
|
||||
default:
|
||||
return policy.BucketAccessPolicy{}, errors.Trace(minio.NotImplemented{})
|
||||
}
|
||||
|
||||
return policyInfo, nil
|
||||
}
|
||||
|
||||
// DeleteBucketPolicies deletes all policies on bucket.
|
||||
func (l *ossObjects) DeleteBucketPolicies(bucket string) error {
|
||||
err := l.Client.SetBucketACL(bucket, oss.ACLPrivate)
|
||||
if err != nil {
|
||||
return ossToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package oss
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
func ossErrResponse(code string) error {
|
||||
return errors.Trace(oss.ServiceError{
|
||||
Code: code,
|
||||
})
|
||||
}
|
||||
|
||||
func TestOSSToObjectError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
inputErr error
|
||||
expectedErr error
|
||||
bucket, object string
|
||||
}{
|
||||
{
|
||||
inputErr: ossErrResponse("BucketAlreadyExists"),
|
||||
expectedErr: minio.BucketAlreadyOwnedByYou{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("BucketNotEmpty"),
|
||||
expectedErr: minio.BucketNotEmpty{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("InvalidBucketName"),
|
||||
expectedErr: minio.BucketNameInvalid{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("NoSuchBucket"),
|
||||
expectedErr: minio.BucketNotFound{},
|
||||
},
|
||||
// with empty object, NoSuchKey is interpreted as BucketNotFound
|
||||
{
|
||||
inputErr: ossErrResponse("NoSuchKey"),
|
||||
expectedErr: minio.BucketNotFound{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("NoSuchUpload"),
|
||||
expectedErr: minio.InvalidUploadID{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("InvalidObjectName"),
|
||||
expectedErr: minio.ObjectNameInvalid{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("AccessDenied"),
|
||||
expectedErr: minio.PrefixAccessDenied{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("NoSuchUpload"),
|
||||
expectedErr: minio.InvalidUploadID{},
|
||||
},
|
||||
{
|
||||
inputErr: ossErrResponse("EntityTooSmall"),
|
||||
expectedErr: minio.PartTooSmall{},
|
||||
},
|
||||
{
|
||||
inputErr: nil,
|
||||
expectedErr: nil,
|
||||
},
|
||||
// Special test case for NoSuchKey with object name
|
||||
{
|
||||
inputErr: ossErrResponse("NoSuchKey"),
|
||||
expectedErr: minio.ObjectNotFound{Bucket: "bucket", Object: "object"},
|
||||
bucket: "bucket",
|
||||
object: "object",
|
||||
},
|
||||
|
||||
// Special test case for error value that is not of
|
||||
// type (*Error)
|
||||
{
|
||||
inputErr: fmt.Errorf("not a *Error"),
|
||||
expectedErr: fmt.Errorf("not a *Error"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
actualErr := ossToObjectError(tc.inputErr, tc.bucket, tc.object)
|
||||
if e, ok := actualErr.(*errors.Error); ok && e.Cause != tc.expectedErr {
|
||||
t.Errorf("Test case %d: Expected error '%v' but received error '%v'", i+1, tc.expectedErr, e.Cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestS3MetaToOSSOptions(t *testing.T) {
|
||||
var err error
|
||||
var headers map[string]string
|
||||
|
||||
headers = map[string]string{
|
||||
"x-amz-meta-invalid_meta": "value",
|
||||
}
|
||||
_, err = appendS3MetaToOSSOptions(nil, headers)
|
||||
if err = errors.Cause(err); err != nil {
|
||||
if _, ok := err.(minio.UnsupportedMetadata); !ok {
|
||||
t.Fatalf("Test failed with unexpected error %s, expected UnsupportedMetadata", err)
|
||||
}
|
||||
}
|
||||
|
||||
headers = map[string]string{
|
||||
"accept-encoding": "gzip", // not this
|
||||
"content-encoding": "gzip",
|
||||
"X-Amz-Meta-Hdr": "value",
|
||||
"X-Amz-Meta-X-test-key": "value",
|
||||
"X-Amz-Meta-X--test--key": "value",
|
||||
"X-Amz-Meta-X-Amz-Key": "hu3ZSqtqwn+aL4V2VhAeov4i+bG3KyCtRMSXQFRHXOk=",
|
||||
"X-Amz-Meta-X-Amz-Matdesc": "{}",
|
||||
"X-Amz-Meta-X-Amz-Iv": "eWmyryl8kq+EVnnsE7jpOg==",
|
||||
}
|
||||
opts, err := appendS3MetaToOSSOptions(nil, headers)
|
||||
if err != nil {
|
||||
t.Fatalf("Test failed, with %s", err)
|
||||
}
|
||||
if len(opts) != len(headers) {
|
||||
t.Fatalf("Test failed, S3 metdata is not fully transformed. expeted: %d, actual: %d", len(headers)-1, len(opts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSSHeaderToS3Meta(t *testing.T) {
|
||||
meta := map[string]string{
|
||||
"x-oss-meta-first_name": "myname",
|
||||
"X-OSS-Meta-x_test_key": "value",
|
||||
"X-Oss-Meta-x_test__key": "value",
|
||||
"X-Oss-Meta-x__test__key": "value",
|
||||
"X-Oss-Meta-x____test____key": "value",
|
||||
"X-Oss-Meta-x_amz_key": "hu3ZSqtqwn+aL4V2VhAeov4i+bG3KyCtRMSXQFRHXOk=",
|
||||
"X-Oss-Meta-x_amz_matdesc": "{}",
|
||||
"x-oss-meta-x_amz_iv": "eWmyryl8kq+EVnnsE7jpOg==",
|
||||
}
|
||||
header := make(http.Header)
|
||||
for k, v := range meta {
|
||||
header.Set(k, v)
|
||||
}
|
||||
|
||||
expectedMeta := map[string]string{
|
||||
"X-Amz-Meta-First-Name": "myname",
|
||||
"X-Amz-Meta-X-Test-Key": "value",
|
||||
"X-Amz-Meta-X-Test_key": "value",
|
||||
"X-Amz-Meta-X_test_key": "value",
|
||||
"X-Amz-Meta-X__test__key": "value",
|
||||
"X-Amz-Meta-X-Amz-Key": "hu3ZSqtqwn+aL4V2VhAeov4i+bG3KyCtRMSXQFRHXOk=",
|
||||
"X-Amz-Meta-X-Amz-Matdesc": "{}",
|
||||
"X-Amz-Meta-X-Amz-Iv": "eWmyryl8kq+EVnnsE7jpOg==",
|
||||
}
|
||||
actualMeta := ossHeaderToS3Meta(header)
|
||||
for k, v := range expectedMeta {
|
||||
if v2, ok := actualMeta[k]; !ok {
|
||||
t.Errorf("Test failed for key %s: missing key", k)
|
||||
} else if v != v2 {
|
||||
t.Errorf("Test failed for key %s, expected '%s', got '%s'", k, v, v2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSSBuildListObjectPartsParams(t *testing.T) {
|
||||
expected := map[string]interface{}{
|
||||
"uploadId": "test",
|
||||
"part-number-marker": "123",
|
||||
"max-parts": "456",
|
||||
}
|
||||
actual := ossBuildListObjectPartsParams("test", 123, 456)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Test failed, expected %v, got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
@@ -14,86 +14,89 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package s3
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
minio "github.com/minio/minio-go"
|
||||
miniogo "github.com/minio/minio-go"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
// AnonPutObject creates a new object anonymously with the incoming data,
|
||||
func (l *s3Objects) AnonPutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo ObjectInfo, e error) {
|
||||
oi, err := l.anonClient.PutObject(bucket, object, data, data.Size(), data.MD5(), data.SHA256(), toMinioClientMetadata(metadata))
|
||||
func (l *s3Objects) AnonPutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, e error) {
|
||||
oi, err := l.anonClient.PutObject(bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), minio.ToMinioClientMetadata(metadata))
|
||||
if err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return fromMinioClientObjectInfo(bucket, oi), nil
|
||||
return minio.FromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// AnonGetObject - Get object anonymously
|
||||
func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error {
|
||||
opts := minio.GetObjectOptions{}
|
||||
opts := miniogo.GetObjectOptions{}
|
||||
if err := opts.SetRange(startOffset, startOffset+length-1); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
object, _, err := l.anonClient.GetObject(bucket, key, opts)
|
||||
if err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
defer object.Close()
|
||||
|
||||
if _, err := io.CopyN(writer, object, length); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnonGetObjectInfo - Get object info anonymously
|
||||
func (l *s3Objects) AnonGetObjectInfo(bucket string, object string) (objInfo ObjectInfo, e error) {
|
||||
oi, err := l.anonClient.StatObject(bucket, object, minio.StatObjectOptions{})
|
||||
func (l *s3Objects) AnonGetObjectInfo(bucket string, object string) (objInfo minio.ObjectInfo, e error) {
|
||||
oi, err := l.anonClient.StatObject(bucket, object, miniogo.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), bucket, object)
|
||||
return objInfo, minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return fromMinioClientObjectInfo(bucket, oi), nil
|
||||
return minio.FromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// AnonListObjects - List objects anonymously
|
||||
func (l *s3Objects) AnonListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi ListObjectsInfo, e error) {
|
||||
func (l *s3Objects) AnonListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, e error) {
|
||||
result, err := l.anonClient.ListObjects(bucket, prefix, marker, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, s3ToObjectError(traceError(err), bucket)
|
||||
return loi, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return fromMinioClientListBucketResult(bucket, result), nil
|
||||
return minio.FromMinioClientListBucketResult(bucket, result), nil
|
||||
}
|
||||
|
||||
// AnonListObjectsV2 - List objects in V2 mode, anonymously
|
||||
func (l *s3Objects) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi ListObjectsV2Info, e error) {
|
||||
func (l *s3Objects) AnonListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, e error) {
|
||||
result, err := l.anonClient.ListObjectsV2(bucket, prefix, continuationToken, fetchOwner, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, s3ToObjectError(traceError(err), bucket)
|
||||
return loi, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return fromMinioClientListBucketV2Result(bucket, result), nil
|
||||
return minio.FromMinioClientListBucketV2Result(bucket, result), nil
|
||||
}
|
||||
|
||||
// AnonGetBucketInfo - Get bucket metadata anonymously.
|
||||
func (l *s3Objects) AnonGetBucketInfo(bucket string) (bi BucketInfo, e error) {
|
||||
func (l *s3Objects) AnonGetBucketInfo(bucket string) (bi minio.BucketInfo, e error) {
|
||||
if exists, err := l.anonClient.BucketExists(bucket); err != nil {
|
||||
return bi, s3ToObjectError(traceError(err), bucket)
|
||||
return bi, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
} else if !exists {
|
||||
return bi, traceError(BucketNotFound{Bucket: bucket})
|
||||
return bi, errors.Trace(minio.BucketNotFound{Bucket: bucket})
|
||||
}
|
||||
|
||||
buckets, err := l.anonClient.ListBuckets()
|
||||
if err != nil {
|
||||
return bi, s3ToObjectError(traceError(err), bucket)
|
||||
return bi, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
for _, bi := range buckets {
|
||||
@@ -101,11 +104,11 @@ func (l *s3Objects) AnonGetBucketInfo(bucket string) (bi BucketInfo, e error) {
|
||||
continue
|
||||
}
|
||||
|
||||
return BucketInfo{
|
||||
return minio.BucketInfo{
|
||||
Name: bi.Name,
|
||||
Created: bi.CreationDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return bi, traceError(BucketNotFound{Bucket: bucket})
|
||||
return bi, errors.Trace(minio.BucketNotFound{Bucket: bucket})
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package s3
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/minio/cli"
|
||||
miniogo "github.com/minio/minio-go"
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
"github.com/minio/minio-go/pkg/s3utils"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
s3Backend = "s3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const s3GatewayTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [ENDPOINT]
|
||||
{{if .VisibleFlags}}
|
||||
FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
ENDPOINT:
|
||||
S3 server endpoint. Default ENDPOINT is https://s3.amazonaws.com
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
ACCESS:
|
||||
MINIO_ACCESS_KEY: Username or access key of S3 storage.
|
||||
MINIO_SECRET_KEY: Password or secret key of S3 storage.
|
||||
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
UPDATE:
|
||||
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for AWS S3 backend.
|
||||
$ export MINIO_ACCESS_KEY=accesskey
|
||||
$ export MINIO_SECRET_KEY=secretkey
|
||||
$ {{.HelpName}}
|
||||
|
||||
2. Start minio gateway server for S3 backend on custom endpoint.
|
||||
$ export MINIO_ACCESS_KEY=Q3AM3UQ867SPQQA43P2F
|
||||
$ export MINIO_SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
|
||||
$ {{.HelpName}} https://play.minio.io:9000
|
||||
`
|
||||
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: s3Backend,
|
||||
Usage: "Amazon Simple Storage Service (S3).",
|
||||
Action: s3GatewayMain,
|
||||
CustomHelpTemplate: s3GatewayTemplate,
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for 'minio gateway s3' command line.
|
||||
func s3GatewayMain(ctx *cli.Context) {
|
||||
// Validate gateway arguments.
|
||||
host := ctx.Args().First()
|
||||
// Validate gateway arguments.
|
||||
minio.FatalIf(minio.ValidateGatewayArguments(ctx.GlobalString("address"), host), "Invalid argument")
|
||||
|
||||
minio.StartGateway(ctx, &S3{host})
|
||||
}
|
||||
|
||||
// S3 implements Gateway.
|
||||
type S3 struct {
|
||||
host string
|
||||
}
|
||||
|
||||
// Name implements Gateway interface.
|
||||
func (g *S3) Name() string {
|
||||
return s3Backend
|
||||
}
|
||||
|
||||
// NewGatewayLayer returns s3 gatewaylayer.
|
||||
func (g *S3) NewGatewayLayer(creds auth.Credentials) (minio.GatewayLayer, error) {
|
||||
var err error
|
||||
var endpoint string
|
||||
var secure = true
|
||||
|
||||
// Validate host parameters.
|
||||
if g.host != "" {
|
||||
// Override default params if the host is provided
|
||||
endpoint, secure, err = minio.ParseGatewayEndpoint(g.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Default endpoint parameters
|
||||
if endpoint == "" {
|
||||
endpoint = "s3.amazonaws.com"
|
||||
}
|
||||
|
||||
// Initialize minio client object.
|
||||
client, err := miniogo.NewCore(endpoint, creds.AccessKey, creds.SecretKey, secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anonClient, err := miniogo.NewCore(endpoint, "", "", secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
anonClient.SetCustomTransport(minio.NewCustomHTTPTransport())
|
||||
|
||||
return &s3Objects{
|
||||
Client: client,
|
||||
anonClient: anonClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Production - s3 gateway is not production ready.
|
||||
func (g *S3) Production() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// s3Objects implements gateway for Minio and S3 compatible object storage servers.
|
||||
type s3Objects struct {
|
||||
minio.GatewayUnsupported
|
||||
Client *miniogo.Core
|
||||
anonClient *miniogo.Core
|
||||
}
|
||||
|
||||
// Shutdown saves any gateway metadata to disk
|
||||
// if necessary and reload upon next restart.
|
||||
func (l *s3Objects) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageInfo is not relevant to S3 backend.
|
||||
func (l *s3Objects) StorageInfo() (si minio.StorageInfo) {
|
||||
return si
|
||||
}
|
||||
|
||||
// MakeBucket creates a new container on S3 backend.
|
||||
func (l *s3Objects) MakeBucketWithLocation(bucket, location string) error {
|
||||
err := l.Client.MakeBucket(bucket, location)
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetBucketInfo gets bucket metadata..
|
||||
func (l *s3Objects) GetBucketInfo(bucket string) (bi minio.BucketInfo, e error) {
|
||||
// Verify if bucket name is valid.
|
||||
// We are using a separate helper function here to validate bucket
|
||||
// names instead of IsValidBucketName() because there is a possibility
|
||||
// that certains users might have buckets which are non-DNS compliant
|
||||
// in us-east-1 and we might severely restrict them by not allowing
|
||||
// access to these buckets.
|
||||
// Ref - http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
|
||||
if s3utils.CheckValidBucketName(bucket) != nil {
|
||||
return bi, errors.Trace(minio.BucketNameInvalid{Bucket: bucket})
|
||||
}
|
||||
|
||||
buckets, err := l.Client.ListBuckets()
|
||||
if err != nil {
|
||||
return bi, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
for _, bi := range buckets {
|
||||
if bi.Name != bucket {
|
||||
continue
|
||||
}
|
||||
|
||||
return minio.BucketInfo{
|
||||
Name: bi.Name,
|
||||
Created: bi.CreationDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return bi, errors.Trace(minio.BucketNotFound{Bucket: bucket})
|
||||
}
|
||||
|
||||
// ListBuckets lists all S3 buckets
|
||||
func (l *s3Objects) ListBuckets() ([]minio.BucketInfo, error) {
|
||||
buckets, err := l.Client.ListBuckets()
|
||||
if err != nil {
|
||||
return nil, minio.ErrorRespToObjectError(errors.Trace(err))
|
||||
}
|
||||
|
||||
b := make([]minio.BucketInfo, len(buckets))
|
||||
for i, bi := range buckets {
|
||||
b[i] = minio.BucketInfo{
|
||||
Name: bi.Name,
|
||||
Created: bi.CreationDate,
|
||||
}
|
||||
}
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket on S3
|
||||
func (l *s3Objects) DeleteBucket(bucket string) error {
|
||||
err := l.Client.RemoveBucket(bucket)
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListObjects lists all blobs in S3 bucket filtered by prefix
|
||||
func (l *s3Objects) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, e error) {
|
||||
result, err := l.Client.ListObjects(bucket, prefix, marker, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return minio.FromMinioClientListBucketResult(bucket, result), nil
|
||||
}
|
||||
|
||||
// ListObjectsV2 lists all blobs in S3 bucket filtered by prefix
|
||||
func (l *s3Objects) ListObjectsV2(bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, e error) {
|
||||
result, err := l.Client.ListObjectsV2(bucket, prefix, continuationToken, fetchOwner, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, minio.ErrorRespToObjectError(errors.Trace(err), bucket)
|
||||
}
|
||||
|
||||
return minio.FromMinioClientListBucketV2Result(bucket, result), nil
|
||||
}
|
||||
|
||||
// GetObject reads an object from S3. Supports additional
|
||||
// parameters like offset and length which are synonymous with
|
||||
// HTTP Range requests.
|
||||
//
|
||||
// startOffset indicates the starting read location of the object.
|
||||
// length indicates the total length of the object.
|
||||
func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error {
|
||||
if length < 0 && length != -1 {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(minio.InvalidRange{}), bucket, key)
|
||||
}
|
||||
|
||||
opts := miniogo.GetObjectOptions{}
|
||||
if startOffset >= 0 && length >= 0 {
|
||||
if err := opts.SetRange(startOffset, startOffset+length-1); err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
}
|
||||
object, _, err := l.Client.GetObject(bucket, key, opts)
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
defer object.Close()
|
||||
|
||||
if _, err := io.Copy(writer, object); err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo
|
||||
func (l *s3Objects) GetObjectInfo(bucket string, object string) (objInfo minio.ObjectInfo, err error) {
|
||||
oi, err := l.Client.StatObject(bucket, object, miniogo.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return minio.ObjectInfo{}, minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return minio.FromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// PutObject creates a new object with the incoming data,
|
||||
func (l *s3Objects) PutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
oi, err := l.Client.PutObject(bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), minio.ToMinioClientMetadata(metadata))
|
||||
if err != nil {
|
||||
return objInfo, minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return minio.FromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// CopyObject copies an object from source bucket to a destination bucket.
|
||||
func (l *s3Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, dstObject string, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
// Set this header such that following CopyObject() always sets the right metadata on the destination.
|
||||
// metadata input is already a trickled down value from interpreting x-amz-metadata-directive at
|
||||
// handler layer. So what we have right now is supposed to be applied on the destination object anyways.
|
||||
// So preserve it by adding "REPLACE" directive to save all the metadata set by CopyObject API.
|
||||
metadata["x-amz-metadata-directive"] = "REPLACE"
|
||||
if _, err = l.Client.CopyObject(srcBucket, srcObject, dstBucket, dstObject, metadata); err != nil {
|
||||
return objInfo, minio.ErrorRespToObjectError(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
return l.GetObjectInfo(dstBucket, dstObject)
|
||||
}
|
||||
|
||||
// DeleteObject deletes a blob in bucket
|
||||
func (l *s3Objects) DeleteObject(bucket string, object string) error {
|
||||
err := l.Client.RemoveObject(bucket, object)
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListMultipartUploads lists all multipart uploads.
|
||||
func (l *s3Objects) ListMultipartUploads(bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, e error) {
|
||||
result, err := l.Client.ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
|
||||
if err != nil {
|
||||
return lmi, err
|
||||
}
|
||||
|
||||
return minio.FromMinioClientListMultipartsInfo(result), nil
|
||||
}
|
||||
|
||||
// NewMultipartUpload upload object in multiple parts
|
||||
func (l *s3Objects) NewMultipartUpload(bucket string, object string, metadata map[string]string) (uploadID string, err error) {
|
||||
// Create PutObject options
|
||||
opts := miniogo.PutObjectOptions{UserMetadata: metadata}
|
||||
uploadID, err = l.Client.NewMultipartUpload(bucket, object, opts)
|
||||
if err != nil {
|
||||
return uploadID, minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
return uploadID, nil
|
||||
}
|
||||
|
||||
// PutObjectPart puts a part of object in bucket
|
||||
func (l *s3Objects) PutObjectPart(bucket string, object string, uploadID string, partID int, data *hash.Reader) (pi minio.PartInfo, e error) {
|
||||
info, err := l.Client.PutObjectPart(bucket, object, uploadID, partID, data, data.Size(), data.MD5Base64String(), data.SHA256HexString())
|
||||
if err != nil {
|
||||
return pi, minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return minio.FromMinioClientObjectPart(info), nil
|
||||
}
|
||||
|
||||
// CopyObjectPart creates a part in a multipart upload by copying
|
||||
// existing object or a part of it.
|
||||
func (l *s3Objects) CopyObjectPart(srcBucket, srcObject, destBucket, destObject, uploadID string,
|
||||
partID int, startOffset, length int64, metadata map[string]string) (p minio.PartInfo, err error) {
|
||||
|
||||
completePart, err := l.Client.CopyObjectPart(srcBucket, srcObject, destBucket, destObject,
|
||||
uploadID, partID, startOffset, length, metadata)
|
||||
if err != nil {
|
||||
return p, minio.ErrorRespToObjectError(errors.Trace(err), srcBucket, srcObject)
|
||||
}
|
||||
p.PartNumber = completePart.PartNumber
|
||||
p.ETag = completePart.ETag
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ListObjectParts returns all object parts for specified object in specified bucket
|
||||
func (l *s3Objects) ListObjectParts(bucket string, object string, uploadID string, partNumberMarker int, maxParts int) (lpi minio.ListPartsInfo, e error) {
|
||||
result, err := l.Client.ListObjectParts(bucket, object, uploadID, partNumberMarker, maxParts)
|
||||
if err != nil {
|
||||
return lpi, err
|
||||
}
|
||||
|
||||
return minio.FromMinioClientListPartsInfo(result), nil
|
||||
}
|
||||
|
||||
// AbortMultipartUpload aborts a ongoing multipart upload
|
||||
func (l *s3Objects) AbortMultipartUpload(bucket string, object string, uploadID string) error {
|
||||
err := l.Client.AbortMultipartUpload(bucket, object, uploadID)
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload completes ongoing multipart upload and finalizes object
|
||||
func (l *s3Objects) CompleteMultipartUpload(bucket string, object string, uploadID string, uploadedParts []minio.CompletePart) (oi minio.ObjectInfo, e error) {
|
||||
err := l.Client.CompleteMultipartUpload(bucket, object, uploadID, minio.ToMinioClientCompleteParts(uploadedParts))
|
||||
if err != nil {
|
||||
return oi, minio.ErrorRespToObjectError(errors.Trace(err), bucket, object)
|
||||
}
|
||||
|
||||
return l.GetObjectInfo(bucket, object)
|
||||
}
|
||||
|
||||
// SetBucketPolicies sets policy on bucket
|
||||
func (l *s3Objects) SetBucketPolicies(bucket string, policyInfo policy.BucketAccessPolicy) error {
|
||||
if err := l.Client.PutBucketPolicy(bucket, policyInfo); err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBucketPolicies will get policy on bucket
|
||||
func (l *s3Objects) GetBucketPolicies(bucket string) (policy.BucketAccessPolicy, error) {
|
||||
policyInfo, err := l.Client.GetBucketPolicy(bucket)
|
||||
if err != nil {
|
||||
return policy.BucketAccessPolicy{}, minio.ErrorRespToObjectError(errors.Trace(err), bucket, "")
|
||||
}
|
||||
return policyInfo, nil
|
||||
}
|
||||
|
||||
// DeleteBucketPolicies deletes all policies on bucket
|
||||
func (l *s3Objects) DeleteBucketPolicies(bucket string) error {
|
||||
if err := l.Client.PutBucketPolicy(bucket, policy.BucketAccessPolicy{}); err != nil {
|
||||
return minio.ErrorRespToObjectError(errors.Trace(err), bucket, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -14,18 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package s3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
minio "github.com/minio/minio-go"
|
||||
miniogo "github.com/minio/minio-go"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
func errResponse(code string) minio.ErrorResponse {
|
||||
return minio.ErrorResponse{
|
||||
func errResponse(code string) miniogo.ErrorResponse {
|
||||
return miniogo.ErrorResponse{
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
@@ -38,41 +41,41 @@ func TestS3ToObjectError(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
inputErr: errResponse("BucketAlreadyOwnedByYou"),
|
||||
expectedErr: BucketAlreadyOwnedByYou{},
|
||||
expectedErr: minio.BucketAlreadyOwnedByYou{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("BucketNotEmpty"),
|
||||
expectedErr: BucketNotEmpty{},
|
||||
expectedErr: minio.BucketNotEmpty{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("InvalidBucketName"),
|
||||
expectedErr: BucketNameInvalid{},
|
||||
expectedErr: minio.BucketNameInvalid{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("NoSuchBucketPolicy"),
|
||||
expectedErr: PolicyNotFound{},
|
||||
expectedErr: minio.PolicyNotFound{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("NoSuchBucket"),
|
||||
expectedErr: BucketNotFound{},
|
||||
expectedErr: minio.BucketNotFound{},
|
||||
},
|
||||
// with empty Object in minio.ErrorRepsonse, NoSuchKey
|
||||
// with empty Object in miniogo.ErrorRepsonse, NoSuchKey
|
||||
// is interpreted as BucketNotFound
|
||||
{
|
||||
inputErr: errResponse("NoSuchKey"),
|
||||
expectedErr: BucketNotFound{},
|
||||
expectedErr: minio.BucketNotFound{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("NoSuchUpload"),
|
||||
expectedErr: InvalidUploadID{},
|
||||
expectedErr: minio.InvalidUploadID{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("XMinioInvalidObjectName"),
|
||||
expectedErr: ObjectNameInvalid{},
|
||||
expectedErr: minio.ObjectNameInvalid{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("AccessDenied"),
|
||||
expectedErr: PrefixAccessDenied{},
|
||||
expectedErr: minio.PrefixAccessDenied{},
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("XAmzContentSHA256Mismatch"),
|
||||
@@ -80,7 +83,7 @@ func TestS3ToObjectError(t *testing.T) {
|
||||
},
|
||||
{
|
||||
inputErr: errResponse("EntityTooSmall"),
|
||||
expectedErr: PartTooSmall{},
|
||||
expectedErr: minio.PartTooSmall{},
|
||||
},
|
||||
{
|
||||
inputErr: nil,
|
||||
@@ -88,34 +91,37 @@ func TestS3ToObjectError(t *testing.T) {
|
||||
},
|
||||
// Special test case for NoSuchKey with object name
|
||||
{
|
||||
inputErr: minio.ErrorResponse{
|
||||
inputErr: miniogo.ErrorResponse{
|
||||
Code: "NoSuchKey",
|
||||
},
|
||||
expectedErr: ObjectNotFound{},
|
||||
bucket: "bucket",
|
||||
object: "obbject",
|
||||
expectedErr: minio.ObjectNotFound{
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
},
|
||||
bucket: "bucket",
|
||||
object: "object",
|
||||
},
|
||||
|
||||
// N B error values that aren't of expected types
|
||||
// should be left untouched.
|
||||
// Special test case for error that is not of type
|
||||
// minio.ErrorResponse
|
||||
// miniogo.ErrorResponse
|
||||
{
|
||||
inputErr: errors.New("not a minio.ErrorResponse"),
|
||||
expectedErr: errors.New("not a minio.ErrorResponse"),
|
||||
inputErr: fmt.Errorf("not a minio.ErrorResponse"),
|
||||
expectedErr: fmt.Errorf("not a minio.ErrorResponse"),
|
||||
},
|
||||
// Special test case for error value that is not of
|
||||
// type (*Error)
|
||||
{
|
||||
inputErr: errors.New("not a *Error"),
|
||||
expectedErr: errors.New("not a *Error"),
|
||||
inputErr: fmt.Errorf("not a *Error"),
|
||||
expectedErr: fmt.Errorf("not a *Error"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
actualErr := s3ToObjectError(tc.inputErr, tc.bucket, tc.object)
|
||||
if e, ok := actualErr.(*Error); ok && e.e != tc.expectedErr {
|
||||
t.Errorf("Test case %d: Expected error %v but received error %v", i+1, tc.expectedErr, e.e)
|
||||
actualErr := minio.ErrorRespToObjectError(errors.Trace(tc.inputErr), tc.bucket, tc.object)
|
||||
if e, ok := actualErr.(*errors.Error); ok && e.Cause.Error() != tc.expectedErr.Error() {
|
||||
t.Errorf("Test case %d: Expected error %v but received error %v", i+1, tc.expectedErr, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sia
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/fatih/color"
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
siaBackend = "sia"
|
||||
)
|
||||
|
||||
type siaObjects struct {
|
||||
minio.GatewayUnsupported
|
||||
Address string // Address and port of Sia Daemon.
|
||||
TempDir string // Temporary storage location for file transfers.
|
||||
RootDir string // Root directory to store files on Sia.
|
||||
password string // Sia password for uploading content in authenticated manner.
|
||||
}
|
||||
|
||||
func init() {
|
||||
const siaGatewayTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [SIA_DAEMON_ADDR]
|
||||
{{if .VisibleFlags}}
|
||||
FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
ENVIRONMENT VARIABLES: (Default values in parenthesis)
|
||||
ACCESS:
|
||||
MINIO_ACCESS_KEY: Custom access key (Do not reuse same access keys on all instances)
|
||||
MINIO_SECRET_KEY: Custom secret key (Do not reuse same secret keys on all instances)
|
||||
|
||||
BROWSER:
|
||||
MINIO_BROWSER: To disable web browser access, set this value to "off".
|
||||
|
||||
UPDATE:
|
||||
MINIO_UPDATE: To turn off in-place upgrades, set this value to "off".
|
||||
|
||||
SIA_TEMP_DIR: The name of the local Sia temporary storage directory. (.sia_temp)
|
||||
SIA_API_PASSWORD: API password for Sia daemon. (default is empty)
|
||||
|
||||
EXAMPLES:
|
||||
1. Start minio gateway server for Sia backend.
|
||||
$ {{.HelpName}}
|
||||
|
||||
`
|
||||
|
||||
minio.RegisterGatewayCommand(cli.Command{
|
||||
Name: siaBackend,
|
||||
Usage: "Sia Decentralized Cloud.",
|
||||
Action: siaGatewayMain,
|
||||
CustomHelpTemplate: siaGatewayTemplate,
|
||||
HideHelpCommand: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for 'minio gateway sia' command line.
|
||||
func siaGatewayMain(ctx *cli.Context) {
|
||||
// Validate gateway arguments.
|
||||
host := ctx.Args().First()
|
||||
// Validate gateway arguments.
|
||||
minio.FatalIf(minio.ValidateGatewayArguments(ctx.GlobalString("address"), host), "Invalid argument")
|
||||
|
||||
minio.StartGateway(ctx, &Sia{host})
|
||||
}
|
||||
|
||||
// Sia implements Gateway.
|
||||
type Sia struct {
|
||||
host string // Sia daemon host address
|
||||
}
|
||||
|
||||
// Name implements Gateway interface.
|
||||
func (g *Sia) Name() string {
|
||||
return siaBackend
|
||||
}
|
||||
|
||||
// NewGatewayLayer returns Sia gateway layer, implements GatewayLayer interface to
|
||||
// talk to Sia backend.
|
||||
func (g *Sia) NewGatewayLayer(creds auth.Credentials) (minio.GatewayLayer, error) {
|
||||
sia := &siaObjects{
|
||||
Address: g.host,
|
||||
// RootDir uses access key directly, provides partitioning for
|
||||
// concurrent users talking to same sia daemon.
|
||||
RootDir: creds.AccessKey,
|
||||
TempDir: os.Getenv("SIA_TEMP_DIR"),
|
||||
password: os.Getenv("SIA_API_PASSWORD"),
|
||||
}
|
||||
|
||||
// If Address not provided on command line or ENV, default to:
|
||||
if sia.Address == "" {
|
||||
sia.Address = "127.0.0.1:9980"
|
||||
}
|
||||
|
||||
// If local Sia temp directory not specified, default to:
|
||||
if sia.TempDir == "" {
|
||||
sia.TempDir = ".sia_temp"
|
||||
}
|
||||
|
||||
var err error
|
||||
sia.TempDir, err = filepath.Abs(sia.TempDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the temp directory with proper permissions.
|
||||
// Ignore error when dir already exists.
|
||||
if err = os.MkdirAll(sia.TempDir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
colorBlue := color.New(color.FgBlue).SprintfFunc()
|
||||
colorBold := color.New(color.Bold).SprintFunc()
|
||||
|
||||
log.Println(colorBlue("\nSia Gateway Configuration:"))
|
||||
log.Println(colorBlue(" Sia Daemon API Address:") + colorBold(fmt.Sprintf(" %s\n", sia.Address)))
|
||||
log.Println(colorBlue(" Sia Temp Directory:") + colorBold(fmt.Sprintf(" %s\n", sia.TempDir)))
|
||||
return sia, nil
|
||||
}
|
||||
|
||||
// Production - sia gateway is not ready for production use.
|
||||
func (g *Sia) Production() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// non2xx returns true for non-success HTTP status codes.
|
||||
func non2xx(code int) bool {
|
||||
return code < 200 || code > 299
|
||||
}
|
||||
|
||||
// decodeError returns the api.Error from a API response. This method should
|
||||
// only be called if the response's status code is non-2xx. The error returned
|
||||
// may not be of type api.Error in the event of an error unmarshalling the
|
||||
// JSON.
|
||||
type siaError struct {
|
||||
// Message describes the error in English. Typically it is set to
|
||||
// `err.Error()`. This field is required.
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (s siaError) Error() string {
|
||||
return s.Message
|
||||
}
|
||||
|
||||
func decodeError(resp *http.Response) error {
|
||||
// Error is a type that is encoded as JSON and returned in an API response in
|
||||
// the event of an error. Only the Message field is required. More fields may
|
||||
// be added to this struct in the future for better error reporting.
|
||||
var apiErr siaError
|
||||
if err := json.NewDecoder(resp.Body).Decode(&apiErr); err != nil {
|
||||
return err
|
||||
}
|
||||
return apiErr
|
||||
}
|
||||
|
||||
// MethodNotSupported - returned if call returned error.
|
||||
type MethodNotSupported struct {
|
||||
method string
|
||||
}
|
||||
|
||||
func (s MethodNotSupported) Error() string {
|
||||
return fmt.Sprintf("API call not recognized: %s", s.method)
|
||||
}
|
||||
|
||||
// apiGet wraps a GET request with a status code check, such that if the GET does
|
||||
// not return 2xx, the error will be read and returned. The response body is
|
||||
// not closed.
|
||||
func apiGet(addr, call, apiPassword string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("GET", "http://"+addr+call, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
req.Header.Set("User-Agent", "Sia-Agent")
|
||||
if apiPassword != "" {
|
||||
req.SetBasicAuth("", apiPassword)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
resp.Body.Close()
|
||||
return nil, MethodNotSupported{call}
|
||||
}
|
||||
if non2xx(resp.StatusCode) {
|
||||
err := decodeError(resp)
|
||||
resp.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// apiPost wraps a POST request with a status code check, such that if the POST
|
||||
// does not return 2xx, the error will be read and returned. The response body
|
||||
// is not closed.
|
||||
func apiPost(addr, call, vals, apiPassword string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("POST", "http://"+addr+call, strings.NewReader(vals))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Sia-Agent")
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if apiPassword != "" {
|
||||
req.SetBasicAuth("", apiPassword)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
resp.Body.Close()
|
||||
return nil, MethodNotSupported{call}
|
||||
}
|
||||
|
||||
if non2xx(resp.StatusCode) {
|
||||
err := decodeError(resp)
|
||||
resp.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// post makes an API call and discards the response. An error is returned if
|
||||
// the response status is not 2xx.
|
||||
func post(addr, call, vals, apiPassword string) error {
|
||||
resp, err := apiPost(addr, call, vals, apiPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// list makes a lists all the uploaded files, decodes the json response.
|
||||
func list(addr string, apiPassword string, obj *renterFiles) error {
|
||||
resp, err := apiGet(addr, "/renter/files", apiPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return fmt.Errorf("Expecting a response, but API returned %s", resp.Status)
|
||||
}
|
||||
|
||||
return json.NewDecoder(resp.Body).Decode(obj)
|
||||
}
|
||||
|
||||
// get makes an API call and discards the response. An error is returned if the
|
||||
// responsee status is not 2xx.
|
||||
func get(addr, call, apiPassword string) error {
|
||||
resp, err := apiGet(addr, call, apiPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown saves any gateway metadata to disk
|
||||
// if necessary and reload upon next restart.
|
||||
func (s *siaObjects) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageInfo is not relevant to Sia backend.
|
||||
func (s *siaObjects) StorageInfo() (si minio.StorageInfo) {
|
||||
return si
|
||||
}
|
||||
|
||||
// MakeBucket creates a new container on Sia backend.
|
||||
func (s *siaObjects) MakeBucketWithLocation(bucket, location string) error {
|
||||
srcFile := path.Join(s.TempDir, minio.MustGetUUID())
|
||||
defer os.Remove(srcFile)
|
||||
|
||||
writer, err := os.Create(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.Copy(writer, bytes.NewReader([]byte(""))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sha256sum := sha256.Sum256([]byte(bucket))
|
||||
var siaObj = path.Join(s.RootDir, bucket, hex.EncodeToString(sha256sum[:]))
|
||||
return post(s.Address, "/renter/upload/"+siaObj, "source="+srcFile, s.password)
|
||||
}
|
||||
|
||||
// GetBucketInfo gets bucket metadata.
|
||||
func (s *siaObjects) GetBucketInfo(bucket string) (bi minio.BucketInfo, err error) {
|
||||
sha256sum := sha256.Sum256([]byte(bucket))
|
||||
var siaObj = path.Join(s.RootDir, bucket, hex.EncodeToString(sha256sum[:]))
|
||||
|
||||
dstFile := path.Join(s.TempDir, minio.MustGetUUID())
|
||||
defer os.Remove(dstFile)
|
||||
|
||||
if err := get(s.Address, "/renter/download/"+siaObj+"?destination="+url.QueryEscape(dstFile), s.password); err != nil {
|
||||
return bi, err
|
||||
}
|
||||
return minio.BucketInfo{Name: bucket}, nil
|
||||
}
|
||||
|
||||
// ListBuckets will detect and return existing buckets on Sia.
|
||||
func (s *siaObjects) ListBuckets() (buckets []minio.BucketInfo, err error) {
|
||||
sObjs, serr := s.listRenterFiles("")
|
||||
if serr != nil {
|
||||
return buckets, serr
|
||||
}
|
||||
|
||||
m := make(set.StringSet)
|
||||
|
||||
prefix := s.RootDir + "/"
|
||||
for _, sObj := range sObjs {
|
||||
if strings.HasPrefix(sObj.SiaPath, prefix) {
|
||||
siaObj := strings.TrimPrefix(sObj.SiaPath, prefix)
|
||||
idx := strings.Index(siaObj, "/")
|
||||
if idx > 0 {
|
||||
m.Add(siaObj[0:idx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, bktName := range m.ToSlice() {
|
||||
buckets = append(buckets, minio.BucketInfo{
|
||||
Name: bktName,
|
||||
Created: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
}
|
||||
|
||||
return buckets, nil
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket on Sia.
|
||||
func (s *siaObjects) DeleteBucket(bucket string) error {
|
||||
sha256sum := sha256.Sum256([]byte(bucket))
|
||||
var siaObj = path.Join(s.RootDir, bucket, hex.EncodeToString(sha256sum[:]))
|
||||
|
||||
return post(s.Address, "/renter/delete/"+siaObj, "", s.password)
|
||||
}
|
||||
|
||||
func (s *siaObjects) ListObjects(bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, err error) {
|
||||
siaObjs, siaErr := s.listRenterFiles(bucket)
|
||||
if siaErr != nil {
|
||||
return loi, siaErr
|
||||
}
|
||||
|
||||
loi.IsTruncated = false
|
||||
loi.NextMarker = ""
|
||||
|
||||
root := s.RootDir + "/"
|
||||
|
||||
sha256sum := sha256.Sum256([]byte(bucket))
|
||||
// FIXME(harsha) - No paginated output supported for Sia backend right now, only prefix
|
||||
// based filtering. Once list renter files API supports paginated output we can support
|
||||
// paginated results here as well - until then Listing is an expensive operation.
|
||||
for _, sObj := range siaObjs {
|
||||
name := strings.TrimPrefix(sObj.SiaPath, path.Join(root, bucket)+"/")
|
||||
// Skip the file created specially when bucket was created.
|
||||
if name == hex.EncodeToString(sha256sum[:]) {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
loi.Objects = append(loi.Objects, minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: name,
|
||||
Size: int64(sObj.Filesize),
|
||||
IsDir: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
return loi, nil
|
||||
}
|
||||
|
||||
func (s *siaObjects) GetObject(bucket string, object string, startOffset int64, length int64, writer io.Writer) error {
|
||||
dstFile := path.Join(s.TempDir, minio.MustGetUUID())
|
||||
defer os.Remove(dstFile)
|
||||
|
||||
var siaObj = path.Join(s.RootDir, bucket, object)
|
||||
if err := get(s.Address, "/renter/download/"+siaObj+"?destination="+url.QueryEscape(dstFile), s.password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reader, err := os.Open(dstFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
st, err := reader.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size := st.Size()
|
||||
if _, err = reader.Seek(startOffset, os.SEEK_SET); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For negative length we read everything.
|
||||
if length < 0 {
|
||||
length = size - startOffset
|
||||
}
|
||||
|
||||
bufSize := int64(1 * humanize.MiByte)
|
||||
if bufSize > length {
|
||||
bufSize = length
|
||||
}
|
||||
|
||||
// Reply back invalid range if the input offset and length fall out of range.
|
||||
if startOffset > size || startOffset+length > size {
|
||||
return errors.Trace(minio.InvalidRange{
|
||||
OffsetBegin: startOffset,
|
||||
OffsetEnd: length,
|
||||
ResourceSize: size,
|
||||
})
|
||||
}
|
||||
|
||||
// Allocate a staging buffer.
|
||||
buf := make([]byte, int(bufSize))
|
||||
|
||||
_, err = io.CopyBuffer(writer, io.LimitReader(reader, length), buf)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// findSiaObject retrieves the siaObjectInfo for the Sia object with the given
|
||||
// Sia path name.
|
||||
func (s *siaObjects) findSiaObject(bucket, object string) (siaObjectInfo, error) {
|
||||
siaPath := path.Join(s.RootDir, bucket, object)
|
||||
|
||||
sObjs, err := s.listRenterFiles("")
|
||||
if err != nil {
|
||||
return siaObjectInfo{}, err
|
||||
}
|
||||
|
||||
for _, sObj := range sObjs {
|
||||
if sObj.SiaPath == siaPath {
|
||||
return sObj, nil
|
||||
}
|
||||
}
|
||||
|
||||
return siaObjectInfo{}, errors.Trace(minio.ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
})
|
||||
}
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo
|
||||
func (s *siaObjects) GetObjectInfo(bucket string, object string) (minio.ObjectInfo, error) {
|
||||
so, err := s.findSiaObject(bucket, object)
|
||||
if err != nil {
|
||||
return minio.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
// Metadata about sia objects is just quite minimal. Sia only provides file size.
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
ModTime: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Size: int64(so.Filesize),
|
||||
IsDir: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PutObject creates a new object with the incoming data,
|
||||
func (s *siaObjects) PutObject(bucket string, object string, data *hash.Reader, metadata map[string]string) (objInfo minio.ObjectInfo, err error) {
|
||||
srcFile := path.Join(s.TempDir, minio.MustGetUUID())
|
||||
writer, err := os.Create(srcFile)
|
||||
if err != nil {
|
||||
return objInfo, err
|
||||
}
|
||||
|
||||
wsize, err := io.CopyN(writer, data, data.Size())
|
||||
if err != nil {
|
||||
os.Remove(srcFile)
|
||||
return objInfo, err
|
||||
}
|
||||
|
||||
if err = post(s.Address, "/renter/upload/"+path.Join(s.RootDir, bucket, object), "source="+srcFile, s.password); err != nil {
|
||||
os.Remove(srcFile)
|
||||
return objInfo, err
|
||||
}
|
||||
defer s.deleteTempFileWhenUploadCompletes(srcFile, bucket, object)
|
||||
|
||||
return minio.ObjectInfo{
|
||||
Name: object,
|
||||
Bucket: bucket,
|
||||
ModTime: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Size: wsize,
|
||||
ETag: minio.GenETag(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteObject deletes a blob in bucket
|
||||
func (s *siaObjects) DeleteObject(bucket string, object string) error {
|
||||
// Tell Sia daemon to delete the object
|
||||
var siaObj = path.Join(s.RootDir, bucket, object)
|
||||
return post(s.Address, "/renter/delete/"+siaObj, "", s.password)
|
||||
}
|
||||
|
||||
// siaObjectInfo represents object info stored on Sia
|
||||
type siaObjectInfo struct {
|
||||
SiaPath string `json:"siapath"`
|
||||
LocalPath string `json:"localpath"`
|
||||
Filesize uint64 `json:"filesize"`
|
||||
Available bool `json:"available"`
|
||||
Renewing bool `json:"renewing"`
|
||||
Redundancy float64 `json:"redundancy"`
|
||||
UploadProgress float64 `json:"uploadprogress"`
|
||||
}
|
||||
|
||||
type renterFiles struct {
|
||||
Files []siaObjectInfo `json:"files"`
|
||||
}
|
||||
|
||||
// listRenterFiles will return a list of existing objects in the bucket provided
|
||||
func (s *siaObjects) listRenterFiles(bucket string) (siaObjs []siaObjectInfo, err error) {
|
||||
// Get list of all renter files
|
||||
var rf renterFiles
|
||||
if err = list(s.Address, s.password, &rf); err != nil {
|
||||
return siaObjs, err
|
||||
}
|
||||
|
||||
var prefix string
|
||||
root := s.RootDir + "/"
|
||||
if bucket == "" {
|
||||
prefix = root
|
||||
} else {
|
||||
prefix = root + bucket + "/"
|
||||
}
|
||||
|
||||
for _, f := range rf.Files {
|
||||
if strings.HasPrefix(f.SiaPath, prefix) {
|
||||
siaObjs = append(siaObjs, f)
|
||||
}
|
||||
}
|
||||
|
||||
return siaObjs, nil
|
||||
}
|
||||
|
||||
// deleteTempFileWhenUploadCompletes checks the status of a Sia file upload
|
||||
// until it reaches 100% upload progress, then deletes the local temp copy from
|
||||
// the filesystem.
|
||||
func (s *siaObjects) deleteTempFileWhenUploadCompletes(tempFile string, bucket, object string) {
|
||||
var soi siaObjectInfo
|
||||
// Wait until 100% upload instead of 1x redundancy because if we delete
|
||||
// after 1x redundancy, the user has to pay the cost of other hosts
|
||||
// redistributing the file.
|
||||
for soi.UploadProgress < 100.0 {
|
||||
var err error
|
||||
soi, err = s.findSiaObject(bucket, object)
|
||||
if err != nil {
|
||||
minio.ErrorIf(err, "Unable to find file uploaded to Sia path %s/%s", bucket, object)
|
||||
break
|
||||
}
|
||||
|
||||
// Sleep between each check so that we're not hammering
|
||||
// the Sia daemon with requests.
|
||||
time.Sleep(15 * time.Second)
|
||||
}
|
||||
|
||||
os.Remove(tempFile)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sia
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSianon2xx(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
actual := non2xx(i)
|
||||
expected := i < 200 || i > 299
|
||||
|
||||
if actual != expected {
|
||||
t.Errorf("Test case %d: non2xx(%d) returned %t but expected %t", i+1, i, actual, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,16 @@ package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/pkg/sys"
|
||||
"github.com/rs/cors"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// HandlerFunc - useful to chain different middleware http.Handler
|
||||
@@ -554,3 +557,45 @@ func (h pathValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// setRateLimitHandler middleware limits the throughput to h using a
|
||||
// rate.Limiter token bucket configured with maxOpenFileLimit and
|
||||
// burst set to 1. The request will idle for up to 1*time.Second.
|
||||
// If the limiter detects the deadline will be exceeded, the request is
|
||||
// cancelled immediately.
|
||||
func setRateLimitHandler(h http.Handler) http.Handler {
|
||||
_, maxLimit, err := sys.GetMaxOpenFileLimit()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Burst value is set to 1 to allow only maxOpenFileLimit
|
||||
// requests to happen at once.
|
||||
l := rate.NewLimiter(rate.Limit(maxLimit), 1)
|
||||
return rateLimit{l, h}
|
||||
}
|
||||
|
||||
type rateLimit struct {
|
||||
*rate.Limiter
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func (l rateLimit) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// create a new context from the request with the wait timeout
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
|
||||
defer cancel() // always cancel the context!
|
||||
|
||||
// Wait errors out if the request cannot be processed within
|
||||
// the deadline. time/rate tries to reserve a slot if possible
|
||||
// with in the given duration if it's not possible then Wait(ctx)
|
||||
// returns an error and we cancel the request with ErrSlowDown
|
||||
// error message to the client. This context wait also ensures
|
||||
// requests doomed to fail are terminated early, preventing a
|
||||
// potential pileup on the server.
|
||||
if err := l.Wait(ctx); err != nil {
|
||||
// Send an S3 compatible error, SlowDown.
|
||||
writeErrorResponse(w, ErrSlowDown, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
l.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
+14
-5
@@ -53,9 +53,6 @@ const (
|
||||
globalMinioModeDistXL = "mode-server-distributed-xl"
|
||||
globalMinioModeGatewayPrefix = "mode-gateway-"
|
||||
|
||||
// globalMinioSysTmp prefix is used in Azure/GCS gateway for save metadata sent by Initialize Multipart Upload API.
|
||||
globalMinioSysTmp = "minio.sys.tmp/"
|
||||
|
||||
// Add new global values here.
|
||||
)
|
||||
|
||||
@@ -92,9 +89,12 @@ var (
|
||||
// Set to true if credentials were passed from env, default is false.
|
||||
globalIsEnvCreds = false
|
||||
|
||||
// This flag is set to 'true' wen MINIO_REGION env is set.
|
||||
// This flag is set to 'true' when MINIO_REGION env is set.
|
||||
globalIsEnvRegion = false
|
||||
|
||||
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
|
||||
globalInplaceUpdateDisabled = false
|
||||
|
||||
// This flag is set to 'us-east-1' by default
|
||||
globalServerRegion = globalMinioDefaultRegion
|
||||
|
||||
@@ -149,7 +149,6 @@ var (
|
||||
|
||||
globalIsEnvDomainName bool
|
||||
globalDomainName string // Root domain for virtual host style requests
|
||||
// Add new variable global values here.
|
||||
|
||||
globalListingTimeout = newDynamicTimeout( /*30*/ 600*time.Second /*5*/, 600*time.Second) // timeout for listing related ops
|
||||
globalObjectTimeout = newDynamicTimeout( /*1*/ 10*time.Minute /*10*/, 600*time.Second) // timeout for Object API related ops
|
||||
@@ -158,6 +157,16 @@ var (
|
||||
|
||||
// Keep connection active for clients actively using ListenBucketNotification.
|
||||
globalSNSConnAlive = 5 * time.Second // Send a whitespace every 5 seconds.
|
||||
|
||||
// Storage classes
|
||||
// Set to indicate if storage class is set up
|
||||
globalIsStorageClass bool
|
||||
// Set to store reduced redundancy storage class
|
||||
globalRRStorageClass storageClass
|
||||
// Set to store standard storage class
|
||||
globalStandardStorageClass storageClass
|
||||
|
||||
// Add new variable global values here.
|
||||
)
|
||||
|
||||
// global colors.
|
||||
|
||||
+14
-10
@@ -25,6 +25,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
httptracer "github.com/minio/minio/pkg/handlers"
|
||||
)
|
||||
|
||||
@@ -42,7 +43,7 @@ func parseLocationConstraint(r *http.Request) (location string, s3Error APIError
|
||||
} // else for both err as nil or io.EOF
|
||||
location = locationConstraint.Location
|
||||
if location == "" {
|
||||
location = serverConfig.GetRegion()
|
||||
location = globalServerConfig.GetRegion()
|
||||
}
|
||||
return location, ErrNone
|
||||
}
|
||||
@@ -50,7 +51,7 @@ func parseLocationConstraint(r *http.Request) (location string, s3Error APIError
|
||||
// Validates input location is same as configured region
|
||||
// of Minio server.
|
||||
func isValidLocation(location string) bool {
|
||||
return serverConfig.GetRegion() == "" || serverConfig.GetRegion() == location
|
||||
return globalServerConfig.GetRegion() == "" || globalServerConfig.GetRegion() == location
|
||||
}
|
||||
|
||||
// Supported headers that needs to be extracted.
|
||||
@@ -59,6 +60,7 @@ var supportedHeaders = []string{
|
||||
"cache-control",
|
||||
"content-encoding",
|
||||
"content-disposition",
|
||||
amzStorageClass,
|
||||
// Add more supported headers here.
|
||||
}
|
||||
|
||||
@@ -112,10 +114,11 @@ var userMetadataKeyPrefixes = []string{
|
||||
// extractMetadataFromHeader extracts metadata from HTTP header.
|
||||
func extractMetadataFromHeader(header http.Header) (map[string]string, error) {
|
||||
if header == nil {
|
||||
return nil, traceError(errInvalidArgument)
|
||||
return nil, errors.Trace(errInvalidArgument)
|
||||
}
|
||||
metadata := make(map[string]string)
|
||||
// Save standard supported headers.
|
||||
|
||||
// Save all supported headers.
|
||||
for _, supportedHeader := range supportedHeaders {
|
||||
canonicalHeader := http.CanonicalHeaderKey(supportedHeader)
|
||||
// HTTP headers are case insensitive, look for both canonical
|
||||
@@ -126,10 +129,11 @@ func extractMetadataFromHeader(header http.Header) (map[string]string, error) {
|
||||
metadata[supportedHeader] = header.Get(supportedHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// Go through all other headers for any additional headers that needs to be saved.
|
||||
for key := range header {
|
||||
if key != http.CanonicalHeaderKey(key) {
|
||||
return nil, traceError(errInvalidArgument)
|
||||
return nil, errors.Trace(errInvalidArgument)
|
||||
}
|
||||
for _, prefix := range userMetadataKeyPrefixes {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
@@ -187,7 +191,7 @@ func validateFormFieldSize(formValues http.Header) error {
|
||||
for k := range formValues {
|
||||
// Check if value's field exceeds S3 limit
|
||||
if int64(len(formValues.Get(k))) > maxFormFieldSize {
|
||||
return traceError(errSizeUnexpected)
|
||||
return errors.Trace(errSizeUnexpected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +220,7 @@ func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser,
|
||||
canonicalFormName := http.CanonicalHeaderKey(k)
|
||||
if canonicalFormName == "File" {
|
||||
if len(v) == 0 {
|
||||
return nil, "", 0, nil, traceError(errInvalidArgument)
|
||||
return nil, "", 0, nil, errors.Trace(errInvalidArgument)
|
||||
}
|
||||
// Fetch fileHeader which has the uploaded file information
|
||||
fileHeader := v[0]
|
||||
@@ -225,17 +229,17 @@ func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser,
|
||||
// Open the uploaded part
|
||||
filePart, err = fileHeader.Open()
|
||||
if err != nil {
|
||||
return nil, "", 0, nil, traceError(err)
|
||||
return nil, "", 0, nil, errors.Trace(err)
|
||||
}
|
||||
// Compute file size
|
||||
fileSize, err = filePart.(io.Seeker).Seek(0, 2)
|
||||
if err != nil {
|
||||
return nil, "", 0, nil, traceError(err)
|
||||
return nil, "", 0, nil, errors.Trace(err)
|
||||
}
|
||||
// Reset Seek to the beginning
|
||||
_, err = filePart.(io.Seeker).Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, "", 0, nil, traceError(err)
|
||||
return nil, "", 0, nil, errors.Trace(err)
|
||||
}
|
||||
// File found and ready for reading
|
||||
break
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
// Tests validate bucket LocationConstraint.
|
||||
@@ -75,7 +77,7 @@ func TestIsValidLocationContraint(t *testing.T) {
|
||||
if e != nil {
|
||||
t.Fatalf("Test %d: Failed to Marshal bucket configuration", i+1)
|
||||
}
|
||||
serverConfig.SetRegion(testCase.serverConfigRegion)
|
||||
globalServerConfig.SetRegion(testCase.serverConfigRegion)
|
||||
_, actualCode := parseLocationConstraint(inputRequest)
|
||||
if testCase.expectedCode != actualCode {
|
||||
t.Errorf("Test %d: Expected the APIErrCode to be %d, but instead found %d", i+1, testCase.expectedCode, actualCode)
|
||||
@@ -114,7 +116,7 @@ func TestValidateFormFieldSize(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
err := validateFormFieldSize(testCase.header)
|
||||
if err != nil {
|
||||
if errorCause(err).Error() != testCase.err.Error() {
|
||||
if errors.Cause(err).Error() != testCase.err.Error() {
|
||||
t.Errorf("Test %d: Expected error %s, got %s", i+1, testCase.err, err)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -53,7 +53,7 @@ func authenticateJWT(accessKey, secretKey string, expiry time.Duration) (string,
|
||||
return "", err
|
||||
}
|
||||
|
||||
serverCred := serverConfig.GetCredential()
|
||||
serverCred := globalServerConfig.GetCredential()
|
||||
|
||||
if serverCred.AccessKey != passedCredential.AccessKey {
|
||||
return "", errInvalidAccessKeyID
|
||||
@@ -90,7 +90,7 @@ func keyFuncCallback(jwtToken *jwtgo.Token) (interface{}, error) {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"])
|
||||
}
|
||||
|
||||
return []byte(serverConfig.GetCredential().SecretKey), nil
|
||||
return []byte(globalServerConfig.GetCredential().SecretKey), nil
|
||||
}
|
||||
|
||||
func isAuthTokenValid(tokenString string) bool {
|
||||
@@ -107,7 +107,7 @@ func isAuthTokenValid(tokenString string) bool {
|
||||
errorIf(err, "Invalid claims in JWT token string")
|
||||
return false
|
||||
}
|
||||
return jwtToken.Valid && claims.Subject == serverConfig.GetCredential().AccessKey
|
||||
return jwtToken.Valid && claims.Subject == globalServerConfig.GetCredential().AccessKey
|
||||
}
|
||||
|
||||
func isHTTPRequestValid(req *http.Request) bool {
|
||||
@@ -129,7 +129,7 @@ func webRequestAuthenticate(req *http.Request) error {
|
||||
if err = claims.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if claims.Subject != serverConfig.GetCredential().AccessKey {
|
||||
if claims.Subject != globalServerConfig.GetCredential().AccessKey {
|
||||
return errInvalidAccessKeyID
|
||||
}
|
||||
if !jwtToken.Valid {
|
||||
|
||||
+4
-4
@@ -31,7 +31,7 @@ func testAuthenticate(authType string, t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(testPath)
|
||||
cred := auth.MustGetNewCredentials()
|
||||
serverConfig.SetCredential(cred)
|
||||
globalServerConfig.SetCredential(cred)
|
||||
|
||||
// Define test cases.
|
||||
testCases := []struct {
|
||||
@@ -95,7 +95,7 @@ func TestWebRequestAuthenticate(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(testPath)
|
||||
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
token, err := getTokenString(creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("unable get token %s", err)
|
||||
@@ -146,7 +146,7 @@ func BenchmarkAuthenticateNode(b *testing.B) {
|
||||
}
|
||||
defer os.RemoveAll(testPath)
|
||||
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -161,7 +161,7 @@ func BenchmarkAuthenticateWeb(b *testing.B) {
|
||||
}
|
||||
defer os.RemoveAll(testPath)
|
||||
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
+13
-11
@@ -19,6 +19,8 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
type statusType string
|
||||
@@ -116,23 +118,23 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockSource, opsID stri
|
||||
// Check whether the lock info entry for <volume, path> pair already exists.
|
||||
_, ok := n.debugLockMap[param]
|
||||
if !ok {
|
||||
return traceError(LockInfoVolPathMissing{param.volume, param.path})
|
||||
return errors.Trace(LockInfoVolPathMissing{param.volume, param.path})
|
||||
}
|
||||
|
||||
// Check whether lock info entry for the given `opsID` exists.
|
||||
lockInfo, ok := n.debugLockMap[param].lockInfo[opsID]
|
||||
if !ok {
|
||||
return traceError(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
|
||||
return errors.Trace(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
|
||||
}
|
||||
|
||||
// Check whether lockSource is same.
|
||||
if lockInfo.lockSource != lockSource {
|
||||
return traceError(LockInfoOriginMismatch{param.volume, param.path, opsID, lockSource})
|
||||
return errors.Trace(LockInfoOriginMismatch{param.volume, param.path, opsID, lockSource})
|
||||
}
|
||||
|
||||
// Status of the lock should be set to "Blocked".
|
||||
if lockInfo.status != blockedStatus {
|
||||
return traceError(LockInfoStateNotBlocked{param.volume, param.path, opsID})
|
||||
return errors.Trace(LockInfoStateNotBlocked{param.volume, param.path, opsID})
|
||||
}
|
||||
// Change lock status to running and update the time.
|
||||
n.debugLockMap[param].lockInfo[opsID] = newDebugLockInfo(lockSource, runningStatus, readLock)
|
||||
@@ -181,23 +183,23 @@ func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockSource, opsID string,
|
||||
func (n *nsLockMap) statusBlockedToNone(param nsParam, lockSource, opsID string, readLock bool) error {
|
||||
_, ok := n.debugLockMap[param]
|
||||
if !ok {
|
||||
return traceError(LockInfoVolPathMissing{param.volume, param.path})
|
||||
return errors.Trace(LockInfoVolPathMissing{param.volume, param.path})
|
||||
}
|
||||
|
||||
// Check whether lock info entry for the given `opsID` exists.
|
||||
lockInfo, ok := n.debugLockMap[param].lockInfo[opsID]
|
||||
if !ok {
|
||||
return traceError(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
|
||||
return errors.Trace(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
|
||||
}
|
||||
|
||||
// Check whether lockSource is same.
|
||||
if lockInfo.lockSource != lockSource {
|
||||
return traceError(LockInfoOriginMismatch{param.volume, param.path, opsID, lockSource})
|
||||
return errors.Trace(LockInfoOriginMismatch{param.volume, param.path, opsID, lockSource})
|
||||
}
|
||||
|
||||
// Status of the lock should be set to "Blocked".
|
||||
if lockInfo.status != blockedStatus {
|
||||
return traceError(LockInfoStateNotBlocked{param.volume, param.path, opsID})
|
||||
return errors.Trace(LockInfoStateNotBlocked{param.volume, param.path, opsID})
|
||||
}
|
||||
// Clear the status by removing the entry for the given `opsID`.
|
||||
delete(n.debugLockMap[param].lockInfo, opsID)
|
||||
@@ -214,7 +216,7 @@ func (n *nsLockMap) statusBlockedToNone(param nsParam, lockSource, opsID string,
|
||||
func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error {
|
||||
// delete the lock info for the given operation.
|
||||
if _, found := n.debugLockMap[param]; !found {
|
||||
return traceError(LockInfoVolPathMissing{param.volume, param.path})
|
||||
return errors.Trace(LockInfoVolPathMissing{param.volume, param.path})
|
||||
}
|
||||
|
||||
// The following stats update is relevant only in case of a
|
||||
@@ -238,14 +240,14 @@ func (n *nsLockMap) deleteLockInfoEntryForOps(param nsParam, opsID string) error
|
||||
// delete the lock info for the given operation.
|
||||
infoMap, found := n.debugLockMap[param]
|
||||
if !found {
|
||||
return traceError(LockInfoVolPathMissing{param.volume, param.path})
|
||||
return errors.Trace(LockInfoVolPathMissing{param.volume, param.path})
|
||||
}
|
||||
// The operation finished holding the lock on the resource, remove
|
||||
// the entry for the given operation with the operation ID.
|
||||
opsIDLock, foundInfo := infoMap.lockInfo[opsID]
|
||||
if !foundInfo {
|
||||
// Unlock request with invalid operation ID not accepted.
|
||||
return traceError(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
|
||||
return errors.Trace(LockInfoOpsIDNotFound{param.volume, param.path, opsID})
|
||||
}
|
||||
// Update global and (volume, path) lock status.
|
||||
granted := opsIDLock.status == runningStatus
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
package cmd
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
type lockStateCase struct {
|
||||
volume string
|
||||
@@ -278,7 +282,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedErr := LockInfoVolPathMissing{testCases[0].volume, testCases[0].path}
|
||||
if errorCause(actualErr) != expectedErr {
|
||||
if errors.Cause(actualErr) != expectedErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedErr, actualErr)
|
||||
}
|
||||
|
||||
@@ -298,7 +302,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedOpsErr := LockInfoOpsIDNotFound{testCases[0].volume, testCases[0].path, testCases[0].opsID}
|
||||
if errorCause(actualErr) != expectedOpsErr {
|
||||
if errors.Cause(actualErr) != expectedOpsErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedOpsErr, actualErr)
|
||||
}
|
||||
|
||||
@@ -321,7 +325,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedBlockErr := LockInfoStateNotBlocked{testCases[0].volume, testCases[0].path, testCases[0].opsID}
|
||||
if errorCause(actualErr) != expectedBlockErr {
|
||||
if errors.Cause(actualErr) != expectedBlockErr {
|
||||
t.Fatalf("Errors mismatch: Expected: \"%s\", got: \"%s\"", expectedBlockErr, actualErr)
|
||||
}
|
||||
|
||||
@@ -342,7 +346,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
}
|
||||
// invoking the method under test.
|
||||
actualErr = globalNSMutex.statusBlockedToRunning(param, testCase.lockSource, testCase.opsID, testCase.readLock)
|
||||
if errorCause(actualErr) != testCase.expectedErr {
|
||||
if errors.Cause(actualErr) != testCase.expectedErr {
|
||||
t.Fatalf("Test %d: Errors mismatch: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, actualErr)
|
||||
}
|
||||
// In case of no error proceed with validating the lock state information.
|
||||
@@ -461,7 +465,7 @@ func TestNsLockMapStatusNoneToBlocked(t *testing.T) {
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedErr := LockInfoVolPathMissing{testCases[0].volume, testCases[0].path}
|
||||
if errorCause(actualErr) != expectedErr {
|
||||
if errors.Cause(actualErr) != expectedErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedErr, actualErr)
|
||||
}
|
||||
|
||||
@@ -505,7 +509,7 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) {
|
||||
actualErr := globalNSMutex.deleteLockInfoEntryForOps(param, testCases[0].opsID)
|
||||
|
||||
expectedErr := LockInfoVolPathMissing{testCases[0].volume, testCases[0].path}
|
||||
if errorCause(actualErr) != expectedErr {
|
||||
if errors.Cause(actualErr) != expectedErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedErr, actualErr)
|
||||
}
|
||||
|
||||
@@ -524,7 +528,7 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) {
|
||||
actualErr = globalNSMutex.deleteLockInfoEntryForOps(param, "non-existent-OpsID")
|
||||
|
||||
expectedOpsIDErr := LockInfoOpsIDNotFound{param.volume, param.path, "non-existent-OpsID"}
|
||||
if errorCause(actualErr) != expectedOpsIDErr {
|
||||
if errors.Cause(actualErr) != expectedOpsIDErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedOpsIDErr, actualErr)
|
||||
}
|
||||
// case - 4.
|
||||
@@ -588,7 +592,7 @@ func TestNsLockMapDeleteLockInfoEntryForVolumePath(t *testing.T) {
|
||||
param := nsParam{testCases[0].volume, testCases[0].path}
|
||||
actualErr := globalNSMutex.deleteLockInfoEntryForVolumePath(param)
|
||||
expectedNilErr := LockInfoVolPathMissing{param.volume, param.path}
|
||||
if errorCause(actualErr) != expectedNilErr {
|
||||
if errors.Cause(actualErr) != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
"github.com/minio/dsync"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -100,7 +101,7 @@ func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error
|
||||
for _, lockServer := range lockServers {
|
||||
lockRPCServer := newRPCServer()
|
||||
if err := lockRPCServer.RegisterName(lockServiceName, lockServer); err != nil {
|
||||
return traceError(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
lockRouter := mux.PathPrefix(minioReservedBucketPath).Subrouter()
|
||||
lockRouter.Path(path.Join(lockServicePath, lockServer.ll.serviceEndpoint)).Handler(lockRPCServer)
|
||||
@@ -309,7 +310,7 @@ func (l *lockServer) lockMaintenance(interval time.Duration) {
|
||||
nlripLongLived := getLongLivedLocks(l.ll.lockMap, interval)
|
||||
l.ll.mutex.Unlock()
|
||||
|
||||
serverCred := serverConfig.GetCredential()
|
||||
serverCred := globalServerConfig.GetCredential()
|
||||
// Validate if long lived locks are indeed clean.
|
||||
for _, nlrip := range nlripLongLived {
|
||||
// Initialize client based on the long live locks.
|
||||
|
||||
@@ -57,7 +57,7 @@ func createLockTestServer(t *testing.T) (string, *lockServer, string) {
|
||||
lockMap: make(map[string][]lockRequesterInfo),
|
||||
},
|
||||
}
|
||||
creds := serverConfig.GetCredential()
|
||||
creds := globalServerConfig.GetCredential()
|
||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
+4
-51
@@ -17,66 +17,19 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/minio/mc/pkg/console"
|
||||
"github.com/minio/minio/pkg/errors"
|
||||
)
|
||||
|
||||
var log = NewLogger()
|
||||
|
||||
type loggers struct {
|
||||
sync.RWMutex
|
||||
Console ConsoleLogger `json:"console"`
|
||||
File FileLogger `json:"file"`
|
||||
}
|
||||
|
||||
// Validate - Check whether loggers are valid or not.
|
||||
func (l *loggers) Validate() (err error) {
|
||||
if l != nil {
|
||||
fileLogger := l.GetFile()
|
||||
if fileLogger.Enable && fileLogger.Filename == "" {
|
||||
err = errors.New("Missing filename for enabled file logger")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SetFile set new file logger.
|
||||
func (l *loggers) SetFile(flogger FileLogger) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.File = flogger
|
||||
}
|
||||
|
||||
// GetFileLogger get current file logger.
|
||||
func (l *loggers) GetFile() FileLogger {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.File
|
||||
}
|
||||
|
||||
// SetConsole set new console logger.
|
||||
func (l *loggers) SetConsole(clogger ConsoleLogger) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.Console = clogger
|
||||
}
|
||||
|
||||
// GetConsole get current console logger.
|
||||
func (l *loggers) GetConsole() ConsoleLogger {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.Console
|
||||
}
|
||||
|
||||
// LogTarget - interface for log target.
|
||||
type LogTarget interface {
|
||||
Fire(entry *logrus.Entry) error
|
||||
@@ -186,7 +139,7 @@ func getSource() string {
|
||||
|
||||
func logIf(level logrus.Level, source string, err error, msg string, data ...interface{}) {
|
||||
isErrIgnored := func(err error) (ok bool) {
|
||||
err = errorCause(err)
|
||||
err = errors.Cause(err)
|
||||
switch err.(type) {
|
||||
case BucketNotFound, BucketNotEmpty, BucketExists:
|
||||
ok = true
|
||||
@@ -207,8 +160,8 @@ func logIf(level logrus.Level, source string, err error, msg string, data ...int
|
||||
"cause": err.Error(),
|
||||
}
|
||||
|
||||
if terr, ok := err.(*Error); ok {
|
||||
fields["stack"] = strings.Join(terr.Trace(), " ")
|
||||
if terr, ok := err.(*errors.Error); ok {
|
||||
fields["stack"] = strings.Join(terr.Stack(), " ")
|
||||
}
|
||||
|
||||
switch level {
|
||||
|
||||
@@ -53,7 +53,7 @@ type RWLockerSync interface {
|
||||
// Initialize distributed locking only in case of distributed setup.
|
||||
// Returns lock clients and the node index for the current server.
|
||||
func newDsyncNodes(endpoints EndpointList) (clnts []dsync.NetLocker, myNode int) {
|
||||
cred := serverConfig.GetCredential()
|
||||
cred := globalServerConfig.GetCredential()
|
||||
clnts = make([]dsync.NetLocker, len(endpoints))
|
||||
myNode = -1
|
||||
for index, endpoint := range endpoints {
|
||||
|
||||
+9
-9
@@ -68,7 +68,7 @@ func isAMQPQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeAMQP {
|
||||
return false
|
||||
}
|
||||
amqpL := serverConfig.Notify.GetAMQPByID(sqsArn.AccountID)
|
||||
amqpL := globalServerConfig.Notify.GetAMQPByID(sqsArn.AccountID)
|
||||
if !amqpL.Enable {
|
||||
return false
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func isMQTTQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeMQTT {
|
||||
return false
|
||||
}
|
||||
mqttL := serverConfig.Notify.GetMQTTByID(sqsArn.AccountID)
|
||||
mqttL := globalServerConfig.Notify.GetMQTTByID(sqsArn.AccountID)
|
||||
if !mqttL.Enable {
|
||||
return false
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func isNATSQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeNATS {
|
||||
return false
|
||||
}
|
||||
natsL := serverConfig.Notify.GetNATSByID(sqsArn.AccountID)
|
||||
natsL := globalServerConfig.Notify.GetNATSByID(sqsArn.AccountID)
|
||||
if !natsL.Enable {
|
||||
return false
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func isWebhookQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeWebhook {
|
||||
return false
|
||||
}
|
||||
rNotify := serverConfig.Notify.GetWebhookByID(sqsArn.AccountID)
|
||||
rNotify := globalServerConfig.Notify.GetWebhookByID(sqsArn.AccountID)
|
||||
return rNotify.Enable
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func isRedisQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeRedis {
|
||||
return false
|
||||
}
|
||||
rNotify := serverConfig.Notify.GetRedisByID(sqsArn.AccountID)
|
||||
rNotify := globalServerConfig.Notify.GetRedisByID(sqsArn.AccountID)
|
||||
if !rNotify.Enable {
|
||||
return false
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func isElasticQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeElastic {
|
||||
return false
|
||||
}
|
||||
esNotify := serverConfig.Notify.GetElasticSearchByID(sqsArn.AccountID)
|
||||
esNotify := globalServerConfig.Notify.GetElasticSearchByID(sqsArn.AccountID)
|
||||
if !esNotify.Enable {
|
||||
return false
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func isPostgreSQLQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypePostgreSQL {
|
||||
return false
|
||||
}
|
||||
pgNotify := serverConfig.Notify.GetPostgreSQLByID(sqsArn.AccountID)
|
||||
pgNotify := globalServerConfig.Notify.GetPostgreSQLByID(sqsArn.AccountID)
|
||||
if !pgNotify.Enable {
|
||||
return false
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func isMySQLQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeMySQL {
|
||||
return false
|
||||
}
|
||||
msqlNotify := serverConfig.Notify.GetMySQLByID(sqsArn.AccountID)
|
||||
msqlNotify := globalServerConfig.Notify.GetMySQLByID(sqsArn.AccountID)
|
||||
if !msqlNotify.Enable {
|
||||
return false
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func isKafkaQueue(sqsArn arnSQS) bool {
|
||||
if sqsArn.Type != queueTypeKafka {
|
||||
return false
|
||||
}
|
||||
kafkaNotifyCfg := serverConfig.Notify.GetKafkaByID(sqsArn.AccountID)
|
||||
kafkaNotifyCfg := globalServerConfig.Notify.GetKafkaByID(sqsArn.AccountID)
|
||||
if !kafkaNotifyCfg.Enable {
|
||||
return false
|
||||
}
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ func dialAMQP(amqpL amqpNotify) (*amqpConn, error) {
|
||||
}
|
||||
|
||||
func newAMQPNotify(accountID string) (*logrus.Logger, error) {
|
||||
amqpL := serverConfig.Notify.GetAMQPByID(accountID)
|
||||
amqpL := globalServerConfig.Notify.GetAMQPByID(accountID)
|
||||
|
||||
// Connect to amqp server.
|
||||
amqpC, err := dialAMQP(amqpL)
|
||||
|
||||
@@ -75,7 +75,7 @@ func dialElastic(esNotify elasticSearchNotify) (*elastic.Client, error) {
|
||||
}
|
||||
|
||||
func newElasticNotify(accountID string) (*logrus.Logger, error) {
|
||||
esNotify := serverConfig.Notify.GetElasticSearchByID(accountID)
|
||||
esNotify := globalServerConfig.Notify.GetElasticSearchByID(accountID)
|
||||
|
||||
// Dial to elastic search.
|
||||
client, err := dialElastic(esNotify)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user