mirror of
https://github.com/pgsty/minio.git
synced 2026-08-10 08:13:28 +03:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b8255529a | |||
| 019fe69a57 | |||
| d90ab904e7 | |||
| 6ae30b21c9 | |||
| b16781846e | |||
| 5ce82b45da | |||
| e99bc177c0 | |||
| b68bc75dad | |||
| d61eac080b | |||
| d8be9f12a2 | |||
| 0cf4539fe8 | |||
| db6bba709a | |||
| 2174a22835 | |||
| a8dd7b3eda | |||
| 25a55bae6f | |||
| fe157166ca | |||
| f7259adf83 | |||
| 6669560cb9 | |||
| b46ab7e921 | |||
| 27266f8a54 | |||
| 1b6ba0d062 | |||
| 0d0f09ee66 | |||
| f200a7fb6a | |||
| 98691f75bc |
@@ -10293,25 +10293,6 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/inconshreveable/go-update
|
||||
https://github.com/inconshreveable/go-update
|
||||
----------------------------------------------------------------
|
||||
Copyright 2015 Alan Shreve
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/jcmturner/gofork
|
||||
@@ -11470,8 +11451,216 @@ https://github.com/minio/md5-simd
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/minio-go/v6
|
||||
https://github.com/minio/minio-go/v6
|
||||
github.com/minio/minio-go/v7
|
||||
https://github.com/minio/minio-go/v7
|
||||
----------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/selfupdate
|
||||
https://github.com/minio/selfupdate
|
||||
----------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
|
||||
@@ -23,8 +23,7 @@ RUN \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/verify-minio.sh && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/release/CREDITS && \
|
||||
/usr/bin/verify-minio.sh
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ RUN \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/verify-minio.sh && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/release/CREDITS && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||
/usr/bin/verify-minio.sh
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
@@ -22,7 +22,7 @@ RUN \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/verify-minio.sh && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/release/CREDITS && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||
/usr/bin/verify-minio.sh
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ RUN \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/verify-minio.sh && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/release/CREDITS && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||
/usr/bin/verify-minio.sh
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
@@ -22,7 +22,7 @@ RUN \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/verify-minio.sh && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/release/CREDITS && \
|
||||
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
|
||||
/usr/bin/verify-minio.sh
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
+103
-13
@@ -7,29 +7,33 @@ MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合
|
||||
## Docker 容器
|
||||
### 稳定版
|
||||
```
|
||||
docker pull minio/minio
|
||||
docker run -p 9000:9000 minio/minio server /data
|
||||
docker run -p 9000:9000 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
### 尝鲜版
|
||||
```
|
||||
docker pull minio/minio:edge
|
||||
docker run -p 9000:9000 minio/minio:edge server /data
|
||||
docker run -p 9000:9000 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
minio/minio:edge server /data
|
||||
```
|
||||
更多Docker部署信息请访问 [这里](https://docs.min.io/docs/minio-docker-quickstart-guide)
|
||||
|
||||
> 提示:除非你通过`-it`(TTY交互)参数启动容器,否则Docker将不会显示默认的密钥。一般情况下,并不推荐使用容器的默认密钥,更多Docker部署信息请访问 [这里](https://docs.min.io/docs/minio-docker-quickstart-guide)
|
||||
|
||||
## macOS
|
||||
### Homebrew
|
||||
### 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 install minio`安装过minio, 可以用 `minio/stable/minio` 官方镜像进行重装. 由于golang 1.8的bug,homebrew版本不太稳定。
|
||||
```sh
|
||||
brew uninstall minio
|
||||
brew install minio/stable/minio
|
||||
```
|
||||
@@ -49,6 +53,16 @@ chmod 755 minio
|
||||
| ---------- | -------- | ------ |
|
||||
| GNU/Linux | 64-bit Intel | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
||||
```sh
|
||||
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
||||
chmod +x minio
|
||||
./minio server /data
|
||||
```
|
||||
|
||||
| 操作系统 | CPU架构 | 地址 |
|
||||
| ---------- | -------- | ------ |
|
||||
| GNU/Linux | ppc64le | https://dl.min.io/server/minio/release/linux-ppc64le/minio |
|
||||
```sh
|
||||
wget https://dl.min.io/server/minio/release/linux-ppc64le/minio
|
||||
chmod +x minio
|
||||
./minio server /data
|
||||
```
|
||||
@@ -64,7 +78,7 @@ minio.exe server D:\Photos
|
||||
|
||||
## FreeBSD
|
||||
### Port
|
||||
使用 [pkg](https://github.com/freebsd/pkg)进行安装。
|
||||
使用 [pkg](https://github.com/freebsd/pkg)进行安装,, MinIO官方并没有提供FreeBSD二进制文件, 它由FreeBSD上游维护,点击 [这里](https://www.freshports.org/www/minio)查看。
|
||||
|
||||
```sh
|
||||
pkg install minio
|
||||
@@ -75,14 +89,68 @@ service minio start
|
||||
|
||||
## 使用源码安装
|
||||
|
||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install).
|
||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install)。最低需要Golang版本为 [go1.13](https://golang.org/dl/#stable)
|
||||
|
||||
```sh
|
||||
go get -u github.com/minio/minio
|
||||
GO111MODULE=on go get github.com/minio/minio
|
||||
```
|
||||
|
||||
## 为防火墙设置允许访问的端口
|
||||
|
||||
默认情况下,MinIO 使用端口9000来侦听传入的连接。如果你的平台默认阻止了该端口,则需要启用对该端口的访问。
|
||||
|
||||
### ufw
|
||||
|
||||
对于启用了ufw的主机(基于Debian的发行版), 你可以通过`ufw`命令允许指定端口上的所有流量连接. 通过如下命令允许访问端口9000
|
||||
|
||||
```sh
|
||||
ufw allow 9000
|
||||
```
|
||||
|
||||
如下命令允许端口9000-9010上的所有传入流量。
|
||||
|
||||
```sh
|
||||
ufw allow 9000:9010/tcp
|
||||
```
|
||||
|
||||
### firewall-cmd
|
||||
|
||||
对于启用了firewall-cmd的主机(CentOS), 你可以通过`firewall-cmd`命令允许指定端口上的所有流量连接。 通过如下命令允许访问端口9000
|
||||
|
||||
```sh
|
||||
firewall-cmd --get-active-zones
|
||||
```
|
||||
|
||||
这个命令获取当前正在使用的区域。 现在,就可以为以上返回的区域应用端口规则了。 假如返回的区域是 `public`, 使用如下命令
|
||||
|
||||
```sh
|
||||
firewall-cmd --zone=public --add-port=9000/tcp --permanent
|
||||
```
|
||||
|
||||
这里的`permanent`参数表示持久化存储规则,可用于防火墙启动、重启和重新加载。 最后,需要防火墙重新加载,让我们刚刚的修改生效。
|
||||
|
||||
```sh
|
||||
firewall-cmd --reload
|
||||
```
|
||||
|
||||
### iptables
|
||||
|
||||
对于启用了iptables的主机(RHEL, CentOS, etc), 你可以通过`iptables`命令允许指定端口上的所有流量连接。 通过如下命令允许访问端口9000
|
||||
|
||||
```sh
|
||||
iptables -A INPUT -p tcp --dport 9000 -j ACCEPT
|
||||
service iptables restart
|
||||
```
|
||||
|
||||
如下命令允许端口9000-9010上的所有传入流量。
|
||||
|
||||
```sh
|
||||
iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
|
||||
service iptables restart
|
||||
```
|
||||
|
||||
## 使用MinIO浏览器进行验证
|
||||
安装后使用浏览器访问[http://127.0.0.1:9000](http://127.0.0.1:9000),如果可以访问,则表示minio已经安装成功。
|
||||
MinIO Server带有一个嵌入的Web对象浏览器,安装后使用浏览器访问[http://127.0.0.1:9000](http://127.0.0.1:9000),如果可以访问,则表示minio已经安装成功。
|
||||
|
||||

|
||||
|
||||
@@ -94,6 +162,25 @@ go get -u github.com/minio/minio
|
||||
|
||||
上述描述对所有网关后端同样有效。
|
||||
|
||||
## 升级 MinIO
|
||||
MinIO 服务端支持滚动升级, 也就是说你可以一次更新分布式集群中的一个MinIO实例。 这样可以在不停机的情况下进行升级。可以通过将二进制文件替换为最新版本并以滚动方式重新启动所有服务器来手动完成升级。但是, 我们建议所有用户从客户端使用 [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) 命令升级。 这将同时更新集群中的所有节点并重新启动它们, 如下命令所示:
|
||||
|
||||
```
|
||||
mc admin update <minio alias, e.g., myminio>
|
||||
```
|
||||
|
||||
> 注意: 有些发行版可能不允许滚动升级,这通常在发行说明中提到,所以建议在升级之前阅读发行说明。在这种情况下,建议使用`mc admin update`升级机制来一次升级所有服务器。
|
||||
|
||||
### MinIO升级时要记住的重要事项
|
||||
|
||||
- `mc admin update` 命令仅当运行MinIO的用户对二进制文件所在的父目录具有写权限时才工作, 比如当前二进制文件位于`/usr/local/bin/minio`, 你需要具备`/usr/local/bin`目录的写权限.
|
||||
- `mc admin update` 命令同时更新并重新启动所有服务器,应用程序将在升级后重试并继续各自的操作。
|
||||
- `mc admin update` 命令在 kubernetes/container 环境下是不能用的, 容器环境提供了它自己的更新机制来更新。
|
||||
- 对于联盟部署模式,应分别针对每个群集运行`mc admin update`。 在成功更新所有群集之前,不要将`mc`更新为任何新版本。
|
||||
- 如果将`kes`用作MinIO的KMS,只需替换二进制文件并重新启动`kes`,可以在 [这里](https://github.com/minio/kes/wiki) 找到有关`kes`的更多信息。
|
||||
- 如果将Vault作为MinIO的KMS,请确保已遵循如下Vault升级过程的概述:https://www.vaultproject.io/docs/upgrading/index.html
|
||||
- 如果将MindIO与etcd配合使用, 请确保已遵循如下etcd升级过程的概述: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
||||
|
||||
## 了解更多
|
||||
- [MinIO纠删码入门](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
||||
- [`mc`快速入门](https://docs.min.io/docs/minio-client-quickstart-guide)
|
||||
@@ -104,3 +191,6 @@ go get -u github.com/minio/minio
|
||||
|
||||
## 如何参与到MinIO项目
|
||||
请参考 [贡献者指南](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)。欢迎各位中国程序员加到MinIO项目中。
|
||||
|
||||
## 授权许可
|
||||
MinIO的使用受 Apache 2.0 License 约束,你可以在 [LICENSE](./LICENSE) 查看许可。
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -32,8 +33,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
bucketQuotaConfigFile = "quota.json"
|
||||
bucketReplicationTargetsFile = "replication-targets.json"
|
||||
bucketQuotaConfigFile = "quota.json"
|
||||
bucketTargetsFile = "bucket-targets.json"
|
||||
)
|
||||
|
||||
// PutBucketQuotaConfigHandler - PUT Bucket quota configuration.
|
||||
@@ -121,16 +122,16 @@ func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
||||
writeSuccessResponseJSON(w, configData)
|
||||
}
|
||||
|
||||
// SetBucketReplicationTargetHandler - sets a replication target for bucket
|
||||
func (a adminAPIHandlers) SetBucketReplicationTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SetBucketReplicationTarget")
|
||||
// SetBucketTargetHandler - sets a remote target for bucket
|
||||
func (a adminAPIHandlers) SetBucketTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SetBucketTarget")
|
||||
|
||||
defer logger.AuditLog(w, r, "SetBucketReplicationTarget", mustGetClaimsFromToken(r))
|
||||
defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r))
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketReplicationTargetAction)
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||
if objectAPI == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
@@ -139,11 +140,6 @@ func (a adminAPIHandlers) SetBucketReplicationTargetHandler(w http.ResponseWrite
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
// Turn off replication if disk crawl is unavailable.
|
||||
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOff {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketReplicationDisabledError), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if bucket exists.
|
||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
|
||||
@@ -151,17 +147,11 @@ func (a adminAPIHandlers) SetBucketReplicationTargetHandler(w http.ResponseWrite
|
||||
return
|
||||
}
|
||||
|
||||
if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrReplicationBucketNeedsVersioningError), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
password := cred.SecretKey
|
||||
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
@@ -169,58 +159,95 @@ func (a adminAPIHandlers) SetBucketReplicationTargetHandler(w http.ResponseWrite
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
var target madmin.BucketReplicationTarget
|
||||
var target madmin.BucketTarget
|
||||
if err = json.Unmarshal(reqBytes, &target); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
target.Arn = globalBucketReplicationSys.getReplicationARN(target.URL())
|
||||
tgtBytes, err := json.Marshal(&target)
|
||||
host, port, err := net.SplitHostPort(target.Endpoint)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err = globalBucketMetadataSys.Update(bucket, bucketReplicationTargetsFile, tgtBytes); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
sameTarget, _ := isLocalHost(host, port, globalMinioPort)
|
||||
if sameTarget && bucket == target.TargetBucket {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
||||
return
|
||||
}
|
||||
if err = globalBucketReplicationSys.SetTarget(ctx, bucket, &target); err != nil {
|
||||
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
||||
if target.Arn == "" {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
targets, err := globalBucketTargetSys.ListTargets(ctx, bucket)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
}
|
||||
tgtBytes, err := json.Marshal(&targets)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(target.Arn)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
// Write success response.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
writeSuccessResponseJSON(w, data)
|
||||
}
|
||||
|
||||
// GetBucketReplicationTargetsHandler - gets bucket replication targets for a particular bucket
|
||||
func (a adminAPIHandlers) GetBucketReplicationTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetBucketReplicationTarget")
|
||||
// ListBucketTargetsHandler - lists remote target(s) for a bucket or gets a target
|
||||
// for a particular ARN type
|
||||
func (a adminAPIHandlers) ListBucketTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListBucketTargets")
|
||||
|
||||
defer logger.AuditLog(w, r, "GetBucketReplicationTarget", mustGetClaimsFromToken(r))
|
||||
defer logger.AuditLog(w, r, "ListBucketTargets", mustGetClaimsFromToken(r))
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
arnType := vars["type"]
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketReplicationTargetAction)
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
||||
if objectAPI == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
target, err := globalBucketMetadataSys.GetReplicationTargetConfig(bucket)
|
||||
cfg, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// remove secretKey from creds
|
||||
var tgt madmin.BucketReplicationTarget
|
||||
if !target.Empty() {
|
||||
var creds auth.Credentials
|
||||
creds.AccessKey = target.Credentials.AccessKey
|
||||
tgt = madmin.BucketReplicationTarget{Endpoint: target.Endpoint, TargetBucket: target.TargetBucket, Credentials: &creds, Arn: target.Arn}
|
||||
var (
|
||||
targets []madmin.BucketTarget
|
||||
tgt, ct madmin.BucketTarget
|
||||
creds auth.Credentials
|
||||
)
|
||||
if cfg != nil && !cfg.Empty() {
|
||||
for idx, t := range cfg.Targets {
|
||||
if string(t.Type) == arnType || arnType == "" {
|
||||
ct = cfg.Targets[idx]
|
||||
// remove secretKey from creds
|
||||
creds.AccessKey = ct.Credentials.AccessKey
|
||||
tgt = madmin.BucketTarget{Endpoint: ct.Endpoint, Secure: ct.Secure, TargetBucket: ct.TargetBucket, Credentials: &creds, Arn: ct.Arn, Type: ct.Type}
|
||||
targets = append(targets, tgt)
|
||||
}
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(tgt)
|
||||
data, err := json.Marshal(targets)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -229,24 +256,44 @@ func (a adminAPIHandlers) GetBucketReplicationTargetsHandler(w http.ResponseWrit
|
||||
writeSuccessResponseJSON(w, data)
|
||||
}
|
||||
|
||||
// GetBucketReplicationARNHandler - gets replication ARN for a particular remote
|
||||
func (a adminAPIHandlers) GetBucketReplicationARNHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "GetBucketReplicationARN")
|
||||
// RemoveBucketTargetHandler - removes a remote target for bucket with specified ARN
|
||||
func (a adminAPIHandlers) RemoveBucketTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "RemoveBucketTarget")
|
||||
|
||||
defer logger.AuditLog(w, r, "GetBucketReplicationARN", mustGetClaimsFromToken(r))
|
||||
defer logger.AuditLog(w, r, "RemoveBucketTarget", mustGetClaimsFromToken(r))
|
||||
vars := mux.Vars(r)
|
||||
rURL := vars["url"]
|
||||
bucket := vars["bucket"]
|
||||
arn := vars["arn"]
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetBucketReplicationTargetAction)
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
||||
if objectAPI == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
data, err := json.Marshal(globalBucketReplicationSys.getARN(rURL))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
if !globalIsErasure {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
if err := globalBucketTargetSys.RemoveTarget(ctx, bucket, arn); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
targets, err := globalBucketTargetSys.ListTargets(ctx, bucket)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
tgtBytes, err := json.Marshal(&targets)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseJSON(w, data)
|
||||
writeSuccessNoContent(w)
|
||||
}
|
||||
|
||||
+21
-8
@@ -695,7 +695,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Start writing response to client
|
||||
started = true
|
||||
setCommonHeaders(w)
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
// Set 200 OK status
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
@@ -995,7 +995,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
|
||||
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
@@ -1064,7 +1064,8 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
||||
// This is needed to make r.Context().Done() work as
|
||||
// expected in case of read timeout
|
||||
w.Header().Add("Connection", "close")
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
|
||||
setEventStreamHeaders(w)
|
||||
|
||||
logCh := make(chan interface{}, 4000)
|
||||
|
||||
@@ -1223,7 +1224,9 @@ func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
setCommonHeaders(w)
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
|
||||
setEventStreamHeaders(w)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
errResp := func(err error) {
|
||||
@@ -1628,9 +1631,6 @@ func fetchLoggerInfo(cfg config.Config) ([]madmin.Logger, []madmin.Audit) {
|
||||
|
||||
// checkConnection - ping an endpoint , return err in case of no connection
|
||||
func checkConnection(endpointStr string, timeout time.Duration) error {
|
||||
tr := newCustomHTTPTransport(&tls.Config{RootCAs: globalRootCAs}, timeout)()
|
||||
defer tr.CloseIdleConnections()
|
||||
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, timeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -1639,7 +1639,20 @@ func checkConnection(endpointStr string, timeout time.Duration) error {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
client := &http.Client{Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: xhttp.NewCustomDialContext(timeout),
|
||||
ResponseHeaderTimeout: 5 * time.Second,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
ExpectContinueTimeout: 5 * time.Second,
|
||||
TLSClientConfig: &tls.Config{RootCAs: globalRootCAs},
|
||||
// Go net/http automatically unzip if content-type is
|
||||
// gzip disable this feature, as we are always interested
|
||||
// in raw stream.
|
||||
DisableCompression: true,
|
||||
}}
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+9
-9
@@ -182,15 +182,15 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
||||
httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||
}
|
||||
// Bucket replication operations
|
||||
// GetBucketReplicationTargetHandler
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-replication-target").HandlerFunc(
|
||||
httpTraceHdrs(adminAPI.GetBucketReplicationTargetsHandler)).Queries("bucket", "{bucket:.*}")
|
||||
// GetBucketReplicationARN Handler
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-replication-arn").HandlerFunc(
|
||||
httpTraceHdrs(adminAPI.GetBucketReplicationARNHandler)).Queries("url", "{url:.*}")
|
||||
// SetBucketReplicationTargetHandler
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-replication-target").HandlerFunc(
|
||||
httpTraceHdrs(adminAPI.SetBucketReplicationTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||
// GetBucketTargetHandler
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-bucket-targets").HandlerFunc(
|
||||
httpTraceHdrs(adminAPI.ListBucketTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
||||
// SetBucketTargetHandler
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-target").HandlerFunc(
|
||||
httpTraceHdrs(adminAPI.SetBucketTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||
// SetBucketTargetHandler
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-bucket-target").HandlerFunc(
|
||||
httpTraceHdrs(adminAPI.RemoveBucketTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||
}
|
||||
|
||||
// -- Top APIs --
|
||||
|
||||
+41
-3
@@ -108,6 +108,11 @@ const (
|
||||
ErrReplicationConfigurationNotFoundError
|
||||
ErrReplicationDestinationNotFoundError
|
||||
ErrReplicationTargetNotFoundError
|
||||
ErrBucketRemoteIdenticalToSource
|
||||
ErrBucketRemoteAlreadyExists
|
||||
ErrBucketRemoteArnTypeInvalid
|
||||
ErrBucketRemoteArnInvalid
|
||||
ErrBucketRemoteRemoveDisallowed
|
||||
ErrReplicationTargetNotVersionedError
|
||||
ErrReplicationNeedsVersioningError
|
||||
ErrReplicationBucketNeedsVersioningError
|
||||
@@ -826,14 +831,39 @@ var errorCodes = errorCodeMap{
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrReplicationTargetNotFoundError: {
|
||||
Code: "ReplicationTargetNotFoundError",
|
||||
Code: "XminioAdminReplicationTargetNotFoundError",
|
||||
Description: "The replication target does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrBucketRemoteIdenticalToSource: {
|
||||
Code: "XminioAdminRemoteIdenticalToSource",
|
||||
Description: "The remote target cannot be identical to source",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrBucketRemoteAlreadyExists: {
|
||||
Code: "XminioAdminBucketRemoteAlreadyExists",
|
||||
Description: "The remote target already exists",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrBucketRemoteRemoveDisallowed: {
|
||||
Code: "XMinioAdminRemoteRemoveDisallowed",
|
||||
Description: "Replication configuration exists with this ARN.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrBucketRemoteArnTypeInvalid: {
|
||||
Code: "XMinioAdminRemoteARNTypeInvalid",
|
||||
Description: "The bucket remote ARN type is not valid",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrBucketRemoteArnInvalid: {
|
||||
Code: "XMinioAdminRemoteArnInvalid",
|
||||
Description: "The bucket remote ARN does not have correct format",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrReplicationTargetNotVersionedError: {
|
||||
Code: "ReplicationTargetNotVersionedError",
|
||||
Description: "The replication target does not have versioning enabled",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrReplicationNeedsVersioningError: {
|
||||
Code: "InvalidRequest",
|
||||
@@ -1879,8 +1909,16 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrReplicationConfigurationNotFoundError
|
||||
case BucketReplicationDestinationNotFound:
|
||||
apiErr = ErrReplicationDestinationNotFoundError
|
||||
case BucketReplicationTargetNotFound:
|
||||
case BucketRemoteTargetNotFound:
|
||||
apiErr = ErrReplicationTargetNotFoundError
|
||||
case BucketRemoteAlreadyExists:
|
||||
apiErr = ErrBucketRemoteAlreadyExists
|
||||
case BucketRemoteArnTypeInvalid:
|
||||
apiErr = ErrBucketRemoteArnTypeInvalid
|
||||
case BucketRemoteArnInvalid:
|
||||
apiErr = ErrBucketRemoteArnInvalid
|
||||
case BucketRemoteRemoveDisallowed:
|
||||
apiErr = ErrBucketRemoteRemoveDisallowed
|
||||
case BucketReplicationTargetNotVersioned:
|
||||
apiErr = ErrReplicationTargetNotVersionedError
|
||||
case BucketQuotaExceeded:
|
||||
|
||||
@@ -38,6 +38,13 @@ func mustGetRequestID(t time.Time) string {
|
||||
return fmt.Sprintf("%X", t.UnixNano())
|
||||
}
|
||||
|
||||
// setEventStreamHeaders to allow proxies to avoid buffering proxy responses
|
||||
func setEventStreamHeaders(w http.ResponseWriter) {
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
w.Header().Set(xhttp.CacheControl, "no-cache") // nginx to turn off buffering
|
||||
w.Header().Set("X-Accel-Buffering", "no") // nginx to turn off buffering
|
||||
}
|
||||
|
||||
// Write http common headers
|
||||
func setCommonHeaders(w http.ResponseWriter) {
|
||||
w.Header().Set(xhttp.ServerInfo, "MinIO/"+ReleaseTag)
|
||||
|
||||
@@ -228,7 +228,7 @@ func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
|
||||
}
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
|
||||
|
||||
@@ -1271,7 +1271,7 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
sameTarget, err := globalBucketReplicationSys.validateDestination(ctx, bucket, replicationConfig)
|
||||
sameTarget, err := validateReplicationDestination(ctx, bucket, replicationConfig)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
@@ -156,8 +156,8 @@ func (sys *BucketMetadataSys) Update(bucket string, configFile string, configDat
|
||||
meta.QuotaConfigJSON = configData
|
||||
case bucketReplicationConfig:
|
||||
meta.ReplicationConfigXML = configData
|
||||
case bucketReplicationTargetsFile:
|
||||
meta.ReplicationTargetsConfigJSON = configData
|
||||
case bucketTargetsFile:
|
||||
meta.BucketTargetsConfigJSON = configData
|
||||
default:
|
||||
return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
|
||||
}
|
||||
@@ -340,17 +340,17 @@ func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket s
|
||||
return meta.replicationConfig, nil
|
||||
}
|
||||
|
||||
// GetReplicationTargetConfig returns configured bucket replication target for this bucket
|
||||
// GetBucketTargetsConfig returns configured bucket targets for this bucket
|
||||
// The returned object may not be modified.
|
||||
func (sys *BucketMetadataSys) GetReplicationTargetConfig(bucket string) (*madmin.BucketReplicationTarget, error) {
|
||||
func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.BucketTargets, error) {
|
||||
meta, err := sys.GetConfig(bucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if meta.replicationTargetConfig == nil {
|
||||
return nil, BucketReplicationTargetNotFound{Bucket: bucket}
|
||||
if meta.bucketTargetConfig == nil {
|
||||
return nil, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||
}
|
||||
return meta.replicationTargetConfig, nil
|
||||
return meta.bucketTargetConfig, nil
|
||||
}
|
||||
|
||||
// GetConfig returns a specific configuration from the bucket metadata.
|
||||
|
||||
+36
-30
@@ -61,31 +61,31 @@ var (
|
||||
// bucketMetadataFormat refers to the format.
|
||||
// bucketMetadataVersion can be used to track a rolling upgrade of a field.
|
||||
type BucketMetadata struct {
|
||||
Name string
|
||||
Created time.Time
|
||||
LockEnabled bool // legacy not used anymore.
|
||||
PolicyConfigJSON []byte
|
||||
NotificationConfigXML []byte
|
||||
LifecycleConfigXML []byte
|
||||
ObjectLockConfigXML []byte
|
||||
VersioningConfigXML []byte
|
||||
EncryptionConfigXML []byte
|
||||
TaggingConfigXML []byte
|
||||
QuotaConfigJSON []byte
|
||||
ReplicationConfigXML []byte
|
||||
ReplicationTargetsConfigJSON []byte
|
||||
Name string
|
||||
Created time.Time
|
||||
LockEnabled bool // legacy not used anymore.
|
||||
PolicyConfigJSON []byte
|
||||
NotificationConfigXML []byte
|
||||
LifecycleConfigXML []byte
|
||||
ObjectLockConfigXML []byte
|
||||
VersioningConfigXML []byte
|
||||
EncryptionConfigXML []byte
|
||||
TaggingConfigXML []byte
|
||||
QuotaConfigJSON []byte
|
||||
ReplicationConfigXML []byte
|
||||
BucketTargetsConfigJSON []byte
|
||||
|
||||
// Unexported fields. Must be updated atomically.
|
||||
policyConfig *policy.Policy
|
||||
notificationConfig *event.Config
|
||||
lifecycleConfig *lifecycle.Lifecycle
|
||||
objectLockConfig *objectlock.Config
|
||||
versioningConfig *versioning.Versioning
|
||||
sseConfig *bucketsse.BucketSSEConfig
|
||||
taggingConfig *tags.Tags
|
||||
quotaConfig *madmin.BucketQuota
|
||||
replicationConfig *replication.Config
|
||||
replicationTargetConfig *madmin.BucketReplicationTarget
|
||||
policyConfig *policy.Policy
|
||||
notificationConfig *event.Config
|
||||
lifecycleConfig *lifecycle.Lifecycle
|
||||
objectLockConfig *objectlock.Config
|
||||
versioningConfig *versioning.Versioning
|
||||
sseConfig *bucketsse.BucketSSEConfig
|
||||
taggingConfig *tags.Tags
|
||||
quotaConfig *madmin.BucketQuota
|
||||
replicationConfig *replication.Config
|
||||
bucketTargetConfig *madmin.BucketTargets
|
||||
}
|
||||
|
||||
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
|
||||
@@ -100,7 +100,7 @@ func newBucketMetadata(name string) BucketMetadata {
|
||||
versioningConfig: &versioning.Versioning{
|
||||
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
|
||||
},
|
||||
replicationTargetConfig: &madmin.BucketReplicationTarget{},
|
||||
bucketTargetConfig: &madmin.BucketTargets{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,12 +227,12 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa
|
||||
b.replicationConfig = nil
|
||||
}
|
||||
|
||||
if len(b.ReplicationTargetsConfigJSON) != 0 {
|
||||
if err = json.Unmarshal(b.ReplicationTargetsConfigJSON, b.replicationTargetConfig); err != nil {
|
||||
if len(b.BucketTargetsConfigJSON) != 0 {
|
||||
if err = json.Unmarshal(b.BucketTargetsConfigJSON, b.bucketTargetConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
b.replicationTargetConfig = &madmin.BucketReplicationTarget{}
|
||||
b.bucketTargetConfig = &madmin.BucketTargets{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
||||
bucketSSEConfig,
|
||||
bucketTaggingConfig,
|
||||
bucketReplicationConfig,
|
||||
bucketReplicationTargetsFile,
|
||||
bucketTargetsFile,
|
||||
objectLockConfig,
|
||||
}
|
||||
|
||||
@@ -265,6 +265,12 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
||||
|
||||
configData, err := readConfig(ctx, objectAPI, configFile)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case ObjectExistsAsDirectory:
|
||||
// in FS mode it possible that we have actual
|
||||
// files in this folder with `.minio.sys/buckets/bucket/configFile`
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, errConfigNotFound) {
|
||||
// legacy file config not found, proceed to look for new metadata.
|
||||
continue
|
||||
@@ -306,8 +312,8 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
||||
b.QuotaConfigJSON = configData
|
||||
case bucketReplicationConfig:
|
||||
b.ReplicationConfigXML = configData
|
||||
case bucketReplicationTargetsFile:
|
||||
b.ReplicationTargetsConfigJSON = configData
|
||||
case bucketTargetsFile:
|
||||
b.BucketTargetsConfigJSON = configData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -96,10 +96,10 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "ReplicationConfigXML")
|
||||
return
|
||||
}
|
||||
case "ReplicationTargetsConfigJSON":
|
||||
z.ReplicationTargetsConfigJSON, err = dc.ReadBytes(z.ReplicationTargetsConfigJSON)
|
||||
case "BucketTargetsConfigJSON":
|
||||
z.BucketTargetsConfigJSON, err = dc.ReadBytes(z.BucketTargetsConfigJSON)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "ReplicationTargetsConfigJSON")
|
||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||
return
|
||||
}
|
||||
default:
|
||||
@@ -236,14 +236,14 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "ReplicationConfigXML")
|
||||
return
|
||||
}
|
||||
// write "ReplicationTargetsConfigJSON"
|
||||
err = en.Append(0xbc, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
||||
// write "BucketTargetsConfigJSON"
|
||||
err = en.Append(0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteBytes(z.ReplicationTargetsConfigJSON)
|
||||
err = en.WriteBytes(z.BucketTargetsConfigJSON)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "ReplicationTargetsConfigJSON")
|
||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||
return
|
||||
}
|
||||
return
|
||||
@@ -289,9 +289,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "ReplicationConfigXML"
|
||||
o = append(o, 0xb4, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x58, 0x4d, 0x4c)
|
||||
o = msgp.AppendBytes(o, z.ReplicationConfigXML)
|
||||
// string "ReplicationTargetsConfigJSON"
|
||||
o = append(o, 0xbc, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
||||
o = msgp.AppendBytes(o, z.ReplicationTargetsConfigJSON)
|
||||
// string "BucketTargetsConfigJSON"
|
||||
o = append(o, 0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
|
||||
o = msgp.AppendBytes(o, z.BucketTargetsConfigJSON)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -385,10 +385,10 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "ReplicationConfigXML")
|
||||
return
|
||||
}
|
||||
case "ReplicationTargetsConfigJSON":
|
||||
z.ReplicationTargetsConfigJSON, bts, err = msgp.ReadBytesBytes(bts, z.ReplicationTargetsConfigJSON)
|
||||
case "BucketTargetsConfigJSON":
|
||||
z.BucketTargetsConfigJSON, bts, err = msgp.ReadBytesBytes(bts, z.BucketTargetsConfigJSON)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "ReplicationTargetsConfigJSON")
|
||||
err = msgp.WrapError(err, "BucketTargetsConfigJSON")
|
||||
return
|
||||
}
|
||||
default:
|
||||
@@ -405,6 +405,6 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *BucketMetadata) Msgsize() (s int) {
|
||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 29 + msgp.BytesPrefixSize + len(z.ReplicationTargetsConfigJSON)
|
||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON)
|
||||
return
|
||||
}
|
||||
|
||||
+16
-162
@@ -18,35 +18,22 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/replication"
|
||||
"github.com/minio/minio/pkg/bucket/versioning"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// BucketReplicationSys represents replication subsystem
|
||||
type BucketReplicationSys struct {
|
||||
sync.RWMutex
|
||||
targetsMap map[string]*miniogo.Core
|
||||
targetsARNMap map[string]string
|
||||
}
|
||||
|
||||
// GetConfig - gets replication config associated to a given bucket name.
|
||||
func (sys *BucketReplicationSys) GetConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||
// gets replication config associated to a given bucket name.
|
||||
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||
if globalIsGateway {
|
||||
objAPI := newObjectLayerWithoutSafeModeFn()
|
||||
if objAPI == nil {
|
||||
@@ -59,153 +46,29 @@ func (sys *BucketReplicationSys) GetConfig(ctx context.Context, bucketName strin
|
||||
return globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName)
|
||||
}
|
||||
|
||||
// SetTarget - sets a new minio-go client replication target for this bucket.
|
||||
func (sys *BucketReplicationSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketReplicationTarget) error {
|
||||
if globalIsGateway {
|
||||
return nil
|
||||
}
|
||||
// delete replication targets that were removed
|
||||
if tgt.Empty() {
|
||||
sys.Lock()
|
||||
if currTgt, ok := sys.targetsMap[bucket]; ok {
|
||||
delete(sys.targetsARNMap, currTgt.EndpointURL().String())
|
||||
}
|
||||
delete(sys.targetsMap, bucket)
|
||||
sys.Unlock()
|
||||
return nil
|
||||
}
|
||||
clnt, err := getReplicationTargetClient(tgt)
|
||||
if err != nil {
|
||||
return BucketReplicationTargetNotFound{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
ok, err := clnt.BucketExists(ctx, tgt.TargetBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return BucketReplicationDestinationNotFound{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
||||
if err != nil || vcfg.Status != string(versioning.Enabled) {
|
||||
return BucketReplicationTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
sys.Lock()
|
||||
sys.targetsMap[bucket] = clnt
|
||||
sys.targetsARNMap[tgt.URL()] = tgt.Arn
|
||||
sys.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTargetClient returns minio-go client for target instance
|
||||
func (sys *BucketReplicationSys) GetTargetClient(ctx context.Context, bucket string) *miniogo.Core {
|
||||
var clnt *miniogo.Core
|
||||
sys.RLock()
|
||||
if c, ok := sys.targetsMap[bucket]; ok {
|
||||
clnt = c
|
||||
}
|
||||
sys.RUnlock()
|
||||
return clnt
|
||||
}
|
||||
|
||||
// validateDestination returns error if replication destination bucket missing or not configured
|
||||
// validateReplicationDestination returns error if replication destination bucket missing or not configured
|
||||
// It also returns true if replication destination is same as this server.
|
||||
func (sys *BucketReplicationSys) validateDestination(ctx context.Context, bucket string, rCfg *replication.Config) (bool, error) {
|
||||
clnt := sys.GetTargetClient(ctx, bucket)
|
||||
func validateReplicationDestination(ctx context.Context, bucket string, rCfg *replication.Config) (bool, error) {
|
||||
clnt := globalBucketTargetSys.GetReplicationTargetClient(ctx, rCfg.ReplicationArn)
|
||||
if clnt == nil {
|
||||
return false, BucketReplicationTargetNotFound{Bucket: bucket}
|
||||
return false, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||
}
|
||||
if found, _ := clnt.BucketExists(ctx, rCfg.GetDestination().Bucket); !found {
|
||||
return false, BucketReplicationDestinationNotFound{Bucket: rCfg.GetDestination().Bucket}
|
||||
}
|
||||
// validate replication ARN against target endpoint
|
||||
for k, v := range sys.targetsARNMap {
|
||||
if v == rCfg.ReplicationArn {
|
||||
if k == clnt.EndpointURL().String() {
|
||||
sameTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort)
|
||||
return sameTarget, nil
|
||||
}
|
||||
c, ok := globalBucketTargetSys.arnRemotesMap[rCfg.ReplicationArn]
|
||||
if ok {
|
||||
if c.EndpointURL().String() == clnt.EndpointURL().String() {
|
||||
sameTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort)
|
||||
return sameTarget, nil
|
||||
}
|
||||
}
|
||||
return false, BucketReplicationTargetNotFound{Bucket: bucket}
|
||||
}
|
||||
|
||||
// NewBucketReplicationSys - creates new replication system.
|
||||
func NewBucketReplicationSys() *BucketReplicationSys {
|
||||
return &BucketReplicationSys{
|
||||
targetsMap: make(map[string]*miniogo.Core),
|
||||
targetsARNMap: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the bucket replication subsystem for buckets with replication config
|
||||
func (sys *BucketReplicationSys) Init(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
// In gateway mode, replication is not supported.
|
||||
if globalIsGateway {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load bucket replication targets once during boot.
|
||||
sys.load(ctx, buckets, objAPI)
|
||||
return nil
|
||||
}
|
||||
|
||||
// create minio-go clients for buckets having replication targets
|
||||
func (sys *BucketReplicationSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
||||
for _, bucket := range buckets {
|
||||
tgt, err := globalBucketMetadataSys.GetReplicationTargetConfig(bucket.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if tgt == nil || tgt.Empty() {
|
||||
continue
|
||||
}
|
||||
tgtClient, err := getReplicationTargetClient(tgt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
sys.Lock()
|
||||
sys.targetsMap[bucket.Name] = tgtClient
|
||||
sys.targetsARNMap[tgt.URL()] = tgt.Arn
|
||||
sys.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// GetARN returns the ARN associated with replication target URL
|
||||
func (sys *BucketReplicationSys) getARN(endpoint string) string {
|
||||
return sys.targetsARNMap[endpoint]
|
||||
}
|
||||
|
||||
// getReplicationTargetInstanceTransport contains a singleton roundtripper.
|
||||
var getReplicationTargetInstanceTransport http.RoundTripper
|
||||
var getReplicationTargetInstanceTransportOnce sync.Once
|
||||
|
||||
// Returns a minio-go Client configured to access remote host described in replication target config.
|
||||
var getReplicationTargetClient = func(tcfg *madmin.BucketReplicationTarget) (*miniogo.Core, error) {
|
||||
config := tcfg.Credentials
|
||||
// if Signature version '4' use NewV4 directly.
|
||||
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
||||
// if Signature version '2' use NewV2 directly.
|
||||
if strings.ToUpper(tcfg.API) == "S3V2" {
|
||||
creds = credentials.NewStaticV2(config.AccessKey, config.SecretKey, "")
|
||||
}
|
||||
|
||||
getReplicationTargetInstanceTransportOnce.Do(func() {
|
||||
getReplicationTargetInstanceTransport = NewGatewayHTTPTransport()
|
||||
})
|
||||
core, err := miniogo.NewCore(tcfg.Endpoint, &miniogo.Options{
|
||||
Creds: creds,
|
||||
Secure: tcfg.IsSSL,
|
||||
Transport: getReplicationTargetInstanceTransport,
|
||||
})
|
||||
return core, err
|
||||
return false, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||
}
|
||||
|
||||
// mustReplicate returns true if object meets replication criteria.
|
||||
func (sys *BucketReplicationSys) mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool {
|
||||
func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool {
|
||||
if globalIsGateway {
|
||||
return false
|
||||
}
|
||||
@@ -218,7 +81,7 @@ func (sys *BucketReplicationSys) mustReplicate(ctx context.Context, r *http.Requ
|
||||
if s3Err := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone {
|
||||
return false
|
||||
}
|
||||
cfg, err := globalBucketReplicationSys.GetConfig(ctx, bucket)
|
||||
cfg, err := getReplicationConfig(ctx, bucket)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -279,12 +142,12 @@ func putReplicationOpts(dest replication.Destination, objInfo ObjectInfo) (putOp
|
||||
// replicateObject replicates the specified version of the object to destination bucket
|
||||
// The source object is then updated to reflect the replication status.
|
||||
func replicateObject(ctx context.Context, bucket, object, versionID string, objectAPI ObjectLayer, eventArg *eventArgs, healPending bool) {
|
||||
cfg, err := globalBucketReplicationSys.GetConfig(ctx, bucket)
|
||||
cfg, err := getReplicationConfig(ctx, bucket)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
tgt := globalBucketReplicationSys.GetTargetClient(ctx, bucket)
|
||||
tgt := globalBucketTargetSys.GetReplicationTargetClient(ctx, cfg.ReplicationArn)
|
||||
if tgt == nil {
|
||||
return
|
||||
}
|
||||
@@ -344,12 +207,3 @@ func replicateObject(ctx context.Context, bucket, object, versionID string, obje
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// getReplicationARN gets existing ARN for an endpoint or generates a new one.
|
||||
func (sys *BucketReplicationSys) getReplicationARN(endpoint string) string {
|
||||
arn, ok := sys.targetsARNMap[endpoint]
|
||||
if ok {
|
||||
return arn
|
||||
}
|
||||
return fmt.Sprintf("arn:minio:s3::%s:*", mustGetUUID())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio/pkg/bucket/versioning"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// BucketTargetSys represents bucket targets subsystem
|
||||
type BucketTargetSys struct {
|
||||
sync.RWMutex
|
||||
arnRemotesMap map[string]*miniogo.Core
|
||||
targetsMap map[string][]madmin.BucketTarget
|
||||
clientsCache map[string]*miniogo.Core
|
||||
}
|
||||
|
||||
// ListTargets - gets list of bucket targets for this bucket.
|
||||
func (sys *BucketTargetSys) ListTargets(ctx context.Context, bucket string) (*madmin.BucketTargets, error) {
|
||||
if globalIsGateway {
|
||||
return nil, nil
|
||||
}
|
||||
tgts, ok := sys.targetsMap[bucket]
|
||||
if ok {
|
||||
return &madmin.BucketTargets{Targets: tgts}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("No remote targets exist for bucket %s", bucket)
|
||||
}
|
||||
|
||||
// SetTarget - sets a new minio-go client target for this bucket.
|
||||
func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget) error {
|
||||
if globalIsGateway {
|
||||
return nil
|
||||
}
|
||||
if !tgt.Type.IsValid() {
|
||||
return BucketRemoteArnTypeInvalid{Bucket: bucket}
|
||||
}
|
||||
clnt, err := sys.getRemoteTargetClient(tgt)
|
||||
if err != nil {
|
||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
|
||||
if tgt.Type == madmin.ReplicationArn {
|
||||
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
||||
if err != nil || vcfg.Status != string(versioning.Enabled) {
|
||||
if isErrBucketNotFound(err) {
|
||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
return BucketReplicationTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||
}
|
||||
}
|
||||
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
tgts := sys.targetsMap[bucket]
|
||||
newtgts := make([]madmin.BucketTarget, len(tgts))
|
||||
found := false
|
||||
for idx, t := range tgts {
|
||||
if t.Type == tgt.Type {
|
||||
if t.Arn == tgt.Arn {
|
||||
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
|
||||
}
|
||||
newtgts[idx] = *tgt
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
newtgts[idx] = t
|
||||
}
|
||||
if !found {
|
||||
newtgts = append(newtgts, *tgt)
|
||||
}
|
||||
|
||||
sys.targetsMap[bucket] = newtgts
|
||||
sys.arnRemotesMap[tgt.Arn] = clnt
|
||||
if _, ok := sys.clientsCache[clnt.EndpointURL().String()]; !ok {
|
||||
sys.clientsCache[clnt.EndpointURL().String()] = clnt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTarget - removes a remote bucket target for this source bucket.
|
||||
func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr string) error {
|
||||
if globalIsGateway {
|
||||
return nil
|
||||
}
|
||||
if arnStr == "" {
|
||||
return BucketRemoteArnInvalid{Bucket: bucket}
|
||||
}
|
||||
arn, err := madmin.ParseARN(arnStr)
|
||||
if err != nil {
|
||||
return BucketRemoteArnInvalid{Bucket: bucket}
|
||||
}
|
||||
if arn.Type == madmin.ReplicationArn {
|
||||
// reject removal of remote target if replication configuration is present
|
||||
rcfg, err := getReplicationConfig(ctx, bucket)
|
||||
if err == nil && rcfg.ReplicationArn == arnStr {
|
||||
if _, ok := sys.arnRemotesMap[arnStr]; ok {
|
||||
return BucketRemoteRemoveDisallowed{Bucket: bucket}
|
||||
}
|
||||
}
|
||||
}
|
||||
// delete ARN type from list of matching targets
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
targets := make([]madmin.BucketTarget, 0)
|
||||
tgts := sys.targetsMap[bucket]
|
||||
for _, tgt := range tgts {
|
||||
if tgt.Arn != arnStr {
|
||||
targets = append(targets, tgt)
|
||||
}
|
||||
}
|
||||
sys.targetsMap[bucket] = targets
|
||||
delete(sys.arnRemotesMap, arnStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetReplicationTargetClient returns minio-go client for replication target instance
|
||||
func (sys *BucketTargetSys) GetReplicationTargetClient(ctx context.Context, arn string) *miniogo.Core {
|
||||
sys.RLock()
|
||||
defer sys.RUnlock()
|
||||
return sys.arnRemotesMap[arn]
|
||||
}
|
||||
|
||||
// NewBucketTargetSys - creates new replication system.
|
||||
func NewBucketTargetSys() *BucketTargetSys {
|
||||
return &BucketTargetSys{
|
||||
arnRemotesMap: make(map[string]*miniogo.Core),
|
||||
targetsMap: make(map[string][]madmin.BucketTarget),
|
||||
clientsCache: make(map[string]*miniogo.Core),
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the bucket targets subsystem for buckets which have targets configured.
|
||||
func (sys *BucketTargetSys) Init(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
// In gateway mode, bucket targets is not supported.
|
||||
if globalIsGateway {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load bucket targets once during boot.
|
||||
sys.load(ctx, buckets, objAPI)
|
||||
return nil
|
||||
}
|
||||
|
||||
// create minio-go clients for buckets having remote targets
|
||||
func (sys *BucketTargetSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
||||
for _, bucket := range buckets {
|
||||
cfg, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if cfg == nil || cfg.Empty() {
|
||||
continue
|
||||
}
|
||||
if len(cfg.Targets) > 0 {
|
||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
||||
}
|
||||
for _, tgt := range cfg.Targets {
|
||||
tgtClient, err := sys.getRemoteTargetClient(&tgt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||
if _, ok := sys.clientsCache[tgtClient.EndpointURL().String()]; !ok {
|
||||
sys.clientsCache[tgtClient.EndpointURL().String()] = tgtClient
|
||||
}
|
||||
}
|
||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
||||
}
|
||||
}
|
||||
|
||||
// getRemoteTargetInstanceTransport contains a singleton roundtripper.
|
||||
var getRemoteTargetInstanceTransport http.RoundTripper
|
||||
var getRemoteTargetInstanceTransportOnce sync.Once
|
||||
|
||||
// Returns a minio-go Client configured to access remote host described in replication target config.
|
||||
func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*miniogo.Core, error) {
|
||||
if clnt, ok := sys.clientsCache[tcfg.Endpoint]; ok {
|
||||
return clnt, nil
|
||||
}
|
||||
config := tcfg.Credentials
|
||||
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
||||
|
||||
getRemoteTargetInstanceTransportOnce.Do(func() {
|
||||
getRemoteTargetInstanceTransport = NewGatewayHTTPTransport()
|
||||
})
|
||||
core, err := miniogo.NewCore(tcfg.Endpoint, &miniogo.Options{
|
||||
Creds: creds,
|
||||
Secure: tcfg.Secure,
|
||||
Transport: getRemoteTargetInstanceTransport,
|
||||
})
|
||||
return core, err
|
||||
}
|
||||
|
||||
// getRemoteARN gets existing ARN for an endpoint or generates a new one.
|
||||
func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget) string {
|
||||
if target == nil {
|
||||
return ""
|
||||
}
|
||||
tgts := sys.targetsMap[bucket]
|
||||
for _, tgt := range tgts {
|
||||
if tgt.Type == target.Type && tgt.TargetBucket == target.TargetBucket && target.URL() == tgt.URL() {
|
||||
return tgt.Arn
|
||||
}
|
||||
}
|
||||
if !madmin.ArnType(target.Type).IsValid() {
|
||||
return ""
|
||||
}
|
||||
arn := madmin.ARN{
|
||||
Type: target.Type,
|
||||
ID: mustGetUUID(),
|
||||
Region: target.Region,
|
||||
Bucket: target.TargetBucket,
|
||||
}
|
||||
return arn.String()
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
)
|
||||
|
||||
@@ -36,8 +35,6 @@ func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) ([]b
|
||||
return nil, errConfigNotFound
|
||||
}
|
||||
|
||||
logger.GetReqInfo(ctx).AppendTags("configFile", configFile)
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -74,8 +71,6 @@ func checkConfig(ctx context.Context, objAPI ObjectLayer, configFile string) err
|
||||
return errConfigNotFound
|
||||
}
|
||||
|
||||
logger.GetReqInfo(ctx).AppendTags("configFile", configFile)
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -92,7 +92,7 @@ func initHelp() {
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.PolicyOPASubSys,
|
||||
Description: "enable external OPA for policy enforcement",
|
||||
Description: "[DEPRECATED] enable external OPA for policy enforcement",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.KmsVaultSubSys,
|
||||
|
||||
@@ -187,12 +187,6 @@ Example 1:
|
||||
"",
|
||||
)
|
||||
|
||||
ErrCorruptedBackend = newErrFn(
|
||||
"Unable to use the specified backend, pre-existing content detected",
|
||||
"Please ensure your disk mount does not have any pre-existing content",
|
||||
"",
|
||||
)
|
||||
|
||||
ErrUnableToWriteInBackend = newErrFn(
|
||||
"Unable to write to the backend",
|
||||
"Please ensure MinIO binary has write permissions for the backend",
|
||||
|
||||
@@ -331,7 +331,7 @@ func SetNotifyPostgres(s config.Config, psqName string, cfg target.PostgreSQLArg
|
||||
},
|
||||
config.KV{
|
||||
Key: target.PostgresUsername,
|
||||
Value: cfg.User,
|
||||
Value: cfg.Username,
|
||||
},
|
||||
config.KV{
|
||||
Key: target.PostgresPassword,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2019,2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,12 +23,12 @@ var (
|
||||
Help = config.HelpKVS{
|
||||
config.HelpKV{
|
||||
Key: URL,
|
||||
Description: `OPA HTTP(s) endpoint e.g. "http://localhost:8181/v1/data/httpapi/authz/allow"`,
|
||||
Description: `[DEPRECATED] OPA HTTP(s) endpoint e.g. "http://localhost:8181/v1/data/httpapi/authz/allow"`,
|
||||
Type: "url",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: AuthToken,
|
||||
Description: "authorization token for OPA endpoint",
|
||||
Description: "[DEPRECATED] authorization token for OPA endpoint",
|
||||
Optional: true,
|
||||
Type: "string",
|
||||
},
|
||||
|
||||
+52
-45
@@ -510,8 +510,7 @@ func (i *crawlItem) transformMetaDir() {
|
||||
// actionMeta contains information used to apply actions.
|
||||
type actionMeta struct {
|
||||
oi ObjectInfo
|
||||
trustOI bool // Set true if oi can be trusted and has been read with quorum.
|
||||
numVersions int // The number of versions of this object
|
||||
numVersions int // The number of versions of this object
|
||||
}
|
||||
|
||||
// applyActions will apply lifecycle checks on to a scanned item.
|
||||
@@ -539,7 +538,7 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
||||
NumVersions: meta.numVersions,
|
||||
})
|
||||
if i.debug {
|
||||
logger.Info(color.Green("applyActions:")+" lifecycle: %q, Initial scan: %v", i.objectPath(), action)
|
||||
logger.Info(color.Green("applyActions:")+" lifecycle: %q (version-id=%s), Initial scan: %v", i.objectPath(), versionID, action)
|
||||
}
|
||||
switch action {
|
||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
||||
@@ -548,63 +547,71 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
|
||||
return size
|
||||
}
|
||||
|
||||
// These (expensive) operations should only run on items we are likely to delete.
|
||||
// Load to ensure that we have the correct version and not an unsynced version.
|
||||
if !meta.trustOI {
|
||||
obj, err := o.GetObjectInfo(ctx, i.bucket, i.objectPath(), ObjectOptions{
|
||||
VersionID: versionID,
|
||||
})
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case MethodNotAllowed: // This happens usually for a delete marker
|
||||
if !obj.DeleteMarker { // if this is not a delete marker log and return
|
||||
// Do nothing - heal in the future.
|
||||
logger.LogIf(ctx, err)
|
||||
return size
|
||||
}
|
||||
case ObjectNotFound:
|
||||
// object not found return 0
|
||||
return 0
|
||||
default:
|
||||
// All other errors proceed.
|
||||
obj, err := o.GetObjectInfo(ctx, i.bucket, i.objectPath(), ObjectOptions{
|
||||
VersionID: versionID,
|
||||
})
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case MethodNotAllowed: // This happens usually for a delete marker
|
||||
if !obj.DeleteMarker { // if this is not a delete marker log and return
|
||||
// Do nothing - heal in the future.
|
||||
logger.LogIf(ctx, err)
|
||||
return size
|
||||
}
|
||||
}
|
||||
size = obj.Size
|
||||
|
||||
// Recalculate action.
|
||||
action = i.lifeCycle.ComputeAction(
|
||||
lifecycle.ObjectOpts{
|
||||
Name: i.objectPath(),
|
||||
UserTags: obj.UserTags,
|
||||
ModTime: obj.ModTime,
|
||||
VersionID: obj.VersionID,
|
||||
DeleteMarker: obj.DeleteMarker,
|
||||
IsLatest: obj.IsLatest,
|
||||
NumVersions: meta.numVersions,
|
||||
})
|
||||
if i.debug {
|
||||
logger.Info(color.Green("applyActions:")+" lifecycle: Secondary scan: %v", action)
|
||||
}
|
||||
versionID = obj.VersionID
|
||||
switch action {
|
||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
||||
case ObjectNotFound:
|
||||
// object not found return 0
|
||||
return 0
|
||||
default:
|
||||
// No action.
|
||||
// All other errors proceed.
|
||||
logger.LogIf(ctx, err)
|
||||
return size
|
||||
}
|
||||
}
|
||||
size = obj.Size
|
||||
|
||||
// Recalculate action.
|
||||
action = i.lifeCycle.ComputeAction(
|
||||
lifecycle.ObjectOpts{
|
||||
Name: i.objectPath(),
|
||||
UserTags: obj.UserTags,
|
||||
ModTime: obj.ModTime,
|
||||
VersionID: obj.VersionID,
|
||||
DeleteMarker: obj.DeleteMarker,
|
||||
IsLatest: obj.IsLatest,
|
||||
NumVersions: meta.numVersions,
|
||||
})
|
||||
if i.debug {
|
||||
logger.Info(color.Green("applyActions:")+" lifecycle: Secondary scan: %v", action)
|
||||
}
|
||||
switch action {
|
||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
|
||||
default:
|
||||
// No action.
|
||||
return size
|
||||
}
|
||||
|
||||
opts := ObjectOptions{}
|
||||
switch action {
|
||||
case lifecycle.DeleteVersionAction:
|
||||
opts.VersionID = versionID
|
||||
// Defensive code, should never happen
|
||||
if obj.VersionID == "" {
|
||||
return size
|
||||
}
|
||||
if rcfg, _ := globalBucketObjectLockSys.Get(i.bucket); rcfg.LockEnabled {
|
||||
locked := enforceRetentionForDeletion(ctx, obj)
|
||||
if locked {
|
||||
if i.debug {
|
||||
logger.Info(color.Green("applyActions:")+" lifecycle: %s is locked, not deleting", i.objectPath())
|
||||
}
|
||||
return size
|
||||
}
|
||||
}
|
||||
opts.VersionID = obj.VersionID
|
||||
case lifecycle.DeleteAction:
|
||||
opts.Versioned = globalBucketVersioningSys.Enabled(i.bucket)
|
||||
}
|
||||
|
||||
obj, err := o.DeleteObject(ctx, i.bucket, i.objectPath(), opts)
|
||||
obj, err = o.DeleteObject(ctx, i.bucket, i.objectPath(), opts)
|
||||
if err != nil {
|
||||
// Assume it is still there.
|
||||
logger.LogIf(ctx, err)
|
||||
|
||||
+9
-5
@@ -684,11 +684,13 @@ func newServerCacheObjects(ctx context.Context, config cache.Config) (CacheObjec
|
||||
c.cacheStats.GetDiskStats = func() []CacheDiskStats {
|
||||
cacheDiskStats := make([]CacheDiskStats, len(c.cache))
|
||||
for i := range c.cache {
|
||||
cacheDiskStats[i] = CacheDiskStats{
|
||||
Dir: c.cache[i].stats.Dir,
|
||||
dcache := c.cache[i]
|
||||
cacheDiskStats[i] = CacheDiskStats{}
|
||||
if dcache != nil {
|
||||
cacheDiskStats[i].Dir = dcache.stats.Dir
|
||||
atomic.StoreInt32(&cacheDiskStats[i].UsageState, atomic.LoadInt32(&dcache.stats.UsageState))
|
||||
atomic.StoreUint64(&cacheDiskStats[i].UsagePercent, atomic.LoadUint64(&dcache.stats.UsagePercent))
|
||||
}
|
||||
atomic.StoreInt32(&cacheDiskStats[i].UsageState, atomic.LoadInt32(&c.cache[i].stats.UsageState))
|
||||
atomic.StoreUint64(&cacheDiskStats[i].UsagePercent, atomic.LoadUint64(&c.cache[i].stats.UsagePercent))
|
||||
}
|
||||
return cacheDiskStats
|
||||
}
|
||||
@@ -712,7 +714,9 @@ func (c *cacheObjects) gc(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
for _, dcache := range c.cache {
|
||||
dcache.triggerGC <- struct{}{}
|
||||
if dcache != nil {
|
||||
dcache.triggerGC <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
@@ -197,10 +198,10 @@ func listAllBuckets(storageDisks []StorageAPI, healBuckets map[string]VolInfo) (
|
||||
// Only heal on disks where we are sure that healing is needed. We can expand
|
||||
// this list as and when we figure out more errors can be added to this list safely.
|
||||
func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, quorumModTime time.Time) bool {
|
||||
switch erErr {
|
||||
case errFileNotFound, errFileVersionNotFound:
|
||||
switch {
|
||||
case errors.Is(erErr, errFileNotFound) || errors.Is(erErr, errFileVersionNotFound):
|
||||
return true
|
||||
case errCorruptedFormat:
|
||||
case errors.Is(erErr, errCorruptedFormat):
|
||||
return true
|
||||
}
|
||||
if erErr == nil {
|
||||
@@ -686,9 +687,9 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
// or when er.meta is not readable in read quorum disks.
|
||||
var notFoundErasureMeta, corruptedErasureMeta int
|
||||
for _, readErr := range errs {
|
||||
if readErr == errFileNotFound || readErr == errFileVersionNotFound {
|
||||
if errors.Is(readErr, errFileNotFound) || errors.Is(readErr, errFileVersionNotFound) {
|
||||
notFoundErasureMeta++
|
||||
} else if readErr == errCorruptedFormat {
|
||||
} else if errors.Is(readErr, errCorruptedFormat) {
|
||||
corruptedErasureMeta++
|
||||
}
|
||||
}
|
||||
@@ -699,7 +700,10 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
// double counting when both parts and er.meta
|
||||
// are not available.
|
||||
if errs[i] != dataErrs[i] {
|
||||
if dataErrs[i] == errFileNotFound || dataErrs[i] == errFileVersionNotFound {
|
||||
if IsErr(dataErrs[i], []error{
|
||||
errFileNotFound,
|
||||
errFileVersionNotFound,
|
||||
}...) {
|
||||
notFoundParts++
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -88,9 +88,10 @@ func (fi FileInfo) IsValid() bool {
|
||||
// for erasure coding information
|
||||
return true
|
||||
}
|
||||
data := fi.Erasure.DataBlocks
|
||||
parity := fi.Erasure.ParityBlocks
|
||||
return ((data >= parity) && (data != 0) && (parity != 0))
|
||||
dataBlocks := fi.Erasure.DataBlocks
|
||||
parityBlocks := fi.Erasure.ParityBlocks
|
||||
return ((dataBlocks >= parityBlocks) &&
|
||||
(dataBlocks != 0) && (parityBlocks != 0))
|
||||
}
|
||||
|
||||
// ToObjectInfo - Converts metadata to object info.
|
||||
@@ -111,6 +112,7 @@ func (fi FileInfo) ToObjectInfo(bucket, object string) ObjectInfo {
|
||||
DeleteMarker: fi.Deleted,
|
||||
Size: fi.Size,
|
||||
ModTime: fi.ModTime,
|
||||
Legacy: fi.XLV1,
|
||||
ContentType: fi.Metadata["content-type"],
|
||||
ContentEncoding: fi.Metadata["content-encoding"],
|
||||
}
|
||||
@@ -323,7 +325,18 @@ func objectQuorumFromMeta(ctx context.Context, er erasureObjects, partsMetaData
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
dataBlocks := latestFileInfo.Erasure.DataBlocks
|
||||
parityBlocks := globalStorageClass.GetParityForSC(latestFileInfo.Metadata[xhttp.AmzStorageClass])
|
||||
if parityBlocks == 0 {
|
||||
parityBlocks = dataBlocks
|
||||
}
|
||||
|
||||
writeQuorum := dataBlocks
|
||||
if dataBlocks == parityBlocks {
|
||||
writeQuorum = dataBlocks + 1
|
||||
}
|
||||
|
||||
// Since all the valid erasure code meta updated at the same time are equivalent, pass dataBlocks
|
||||
// from latestFileInfo to get the quorum
|
||||
return latestFileInfo.Erasure.DataBlocks, latestFileInfo.Erasure.DataBlocks + 1, nil
|
||||
return dataBlocks, writeQuorum, nil
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
||||
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, tmpPartPath, erasure.ShardFileSize(data.Size()), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
|
||||
n, err := erasure.Encode(ctx, data, writers, buffer, fi.Erasure.DataBlocks+1)
|
||||
n, err := erasure.Encode(ctx, data, writers, buffer, writeQuorum)
|
||||
closeBitrotWriters(writers)
|
||||
if err != nil {
|
||||
return pi, toObjectErr(err, bucket, object)
|
||||
|
||||
+17
-2
@@ -65,7 +65,7 @@ func (er erasureObjects) putObjectDir(ctx context.Context, bucket, object string
|
||||
// if source object and destination object are same we only
|
||||
// update metadata.
|
||||
func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, e error) {
|
||||
// This call shouldn't be used for anything other than metadata updates.
|
||||
// This call shouldn't be used for anything other than metadata updates or adding self referential versions.
|
||||
if !srcInfo.metadataOnly {
|
||||
return oi, NotImplemented{}
|
||||
}
|
||||
@@ -97,8 +97,23 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
|
||||
return fi.ToObjectInfo(srcBucket, srcObject), toObjectErr(errMethodNotAllowed, srcBucket, srcObject)
|
||||
}
|
||||
|
||||
versionID := srcInfo.VersionID
|
||||
if srcInfo.versionOnly {
|
||||
versionID = dstOpts.VersionID
|
||||
// preserve destination versionId if specified.
|
||||
if versionID == "" {
|
||||
versionID = mustGetUUID()
|
||||
}
|
||||
modTime = UTCNow()
|
||||
}
|
||||
|
||||
fi.VersionID = versionID // set any new versionID we might have created
|
||||
fi.ModTime = modTime // set modTime for the new versionID
|
||||
|
||||
// Update `xl.meta` content on each disks.
|
||||
for index := range metaArr {
|
||||
metaArr[index].ModTime = modTime
|
||||
metaArr[index].VersionID = versionID
|
||||
metaArr[index].Metadata = srcInfo.UserDefined
|
||||
metaArr[index].Metadata["etag"] = srcInfo.ETag
|
||||
}
|
||||
@@ -670,7 +685,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, tempErasureObj, erasure.ShardFileSize(data.Size()), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
|
||||
n, erasureErr := erasure.Encode(ctx, data, writers, buffer, fi.Erasure.DataBlocks+1)
|
||||
n, erasureErr := erasure.Encode(ctx, data, writers, buffer, writeQuorum)
|
||||
closeBitrotWriters(writers)
|
||||
if erasureErr != nil {
|
||||
return ObjectInfo{}, toObjectErr(erasureErr, minioMetaTmpBucket, tempErasureObj)
|
||||
|
||||
@@ -420,6 +420,8 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
|
||||
parts1, errs1 := readAllFileInfo(ctx, erasureDisks, bucket, object1, "")
|
||||
|
||||
parts1SC := globalStorageClass
|
||||
|
||||
// Object for test case 2 - No StorageClass defined, MetaData in PutObject requesting RRS Class
|
||||
object2 := "object2"
|
||||
metadata2 := make(map[string]string)
|
||||
@@ -430,6 +432,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
}
|
||||
|
||||
parts2, errs2 := readAllFileInfo(ctx, erasureDisks, bucket, object2, "")
|
||||
parts2SC := globalStorageClass
|
||||
|
||||
// Object for test case 3 - No StorageClass defined, MetaData in PutObject requesting Standard Storage Class
|
||||
object3 := "object3"
|
||||
@@ -441,6 +444,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
}
|
||||
|
||||
parts3, errs3 := readAllFileInfo(ctx, erasureDisks, bucket, object3, "")
|
||||
parts3SC := globalStorageClass
|
||||
|
||||
// Object for test case 4 - Standard StorageClass defined as Parity 6, MetaData in PutObject requesting Standard Storage Class
|
||||
object4 := "object4"
|
||||
@@ -458,6 +462,11 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
}
|
||||
|
||||
parts4, errs4 := readAllFileInfo(ctx, erasureDisks, bucket, object4, "")
|
||||
parts4SC := storageclass.Config{
|
||||
Standard: storageclass.StorageClass{
|
||||
Parity: 6,
|
||||
},
|
||||
}
|
||||
|
||||
// Object for test case 5 - RRS StorageClass defined as Parity 2, MetaData in PutObject requesting RRS Class
|
||||
// Reset global storage class flags
|
||||
@@ -476,6 +485,11 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
}
|
||||
|
||||
parts5, errs5 := readAllFileInfo(ctx, erasureDisks, bucket, object5, "")
|
||||
parts5SC := storageclass.Config{
|
||||
RRS: storageclass.StorageClass{
|
||||
Parity: 2,
|
||||
},
|
||||
}
|
||||
|
||||
// Object for test case 6 - RRS StorageClass defined as Parity 2, MetaData in PutObject requesting Standard Storage Class
|
||||
object6 := "object6"
|
||||
@@ -493,6 +507,11 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
}
|
||||
|
||||
parts6, errs6 := readAllFileInfo(ctx, erasureDisks, bucket, object6, "")
|
||||
parts6SC := storageclass.Config{
|
||||
RRS: storageclass.StorageClass{
|
||||
Parity: 2,
|
||||
},
|
||||
}
|
||||
|
||||
// Object for test case 7 - Standard StorageClass defined as Parity 5, MetaData in PutObject requesting RRS Class
|
||||
// Reset global storage class flags
|
||||
@@ -511,25 +530,32 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
}
|
||||
|
||||
parts7, errs7 := readAllFileInfo(ctx, erasureDisks, bucket, object7, "")
|
||||
parts7SC := storageclass.Config{
|
||||
Standard: storageclass.StorageClass{
|
||||
Parity: 5,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
parts []FileInfo
|
||||
errs []error
|
||||
expectedReadQuorum int
|
||||
expectedWriteQuorum int
|
||||
storageClassCfg storageclass.Config
|
||||
expectedError error
|
||||
}{
|
||||
{parts1, errs1, 8, 9, nil},
|
||||
{parts2, errs2, 14, 15, nil},
|
||||
{parts3, errs3, 8, 9, nil},
|
||||
{parts4, errs4, 10, 11, nil},
|
||||
{parts5, errs5, 14, 15, nil},
|
||||
{parts6, errs6, 8, 9, nil},
|
||||
{parts7, errs7, 14, 15, nil},
|
||||
{parts1, errs1, 8, 9, parts1SC, nil},
|
||||
{parts2, errs2, 14, 14, parts2SC, nil},
|
||||
{parts3, errs3, 8, 9, parts3SC, nil},
|
||||
{parts4, errs4, 10, 10, parts4SC, nil},
|
||||
{parts5, errs5, 14, 14, parts5SC, nil},
|
||||
{parts6, errs6, 8, 9, parts6SC, nil},
|
||||
{parts7, errs7, 14, 14, parts7SC, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.(*testing.T).Run("", func(t *testing.T) {
|
||||
globalStorageClass = tt.storageClassCfg
|
||||
actualReadQuorum, actualWriteQuorum, err := objectQuorumFromMeta(ctx, *xl, tt.parts, tt.errs)
|
||||
if tt.expectedError != nil && err == nil {
|
||||
t.Errorf("Expected %s, got %s", tt.expectedError, err)
|
||||
|
||||
+12
-22
@@ -738,14 +738,24 @@ func (s *erasureSets) CopyObject(ctx context.Context, srcBucket, srcObject, dstB
|
||||
srcSet := s.getHashedSet(srcObject)
|
||||
dstSet := s.getHashedSet(dstObject)
|
||||
|
||||
cpSrcDstSame := srcSet == dstSet
|
||||
|
||||
// Check if this request is only metadata update.
|
||||
if srcSet == dstSet && srcInfo.metadataOnly {
|
||||
if cpSrcDstSame && srcInfo.metadataOnly {
|
||||
if dstOpts.VersionID != "" && srcOpts.VersionID == dstOpts.VersionID {
|
||||
return srcSet.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
||||
}
|
||||
if !dstOpts.Versioned && srcOpts.VersionID == "" {
|
||||
return srcSet.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
||||
}
|
||||
// CopyObject optimization where we don't create an entire copy
|
||||
// of the content, instead we add a reference, we disallow legacy
|
||||
// objects to be self referenced in this manner so make sure
|
||||
// that we actually create a new dataDir for legacy objects.
|
||||
if dstOpts.Versioned && srcOpts.VersionID != dstOpts.VersionID && !srcInfo.Legacy {
|
||||
srcInfo.versionOnly = true
|
||||
return srcSet.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
||||
}
|
||||
}
|
||||
|
||||
putOpts := ObjectOptions{
|
||||
@@ -1189,21 +1199,11 @@ func (s *erasureSets) ReloadFormat(ctx context.Context, dryRun bool) (err error)
|
||||
}
|
||||
}(storageDisks)
|
||||
|
||||
formats, sErrs := loadFormatErasureAll(storageDisks, false)
|
||||
formats, _ := loadFormatErasureAll(storageDisks, false)
|
||||
if err = checkFormatErasureValues(formats, s.drivesPerSet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for index, sErr := range sErrs {
|
||||
if sErr != nil {
|
||||
// Look for acceptable heal errors, for any other
|
||||
// errors we should simply quit and return.
|
||||
if _, ok := formatHealErrors[sErr]; !ok {
|
||||
return fmt.Errorf("Disk %s: %w", s.endpoints[index], sErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refFormat, err := getFormatErasureInQuorum(formats)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1347,16 +1347,6 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
||||
res.After.Drives[k] = madmin.HealDriveInfo(v)
|
||||
}
|
||||
|
||||
for index, sErr := range sErrs {
|
||||
if sErr != nil {
|
||||
// Look for acceptable heal errors, for any other
|
||||
// errors we should simply quit and return.
|
||||
if _, ok := formatHealErrors[sErr]; !ok {
|
||||
return res, fmt.Errorf("Disk %s: %w", s.endpoints[index], sErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if countErrs(sErrs, errUnformattedDisk) == 0 {
|
||||
// No unformatted disks found disks are either offline
|
||||
// or online, no healing is required.
|
||||
|
||||
@@ -626,22 +626,24 @@ func (z *erasureZones) CopyObject(ctx context.Context, srcBucket, srcObject, dst
|
||||
defer lk.Unlock()
|
||||
}
|
||||
|
||||
if z.SingleZone() {
|
||||
return z.zones[0].CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
||||
}
|
||||
|
||||
zoneIdx, err := z.getZoneIdx(ctx, dstBucket, dstObject, dstOpts, srcInfo.Size)
|
||||
if err != nil {
|
||||
return objInfo, err
|
||||
}
|
||||
|
||||
if cpSrcDstSame && srcInfo.metadataOnly && srcOpts.VersionID == dstOpts.VersionID {
|
||||
if cpSrcDstSame && srcInfo.metadataOnly {
|
||||
if dstOpts.VersionID != "" && srcOpts.VersionID == dstOpts.VersionID {
|
||||
return z.zones[zoneIdx].CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
||||
}
|
||||
if !dstOpts.Versioned && srcOpts.VersionID == "" {
|
||||
return z.zones[zoneIdx].CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
||||
}
|
||||
if dstOpts.Versioned && srcOpts.VersionID != dstOpts.VersionID && !srcInfo.Legacy {
|
||||
// CopyObject optimization where we don't create an entire copy
|
||||
// of the content, instead we add a reference.
|
||||
srcInfo.versionOnly = true
|
||||
return z.zones[zoneIdx].CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
||||
}
|
||||
}
|
||||
|
||||
putOpts := ObjectOptions{
|
||||
|
||||
+8
-7
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
@@ -89,18 +90,18 @@ func (d byDiskTotal) Less(i, j int) bool {
|
||||
|
||||
func diskErrToDriveState(err error) (state string) {
|
||||
state = madmin.DriveStateUnknown
|
||||
switch err {
|
||||
case errDiskNotFound:
|
||||
switch {
|
||||
case errors.Is(err, errDiskNotFound):
|
||||
state = madmin.DriveStateOffline
|
||||
case errCorruptedFormat:
|
||||
case errors.Is(err, errCorruptedFormat):
|
||||
state = madmin.DriveStateCorrupt
|
||||
case errUnformattedDisk:
|
||||
case errors.Is(err, errUnformattedDisk):
|
||||
state = madmin.DriveStateUnformatted
|
||||
case errDiskAccessDenied:
|
||||
case errors.Is(err, errDiskAccessDenied):
|
||||
state = madmin.DriveStatePermission
|
||||
case errFaultyDisk:
|
||||
case errors.Is(err, errFaultyDisk):
|
||||
state = madmin.DriveStateFaulty
|
||||
case nil:
|
||||
case err == nil:
|
||||
state = madmin.DriveStateOk
|
||||
}
|
||||
return
|
||||
|
||||
+6
-23
@@ -27,7 +27,6 @@ import (
|
||||
"sync"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/color"
|
||||
@@ -58,18 +57,6 @@ const (
|
||||
// Offline disk UUID represents an offline disk.
|
||||
const offlineDiskUUID = "ffffffff-ffff-ffff-ffff-ffffffffffff"
|
||||
|
||||
// Healing is only supported for the list of errors mentioned here.
|
||||
var formatHealErrors = map[error]struct{}{
|
||||
errUnformattedDisk: {},
|
||||
errDiskNotFound: {},
|
||||
}
|
||||
|
||||
// List of errors considered critical for disk formatting.
|
||||
var formatCriticalErrors = map[error]struct{}{
|
||||
errCorruptedFormat: {},
|
||||
errFaultyDisk: {},
|
||||
}
|
||||
|
||||
// Used to detect the version of "xl" format.
|
||||
type formatErasureVersionDetect struct {
|
||||
Erasure struct {
|
||||
@@ -415,7 +402,8 @@ func loadFormatErasure(disk StorageAPI) (format *formatErasureV3, err error) {
|
||||
}
|
||||
if !isHiddenDirectories(vols...) {
|
||||
// 'format.json' not found, but we found user data, reject such disks.
|
||||
return nil, errCorruptedFormat
|
||||
return nil, fmt.Errorf("some unexpected files '%v' found on %s: %w",
|
||||
vols, disk, errCorruptedFormat)
|
||||
}
|
||||
// No other data found, its a fresh disk.
|
||||
return nil, errUnformattedDisk
|
||||
@@ -490,7 +478,8 @@ func formatErasureGetDeploymentID(refFormat *formatErasureV3, formats []*formatE
|
||||
} else if deploymentID != format.ID {
|
||||
// DeploymentID found earlier doesn't match with the
|
||||
// current format.json's ID.
|
||||
return "", errCorruptedFormat
|
||||
return "", fmt.Errorf("Deployment IDs do not match expected %s, got %s: %w",
|
||||
deploymentID, format.ID, errCorruptedFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,14 +489,7 @@ func formatErasureGetDeploymentID(refFormat *formatErasureV3, formats []*formatE
|
||||
// formatErasureFixDeploymentID - Add deployment id if it is not present.
|
||||
func formatErasureFixDeploymentID(endpoints Endpoints, storageDisks []StorageAPI, refFormat *formatErasureV3) (err error) {
|
||||
// Attempt to load all `format.json` from all disks.
|
||||
var sErrs []error
|
||||
formats, sErrs := loadFormatErasureAll(storageDisks, false)
|
||||
for i, sErr := range sErrs {
|
||||
if _, ok := formatCriticalErrors[sErr]; ok {
|
||||
return config.ErrCorruptedBackend(err).Hint(fmt.Sprintf("Clear any pre-existing content on %s", endpoints[i]))
|
||||
}
|
||||
}
|
||||
|
||||
formats, _ := loadFormatErasureAll(storageDisks, false)
|
||||
for index := range formats {
|
||||
// If the Erasure sets do not match, set those formats to nil,
|
||||
// We do not have to update the ID on those format.json file.
|
||||
@@ -515,6 +497,7 @@ func formatErasureFixDeploymentID(endpoints Endpoints, storageDisks []StorageAPI
|
||||
formats[index] = nil
|
||||
}
|
||||
}
|
||||
|
||||
refFormat.ID, err = formatErasureGetDeploymentID(refFormat, formats)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -436,8 +437,8 @@ func TestGetErasureID(t *testing.T) {
|
||||
}
|
||||
|
||||
formats[2].ID = "bad-id"
|
||||
if _, err = formatErasureGetDeploymentID(quorumFormat, formats); err != errCorruptedFormat {
|
||||
t.Fatal("Unexpected Success")
|
||||
if _, err = formatErasureGetDeploymentID(quorumFormat, formats); !errors.Is(err, errCorruptedFormat) {
|
||||
t.Fatalf("Unexpect error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-7
@@ -256,7 +256,6 @@ func fsOpenFile(ctx context.Context, readPath string, offset int64) (io.ReadClos
|
||||
|
||||
// Verify if its not a regular file, since subsequent Seek is undefined.
|
||||
if !st.Mode().IsRegular() {
|
||||
logger.LogIf(ctx, errIsNotRegular)
|
||||
return nil, 0, errIsNotRegular
|
||||
}
|
||||
|
||||
@@ -286,12 +285,18 @@ func fsCreateFile(ctx context.Context, filePath string, reader io.Reader, buf []
|
||||
}
|
||||
|
||||
if err := mkdirAll(pathutil.Dir(filePath), 0777); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := checkDiskFree(pathutil.Dir(filePath), fallocSize); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
switch {
|
||||
case os.IsPermission(err):
|
||||
return 0, errFileAccessDenied
|
||||
case os.IsExist(err):
|
||||
return 0, errFileAccessDenied
|
||||
case isSysErrIO(err):
|
||||
return 0, errFaultyDisk
|
||||
case isSysErrInvalidArg(err):
|
||||
return 0, errUnsupportedDisk
|
||||
case isSysErrNoSpace(err):
|
||||
return 0, errDiskFull
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
||||
@@ -298,6 +298,7 @@ func (fs *FSObjects) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter,
|
||||
if intDataUpdateTracker.debug {
|
||||
logger.Info(color.Green("CrawlAndGetDataUsage:")+" Saving totals cache with %d entries", len(totalCache.Cache))
|
||||
}
|
||||
totalCache.Info.LastUpdate = time.Now()
|
||||
logger.LogIf(ctx, totalCache.save(ctx, fs, dataUsageCacheName))
|
||||
cloned := totalCache.clone()
|
||||
updates <- cloned.dui(dataUsageRoot, buckets)
|
||||
|
||||
+3
-4
@@ -58,7 +58,6 @@ const (
|
||||
globalMinioDefaultOwnerID = "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4"
|
||||
globalMinioDefaultStorageClass = "STANDARD"
|
||||
globalWindowsOSName = "windows"
|
||||
globalNetBSDOSName = "netbsd"
|
||||
globalMacOSName = "darwin"
|
||||
globalMinioModeFS = "mode-server-fs"
|
||||
globalMinioModeErasure = "mode-server-xl"
|
||||
@@ -154,9 +153,9 @@ var (
|
||||
globalPolicySys *PolicySys
|
||||
globalIAMSys *IAMSys
|
||||
|
||||
globalLifecycleSys *LifecycleSys
|
||||
globalBucketSSEConfigSys *BucketSSEConfigSys
|
||||
globalBucketReplicationSys *BucketReplicationSys
|
||||
globalLifecycleSys *LifecycleSys
|
||||
globalBucketSSEConfigSys *BucketSSEConfigSys
|
||||
globalBucketTargetSys *BucketTargetSys
|
||||
// globalAPIConfig controls S3 API requests throttling,
|
||||
// healthcheck readiness deadlines and cors settings.
|
||||
globalAPIConfig apiConfig
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
// ClusterCheckHandler returns if the server is ready for requests.
|
||||
func ClusterCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ClusterCheckCheckHandler")
|
||||
ctx := newContext(r, w, "ClusterCheckHandler")
|
||||
|
||||
objLayer := newObjectLayerFn()
|
||||
// Service not initialized yet
|
||||
|
||||
+26
-7
@@ -49,20 +49,17 @@ func setInternalTCPParameters(c syscall.RawConn) error {
|
||||
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 5)
|
||||
|
||||
// Wait time after successful probe in seconds.
|
||||
// ~ cat /proc/sys/net/ipv4/tcp_keepalive_intvl (defaults to 75 secs, we reduce it to 2 secs)
|
||||
// ~ cat /proc/sys/net/ipv4/tcp_keepalive_intvl (defaults to 75 secs, we reduce it to 3 secs)
|
||||
// 75
|
||||
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 2)
|
||||
|
||||
// Set TCP_USER_TIMEOUT to TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT.
|
||||
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, 15)
|
||||
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 3)
|
||||
})
|
||||
}
|
||||
|
||||
// DialContext is a function to make custom Dial for internode communications
|
||||
type DialContext func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
// NewCustomDialContext setups a custom dialer for internode communications
|
||||
func NewCustomDialContext(dialTimeout time.Duration) DialContext {
|
||||
// NewInternodeDialContext setups a custom dialer for internode communication
|
||||
func NewInternodeDialContext(dialTimeout time.Duration) DialContext {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: dialTimeout,
|
||||
@@ -73,3 +70,25 @@ func NewCustomDialContext(dialTimeout time.Duration) DialContext {
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// NewCustomDialContext setups a custom dialer for any external communication and proxies.
|
||||
func NewCustomDialContext(dialTimeout time.Duration) DialContext {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: dialTimeout,
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fdPtr uintptr) {
|
||||
// got socket file descriptor to set parameters.
|
||||
fd := int(fdPtr)
|
||||
|
||||
// Enable TCP fast connect
|
||||
// TCPFastOpenConnect sets the underlying socket to use
|
||||
// the TCP fast open connect. This feature is supported
|
||||
// since Linux 4.11.
|
||||
_ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN_CONNECT, 1)
|
||||
})
|
||||
},
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ func setInternalTCPParameters(c syscall.RawConn) error {
|
||||
// DialContext is a function to make custom Dial for internode communications
|
||||
type DialContext func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
// NewInternodeDialContext setups a custom dialer for internode communication
|
||||
var NewInternodeDialContext = NewCustomDialContext
|
||||
|
||||
// NewCustomDialContext configures a custom dialer for internode communications
|
||||
func NewCustomDialContext(dialTimeout time.Duration) DialContext {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
|
||||
+1
-1
@@ -451,7 +451,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
|
||||
for range retry.NewTimerWithJitter(retryCtx, time.Second, 5*time.Second, retry.MaxJitter) {
|
||||
// let one of the server acquire the lock, if not let them timeout.
|
||||
// which shall be retried again by this loop.
|
||||
if err := txnLk.GetLock(newDynamicTimeout(1*time.Second, 5*time.Second)); err != nil {
|
||||
if err := txnLk.GetLock(newDynamicTimeout(3*time.Second, 5*time.Second)); err != nil {
|
||||
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. trying to acquire lock")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
policy "github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
@@ -119,7 +118,7 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
|
||||
|
||||
rulesMap := event.NewRulesMap(eventNames, pattern, event.TargetID{ID: mustGetUUID()})
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
|
||||
// Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
|
||||
+68
-52
@@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -71,30 +72,35 @@ func (l *localLocker) canTakeLock(resources ...string) bool {
|
||||
return noLkCnt == len(resources)
|
||||
}
|
||||
|
||||
func (l *localLocker) Lock(args dsync.LockArgs) (reply bool, err error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
func (l *localLocker) Lock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
default:
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if !l.canTakeLock(args.Resources...) {
|
||||
// Not all locks can be taken on resources,
|
||||
// reject it completely.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// No locks held on the all resources, so claim write
|
||||
// lock on all resources at once.
|
||||
for _, resource := range args.Resources {
|
||||
l.lockMap[resource] = []lockRequesterInfo{
|
||||
{
|
||||
Writer: true,
|
||||
Source: args.Source,
|
||||
UID: args.UID,
|
||||
Timestamp: UTCNow(),
|
||||
TimeLastCheck: UTCNow(),
|
||||
},
|
||||
if !l.canTakeLock(args.Resources...) {
|
||||
// Not all locks can be taken on resources,
|
||||
// reject it completely.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// No locks held on the all resources, so claim write
|
||||
// lock on all resources at once.
|
||||
for _, resource := range args.Resources {
|
||||
l.lockMap[resource] = []lockRequesterInfo{
|
||||
{
|
||||
Writer: true,
|
||||
Source: args.Source,
|
||||
UID: args.UID,
|
||||
Timestamp: UTCNow(),
|
||||
TimeLastCheck: UTCNow(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (l *localLocker) Unlock(args dsync.LockArgs) (reply bool, err error) {
|
||||
@@ -138,28 +144,33 @@ func (l *localLocker) removeEntry(name, uid string, lri *[]lockRequesterInfo) bo
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *localLocker) RLock(args dsync.LockArgs) (reply bool, err error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
lrInfo := lockRequesterInfo{
|
||||
Writer: false,
|
||||
Source: args.Source,
|
||||
UID: args.UID,
|
||||
Timestamp: UTCNow(),
|
||||
TimeLastCheck: UTCNow(),
|
||||
}
|
||||
resource := args.Resources[0]
|
||||
if lri, ok := l.lockMap[resource]; ok {
|
||||
if reply = !isWriteLock(lri); reply {
|
||||
// Unless there is a write lock
|
||||
l.lockMap[resource] = append(l.lockMap[resource], lrInfo)
|
||||
func (l *localLocker) RLock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
default:
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
lrInfo := lockRequesterInfo{
|
||||
Writer: false,
|
||||
Source: args.Source,
|
||||
UID: args.UID,
|
||||
Timestamp: UTCNow(),
|
||||
TimeLastCheck: UTCNow(),
|
||||
}
|
||||
} else {
|
||||
// No locks held on the given name, so claim (first) read lock
|
||||
l.lockMap[resource] = []lockRequesterInfo{lrInfo}
|
||||
reply = true
|
||||
resource := args.Resources[0]
|
||||
if lri, ok := l.lockMap[resource]; ok {
|
||||
if reply = !isWriteLock(lri); reply {
|
||||
// Unless there is a write lock
|
||||
l.lockMap[resource] = append(l.lockMap[resource], lrInfo)
|
||||
}
|
||||
} else {
|
||||
// No locks held on the given name, so claim (first) read lock
|
||||
l.lockMap[resource] = []lockRequesterInfo{lrInfo}
|
||||
reply = true
|
||||
}
|
||||
return reply, nil
|
||||
}
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (l *localLocker) RUnlock(args dsync.LockArgs) (reply bool, err error) {
|
||||
@@ -202,22 +213,27 @@ func (l *localLocker) IsOnline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *localLocker) Expired(args dsync.LockArgs) (expired bool, err error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
func (l *localLocker) Expired(ctx context.Context, args dsync.LockArgs) (expired bool, err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
default:
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
// Lock found, proceed to verify if belongs to given uid.
|
||||
for _, resource := range args.Resources {
|
||||
if lri, ok := l.lockMap[resource]; ok {
|
||||
// Check whether uid is still active
|
||||
for _, entry := range lri {
|
||||
if entry.UID == args.UID {
|
||||
return false, nil
|
||||
// Lock found, proceed to verify if belongs to given uid.
|
||||
for _, resource := range args.Resources {
|
||||
if lri, ok := l.lockMap[resource]; ok {
|
||||
// Check whether uid is still active
|
||||
for _, entry := range lri {
|
||||
if entry.UID == args.UID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Similar to removeEntry but only removes an entry only if the lock entry exists in map.
|
||||
|
||||
+12
-12
@@ -58,7 +58,7 @@ func (client *lockRESTClient) String() string {
|
||||
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
|
||||
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
|
||||
// after verifying format.json
|
||||
func (client *lockRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
|
||||
func (client *lockRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
|
||||
if values == nil {
|
||||
values = make(url.Values)
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func (client *lockRESTClient) Close() error {
|
||||
}
|
||||
|
||||
// restCall makes a call to the lock REST server.
|
||||
func (client *lockRESTClient) restCall(call string, args dsync.LockArgs) (reply bool, err error) {
|
||||
func (client *lockRESTClient) restCall(ctx context.Context, call string, args dsync.LockArgs) (reply bool, err error) {
|
||||
values := url.Values{}
|
||||
values.Set(lockRESTUID, args.UID)
|
||||
values.Set(lockRESTSource, args.Source)
|
||||
@@ -92,7 +92,7 @@ func (client *lockRESTClient) restCall(call string, args dsync.LockArgs) (reply
|
||||
buffer.WriteString(resource)
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
respBody, err := client.call(call, values, &buffer, -1)
|
||||
respBody, err := client.callWithContext(ctx, call, values, &buffer, -1)
|
||||
defer http.DrainBody(respBody)
|
||||
switch err {
|
||||
case nil:
|
||||
@@ -105,28 +105,28 @@ func (client *lockRESTClient) restCall(call string, args dsync.LockArgs) (reply
|
||||
}
|
||||
|
||||
// RLock calls read lock REST API.
|
||||
func (client *lockRESTClient) RLock(args dsync.LockArgs) (reply bool, err error) {
|
||||
return client.restCall(lockRESTMethodRLock, args)
|
||||
func (client *lockRESTClient) RLock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
|
||||
return client.restCall(ctx, lockRESTMethodRLock, args)
|
||||
}
|
||||
|
||||
// Lock calls lock REST API.
|
||||
func (client *lockRESTClient) Lock(args dsync.LockArgs) (reply bool, err error) {
|
||||
return client.restCall(lockRESTMethodLock, args)
|
||||
func (client *lockRESTClient) Lock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
|
||||
return client.restCall(ctx, lockRESTMethodLock, args)
|
||||
}
|
||||
|
||||
// RUnlock calls read unlock REST API.
|
||||
func (client *lockRESTClient) RUnlock(args dsync.LockArgs) (reply bool, err error) {
|
||||
return client.restCall(lockRESTMethodRUnlock, args)
|
||||
return client.restCall(context.Background(), lockRESTMethodRUnlock, args)
|
||||
}
|
||||
|
||||
// Unlock calls write unlock RPC.
|
||||
func (client *lockRESTClient) Unlock(args dsync.LockArgs) (reply bool, err error) {
|
||||
return client.restCall(lockRESTMethodUnlock, args)
|
||||
return client.restCall(context.Background(), lockRESTMethodUnlock, args)
|
||||
}
|
||||
|
||||
// Expired calls expired handler to check if lock args have expired.
|
||||
func (client *lockRESTClient) Expired(args dsync.LockArgs) (expired bool, err error) {
|
||||
return client.restCall(lockRESTMethodExpired, args)
|
||||
func (client *lockRESTClient) Expired(ctx context.Context, args dsync.LockArgs) (expired bool, err error) {
|
||||
return client.restCall(ctx, lockRESTMethodExpired, args)
|
||||
}
|
||||
|
||||
func newLockAPI(endpoint Endpoint) dsync.NetLocker {
|
||||
@@ -153,7 +153,7 @@ func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
|
||||
}
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/pkg/dsync"
|
||||
@@ -34,12 +35,12 @@ func TestLockRESTlient(t *testing.T) {
|
||||
}
|
||||
|
||||
// Attempt all calls.
|
||||
_, err = lkClient.RLock(dsync.LockArgs{})
|
||||
_, err = lkClient.RLock(context.Background(), dsync.LockArgs{})
|
||||
if err == nil {
|
||||
t.Fatal("Expected for Rlock to fail")
|
||||
}
|
||||
|
||||
_, err = lkClient.Lock(dsync.LockArgs{})
|
||||
_, err = lkClient.Lock(context.Background(), dsync.LockArgs{})
|
||||
if err == nil {
|
||||
t.Fatal("Expected for Lock to fail")
|
||||
}
|
||||
|
||||
+18
-18
@@ -96,7 +96,7 @@ func (l *lockRESTServer) LockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
success, err := l.ll.Lock(args)
|
||||
success, err := l.ll.Lock(r.Context(), args)
|
||||
if err == nil && !success {
|
||||
err = errLockConflict
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (l *lockRESTServer) RLockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
success, err := l.ll.RLock(args)
|
||||
success, err := l.ll.RLock(r.Context(), args)
|
||||
if err == nil && !success {
|
||||
err = errLockConflict
|
||||
}
|
||||
@@ -185,20 +185,14 @@ func (l *lockRESTServer) ExpiredHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
l.ll.mutex.Lock()
|
||||
defer l.ll.mutex.Unlock()
|
||||
|
||||
// Lock found, proceed to verify if belongs to given uid.
|
||||
for _, resource := range args.Resources {
|
||||
if lri, ok := l.ll.lockMap[resource]; ok {
|
||||
// Check whether uid is still active
|
||||
for _, entry := range lri {
|
||||
if entry.UID == args.UID {
|
||||
l.writeErrorResponse(w, errLockNotExpired)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
expired, err := l.ll.Expired(r.Context(), args)
|
||||
if err != nil {
|
||||
l.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
if !expired {
|
||||
l.writeErrorResponse(w, errLockNotExpired)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +213,10 @@ func getLongLivedLocks(interval time.Duration) map[Endpoint][]nameLockRequesterI
|
||||
for idx := range lriArray {
|
||||
// Check whether enough time has gone by since last check
|
||||
if time.Since(lriArray[idx].TimeLastCheck) >= interval {
|
||||
rslt = append(rslt, nameLockRequesterInfoPair{name: name, lri: lriArray[idx]})
|
||||
rslt = append(rslt, nameLockRequesterInfoPair{
|
||||
name: name,
|
||||
lri: lriArray[idx],
|
||||
})
|
||||
lriArray[idx].TimeLastCheck = UTCNow()
|
||||
}
|
||||
}
|
||||
@@ -254,12 +251,15 @@ func lockMaintenance(ctx context.Context, interval time.Duration) error {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, 5*time.Second)
|
||||
|
||||
// Call back to original server verify whether the lock is
|
||||
// still active (based on name & uid)
|
||||
expired, err := c.Expired(dsync.LockArgs{
|
||||
expired, err := c.Expired(ctx, dsync.LockArgs{
|
||||
UID: nlrip.lri.UID,
|
||||
Resources: []string{nlrip.name},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
nlripsMap[nlrip.name]++
|
||||
c.Close()
|
||||
|
||||
@@ -378,6 +378,10 @@ func bucketUsageMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
return
|
||||
}
|
||||
|
||||
if globalIsGateway {
|
||||
return
|
||||
}
|
||||
|
||||
// Crawler disabled, nothing to do.
|
||||
if env.Get(envDataUsageCrawlConf, config.EnableOn) != config.EnableOn {
|
||||
return
|
||||
|
||||
@@ -198,11 +198,14 @@ type ObjectInfo struct {
|
||||
PutObjReader *PutObjReader `json:"-"`
|
||||
|
||||
metadataOnly bool
|
||||
versionOnly bool // adds a new version, only used by CopyObject
|
||||
keyRotation bool
|
||||
|
||||
// Date and time when the object was last accessed.
|
||||
AccTime time.Time
|
||||
|
||||
Legacy bool // indicates object on disk is in legacy data format
|
||||
|
||||
// backendType indicates which backend filled this structure
|
||||
backendType BackendType
|
||||
}
|
||||
|
||||
@@ -362,11 +362,39 @@ func (e BucketReplicationDestinationNotFound) Error() string {
|
||||
return "Destination bucket does not exist: " + e.Bucket
|
||||
}
|
||||
|
||||
// BucketReplicationTargetNotFound replication target does not exist.
|
||||
type BucketReplicationTargetNotFound GenericError
|
||||
// BucketRemoteTargetNotFound remote target does not exist.
|
||||
type BucketRemoteTargetNotFound GenericError
|
||||
|
||||
func (e BucketReplicationTargetNotFound) Error() string {
|
||||
return "Replication target not found: " + e.Bucket
|
||||
func (e BucketRemoteTargetNotFound) Error() string {
|
||||
return "Remote target not found: " + e.Bucket
|
||||
}
|
||||
|
||||
// BucketRemoteAlreadyExists remote already exists for this target type.
|
||||
type BucketRemoteAlreadyExists GenericError
|
||||
|
||||
func (e BucketRemoteAlreadyExists) Error() string {
|
||||
return "Remote already exists for this bucket: " + e.Bucket
|
||||
}
|
||||
|
||||
// BucketRemoteArnTypeInvalid arn type for remote is not valid.
|
||||
type BucketRemoteArnTypeInvalid GenericError
|
||||
|
||||
func (e BucketRemoteArnTypeInvalid) Error() string {
|
||||
return "Remote ARN type not valid: " + e.Bucket
|
||||
}
|
||||
|
||||
// BucketRemoteArnInvalid arn needs to be specified.
|
||||
type BucketRemoteArnInvalid GenericError
|
||||
|
||||
func (e BucketRemoteArnInvalid) Error() string {
|
||||
return "Remote ARN has invalid format: " + e.Bucket
|
||||
}
|
||||
|
||||
// BucketRemoteRemoveDisallowed when replication configuration exists
|
||||
type BucketRemoteRemoveDisallowed GenericError
|
||||
|
||||
func (e BucketRemoteRemoveDisallowed) Error() string {
|
||||
return "Replication configuration exists with this ARN:" + e.Bucket
|
||||
}
|
||||
|
||||
// BucketReplicationTargetNotVersioned replication target does not have versioning enabled.
|
||||
|
||||
@@ -1177,7 +1177,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
if globalBucketReplicationSys.mustReplicate(ctx, r, dstBucket, dstObject, srcInfo.UserDefined, srcInfo.ReplicationStatus.String()) {
|
||||
if mustReplicate(ctx, r, dstBucket, dstObject, srcInfo.UserDefined, srcInfo.ReplicationStatus.String()) {
|
||||
srcInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
|
||||
}
|
||||
|
||||
@@ -1258,7 +1258,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
|
||||
response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime)
|
||||
encodedSuccessResponse := encodeResponse(response)
|
||||
if globalBucketReplicationSys.mustReplicate(ctx, r, dstBucket, dstObject, objInfo.UserDefined, objInfo.ReplicationStatus.String()) {
|
||||
if mustReplicate(ctx, r, dstBucket, dstObject, objInfo.UserDefined, objInfo.ReplicationStatus.String()) {
|
||||
defer replicateObject(ctx, dstBucket, dstObject, objInfo.VersionID, objectAPI, &eventArgs{
|
||||
EventName: event.ObjectCreatedCopy,
|
||||
BucketName: dstBucket,
|
||||
@@ -1511,7 +1511,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
if globalBucketReplicationSys.mustReplicate(ctx, r, bucket, object, metadata, "") {
|
||||
if mustReplicate(ctx, r, bucket, object, metadata, "") {
|
||||
metadata[xhttp.AmzBucketReplicationStatus] = string(replication.Pending)
|
||||
}
|
||||
if r.Header.Get(xhttp.AmzBucketReplicationStatus) == replication.Replica.String() {
|
||||
@@ -1574,7 +1574,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
}
|
||||
if globalBucketReplicationSys.mustReplicate(ctx, r, bucket, object, metadata, "") {
|
||||
if mustReplicate(ctx, r, bucket, object, metadata, "") {
|
||||
defer replicateObject(ctx, bucket, object, objInfo.VersionID, objectAPI, &eventArgs{
|
||||
EventName: event.ObjectCreatedPut,
|
||||
BucketName: bucket,
|
||||
@@ -1696,7 +1696,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
if globalBucketReplicationSys.mustReplicate(ctx, r, bucket, object, metadata, "") {
|
||||
if mustReplicate(ctx, r, bucket, object, metadata, "") {
|
||||
metadata[xhttp.AmzBucketReplicationStatus] = string(replication.Pending)
|
||||
}
|
||||
// We need to preserve the encryption headers set in EncryptRequest,
|
||||
@@ -2615,7 +2615,9 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
w.Write(encodedErrorResponse)
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
|
||||
setEventStreamHeaders(w)
|
||||
|
||||
w = &whiteSpaceWriter{ResponseWriter: w, Flusher: w.(http.Flusher)}
|
||||
completeDoneCh := sendWhiteSpace(w)
|
||||
objInfo, err := completeMultiPartUpload(ctx, bucket, object, uploadID, completeParts, opts)
|
||||
@@ -2647,7 +2649,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
}
|
||||
|
||||
setPutObjHeaders(w, objInfo, false)
|
||||
if globalBucketReplicationSys.mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String()) {
|
||||
if mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String()) {
|
||||
defer replicateObject(ctx, bucket, object, objInfo.VersionID, objectAPI, &eventArgs{
|
||||
EventName: event.ObjectCreatedCompleteMultipartUpload,
|
||||
BucketName: bucket,
|
||||
|
||||
+25
-31
@@ -23,7 +23,6 @@ import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -122,19 +121,22 @@ type progressReader struct {
|
||||
|
||||
func (p *progressReader) Read(b []byte) (int, error) {
|
||||
n, err := p.r.Read(b)
|
||||
if err != nil && err != io.EOF {
|
||||
return n, err
|
||||
if n >= 0 {
|
||||
p.progressChan <- int64(n)
|
||||
}
|
||||
p.progressChan <- int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
type nullReader struct{}
|
||||
|
||||
func (r *nullReader) Read(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) doNetOBDTest(ctx context.Context, dataSize int64, threadCount uint) (info madmin.NetOBDInfo, err error) {
|
||||
latencies := []float64{}
|
||||
throughputs := []float64{}
|
||||
|
||||
buf := make([]byte, dataSize)
|
||||
|
||||
buflimiter := make(chan struct{}, threadCount)
|
||||
errChan := make(chan error, threadCount)
|
||||
|
||||
@@ -163,7 +165,7 @@ func (client *peerRESTClient) doNetOBDTest(ctx context.Context, dataSize int64,
|
||||
}
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
var wg sync.WaitGroup
|
||||
finish := func() {
|
||||
<-buflimiter
|
||||
wg.Done()
|
||||
@@ -183,27 +185,26 @@ func (client *peerRESTClient) doNetOBDTest(ctx context.Context, dataSize int64,
|
||||
}
|
||||
|
||||
go func(i int) {
|
||||
bufReader := bytes.NewReader(buf)
|
||||
bufReadCloser := ioutil.NopCloser(&progressReader{
|
||||
r: bufReader,
|
||||
progress := &progressReader{
|
||||
r: io.LimitReader(&nullReader{}, dataSize),
|
||||
progressChan: transferChan,
|
||||
})
|
||||
}
|
||||
start := time.Now()
|
||||
before := atomic.LoadInt64(&totalTransferred)
|
||||
|
||||
ctx, cancel := context.WithTimeout(innerCtx, 10*time.Second)
|
||||
defer cancel()
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodNetOBDInfo, nil, bufReadCloser, dataSize)
|
||||
if err != nil {
|
||||
|
||||
if netErr, ok := err.(*rest.NetworkError); ok {
|
||||
if urlErr, ok := netErr.Err.(*url.Error); ok {
|
||||
if urlErr.Err.Error() == context.DeadlineExceeded.Error() {
|
||||
slowSample()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
}
|
||||
// Turn off healthCheckFn for OBD tests to cater for higher load on the peers.
|
||||
clnt := newPeerRESTClient(client.host)
|
||||
clnt.restClient.HealthCheckFn = nil
|
||||
|
||||
respBody, err := clnt.callWithContext(ctx, peerRESTMethodNetOBDInfo, nil, progress, dataSize)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
slowSample()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
errChan <- err
|
||||
@@ -329,15 +330,8 @@ func (client *peerRESTClient) NetOBDInfo(ctx context.Context) (info madmin.NetOB
|
||||
continue
|
||||
}
|
||||
|
||||
if netErr, ok := err.(*rest.NetworkError); ok {
|
||||
if urlErr, ok := netErr.Err.(*url.Error); ok {
|
||||
if urlErr.Err.Error() == context.Canceled.Error() {
|
||||
continue
|
||||
}
|
||||
if urlErr.Err.Error() == context.DeadlineExceeded.Error() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return info, err
|
||||
@@ -880,7 +874,7 @@ func newPeerRESTClient(peer *xnet.Host) *peerRESTClient {
|
||||
}
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
|
||||
// Construct a new health function.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/sync/errgroup"
|
||||
@@ -213,7 +213,10 @@ func IsServerResolvable(endpoint Endpoint) error {
|
||||
}
|
||||
defer httpClient.CloseIdleConnections()
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := httpClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -253,10 +256,7 @@ func connectLoadInitFormats(retryCount int, firstDisk bool, endpoints Endpoints,
|
||||
formatConfigs, sErrs := loadFormatErasureAll(storageDisks, false)
|
||||
// Check if we have
|
||||
for i, sErr := range sErrs {
|
||||
if _, ok := formatCriticalErrors[sErr]; ok {
|
||||
return nil, nil, config.ErrCorruptedBackend(err).Hint(fmt.Sprintf("Clear any pre-existing content on %s", endpoints[i]))
|
||||
}
|
||||
// not critical error but still print the error, nonetheless, which is perhaps unhandled
|
||||
// print the error, nonetheless, which is perhaps unhandled
|
||||
if sErr != errUnformattedDisk && sErr != errDiskNotFound && retryCount >= 5 {
|
||||
if sErr != nil {
|
||||
logger.Info("Unable to read 'format.json' from %s: %v\n", endpoints[i], sErr)
|
||||
|
||||
+11
-1
@@ -85,10 +85,20 @@ const (
|
||||
querySep = "?"
|
||||
)
|
||||
|
||||
type restError string
|
||||
|
||||
func (e restError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e restError) Timeout() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CallWithContext - make a REST call with context.
|
||||
func (c *Client) CallWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
|
||||
if !c.IsOnline() {
|
||||
return nil, &NetworkError{Err: errors.New("remote server offline")}
|
||||
return nil, &NetworkError{Err: &url.Error{Op: method, URL: c.url.String(), Err: restError("remote server offline")}}
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, c.url.String()+method+querySep+values.Encode(), body)
|
||||
if err != nil {
|
||||
|
||||
+6
-5
@@ -170,7 +170,7 @@ func newAllSubsystems() {
|
||||
globalBucketVersioningSys = NewBucketVersioningSys()
|
||||
|
||||
// Create new bucket replication subsytem
|
||||
globalBucketReplicationSys = NewBucketReplicationSys()
|
||||
globalBucketTargetSys = NewBucketTargetSys()
|
||||
}
|
||||
|
||||
func initSafeMode(ctx context.Context, newObject ObjectLayer) (err error) {
|
||||
@@ -229,7 +229,7 @@ func initSafeMode(ctx context.Context, newObject ObjectLayer) (err error) {
|
||||
for range retry.NewTimer(retryCtx) {
|
||||
// let one of the server acquire the lock, if not let them timeout.
|
||||
// which shall be retried again by this loop.
|
||||
if err = txnLk.GetLock(newDynamicTimeout(1*time.Second, 3*time.Second)); err != nil {
|
||||
if err = txnLk.GetLock(newDynamicTimeout(3*time.Second, 3*time.Second)); err != nil {
|
||||
logger.Info("Waiting for all MinIO sub-systems to be initialized.. trying to acquire lock")
|
||||
continue
|
||||
}
|
||||
@@ -340,10 +340,11 @@ func initAllSubsystems(ctx context.Context, newObject ObjectLayer) (err error) {
|
||||
return fmt.Errorf("Unable to initialize notification system: %w", err)
|
||||
}
|
||||
|
||||
// Initialize bucket replication sub-system.
|
||||
if err = globalBucketReplicationSys.Init(GlobalContext, buckets, newObject); err != nil {
|
||||
return fmt.Errorf("Unable to initialize bucket replication sub-system: %w", err)
|
||||
// Initialize bucket targets sub-system.
|
||||
if err = globalBucketTargetSys.Init(GlobalContext, buckets, newObject); err != nil {
|
||||
return fmt.Errorf("Unable to initialize bucket target sub-system: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -19,10 +19,10 @@ package cmd
|
||||
import "os"
|
||||
|
||||
// errUnexpected - unexpected error, requires manual intervention.
|
||||
var errUnexpected = StorageErr("Unexpected error, please report this issue at https://github.com/minio/minio/issues")
|
||||
var errUnexpected = StorageErr("unexpected error, please report this issue at https://github.com/minio/minio/issues")
|
||||
|
||||
// errCorruptedFormat - corrupted backend format.
|
||||
var errCorruptedFormat = StorageErr("corrupted backend format, please join https://slack.min.io for assistance")
|
||||
var errCorruptedFormat = StorageErr("corrupted backend format, specified disk mount has unexpected previous content")
|
||||
|
||||
// errUnformattedDisk - unformatted disk found.
|
||||
var errUnformattedDisk = StorageErr("unformatted disk found")
|
||||
@@ -142,5 +142,14 @@ func osErrToFileErr(err error) error {
|
||||
if isSysErrHandleInvalid(err) {
|
||||
return errFileNotFound
|
||||
}
|
||||
if isSysErrIO(err) {
|
||||
return errFaultyDisk
|
||||
}
|
||||
if isSysErrInvalidArg(err) {
|
||||
return errUnsupportedDisk
|
||||
}
|
||||
if isSysErrNoSpace(err) {
|
||||
return errDiskFull
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -667,7 +667,7 @@ func newStorageRESTClient(endpoint Endpoint) *storageRESTClient {
|
||||
}
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
restClient.HealthCheckInterval = 500 * time.Millisecond
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
|
||||
@@ -151,7 +151,8 @@ func (s *storageRESTServer) CrawlAndGetDataUsageHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
|
||||
var cache dataUsageCache
|
||||
err := cache.deserialize(r.Body)
|
||||
if err != nil {
|
||||
@@ -520,7 +521,7 @@ func (s *storageRESTServer) WalkSplunkHandler(w http.ResponseWriter, r *http.Req
|
||||
dirPath := vars[storageRESTDirPath]
|
||||
markerPath := vars[storageRESTMarkerPath]
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
encoder := gob.NewEncoder(w)
|
||||
|
||||
fch, err := s.storage.WalkSplunk(volume, dirPath, markerPath, r.Context().Done())
|
||||
@@ -548,7 +549,7 @@ func (s *storageRESTServer) WalkVersionsHandler(w http.ResponseWriter, r *http.R
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
encoder := gob.NewEncoder(w)
|
||||
|
||||
fch, err := s.storage.WalkVersions(volume, dirPath, markerPath, recursive, r.Context().Done())
|
||||
@@ -576,7 +577,7 @@ func (s *storageRESTServer) WalkHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
encoder := gob.NewEncoder(w)
|
||||
|
||||
fch, err := s.storage.Walk(volume, dirPath, markerPath, recursive, r.Context().Done())
|
||||
@@ -659,7 +660,7 @@ func (s *storageRESTServer) DeleteVersionsHandler(w http.ResponseWriter, r *http
|
||||
|
||||
dErrsResp := &DeleteVersionsErrsResp{Errs: make([]error, totalVersions)}
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
encoder := gob.NewEncoder(w)
|
||||
done := keepHTTPResponseAlive(w)
|
||||
errs := s.storage.DeleteVersions(volume, versions)
|
||||
@@ -805,7 +806,7 @@ func (s *storageRESTServer) VerifyFileHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(xhttp.ContentType, "text/event-stream")
|
||||
setEventStreamHeaders(w)
|
||||
encoder := gob.NewEncoder(w)
|
||||
done := keepHTTPResponseAlive(w)
|
||||
err = s.storage.VerifyFile(volume, filePath, fi)
|
||||
|
||||
+1
-1
@@ -328,7 +328,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
policyName = globalIAMSys.currentPolicies(strings.Join(policySet.ToSlice(), ","))
|
||||
}
|
||||
|
||||
if policyName == "" {
|
||||
if policyName == "" && globalPolicyOPA == nil {
|
||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID()))
|
||||
return
|
||||
}
|
||||
|
||||
+25
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2015, 2016, 2017 MinIO, Inc.
|
||||
* MinIO Cloud Storage, (C) 2015-2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -170,7 +170,7 @@ const (
|
||||
// (Acceptable values range from 1 to 10000 inclusive)
|
||||
globalMaxPartID = 10000
|
||||
|
||||
// Default values used while communicating for internode communication.
|
||||
// Default values used while communicating for gateway communication
|
||||
defaultDialTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
@@ -449,6 +449,29 @@ func ToS3ETag(etag string) string {
|
||||
return etag
|
||||
}
|
||||
|
||||
func newInternodeHTTPTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() *http.Transport {
|
||||
// For more details about various values used here refer
|
||||
// https://golang.org/pkg/net/http/#Transport documentation
|
||||
tr := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: xhttp.NewInternodeDialContext(dialTimeout),
|
||||
MaxIdleConnsPerHost: 16,
|
||||
MaxIdleConns: 16,
|
||||
IdleConnTimeout: 1 * time.Minute,
|
||||
ResponseHeaderTimeout: 3 * time.Minute, // Set conservative timeouts for MinIO internode.
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 10 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
// Go net/http automatically unzip if content-type is
|
||||
// gzip disable this feature, as we are always interested
|
||||
// in raw stream.
|
||||
DisableCompression: true,
|
||||
}
|
||||
return func() *http.Transport {
|
||||
return tr
|
||||
}
|
||||
}
|
||||
|
||||
func newCustomHTTPTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() *http.Transport {
|
||||
// For more details about various values used here refer
|
||||
// https://golang.org/pkg/net/http/#Transport documentation
|
||||
|
||||
@@ -442,11 +442,6 @@ func (z *xlMetaV2) DeleteVersion(fi FileInfo) (string, bool, error) {
|
||||
z.Versions = append(z.Versions[:i], z.Versions[i+1:]...)
|
||||
return version.ObjectV1.DataDir, len(z.Versions) == 0, nil
|
||||
}
|
||||
case ObjectType:
|
||||
if bytes.Equal(version.ObjectV2.VersionID[:], uv[:]) {
|
||||
z.Versions = append(z.Versions[:i], z.Versions[i+1:]...)
|
||||
return uuid.UUID(version.ObjectV2.DataDir).String(), len(z.Versions) == 0, nil
|
||||
}
|
||||
case DeleteType:
|
||||
if bytes.Equal(version.DeleteMarker.VersionID[:], uv[:]) {
|
||||
z.Versions = append(z.Versions[:i], z.Versions[i+1:]...)
|
||||
@@ -454,6 +449,38 @@ func (z *xlMetaV2) DeleteVersion(fi FileInfo) (string, bool, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findDataDir := func(dataDir [16]byte, versions []xlMetaV2Version) int {
|
||||
var sameDataDirCount int
|
||||
for _, version := range versions {
|
||||
switch version.Type {
|
||||
case ObjectType:
|
||||
if bytes.Equal(version.ObjectV2.DataDir[:], dataDir[:]) {
|
||||
sameDataDirCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
return sameDataDirCount
|
||||
}
|
||||
|
||||
for i, version := range z.Versions {
|
||||
if !version.Valid() {
|
||||
return "", false, errFileCorrupt
|
||||
}
|
||||
switch version.Type {
|
||||
case ObjectType:
|
||||
if bytes.Equal(version.ObjectV2.VersionID[:], uv[:]) {
|
||||
z.Versions = append(z.Versions[:i], z.Versions[i+1:]...)
|
||||
if findDataDir(version.ObjectV2.DataDir, z.Versions) > 0 {
|
||||
// Found that another version references the same dataDir
|
||||
// we shouldn't remove it, and only remove the version instead
|
||||
return "", len(z.Versions) == 0, nil
|
||||
}
|
||||
return uuid.UUID(version.ObjectV2.DataDir).String(), len(z.Versions) == 0, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false, errFileVersionNotFound
|
||||
}
|
||||
|
||||
|
||||
+58
-65
@@ -40,16 +40,17 @@ import (
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/klauspost/readahead"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/disk"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
"github.com/minio/minio/pkg/mountinfo"
|
||||
)
|
||||
|
||||
const (
|
||||
nullVersionID = "null"
|
||||
diskMinFreeSpace = 900 * humanize.MiByte // Min 900MiB free space.
|
||||
diskMinTotalSpace = diskMinFreeSpace // Min 900MiB total space.
|
||||
diskMinTotalSpace = 900 * humanize.MiByte // Min 900MiB total space.
|
||||
readBlockSize = 4 * humanize.MiByte // Default read block size 4MiB.
|
||||
|
||||
// On regular files bigger than this;
|
||||
@@ -94,6 +95,8 @@ type xlStorage struct {
|
||||
|
||||
pool sync.Pool
|
||||
|
||||
globalSync bool
|
||||
|
||||
diskMount bool // indicates if the path is an actual mount.
|
||||
|
||||
diskID string
|
||||
@@ -175,6 +178,7 @@ func getValidPath(path string, requireDirectIO bool) (string, error) {
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
if err = checkDiskMinTotal(di); err != nil {
|
||||
return path, err
|
||||
}
|
||||
@@ -245,7 +249,8 @@ func newXLStorage(path string, hostname string) (*xlStorage, error) {
|
||||
return &b
|
||||
},
|
||||
},
|
||||
diskMount: mountinfo.IsLikelyMountPoint(path),
|
||||
globalSync: env.Get(config.EnvFSOSync, config.EnableOn) == config.EnableOn,
|
||||
diskMount: mountinfo.IsLikelyMountPoint(path),
|
||||
// Allow disk usage crawler to run with up to 2 concurrent
|
||||
// I/O ops, if and when activeIOCount reaches this
|
||||
// value disk usage routine suspends the crawler
|
||||
@@ -276,13 +281,6 @@ func getDiskInfo(diskPath string) (di disk.Info, err error) {
|
||||
return di, err
|
||||
}
|
||||
|
||||
// List of operating systems where we ignore disk space
|
||||
// verification.
|
||||
var ignoreDiskFreeOS = []string{
|
||||
globalWindowsOSName,
|
||||
globalNetBSDOSName,
|
||||
}
|
||||
|
||||
// check if disk total has minimum required size.
|
||||
func checkDiskMinTotal(di disk.Info) (err error) {
|
||||
// Remove 5% from total space for cumulative disk space
|
||||
@@ -294,46 +292,6 @@ func checkDiskMinTotal(di disk.Info) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if disk free has minimum required size.
|
||||
func checkDiskMinFree(di disk.Info) error {
|
||||
// Remove 5% from free space for cumulative disk space used for journalling, inodes etc.
|
||||
availableDiskSpace := float64(di.Free) * diskFillFraction
|
||||
if int64(availableDiskSpace) <= diskMinFreeSpace {
|
||||
return errDiskFull
|
||||
}
|
||||
|
||||
// Success.
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDiskFree verifies if disk path has sufficient minimum free disk space and files.
|
||||
func checkDiskFree(diskPath string, neededSpace int64) (err error) {
|
||||
// We don't validate disk space or inode utilization on windows.
|
||||
// Each windows call to 'GetVolumeInformationW' takes around
|
||||
// 3-5seconds. And StatDISK is not supported by Go for solaris
|
||||
// and netbsd.
|
||||
if contains(ignoreDiskFreeOS, runtime.GOOS) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var di disk.Info
|
||||
di, err = getDiskInfo(diskPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = checkDiskMinFree(di); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if we have enough space to store data
|
||||
if neededSpace > int64(float64(di.Free)*diskFillFraction) {
|
||||
return errDiskFull
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements stringer compatible interface.
|
||||
func (s *xlStorage) String() string {
|
||||
return s.diskPath
|
||||
@@ -505,6 +463,7 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
||||
// Somebody else got the lock first.
|
||||
return diskID, nil
|
||||
}
|
||||
|
||||
formatFile := pathJoin(s.diskPath, minioMetaBucket, formatConfigFile)
|
||||
fi, err := os.Stat(formatFile)
|
||||
if err != nil {
|
||||
@@ -520,8 +479,12 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
||||
} else if os.IsPermission(err) {
|
||||
return "", errDiskAccessDenied
|
||||
}
|
||||
return "", err
|
||||
logger.LogIf(GlobalContext, err) // log unexpected errors
|
||||
return "", errCorruptedFormat
|
||||
} else if os.IsPermission(err) {
|
||||
return "", errDiskAccessDenied
|
||||
}
|
||||
logger.LogIf(GlobalContext, err) // log unexpected errors
|
||||
return "", errCorruptedFormat
|
||||
}
|
||||
|
||||
@@ -533,13 +496,34 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
||||
|
||||
b, err := ioutil.ReadFile(formatFile)
|
||||
if err != nil {
|
||||
// If the disk is still not initialized.
|
||||
if os.IsNotExist(err) {
|
||||
_, err = os.Stat(s.diskPath)
|
||||
if err == nil {
|
||||
// Disk is present but missing `format.json`
|
||||
return "", errUnformattedDisk
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return "", errDiskNotFound
|
||||
} else if os.IsPermission(err) {
|
||||
return "", errDiskAccessDenied
|
||||
}
|
||||
logger.LogIf(GlobalContext, err) // log unexpected errors
|
||||
return "", errCorruptedFormat
|
||||
} else if os.IsPermission(err) {
|
||||
return "", errDiskAccessDenied
|
||||
}
|
||||
logger.LogIf(GlobalContext, err) // log unexpected errors
|
||||
return "", errCorruptedFormat
|
||||
}
|
||||
|
||||
format := &formatErasureV3{}
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err = json.Unmarshal(b, &format); err != nil {
|
||||
logger.LogIf(GlobalContext, err) // log unexpected errors
|
||||
return "", errCorruptedFormat
|
||||
}
|
||||
|
||||
s.diskID = format.Erasure.This
|
||||
s.formatFileInfo = fi
|
||||
s.formatLastCheck = time.Now()
|
||||
@@ -1237,8 +1221,10 @@ func (s *xlStorage) renameLegacyMetadata(volume, path string) error {
|
||||
// Renaming xl.json to xl.meta should be fully synced to disk.
|
||||
defer func() {
|
||||
if err == nil {
|
||||
// Sync to disk only upon success.
|
||||
globalSync()
|
||||
if s.globalSync {
|
||||
// Sync to disk only upon success.
|
||||
globalSync()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -1617,14 +1603,6 @@ func (s *xlStorage) CreateFile(volume, path string, fileSize int64, r io.Reader)
|
||||
atomic.AddInt32(&s.activeIOCount, -1)
|
||||
}()
|
||||
|
||||
// Validate if disk is indeed free.
|
||||
if err = checkDiskFree(s.diskPath, fileSize); err != nil {
|
||||
if isSysErrIO(err) {
|
||||
return errFaultyDisk
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1648,8 +1626,17 @@ func (s *xlStorage) CreateFile(volume, path string, fileSize int64, r io.Reader)
|
||||
// Create top level directories if they don't exist.
|
||||
// with mode 0777 mkdir honors system umask.
|
||||
if err = mkdirAll(slashpath.Dir(filePath), 0777); err != nil {
|
||||
if errors.Is(err, &os.PathError{}) {
|
||||
switch {
|
||||
case os.IsPermission(err):
|
||||
return errFileAccessDenied
|
||||
case os.IsExist(err):
|
||||
return errFileAccessDenied
|
||||
case isSysErrIO(err):
|
||||
return errFaultyDisk
|
||||
case isSysErrInvalidArg(err):
|
||||
return errUnsupportedDisk
|
||||
case isSysErrNoSpace(err):
|
||||
return errDiskFull
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1665,6 +1652,8 @@ func (s *xlStorage) CreateFile(volume, path string, fileSize int64, r io.Reader)
|
||||
return errFaultyDisk
|
||||
case isSysErrInvalidArg(err):
|
||||
return errUnsupportedDisk
|
||||
case isSysErrNoSpace(err):
|
||||
return errDiskFull
|
||||
default:
|
||||
return err
|
||||
}
|
||||
@@ -2122,8 +2111,10 @@ func (s *xlStorage) RenameData(srcVolume, srcPath, dataDir, dstVolume, dstPath s
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
|
||||
// Sync all the previous directory operations.
|
||||
globalSync()
|
||||
if s.globalSync {
|
||||
// Sync all the previous directory operations.
|
||||
globalSync()
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry == xlStorageFormatFile {
|
||||
@@ -2139,7 +2130,9 @@ func (s *xlStorage) RenameData(srcVolume, srcPath, dataDir, dstVolume, dstPath s
|
||||
}
|
||||
|
||||
// Sync all the metadata operations once renames are done.
|
||||
globalSync()
|
||||
if s.globalSync {
|
||||
globalSync()
|
||||
}
|
||||
}
|
||||
|
||||
var oldDstDataPath string
|
||||
|
||||
@@ -1766,55 +1766,3 @@ func TestCheckDiskTotalMin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks for restrictions for min free disk space and inodes.
|
||||
func TestCheckDiskFreeMin(t *testing.T) {
|
||||
testCases := []struct {
|
||||
diskInfo disk.Info
|
||||
err error
|
||||
}{
|
||||
// Test 1 - when fstype is nfs.
|
||||
{
|
||||
diskInfo: disk.Info{
|
||||
Free: diskMinTotalSpace * 3,
|
||||
FSType: "NFS",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
// Test 2 - when fstype is xfs and total inodes are less than 10k.
|
||||
{
|
||||
diskInfo: disk.Info{
|
||||
Free: diskMinTotalSpace * 3,
|
||||
FSType: "XFS",
|
||||
Files: 9999,
|
||||
Ffree: 9999,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
// Test 3 - when fstype is btrfs and total inodes are empty.
|
||||
{
|
||||
diskInfo: disk.Info{
|
||||
Free: diskMinTotalSpace * 3,
|
||||
FSType: "BTRFS",
|
||||
Files: 0,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
// Test 4 - when fstype is xfs and total disk space is really small.
|
||||
{
|
||||
diskInfo: disk.Info{
|
||||
Free: diskMinTotalSpace - diskMinTotalSpace/1024,
|
||||
FSType: "XFS",
|
||||
Files: 9999,
|
||||
},
|
||||
err: errDiskFull,
|
||||
},
|
||||
}
|
||||
|
||||
// Validate all cases.
|
||||
for i, test := range testCases {
|
||||
if err := checkDiskMinFree(test.diskInfo); test.err != err {
|
||||
t.Errorf("Test %d: Expected error %s, got %s", i+1, test.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,28 +13,42 @@ To replicate objects in a bucket to a destination bucket on a target site either
|
||||
Create a replication target on the source cluster as shown below:
|
||||
|
||||
```
|
||||
mc admin bucket replication set myminio/srcbucket https://accessKey:secretKey@replica-endpoint:9000/destbucket --path ON --api s3v4
|
||||
Replication ARN = 'arn:minio:s3::dadddae7-f1d7-440f-b5d6-651aa9a8c8a7:*'
|
||||
mc admin bucket remote set myminio/srcbucket https://accessKey:secretKey@replica-endpoint:9000/destbucket --path ON --type "replica"
|
||||
Replication ARN = 'arn:minio:replica::c5be6b16-769d-432a-9ef1-4567081f3566:destbucket'
|
||||
```
|
||||
|
||||
Note that the admin needs *s3:GetReplicationConfigurationAction* permission on source cluster. The credential used at the destination requires *s3:ReplicateObject* permission. Once successfully created and authorized this generates a replication target ARN. The command below lists all the currently authorized replication targets:
|
||||
|
||||
```
|
||||
mc admin bucket remote myminio/srcbucket https://replica-endpoint:9000
|
||||
Replication ARN = 'arn:minio:s3::dadddae7-f1d7-440f-b5d6-651aa9a8c8a7:*'
|
||||
mc admin bucket remote list myminio/srcbucket --type "replica"
|
||||
Replication ARN = 'arn:minio:replica::c5be6b16-769d-432a-9ef1-4567081f3566:destbucket'
|
||||
```
|
||||
|
||||
The replication configuration can now be added to the source bucket by applying the json file with replication configuration. The ReplicationArn is passed in as a json element in the configuration.
|
||||
|
||||
```json
|
||||
{
|
||||
"ReplicationArn" : "arn:minio:s3::dadddae7-f1d7-440f-b5d6-651aa9a8c8a7:*",
|
||||
"ReplicationArn" :"arn:minio:replica::c5be6b16-769d-432a-9ef1-4567081f3566:destbucket",
|
||||
"Rules": [
|
||||
{
|
||||
"Status": "Enabled",
|
||||
"Priority": 1,
|
||||
"DeleteMarkerReplication": { "Status": "Disabled" },
|
||||
"Filter" : { "Prefix": "Tax"},
|
||||
"Filter" : {
|
||||
"And": {
|
||||
"Prefix": "Tax",
|
||||
"Tags": [
|
||||
{
|
||||
"Key": "Year",
|
||||
"Value": "2019"
|
||||
},
|
||||
{
|
||||
"Key": "Company",
|
||||
"Value": "AcmeCorp"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Destination": {
|
||||
"Bucket": "arn:aws:s3:::destbucket",
|
||||
"StorageClass": "STANDARD"
|
||||
@@ -45,7 +59,7 @@ The replication configuration can now be added to the source bucket by applying
|
||||
```
|
||||
|
||||
```
|
||||
mc bucket replicate myminio/srcbucket --config replicate-config.json
|
||||
mc replicate add myminio/srcbucket --priority 1 --prefix "Tax" --arn "arn:minio:replica::c5be6b16-769d-432a-9ef1-4567081f3566:destbucket" --tags "Year=2019&Company=AcmeCorp" --storage-class "STANDARD"
|
||||
Replication configuration applied successfully to myminio/srcbucket.
|
||||
```
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
This topic provides commands to set up different configurations of hosts, nodes, and drives. The examples provided here can be used as a starting point for other configurations.
|
||||
|
||||
1. [Standalone Deployment](#standalone-deployment)
|
||||
2. [Distributed Deployment](#distributed-deployment)
|
||||
1. [Standalone Deployment](#standalone-deployment)
|
||||
2. [Distributed Deployment](#distributed-deployment)
|
||||
3. [Cloud Scale Deployment](#cloud-scale-deployment)
|
||||
|
||||
## <a name="standalone-deployment"></a>1. Standalone Deployment
|
||||
|
||||
To host multiple tenants on a single machine, run one MinIO Server per tenant with a dedicated HTTPS port, configuration, and data directory.
|
||||
To host multiple tenants on a single machine, run one MinIO Server per tenant with a dedicated HTTPS port, configuration, and data directory.
|
||||
|
||||
### 1.1 Host Multiple Tenants on a Single Drive
|
||||
|
||||
@@ -27,16 +27,16 @@ minio server --address :9003 /data/tenant3
|
||||
Use the following commands to host 3 tenants on multiple drives:
|
||||
|
||||
```sh
|
||||
minio server --address :9001 /disk1/data/tenant1 /disk2/data/tenant1 /disk3/data/tenant1 /disk4/data/tenant1
|
||||
minio server --address :9002 /disk1/data/tenant2 /disk2/data/tenant2 /disk3/data/tenant2 /disk4/data/tenant2
|
||||
minio server --address :9003 /disk1/data/tenant3 /disk2/data/tenant3 /disk3/data/tenant3 /disk4/data/tenant3
|
||||
minio server --address :9001 /disk{1...4}/data/tenant1
|
||||
minio server --address :9002 /disk{1...4}/data/tenant2
|
||||
minio server --address :9003 /disk{1...4}/data/tenant3
|
||||
```
|
||||
|
||||

|
||||
|
||||
## <a name="distributed-deployment"></a>2. Distributed Deployment
|
||||
|
||||
To host multiple tenants in a distributed environment, run several distributed MinIO Server instances concurrently.
|
||||
To host multiple tenants in a distributed environment, run several distributed MinIO Server instances concurrently.
|
||||
|
||||
### 2.1 Host Multiple Tenants on Multiple Drives (Erasure Code)
|
||||
|
||||
@@ -45,18 +45,18 @@ Use the following commands to host 3 tenants on a 4-node distributed configurati
|
||||
```sh
|
||||
export MINIO_ACCESS_KEY=<TENANT1_ACCESS_KEY>
|
||||
export MINIO_SECRET_KEY=<TENANT1_SECRET_KEY>
|
||||
minio server --address :9001 http://192.168.10.11/data/tenant1 http://192.168.10.12/data/tenant1 http://192.168.10.13/data/tenant1 http://192.168.10.14/data/tenant1
|
||||
minio server --address :9001 http://192.168.10.1{1...4}/data/tenant1
|
||||
|
||||
export MINIO_ACCESS_KEY=<TENANT2_ACCESS_KEY>
|
||||
export MINIO_SECRET_KEY=<TENANT2_SECRET_KEY>
|
||||
minio server --address :9002 http://192.168.10.11/data/tenant2 http://192.168.10.12/data/tenant2 http://192.168.10.13/data/tenant2 http://192.168.10.14/data/tenant2
|
||||
minio server --address :9002 http://192.168.10.1{1...4}/data/tenant2
|
||||
|
||||
export MINIO_ACCESS_KEY=<TENANT3_ACCESS_KEY>
|
||||
export MINIO_SECRET_KEY=<TENANT3_SECRET_KEY>
|
||||
minio server --address :9003 http://192.168.10.11/data/tenant3 http://192.168.10.12/data/tenant3 http://192.168.10.13/data/tenant3 http://192.168.10.14/data/tenant3
|
||||
minio server --address :9003 http://192.168.10.1{1...4}/data/tenant3
|
||||
```
|
||||
|
||||
**Note:** Execute the commands on all 4 nodes.
|
||||
**Note:** Execute the commands on all 4 nodes.
|
||||
|
||||

|
||||
|
||||
@@ -64,4 +64,4 @@ minio server --address :9003 http://192.168.10.11/data/tenant3 http://192.168.10
|
||||
|
||||
## <a name="cloud-scale-deployment"></a>Cloud Scale Deployment
|
||||
|
||||
A container orchestration platform (e.g. Kubernetes, DC/OS, or Docker Swarm) is recommended for large-scale, multi-tenant MinIO deployments. See the [MinIO Deployment Quickstart Guide](https://docs.min.io/docs/minio-deployment-quickstart-guide) to get started with MinIO on orchestration platforms.
|
||||
A container orchestration platform (e.g. Kubernetes) is recommended for large-scale, multi-tenant MinIO deployments. See the [MinIO Deployment Quickstart Guide](https://docs.min.io/docs/minio-deployment-quickstart-guide) to get started with MinIO on orchestration platforms.
|
||||
|
||||
@@ -5,7 +5,7 @@ version: '3.7'
|
||||
# 9001 through 9004.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
volumes:
|
||||
- data1-1:/data1
|
||||
- data1-2:/data2
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
volumes:
|
||||
- data2-1:/data1
|
||||
- data2-2:/data2
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
volumes:
|
||||
- data3-1:/data1
|
||||
- data3-2:/data2
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
volumes:
|
||||
- data4-1:/data1
|
||||
- data4-2:/data2
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -83,7 +83,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-24T22-43-05Z
|
||||
image: minio/minio:RELEASE.2020-07-31T03-39-05Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export MINIO_ACCESS_KEY=minio
|
||||
export MINIO_SECRET_KEY=minio123
|
||||
export MINIO_IDENTITY_OPENID_CONFIG_URL=https://localhost:9443/oauth2/oidcdiscovery/.well-known/openid-configuration
|
||||
export MINIO_POLICY_OPA_URL=http://localhost:8181/v1/data/httpapi/authz
|
||||
export MINIO_ETCD_ENDPOINTS=http://localhost:2379
|
||||
+119
-32
@@ -1,63 +1,150 @@
|
||||
# MinIO Server `config.json` (v18) 指南 [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||
|
||||
MinIO server在默认情况下会将所有配置信息存到 `${HOME}/.minio/config.json` 文件中。 以下部分提供每个字段的详细说明以及如何自定义它们。一个完整的 `config.json` 在 [这里](https://raw.githubusercontent.com/minio/minio/master/docs/config/config.sample.json)
|
||||
# MinIO Server 配置指南 [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||
|
||||
## 配置目录
|
||||
|
||||
默认的配置目录是 `${HOME}/.minio`,你可以使用`--config-dir`命令行选项重写之。MinIO server在首次启动时会生成一个新的`config.json`,里面带有自动生成的访问凭据。
|
||||
|
||||
```sh
|
||||
minio server --config-dir /etc/minio /data
|
||||
```
|
||||
|
||||
截止到 MinIO `RELEASE.2018-08-02T23-11-36Z` 版本, MinIO server 的配置文件(`config.json`) 被存储在通过 `--config-dir` 指定的目录或者默认的 `${HOME}/.minio` 目录。 但是从 `RELEASE.2018-08-18T03-49-57Z` 版本之后, 配置文件 (仅仅), 已经被迁移到存储后端 (存储后端指的是启动一个服务器的时候,传递给MinIO server的目录)。
|
||||
|
||||
您可以使用`--config-dir`指定现有配置的位置, MinIO 会迁移 `config.json` 配置到你的存储后端。 迁移成功后,你当前 `--config-dir` 目录中的 `config.json` 将被重命名为 `config.json.deprecated`。 迁移后,所有现有配置都将得到保留。
|
||||
|
||||
此外,`--config-dir`现在是一个旧配置,计划在将来删除,因此请相应地更新本地startup和ansible脚本。
|
||||
|
||||
```sh
|
||||
minio server /data
|
||||
```
|
||||
|
||||
MinIO还使用管理员凭据对所有配置,IAM和策略内容进行加密。
|
||||
|
||||
### 证书目录
|
||||
TLS证书存在``${HOME}/.minio/certs``目录下,你需要将证书放在该目录下来启用`HTTPS` 。如果你是一个乐学上进的好青年,这里有一本免费的秘籍传授一你: [如何使用TLS安全的访问minio](https://docs.min.io/cn/how-to-secure-access-to-minio-server-with-tls).
|
||||
|
||||
以下是一个带来TLS证书的MinIO server的目录结构。
|
||||
以下是一个具有TLS证书的MinIO server的目录结构。
|
||||
|
||||
```sh
|
||||
$ tree ~/.minio
|
||||
$ mc tree --files ~/.minio
|
||||
/home/user1/.minio
|
||||
├── certs
|
||||
│ ├── CAs
|
||||
│ ├── private.key
|
||||
│ └── public.crt
|
||||
└── config.json
|
||||
└─ certs
|
||||
├─ CAs
|
||||
├─ private.key
|
||||
└─ public.crt
|
||||
```
|
||||
|
||||
### 配置参数
|
||||
#### 版本
|
||||
|参数|类型|描述|
|
||||
|:---|:---|:---|
|
||||
|``version``|_string_| `version`决定了配置文件的格式,任何老版本都会在启动时自动迁移到新版本中。 [请勿手动修改]|
|
||||
你可以使用`--certs-dir`命令行选项提供自定义certs目录。
|
||||
|
||||
#### 凭据
|
||||
|参数|类型|描述|
|
||||
|:---|:---|:---|
|
||||
|``credential``| |对象存储和Web访问的验证凭据。|
|
||||
|``credential.accessKey`` | _string_ | Access key长度最小是5个字符,你可以通过 `MINIO_ACCESS_KEY`环境变量进行修改|
|
||||
|``credential.secretKey`` | _string_ | Secret key长度最小是8个字符,你可以通过`MINIO_SECRET_KEY`环境变量进行修改|
|
||||
只能通过环境变量`MINIO_ACCESS_KEY` 和 `MINIO_SECRET_KEY` 更改MinIO的admin凭据和root凭据。使用这两个值的组合,MinIO加密存储在后端的配置
|
||||
|
||||
```
|
||||
export MINIO_ACCESS_KEY=minio
|
||||
export MINIO_SECRET_KEY=minio13
|
||||
minio server /data
|
||||
```
|
||||
|
||||
##### 使用新的凭据轮换加密
|
||||
|
||||
另外,如果您想更改管理员凭据,则MinIO将自动检测到该凭据,并使用新凭据重新加密,如下所示。一次只需要设置如下所示的环境变量即可轮换加密配置。
|
||||
|
||||
> 旧的环境变量永远不会在内存中被记住,并且在使用新凭据迁移现有内容后立即销毁。在服务器再次成功重启后,你可以安全的删除它们。
|
||||
|
||||
```
|
||||
export MINIO_ACCESS_KEY=newminio
|
||||
export MINIO_SECRET_KEY=newminio123
|
||||
export MINIO_ACCESS_KEY_OLD=minio
|
||||
export MINIO_SECRET_KEY_OLD=minio123
|
||||
minio server /data
|
||||
```
|
||||
|
||||
迁移完成后, 服务器会自动的取消进程空间中的`MINIO_ACCESS_KEY_OLD` and `MINIO_SECRET_KEY_OLD`设置。
|
||||
|
||||
> **注意: 在下一次服务重新启动前,要确保移除脚本或者服务文件中的 `MINIO_ACCESS_KEY_OLD` and `MINIO_SECRET_KEY_OLD`, 避免现有的内容被双重加密**
|
||||
|
||||
#### 区域
|
||||
```
|
||||
KEY:
|
||||
region 服务器的物理位置标记
|
||||
|
||||
ARGS:
|
||||
name (string) 服务器的物理位置名字,例如 "us-west-rack2"
|
||||
comment (sentence) 为这个设置添加一个可选的注释
|
||||
```
|
||||
|
||||
或者通过环境变量
|
||||
```
|
||||
KEY:
|
||||
region 服务器的物理位置标记
|
||||
|
||||
ARGS:
|
||||
MINIO_REGION_NAME (string) 服务器的物理位置名字,例如 "us-west-rack2"
|
||||
MINIO_REGION_COMMENT (sentence) 为这个设置添加一个可选的注释
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```sh
|
||||
export MINIO_ACCESS_KEY=admin
|
||||
export MINIO_SECRET_KEY=password
|
||||
export MINIO_REGION_NAME="my_region"
|
||||
minio server /data
|
||||
```
|
||||
|
||||
#### 区域(Region)
|
||||
|参数|类型|描述|
|
||||
|:---|:---|:---|
|
||||
|``region``| _string_ | `region`描述的是服务器的物理位置,默认是`us-east-1`(美国东区1),这也是亚马逊S3的默认区域。你可以通过`MINIO_REGION_NAME` 环境变量进行修改。如果不了解这块,建议不要随意修改|
|
||||
### 存储类型
|
||||
默认情况下,标准存储类型的奇偶校验值设置为N/2,低冗余的存储类型奇偶校验值设置为2。在[此处](https://github.com/minio/minio/blob/master/docs/zh_CN/erasure/storage-class/README.md)了解有关MinIO服务器存储类型的更多信息。
|
||||
|
||||
示例:
|
||||
```
|
||||
KEY:
|
||||
storage_class 定义对象级冗余
|
||||
|
||||
```sh
|
||||
export MINIO_REGION_NAME="中国华北一区"
|
||||
minio server /data
|
||||
ARGS:
|
||||
standard (string) 设置默认标准存储类型的奇偶校验计数,例如"EC:4"
|
||||
rrs (string) 设置默认低冗余存储类型的奇偶校验计数,例如"EC:2"
|
||||
comment (sentence) 为这个设置添加一个可选的注释
|
||||
```
|
||||
|
||||
#### 浏览器
|
||||
或者通过环境变量
|
||||
```
|
||||
KEY:
|
||||
storage_class 定义对象级冗余
|
||||
|
||||
ARGS:
|
||||
MINIO_STORAGE_CLASS_STANDARD (string) 设置默认标准存储类型的奇偶校验计数,例如"EC:4"
|
||||
MINIO_STORAGE_CLASS_RRS (string) 设置默认低冗余存储类型的奇偶校验计数,例如"EC:2"
|
||||
MINIO_STORAGE_CLASS_COMMENT (sentence) 为这个设置添加一个可选的注释
|
||||
```
|
||||
|
||||
### 缓存
|
||||
MinIO为主要的网关部署提供了缓存存储层,使您可以缓存内容以实现更快的读取速度,并节省从云中重复下载的成本。
|
||||
|
||||
```
|
||||
KEY:
|
||||
cache 添加缓存存储层
|
||||
|
||||
ARGS:
|
||||
drives* (csv) 逗号分隔的挂载点,例如 "/optane1,/optane2"
|
||||
expiry (number) 缓存有效期限(天),例如 "90"
|
||||
quota (number) 以百分比限制缓存驱动器的使用,例如 "90"
|
||||
exclude (csv) 逗号分隔的通配符排除模式,例如 "bucket/*.tmp,*.exe"
|
||||
after (number) 缓存对象之前的最小可访问次数
|
||||
comment (sentence) 为这个设置添加一个可选的注释
|
||||
```
|
||||
|
||||
或者通过环境变量
|
||||
```
|
||||
KEY:
|
||||
cache 添加缓存存储层
|
||||
|
||||
ARGS:
|
||||
MINIO_CACHE_DRIVES* (csv) 逗号分隔的挂载点,例如 "/optane1,/optane2"
|
||||
MINIO_CACHE_EXPIRY (number) 缓存有效期限(天),例如 "90"
|
||||
MINIO_CACHE_QUOTA (number) 以百分比限制缓存驱动器的使用,例如 "90"
|
||||
MINIO_CACHE_EXCLUDE (csv) 逗号分隔的通配符排除模式,例如 "bucket/*.tmp,*.exe"
|
||||
MINIO_CACHE_AFTER (number) 缓存对象之前的最小可访问次数
|
||||
MINIO_CACHE_COMMENT (sentence) 为这个设置添加一个可选的注释
|
||||
```
|
||||
|
||||
### 浏览器
|
||||
|参数|类型|描述|
|
||||
|:---|:---|:---|
|
||||
|``browser``| _string_ | 开启或关闭浏览器访问,默认是开启的,你可以通过``MINIO_BROWSER``环境变量进行修改|
|
||||
@@ -83,4 +170,4 @@ minio server /data
|
||||
|``notify.webhook``| |[通过Webhooks发布MinIO事件](https://docs.min.io/cn/minio-bucket-notification-guide#webhooks)|
|
||||
|
||||
## 了解更多
|
||||
* [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide)
|
||||
* [MinIO Quickstart Guide](https://docs.min.io/cn/minio-quickstart-guide)
|
||||
|
||||
@@ -7,24 +7,31 @@
|
||||
MinIO 需要一个持久卷来存储配置和应用数据。不过, 如果只是为了测试一下, 您可以通过简单地传递一个目录(在下面的示例中为`/ data`)启动MinIO。这个目录会在容器启动时在容器的文件系统中创建,不过所有的数据都会在容器退出时丢失。
|
||||
|
||||
```sh
|
||||
docker run -p 9000:9000 minio/minio server /data
|
||||
docker run -p 9000:9000 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
要创建具有永久存储的MinIO容器,您需要将本地持久目录从主机操作系统映射到虚拟配置`~/.minio` 并导出`/data`目录。 为此,请运行以下命令
|
||||
|
||||
#### GNU/Linux 和 macOS
|
||||
```sh
|
||||
docker run -p 9000:9000 --name minio1 \
|
||||
docker run -p 9000:9000 \
|
||||
--name minio1 \
|
||||
-v /mnt/data:/data \
|
||||
-v /mnt/config:/root/.minio \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
#### Windows
|
||||
```sh
|
||||
docker run -p 9000:9000 --name minio1 \
|
||||
docker run -p 9000:9000 \
|
||||
--name minio1 \
|
||||
-v D:\data:/data \
|
||||
-v D:\minio\config:/root/.minio \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
@@ -44,7 +51,6 @@ docker run -p 9000:9000 --name minio1 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
-v /mnt/data:/data \
|
||||
-v /mnt/config:/root/.minio \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
@@ -54,7 +60,39 @@ docker run -p 9000:9000 --name minio1 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
|
||||
-v D:\data:/data \
|
||||
-v D:\minio\config:/root/.minio \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
### 以普通用户身份运行MinIO Docker
|
||||
Docker提供了标准化的机制,可以以非root用户身份运行docker容器。
|
||||
|
||||
#### GNU/Linux 和 macOS
|
||||
在 Linux 和 macOS 上, 你可以使用 `--user` 以普通用户身份来运行容器。
|
||||
|
||||
> 注意: 在使用`--user`前,一定要确保--user指定的用户具备 *${HOME}/data* 的写入权限。
|
||||
```sh
|
||||
mkdir -p ${HOME}/data
|
||||
docker run -p 9000:9000 \
|
||||
--user $(id -u):$(id -g) \
|
||||
--name minio1 \
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY" \
|
||||
-v ${HOME}/data:/data \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
#### Windows
|
||||
在windows上, 你需要用到 [Docker集成Windows身份验证](https://success.docker.com/article/modernizing-traditional-dot-net-applications#integratedwindowsauthentication) 和 [创建具有Active Directory支持的容器](https://blogs.msdn.microsoft.com/containerstuff/2017/01/30/create-a-container-with-active-directory-support/) 的能力
|
||||
|
||||
> 注意: 在使用`credentialspec=`欠,要确保你的AD/Windows用户具备 *D:\data* 的写入权限。
|
||||
|
||||
```powershell
|
||||
docker run -p 9000:9000 \
|
||||
--name minio1 \
|
||||
--security-opt "credentialspec=file://myuser.json"
|
||||
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
|
||||
-e "MINIO_SECRET_KEY=wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY" \
|
||||
-v D:\data:/data \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
@@ -73,6 +111,17 @@ docker service create --name="minio-service" --secret="access_key" --secret="sec
|
||||
|
||||
更多 `docker service`信息,请访问 [这里](https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/)
|
||||
|
||||
#### 自定义MinIO Access和Secret密钥文件
|
||||
要使用其他密钥名称,请把上面的`access_key` 和 `secret_key`替换为你自定义的名称(比如`my_secret_key`,`my_custom_key`)。使用如下命令运行服务
|
||||
```
|
||||
docker service create --name="minio-service" \
|
||||
--secret="my_access_key" \
|
||||
--secret="my_secret_key" \
|
||||
--env="MINIO_ACCESS_KEY_FILE=my_access_key" \
|
||||
--env="MINIO_SECRET_KEY_FILE=my_secret_key" \
|
||||
minio/minio server /data
|
||||
```
|
||||
|
||||
### 获取容器ID
|
||||
在容器中使用Docker命令, 你需要知道这个容器的 `容器ID` 。 为了获取 `Container ID`, 运行
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# Minio纠删码快速入门 [](https://slack.min.io)
|
||||
|
||||
Minio使用纠删码`erasure code`和校验和`checksum`来保护数据免受硬件故障和无声数据损坏。 即便您丢失一半数量(N/2)的硬盘,您仍然可以恢复数据。
|
||||
Minio使用纠删码`erasure code`和`checksum`来保护数据免受硬件故障和无声数据损坏。 即便您丢失一半数量(N/2)的硬盘,您仍然可以恢复数据。
|
||||
|
||||
## 什么是纠删码`erasure code`?
|
||||
|
||||
纠删码是一种恢复丢失和损坏数据的数学算法, Minio采用Reed-Solomon code将对象拆分成N/2数据和N/2 奇偶校验块。 这就意味着如果是12块盘,一个对象会被分成6个数据块、6个奇偶校验块,你可以丢失任意6块盘(不管其是存放的数据块还是奇偶校验块),你仍可以从剩下的盘中的数据进行恢复,是不是很NB,感兴趣的同学请翻墙google。
|
||||
纠删码是一种恢复丢失和损坏数据的数学算法, Minio采用里德-所罗门码将对象分片为数据和奇偶校验块。 这就意味着如果是12块盘,一个对象可被分片的范围是:6个数据块和6个奇偶校验块 到 10个数据块和2个奇偶校验块之间。
|
||||
|
||||
默认情况下, MinIO 将对象拆分成N/2数据和N/2 奇偶校验盘. 虽然你可以通过 [存储类型](https://github.com/minio/minio/tree/master/docs/zh_CN/erasure/storage-class) 自定义配置, 但是我们还是推荐N/2个数据和奇偶校验块, 因为它可以确保对硬盘故障提供最佳保护。
|
||||
|
||||
比如上面12个盘的例子,通过默认配置运行MinIO服务的话,你可以丢失任意6块盘(不管其是存放的数据块还是奇偶校验块),你仍可以从剩下的盘中的数据进行恢复,是不是很NB,感兴趣的同学请翻墙google。
|
||||
|
||||
## 为什么纠删码有用?
|
||||
|
||||
@@ -16,6 +20,14 @@ Minio使用纠删码`erasure code`和校验和`checksum`来保护数据免受硬
|
||||
|
||||
位衰减又被称为数据腐化`Data Rot`、无声数据损坏`Silent Data Corruption`,是目前硬盘数据的一种严重数据丢失问题。硬盘上的数据可能会神不知鬼不觉就损坏了,也没有什么错误日志。正所谓明枪易躲,暗箭难防,这种背地里犯的错比硬盘直接咔咔宕了还危险。 不过不用怕,Minio纠删码采用了高速 [HighwayHash](https://github.com/minio/highwayhash) 基于哈希的校验和来防范位衰减。
|
||||
|
||||
## 驱动器(盘)如何使用纠删码?
|
||||
|
||||
MinIO会把你提供的所有驱动器,按照*4 到 16*个一组划分为多个纠删码集合,因此,你提供的驱动器数量必须是以上这些数字(4到16)的倍数。每个对象都会被写入一个单独的纠删码集合中。
|
||||
|
||||
Minio会尽可能使用最大的纠删码集合大小(EC set size)进行划分.比如 *18个盘*会被划分为2个纠删码集合,每个集合有9个盘;*24个盘*也会被划分为2个纠删码集合,每个集合有12个盘。对于将MinIO作为独立的纠删码部署运行的场景而言,这是正确的。然而,在 [分布式部署](https://docs.minio.io/cn/distributed-minio-quickstart-guide.html) 时,选择的是基于节点(亲和力)的纠删条带大小.
|
||||
|
||||
驱动器的大小应当都差不多。
|
||||
|
||||
## Minio纠删码快速入门
|
||||
|
||||
### 1. 前提条件:
|
||||
@@ -27,7 +39,7 @@ Minio使用纠删码`erasure code`和校验和`checksum`来保护数据免受硬
|
||||
示例: 使用Minio,在12个盘中启动Minio服务。
|
||||
|
||||
```sh
|
||||
minio server /data1 /data2 /data3 /data4 /data5 /data6 /data7 /data8 /data9 /data10 /data11 /data12
|
||||
minio server /data{1...12}
|
||||
```
|
||||
|
||||
示例: 使用Minio Docker镜像,在8块盘中启动Minio服务。
|
||||
@@ -42,7 +54,7 @@ docker run -p 9000:9000 --name minio \
|
||||
-v /mnt/data6:/data6 \
|
||||
-v /mnt/data7:/data7 \
|
||||
-v /mnt/data8:/data8 \
|
||||
minio/minio server /data1 /data2 /data3 /data4 /data5 /data6 /data7 /data8
|
||||
minio/minio server /data{1...8}
|
||||
```
|
||||
|
||||
### 3. 验证是否设置成功
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# MinIO 存储类型快速入门 [](https://slack.min.io)
|
||||
|
||||
在纠删码模式下,MinIO server支持存储类型. 这可以指定每个对象的数据和奇偶校验盘,其实就是可以为对象选择不同的存储类型.
|
||||
|
||||
## 概述
|
||||
|
||||
MinIO 支持两种存储类型, 低冗余存储和标准存储。 这些存储类型可以在MinIO服务器启动之前通过环境变量定义。 在通过环境变量定义了每个存储类型的数据和奇偶校验盘的数量后,
|
||||
你可以通过请求中的元数据字段`x-amz-storage-class`来设置一个对象的存储类型。然后,MinIO服务器通过将对象保存在特定数量的数据和奇偶校验盘中来兑现存储类型。
|
||||
|
||||
## 可用存储空间
|
||||
|
||||
选择不同的数据和奇偶校验盘的数量会直接影响到存储空间的使用。通过存储类型,你能优化以实现高冗余或者是更好的空间利用率。
|
||||
|
||||
让我们以在16个盘的MinIO部署中存储100M文件为例,来了解数据和奇偶校验盘数量的不同组合是如何影响可用存储空间的。如果你使用8个数据盘和8个奇偶校验盘,文件空间使用量约为两倍,
|
||||
即100M文件将占用200M空间。但是,如果你是用10个数据盘和6个奇偶校验盘,则同样的100M文件大约需要160M的空间。如果你是用14个数据盘和2个奇偶校验盘,100M文件仅仅需要约114M空间。
|
||||
|
||||
以下是一张16盘的MinIO部署,数据/奇偶校验盘数量和相应的 _近似_ 存储储空间使用情况列表。_空间使用率_ 约等于纠删编码下的使用空间除以文件的实际大小。
|
||||
|
||||
| 盘总个数 (N) | 数据盘个数 (D) | 奇偶校验码个数 (P) | 空间使用率 |
|
||||
|------------------|-----------------|-------------------|---------------------|
|
||||
| 16 | 8 | 8 | 2.00 |
|
||||
| 16 | 9 | 7 | 1.79 |
|
||||
| 16 | 10 | 6 | 1.60 |
|
||||
| 16 | 11 | 5 | 1.45 |
|
||||
| 16 | 12 | 4 | 1.34 |
|
||||
| 16 | 13 | 3 | 1.23 |
|
||||
| 16 | 14 | 2 | 1.14 |
|
||||
|
||||
你可以使用公式: `盘总个数 (N)/数据盘个数 (D)`来计算 _大概的_ 空间使用率。
|
||||
|
||||
### 标准(STANDARD)存储类型的允许值
|
||||
|
||||
`STANDARD`存储类型意味着奇偶校验盘比`REDUCED_REDUNDANCY`多。 所以, `STANDARD`的奇偶校验盘数量应该
|
||||
|
||||
- 如果`REDUCED_REDUNDANCY`的奇偶校验盘未设置的话,应该大于等于2。
|
||||
- 如果已设置的话,应该大于`REDUCED_REDUNDANCY`的奇偶校验盘数量。
|
||||
|
||||
奇偶校验块不能大于数据块,所以`STANDARD`存储类型的奇偶校验块不能大于N/2。(N是盘总个数)
|
||||
|
||||
`STANDARD`存储类型的默认值是`N/2`(N是盘总个数)。
|
||||
|
||||
### 低冗余(REDUCED_REDUNDANCY)存储类型的允许值
|
||||
|
||||
`REDUCED_REDUNDANCY`存储类型意味着奇偶校验盘比`REDUCED_REDUNDANCY`少。 所以, `REDUCED_REDUNDANCY`的奇偶校验盘数量应该
|
||||
|
||||
- 如果`STANDARD`的奇偶校验盘未设置的话,应该小于2。
|
||||
- 如果设置的话,应该小于`STANDARD`的奇偶校验盘数量。
|
||||
|
||||
因为不建议奇偶校验盘数量低于2, 所以4个盘组成的纠删码模式部署是不支持`REDUCED_REDUNDANCY`存储类型的。
|
||||
|
||||
`REDUCED_REDUNDANCY`存储类型的默认值是`2`。
|
||||
|
||||
## 存储类型入门
|
||||
|
||||
### 设置存储类型
|
||||
|
||||
设置存储类型环境变量的格式如下
|
||||
|
||||
`MINIO_STORAGE_CLASS_STANDARD=EC:parity`
|
||||
`MINIO_STORAGE_CLASS_RRS=EC:parity`
|
||||
|
||||
例如, 设置 `MINIO_STORAGE_CLASS_RRS` 奇偶校验盘为2 以及设置 `MINIO_STORAGE_CLASS_STANDARD` 奇偶校验盘为3
|
||||
|
||||
```sh
|
||||
export MINIO_STORAGE_CLASS_STANDARD=EC:3
|
||||
export MINIO_STORAGE_CLASS_RRS=EC:2
|
||||
```
|
||||
|
||||
也可以通过`mc admin config` get/set 命令来设置存储类型。参考 [存储类型](https://github.com/minio/minio/tree/master/docs/zh_CN/config#存储类型) 获取更多详细信息。
|
||||
|
||||
|
||||
*注意*
|
||||
|
||||
- 如果通过环境变量或`mc admin config` get/set命令设置了`STANDARD`存储类型,并且请求元数据中不存在`x-amz-storage-class`,则MinIO服务器会将`STANDARD`存储类型应用于该对象。这意味着将按照`STANDARD`存储类型中的设置使用数据和奇偶校验盘数量。
|
||||
|
||||
- 如果在启动MinIO服务器之前未定义存储类型,并且随后的PutObject元数据字段中存在`x-amz-storage-class`,其值为`REDUCED_REDUNDANCY`或`STANDARD`,则MinIO服务器将使用默认的奇偶校验值。
|
||||
|
||||
### 设置元数据
|
||||
|
||||
如下`minio-go`的示例中,存储类型被设置为`REDUCED_REDUNDANCY`。这意味着对象被拆分为6个数据块和2个奇偶校验块(按照上一步骤中的存储类型设置)。
|
||||
|
||||
```go
|
||||
s3Client, err := minio.New("localhost:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
object, err := os.Open("my-testfile")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer object.Close()
|
||||
objectStat, err := object.Stat()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
n, err := s3Client.PutObject("my-bucketname", "my-objectname", object, objectStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream", StorageClass: "REDUCED_REDUNDANCY"})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Uploaded", "my-objectname", " of size: ", n, "Successfully.")
|
||||
```
|
||||
@@ -48,7 +48,7 @@ require (
|
||||
github.com/minio/cli v1.22.0
|
||||
github.com/minio/highwayhash v1.0.0
|
||||
github.com/minio/minio-go/v7 v7.0.1
|
||||
github.com/minio/selfupdate v0.3.0
|
||||
github.com/minio/selfupdate v0.3.1
|
||||
github.com/minio/sha256-simd v0.1.1
|
||||
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37
|
||||
github.com/minio/sio v0.2.0
|
||||
@@ -84,6 +84,7 @@ require (
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae
|
||||
golang.org/x/text v0.3.3 // indirect
|
||||
golang.org/x/tools v0.0.0-20200724172932-b5fc9d354d99 // indirect
|
||||
google.golang.org/api v0.5.0
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.3.0
|
||||
gopkg.in/ldap.v3 v3.0.3
|
||||
|
||||
@@ -295,6 +295,8 @@ github.com/minio/minio-go/v7 v7.0.1 h1:sL2y4uuNUEi7AjvWjoGyDFQKFX2zA0DU2tGM9m3s5
|
||||
github.com/minio/minio-go/v7 v7.0.1/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6JUMSQ2zAns=
|
||||
github.com/minio/selfupdate v0.3.0 h1:1qfaZscU3hWwX1cF5m5Dov8Z5aZNvPHk9LROzIkas1k=
|
||||
github.com/minio/selfupdate v0.3.0/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
|
||||
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
|
||||
github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37 h1:pDeao6M5AEd8hwTtGmE0pVKomlL56JFRa5SiXDZAuJE=
|
||||
@@ -445,6 +447,7 @@ github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd/v3 v3.3.0-rc.0.0.20200707003333-58bb8ae09f8e h1:HZQLoe71Q24wVyDrGBRcVuogx32U+cPlcm/WoSLUI6c=
|
||||
@@ -485,6 +488,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -499,6 +504,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -511,6 +517,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -561,6 +569,8 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c h1:iHhCR0b26amDCiiO+kBguKZom9aMF+NrFxh9zeKR/XU=
|
||||
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200724172932-b5fc9d354d99 h1:OHn441rq5CeM5r1xJ0OmY7lfdTvnedi6k+vQiI7G9b8=
|
||||
golang.org/x/tools v0.0.0-20200724172932-b5fc9d354d99/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
|
||||
+3
-7
@@ -65,13 +65,6 @@ func GetOBDInfo(ctx context.Context, drive, fsPath string) (Latency, Throughput,
|
||||
os.Remove(fsPath)
|
||||
}()
|
||||
|
||||
// going to leave this here incase we decide to go back to caching again
|
||||
// if gl, ok := globalLatency[drive]; ok {
|
||||
// if gt, ok := globalThroughput[drive]; ok {
|
||||
// return gl, gt, nil
|
||||
// }
|
||||
// }
|
||||
|
||||
blockSize := 4 * humanize.MiByte
|
||||
fileSize := 256 * humanize.MiByte
|
||||
|
||||
@@ -94,6 +87,9 @@ func GetOBDInfo(ctx context.Context, drive, fsPath string) (Latency, Throughput,
|
||||
latencies[i] = float64(latencyInSecs)
|
||||
}
|
||||
|
||||
// Sync every full writes fdatasync
|
||||
Fdatasync(w)
|
||||
|
||||
for i := range latencies {
|
||||
throughput := float64(blockSize) / latencies[i]
|
||||
throughputs[i] = throughput
|
||||
|
||||
@@ -140,8 +140,8 @@ func (dm *DRWMutex) lockBlocking(ctx context.Context, timeout time.Duration, id,
|
||||
locks := make([]string, len(restClnts))
|
||||
|
||||
// Try to acquire the lock.
|
||||
success := lock(dm.clnt, &locks, id, source, isReadLock, dm.Names...)
|
||||
if !success {
|
||||
locked = lock(retryCtx, dm.clnt, &locks, id, source, isReadLock, dm.Names...)
|
||||
if !locked {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -158,16 +158,16 @@ func (dm *DRWMutex) lockBlocking(ctx context.Context, timeout time.Duration, id,
|
||||
}
|
||||
|
||||
dm.m.Unlock()
|
||||
return true
|
||||
return locked
|
||||
}
|
||||
|
||||
// Failed to acquire the lock on this attempt, incrementally wait
|
||||
// for a longer back-off time and try again afterwards.
|
||||
return false
|
||||
return locked
|
||||
}
|
||||
|
||||
// lock tries to acquire the distributed lock, returning true or false.
|
||||
func lock(ds *Dsync, locks *[]string, id, source string, isReadLock bool, lockNames ...string) bool {
|
||||
func lock(ctx context.Context, ds *Dsync, locks *[]string, id, source string, isReadLock bool, lockNames ...string) bool {
|
||||
|
||||
restClnts := ds.GetLockersFn()
|
||||
|
||||
@@ -199,11 +199,11 @@ func lock(ds *Dsync, locks *[]string, id, source string, isReadLock bool, lockNa
|
||||
var locked bool
|
||||
var err error
|
||||
if isReadLock {
|
||||
if locked, err = c.RLock(args); err != nil {
|
||||
if locked, err = c.RLock(ctx, args); err != nil {
|
||||
log("dsync: Unable to call RLock failed with %s for %#v at %s\n", err, args, c)
|
||||
}
|
||||
} else {
|
||||
if locked, err = c.Lock(args); err != nil {
|
||||
if locked, err = c.Lock(ctx, args); err != nil {
|
||||
log("dsync: Unable to call Lock failed with %s for %#v at %s\n", err, args, c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package dsync_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/rpc"
|
||||
"sync"
|
||||
|
||||
@@ -89,12 +90,12 @@ func (rpcClient *ReconnectRPCClient) Call(serviceMethod string, args interface{}
|
||||
return err
|
||||
}
|
||||
|
||||
func (rpcClient *ReconnectRPCClient) RLock(args LockArgs) (status bool, err error) {
|
||||
func (rpcClient *ReconnectRPCClient) RLock(ctx context.Context, args LockArgs) (status bool, err error) {
|
||||
err = rpcClient.Call("Dsync.RLock", &args, &status)
|
||||
return status, err
|
||||
}
|
||||
|
||||
func (rpcClient *ReconnectRPCClient) Lock(args LockArgs) (status bool, err error) {
|
||||
func (rpcClient *ReconnectRPCClient) Lock(ctx context.Context, args LockArgs) (status bool, err error) {
|
||||
err = rpcClient.Call("Dsync.Lock", &args, &status)
|
||||
return status, err
|
||||
}
|
||||
@@ -109,7 +110,7 @@ func (rpcClient *ReconnectRPCClient) Unlock(args LockArgs) (status bool, err err
|
||||
return status, err
|
||||
}
|
||||
|
||||
func (rpcClient *ReconnectRPCClient) Expired(args LockArgs) (expired bool, err error) {
|
||||
func (rpcClient *ReconnectRPCClient) Expired(ctx context.Context, args LockArgs) (expired bool, err error) {
|
||||
err = rpcClient.Call("Dsync.Expired", &args, &expired)
|
||||
return expired, err
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package dsync
|
||||
|
||||
import "context"
|
||||
|
||||
// LockArgs is minimal required values for any dsync compatible lock operation.
|
||||
type LockArgs struct {
|
||||
// Unique ID of lock/unlock request.
|
||||
@@ -34,12 +36,12 @@ type NetLocker interface {
|
||||
// Do read lock for given LockArgs. It should return
|
||||
// * a boolean to indicate success/failure of the operation
|
||||
// * an error on failure of lock request operation.
|
||||
RLock(args LockArgs) (bool, error)
|
||||
RLock(ctx context.Context, args LockArgs) (bool, error)
|
||||
|
||||
// Do write lock for given LockArgs. It should return
|
||||
// * a boolean to indicate success/failure of the operation
|
||||
// * an error on failure of lock request operation.
|
||||
Lock(args LockArgs) (bool, error)
|
||||
Lock(ctx context.Context, args LockArgs) (bool, error)
|
||||
|
||||
// Do read unlock for given LockArgs. It should return
|
||||
// * a boolean to indicate success/failure of the operation
|
||||
@@ -52,7 +54,7 @@ type NetLocker interface {
|
||||
Unlock(args LockArgs) (bool, error)
|
||||
|
||||
// Expired returns if current lock args has expired.
|
||||
Expired(args LockArgs) (bool, error)
|
||||
Expired(ctx context.Context, args LockArgs) (bool, error)
|
||||
|
||||
// Returns underlying endpoint of this lock client instance.
|
||||
String() string
|
||||
|
||||
@@ -116,7 +116,7 @@ type PostgreSQLArgs struct {
|
||||
Table string `json:"table"`
|
||||
Host xnet.Host `json:"host"` // default: localhost
|
||||
Port string `json:"port"` // default: 5432
|
||||
User string `json:"user"` // default: user running minio
|
||||
Username string `json:"username"` // default: user running minio
|
||||
Password string `json:"password"` // default: no password
|
||||
Database string `json:"database"` // default: same as user
|
||||
QueueDir string `json:"queueDir"`
|
||||
@@ -357,20 +357,23 @@ func (target *PostgreSQLTarget) executeStmts() error {
|
||||
// NewPostgreSQLTarget - creates new PostgreSQL target.
|
||||
func NewPostgreSQLTarget(id string, args PostgreSQLArgs, doneCh <-chan struct{}, loggerOnce func(ctx context.Context, err error, id interface{}, kind ...interface{}), test bool) (*PostgreSQLTarget, error) {
|
||||
params := []string{args.ConnectionString}
|
||||
if !args.Host.IsEmpty() {
|
||||
params = append(params, "host="+args.Host.String())
|
||||
}
|
||||
if args.Port != "" {
|
||||
params = append(params, "port="+args.Port)
|
||||
}
|
||||
if args.User != "" {
|
||||
params = append(params, "user="+args.User)
|
||||
}
|
||||
if args.Password != "" {
|
||||
params = append(params, "password="+args.Password)
|
||||
}
|
||||
if args.Database != "" {
|
||||
params = append(params, "dbname="+args.Database)
|
||||
if args.ConnectionString == "" {
|
||||
params = []string{}
|
||||
if !args.Host.IsEmpty() {
|
||||
params = append(params, "host="+args.Host.String())
|
||||
}
|
||||
if args.Port != "" {
|
||||
params = append(params, "port="+args.Port)
|
||||
}
|
||||
if args.Username != "" {
|
||||
params = append(params, "username="+args.Username)
|
||||
}
|
||||
if args.Password != "" {
|
||||
params = append(params, "password="+args.Password)
|
||||
}
|
||||
if args.Database != "" {
|
||||
params = append(params, "dbname="+args.Database)
|
||||
}
|
||||
}
|
||||
connStr := strings.Join(params, " ")
|
||||
|
||||
|
||||
@@ -108,12 +108,12 @@ const (
|
||||
// GetBucketQuotaAdminAction - allow getting bucket quota
|
||||
GetBucketQuotaAdminAction = "admin:GetBucketQuota"
|
||||
|
||||
// Bucket Replication admin Actions
|
||||
// Bucket Target admin Actions
|
||||
|
||||
// SetBucketReplicationTargetAction - allow setting bucket replication target
|
||||
SetBucketReplicationTargetAction = "admin:SetBucketReplicationTarget"
|
||||
// GetBucketReplicationTargetAction - allow getting bucket replication targets
|
||||
GetBucketReplicationTargetAction = "admin:GetBucketReplicationTarget"
|
||||
// SetBucketTargetAction - allow setting bucket target
|
||||
SetBucketTargetAction = "admin:SetBucketTarget"
|
||||
// GetBucketTargetAction - allow getting bucket targets
|
||||
GetBucketTargetAction = "admin:GetBucketTarget"
|
||||
|
||||
// AllAdminActions - provides all admin permissions
|
||||
AllAdminActions = "admin:*"
|
||||
@@ -121,42 +121,42 @@ const (
|
||||
|
||||
// List of all supported admin actions.
|
||||
var supportedAdminActions = map[AdminAction]struct{}{
|
||||
HealAdminAction: {},
|
||||
StorageInfoAdminAction: {},
|
||||
DataUsageInfoAdminAction: {},
|
||||
TopLocksAdminAction: {},
|
||||
ProfilingAdminAction: {},
|
||||
TraceAdminAction: {},
|
||||
ConsoleLogAdminAction: {},
|
||||
KMSKeyStatusAdminAction: {},
|
||||
ServerInfoAdminAction: {},
|
||||
OBDInfoAdminAction: {},
|
||||
ServerUpdateAdminAction: {},
|
||||
ServiceRestartAdminAction: {},
|
||||
ServiceStopAdminAction: {},
|
||||
ConfigUpdateAdminAction: {},
|
||||
CreateUserAdminAction: {},
|
||||
DeleteUserAdminAction: {},
|
||||
ListUsersAdminAction: {},
|
||||
EnableUserAdminAction: {},
|
||||
DisableUserAdminAction: {},
|
||||
GetUserAdminAction: {},
|
||||
AddUserToGroupAdminAction: {},
|
||||
RemoveUserFromGroupAdminAction: {},
|
||||
GetGroupAdminAction: {},
|
||||
ListGroupsAdminAction: {},
|
||||
EnableGroupAdminAction: {},
|
||||
DisableGroupAdminAction: {},
|
||||
CreatePolicyAdminAction: {},
|
||||
DeletePolicyAdminAction: {},
|
||||
GetPolicyAdminAction: {},
|
||||
AttachPolicyAdminAction: {},
|
||||
ListUserPoliciesAdminAction: {},
|
||||
SetBucketQuotaAdminAction: {},
|
||||
GetBucketQuotaAdminAction: {},
|
||||
SetBucketReplicationTargetAction: {},
|
||||
GetBucketReplicationTargetAction: {},
|
||||
AllAdminActions: {},
|
||||
HealAdminAction: {},
|
||||
StorageInfoAdminAction: {},
|
||||
DataUsageInfoAdminAction: {},
|
||||
TopLocksAdminAction: {},
|
||||
ProfilingAdminAction: {},
|
||||
TraceAdminAction: {},
|
||||
ConsoleLogAdminAction: {},
|
||||
KMSKeyStatusAdminAction: {},
|
||||
ServerInfoAdminAction: {},
|
||||
OBDInfoAdminAction: {},
|
||||
ServerUpdateAdminAction: {},
|
||||
ServiceRestartAdminAction: {},
|
||||
ServiceStopAdminAction: {},
|
||||
ConfigUpdateAdminAction: {},
|
||||
CreateUserAdminAction: {},
|
||||
DeleteUserAdminAction: {},
|
||||
ListUsersAdminAction: {},
|
||||
EnableUserAdminAction: {},
|
||||
DisableUserAdminAction: {},
|
||||
GetUserAdminAction: {},
|
||||
AddUserToGroupAdminAction: {},
|
||||
RemoveUserFromGroupAdminAction: {},
|
||||
GetGroupAdminAction: {},
|
||||
ListGroupsAdminAction: {},
|
||||
EnableGroupAdminAction: {},
|
||||
DisableGroupAdminAction: {},
|
||||
CreatePolicyAdminAction: {},
|
||||
DeletePolicyAdminAction: {},
|
||||
GetPolicyAdminAction: {},
|
||||
AttachPolicyAdminAction: {},
|
||||
ListUserPoliciesAdminAction: {},
|
||||
SetBucketQuotaAdminAction: {},
|
||||
GetBucketQuotaAdminAction: {},
|
||||
SetBucketTargetAction: {},
|
||||
GetBucketTargetAction: {},
|
||||
AllAdminActions: {},
|
||||
}
|
||||
|
||||
// IsValid - checks if action is valid or not.
|
||||
@@ -167,39 +167,39 @@ func (action AdminAction) IsValid() bool {
|
||||
|
||||
// adminActionConditionKeyMap - holds mapping of supported condition key for an action.
|
||||
var adminActionConditionKeyMap = map[Action]condition.KeySet{
|
||||
AllAdminActions: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
HealAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
StorageInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServerInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DataUsageInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
OBDInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
TopLocksAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ProfilingAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
TraceAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ConsoleLogAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
KMSKeyStatusAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServerUpdateAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServiceRestartAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServiceStopAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ConfigUpdateAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
CreateUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DeleteUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ListUsersAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
EnableUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DisableUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
AddUserToGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
RemoveUserFromGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ListGroupsAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
EnableGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DisableGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
CreatePolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DeletePolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetPolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
AttachPolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ListUserPoliciesAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
SetBucketQuotaAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetBucketQuotaAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
SetBucketReplicationTargetAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetBucketReplicationTargetAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
AllAdminActions: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
HealAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
StorageInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServerInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DataUsageInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
OBDInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
TopLocksAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ProfilingAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
TraceAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ConsoleLogAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
KMSKeyStatusAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServerUpdateAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServiceRestartAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ServiceStopAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ConfigUpdateAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
CreateUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DeleteUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ListUsersAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
EnableUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DisableUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
AddUserToGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
RemoveUserFromGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ListGroupsAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
EnableGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DisableGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
CreatePolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
DeletePolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetPolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
AttachPolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
ListUserPoliciesAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
SetBucketQuotaAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetBucketQuotaAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
SetBucketTargetAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
GetBucketTargetAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
|
||||
}
|
||||
|
||||
@@ -42,19 +42,24 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
target := madmin.BucketReplicationTarget{Endpoint: "site2:9000", Credentials: creds, TargetBucket: "destbucket", IsSSL: false}
|
||||
// Set bucket replication target
|
||||
if err := madmClnt.SetBucketReplicationTarget(ctx, "srcbucket", &target); err != nil {
|
||||
target := madmin.BucketTarget{Endpoint: "site2:9000", Credentials: creds, TargetBucket: "destbucket", IsSSL: false, Type: madmin.ReplicationArn}
|
||||
// Set bucket target
|
||||
if err := madmClnt.SetBucketTarget(ctx, "srcbucket", &target); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// Get bucket replication target
|
||||
target, err = madmClnt.GetBucketReplicationTarget(ctx, "srcbucket")
|
||||
// List all bucket target(s)
|
||||
target, err = madmClnt.ListBucketTargets(ctx, "srcbucket", "")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Remove bucket replication target
|
||||
if err := madmClnt.SetBucketReplicationTarget(ctx, "srcbucket", nil); err != nil {
|
||||
// Get bucket target for arn type "replica"
|
||||
target, err = madmClnt.ListBucketTargets(ctx, "srcbucket", "replica")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// Remove bucket target
|
||||
arn := "arn:minio:replica::ac66b2cf-dd8f-4e7e-a882-9a64132f0d59:dest"
|
||||
if err := madmClnt.RemoveBucketTarget(ctx, "srcbucket", arn); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
)
|
||||
|
||||
// ArnType represents bucket ARN type
|
||||
type ArnType string
|
||||
|
||||
const (
|
||||
// ReplicationArn specifies a ARN type of replication
|
||||
ReplicationArn ArnType = "replica"
|
||||
)
|
||||
|
||||
// IsValid returns true if ARN type is replication
|
||||
func (t ArnType) IsValid() bool {
|
||||
return t == ReplicationArn
|
||||
}
|
||||
|
||||
// ARN is a struct to define arn.
|
||||
type ARN struct {
|
||||
Type ArnType
|
||||
ID string
|
||||
Region string
|
||||
Bucket string
|
||||
}
|
||||
|
||||
// Empty returns true if arn struct is empty
|
||||
func (a ARN) Empty() bool {
|
||||
return !a.Type.IsValid()
|
||||
}
|
||||
func (a ARN) String() string {
|
||||
return fmt.Sprintf("arn:minio:%s:%s:%s:%s", a.Type, a.Region, a.ID, a.Bucket)
|
||||
}
|
||||
|
||||
// ParseARN return ARN struct from string in arn format.
|
||||
func ParseARN(s string) (*ARN, error) {
|
||||
// ARN must be in the format of arn:minio:<Type>:<REGION>:<ID>:<remote-bucket>
|
||||
if !strings.HasPrefix(s, "arn:minio:") {
|
||||
return nil, fmt.Errorf("Invalid ARN %s", s)
|
||||
}
|
||||
|
||||
tokens := strings.Split(s, ":")
|
||||
if len(tokens) != 6 {
|
||||
return nil, fmt.Errorf("Invalid ARN %s", s)
|
||||
}
|
||||
|
||||
if tokens[4] == "" || tokens[5] == "" {
|
||||
return nil, fmt.Errorf("Invalid ARN %s", s)
|
||||
}
|
||||
|
||||
return &ARN{
|
||||
Type: ArnType(tokens[2]),
|
||||
Region: tokens[3],
|
||||
ID: tokens[4],
|
||||
Bucket: tokens[5],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BucketTarget represents the target bucket and site association.
|
||||
type BucketTarget struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Credentials *auth.Credentials `json:"credentials"`
|
||||
TargetBucket string `json:"targetbucket"`
|
||||
Secure bool `json:"secure"`
|
||||
Path string `json:"path,omitempty"`
|
||||
API string `json:"api,omitempty"`
|
||||
Arn string `json:"arn,omitempty"`
|
||||
Type ArnType `json:"type"`
|
||||
Region string `json:"omitempty"`
|
||||
}
|
||||
|
||||
// URL returns target url
|
||||
func (t BucketTarget) URL() string {
|
||||
scheme := "http"
|
||||
if t.Secure {
|
||||
scheme = "https"
|
||||
}
|
||||
return fmt.Sprintf("%s://%s", scheme, t.Endpoint)
|
||||
}
|
||||
|
||||
// Empty returns true if struct is empty.
|
||||
func (t BucketTarget) Empty() bool {
|
||||
return t.String() == "" || t.Credentials == nil
|
||||
}
|
||||
|
||||
func (t *BucketTarget) String() string {
|
||||
return fmt.Sprintf("%s %s", t.Endpoint, t.TargetBucket)
|
||||
}
|
||||
|
||||
// BucketTargets represents a slice of bucket targets by type and endpoint
|
||||
type BucketTargets struct {
|
||||
Targets []BucketTarget
|
||||
}
|
||||
|
||||
// Empty returns true if struct is empty.
|
||||
func (t BucketTargets) Empty() bool {
|
||||
if len(t.Targets) == 0 {
|
||||
return true
|
||||
}
|
||||
empty := true
|
||||
for _, t := range t.Targets {
|
||||
if !t.Empty() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return empty
|
||||
}
|
||||
|
||||
// ListBucketTargets - gets target(s) for this bucket
|
||||
func (adm *AdminClient) ListBucketTargets(ctx context.Context, bucket, arnType string) (targets []BucketTarget, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
queryValues.Set("type", arnType)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/list-bucket-targets",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/list-bucket-targets
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return targets, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return targets, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return targets, err
|
||||
}
|
||||
if err = json.Unmarshal(b, &targets); err != nil {
|
||||
return targets, err
|
||||
}
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
// SetBucketTarget sets up a remote target for this bucket
|
||||
func (adm *AdminClient) SetBucketTarget(ctx context.Context, bucket string, target *BucketTarget) (string, error) {
|
||||
data, err := json.Marshal(target)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encData, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-bucket-target",
|
||||
queryValues: queryValues,
|
||||
content: encData,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-bucket-target to set a target for this bucket of specific arn type.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", httpRespToErrorResponse(resp)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var arn string
|
||||
if err = json.Unmarshal(b, &arn); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return arn, nil
|
||||
}
|
||||
|
||||
// RemoveBucketTarget removes a remote target associated with particular ARN for this bucket
|
||||
func (adm *AdminClient) RemoveBucketTarget(ctx context.Context, bucket, arn string) error {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
queryValues.Set("arn", arn)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/remove-bucket-target",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/remove-bucket-target to remove a target for this bucket
|
||||
// with specific ARN
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
)
|
||||
|
||||
// BucketReplicationTarget represents the target bucket and site to be replicated to.
|
||||
type BucketReplicationTarget struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Credentials *auth.Credentials `json:"credentials"`
|
||||
TargetBucket string `json:"targetbucket"`
|
||||
IsSSL bool `json:"isssl"`
|
||||
Path string `json:"path,omitempty"`
|
||||
API string `json:"api,omitempty"`
|
||||
Arn string `json:"arn,omitempty"`
|
||||
}
|
||||
|
||||
// URL returns replication target url
|
||||
func (t BucketReplicationTarget) URL() string {
|
||||
scheme := "http"
|
||||
if t.IsSSL {
|
||||
scheme = "https"
|
||||
}
|
||||
return fmt.Sprintf("%s://%s", scheme, t.Endpoint)
|
||||
}
|
||||
|
||||
// Empty returns true if struct is empty.
|
||||
func (t BucketReplicationTarget) Empty() bool {
|
||||
return t.String() == "" || t.Credentials == nil
|
||||
}
|
||||
|
||||
func (t *BucketReplicationTarget) String() string {
|
||||
return fmt.Sprintf("%s %s", t.Endpoint, t.TargetBucket)
|
||||
}
|
||||
|
||||
// GetBucketReplicationTarget - gets replication target for this bucket
|
||||
func (adm *AdminClient) GetBucketReplicationTarget(ctx context.Context, bucket string) (target BucketReplicationTarget, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/get-bucket-replication-target",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/get-bucket-replication-target
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return target, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return target, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return target, err
|
||||
}
|
||||
if err = json.Unmarshal(b, &target); err != nil {
|
||||
return target, err
|
||||
}
|
||||
if target.Empty() {
|
||||
return target, errors.New("No Replication target configured")
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// SetBucketReplicationTarget sets up a replication target for this bucket
|
||||
func (adm *AdminClient) SetBucketReplicationTarget(ctx context.Context, bucket string, target *BucketReplicationTarget) error {
|
||||
data, err := json.Marshal(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encData, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-bucket-replication-target",
|
||||
queryValues: queryValues,
|
||||
content: encData,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-bucket-replication-target to set a replication target for this bucket.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBucketReplicationARN - gets replication Arn for this remote
|
||||
func (adm *AdminClient) GetBucketReplicationARN(ctx context.Context, rURL string) (arn string, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("url", rURL)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/get-bucket-replication-arn",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/list-bucket-replication-arn
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return arn, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return arn, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return arn, err
|
||||
}
|
||||
if err = json.Unmarshal(b, &arn); err != nil {
|
||||
return arn, err
|
||||
}
|
||||
if arn == "" {
|
||||
return arn, fmt.Errorf("Missing Replication ARN")
|
||||
}
|
||||
return arn, nil
|
||||
}
|
||||
Reference in New Issue
Block a user