mirror of
https://github.com/pgsty/minio.git
synced 2026-11-09 09:11:51 +03:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc6c794972 | |||
| 2e33b99c6b | |||
| 3b7292b637 | |||
| e4f469ae7a | |||
| c921dc75c7 | |||
| 86d543d0f6 | |||
| e4e90b53c1 | |||
| 58d776daa0 | |||
| f6b2e89109 | |||
| ac85c2af76 | |||
| 5aba2aedb3 | |||
| bd77f1df4c | |||
| a8332efa94 | |||
| 3dbef72dc7 | |||
| 2d16e74f38 | |||
| de5070446d | |||
| 374abd1e7d | |||
| 0506d9e83d | |||
| bd3dfad8b9 | |||
| 9fff315555 | |||
| 07b6dce1a5 | |||
| 18fb86b7be | |||
| 58a8275e84 | |||
| 196fab6834 | |||
| 85fc7cea97 | |||
| 328d660106 | |||
| c68910005b | |||
| c79bcc8838 | |||
| 0fe58dbb34 | |||
| 6cb2f56395 | |||
| 59e33b3b21 | |||
| 0e3c92c027 | |||
| db7a9b2c37 | |||
| 44097faec1 | |||
| ff5fca76ab | |||
| bf3da5081f | |||
| 5532982857 | |||
| 783dd875f7 | |||
| 97112c69be | |||
| b0b573052a | |||
| 2939000342 | |||
| 41e1654f9a | |||
| e3cb0278ce | |||
| 0c81f1bdb3 | |||
| 6220875803 | |||
| afd4279cd8 | |||
| 0c8dd8046a | |||
| 3c4ef4338f | |||
| 64cf887b28 | |||
| 927a879052 | |||
| dfe0c96b87 | |||
| e856e10ac2 | |||
| 6d6a731d6d | |||
| 928feb0889 | |||
| b3febe2d24 | |||
| b6b26dba87 | |||
| 5c034e26bd | |||
| cef0fb1434 | |||
| 158d0e26a2 | |||
| 78385bfbeb | |||
| 2a13cc28f2 | |||
| 4d761fda81 | |||
| 4bdf41a6c7 | |||
| 3c605c93fe | |||
| 121f18a443 | |||
| 538aeef27a | |||
| be0d2537b7 | |||
| 3307aa1260 | |||
| 57cfdfd8fb | |||
| dc6733dacc |
@@ -28,14 +28,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -28,24 +28,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
%LocalAppData%\go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
env:
|
env:
|
||||||
@@ -63,9 +45,6 @@ jobs:
|
|||||||
sudo apt install jq -y
|
sudo apt install jq -y
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
|
||||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
|
||||||
go list -deps -json ./... | jq -s 'unique_by(.Module.Path)|.[]|select(has("Module"))|.Module' | ./nancy sleuth
|
|
||||||
make
|
make
|
||||||
make test
|
make test
|
||||||
make test-race
|
make test-race
|
||||||
|
|||||||
@@ -28,14 +28,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -80,14 +80,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Test LDAP/OpenID/Etcd combo
|
- name: Test LDAP/OpenID/Etcd combo
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
|
|||||||
@@ -29,14 +29,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Test Decom
|
- name: Test Decom
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
golint:
|
|
||||||
min-confidence: 0
|
|
||||||
|
|
||||||
gofumpt:
|
gofumpt:
|
||||||
lang-version: "1.18"
|
lang-version: "1.18"
|
||||||
|
|
||||||
# Choose whether or not to use the extra rules that are disabled
|
|
||||||
# by default
|
|
||||||
extra-rules: false
|
|
||||||
|
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
CVE-2020-26160
|
|
||||||
CVE-2020-15136
|
|
||||||
CVE-2020-15115
|
|
||||||
CVE-2020-15114
|
|
||||||
CVE-2022-2835
|
|
||||||
CVE-2022-2837
|
|
||||||
@@ -4514,213 +4514,6 @@ https://github.com/docker/go-units
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
github.com/dswarbrick/smart
|
|
||||||
https://github.com/dswarbrick/smart
|
|
||||||
----------------------------------------------------------------
|
|
||||||
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/dustin/go-humanize
|
github.com/dustin/go-humanize
|
||||||
https://github.com/dustin/go-humanize
|
https://github.com/dustin/go-humanize
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
@@ -14610,6 +14403,33 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
|
github.com/lithammer/shortuuid/v4
|
||||||
|
https://github.com/lithammer/shortuuid/v4
|
||||||
|
----------------------------------------------------------------
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Peter Lithammer
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
================================================================
|
||||||
|
|
||||||
github.com/lucasb-eyer/go-colorful
|
github.com/lucasb-eyer/go-colorful
|
||||||
https://github.com/lucasb-eyer/go-colorful
|
https://github.com/lucasb-eyer/go-colorful
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|||||||
@@ -1083,7 +1083,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
||||||
// to the connected HTTP client. This is a MinIO only extension
|
// to the connected HTTP client.
|
||||||
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ReplicationDiff")
|
ctx := newContext(r, w, "ReplicationDiff")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
@@ -1096,15 +1096,8 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current object layer instance.
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ReplicationDiff)
|
||||||
objectAPI := newObjectLayerFn()
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check if user has permissions to perform this operation
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketVersionsAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -27,16 +28,13 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
|
"github.com/minio/pkg/ldap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List of implemented ID config types.
|
|
||||||
var idCfgTypes = set.CreateStringSet("openid")
|
|
||||||
|
|
||||||
// SetIdentityProviderCfg:
|
// SetIdentityProviderCfg:
|
||||||
//
|
//
|
||||||
// PUT <admin-prefix>/id-cfg?type=openid&name=dex1
|
// PUT <admin-prefix>/id-cfg?type=openid&name=dex1
|
||||||
@@ -64,18 +62,18 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgType := mux.Vars(r)["type"]
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
if !idCfgTypes.Contains(cfgType) {
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
// TODO: change this to invalid type error when implementation
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
// is complete.
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfgDataBuilder strings.Builder
|
var cfgDataBuilder strings.Builder
|
||||||
switch cfgType {
|
switch idpCfgType {
|
||||||
case "openid":
|
case madmin.OpenidIDPCfg:
|
||||||
fmt.Fprintf(&cfgDataBuilder, "identity_openid")
|
fmt.Fprintf(&cfgDataBuilder, "identity_openid")
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
fmt.Fprintf(&cfgDataBuilder, "identity_ldap")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure body content type is opaque.
|
// Ensure body content type is opaque.
|
||||||
@@ -88,6 +86,13 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
// Subsystem configuration name could be empty.
|
// Subsystem configuration name could be empty.
|
||||||
cfgName := mux.Vars(r)["name"]
|
cfgName := mux.Vars(r)["name"]
|
||||||
if cfgName != "" {
|
if cfgName != "" {
|
||||||
|
if idpCfgType == madmin.LDAPIDPCfg {
|
||||||
|
// LDAP does not support multiple configurations. So this must be
|
||||||
|
// empty.
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.SubSystemSeparator, cfgName)
|
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.SubSystemSeparator, cfgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +124,16 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, subSys); err != nil {
|
if err = validateConfig(cfg, subSys); err != nil {
|
||||||
|
|
||||||
|
var validationErr ldap.Validation
|
||||||
|
if errors.As(err, &validationErr) {
|
||||||
|
// If we got an LDAP validation error, we need to send appropriate
|
||||||
|
// error message back to client (likely mc).
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigLDAPValidation),
|
||||||
|
validationErr.FormatError(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -156,28 +171,32 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgType := mux.Vars(r)["type"]
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
cfgName := r.Form.Get("name")
|
cfgName := r.Form.Get("name")
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
|
|
||||||
if !idCfgTypes.Contains(cfgType) {
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
// TODO: change this to invalid type error when implementation
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
// is complete.
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no cfgName is provided, we list.
|
// If no cfgName is provided, we list.
|
||||||
if cfgName == "" {
|
if cfgName == "" {
|
||||||
a.listIdentityProviders(ctx, w, r, cfgType, password)
|
a.listIdentityProviders(ctx, w, r, idpCfgType, password)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
cfg := globalServerConfig.Clone()
|
||||||
|
var cfgInfos []madmin.IDPCfgInfo
|
||||||
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
var err error
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
cfgInfos, err = globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
cfgInfos, err = globalLDAPConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == openid.ErrProviderConfigNotFound {
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -187,7 +206,7 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := madmin.IDPConfig{
|
res := madmin.IDPConfig{
|
||||||
Type: cfgType,
|
Type: idpCfgType,
|
||||||
Name: cfgName,
|
Name: cfgName,
|
||||||
Info: cfgInfos,
|
Info: cfgInfos,
|
||||||
}
|
}
|
||||||
@@ -206,18 +225,22 @@ func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
writeSuccessResponseJSON(w, econfigData)
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, cfgType, password string) {
|
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, idpCfgType, password string) {
|
||||||
// var subSys string
|
var cfgList []madmin.IDPListItem
|
||||||
switch cfgType {
|
var err error
|
||||||
case "openid":
|
switch idpCfgType {
|
||||||
// subSys = config.IdentityOpenIDSubSys
|
case madmin.OpenidIDPCfg:
|
||||||
|
cfg := globalServerConfig.Clone()
|
||||||
|
cfgList, err = globalOpenIDConfig.GetConfigList(cfg)
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
cfg := globalServerConfig.Clone()
|
||||||
|
cfgList, err = globalLDAPConfig.GetConfigList(cfg)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
|
||||||
cfgList, err := globalOpenIDConfig.GetConfigList(cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -251,57 +274,82 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgType := mux.Vars(r)["type"]
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
cfgName := mux.Vars(r)["name"]
|
cfgName := mux.Vars(r)["name"]
|
||||||
if !idCfgTypes.Contains(cfgType) {
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
// TODO: change this to invalid type error when implementation
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
// is complete.
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
cfgCopy := globalServerConfig.Clone()
|
||||||
|
var subSys string
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
subSys = config.IdentityOpenIDSubSys
|
||||||
|
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
if err != nil {
|
|
||||||
if err == openid.ErrProviderConfigNotFound {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
hasEnv := false
|
||||||
return
|
for _, ci := range cfgInfos {
|
||||||
}
|
if ci.IsCfg && ci.IsEnv {
|
||||||
|
hasEnv = true
|
||||||
hasEnv := false
|
break
|
||||||
for _, ci := range cfgInfos {
|
}
|
||||||
if ci.IsCfg && ci.IsEnv {
|
|
||||||
hasEnv = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if hasEnv {
|
if hasEnv {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
subSys = config.IdentityLDAPSubSys
|
||||||
|
cfgInfos, err := globalLDAPConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var subSys string
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
switch cfgType {
|
return
|
||||||
case "openid":
|
}
|
||||||
subSys = config.IdentityOpenIDSubSys
|
|
||||||
|
hasEnv := false
|
||||||
|
for _, ci := range cfgInfos {
|
||||||
|
if ci.IsCfg && ci.IsEnv {
|
||||||
|
hasEnv = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasEnv {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err = readServerConfig(ctx, objectAPI)
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cfg.DelKVS(fmt.Sprintf("%s:%s", subSys, cfgName)); err != nil {
|
cfgKey := fmt.Sprintf("%s:%s", subSys, cfgName)
|
||||||
|
if cfgName == madmin.Default {
|
||||||
|
cfgKey = subSys
|
||||||
|
}
|
||||||
|
if err = cfg.DelKVS(cfgKey); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-15
@@ -135,7 +135,7 @@ func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Add ldap users which have mapped policies if in LDAP mode
|
// Add ldap users which have mapped policies if in LDAP mode
|
||||||
// FIXME(vadmeste): move this to policy info in the future
|
// FIXME(vadmeste): move this to policy info in the future
|
||||||
ldapUsers, err := globalIAMSys.ListLDAPUsers()
|
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
|
||||||
if err != nil && err != errIAMActionNotAllowed {
|
if err != nil && err != errIAMActionNotAllowed {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -657,7 +657,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||||
// query their groups:
|
// query their groups:
|
||||||
if globalLDAPConfig.Enabled {
|
if globalLDAPConfig.Enabled() {
|
||||||
opts.claims[ldapUserN] = targetUser // simple username
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
targetUser, targetGroups, err = globalLDAPConfig.LookupUserDN(targetUser)
|
targetUser, targetGroups, err = globalLDAPConfig.LookupUserDN(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1189,31 +1189,42 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
accountName = cred.ParentUser
|
accountName = cred.ParentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
roleArn := iampolicy.Args{Claims: claims}.GetRoleArn()
|
||||||
|
var effectivePolicy iampolicy.Policy
|
||||||
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
if accountName == globalActiveCred.AccessKey {
|
switch {
|
||||||
|
case accountName == globalActiveCred.AccessKey:
|
||||||
for _, policy := range iampolicy.DefaultPolicies {
|
for _, policy := range iampolicy.DefaultPolicies {
|
||||||
if policy.Name == "consoleAdmin" {
|
if policy.Name == "consoleAdmin" {
|
||||||
buf, err = json.MarshalIndent(policy.Definition, "", " ")
|
effectivePolicy = policy.Definition
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
case roleArn != "":
|
||||||
|
_, policy, err := globalIAMSys.GetRolePolicy(roleArn)
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
policySlice := newMappedPolicy(policy).toSlice()
|
||||||
|
effectivePolicy = globalIAMSys.GetCombinedPolicy(policySlice...)
|
||||||
|
|
||||||
|
default:
|
||||||
policies, err := globalIAMSys.PolicyDBGet(accountName, false, cred.Groups...)
|
policies, err := globalIAMSys.PolicyDBGet(accountName, false, cred.Groups...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
effectivePolicy = globalIAMSys.GetCombinedPolicy(policies...)
|
||||||
|
|
||||||
buf, err = json.MarshalIndent(globalIAMSys.GetCombinedPolicy(policies...), "", " ")
|
}
|
||||||
if err != nil {
|
buf, err = json.MarshalIndent(effectivePolicy, "", " ")
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
if err != nil {
|
||||||
return
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
acctInfo := madmin.AccountInfo{
|
acctInfo := madmin.AccountInfo{
|
||||||
@@ -2086,7 +2097,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||||
// query their groups:
|
// query their groups:
|
||||||
if globalLDAPConfig.Enabled {
|
if globalLDAPConfig.Enabled() {
|
||||||
opts.claims[ldapUserN] = svcAcctReq.AccessKey // simple username
|
opts.claims[ldapUserN] = svcAcctReq.AccessKey // simple username
|
||||||
targetUser, _, err := globalLDAPConfig.LookupUserDN(svcAcctReq.AccessKey)
|
targetUser, _, err := globalLDAPConfig.LookupUserDN(svcAcctReq.AccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1231,6 +1231,19 @@ func (c *check) mustGetObject(ctx context.Context, client *minio.Client, bucket,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *check) mustHeadObject(ctx context.Context, client *minio.Client, bucket, object string, tagCount int) {
|
||||||
|
c.Helper()
|
||||||
|
|
||||||
|
oinfo, err := client.StatObject(ctx, bucket, object, minio.StatObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to download the object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if oinfo.UserTagCount != tagCount {
|
||||||
|
c.Fatalf("expected tagCount: %d, got %d", tagCount, oinfo.UserTagCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
c.Helper()
|
c.Helper()
|
||||||
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
||||||
|
|||||||
+24
-102
@@ -411,6 +411,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
jobID := r.Form.Get("by-jobID")
|
||||||
|
|
||||||
hosts := strings.Split(r.Form.Get("hosts"), ",")
|
hosts := strings.Split(r.Form.Get("hosts"), ",")
|
||||||
byHost := strings.EqualFold(r.Form.Get("by-host"), "true")
|
byHost := strings.EqualFold(r.Form.Get("by-host"), "true")
|
||||||
@@ -432,12 +433,20 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
for n > 0 {
|
for n > 0 {
|
||||||
var m madmin.RealtimeMetrics
|
var m madmin.RealtimeMetrics
|
||||||
mLocal := collectLocalMetrics(types, hostMap, diskMap)
|
mLocal := collectLocalMetrics(types, collectMetricsOpts{
|
||||||
|
hosts: hostMap,
|
||||||
|
disks: diskMap,
|
||||||
|
jobID: jobID,
|
||||||
|
})
|
||||||
m.Merge(&mLocal)
|
m.Merge(&mLocal)
|
||||||
|
|
||||||
// Allow half the interval for collecting remote...
|
// Allow half the interval for collecting remote...
|
||||||
cctx, cancel := context.WithTimeout(ctx, interval/2)
|
cctx, cancel := context.WithTimeout(ctx, interval/2)
|
||||||
mRemote := collectRemoteMetrics(cctx, types, hostMap, diskMap)
|
mRemote := collectRemoteMetrics(cctx, types, collectMetricsOpts{
|
||||||
|
hosts: hostMap,
|
||||||
|
disks: diskMap,
|
||||||
|
jobID: jobID,
|
||||||
|
})
|
||||||
cancel()
|
cancel()
|
||||||
m.Merge(&mRemote)
|
m.Merge(&mRemote)
|
||||||
if !byHost {
|
if !byHost {
|
||||||
@@ -449,7 +458,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
m.Final = n <= 1
|
m.Final = n <= 1
|
||||||
|
|
||||||
// Marshal API response
|
// Marshal API reesponse
|
||||||
if err := enc.Encode(&m); err != nil {
|
if err := enc.Encode(&m); err != nil {
|
||||||
n = 0
|
n = 0
|
||||||
}
|
}
|
||||||
@@ -1789,8 +1798,8 @@ func getServerInfo(ctx context.Context, r *http.Request) madmin.InfoMessage {
|
|||||||
kmsStat := fetchKMSStatus()
|
kmsStat := fetchKMSStatus()
|
||||||
|
|
||||||
ldap := madmin.LDAP{}
|
ldap := madmin.LDAP{}
|
||||||
if globalLDAPConfig.Enabled {
|
if globalLDAPConfig.Enabled() {
|
||||||
ldapConn, err := globalLDAPConfig.Connect()
|
ldapConn, err := globalLDAPConfig.LDAP.Connect()
|
||||||
//nolint:gocritic
|
//nolint:gocritic
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ldap.Status = string(madmin.ItemOffline)
|
ldap.Status = string(madmin.ItemOffline)
|
||||||
@@ -1959,11 +1968,6 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
deadline := 10 * time.Second // Default deadline is 10secs for health diagnostics.
|
deadline := 10 * time.Second // Default deadline is 10secs for health diagnostics.
|
||||||
if query.Get(string(madmin.HealthDataTypePerfNet)) != "" ||
|
|
||||||
query.Get(string(madmin.HealthDataTypePerfDrive)) != "" ||
|
|
||||||
query.Get(string(madmin.HealthDataTypePerfObj)) != "" {
|
|
||||||
deadline = 1 * time.Hour
|
|
||||||
}
|
|
||||||
if dstr := r.Form.Get("deadline"); dstr != "" {
|
if dstr := r.Form.Get("deadline"); dstr != "" {
|
||||||
var err error
|
var err error
|
||||||
deadline, err = time.ParseDuration(dstr)
|
deadline, err = time.ParseDuration(dstr)
|
||||||
@@ -1984,12 +1988,6 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
healthCtx, healthCancel := context.WithTimeout(lkctx.Context(), deadline)
|
healthCtx, healthCancel := context.WithTimeout(lkctx.Context(), deadline)
|
||||||
defer healthCancel()
|
defer healthCancel()
|
||||||
|
|
||||||
// Freeze all incoming S3 API calls before running speedtest.
|
|
||||||
globalNotificationSys.ServiceFreeze(ctx, true)
|
|
||||||
|
|
||||||
// unfreeze all incoming S3 API calls after speedtest.
|
|
||||||
defer globalNotificationSys.ServiceFreeze(ctx, false)
|
|
||||||
|
|
||||||
hostAnonymizer := createHostAnonymizer()
|
hostAnonymizer := createHostAnonymizer()
|
||||||
// anonAddr - Anonymizes hosts in given input string.
|
// anonAddr - Anonymizes hosts in given input string.
|
||||||
anonAddr := func(addr string) string {
|
anonAddr := func(addr string) string {
|
||||||
@@ -2135,7 +2133,10 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
if !globalIsDistErasure {
|
if !globalIsDistErasure {
|
||||||
// FS mode - single server - hard code to `server1`
|
// FS mode - single server - hard code to `server1`
|
||||||
anonCmdLine := strings.ReplaceAll(cmdLine, globalLocalNodeName, "server1")
|
anonCmdLine := strings.ReplaceAll(cmdLine, globalLocalNodeName, "server1")
|
||||||
return strings.ReplaceAll(anonCmdLine, globalMinioConsoleHost, "server1")
|
if len(globalMinioConsoleHost) > 0 {
|
||||||
|
anonCmdLine = strings.ReplaceAll(anonCmdLine, globalMinioConsoleHost, "server1")
|
||||||
|
}
|
||||||
|
return anonCmdLine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server start command regex groups:
|
// Server start command regex groups:
|
||||||
@@ -2225,87 +2226,6 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAndWriteDrivePerfInfo := func() {
|
|
||||||
if query.Get(string(madmin.HealthDataTypePerfDrive)) == "true" {
|
|
||||||
opts := madmin.DriveSpeedTestOpts{
|
|
||||||
Serial: false,
|
|
||||||
BlockSize: 4 * humanize.MiByte,
|
|
||||||
FileSize: 1 * humanize.GiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
localDPI := driveSpeedTest(ctx, opts)
|
|
||||||
healthInfo.Perf.DrivePerf = append(healthInfo.Perf.DrivePerf, localDPI)
|
|
||||||
|
|
||||||
perfCh := globalNotificationSys.DriveSpeedTest(ctx, opts)
|
|
||||||
for perfInfo := range perfCh {
|
|
||||||
healthInfo.Perf.DrivePerf = append(healthInfo.Perf.DrivePerf, perfInfo)
|
|
||||||
}
|
|
||||||
partialWrite(healthInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAndWriteObjPerfInfo := func() {
|
|
||||||
if query.Get(string(madmin.HealthDataTypePerfObj)) == "true" {
|
|
||||||
concurrent := 32
|
|
||||||
|
|
||||||
storageInfo, _ := objectAPI.StorageInfo(ctx)
|
|
||||||
|
|
||||||
size := 64 * humanize.MiByte
|
|
||||||
autotune := true
|
|
||||||
|
|
||||||
sufficientCapacity, canAutotune, capacityErrMsg := validateObjPerfOptions(ctx, storageInfo, concurrent, size, autotune)
|
|
||||||
if !sufficientCapacity {
|
|
||||||
healthInfo.Perf.Error = capacityErrMsg
|
|
||||||
partialWrite(healthInfo)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !canAutotune {
|
|
||||||
autotune = false
|
|
||||||
}
|
|
||||||
|
|
||||||
bucketExists, err := makeObjectPerfBucket(ctx, objectAPI, globalObjectPerfBucket)
|
|
||||||
if err != nil {
|
|
||||||
healthInfo.Perf.Error = "Unable to create bucket: " + err.Error()
|
|
||||||
partialWrite(healthInfo)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bucketExists {
|
|
||||||
defer deleteObjectPerfBucket(objectAPI)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := speedTestOpts{
|
|
||||||
objectSize: size,
|
|
||||||
concurrencyStart: concurrent,
|
|
||||||
duration: 10 * time.Second,
|
|
||||||
autotune: autotune,
|
|
||||||
}
|
|
||||||
|
|
||||||
perfCh := objectSpeedTest(ctx, opts)
|
|
||||||
for perfInfo := range perfCh {
|
|
||||||
healthInfo.Perf.ObjPerf = append(healthInfo.Perf.ObjPerf, perfInfo)
|
|
||||||
}
|
|
||||||
partialWrite(healthInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAndWriteNetPerfInfo := func() {
|
|
||||||
if query.Get(string(madmin.HealthDataTypePerfObj)) == "true" {
|
|
||||||
if !globalIsDistErasure {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
netPerf := globalNotificationSys.Netperf(ctx, time.Second*10)
|
|
||||||
for _, np := range netPerf {
|
|
||||||
np.Endpoint = anonAddr(np.Endpoint)
|
|
||||||
healthInfo.Perf.NetPerf = append(healthInfo.Perf.NetPerf, np)
|
|
||||||
}
|
|
||||||
|
|
||||||
partialWrite(healthInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anonymizeNetwork := func(network map[string]string) map[string]string {
|
anonymizeNetwork := func(network map[string]string) map[string]string {
|
||||||
anonNetwork := map[string]string{}
|
anonNetwork := map[string]string{}
|
||||||
for endpoint, status := range network {
|
for endpoint, status := range network {
|
||||||
@@ -2335,16 +2255,13 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
getAndWriteMemInfo()
|
getAndWriteMemInfo()
|
||||||
getAndWriteProcInfo()
|
getAndWriteProcInfo()
|
||||||
getAndWriteMinioConfig()
|
getAndWriteMinioConfig()
|
||||||
getAndWriteDrivePerfInfo()
|
|
||||||
getAndWriteObjPerfInfo()
|
|
||||||
getAndWriteNetPerfInfo()
|
|
||||||
getAndWriteSysErrors()
|
getAndWriteSysErrors()
|
||||||
getAndWriteSysServices()
|
getAndWriteSysServices()
|
||||||
getAndWriteSysConfig()
|
getAndWriteSysConfig()
|
||||||
|
|
||||||
if query.Get("minioinfo") == "true" {
|
if query.Get("minioinfo") == "true" {
|
||||||
infoMessage := getServerInfo(ctx, r)
|
infoMessage := getServerInfo(ctx, r)
|
||||||
servers := []madmin.ServerInfo{}
|
servers := make([]madmin.ServerInfo, 0, len(infoMessage.Servers))
|
||||||
for _, server := range infoMessage.Servers {
|
for _, server := range infoMessage.Servers {
|
||||||
anonEndpoint := anonAddr(server.Endpoint)
|
anonEndpoint := anonAddr(server.Endpoint)
|
||||||
servers = append(servers, madmin.ServerInfo{
|
servers = append(servers, madmin.ServerInfo{
|
||||||
@@ -2363,6 +2280,11 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
Frees: server.MemStats.Frees,
|
Frees: server.MemStats.Frees,
|
||||||
HeapAlloc: server.MemStats.HeapAlloc,
|
HeapAlloc: server.MemStats.HeapAlloc,
|
||||||
},
|
},
|
||||||
|
GoMaxProcs: server.GoMaxProcs,
|
||||||
|
NumCPU: server.NumCPU,
|
||||||
|
RuntimeVersion: server.RuntimeVersion,
|
||||||
|
GCStats: server.GCStats,
|
||||||
|
MinioEnvVars: server.MinioEnvVars,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
|
|||||||
|
|
||||||
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
|
|||||||
@@ -100,14 +100,14 @@ type allHealState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newHealState - initialize global heal state management
|
// newHealState - initialize global heal state management
|
||||||
func newHealState(cleanup bool) *allHealState {
|
func newHealState(ctx context.Context, cleanup bool) *allHealState {
|
||||||
hstate := &allHealState{
|
hstate := &allHealState{
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
healLocalDisks: make(map[Endpoint]bool),
|
healLocalDisks: make(map[Endpoint]bool),
|
||||||
healStatus: make(map[string]healingTracker),
|
healStatus: make(map[string]healingTracker),
|
||||||
}
|
}
|
||||||
if cleanup {
|
if cleanup {
|
||||||
go hstate.periodicHealSeqsClean(GlobalContext)
|
go hstate.periodicHealSeqsClean(ctx)
|
||||||
}
|
}
|
||||||
return hstate
|
return hstate
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,16 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ReplicationDiffHandler))).Queries("bucket", "{bucket:.*}")
|
gz(httpTraceHdrs(adminAPI.ReplicationDiffHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
|
|
||||||
|
// Batch job operations
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/start-job").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.StartBatchJob)))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.ListBatchJobs)))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
|
||||||
|
gz(httpTraceHdrs(adminAPI.DescribeBatchJob)))
|
||||||
|
|
||||||
// Bucket migration operations
|
// Bucket migration operations
|
||||||
// ExportBucketMetaHandler
|
// ExportBucketMetaHandler
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,6 +71,22 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
var memstats runtime.MemStats
|
var memstats runtime.MemStats
|
||||||
runtime.ReadMemStats(&memstats)
|
runtime.ReadMemStats(&memstats)
|
||||||
|
|
||||||
|
gcStats := debug.GCStats{
|
||||||
|
// If stats.PauseQuantiles is non-empty, ReadGCStats fills
|
||||||
|
// it with quantiles summarizing the distribution of pause time.
|
||||||
|
// For example, if len(stats.PauseQuantiles) is 5, it will be
|
||||||
|
// filled with the minimum, 25%, 50%, 75%, and maximum pause times.
|
||||||
|
PauseQuantiles: make([]time.Duration, 5),
|
||||||
|
}
|
||||||
|
debug.ReadGCStats(&gcStats)
|
||||||
|
// Truncate GC stats to max 5 entries.
|
||||||
|
if len(gcStats.PauseEnd) > 5 {
|
||||||
|
gcStats.PauseEnd = gcStats.PauseEnd[len(gcStats.PauseEnd)-5:]
|
||||||
|
}
|
||||||
|
if len(gcStats.Pause) > 5 {
|
||||||
|
gcStats.Pause = gcStats.Pause[len(gcStats.Pause)-5:]
|
||||||
|
}
|
||||||
|
|
||||||
props := madmin.ServerProperties{
|
props := madmin.ServerProperties{
|
||||||
State: string(madmin.ItemInitializing),
|
State: string(madmin.ItemInitializing),
|
||||||
Endpoint: addr,
|
Endpoint: addr,
|
||||||
@@ -81,6 +101,44 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
Frees: memstats.Frees,
|
Frees: memstats.Frees,
|
||||||
HeapAlloc: memstats.HeapAlloc,
|
HeapAlloc: memstats.HeapAlloc,
|
||||||
},
|
},
|
||||||
|
GoMaxProcs: runtime.GOMAXPROCS(0),
|
||||||
|
NumCPU: runtime.NumCPU(),
|
||||||
|
RuntimeVersion: runtime.Version(),
|
||||||
|
GCStats: &madmin.GCStats{
|
||||||
|
LastGC: gcStats.LastGC,
|
||||||
|
NumGC: gcStats.NumGC,
|
||||||
|
PauseTotal: gcStats.PauseTotal,
|
||||||
|
Pause: gcStats.Pause,
|
||||||
|
PauseEnd: gcStats.PauseEnd,
|
||||||
|
},
|
||||||
|
MinioEnvVars: make(map[string]string, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
sensitive := map[string]struct{}{
|
||||||
|
config.EnvAccessKey: {},
|
||||||
|
config.EnvSecretKey: {},
|
||||||
|
config.EnvRootUser: {},
|
||||||
|
config.EnvRootPassword: {},
|
||||||
|
config.EnvMinIOSubnetAPIKey: {},
|
||||||
|
config.EnvKMSSecretKey: {},
|
||||||
|
}
|
||||||
|
for _, v := range os.Environ() {
|
||||||
|
if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
split := strings.SplitN(v, "=", 2)
|
||||||
|
key := split[0]
|
||||||
|
value := ""
|
||||||
|
if len(split) > 1 {
|
||||||
|
value = split[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not send sensitive creds.
|
||||||
|
if _, ok := sensitive[key]; ok || strings.Contains(strings.ToLower(key), "password") || strings.HasSuffix(strings.ToLower(key), "key") {
|
||||||
|
props.MinioEnvVars[key] = "*** EXISTS, REDACTED ***"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
props.MinioEnvVars[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
objLayer := newObjectLayerFn()
|
objLayer := newObjectLayerFn()
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
@@ -263,6 +264,7 @@ const (
|
|||||||
ErrAdminNoSuchUser
|
ErrAdminNoSuchUser
|
||||||
ErrAdminNoSuchGroup
|
ErrAdminNoSuchGroup
|
||||||
ErrAdminGroupNotEmpty
|
ErrAdminGroupNotEmpty
|
||||||
|
ErrAdminNoSuchJob
|
||||||
ErrAdminNoSuchPolicy
|
ErrAdminNoSuchPolicy
|
||||||
ErrAdminInvalidArgument
|
ErrAdminInvalidArgument
|
||||||
ErrAdminInvalidAccessKey
|
ErrAdminInvalidAccessKey
|
||||||
@@ -273,6 +275,8 @@ const (
|
|||||||
ErrAdminNoSuchConfigTarget
|
ErrAdminNoSuchConfigTarget
|
||||||
ErrAdminConfigEnvOverridden
|
ErrAdminConfigEnvOverridden
|
||||||
ErrAdminConfigDuplicateKeys
|
ErrAdminConfigDuplicateKeys
|
||||||
|
ErrAdminConfigInvalidIDPType
|
||||||
|
ErrAdminConfigLDAPValidation
|
||||||
ErrAdminCredentialsMismatch
|
ErrAdminCredentialsMismatch
|
||||||
ErrInsecureClientRequest
|
ErrInsecureClientRequest
|
||||||
ErrObjectTampered
|
ErrObjectTampered
|
||||||
@@ -1226,6 +1230,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The specified group does not exist.",
|
Description: "The specified group does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrAdminNoSuchJob: {
|
||||||
|
Code: "XMinioAdminNoSuchJob",
|
||||||
|
Description: "The specified job does not exist.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrAdminGroupNotEmpty: {
|
ErrAdminGroupNotEmpty: {
|
||||||
Code: "XMinioAdminGroupNotEmpty",
|
Code: "XMinioAdminGroupNotEmpty",
|
||||||
Description: "The specified group is not empty - cannot remove it.",
|
Description: "The specified group is not empty - cannot remove it.",
|
||||||
@@ -1282,6 +1291,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "JSON configuration provided has objects with duplicate keys",
|
Description: "JSON configuration provided has objects with duplicate keys",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminConfigInvalidIDPType: {
|
||||||
|
Code: "XMinioAdminConfigInvalidIDPType",
|
||||||
|
Description: fmt.Sprintf("Invalid IDP configuration type - must be one of %v", madmin.ValidIDPConfigTypes),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigLDAPValidation: {
|
||||||
|
Code: "XMinioAdminConfigLDAPValidation",
|
||||||
|
Description: "LDAP Configuration validation failed",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminConfigNotificationTargetsFailed: {
|
ErrAdminConfigNotificationTargetsFailed: {
|
||||||
Code: "XMinioAdminNotificationTargetsTestFailed",
|
Code: "XMinioAdminNotificationTargetsTestFailed",
|
||||||
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
||||||
@@ -1923,6 +1942,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminNoSuchGroup
|
apiErr = ErrAdminNoSuchGroup
|
||||||
case errGroupNotEmpty:
|
case errGroupNotEmpty:
|
||||||
apiErr = ErrAdminGroupNotEmpty
|
apiErr = ErrAdminGroupNotEmpty
|
||||||
|
case errNoSuchJob:
|
||||||
|
apiErr = ErrAdminNoSuchJob
|
||||||
case errNoSuchPolicy:
|
case errNoSuchPolicy:
|
||||||
apiErr = ErrAdminNoSuchPolicy
|
apiErr = ErrAdminNoSuchPolicy
|
||||||
case errSignatureMismatch:
|
case errSignatureMismatch:
|
||||||
@@ -2220,6 +2241,12 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
// their internal error types. This code is only
|
// their internal error types. This code is only
|
||||||
// useful with gateway implementations.
|
// useful with gateway implementations.
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
|
case batchReplicationJobError:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: e.Code,
|
||||||
|
Description: e.Description,
|
||||||
|
HTTPStatusCode: e.HTTPStatusCode,
|
||||||
|
}
|
||||||
case InvalidArgument:
|
case InvalidArgument:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
|
|||||||
+24
-27
@@ -34,6 +34,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -90,8 +91,7 @@ type ListVersionsResponse struct {
|
|||||||
IsTruncated bool
|
IsTruncated bool
|
||||||
|
|
||||||
CommonPrefixes []CommonPrefix
|
CommonPrefixes []CommonPrefix
|
||||||
DeleteMarkers []DeleteMarkerVersion `xml:"DeleteMarker,omitempty"`
|
Versions []ObjectVersion
|
||||||
Versions []ObjectVersion `xml:"Version,omitempty"`
|
|
||||||
|
|
||||||
// Encoding type used to encode object keys in the response.
|
// Encoding type used to encode object keys in the response.
|
||||||
EncodingType string `xml:"EncodingType,omitempty"`
|
EncodingType string `xml:"EncodingType,omitempty"`
|
||||||
@@ -166,10 +166,10 @@ type Part struct {
|
|||||||
Size int64
|
Size int64
|
||||||
|
|
||||||
// Checksum values
|
// Checksum values
|
||||||
ChecksumCRC32 string
|
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||||
ChecksumCRC32C string
|
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||||
ChecksumSHA1 string
|
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||||
ChecksumSHA256 string
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPartsResponse - format for list parts response.
|
// ListPartsResponse - format for list parts response.
|
||||||
@@ -256,6 +256,19 @@ type ObjectVersion struct {
|
|||||||
Object
|
Object
|
||||||
IsLatest bool
|
IsLatest bool
|
||||||
VersionID string `xml:"VersionId"`
|
VersionID string `xml:"VersionId"`
|
||||||
|
|
||||||
|
isDeleteMarker bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalXML - marshal ObjectVersion
|
||||||
|
func (o ObjectVersion) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||||
|
if o.isDeleteMarker {
|
||||||
|
start.Name.Local = "DeleteMarker"
|
||||||
|
} else {
|
||||||
|
start.Name.Local = "Version"
|
||||||
|
}
|
||||||
|
type objectVersionWrapper ObjectVersion
|
||||||
|
return e.EncodeElement(objectVersionWrapper(o), start)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMarkerVersion container for delete marker metadata
|
// DeleteMarkerVersion container for delete marker metadata
|
||||||
@@ -390,10 +403,10 @@ type CompleteMultipartUploadResponse struct {
|
|||||||
Key string
|
Key string
|
||||||
ETag string
|
ETag string
|
||||||
|
|
||||||
ChecksumCRC32 string
|
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||||
ChecksumCRC32C string
|
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||||
ChecksumSHA1 string
|
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||||
ChecksumSHA256 string
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteError structure.
|
// DeleteError structure.
|
||||||
@@ -482,7 +495,6 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
deleteMarkers := make([]DeleteMarkerVersion, 0, len(resp.Objects))
|
|
||||||
|
|
||||||
owner := Owner{
|
owner := Owner{
|
||||||
ID: globalMinioDefaultOwnerID,
|
ID: globalMinioDefaultOwnerID,
|
||||||
@@ -494,21 +506,6 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
if object.Name == "" {
|
if object.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if object.DeleteMarker {
|
|
||||||
deleteMarker := DeleteMarkerVersion{
|
|
||||||
Key: s3EncodeName(object.Name, encodingType),
|
|
||||||
LastModified: object.ModTime.UTC().Format(iso8601TimeFormat),
|
|
||||||
Owner: owner,
|
|
||||||
VersionID: object.VersionID,
|
|
||||||
}
|
|
||||||
if deleteMarker.VersionID == "" {
|
|
||||||
deleteMarker.VersionID = nullVersionID
|
|
||||||
}
|
|
||||||
deleteMarker.IsLatest = object.IsLatest
|
|
||||||
deleteMarkers = append(deleteMarkers, deleteMarker)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content := ObjectVersion{}
|
content := ObjectVersion{}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
||||||
@@ -527,12 +524,12 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
content.VersionID = nullVersionID
|
content.VersionID = nullVersionID
|
||||||
}
|
}
|
||||||
content.IsLatest = object.IsLatest
|
content.IsLatest = object.IsLatest
|
||||||
|
content.isDeleteMarker = object.DeleteMarker
|
||||||
versions = append(versions, content)
|
versions = append(versions, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Name = bucket
|
data.Name = bucket
|
||||||
data.Versions = versions
|
data.Versions = versions
|
||||||
data.DeleteMarkers = deleteMarkers
|
|
||||||
data.EncodingType = encodingType
|
data.EncodingType = encodingType
|
||||||
data.Prefix = s3EncodeName(prefix, encodingType)
|
data.Prefix = s3EncodeName(prefix, encodingType)
|
||||||
data.KeyMarker = s3EncodeName(marker, encodingType)
|
data.KeyMarker = s3EncodeName(marker, encodingType)
|
||||||
|
|||||||
+129
-126
File diff suppressed because one or more lines are too long
+11
-10
@@ -359,7 +359,10 @@ func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int6
|
|||||||
|
|
||||||
// Tests is requested authenticated function, tests replies for s3 errors.
|
// Tests is requested authenticated function, tests replies for s3 errors.
|
||||||
func TestIsReqAuthenticated(t *testing.T) {
|
func TestIsReqAuthenticated(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(GlobalContext)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -368,10 +371,7 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
@@ -413,7 +413,10 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckAdminRequestAuthType(t *testing.T) {
|
func TestCheckAdminRequestAuthType(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -439,7 +442,6 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
@@ -451,7 +453,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS()
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -461,8 +463,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by "stringer -type=batchReplicationMetric -trimprefix=batchReplicationMetric batch-handlers.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[batchReplicationMetricObject-0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _batchReplicationMetric_name = "Object"
|
||||||
|
|
||||||
|
var _batchReplicationMetric_index = [...]uint8{0, 6}
|
||||||
|
|
||||||
|
func (i batchReplicationMetric) String() string {
|
||||||
|
if i >= batchReplicationMetric(len(_batchReplicationMetric_index)-1) {
|
||||||
|
return "batchReplicationMetric(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _batchReplicationMetric_name[_batchReplicationMetric_index[i]:_batchReplicationMetric_index[i+1]]
|
||||||
|
}
|
||||||
+17
-4
@@ -24,6 +24,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
@@ -146,16 +147,28 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
// Can never happen unless there are programmer bugs
|
// Can never happen unless there are programmer bugs
|
||||||
return 0, errUnexpected
|
return 0, errUnexpected
|
||||||
}
|
}
|
||||||
|
ignoredErrs := []error{
|
||||||
|
errDiskNotFound,
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(b.volume, minioMetaBucket) {
|
||||||
|
ignoredErrs = append(ignoredErrs,
|
||||||
|
errFileNotFound,
|
||||||
|
errVolumeNotFound,
|
||||||
|
errFileVersionNotFound,
|
||||||
|
)
|
||||||
|
}
|
||||||
if b.rc == nil {
|
if b.rc == nil {
|
||||||
// For the first ReadAt() call we need to open the stream for reading.
|
// For the first ReadAt() call we need to open the stream for reading.
|
||||||
b.currOffset = offset
|
b.currOffset = offset
|
||||||
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
||||||
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
||||||
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
||||||
if err != nil && err != errDiskNotFound {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext,
|
if !IsErr(err, ignoredErrs...) {
|
||||||
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
logger.LogIf(GlobalContext,
|
||||||
b.disk, b.volume, b.filePath, err))
|
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
||||||
|
b.disk, b.volume, b.filePath, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete bucket encryption config from object layer
|
// Delete bucket encryption config from object layer
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, nil)
|
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketSSEConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1618,7 +1618,7 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, nil)
|
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketTaggingConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ func (api objectAPIHandlers) DeleteBucketLifecycleHandler(w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, nil); err != nil {
|
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketLifecycleConfig); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-22
@@ -155,9 +155,14 @@ type newerNoncurrentTask struct {
|
|||||||
versions []ObjectToDelete
|
versions []ObjectToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type transitionTask struct {
|
||||||
|
tier string
|
||||||
|
objInfo ObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
type transitionState struct {
|
type transitionState struct {
|
||||||
once sync.Once
|
once sync.Once
|
||||||
transitionCh chan ObjectInfo
|
transitionCh chan transitionTask
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
objAPI ObjectLayer
|
objAPI ObjectLayer
|
||||||
@@ -171,13 +176,13 @@ type transitionState struct {
|
|||||||
lastDayStats map[string]*lastDayTierStats
|
lastDayStats map[string]*lastDayTierStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transitionState) queueTransitionTask(oi ObjectInfo) {
|
func (t *transitionState) queueTransitionTask(oi ObjectInfo, sc string) {
|
||||||
select {
|
select {
|
||||||
case <-GlobalContext.Done():
|
case <-GlobalContext.Done():
|
||||||
t.once.Do(func() {
|
t.once.Do(func() {
|
||||||
close(t.transitionCh)
|
close(t.transitionCh)
|
||||||
})
|
})
|
||||||
case t.transitionCh <- oi:
|
case t.transitionCh <- transitionTask{objInfo: oi, tier: sc}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +191,7 @@ var globalTransitionState *transitionState
|
|||||||
|
|
||||||
func newTransitionState(ctx context.Context, objAPI ObjectLayer) *transitionState {
|
func newTransitionState(ctx context.Context, objAPI ObjectLayer) *transitionState {
|
||||||
return &transitionState{
|
return &transitionState{
|
||||||
transitionCh: make(chan ObjectInfo, 10000),
|
transitionCh: make(chan transitionTask, 10000),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
objAPI: objAPI,
|
objAPI: objAPI,
|
||||||
killCh: make(chan struct{}),
|
killCh: make(chan struct{}),
|
||||||
@@ -213,27 +218,25 @@ func (t *transitionState) worker(ctx context.Context, objectAPI ObjectLayer) {
|
|||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case oi, ok := <-t.transitionCh:
|
case task, ok := <-t.transitionCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&t.activeTasks, 1)
|
atomic.AddInt32(&t.activeTasks, 1)
|
||||||
var tier string
|
if err := transitionObject(ctx, objectAPI, task.objInfo, task.tier); err != nil {
|
||||||
var err error
|
logger.LogIf(ctx, fmt.Errorf("Transition failed for %s/%s version:%s with %w",
|
||||||
if tier, err = transitionObject(ctx, objectAPI, oi); err != nil {
|
task.objInfo.Bucket, task.objInfo.Name, task.objInfo.VersionID, err))
|
||||||
logger.LogIf(ctx, fmt.Errorf("Transition failed for %s/%s version:%s with %w", oi.Bucket, oi.Name, oi.VersionID, err))
|
|
||||||
} else {
|
} else {
|
||||||
ts := tierStats{
|
ts := tierStats{
|
||||||
TotalSize: uint64(oi.Size),
|
TotalSize: uint64(task.objInfo.Size),
|
||||||
NumVersions: 1,
|
NumVersions: 1,
|
||||||
}
|
}
|
||||||
if oi.IsLatest {
|
if task.objInfo.IsLatest {
|
||||||
ts.NumObjects = 1
|
ts.NumObjects = 1
|
||||||
}
|
}
|
||||||
t.addLastDayStats(tier, ts)
|
t.addLastDayStats(task.tier, ts)
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&t.activeTasks, -1)
|
atomic.AddInt32(&t.activeTasks, -1)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,9 +307,10 @@ func validateTransitionTier(lc *lifecycle.Lifecycle) error {
|
|||||||
// This is to be called after a successful upload of an object (version).
|
// This is to be called after a successful upload of an object (version).
|
||||||
func enqueueTransitionImmediate(obj ObjectInfo) {
|
func enqueueTransitionImmediate(obj ObjectInfo) {
|
||||||
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
|
||||||
switch lc.ComputeAction(obj.ToLifecycleOpts()) {
|
event := lc.Eval(obj.ToLifecycleOpts(), time.Now())
|
||||||
|
switch event.Action {
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
globalTransitionState.queueTransitionTask(obj)
|
globalTransitionState.queueTransitionTask(obj, event.StorageClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,12 +407,7 @@ func genTransitionObjName(bucket string) (string, error) {
|
|||||||
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content
|
// storage specified by the transition ARN, the metadata is left behind on source cluster and original content
|
||||||
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
|
||||||
// to the transition tier without decrypting or re-encrypting.
|
// to the transition tier without decrypting or re-encrypting.
|
||||||
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo) (string, error) {
|
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, tier string) error {
|
||||||
lc, err := globalLifecycleSys.Get(oi.Bucket)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
tier := lc.TransitionTier(oi.ToLifecycleOpts())
|
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
Transition: TransitionOptions{
|
Transition: TransitionOptions{
|
||||||
Status: lifecycle.TransitionPending,
|
Status: lifecycle.TransitionPending,
|
||||||
@@ -420,7 +419,7 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo)
|
|||||||
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name),
|
||||||
MTime: oi.ModTime,
|
MTime: oi.ModTime,
|
||||||
}
|
}
|
||||||
return tier, objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
return objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
type auditTierOp struct {
|
type auditTierOp struct {
|
||||||
|
|||||||
@@ -81,9 +81,7 @@ func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update update bucket metadata for the specified config file.
|
func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string, configFile string, configData []byte, parse bool) (updatedAt time.Time, err error) {
|
||||||
// The configData data should not be modified after being sent here.
|
|
||||||
func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configFile string, configData []byte) (updatedAt time.Time, err error) {
|
|
||||||
objAPI := newObjectLayerFn()
|
objAPI := newObjectLayerFn()
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
return updatedAt, errServerNotInitialized
|
return updatedAt, errServerNotInitialized
|
||||||
@@ -107,7 +105,7 @@ func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configF
|
|||||||
return updatedAt, errInvalidArgument
|
return updatedAt, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := loadBucketMetadata(ctx, objAPI, bucket)
|
meta, err := loadBucketMetadataParse(ctx, objAPI, bucket, parse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
|
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
|
||||||
// Only single drive mode needs this fallback.
|
// Only single drive mode needs this fallback.
|
||||||
@@ -165,6 +163,18 @@ func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configF
|
|||||||
return updatedAt, nil
|
return updatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete delete the bucket metadata for the specified bucket.
|
||||||
|
// must be used by all callers instead of using Update() with nil configData.
|
||||||
|
func (sys *BucketMetadataSys) Delete(ctx context.Context, bucket string, configFile string) (updatedAt time.Time, err error) {
|
||||||
|
return sys.updateAndParse(ctx, bucket, configFile, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update update bucket metadata for the specified bucket.
|
||||||
|
// The configData data should not be modified after being sent here.
|
||||||
|
func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configFile string, configData []byte) (updatedAt time.Time, err error) {
|
||||||
|
return sys.updateAndParse(ctx, bucket, configFile, configData, true)
|
||||||
|
}
|
||||||
|
|
||||||
// Get metadata for a bucket.
|
// Get metadata for a bucket.
|
||||||
// If no metadata exists errConfigNotFound is returned and a new metadata is returned.
|
// If no metadata exists errConfigNotFound is returned and a new metadata is returned.
|
||||||
// Only a shallow copy is returned, so referenced data should not be modified,
|
// Only a shallow copy is returned, so referenced data should not be modified,
|
||||||
|
|||||||
+28
-11
@@ -162,8 +162,7 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadBucketMetadata loads and migrates to bucket metadata.
|
func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) {
|
||||||
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
|
||||||
b := newBucketMetadata(bucket)
|
b := newBucketMetadata(bucket)
|
||||||
err := b.Load(ctx, objectAPI, b.Name)
|
err := b.Load(ctx, objectAPI, b.Name)
|
||||||
if err != nil && !errors.Is(err, errConfigNotFound) {
|
if err != nil && !errors.Is(err, errConfigNotFound) {
|
||||||
@@ -172,8 +171,22 @@ func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket strin
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
b.defaultTimestamps()
|
b.defaultTimestamps()
|
||||||
}
|
}
|
||||||
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
|
||||||
if err := b.convertLegacyConfigs(ctx, objectAPI); err != nil {
|
configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(configs) == 0 {
|
||||||
|
if parse {
|
||||||
|
// nothing to update, parse and proceed.
|
||||||
|
err = b.parseAllConfigs(ctx, objectAPI)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Old bucket without bucket metadata. Hence we migrate existing settings.
|
||||||
|
err = b.convertLegacyConfigs(ctx, objectAPI, configs)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +198,11 @@ func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket strin
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadBucketMetadata loads and migrates to bucket metadata.
|
||||||
|
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
|
||||||
|
return loadBucketMetadataParse(ctx, objectAPI, bucket, true)
|
||||||
|
}
|
||||||
|
|
||||||
// parseAllConfigs will parse all configs and populate the private fields.
|
// parseAllConfigs will parse all configs and populate the private fields.
|
||||||
// The first error encountered is returned.
|
// The first error encountered is returned.
|
||||||
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
|
func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
|
||||||
@@ -277,7 +295,7 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer) error {
|
func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI ObjectLayer) (map[string][]byte, error) {
|
||||||
legacyConfigs := []string{
|
legacyConfigs := []string{
|
||||||
legacyBucketObjectLockEnabledConfigFile,
|
legacyBucketObjectLockEnabledConfigFile,
|
||||||
bucketPolicyConfig,
|
bucketPolicyConfig,
|
||||||
@@ -291,7 +309,7 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
|||||||
objectLockConfig,
|
objectLockConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
configs := make(map[string][]byte)
|
configs := make(map[string][]byte, len(legacyConfigs))
|
||||||
|
|
||||||
// Handle migration from lockEnabled to newer format.
|
// Handle migration from lockEnabled to newer format.
|
||||||
if b.LockEnabled {
|
if b.LockEnabled {
|
||||||
@@ -316,16 +334,15 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
configs[legacyFile] = configData
|
configs[legacyFile] = configData
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(configs) == 0 {
|
return configs, nil
|
||||||
// nothing to update, return right away.
|
}
|
||||||
return b.parseAllConfigs(ctx, objectAPI)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer, configs map[string][]byte) error {
|
||||||
for legacyFile, configData := range configs {
|
for legacyFile, configData := range configs {
|
||||||
switch legacyFile {
|
switch legacyFile {
|
||||||
case legacyBucketObjectLockEnabledConfigFile:
|
case legacyBucketObjectLockEnabledConfigFile:
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, nil)
|
updatedAt, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketPolicyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.Respons
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, nil); err != nil {
|
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketReplicationConfig); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-15
@@ -39,6 +39,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||||
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
@@ -117,15 +118,22 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
|
|||||||
return sameTarget, toAPIError(ctx, BucketRemoteTargetNotFound{Bucket: bucket})
|
return sameTarget, toAPIError(ctx, BucketRemoteTargetNotFound{Bucket: bucket})
|
||||||
}
|
}
|
||||||
if checkRemote { // validate remote bucket
|
if checkRemote { // validate remote bucket
|
||||||
if found, err := clnt.BucketExists(ctx, arn.Bucket); !found {
|
found, err := clnt.BucketExists(ctx, arn.Bucket)
|
||||||
|
if err != nil {
|
||||||
return sameTarget, errorCodes.ToAPIErrWithErr(ErrRemoteDestinationNotFoundError, err)
|
return sameTarget, errorCodes.ToAPIErrWithErr(ErrRemoteDestinationNotFoundError, err)
|
||||||
}
|
}
|
||||||
|
if !found {
|
||||||
|
return sameTarget, errorCodes.ToAPIErrWithErr(ErrRemoteDestinationNotFoundError, BucketRemoteTargetNotFound{Bucket: arn.Bucket})
|
||||||
|
}
|
||||||
if ret, err := globalBucketObjectLockSys.Get(bucket); err == nil {
|
if ret, err := globalBucketObjectLockSys.Get(bucket); err == nil {
|
||||||
if ret.LockEnabled {
|
if ret.LockEnabled {
|
||||||
lock, _, _, _, err := clnt.GetObjectLockConfig(ctx, arn.Bucket)
|
lock, _, _, _, err := clnt.GetObjectLockConfig(ctx, arn.Bucket)
|
||||||
if err != nil || lock != "Enabled" {
|
if err != nil {
|
||||||
return sameTarget, errorCodes.ToAPIErrWithErr(ErrReplicationDestinationMissingLock, err)
|
return sameTarget, errorCodes.ToAPIErrWithErr(ErrReplicationDestinationMissingLock, err)
|
||||||
}
|
}
|
||||||
|
if lock != objectlock.Enabled {
|
||||||
|
return sameTarget, errorCodes.ToAPIErrWithErr(ErrReplicationDestinationMissingLock, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,9 +372,11 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
replStatus := string(replicationStatus)
|
replStatus := string(replicationStatus)
|
||||||
auditLogInternal(context.Background(), bucket, dobj.ObjectName, AuditLogOptions{
|
auditLogInternal(context.Background(), AuditLogOptions{
|
||||||
Event: dobj.EventType,
|
Event: dobj.EventType,
|
||||||
APIName: ReplicateDeleteAPI,
|
APIName: ReplicateDeleteAPI,
|
||||||
|
Bucket: bucket,
|
||||||
|
Object: dobj.ObjectName,
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
Status: replStatus,
|
Status: replStatus,
|
||||||
})
|
})
|
||||||
@@ -568,18 +578,26 @@ func replicateDeleteToTarget(ctx context.Context, dobj DeletedObjectReplicationI
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// early return if already replicated delete marker for existing object replication/ healing delete markers
|
// early return if already replicated delete marker for existing object replication/ healing delete markers
|
||||||
if dobj.DeleteMarkerVersionID != "" && (dobj.OpType == replication.ExistingObjectReplicationType || dobj.OpType == replication.HealReplicationType) {
|
if dobj.DeleteMarkerVersionID != "" {
|
||||||
if _, err := tgt.StatObject(ctx, tgt.Bucket, dobj.ObjectName, miniogo.StatObjectOptions{
|
toi, err := tgt.StatObject(ctx, tgt.Bucket, dobj.ObjectName, miniogo.StatObjectOptions{
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
Internal: miniogo.AdvancedGetOptions{
|
Internal: miniogo.AdvancedGetOptions{
|
||||||
ReplicationProxyRequest: "false",
|
ReplicationProxyRequest: "false",
|
||||||
|
IsReplicationReadyForDeleteMarker: true,
|
||||||
},
|
},
|
||||||
}); isErrMethodNotAllowed(ErrorRespToObjectError(err, dobj.Bucket, dobj.ObjectName)) {
|
})
|
||||||
|
if isErrMethodNotAllowed(ErrorRespToObjectError(err, dobj.Bucket, dobj.ObjectName)) {
|
||||||
if dobj.VersionID == "" {
|
if dobj.VersionID == "" {
|
||||||
rinfo.ReplicationStatus = replication.Completed
|
rinfo.ReplicationStatus = replication.Completed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// mark delete marker replication as failed if target cluster not ready to receive
|
||||||
|
// this request yet (object version not replicated yet)
|
||||||
|
if err != nil && !toi.ReplicationReady {
|
||||||
|
rinfo.ReplicationStatus = replication.Failed
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rmErr := tgt.RemoveObject(ctx, tgt.Bucket, dobj.ObjectName, miniogo.RemoveObjectOptions{
|
rmErr := tgt.RemoveObject(ctx, tgt.Bucket, dobj.ObjectName, miniogo.RemoveObjectOptions{
|
||||||
@@ -887,9 +905,11 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
// on disk.
|
// on disk.
|
||||||
replicationStatus = ri.ReplicationStatus
|
replicationStatus = ri.ReplicationStatus
|
||||||
}
|
}
|
||||||
auditLogInternal(ctx, ri.Bucket, ri.Name, AuditLogOptions{
|
auditLogInternal(ctx, AuditLogOptions{
|
||||||
Event: ri.EventType,
|
Event: ri.EventType,
|
||||||
APIName: ReplicateObjectAPI,
|
APIName: ReplicateObjectAPI,
|
||||||
|
Bucket: ri.Bucket,
|
||||||
|
Object: ri.Name,
|
||||||
VersionID: ri.VersionID,
|
VersionID: ri.VersionID,
|
||||||
Status: replicationStatus.String(),
|
Status: replicationStatus.String(),
|
||||||
})
|
})
|
||||||
@@ -1068,7 +1088,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
VersionSuspended: versionSuspended,
|
VersionSuspended: versionSuspended,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1216,7 +1236,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
|||||||
VersionSuspended: versionSuspended,
|
VersionSuspended: versionSuspended,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isErrObjectNotFound(err) {
|
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
EventName: event.ObjectReplicationNotTracked,
|
EventName: event.ObjectReplicationNotTracked,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
@@ -1897,7 +1917,9 @@ func getProxyTargets(ctx context.Context, bucket, object string, opts ObjectOpti
|
|||||||
if opts.VersionSuspended {
|
if opts.VersionSuspended {
|
||||||
return &madmin.BucketTargets{}
|
return &madmin.BucketTargets{}
|
||||||
}
|
}
|
||||||
|
if !opts.ProxyRequest {
|
||||||
|
return &madmin.BucketTargets{}
|
||||||
|
}
|
||||||
cfg, err := getReplicationConfig(ctx, bucket)
|
cfg, err := getReplicationConfig(ctx, bucket)
|
||||||
if err != nil || cfg == nil {
|
if err != nil || cfg == nil {
|
||||||
return &madmin.BucketTargets{}
|
return &madmin.BucketTargets{}
|
||||||
@@ -2654,6 +2676,10 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
|
|||||||
const mrfTimeInterval = 5 * time.Minute
|
const mrfTimeInterval = 5 * time.Minute
|
||||||
|
|
||||||
func (p *ReplicationPool) persistMRF() {
|
func (p *ReplicationPool) persistMRF() {
|
||||||
|
if !p.initialized() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
entries := make(map[string]MRFReplicateEntry)
|
entries := make(map[string]MRFReplicateEntry)
|
||||||
mTimer := time.NewTimer(mrfTimeInterval)
|
mTimer := time.NewTimer(mrfTimeInterval)
|
||||||
@@ -2706,7 +2732,7 @@ func (p *ReplicationPool) persistMRF() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
|
func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
|
||||||
if p == nil {
|
if !p.initialized() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
@@ -2718,6 +2744,10 @@ func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
|
|||||||
|
|
||||||
// save mrf entries to mrf_<uuid>.bin
|
// save mrf entries to mrf_<uuid>.bin
|
||||||
func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string]MRFReplicateEntry) error {
|
func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string]MRFReplicateEntry) error {
|
||||||
|
if !p.initialized() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -2743,6 +2773,10 @@ func (p *ReplicationPool) saveMRFEntries(ctx context.Context, entries map[string
|
|||||||
|
|
||||||
// load mrf entries from disk
|
// load mrf entries from disk
|
||||||
func (p *ReplicationPool) loadMRF(fileName string) (re MRFReplicateEntries, e error) {
|
func (p *ReplicationPool) loadMRF(fileName string) (re MRFReplicateEntries, e error) {
|
||||||
|
if !p.initialized() {
|
||||||
|
return re, nil
|
||||||
|
}
|
||||||
|
|
||||||
data, err := readConfig(p.ctx, p.objLayer, fileName)
|
data, err := readConfig(p.ctx, p.objLayer, fileName)
|
||||||
if err != nil && err != errConfigNotFound {
|
if err != nil && err != errConfigNotFound {
|
||||||
return re, err
|
return re, err
|
||||||
@@ -2779,7 +2813,7 @@ func (p *ReplicationPool) loadMRF(fileName string) (re MRFReplicateEntries, e er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReplicationPool) processMRF() {
|
func (p *ReplicationPool) processMRF() {
|
||||||
if p == nil || p.objLayer == nil {
|
if !p.initialized() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pTimer := time.NewTimer(mrfTimeInterval)
|
pTimer := time.NewTimer(mrfTimeInterval)
|
||||||
@@ -2822,7 +2856,7 @@ func (p *ReplicationPool) processMRF() {
|
|||||||
|
|
||||||
// process sends error logs to the heal channel for an attempt to heal replication.
|
// process sends error logs to the heal channel for an attempt to heal replication.
|
||||||
func (p *ReplicationPool) queueMRFHeal(file string) error {
|
func (p *ReplicationPool) queueMRFHeal(file string) error {
|
||||||
if p == nil || p.objLayer == nil {
|
if !p.initialized() {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2844,6 +2878,10 @@ func (p *ReplicationPool) queueMRFHeal(file string) error {
|
|||||||
|
|
||||||
// load replication stats from disk
|
// load replication stats from disk
|
||||||
func (p *ReplicationPool) loadStatsFromDisk() (rs map[string]BucketReplicationStats, e error) {
|
func (p *ReplicationPool) loadStatsFromDisk() (rs map[string]BucketReplicationStats, e error) {
|
||||||
|
if !p.initialized() {
|
||||||
|
return map[string]BucketReplicationStats{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
data, err := readConfig(p.ctx, p.objLayer, getReplicationStatsPath(globalLocalNodeName))
|
data, err := readConfig(p.ctx, p.objLayer, getReplicationStatsPath(globalLocalNodeName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errConfigNotFound) {
|
if !errors.Is(err, errConfigNotFound) {
|
||||||
@@ -2879,8 +2917,12 @@ func (p *ReplicationPool) loadStatsFromDisk() (rs map[string]BucketReplicationSt
|
|||||||
return rs, nil
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ReplicationPool) initialized() bool {
|
||||||
|
return !(p == nil || p.objLayer == nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ReplicationPool) saveStatsToDisk() {
|
func (p *ReplicationPool) saveStatsToDisk() {
|
||||||
if p == nil || p.objLayer == nil {
|
if !p.initialized() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sTimer := time.NewTimer(replStatsSaveInterval)
|
sTimer := time.NewTimer(replStatsSaveInterval)
|
||||||
@@ -2902,6 +2944,9 @@ func (p *ReplicationPool) saveStatsToDisk() {
|
|||||||
|
|
||||||
// save replication stats to .minio.sys/buckets/replication/node-name.stats
|
// save replication stats to .minio.sys/buckets/replication/node-name.stats
|
||||||
func (p *ReplicationPool) saveStats(ctx context.Context) error {
|
func (p *ReplicationPool) saveStats(ctx context.Context) error {
|
||||||
|
if !p.initialized() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
bsm := globalReplicationStats.getAllCachedLatest()
|
bsm := globalReplicationStats.getAllCachedLatest()
|
||||||
if len(bsm.Stats) == 0 {
|
if len(bsm.Stats) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -2920,6 +2965,9 @@ func (p *ReplicationPool) saveStats(ctx context.Context) error {
|
|||||||
|
|
||||||
// SaveState saves replication stats and mrf data before server restart
|
// SaveState saves replication stats and mrf data before server restart
|
||||||
func (p *ReplicationPool) SaveState(ctx context.Context) error {
|
func (p *ReplicationPool) SaveState(ctx context.Context) error {
|
||||||
|
if !p.initialized() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case p.saveStateCh <- struct{}{}:
|
case p.saveStateCh <- struct{}{}:
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import (
|
|||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
"github.com/minio/minio/internal/bucket/versioning"
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -220,12 +219,16 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
// validate if target credentials are ok
|
// validate if target credentials are ok
|
||||||
if _, err = clnt.BucketExists(ctx, tgt.TargetBucket); err != nil {
|
exists, err := clnt.BucketExists(ctx, tgt.TargetBucket)
|
||||||
|
if err != nil {
|
||||||
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
||||||
}
|
}
|
||||||
|
if !exists {
|
||||||
|
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
||||||
|
}
|
||||||
if tgt.Type == madmin.ReplicationService {
|
if tgt.Type == madmin.ReplicationService {
|
||||||
if !globalBucketVersioningSys.Enabled(bucket) {
|
if !globalBucketVersioningSys.Enabled(bucket) {
|
||||||
return BucketReplicationSourceNotVersioned{Bucket: bucket}
|
return BucketReplicationSourceNotVersioned{Bucket: bucket}
|
||||||
@@ -234,7 +237,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
|
||||||
}
|
}
|
||||||
if vcfg.Status != string(versioning.Enabled) {
|
if !vcfg.Enabled() {
|
||||||
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -175,7 +175,9 @@ func minioConfigToConsoleFeatures() {
|
|||||||
if globalMinioEndpoint != "" {
|
if globalMinioEndpoint != "" {
|
||||||
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
|
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
|
||||||
} else {
|
} else {
|
||||||
os.Setenv("CONSOLE_MINIO_SERVER", getAPIEndpoints()[0])
|
// Explicitly set 127.0.0.1 so Console will automatically bypass TLS verification to the local S3 API.
|
||||||
|
// This will save users from providing a certificate with IP or FQDN SAN that points to the local host.
|
||||||
|
os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort))
|
||||||
}
|
}
|
||||||
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
|
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
|
||||||
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
||||||
@@ -202,7 +204,7 @@ func minioConfigToConsoleFeatures() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Enable if LDAP is enabled.
|
// Enable if LDAP is enabled.
|
||||||
if globalLDAPConfig.Enabled {
|
if globalLDAPConfig.Enabled() {
|
||||||
os.Setenv("CONSOLE_LDAP_ENABLED", config.EnableOn)
|
os.Setenv("CONSOLE_LDAP_ENABLED", config.EnableOn)
|
||||||
}
|
}
|
||||||
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
|
||||||
@@ -236,6 +238,7 @@ func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
|
|||||||
Userinfo: cfg.ClaimUserinfo,
|
Userinfo: cfg.ClaimUserinfo,
|
||||||
RedirectCallbackDynamic: cfg.RedirectURIDynamic,
|
RedirectCallbackDynamic: cfg.RedirectURIDynamic,
|
||||||
RedirectCallback: callback,
|
RedirectCallback: callback,
|
||||||
|
RoleArn: cfg.GetRoleArn(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
|
|||||||
@@ -335,12 +335,12 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case config.IdentityLDAPSubSys:
|
case config.IdentityLDAPSubSys:
|
||||||
cfg, err := xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default], globalRootCAs)
|
cfg, err := xldap.Lookup(s, globalRootCAs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if cfg.Enabled {
|
if cfg.Enabled() {
|
||||||
conn, cerr := cfg.Connect()
|
conn, cerr := cfg.LDAP.Connect()
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
return cerr
|
return cerr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestServerConfig(t *testing.T) {
|
func TestServerConfig(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -27,7 +28,10 @@ import (
|
|||||||
|
|
||||||
// Test if config v1 is purged
|
// Test if config v1 is purged
|
||||||
func TestServerConfigMigrateV1(t *testing.T) {
|
func TestServerConfigMigrateV1(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -154,10 +158,12 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) {
|
|||||||
// Test if a config migration from v2 to v33 is successfully done
|
// Test if a config migration from v2 to v33 is successfully done
|
||||||
func TestServerConfigMigrateV2toV33(t *testing.T) {
|
func TestServerConfigMigrateV2toV33(t *testing.T) {
|
||||||
rootPath := t.TempDir()
|
rootPath := t.TempDir()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
globalConfigDir = &ConfigDir{path: rootPath}
|
globalConfigDir = &ConfigDir{path: rootPath}
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS()
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -855,5 +855,5 @@ type serverConfigV33 struct {
|
|||||||
// Add new external policy enforcements here.
|
// Add new external policy enforcements here.
|
||||||
} `json:"policy"`
|
} `json:"policy"`
|
||||||
|
|
||||||
LDAPServerConfig xldap.Config `json:"ldapserverconfig"`
|
LDAPServerConfig xldap.LegacyConfig `json:"ldapserverconfig"`
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-24
@@ -938,7 +938,10 @@ func (i *scannerItem) applyHealing(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
ScanMode: scanMode,
|
ScanMode: scanMode,
|
||||||
}
|
}
|
||||||
res, err := o.HealObject(ctx, i.bucket, i.objectPath(), oi.VersionID, healOpts)
|
res, err := o.HealObject(ctx, i.bucket, i.objectPath(), oi.VersionID, healOpts)
|
||||||
if err != nil && !errors.Is(err, NotImplemented{}) {
|
if err != nil {
|
||||||
|
if errors.Is(err, NotImplemented{}) || isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -960,21 +963,21 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
|
|||||||
|
|
||||||
versionID := oi.VersionID
|
versionID := oi.VersionID
|
||||||
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
|
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
|
||||||
action := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi, false)
|
lcEvt := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi)
|
||||||
if i.debug {
|
if i.debug {
|
||||||
if versionID != "" {
|
if versionID != "" {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, lcEvt.Action)
|
||||||
} else {
|
} else {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %q Initial scan: %v\n", i.objectPath(), lcEvt.Action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer globalScannerMetrics.timeILM(action)
|
defer globalScannerMetrics.timeILM(lcEvt.Action)
|
||||||
|
|
||||||
switch action {
|
switch lcEvt.Action {
|
||||||
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
return applyLifecycleAction(action, oi), 0
|
return applyLifecycleAction(lcEvt.Action, oi, ""), 0
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
return applyLifecycleAction(action, oi), size
|
return applyLifecycleAction(lcEvt.Action, oi, lcEvt.StorageClass), size
|
||||||
default:
|
default:
|
||||||
// No action.
|
// No action.
|
||||||
return false, size
|
return false, size
|
||||||
@@ -1106,42 +1109,42 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo, debug bool) (action lifecycle.Action) {
|
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) lifecycle.Event {
|
||||||
action = lc.ComputeAction(obj.ToLifecycleOpts())
|
event := lc.Eval(obj.ToLifecycleOpts(), time.Now().UTC())
|
||||||
if debug {
|
if serverDebugLog {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == lifecycle.NoneAction {
|
if event.Action == lifecycle.NoneAction {
|
||||||
return action
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
switch action {
|
switch event.Action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
// Defensive code, should never happen
|
// Defensive code, should never happen
|
||||||
if obj.VersionID == "" {
|
if obj.VersionID == "" {
|
||||||
return lifecycle.NoneAction
|
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||||
}
|
}
|
||||||
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
||||||
if debug {
|
if serverDebugLog {
|
||||||
if obj.VersionID != "" {
|
if obj.VersionID != "" {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %s v(%s) is locked, not deleting\n", obj.Name, obj.VersionID)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %s v(%s) is locked, not deleting\n", obj.Name, obj.VersionID)
|
||||||
} else {
|
} else {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", obj.Name)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: %s is locked, not deleting\n", obj.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lifecycle.NoneAction
|
return lifecycle.Event{Action: lifecycle.NoneAction}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return action
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyTransitionRule(obj ObjectInfo) bool {
|
func applyTransitionRule(obj ObjectInfo, storageClass string) bool {
|
||||||
if obj.DeleteMarker {
|
if obj.DeleteMarker {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
globalTransitionState.queueTransitionTask(obj)
|
globalTransitionState.queueTransitionTask(obj, storageClass)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1210,14 +1213,14 @@ func applyExpiryRule(obj ObjectInfo, restoredObject, applyOnVersion bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform actions (removal or transitioning of objects), return true the action is successfully performed
|
// Perform actions (removal or transitioning of objects), return true the action is successfully performed
|
||||||
func applyLifecycleAction(action lifecycle.Action, obj ObjectInfo) (success bool) {
|
func applyLifecycleAction(action lifecycle.Action, obj ObjectInfo, storageClass string) (success bool) {
|
||||||
switch action {
|
switch action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
success = applyExpiryRule(obj, false, action == lifecycle.DeleteVersionAction)
|
success = applyExpiryRule(obj, false, action == lifecycle.DeleteVersionAction)
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
success = applyExpiryRule(obj, true, action == lifecycle.DeleteRestoredVersionAction)
|
success = applyExpiryRule(obj, true, action == lifecycle.DeleteRestoredVersionAction)
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
success = applyTransitionRule(obj)
|
success = applyTransitionRule(obj, storageClass)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1438,9 +1441,11 @@ func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string) {
|
|||||||
case ILMTransition:
|
case ILMTransition:
|
||||||
apiName = "ILMTransition"
|
apiName = "ILMTransition"
|
||||||
}
|
}
|
||||||
auditLogInternal(ctx, oi.Bucket, oi.Name, AuditLogOptions{
|
auditLogInternal(ctx, AuditLogOptions{
|
||||||
Event: event,
|
Event: event,
|
||||||
APIName: apiName,
|
APIName: apiName,
|
||||||
|
Bucket: oi.Bucket,
|
||||||
|
Object: oi.Name,
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ func TestDynamicTimeoutConcurrent(t *testing.T) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
timeout.LogFailure()
|
|
||||||
for j := 0; j < 100; j++ {
|
for j := 0; j < 100; j++ {
|
||||||
timeout.LogSuccess(time.Duration(float64(time.Second) * rng.Float64()))
|
timeout.LogSuccess(time.Duration(float64(time.Second) * rng.Float64()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,53 +179,6 @@ func listOnlineDisks(disks []StorageAPI, partsMetadata []FileInfo, errs []error)
|
|||||||
return onlineDisks, modTime
|
return onlineDisks, modTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the latest updated FileInfo files and error in case of failure.
|
|
||||||
func getLatestFileInfo(ctx context.Context, partsMetadata []FileInfo, defaultParityCount int, errs []error) (FileInfo, error) {
|
|
||||||
// There should be atleast half correct entries, if not return failure
|
|
||||||
expectedRQuorum := len(partsMetadata) / 2
|
|
||||||
if defaultParityCount == 0 {
|
|
||||||
// if parity count is '0', we expected all entries to be present.
|
|
||||||
expectedRQuorum = len(partsMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, expectedRQuorum)
|
|
||||||
if reducedErr != nil {
|
|
||||||
return FileInfo{}, reducedErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all the file commit ids from parts metadata.
|
|
||||||
modTimes := listObjectModtimes(partsMetadata, errs)
|
|
||||||
|
|
||||||
// Count all latest updated FileInfo values
|
|
||||||
var count int
|
|
||||||
var latestFileInfo FileInfo
|
|
||||||
|
|
||||||
// Reduce list of UUIDs to a single common value - i.e. the last updated Time
|
|
||||||
modTime := commonTime(modTimes)
|
|
||||||
|
|
||||||
if modTime.IsZero() || modTime.Equal(timeSentinel) {
|
|
||||||
return FileInfo{}, errErasureReadQuorum
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interate through all the modTimes and count the FileInfo(s) with latest time.
|
|
||||||
for index, t := range modTimes {
|
|
||||||
if partsMetadata[index].IsValid() && t.Equal(modTime) {
|
|
||||||
latestFileInfo = partsMetadata[index]
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !latestFileInfo.IsValid() {
|
|
||||||
return FileInfo{}, errErasureReadQuorum
|
|
||||||
}
|
|
||||||
|
|
||||||
if count < latestFileInfo.Erasure.DataBlocks {
|
|
||||||
return FileInfo{}, errErasureReadQuorum
|
|
||||||
}
|
|
||||||
|
|
||||||
return latestFileInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// disksWithAllParts - This function needs to be called with
|
// disksWithAllParts - This function needs to be called with
|
||||||
// []StorageAPI returned by listOnlineDisks. Returns,
|
// []StorageAPI returned by listOnlineDisks. Returns,
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -30,6 +30,53 @@ import (
|
|||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Returns the latest updated FileInfo files and error in case of failure.
|
||||||
|
func getLatestFileInfo(ctx context.Context, partsMetadata []FileInfo, defaultParityCount int, errs []error) (FileInfo, error) {
|
||||||
|
// There should be atleast half correct entries, if not return failure
|
||||||
|
expectedRQuorum := len(partsMetadata) / 2
|
||||||
|
if defaultParityCount == 0 {
|
||||||
|
// if parity count is '0', we expected all entries to be present.
|
||||||
|
expectedRQuorum = len(partsMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, expectedRQuorum)
|
||||||
|
if reducedErr != nil {
|
||||||
|
return FileInfo{}, reducedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all the file commit ids from parts metadata.
|
||||||
|
modTimes := listObjectModtimes(partsMetadata, errs)
|
||||||
|
|
||||||
|
// Count all latest updated FileInfo values
|
||||||
|
var count int
|
||||||
|
var latestFileInfo FileInfo
|
||||||
|
|
||||||
|
// Reduce list of UUIDs to a single common value - i.e. the last updated Time
|
||||||
|
modTime := commonTime(modTimes)
|
||||||
|
|
||||||
|
if modTime.IsZero() || modTime.Equal(timeSentinel) {
|
||||||
|
return FileInfo{}, errErasureReadQuorum
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interate through all the modTimes and count the FileInfo(s) with latest time.
|
||||||
|
for index, t := range modTimes {
|
||||||
|
if partsMetadata[index].IsValid() && t.Equal(modTime) {
|
||||||
|
latestFileInfo = partsMetadata[index]
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !latestFileInfo.IsValid() {
|
||||||
|
return FileInfo{}, errErasureReadQuorum
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < latestFileInfo.Erasure.DataBlocks {
|
||||||
|
return FileInfo{}, errErasureReadQuorum
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestFileInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
// validates functionality provided to find most common
|
// validates functionality provided to find most common
|
||||||
// time occurrence from a list of time.
|
// time occurrence from a list of time.
|
||||||
func TestCommonTime(t *testing.T) {
|
func TestCommonTime(t *testing.T) {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/sync/errgroup"
|
"github.com/minio/minio/internal/sync/errgroup"
|
||||||
@@ -165,18 +167,23 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
|
|||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignoredErrs := []error{
|
||||||
|
errFileNotFound,
|
||||||
|
errVolumeNotFound,
|
||||||
|
errFileVersionNotFound,
|
||||||
|
errDiskNotFound,
|
||||||
|
errUnformattedDisk,
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(bucket, minioMetaBucket) {
|
||||||
|
// listing object might be truncated, ignore such errors from logging.
|
||||||
|
ignoredErrs = append(ignoredErrs, io.ErrUnexpectedEOF)
|
||||||
|
}
|
||||||
errs := g.Wait()
|
errs := g.Wait()
|
||||||
for index, err := range errs {
|
for index, err := range errs {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !IsErr(err, []error{
|
if !IsErr(err, ignoredErrs...) {
|
||||||
errFileNotFound,
|
|
||||||
errVolumeNotFound,
|
|
||||||
errFileVersionNotFound,
|
|
||||||
errDiskNotFound,
|
|
||||||
errUnformattedDisk,
|
|
||||||
}...) {
|
|
||||||
logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)",
|
logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)",
|
||||||
disks[index], bucket, object, err),
|
disks[index], bucket, object, err),
|
||||||
disks[index].String())
|
disks[index].String())
|
||||||
|
|||||||
+62
-26
@@ -394,21 +394,73 @@ func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, bucket, prefix
|
|||||||
return evalDisks(disks, mErrs), err
|
return evalDisks(disks, mErrs), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func commonParity(parities []int) int {
|
||||||
|
occMap := make(map[int]int)
|
||||||
|
for _, p := range parities {
|
||||||
|
occMap[p]++
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxOcc, commonParity int
|
||||||
|
|
||||||
|
for parity, occ := range occMap {
|
||||||
|
if parity == -1 {
|
||||||
|
// Ignore non defined parity
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if occ >= maxOcc {
|
||||||
|
maxOcc = occ
|
||||||
|
commonParity = parity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxOcc == 0 {
|
||||||
|
// Did not found anything useful
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return commonParity
|
||||||
|
}
|
||||||
|
|
||||||
|
func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int) {
|
||||||
|
parities = make([]int, len(partsMetadata))
|
||||||
|
for index, metadata := range partsMetadata {
|
||||||
|
if errs[index] != nil {
|
||||||
|
parities[index] = -1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parities[index] = metadata.Erasure.ParityBlocks
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Returns per object readQuorum and writeQuorum
|
// Returns per object readQuorum and writeQuorum
|
||||||
// readQuorum is the min required disks to read data.
|
// readQuorum is the min required disks to read data.
|
||||||
// writeQuorum is the min required disks to write data.
|
// writeQuorum is the min required disks to write data.
|
||||||
func objectQuorumFromMeta(ctx context.Context, partsMetaData []FileInfo, errs []error, defaultParityCount int) (objectReadQuorum, objectWriteQuorum int, err error) {
|
func objectQuorumFromMeta(ctx context.Context, partsMetaData []FileInfo, errs []error, defaultParityCount int) (objectReadQuorum, objectWriteQuorum int, err error) {
|
||||||
// get the latest updated Metadata and a count of all the latest updated FileInfo(s)
|
// There should be atleast half correct entries, if not return failure
|
||||||
latestFileInfo, err := getLatestFileInfo(ctx, partsMetaData, defaultParityCount, errs)
|
expectedRQuorum := len(partsMetaData) / 2
|
||||||
if err != nil {
|
if defaultParityCount == 0 {
|
||||||
return 0, 0, err
|
// if parity count is '0', we expected all entries to be present.
|
||||||
|
expectedRQuorum = len(partsMetaData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if latestFileInfo.Deleted {
|
reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, expectedRQuorum)
|
||||||
// special case when parity is '0'
|
if reducedErr != nil {
|
||||||
if defaultParityCount == 0 {
|
return -1, -1, reducedErr
|
||||||
return len(partsMetaData), len(partsMetaData), nil
|
}
|
||||||
}
|
|
||||||
|
// special case when parity is '0'
|
||||||
|
if defaultParityCount == 0 {
|
||||||
|
return len(partsMetaData), len(partsMetaData), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parities := listObjectParities(partsMetaData, errs)
|
||||||
|
parityBlocks := commonParity(parities)
|
||||||
|
|
||||||
|
if parityBlocks < 0 {
|
||||||
|
return -1, -1, errErasureReadQuorum
|
||||||
|
}
|
||||||
|
|
||||||
|
if parityBlocks == 0 {
|
||||||
// For delete markers do not use 'defaultParityCount' as it is not expected to be the case.
|
// For delete markers do not use 'defaultParityCount' as it is not expected to be the case.
|
||||||
// Use maximum allowed read quorum instead, writeQuorum+1 is returned for compatibility sake
|
// Use maximum allowed read quorum instead, writeQuorum+1 is returned for compatibility sake
|
||||||
// but there are no callers that shall be using this.
|
// but there are no callers that shall be using this.
|
||||||
@@ -416,23 +468,7 @@ func objectQuorumFromMeta(ctx context.Context, partsMetaData []FileInfo, errs []
|
|||||||
return readQuorum, readQuorum + 1, nil
|
return readQuorum, readQuorum + 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
parityBlocks := globalStorageClass.GetParityForSC(latestFileInfo.Metadata[xhttp.AmzStorageClass])
|
dataBlocks := len(partsMetaData) - parityBlocks
|
||||||
if parityBlocks < 0 {
|
|
||||||
parityBlocks = defaultParityCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// For erasure code upgraded objects choose the parity
|
|
||||||
// blocks saved internally, instead of 'defaultParityCount'
|
|
||||||
if _, ok := latestFileInfo.Metadata[minIOErasureUpgraded]; ok {
|
|
||||||
if latestFileInfo.Erasure.ParityBlocks != 0 {
|
|
||||||
parityBlocks = latestFileInfo.Erasure.ParityBlocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataBlocks := latestFileInfo.Erasure.DataBlocks
|
|
||||||
if dataBlocks == 0 {
|
|
||||||
dataBlocks = len(partsMetaData) - parityBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
writeQuorum := dataBlocks
|
writeQuorum := dataBlocks
|
||||||
if dataBlocks == parityBlocks {
|
if dataBlocks == parityBlocks {
|
||||||
|
|||||||
@@ -1103,7 +1103,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
PartNumber: part.PartNumber,
|
PartNumber: part.PartNumber,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checksumCombined = append(checksumCombined, cs.Raw()...)
|
checksumCombined = append(checksumCombined, cs.Raw...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// All parts except the last part has to be at least 5MB.
|
// All parts except the last part has to be at least 5MB.
|
||||||
|
|||||||
+24
-2
@@ -439,10 +439,32 @@ func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object strin
|
|||||||
return er.getObjectInfo(ctx, bucket, object, opts)
|
return er.getObjectInfo(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func auditDanglingObjectDeletion(ctx context.Context, bucket, object, versionID string, pool, set, objectParity int) {
|
||||||
|
if len(logger.AuditTargets()) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tags := make(map[string]interface{})
|
||||||
|
tags["pool"] = pool
|
||||||
|
tags["set"] = set
|
||||||
|
tags["objectParity"] = objectParity
|
||||||
|
|
||||||
|
opts := AuditLogOptions{
|
||||||
|
Event: "DeleteDanglingObject",
|
||||||
|
Bucket: bucket,
|
||||||
|
Object: object,
|
||||||
|
VersionID: versionID,
|
||||||
|
Tags: tags,
|
||||||
|
}
|
||||||
|
|
||||||
|
auditLogInternal(ctx, opts)
|
||||||
|
}
|
||||||
|
|
||||||
func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object string, metaArr []FileInfo, errs []error, dataErrs []error, opts ObjectOptions) (FileInfo, error) {
|
func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object string, metaArr []FileInfo, errs []error, dataErrs []error, opts ObjectOptions) (FileInfo, error) {
|
||||||
var err error
|
var err error
|
||||||
m, ok := isObjectDangling(metaArr, errs, dataErrs)
|
m, ok := isObjectDangling(metaArr, errs, dataErrs)
|
||||||
if ok {
|
if ok {
|
||||||
|
defer auditDanglingObjectDeletion(ctx, bucket, object, m.VersionID, er.poolIndex, er.setIndex, m.Erasure.ParityBlocks)
|
||||||
|
|
||||||
err = errFileNotFound
|
err = errFileNotFound
|
||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
err = errFileVersionNotFound
|
err = errFileVersionNotFound
|
||||||
@@ -1530,9 +1552,9 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
if opts.Expiration.Expire {
|
if opts.Expiration.Expire {
|
||||||
goi, _, err := er.getObjectInfoAndQuorum(ctx, bucket, object, opts)
|
goi, _, err := er.getObjectInfoAndQuorum(ctx, bucket, object, opts)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, goi, false)
|
evt := evalActionFromLifecycle(ctx, *lc, rcfg, goi)
|
||||||
var isErr bool
|
var isErr bool
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.NoneAction:
|
case lifecycle.NoneAction:
|
||||||
isErr = true
|
isErr = true
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
|
|||||||
@@ -701,14 +701,14 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
}
|
}
|
||||||
versioned := vc != nil && vc.Versioned(object)
|
versioned := vc != nil && vc.Versioned(object)
|
||||||
objInfo := fi.ToObjectInfo(bucket, object, versioned)
|
objInfo := fi.ToObjectInfo(bucket, object, versioned)
|
||||||
action := evalActionFromLifecycle(ctx, *lc, lr, objInfo, false)
|
evt := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
globalExpiryState.enqueueByDays(objInfo, false, action == lifecycle.DeleteVersionAction)
|
globalExpiryState.enqueueByDays(objInfo, false, evt.Action == lifecycle.DeleteVersionAction)
|
||||||
// Skip this entry.
|
// Skip this entry.
|
||||||
return true
|
return true
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
globalExpiryState.enqueueByDays(objInfo, true, action == lifecycle.DeleteRestoredVersionAction)
|
globalExpiryState.enqueueByDays(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction)
|
||||||
// Skip this entry.
|
// Skip this entry.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -768,6 +768,10 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
DeleteMarker: true, // make sure we create a delete marker
|
DeleteMarker: true, // make sure we create a delete marker
|
||||||
SkipDecommissioned: true, // make sure we skip the decommissioned pool
|
SkipDecommissioned: true, // make sure we skip the decommissioned pool
|
||||||
})
|
})
|
||||||
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
|
// object/version already deleted by the application, nothing to do here we move on.
|
||||||
|
continue
|
||||||
|
}
|
||||||
var failure bool
|
var failure bool
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
@@ -783,7 +787,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var failure bool
|
var failure, ignore bool
|
||||||
// gr.Close() is ensured by decommissionObject().
|
// gr.Close() is ensured by decommissionObject().
|
||||||
for try := 0; try < 3; try++ {
|
for try := 0; try < 3; try++ {
|
||||||
gr, err := set.GetObjectNInfo(ctx,
|
gr, err := set.GetObjectNInfo(ctx,
|
||||||
@@ -796,9 +800,10 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
VersionID: version.VersionID,
|
VersionID: version.VersionID,
|
||||||
NoDecryption: true,
|
NoDecryption: true,
|
||||||
})
|
})
|
||||||
if isErrObjectNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
// object deleted by the application, nothing to do here we move on.
|
// object deleted by the application, nothing to do here we move on.
|
||||||
return
|
ignore = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failure = true
|
failure = true
|
||||||
@@ -816,6 +821,9 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
|||||||
failure = false
|
failure = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if ignore {
|
||||||
|
continue
|
||||||
|
}
|
||||||
z.poolMetaMutex.Lock()
|
z.poolMetaMutex.Lock()
|
||||||
z.poolMeta.CountItem(idx, version.Size, failure)
|
z.poolMeta.CountItem(idx, version.Size, failure)
|
||||||
z.poolMetaMutex.Unlock()
|
z.poolMetaMutex.Unlock()
|
||||||
@@ -1266,9 +1274,11 @@ func auditLogDecom(ctx context.Context, apiName, bucket, object, versionID strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
errStr = err.Error()
|
errStr = err.Error()
|
||||||
}
|
}
|
||||||
auditLogInternal(ctx, bucket, object, AuditLogOptions{
|
auditLogInternal(ctx, AuditLogOptions{
|
||||||
Event: "decommission",
|
Event: "decommission",
|
||||||
APIName: apiName,
|
APIName: apiName,
|
||||||
|
Bucket: bucket,
|
||||||
|
Object: object,
|
||||||
VersionID: versionID,
|
VersionID: versionID,
|
||||||
Error: errStr,
|
Error: errStr,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1302,8 +1302,8 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
|||||||
objInfo, err := z.GetObjectInfo(ctx, bucket, prefix, ObjectOptions{NoLock: true})
|
objInfo, err := z.GetObjectInfo(ctx, bucket, prefix, ObjectOptions{NoLock: true})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if opts.Lifecycle != nil {
|
if opts.Lifecycle != nil {
|
||||||
action := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo, false)
|
evt := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo)
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
fallthrough
|
fallthrough
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
@@ -1588,6 +1588,8 @@ func (z *erasureServerPools) GetBucketInfo(ctx context.Context, bucket string, o
|
|||||||
meta, err := globalBucketMetadataSys.Get(bucket)
|
meta, err := globalBucketMetadataSys.Get(bucket)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bucketInfo.Created = meta.Created
|
bucketInfo.Created = meta.Created
|
||||||
|
bucketInfo.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
|
||||||
|
bucketInfo.ObjectLocking = meta.LockEnabled
|
||||||
}
|
}
|
||||||
return bucketInfo, nil
|
return bucketInfo, nil
|
||||||
}
|
}
|
||||||
@@ -1602,6 +1604,8 @@ func (z *erasureServerPools) GetBucketInfo(ctx context.Context, bucket string, o
|
|||||||
meta, err := globalBucketMetadataSys.Get(bucket)
|
meta, err := globalBucketMetadataSys.Get(bucket)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bucketInfo.Created = meta.Created
|
bucketInfo.Created = meta.Created
|
||||||
|
bucketInfo.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
|
||||||
|
bucketInfo.ObjectLocking = meta.LockEnabled
|
||||||
}
|
}
|
||||||
return bucketInfo, nil
|
return bucketInfo, nil
|
||||||
}
|
}
|
||||||
@@ -1875,12 +1879,22 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
|||||||
versionsSorter(fivs.Versions).reverse()
|
versionsSorter(fivs.Versions).reverse()
|
||||||
|
|
||||||
for _, version := range fivs.Versions {
|
for _, version := range fivs.Versions {
|
||||||
|
send := true
|
||||||
|
if opts.WalkFilter != nil && !opts.WalkFilter(version) {
|
||||||
|
send = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !send {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
versioned := vcfg != nil && vcfg.Versioned(version.Name)
|
versioned := vcfg != nil && vcfg.Versioned(version.Name)
|
||||||
|
objInfo := version.ToObjectInfo(bucket, version.Name, versioned)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case results <- version.ToObjectInfo(bucket, version.Name, versioned):
|
case results <- objInfo:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1904,7 +1918,7 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
|||||||
path: path,
|
path: path,
|
||||||
filterPrefix: filterPrefix,
|
filterPrefix: filterPrefix,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
forwardTo: "",
|
forwardTo: opts.WalkMarker,
|
||||||
minDisks: 1,
|
minDisks: 1,
|
||||||
reportNotFound: false,
|
reportNotFound: false,
|
||||||
agreed: loadEntry,
|
agreed: loadEntry,
|
||||||
|
|||||||
@@ -357,6 +357,10 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
|
|||||||
endpointStrings[i] = endpoint.String()
|
endpointStrings[i] = endpoint.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if defaultParityCount == 0 {
|
||||||
|
logger.Error("Warning: Default parity set to 0. This can lead to data loss.")
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the erasure sets instance.
|
// Initialize the erasure sets instance.
|
||||||
s := &erasureSets{
|
s := &erasureSets{
|
||||||
sets: make([]*erasureObjects, setCount),
|
sets: make([]*erasureObjects, setCount),
|
||||||
|
|||||||
+92
-17
@@ -344,7 +344,14 @@ func (es *erasureSingle) GetBucketInfo(ctx context.Context, bucket string, opts
|
|||||||
}
|
}
|
||||||
return bi, toObjectErr(err, bucket)
|
return bi, toObjectErr(err, bucket)
|
||||||
}
|
}
|
||||||
return BucketInfo{Name: volInfo.Name, Created: volInfo.Created}, nil
|
bi = BucketInfo{Name: volInfo.Name, Created: volInfo.Created}
|
||||||
|
meta, err := globalBucketMetadataSys.Get(bucket)
|
||||||
|
if err == nil {
|
||||||
|
bi.Created = meta.Created
|
||||||
|
bi.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
|
||||||
|
bi.ObjectLocking = meta.LockEnabled
|
||||||
|
}
|
||||||
|
return bi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBucket - deletes a bucket.
|
// DeleteBucket - deletes a bucket.
|
||||||
@@ -1010,6 +1017,11 @@ func (es *erasureSingle) putObject(ctx context.Context, bucket string, object st
|
|||||||
}
|
}
|
||||||
|
|
||||||
fi.DataDir = mustGetUUID()
|
fi.DataDir = mustGetUUID()
|
||||||
|
fi.Checksum = opts.WantChecksum.AppendTo(nil)
|
||||||
|
if opts.EncryptFn != nil {
|
||||||
|
fi.Checksum = opts.EncryptFn("object-checksum", fi.Checksum)
|
||||||
|
}
|
||||||
|
|
||||||
uniqueID := mustGetUUID()
|
uniqueID := mustGetUUID()
|
||||||
tempObj := uniqueID
|
tempObj := uniqueID
|
||||||
|
|
||||||
@@ -1465,9 +1477,9 @@ func (es *erasureSingle) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.Expiration.Expire {
|
if opts.Expiration.Expire {
|
||||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, goi, false)
|
evt := evalActionFromLifecycle(ctx, *lc, rcfg, goi)
|
||||||
var isErr bool
|
var isErr bool
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.NoneAction:
|
case lifecycle.NoneAction:
|
||||||
isErr = true
|
isErr = true
|
||||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||||
@@ -2586,6 +2598,7 @@ func (es *erasureSingle) ListObjectParts(ctx context.Context, bucket, object, up
|
|||||||
result.MaxParts = maxParts
|
result.MaxParts = maxParts
|
||||||
result.PartNumberMarker = partNumberMarker
|
result.PartNumberMarker = partNumberMarker
|
||||||
result.UserDefined = cloneMSS(fi.Metadata)
|
result.UserDefined = cloneMSS(fi.Metadata)
|
||||||
|
result.ChecksumAlgorithm = fi.Metadata[hash.MinIOMultipartChecksum]
|
||||||
|
|
||||||
// For empty number of parts or maxParts as zero, return right here.
|
// For empty number of parts or maxParts as zero, return right here.
|
||||||
if len(fi.Parts) == 0 || maxParts == 0 {
|
if len(fi.Parts) == 0 || maxParts == 0 {
|
||||||
@@ -2606,10 +2619,15 @@ func (es *erasureSingle) ListObjectParts(ctx context.Context, bucket, object, up
|
|||||||
count := maxParts
|
count := maxParts
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
result.Parts = append(result.Parts, PartInfo{
|
result.Parts = append(result.Parts, PartInfo{
|
||||||
PartNumber: part.Number,
|
PartNumber: part.Number,
|
||||||
ETag: part.ETag,
|
ETag: part.ETag,
|
||||||
LastModified: fi.ModTime,
|
LastModified: fi.ModTime,
|
||||||
Size: part.Size,
|
ActualSize: part.ActualSize,
|
||||||
|
Size: part.Size,
|
||||||
|
ChecksumCRC32: part.Checksums["CRC32"],
|
||||||
|
ChecksumCRC32C: part.Checksums["CRC32C"],
|
||||||
|
ChecksumSHA1: part.Checksums["SHA1"],
|
||||||
|
ChecksumSHA256: part.Checksums["SHA256"],
|
||||||
})
|
})
|
||||||
count--
|
count--
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
@@ -2678,6 +2696,21 @@ func (es *erasureSingle) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
return oi, err
|
return oi, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checksum type set when upload started.
|
||||||
|
var checksumType hash.ChecksumType
|
||||||
|
if cs := fi.Metadata[hash.MinIOMultipartChecksum]; cs != "" {
|
||||||
|
checksumType = hash.NewChecksumType(cs)
|
||||||
|
if opts.WantChecksum != nil && !opts.WantChecksum.Type.Is(checksumType) {
|
||||||
|
return oi, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Object: fi.Name,
|
||||||
|
Err: fmt.Errorf("checksum type mismatch"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var checksumCombined []byte
|
||||||
|
|
||||||
// However, in case of encryption, the persisted part ETags don't match
|
// However, in case of encryption, the persisted part ETags don't match
|
||||||
// what we have sent to the client during PutObjectPart. The reason is
|
// what we have sent to the client during PutObjectPart. The reason is
|
||||||
// that ETags are encrypted. Hence, the client will send a list of complete
|
// that ETags are encrypted. Hence, the client will send a list of complete
|
||||||
@@ -2724,6 +2757,7 @@ func (es *erasureSingle) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
}
|
}
|
||||||
return oi, invp
|
return oi, invp
|
||||||
}
|
}
|
||||||
|
expPart := currentFI.Parts[partIdx]
|
||||||
|
|
||||||
// ensure that part ETag is canonicalized to strip off extraneous quotes
|
// ensure that part ETag is canonicalized to strip off extraneous quotes
|
||||||
part.ETag = canonicalizeETag(part.ETag)
|
part.ETag = canonicalizeETag(part.ETag)
|
||||||
@@ -2737,27 +2771,58 @@ func (es *erasureSingle) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
return oi, invp
|
return oi, invp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if checksumType.IsSet() {
|
||||||
|
crc := expPart.Checksums[checksumType.String()]
|
||||||
|
if crc == "" {
|
||||||
|
return oi, InvalidPart{
|
||||||
|
PartNumber: part.PartNumber,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wantCS := map[string]string{
|
||||||
|
hash.ChecksumCRC32.String(): part.ChecksumCRC32,
|
||||||
|
hash.ChecksumCRC32C.String(): part.ChecksumCRC32C,
|
||||||
|
hash.ChecksumSHA1.String(): part.ChecksumSHA1,
|
||||||
|
hash.ChecksumSHA256.String(): part.ChecksumSHA256,
|
||||||
|
}
|
||||||
|
if wantCS[checksumType.String()] != crc {
|
||||||
|
return oi, InvalidPart{
|
||||||
|
PartNumber: part.PartNumber,
|
||||||
|
ExpETag: wantCS[checksumType.String()],
|
||||||
|
GotETag: crc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cs := hash.NewChecksumString(checksumType.String(), crc)
|
||||||
|
if !cs.Valid() {
|
||||||
|
return oi, InvalidPart{
|
||||||
|
PartNumber: part.PartNumber,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checksumCombined = append(checksumCombined, cs.Raw...)
|
||||||
|
}
|
||||||
|
|
||||||
// All parts except the last part has to be atleast 5MB.
|
// All parts except the last part has to be atleast 5MB.
|
||||||
if (i < len(parts)-1) && !isMinAllowedPartSize(currentFI.Parts[partIdx].ActualSize) {
|
if (i < len(parts)-1) && !isMinAllowedPartSize(currentFI.Parts[partIdx].ActualSize) {
|
||||||
return oi, PartTooSmall{
|
return oi, PartTooSmall{
|
||||||
PartNumber: part.PartNumber,
|
PartNumber: part.PartNumber,
|
||||||
PartSize: currentFI.Parts[partIdx].ActualSize,
|
PartSize: expPart.ActualSize,
|
||||||
PartETag: part.ETag,
|
PartETag: part.ETag,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save for total object size.
|
// Save for total object size.
|
||||||
objectSize += currentFI.Parts[partIdx].Size
|
objectSize += expPart.Size
|
||||||
|
|
||||||
// Save the consolidated actual size.
|
// Save the consolidated actual size.
|
||||||
objectActualSize += currentFI.Parts[partIdx].ActualSize
|
objectActualSize += expPart.ActualSize
|
||||||
|
|
||||||
// Add incoming parts.
|
// Add incoming parts.
|
||||||
fi.Parts[i] = ObjectPartInfo{
|
fi.Parts[i] = ObjectPartInfo{
|
||||||
Number: part.PartNumber,
|
Number: part.PartNumber,
|
||||||
Size: currentFI.Parts[partIdx].Size,
|
Size: expPart.Size,
|
||||||
ActualSize: currentFI.Parts[partIdx].ActualSize,
|
ActualSize: expPart.ActualSize,
|
||||||
Index: currentFI.Parts[partIdx].Index,
|
ModTime: expPart.ModTime,
|
||||||
|
Index: expPart.Index,
|
||||||
|
Checksums: nil, // Not transferred since we do not need it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2897,8 +2962,8 @@ func (es *erasureSingle) ListObjects(ctx context.Context, bucket, prefix, marker
|
|||||||
objInfo, err := es.GetObjectInfo(ctx, bucket, prefix, ObjectOptions{NoLock: true})
|
objInfo, err := es.GetObjectInfo(ctx, bucket, prefix, ObjectOptions{NoLock: true})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if opts.Lifecycle != nil {
|
if opts.Lifecycle != nil {
|
||||||
action := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo, false)
|
evt := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo)
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
fallthrough
|
fallthrough
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
@@ -3050,12 +3115,22 @@ func (es *erasureSingle) Walk(ctx context.Context, bucket, prefix string, result
|
|||||||
versionsSorter(fivs.Versions).reverse()
|
versionsSorter(fivs.Versions).reverse()
|
||||||
|
|
||||||
for _, version := range fivs.Versions {
|
for _, version := range fivs.Versions {
|
||||||
|
send := true
|
||||||
|
if opts.WalkFilter != nil && !opts.WalkFilter(version) {
|
||||||
|
send = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !send {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
versioned := vcfg != nil && vcfg.Versioned(version.Name)
|
versioned := vcfg != nil && vcfg.Versioned(version.Name)
|
||||||
|
objInfo := version.ToObjectInfo(bucket, version.Name, versioned)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case results <- version.ToObjectInfo(bucket, version.Name, versioned):
|
case results <- objInfo:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3079,7 +3154,7 @@ func (es *erasureSingle) Walk(ctx context.Context, bucket, prefix string, result
|
|||||||
path: path,
|
path: path,
|
||||||
filterPrefix: filterPrefix,
|
filterPrefix: filterPrefix,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
forwardTo: "",
|
forwardTo: opts.WalkMarker,
|
||||||
minDisks: 1,
|
minDisks: 1,
|
||||||
reportNotFound: false,
|
reportNotFound: false,
|
||||||
agreed: loadEntry,
|
agreed: loadEntry,
|
||||||
|
|||||||
+7
-4
@@ -107,9 +107,8 @@ func (d byDiskTotal) Less(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func diskErrToDriveState(err error) (state string) {
|
func diskErrToDriveState(err error) (state string) {
|
||||||
state = madmin.DriveStateUnknown
|
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, errDiskNotFound):
|
case errors.Is(err, errDiskNotFound) || errors.Is(err, context.DeadlineExceeded):
|
||||||
state = madmin.DriveStateOffline
|
state = madmin.DriveStateOffline
|
||||||
case errors.Is(err, errCorruptedFormat):
|
case errors.Is(err, errCorruptedFormat):
|
||||||
state = madmin.DriveStateCorrupt
|
state = madmin.DriveStateCorrupt
|
||||||
@@ -121,7 +120,10 @@ func diskErrToDriveState(err error) (state string) {
|
|||||||
state = madmin.DriveStateFaulty
|
state = madmin.DriveStateFaulty
|
||||||
case err == nil:
|
case err == nil:
|
||||||
state = madmin.DriveStateOk
|
state = madmin.DriveStateOk
|
||||||
|
default:
|
||||||
|
state = fmt.Sprintf("%s (cause: %s)", madmin.DriveStateUnknown, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,18 +186,19 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.
|
|||||||
for index := range disks {
|
for index := range disks {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
|
diskEndpoint := endpoints[index].String()
|
||||||
if disks[index] == OfflineDisk {
|
if disks[index] == OfflineDisk {
|
||||||
logger.LogIf(GlobalContext, fmt.Errorf("%s: %s", errDiskNotFound, endpoints[index]))
|
logger.LogIf(GlobalContext, fmt.Errorf("%s: %s", errDiskNotFound, endpoints[index]))
|
||||||
disksInfo[index] = madmin.Disk{
|
disksInfo[index] = madmin.Disk{
|
||||||
State: diskErrToDriveState(errDiskNotFound),
|
State: diskErrToDriveState(errDiskNotFound),
|
||||||
Endpoint: endpoints[index].String(),
|
Endpoint: diskEndpoint,
|
||||||
}
|
}
|
||||||
// Storage disk is empty, perhaps ignored disk or not available.
|
// Storage disk is empty, perhaps ignored disk or not available.
|
||||||
return errDiskNotFound
|
return errDiskNotFound
|
||||||
}
|
}
|
||||||
info, err := disks[index].DiskInfo(context.TODO())
|
info, err := disks[index].DiskInfo(context.TODO())
|
||||||
di := madmin.Disk{
|
di := madmin.Disk{
|
||||||
Endpoint: info.Endpoint,
|
Endpoint: diskEndpoint,
|
||||||
DrivePath: info.MountPath,
|
DrivePath: info.MountPath,
|
||||||
TotalSpace: info.Total,
|
TotalSpace: info.Total,
|
||||||
UsedSpace: info.Used,
|
UsedSpace: info.Used,
|
||||||
|
|||||||
+2
-2
@@ -116,8 +116,7 @@ func initMetaVolumeFS(fsPath, fsUUID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFSObjectLayer - initialize new fs object layer.
|
// NewFSObjectLayer - initialize new fs object layer.
|
||||||
func NewFSObjectLayer(fsPath string) (ObjectLayer, error) {
|
func NewFSObjectLayer(ctx context.Context, fsPath string) (ObjectLayer, error) {
|
||||||
ctx := GlobalContext
|
|
||||||
if fsPath == "" {
|
if fsPath == "" {
|
||||||
return nil, errInvalidArgument
|
return nil, errInvalidArgument
|
||||||
}
|
}
|
||||||
@@ -473,6 +472,7 @@ func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
meta := newBucketMetadata(bucket)
|
meta := newBucketMetadata(bucket)
|
||||||
|
meta.SetCreatedAt(opts.CreatedAt)
|
||||||
if err := meta.Save(ctx, fs); err != nil {
|
if err := meta.Save(ctx, fs); err != nil {
|
||||||
return toObjectErr(err, bucket)
|
return toObjectErr(err, bucket)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -29,17 +30,19 @@ import (
|
|||||||
// TestNewFS - tests initialization of all input disks
|
// TestNewFS - tests initialization of all input disks
|
||||||
// and constructs a valid `FS` object layer.
|
// and constructs a valid `FS` object layer.
|
||||||
func TestNewFS(t *testing.T) {
|
func TestNewFS(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
// Do not attempt to create this path, the test validates
|
// Do not attempt to create this path, the test validates
|
||||||
// so that NewFSObjectLayer initializes non existing paths
|
// so that NewFSObjectLayer initializes non existing paths
|
||||||
// and successfully returns initialized object layer.
|
// and successfully returns initialized object layer.
|
||||||
disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
||||||
defer os.RemoveAll(disk)
|
defer os.RemoveAll(disk)
|
||||||
|
|
||||||
_, err := NewFSObjectLayer("")
|
_, err := NewFSObjectLayer(ctx, "")
|
||||||
if err != errInvalidArgument {
|
if err != errInvalidArgument {
|
||||||
t.Errorf("Expecting error invalid argument, got %s", err)
|
t.Errorf("Expecting error invalid argument, got %s", err)
|
||||||
}
|
}
|
||||||
_, err = NewFSObjectLayer(disk)
|
_, err = NewFSObjectLayer(ctx, disk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := "Unable to recognize backend format, Drive is not in FS format."
|
errMsg := "Unable to recognize backend format, Drive is not in FS format."
|
||||||
if err.Error() == errMsg {
|
if err.Error() == errMsg {
|
||||||
|
|||||||
+2
-11
@@ -249,16 +249,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
getCert = globalTLSCerts.GetCertificate
|
getCert = globalTLSCerts.GetCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners := ctx.Int("listeners")
|
httpServer := xhttp.NewServer(getServerListenAddrs()).
|
||||||
if listeners == 0 {
|
|
||||||
listeners = 1
|
|
||||||
}
|
|
||||||
addrs := make([]string, 0, listeners)
|
|
||||||
for i := 0; i < listeners; i++ {
|
|
||||||
addrs = append(addrs, globalMinioAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpServer := xhttp.NewServer(addrs).
|
|
||||||
UseHandler(setCriticalErrorHandler(corsHandler(router))).
|
UseHandler(setCriticalErrorHandler(corsHandler(router))).
|
||||||
UseTLSConfig(newTLSConfig(getCert)).
|
UseTLSConfig(newTLSConfig(getCert)).
|
||||||
UseShutdownTimeout(ctx.Duration("shutdown-timeout")).
|
UseShutdownTimeout(ctx.Duration("shutdown-timeout")).
|
||||||
@@ -284,7 +275,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
newObject = NewGatewayLayerWithLocker(newObject)
|
newObject = NewGatewayLayerWithLocker(newObject)
|
||||||
|
|
||||||
// Calls all New() for all sub-systems.
|
// Calls all New() for all sub-systems.
|
||||||
initAllSubsystems()
|
initAllSubsystems(GlobalContext)
|
||||||
|
|
||||||
// Once endpoints are finalized, initialize the new object api in safe mode.
|
// Once endpoints are finalized, initialize the new object api in safe mode.
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
|
|||||||
@@ -18,13 +18,17 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test printing Gateway common message.
|
// Test printing Gateway common message.
|
||||||
func TestPrintGatewayCommonMessage(t *testing.T) {
|
func TestPrintGatewayCommonMessage(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -39,7 +43,10 @@ func TestPrintGatewayCommonMessage(t *testing.T) {
|
|||||||
|
|
||||||
// Test print gateway startup message.
|
// Test print gateway startup message.
|
||||||
func TestPrintGatewayStartupMessage(t *testing.T) {
|
func TestPrintGatewayStartupMessage(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func (g *NAS) Name() string {
|
|||||||
// NewGatewayLayer returns nas gatewaylayer.
|
// NewGatewayLayer returns nas gatewaylayer.
|
||||||
func (g *NAS) NewGatewayLayer(creds madmin.Credentials) (minio.ObjectLayer, error) {
|
func (g *NAS) NewGatewayLayer(creds madmin.Credentials) (minio.ObjectLayer, error) {
|
||||||
var err error
|
var err error
|
||||||
newObject, err := minio.NewFSObjectLayer(g.path)
|
newObject, err := minio.NewFSObjectLayer(minio.GlobalContext, g.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,6 +245,12 @@ func isAdminReq(r *http.Request) bool {
|
|||||||
return strings.HasPrefix(r.URL.Path, adminPathPrefix)
|
return strings.HasPrefix(r.URL.Path, adminPathPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check to allow access to the reserved "bucket" `/minio` for KMS
|
||||||
|
// API requests.
|
||||||
|
func isKMSReq(r *http.Request) bool {
|
||||||
|
return strings.HasPrefix(r.URL.Path, kmsPathPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
// Supported Amz date headers.
|
// Supported Amz date headers.
|
||||||
var amzDateHeaders = []string{
|
var amzDateHeaders = []string{
|
||||||
// Do not chane this order, x-amz-date value should be
|
// Do not chane this order, x-amz-date value should be
|
||||||
@@ -428,12 +434,11 @@ func setRequestValidityHandler(h http.Handler) http.Handler {
|
|||||||
// For all other requests reject access to reserved buckets
|
// For all other requests reject access to reserved buckets
|
||||||
bucketName, _ := request2BucketObjectName(r)
|
bucketName, _ := request2BucketObjectName(r)
|
||||||
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
|
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
|
||||||
if !guessIsRPCReq(r) && !guessIsBrowserReq(r) && !guessIsHealthCheckReq(r) && !guessIsMetricsReq(r) && !isAdminReq(r) {
|
if !guessIsRPCReq(r) && !guessIsBrowserReq(r) && !guessIsHealthCheckReq(r) && !guessIsMetricsReq(r) && !isAdminReq(r) && !isKMSReq(r) {
|
||||||
if ok {
|
if ok {
|
||||||
tc.funcName = "handler.ValidRequest"
|
tc.funcName = "handler.ValidRequest"
|
||||||
tc.responseRecorder.LogErrBody = true
|
tc.responseRecorder.LogErrBody = true
|
||||||
}
|
}
|
||||||
|
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ import (
|
|||||||
|
|
||||||
// Tests validate bucket LocationConstraint.
|
// Tests validate bucket LocationConstraint.
|
||||||
func TestIsValidLocationContraint(t *testing.T) {
|
func TestIsValidLocationContraint(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-14
@@ -163,7 +163,7 @@ func (sys *IAMSys) LoadServiceAccount(ctx context.Context, accessKey string) err
|
|||||||
|
|
||||||
// initStore initializes IAM stores
|
// initStore initializes IAM stores
|
||||||
func (sys *IAMSys) initStore(objAPI ObjectLayer, etcdClient *etcd.Client) {
|
func (sys *IAMSys) initStore(objAPI ObjectLayer, etcdClient *etcd.Client) {
|
||||||
if sys.ldapConfig.Enabled {
|
if sys.ldapConfig.Enabled() {
|
||||||
sys.SetUsersSysType(LDAPUsersSysType)
|
sys.SetUsersSysType(LDAPUsersSysType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,8 +222,6 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
|||||||
s := globalServerConfig
|
s := globalServerConfig
|
||||||
globalServerConfigMu.RUnlock()
|
globalServerConfigMu.RUnlock()
|
||||||
|
|
||||||
ldapCfg := s[config.IdentityLDAPSubSys][config.Default]
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
globalOpenIDConfig, err = openid.LookupConfig(s,
|
globalOpenIDConfig, err = openid.LookupConfig(s,
|
||||||
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region)
|
NewGatewayHTTPTransport(), xhttp.DrainBody, globalSite.Region)
|
||||||
@@ -232,7 +230,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize if LDAP is enabled
|
// Initialize if LDAP is enabled
|
||||||
globalLDAPConfig, err = xldap.Lookup(ldapCfg, globalRootCAs)
|
globalLDAPConfig, err = xldap.Lookup(s, globalRootCAs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to parse LDAP configuration: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to parse LDAP configuration: %w", err))
|
||||||
}
|
}
|
||||||
@@ -347,7 +345,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
case sys.ldapConfig.Enabled:
|
case sys.ldapConfig.Enabled():
|
||||||
go func() {
|
go func() {
|
||||||
timer := time.NewTimer(refreshInterval)
|
timer := time.NewTimer(refreshInterval)
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
@@ -774,7 +772,7 @@ func (sys *IAMSys) ListUsers(ctx context.Context) (map[string]madmin.UserInfo, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListLDAPUsers - list LDAP users which has
|
// ListLDAPUsers - list LDAP users which has
|
||||||
func (sys *IAMSys) ListLDAPUsers() (map[string]madmin.UserInfo, error) {
|
func (sys *IAMSys) ListLDAPUsers(ctx context.Context) (map[string]madmin.UserInfo, error) {
|
||||||
if !sys.Initialized() {
|
if !sys.Initialized() {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
@@ -783,16 +781,19 @@ func (sys *IAMSys) ListLDAPUsers() (map[string]madmin.UserInfo, error) {
|
|||||||
return nil, errIAMActionNotAllowed
|
return nil, errIAMActionNotAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
<-sys.configLoaded
|
select {
|
||||||
|
case <-sys.configLoaded:
|
||||||
ldapUsers := make(map[string]madmin.UserInfo)
|
ldapUsers := make(map[string]madmin.UserInfo)
|
||||||
for user, policy := range sys.store.GetUsersWithMappedPolicies() {
|
for user, policy := range sys.store.GetUsersWithMappedPolicies() {
|
||||||
ldapUsers[user] = madmin.UserInfo{
|
ldapUsers[user] = madmin.UserInfo{
|
||||||
PolicyName: policy,
|
PolicyName: policy,
|
||||||
Status: madmin.AccountEnabled,
|
Status: madmin.AccountEnabled,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return ldapUsers, nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
return ldapUsers, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTempUser - returns if given key is a temporary user.
|
// IsTempUser - returns if given key is a temporary user.
|
||||||
|
|||||||
+25
-6
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -29,7 +30,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func testAuthenticate(authType string, t *testing.T) {
|
func testAuthenticate(authType string, t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -103,7 +107,10 @@ func getTokenString(accessKey, secretKey string) (string, error) {
|
|||||||
|
|
||||||
// Tests web request authenticator.
|
// Tests web request authenticator.
|
||||||
func TestWebRequestAuthenticate(t *testing.T) {
|
func TestWebRequestAuthenticate(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -157,7 +164,10 @@ func TestWebRequestAuthenticate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkParseJWTStandardClaims(b *testing.B) {
|
func BenchmarkParseJWTStandardClaims(b *testing.B) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -185,7 +195,10 @@ func BenchmarkParseJWTStandardClaims(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkParseJWTMapClaims(b *testing.B) {
|
func BenchmarkParseJWTMapClaims(b *testing.B) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -215,7 +228,10 @@ func BenchmarkParseJWTMapClaims(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAuthenticateNode(b *testing.B) {
|
func BenchmarkAuthenticateNode(b *testing.B) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -244,7 +260,10 @@ func BenchmarkAuthenticateNode(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAuthenticateWeb(b *testing.B) {
|
func BenchmarkAuthenticateWeb(b *testing.B) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,706 @@
|
|||||||
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/kes"
|
||||||
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KMSStatusHandler - GET /minio/kms/v1/status
|
||||||
|
func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSStatus")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSStatusAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := GlobalKMS.Stat(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status := madmin.KMSStatus{
|
||||||
|
Name: stat.Name,
|
||||||
|
DefaultKeyID: stat.DefaultKey,
|
||||||
|
Endpoints: make(map[string]madmin.ItemState, len(stat.Endpoints)),
|
||||||
|
}
|
||||||
|
for _, endpoint := range stat.Endpoints {
|
||||||
|
status.Endpoints[endpoint] = madmin.ItemOnline // TODO(aead): Implement an online check for mTLS
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := json.Marshal(status)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSMetricsHandler - POST /minio/kms/v1/metrics
|
||||||
|
func (a kmsAPIHandlers) KMSMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSMetrics")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSMetricsAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics, err := GlobalKMS.Metrics(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res, err := json.Marshal(metrics); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
} else {
|
||||||
|
writeSuccessResponseJSON(w, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSAPIsHandler - POST /minio/kms/v1/apis
|
||||||
|
func (a kmsAPIHandlers) KMSAPIsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSAPIs")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAPIAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager, ok := GlobalKMS.(kms.StatusManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apis, err := manager.APIs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res, err := json.Marshal(apis); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
} else {
|
||||||
|
writeSuccessResponseJSON(w, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type versionResponse struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSVersionHandler - POST /minio/kms/v1/version
|
||||||
|
func (a kmsAPIHandlers) KMSVersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSVersion")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSVersionAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager, ok := GlobalKMS.(kms.StatusManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := manager.Version(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := &versionResponse{Version: version}
|
||||||
|
v, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSCreateKeyHandler - POST /minio/kms/v1/key/create?key-id=<master-key-id>
|
||||||
|
func (a kmsAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSCreateKey")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSCreateKeyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.CreateKey(ctx, r.Form.Get("key-id")); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSDeleteKeyHandler - DELETE /minio/kms/v1/key/delete?key-id=<master-key-id>
|
||||||
|
func (a kmsAPIHandlers) KMSDeleteKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSDeleteKey")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteKeyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.DeleteKey(ctx, r.Form.Get("key-id")); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSListKeysHandler - GET /minio/kms/v1/key/list?pattern=<pattern>
|
||||||
|
func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSListKeys")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListKeysAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys, err := manager.ListKeys(ctx, r.Form.Get("pattern"))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values, err := keys.Values(0)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res, err := json.Marshal(values); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
} else {
|
||||||
|
writeSuccessResponseJSON(w, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type importKeyRequest struct {
|
||||||
|
Bytes string
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSImportKeyHandler - POST /minio/kms/v1/key/import?key-id=<master-key-id>
|
||||||
|
func (a kmsAPIHandlers) KMSImportKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSImportKey")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSImportKeyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.KeyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var request importKeyRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.ImportKey(ctx, r.Form.Get("key-id"), []byte(request.Bytes)); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSKeyStatusHandler - GET /minio/kms/v1/key/status?key-id=<master-key-id>
|
||||||
|
func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSKeyStatus")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSKeyStatusAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := GlobalKMS.Stat(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyID := r.Form.Get("key-id")
|
||||||
|
if keyID == "" {
|
||||||
|
keyID = stat.DefaultKey
|
||||||
|
}
|
||||||
|
response := madmin.KMSKeyStatus{
|
||||||
|
KeyID: keyID,
|
||||||
|
}
|
||||||
|
|
||||||
|
kmsContext := kms.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
|
||||||
|
// 1. Generate a new key using the KMS.
|
||||||
|
key, err := GlobalKMS.GenerateKey(ctx, keyID, kmsContext)
|
||||||
|
if err != nil {
|
||||||
|
response.EncryptionErr = err.Error()
|
||||||
|
resp, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Verify that we can indeed decrypt the (encrypted) key
|
||||||
|
decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext)
|
||||||
|
if err != nil {
|
||||||
|
response.DecryptionErr = err.Error()
|
||||||
|
resp, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Compare generated key with decrypted key
|
||||||
|
if subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1 {
|
||||||
|
response.DecryptionErr = "The generated and the decrypted data key do not match"
|
||||||
|
resp, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSDescribePolicyHandler - GET /minio/kms/v1/policy/describe?policy=<policy>
|
||||||
|
func (a kmsAPIHandlers) KMSDescribePolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSDescribePolicy")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribePolicyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
policy, err := manager.DescribePolicy(ctx, r.Form.Get("policy"))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, err := json.Marshal(policy)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
type assignPolicyRequest struct {
|
||||||
|
Identity string
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSAssignPolicyHandler - POST /minio/kms/v1/policy/assign?policy=<policy>
|
||||||
|
func (a kmsAPIHandlers) KMSAssignPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSAssignPolicy")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSAssignPolicyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var request assignPolicyRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := manager.AssignPolicy(ctx, r.Form.Get("policy"), request.Identity)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSSetPolicyHandler - POST /minio/kms/v1/policy/policy?policy=<policy>
|
||||||
|
func (a kmsAPIHandlers) KMSSetPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSSetPolicy")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSSetPolicyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var policy kes.Policy
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&policy); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.SetPolicy(ctx, r.Form.Get("policy"), &policy); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSDeletePolicyHandler - DELETE /minio/kms/v1/policy/delete?policy=<policy>
|
||||||
|
func (a kmsAPIHandlers) KMSDeletePolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSDeletePolicy")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeletePolicyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.DeletePolicy(ctx, r.Form.Get("policy")); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSListPoliciesHandler - GET /minio/kms/v1/policy/list?pattern=<pattern>
|
||||||
|
func (a kmsAPIHandlers) KMSListPoliciesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSListPolicies")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListPoliciesAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
policies, err := manager.ListPolicies(ctx, r.Form.Get("pattern"))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values, err := policies.Values(0)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res, err := json.Marshal(values); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
} else {
|
||||||
|
writeSuccessResponseJSON(w, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSGetPolicyHandler - GET /minio/kms/v1/policy/get?policy=<policy>
|
||||||
|
func (a kmsAPIHandlers) KMSGetPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSGetPolicy")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSGetPolicyAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.PolicyManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
policy, err := manager.GetPolicy(ctx, r.Form.Get("policy"))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p, err := json.Marshal(policy); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
} else {
|
||||||
|
writeSuccessResponseJSON(w, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSDescribeIdentityHandler - GET /minio/kms/v1/identity/describe?identity=<identity>
|
||||||
|
func (a kmsAPIHandlers) KMSDescribeIdentityHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSDescribeIdentity")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeIdentityAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
identity, err := manager.DescribeIdentity(ctx, r.Form.Get("identity"))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i, err := json.Marshal(identity)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
type describeSelfIdentityResponse struct {
|
||||||
|
Policy *kes.Policy `json:"policy"`
|
||||||
|
PolicyName string `json:"policyName"`
|
||||||
|
Identity string `json:"identity"`
|
||||||
|
IsAdmin bool `json:"isAdmin"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
CreatedBy string `json:"createdBy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSDescribeSelfIdentityHandler - GET /minio/kms/v1/identity/describe-self
|
||||||
|
func (a kmsAPIHandlers) KMSDescribeSelfIdentityHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSDescribeSelfIdentity")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDescribeSelfIdentityAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
identity, policy, err := manager.DescribeSelfIdentity(ctx)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := &describeSelfIdentityResponse{
|
||||||
|
Policy: policy,
|
||||||
|
PolicyName: identity.Policy,
|
||||||
|
Identity: identity.Identity.String(),
|
||||||
|
IsAdmin: identity.IsAdmin,
|
||||||
|
CreatedAt: identity.CreatedAt,
|
||||||
|
CreatedBy: identity.CreatedBy.String(),
|
||||||
|
}
|
||||||
|
i, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSDeleteIdentityHandler - DELETE /minio/kms/v1/identity/delete?identity=<identity>
|
||||||
|
func (a kmsAPIHandlers) KMSDeleteIdentityHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSDeleteIdentity")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSDeleteIdentityAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.DeleteIdentity(ctx, r.Form.Get("policy")); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KMSListIdentitiesHandler - GET /minio/kms/v1/identity/list?pattern=<pattern>
|
||||||
|
func (a kmsAPIHandlers) KMSListIdentitiesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "KMSListIdentities")
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.KMSListIdentitiesAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager, ok := GlobalKMS.(kms.IdentityManager)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
identities, err := manager.ListIdentities(ctx, r.Form.Get("pattern"))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values, err := identities.Values(0)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res, err := json.Marshal(values); err != nil {
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
||||||
|
} else {
|
||||||
|
writeSuccessResponseJSON(w, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/klauspost/compress/gzhttp"
|
||||||
|
"github.com/klauspost/compress/gzip"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kmsPathPrefix = minioReservedBucketPath + "/kms"
|
||||||
|
kmsAPIVersion = "v1"
|
||||||
|
kmsAPIVersionPrefix = SlashSeparator + kmsAPIVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
type kmsAPIHandlers struct{}
|
||||||
|
|
||||||
|
// registerKMSRouter - Registers KMS APIs
|
||||||
|
func registerKMSRouter(router *mux.Router) {
|
||||||
|
kmsAPI := kmsAPIHandlers{}
|
||||||
|
kmsRouter := router.PathPrefix(kmsPathPrefix).Subrouter()
|
||||||
|
|
||||||
|
KMSVersions := []string{
|
||||||
|
kmsAPIVersionPrefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
gz, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed))
|
||||||
|
if err != nil {
|
||||||
|
// Static params, so this is very unlikely.
|
||||||
|
logger.Fatal(err, "Unable to initialize server")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, version := range KMSVersions {
|
||||||
|
// KMS Status APIs
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version + "/status").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSStatusHandler)))
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version + "/metrics").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSMetricsHandler)))
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version + "/apis").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSAPIsHandler)))
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version + "/version").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSVersionHandler)))
|
||||||
|
// KMS Key APIs
|
||||||
|
kmsRouter.Methods(http.MethodPost).Path(version+"/key/create").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodPost).Path(version+"/key/import").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSImportKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodDelete).Path(version+"/key/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeleteKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version+"/key/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListKeysHandler))).Queries("pattern", "{pattern:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version + "/key/status").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSKeyStatusHandler)))
|
||||||
|
|
||||||
|
// KMS Policy APIs
|
||||||
|
kmsRouter.Methods(http.MethodPost).Path(version+"/policy/set").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSSetPolicyHandler))).Queries("policy", "{policy:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodPost).Path(version+"/policy/assign").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSAssignPolicyHandler))).Queries("policy", "{policy:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version+"/policy/describe").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribePolicyHandler))).Queries("policy", "{policy:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version+"/policy/get").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSGetPolicyHandler))).Queries("policy", "{policy:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodDelete).Path(version+"/policy/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeletePolicyHandler))).Queries("policy", "{policy:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version+"/policy/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListPoliciesHandler))).Queries("pattern", "{pattern:.*}")
|
||||||
|
|
||||||
|
// KMS Identity APIs
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version+"/identity/describe").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribeIdentityHandler))).Queries("identity", "{identity:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version + "/identity/describe-self").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDescribeSelfIdentityHandler)))
|
||||||
|
kmsRouter.Methods(http.MethodDelete).Path(version+"/identity/delete").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSDeleteIdentityHandler))).Queries("identity", "{identity:.*}")
|
||||||
|
kmsRouter.Methods(http.MethodGet).Path(version+"/identity/list").HandlerFunc(gz(httpTraceAll(kmsAPI.KMSListIdentitiesHandler))).Queries("pattern", "{pattern:.*}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the routes match add default error handler routes
|
||||||
|
kmsRouter.NotFoundHandler = httpTraceAll(errorResponseHandler)
|
||||||
|
kmsRouter.MethodNotAllowedHandler = httpTraceAll(methodNotAllowedHandler("KMS"))
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -27,8 +28,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Helper function to create a lock server for testing
|
// Helper function to create a lock server for testing
|
||||||
func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
|
func createLockTestServer(ctx context.Context, t *testing.T) (string, *lockRESTServer, string) {
|
||||||
obj, fsDir, err := prepareFS()
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,10 @@ func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
|
|||||||
|
|
||||||
// Test function to remove lock entries from map based on name & uid combination
|
// Test function to remove lock entries from map based on name & uid combination
|
||||||
func TestLockRpcServerRemoveEntry(t *testing.T) {
|
func TestLockRpcServerRemoveEntry(t *testing.T) {
|
||||||
testPath, locker, _ := createLockTestServer(t)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
testPath, locker, _ := createLockTestServer(ctx, t)
|
||||||
defer os.RemoveAll(testPath)
|
defer os.RemoveAll(testPath)
|
||||||
|
|
||||||
lockRequesterInfo1 := lockRequesterInfo{
|
lockRequesterInfo1 := lockRequesterInfo{
|
||||||
|
|||||||
@@ -375,7 +375,9 @@ func (es *erasureSingle) listPath(ctx context.Context, o *listPathOptions) (entr
|
|||||||
entries.truncate(0)
|
entries.truncate(0)
|
||||||
o.ID = ""
|
o.ID = ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Resuming listing from drives failed %w, proceeding to do raw listing", err))
|
if !(isErrObjectNotFound(err) || errors.Is(err, IncompleteBody{}) || isErrVersionNotFound(err)) {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Resuming listing from drives failed %w, proceeding to do raw listing", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,14 +627,14 @@ func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCa
|
|||||||
|
|
||||||
objInfo := fi.ToObjectInfo(o.Bucket, obj.name, versioned)
|
objInfo := fi.ToObjectInfo(o.Bucket, obj.name, versioned)
|
||||||
if o.Lifecycle != nil {
|
if o.Lifecycle != nil {
|
||||||
action := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, objInfo, false)
|
evt := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, objInfo)
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
globalExpiryState.enqueueByDays(objInfo, false, action == lifecycle.DeleteVersionAction)
|
globalExpiryState.enqueueByDays(objInfo, false, evt.Action == lifecycle.DeleteVersionAction)
|
||||||
// Skip this entry.
|
// Skip this entry.
|
||||||
continue
|
continue
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
globalExpiryState.enqueueByDays(objInfo, true, action == lifecycle.DeleteRestoredVersionAction)
|
globalExpiryState.enqueueByDays(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction)
|
||||||
// Skip this entry.
|
// Skip this entry.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-7
@@ -25,23 +25,29 @@ import (
|
|||||||
"github.com/minio/minio/internal/disk"
|
"github.com/minio/minio/internal/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
func collectLocalMetrics(types madmin.MetricType, hosts map[string]struct{}, disks map[string]struct{}) (m madmin.RealtimeMetrics) {
|
type collectMetricsOpts struct {
|
||||||
|
hosts map[string]struct{}
|
||||||
|
disks map[string]struct{}
|
||||||
|
jobID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectLocalMetrics(types madmin.MetricType, opts collectMetricsOpts) (m madmin.RealtimeMetrics) {
|
||||||
if types == madmin.MetricsNone {
|
if types == madmin.MetricsNone {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hosts) > 0 {
|
if len(opts.hosts) > 0 {
|
||||||
if _, ok := hosts[globalMinioAddr]; !ok {
|
if _, ok := opts.hosts[globalMinioAddr]; !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if types.Contains(madmin.MetricsDisk) && !globalIsGateway {
|
if types.Contains(madmin.MetricsDisk) {
|
||||||
m.ByDisk = make(map[string]madmin.DiskMetric)
|
m.ByDisk = make(map[string]madmin.DiskMetric)
|
||||||
aggr := madmin.DiskMetric{
|
aggr := madmin.DiskMetric{
|
||||||
CollectedAt: time.Now(),
|
CollectedAt: time.Now(),
|
||||||
}
|
}
|
||||||
for name, disk := range collectLocalDisksMetrics(disks) {
|
for name, disk := range collectLocalDisksMetrics(opts.disks) {
|
||||||
m.ByDisk[name] = disk
|
m.ByDisk[name] = disk
|
||||||
aggr.Merge(&disk)
|
aggr.Merge(&disk)
|
||||||
}
|
}
|
||||||
@@ -56,6 +62,10 @@ func collectLocalMetrics(types madmin.MetricType, hosts map[string]struct{}, dis
|
|||||||
metrics := globalOSMetrics.report()
|
metrics := globalOSMetrics.report()
|
||||||
m.Aggregated.OS = &metrics
|
m.Aggregated.OS = &metrics
|
||||||
}
|
}
|
||||||
|
if types.Contains(madmin.MetricsBatchJobs) {
|
||||||
|
m.Aggregated.BatchJobs = globalBatchJobsMetrics.report(opts.jobID)
|
||||||
|
}
|
||||||
|
|
||||||
// Add types...
|
// Add types...
|
||||||
|
|
||||||
// ByHost is a shallow reference, so careful about sharing.
|
// ByHost is a shallow reference, so careful about sharing.
|
||||||
@@ -143,11 +153,11 @@ func collectLocalDisksMetrics(disks map[string]struct{}) map[string]madmin.DiskM
|
|||||||
return metrics
|
return metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectRemoteMetrics(ctx context.Context, types madmin.MetricType, hosts map[string]struct{}, disks map[string]struct{}) (m madmin.RealtimeMetrics) {
|
func collectRemoteMetrics(ctx context.Context, types madmin.MetricType, opts collectMetricsOpts) (m madmin.RealtimeMetrics) {
|
||||||
if !globalIsDistErasure {
|
if !globalIsDistErasure {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
all := globalNotificationSys.GetMetrics(ctx, types, hosts, disks)
|
all := globalNotificationSys.GetMetrics(ctx, types, opts)
|
||||||
for _, remote := range all {
|
for _, remote := range all {
|
||||||
m.Merge(&remote)
|
m.Merge(&remote)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-20
@@ -44,9 +44,8 @@ func mustSplitHostPort(hostPort string) (host, port string) {
|
|||||||
return xh.Name, xh.Port.String()
|
return xh.Name, xh.Port.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustGetLocalIP4 returns IPv4 addresses of localhost. It panics on error.
|
// mustGetLocalIPs returns IPs of local interface
|
||||||
func mustGetLocalIP4() (ipList set.StringSet) {
|
func mustGetLocalIPs() (ipList []net.IP) {
|
||||||
ipList = set.NewStringSet()
|
|
||||||
ifs, err := net.Interfaces()
|
ifs, err := net.Interfaces()
|
||||||
logger.FatalIf(err, "Unable to get IP addresses of this host")
|
logger.FatalIf(err, "Unable to get IP addresses of this host")
|
||||||
|
|
||||||
@@ -68,36 +67,33 @@ func mustGetLocalIP4() (ipList set.StringSet) {
|
|||||||
ip = v.IP
|
ip = v.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip.To4() != nil {
|
ipList = append(ipList, ip)
|
||||||
ipList.Add(ip.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipList
|
return ipList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mustGetLocalIP4 returns IPv4 addresses of localhost. It panics on error.
|
||||||
|
func mustGetLocalIP4() (ipList set.StringSet) {
|
||||||
|
ipList = set.NewStringSet()
|
||||||
|
for _, ip := range mustGetLocalIPs() {
|
||||||
|
if ip.To4() != nil {
|
||||||
|
ipList.Add(ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// mustGetLocalIP6 returns IPv6 addresses of localhost. It panics on error.
|
// mustGetLocalIP6 returns IPv6 addresses of localhost. It panics on error.
|
||||||
func mustGetLocalIP6() (ipList set.StringSet) {
|
func mustGetLocalIP6() (ipList set.StringSet) {
|
||||||
ipList = set.NewStringSet()
|
ipList = set.NewStringSet()
|
||||||
addrs, err := net.InterfaceAddrs()
|
for _, ip := range mustGetLocalIPs() {
|
||||||
logger.FatalIf(err, "Unable to get IP addresses of this host")
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
var ip net.IP
|
|
||||||
switch v := addr.(type) {
|
|
||||||
case *net.IPNet:
|
|
||||||
ip = v.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = v.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip.To4() == nil {
|
if ip.To4() == nil {
|
||||||
ipList.Add(ip.String())
|
ipList.Add(ip.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return ipList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getHostIP returns IP address of given host.
|
// getHostIP returns IP address of given host.
|
||||||
|
|||||||
+4
-4
@@ -731,7 +731,7 @@ func (sys *NotificationSys) GetOSInfo(ctx context.Context) []madmin.OSInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMetrics - Get metrics from all peers.
|
// GetMetrics - Get metrics from all peers.
|
||||||
func (sys *NotificationSys) GetMetrics(ctx context.Context, t madmin.MetricType, hosts map[string]struct{}, disks map[string]struct{}) []madmin.RealtimeMetrics {
|
func (sys *NotificationSys) GetMetrics(ctx context.Context, t madmin.MetricType, opts collectMetricsOpts) []madmin.RealtimeMetrics {
|
||||||
reply := make([]madmin.RealtimeMetrics, len(sys.peerClients))
|
reply := make([]madmin.RealtimeMetrics, len(sys.peerClients))
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(sys.peerClients))
|
g := errgroup.WithNErrs(len(sys.peerClients))
|
||||||
@@ -740,8 +740,8 @@ func (sys *NotificationSys) GetMetrics(ctx context.Context, t madmin.MetricType,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
host := client.host.String()
|
host := client.host.String()
|
||||||
if len(hosts) > 0 {
|
if len(opts.hosts) > 0 {
|
||||||
if _, ok := hosts[host]; !ok {
|
if _, ok := opts.hosts[host]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -749,7 +749,7 @@ func (sys *NotificationSys) GetMetrics(ctx context.Context, t madmin.MetricType,
|
|||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
var err error
|
var err error
|
||||||
reply[index], err = sys.peerClients[index].GetMetrics(ctx, t, disks)
|
reply[index], err = sys.peerClients[index].GetMetrics(ctx, t, opts)
|
||||||
return err
|
return err
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ type BucketInfo struct {
|
|||||||
// Date and time when the bucket was created.
|
// Date and time when the bucket was created.
|
||||||
Created time.Time
|
Created time.Time
|
||||||
Deleted time.Time
|
Deleted time.Time
|
||||||
|
|
||||||
|
// Bucket features enabled
|
||||||
|
Versioning, ObjectLocking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectInfo - represents object metadata.
|
// ObjectInfo - represents object metadata.
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ type ObjectOptions struct {
|
|||||||
MTime time.Time // Is only set in POST/PUT operations
|
MTime time.Time // Is only set in POST/PUT operations
|
||||||
Expires time.Time // Is only used in POST/PUT operations
|
Expires time.Time // Is only used in POST/PUT operations
|
||||||
|
|
||||||
DeleteMarker bool // Is only set in DELETE operations for delete marker replication
|
DeleteMarker bool // Is only set in DELETE operations for delete marker replication
|
||||||
|
CheckDMReplicationReady bool // Is delete marker ready to be replicated - set only during HEAD
|
||||||
|
|
||||||
UserDefined map[string]string // only set in case of POST/PUT operations
|
UserDefined map[string]string // only set in case of POST/PUT operations
|
||||||
PartNumber int // only useful in case of GetObject/HeadObject
|
PartNumber int // only useful in case of GetObject/HeadObject
|
||||||
CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation
|
CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation
|
||||||
@@ -85,6 +87,8 @@ type ObjectOptions struct {
|
|||||||
// mainly set for certain WRITE operations.
|
// mainly set for certain WRITE operations.
|
||||||
SkipDecommissioned bool
|
SkipDecommissioned bool
|
||||||
|
|
||||||
|
WalkFilter func(info FileInfo) bool // return WalkFilter returns 'true/false'
|
||||||
|
WalkMarker string // set to skip until this object
|
||||||
PrefixEnabledFn func(prefix string) bool // function which returns true if versioning is enabled on prefix
|
PrefixEnabledFn func(prefix string) bool // function which returns true if versioning is enabled on prefix
|
||||||
|
|
||||||
// IndexCB will return any index created but the compression.
|
// IndexCB will return any index created but the compression.
|
||||||
|
|||||||
@@ -1877,7 +1877,7 @@ func initFSObjectsB(disk string, t *testing.B) (obj ObjectLayer) {
|
|||||||
|
|
||||||
newTestConfig(globalMinioDefaultRegion, obj)
|
newTestConfig(globalMinioDefaultRegion, obj)
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(GlobalContext)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,6 +159,23 @@ func getOpts(ctx context.Context, r *http.Request, bucket, object string) (Objec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
replReadyCheck := strings.TrimSpace(r.Header.Get(xhttp.MinIOCheckDMReplicationReady))
|
||||||
|
if replReadyCheck != "" {
|
||||||
|
switch replReadyCheck {
|
||||||
|
case "true":
|
||||||
|
opts.CheckDMReplicationReady = true
|
||||||
|
case "false":
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unable to parse %s, failed with %w", xhttp.MinIOCheckDMReplicationReady, fmt.Errorf("should be true or false"))
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return opts, InvalidArgument{
|
||||||
|
Bucket: bucket,
|
||||||
|
Object: object,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
||||||
opts.VersionSuspended = globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
opts.VersionSuspended = globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||||
return opts, nil
|
return opts, nil
|
||||||
|
|||||||
+74
-40
@@ -495,15 +495,15 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
|||||||
// Automatically remove the object/version is an expiry lifecycle rule can be applied
|
// Automatically remove the object/version is an expiry lifecycle rule can be applied
|
||||||
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
|
||||||
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
|
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
|
||||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo, false)
|
evt := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo)
|
||||||
var success bool
|
var success bool
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
success = applyExpiryRule(objInfo, false, action == lifecycle.DeleteVersionAction)
|
success = applyExpiryRule(objInfo, false, evt.Action == lifecycle.DeleteVersionAction)
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
// Restored object delete would be still allowed to proceed as success
|
// Restored object delete would be still allowed to proceed as success
|
||||||
// since transition behavior is slightly different.
|
// since transition behavior is slightly different.
|
||||||
applyExpiryRule(objInfo, true, action == lifecycle.DeleteRestoredVersionAction)
|
applyExpiryRule(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction)
|
||||||
}
|
}
|
||||||
if success {
|
if success {
|
||||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
|
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
|
||||||
@@ -651,6 +651,44 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for auth type to return S3 compatible error.
|
||||||
|
// type to return the correct error (NoSuchKey vs AccessDenied)
|
||||||
|
if s3Error := authenticateRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||||
|
if getRequestAuthType(r) == authTypeAnonymous {
|
||||||
|
// As per "Permission" section in
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
|
||||||
|
// If the object you request does not exist,
|
||||||
|
// the error Amazon S3 returns depends on
|
||||||
|
// whether you also have the s3:ListBucket
|
||||||
|
// permission.
|
||||||
|
// * If you have the s3:ListBucket permission
|
||||||
|
// on the bucket, Amazon S3 will return an
|
||||||
|
// HTTP status code 404 ("no such key")
|
||||||
|
// error.
|
||||||
|
// * if you don’t have the s3:ListBucket
|
||||||
|
// permission, Amazon S3 will return an HTTP
|
||||||
|
// status code 403 ("access denied") error.`
|
||||||
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
|
Action: policy.ListBucketAction,
|
||||||
|
BucketName: bucket,
|
||||||
|
ConditionValues: getConditionValues(r, "", "", nil),
|
||||||
|
IsOwner: false,
|
||||||
|
}) {
|
||||||
|
getObjectInfo := objectAPI.GetObjectInfo
|
||||||
|
if api.CacheAPI() != nil {
|
||||||
|
getObjectInfo = api.CacheAPI().GetObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = getObjectInfo(ctx, bucket, object, opts)
|
||||||
|
if toAPIError(ctx, err).Code == "NoSuchKey" {
|
||||||
|
s3Error = ErrNoSuchKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get request range.
|
// Get request range.
|
||||||
var rs *HTTPRangeSpec
|
var rs *HTTPRangeSpec
|
||||||
rangeHeader := r.Header.Get(xhttp.Range)
|
rangeHeader := r.Header.Get(xhttp.Range)
|
||||||
@@ -676,38 +714,13 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && objInfo.UserTags != "" {
|
if objInfo.UserTags != "" {
|
||||||
// Set this such that authorization policies can be applied on the object tags.
|
// Set this such that authorization policies can be applied on the object tags.
|
||||||
r.Header.Set(xhttp.AmzObjectTagging, objInfo.UserTags)
|
r.Header.Set(xhttp.AmzObjectTagging, objInfo.UserTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object); s3Error != ErrNone {
|
if s3Error := authorizeRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||||
if getRequestAuthType(r) == authTypeAnonymous {
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
// As per "Permission" section in
|
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
|
|
||||||
// If the object you request does not exist,
|
|
||||||
// the error Amazon S3 returns depends on
|
|
||||||
// whether you also have the s3:ListBucket
|
|
||||||
// permission.
|
|
||||||
// * If you have the s3:ListBucket permission
|
|
||||||
// on the bucket, Amazon S3 will return an
|
|
||||||
// HTTP status code 404 ("no such key")
|
|
||||||
// error.
|
|
||||||
// * if you don’t have the s3:ListBucket
|
|
||||||
// permission, Amazon S3 will return an HTTP
|
|
||||||
// status code 403 ("access denied") error.`
|
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
|
||||||
Action: policy.ListBucketAction,
|
|
||||||
BucketName: bucket,
|
|
||||||
ConditionValues: getConditionValues(r, "", "", nil),
|
|
||||||
IsOwner: false,
|
|
||||||
}) {
|
|
||||||
if toAPIError(ctx, err).Code == "NoSuchKey" {
|
|
||||||
s3Error = ErrNoSuchKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,6 +739,16 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
|||||||
w.Header()[xhttp.AmzDeleteMarker] = []string{strconv.FormatBool(objInfo.DeleteMarker)}
|
w.Header()[xhttp.AmzDeleteMarker] = []string{strconv.FormatBool(objInfo.DeleteMarker)}
|
||||||
}
|
}
|
||||||
QueueReplicationHeal(ctx, bucket, objInfo)
|
QueueReplicationHeal(ctx, bucket, objInfo)
|
||||||
|
// do an additional verification whether object exists when object is deletemarker and request
|
||||||
|
// is from replication
|
||||||
|
if opts.CheckDMReplicationReady {
|
||||||
|
topts := opts
|
||||||
|
topts.VersionID = ""
|
||||||
|
goi, gerr := getObjectInfo(ctx, bucket, object, topts)
|
||||||
|
if gerr == nil || goi.VersionID != "" { // object layer returned more info because object is deleted
|
||||||
|
w.Header().Set(xhttp.MinIOTargetReplicationReady, "true")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||||
return
|
return
|
||||||
@@ -735,15 +758,15 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
|||||||
// Automatically remove the object/version is an expiry lifecycle rule can be applied
|
// Automatically remove the object/version is an expiry lifecycle rule can be applied
|
||||||
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
|
||||||
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
|
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
|
||||||
action := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo, false)
|
evt := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo)
|
||||||
var success bool
|
var success bool
|
||||||
switch action {
|
switch evt.Action {
|
||||||
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction:
|
||||||
success = applyExpiryRule(objInfo, false, action == lifecycle.DeleteVersionAction)
|
success = applyExpiryRule(objInfo, false, evt.Action == lifecycle.DeleteVersionAction)
|
||||||
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
|
||||||
// Restored object delete would be still allowed to proceed as success
|
// Restored object delete would be still allowed to proceed as success
|
||||||
// since transition behavior is slightly different.
|
// since transition behavior is slightly different.
|
||||||
applyExpiryRule(objInfo, true, action == lifecycle.DeleteRestoredVersionAction)
|
applyExpiryRule(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction)
|
||||||
}
|
}
|
||||||
if success {
|
if success {
|
||||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
|
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
|
||||||
@@ -1558,6 +1581,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
origETag := objInfo.ETag
|
||||||
objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
|
objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
|
||||||
response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime)
|
response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime)
|
||||||
encodedSuccessResponse := encodeResponse(response)
|
encodedSuccessResponse := encodeResponse(response)
|
||||||
@@ -1590,6 +1614,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
if !remoteCallRequired && !globalTierConfigMgr.Empty() {
|
if !remoteCallRequired && !globalTierConfigMgr.Empty() {
|
||||||
// Schedule object for immediate transition if eligible.
|
// Schedule object for immediate transition if eligible.
|
||||||
|
objInfo.ETag = origETag
|
||||||
enqueueTransitionImmediate(objInfo)
|
enqueueTransitionImmediate(objInfo)
|
||||||
// Remove the transitioned object whose object version is being overwritten.
|
// Remove the transitioned object whose object version is being overwritten.
|
||||||
logger.LogIf(ctx, os.Sweep())
|
logger.LogIf(ctx, os.Sweep())
|
||||||
@@ -1775,7 +1800,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = actualReader.AddChecksum(r, false); err != nil {
|
if err = actualReader.AddChecksum(r, globalIsGateway); err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1796,7 +1821,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := hashReader.AddChecksum(r, size < 0); err != nil {
|
if err := hashReader.AddChecksum(r, size < 0 || globalIsGateway); err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1920,6 +1945,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
origETag := objInfo.ETag
|
||||||
if kind, encrypted := crypto.IsEncrypted(objInfo.UserDefined); encrypted {
|
if kind, encrypted := crypto.IsEncrypted(objInfo.UserDefined); encrypted {
|
||||||
switch kind {
|
switch kind {
|
||||||
case crypto.S3:
|
case crypto.S3:
|
||||||
@@ -1966,6 +1992,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
// Remove the transitioned object whose object version is being overwritten.
|
// Remove the transitioned object whose object version is being overwritten.
|
||||||
if !globalTierConfigMgr.Empty() {
|
if !globalTierConfigMgr.Empty() {
|
||||||
// Schedule object for immediate transition if eligible.
|
// Schedule object for immediate transition if eligible.
|
||||||
|
objInfo.ETag = origETag
|
||||||
enqueueTransitionImmediate(objInfo)
|
enqueueTransitionImmediate(objInfo)
|
||||||
logger.LogIf(ctx, os.Sweep())
|
logger.LogIf(ctx, os.Sweep())
|
||||||
}
|
}
|
||||||
@@ -2107,7 +2134,7 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = hreader.AddChecksum(r, false); err != nil {
|
if err = hreader.AddChecksum(r, globalIsGateway); err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2260,8 +2287,15 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var opts untarOptions
|
||||||
|
opts.ignoreDirs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreDirs), "true")
|
||||||
|
opts.ignoreErrs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreErrors), "true")
|
||||||
|
opts.prefixAll = r.Header.Get(xhttp.MinIOSnowballPrefix)
|
||||||
|
if opts.prefixAll != "" {
|
||||||
|
opts.prefixAll = trimLeadingSlash(pathJoin(opts.prefixAll, slashSeparator))
|
||||||
|
}
|
||||||
|
|
||||||
if err = untar(hreader, putObjectTar); err != nil {
|
if err = untar(ctx, hreader, putObjectTar, opts); err != nil {
|
||||||
apiErr := errorCodes.ToAPIErr(s3Err)
|
apiErr := errorCodes.ToAPIErr(s3Err)
|
||||||
// If not set, convert or use BadRequest
|
// If not set, convert or use BadRequest
|
||||||
if s3Err == ErrNone {
|
if s3Err == ErrNone {
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = actualReader.AddChecksum(r, false); err != nil {
|
if err = actualReader.AddChecksum(r, globalIsGateway); err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -411,7 +411,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := hashReader.AddChecksum(r, size < 0); err != nil {
|
if err := hashReader.AddChecksum(r, size < 0 || globalIsGateway); err != nil {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,12 +195,13 @@ func (client *peerRESTClient) GetMemInfo(ctx context.Context) (info madmin.MemIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMetrics - fetch metrics from a remote node.
|
// GetMetrics - fetch metrics from a remote node.
|
||||||
func (client *peerRESTClient) GetMetrics(ctx context.Context, t madmin.MetricType, diskMap map[string]struct{}) (info madmin.RealtimeMetrics, err error) {
|
func (client *peerRESTClient) GetMetrics(ctx context.Context, t madmin.MetricType, opts collectMetricsOpts) (info madmin.RealtimeMetrics, err error) {
|
||||||
values := make(url.Values)
|
values := make(url.Values)
|
||||||
values.Set(peerRESTTypes, strconv.FormatUint(uint64(t), 10))
|
values.Set(peerRESTMetricsTypes, strconv.FormatUint(uint64(t), 10))
|
||||||
for disk := range diskMap {
|
for disk := range opts.disks {
|
||||||
values.Set(peerRESTDisk, disk)
|
values.Set(peerRESTDisk, disk)
|
||||||
}
|
}
|
||||||
|
values.Set(peerRESTJobID, opts.jobID)
|
||||||
respBody, err := client.callWithContext(ctx, peerRESTMethodMetrics, values, nil, -1)
|
respBody, err := client.callWithContext(ctx, peerRESTMethodMetrics, values, nil, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -91,8 +91,9 @@ const (
|
|||||||
peerRESTConcurrent = "concurrent"
|
peerRESTConcurrent = "concurrent"
|
||||||
peerRESTDuration = "duration"
|
peerRESTDuration = "duration"
|
||||||
peerRESTStorageClass = "storage-class"
|
peerRESTStorageClass = "storage-class"
|
||||||
peerRESTTypes = "types"
|
peerRESTMetricsTypes = "types"
|
||||||
peerRESTDisk = "disk"
|
peerRESTDisk = "disk"
|
||||||
|
peerRESTJobID = "job-id"
|
||||||
|
|
||||||
peerRESTListenBucket = "bucket"
|
peerRESTListenBucket = "bucket"
|
||||||
peerRESTListenPrefix = "prefix"
|
peerRESTListenPrefix = "prefix"
|
||||||
|
|||||||
@@ -425,23 +425,25 @@ func (s *peerRESTServer) GetMetricsHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
var types madmin.MetricType
|
var types madmin.MetricType
|
||||||
if t, _ := strconv.ParseUint(r.Form.Get(peerRESTTypes), 10, 64); t != 0 {
|
if t, _ := strconv.ParseUint(r.Form.Get(peerRESTMetricsTypes), 10, 64); t != 0 {
|
||||||
types = madmin.MetricType(t)
|
types = madmin.MetricType(t)
|
||||||
} else {
|
} else {
|
||||||
types = madmin.MetricsAll
|
types = madmin.MetricsAll
|
||||||
}
|
}
|
||||||
|
|
||||||
diskMap := make(map[string]struct{})
|
diskMap := make(map[string]struct{})
|
||||||
if r.Form != nil {
|
for _, disk := range r.Form[peerRESTDisk] {
|
||||||
for _, disk := range r.Form[peerRESTDisk] {
|
diskMap[disk] = struct{}{}
|
||||||
diskMap[disk] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
jobID := r.Form.Get(peerRESTJobID)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(r.Context())
|
ctx, cancel := context.WithCancel(r.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
info := collectLocalMetrics(types, nil, diskMap)
|
info := collectLocalMetrics(types, collectMetricsOpts{
|
||||||
|
disks: diskMap,
|
||||||
|
jobID: jobID,
|
||||||
|
})
|
||||||
|
|
||||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
|
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
|
||||||
}
|
}
|
||||||
@@ -1308,7 +1310,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
|||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodServerInfo).HandlerFunc(httpTraceHdrs(server.ServerInfoHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodServerInfo).HandlerFunc(httpTraceHdrs(server.ServerInfoHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodProcInfo).HandlerFunc(httpTraceHdrs(server.GetProcInfoHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodProcInfo).HandlerFunc(httpTraceHdrs(server.GetProcInfoHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodMemInfo).HandlerFunc(httpTraceHdrs(server.GetMemInfoHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodMemInfo).HandlerFunc(httpTraceHdrs(server.GetMemInfoHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodMetrics).HandlerFunc(httpTraceHdrs(server.GetMetricsHandler)).Queries(restQueries(peerRESTTypes)...)
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodMetrics).HandlerFunc(httpTraceHdrs(server.GetMetricsHandler)).Queries(restQueries(peerRESTMetricsTypes)...)
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysErrors).HandlerFunc(httpTraceHdrs(server.GetSysErrorsHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysErrors).HandlerFunc(httpTraceHdrs(server.GetSysErrorsHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysServices).HandlerFunc(httpTraceHdrs(server.GetSysServicesHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysServices).HandlerFunc(httpTraceHdrs(server.GetSysServicesHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysConfig).HandlerFunc(httpTraceHdrs(server.GetSysConfigHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysConfig).HandlerFunc(httpTraceHdrs(server.GetSysConfigHandler))
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ func configureServerHandler(endpointServerPools EndpointServerPools) (http.Handl
|
|||||||
// Add STS router always.
|
// Add STS router always.
|
||||||
registerSTSRouter(router)
|
registerSTSRouter(router)
|
||||||
|
|
||||||
|
// Add KMS router
|
||||||
|
registerKMSRouter(router)
|
||||||
|
|
||||||
// Add API router
|
// Add API router
|
||||||
registerAPIRouter(router)
|
registerAPIRouter(router)
|
||||||
|
|
||||||
|
|||||||
+29
-7
@@ -29,6 +29,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/bucket/policy"
|
||||||
@@ -122,6 +123,17 @@ func (api objectAPIHandlers) getObjectInArchiveFileHandler(ctx context.Context,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not allow offsetting into extracted files.
|
||||||
|
if opts.PartNumber != 0 {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartNumber), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Header.Get(xhttp.Range) != "" {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRange), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Validate pre-conditions if any.
|
// Validate pre-conditions if any.
|
||||||
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
@@ -192,6 +204,8 @@ func (api objectAPIHandlers) getObjectInArchiveFileHandler(ctx context.Context,
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// s3zip does not allow ranges
|
||||||
|
w.Header().Del(xhttp.AcceptRanges)
|
||||||
|
|
||||||
setHeadGetRespHeaders(w, r.Form)
|
setHeadGetRespHeaders(w, r.Form)
|
||||||
|
|
||||||
@@ -410,13 +424,22 @@ func (api objectAPIHandlers) headObjectInArchiveFileHandler(ctx context.Context,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var rs *HTTPRangeSpec
|
|
||||||
|
|
||||||
// Validate pre-conditions if any.
|
// Validate pre-conditions if any.
|
||||||
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||||
return checkPreconditions(ctx, w, r, oi, opts)
|
return checkPreconditions(ctx, w, r, oi, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not allow offsetting into extracted files.
|
||||||
|
if opts.PartNumber != 0 {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartNumber), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Header.Get(xhttp.Range) != "" {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRange), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
zipObjInfo, err := getObjectInfo(ctx, bucket, zipPath, opts)
|
zipObjInfo, err := getObjectInfo(ctx, bucket, zipPath, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -455,15 +478,14 @@ func (api objectAPIHandlers) headObjectInArchiveFileHandler(ctx context.Context,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// s3zip does not allow ranges.
|
||||||
|
w.Header().Del(xhttp.AcceptRanges)
|
||||||
|
|
||||||
// Set any additional requested response headers.
|
// Set any additional requested response headers.
|
||||||
setHeadGetRespHeaders(w, r.Form)
|
setHeadGetRespHeaders(w, r.Form)
|
||||||
|
|
||||||
// Successful response.
|
// Successful response.
|
||||||
if rs != nil {
|
w.WriteHeader(http.StatusOK)
|
||||||
w.WriteHeader(http.StatusPartialContent)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the passed zip object metadata with the zip contents info, file name, modtime, size, etc..
|
// Update the passed zip object metadata with the zip contents info, file name, modtime, size, etc..
|
||||||
|
|||||||
+31
-17
@@ -25,6 +25,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -36,6 +37,7 @@ import (
|
|||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||||
"github.com/minio/minio/internal/color"
|
"github.com/minio/minio/internal/color"
|
||||||
@@ -57,10 +59,11 @@ var ServerFlags = []cli.Flag{
|
|||||||
EnvVar: "MINIO_ADDRESS",
|
EnvVar: "MINIO_ADDRESS",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "listeners",
|
Name: "listeners", // Deprecated Oct 2022
|
||||||
Value: 1,
|
Value: 1,
|
||||||
Usage: "bind N number of listeners per ADDRESS:PORT",
|
Usage: "bind N number of listeners per ADDRESS:PORT",
|
||||||
EnvVar: "MINIO_LISTENERS",
|
EnvVar: "MINIO_LISTENERS",
|
||||||
|
Hidden: true,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "console-address",
|
Name: "console-address",
|
||||||
@@ -241,11 +244,11 @@ func serverHandleEnvVars() {
|
|||||||
|
|
||||||
var globalHealStateLK sync.RWMutex
|
var globalHealStateLK sync.RWMutex
|
||||||
|
|
||||||
func initAllSubsystems() {
|
func initAllSubsystems(ctx context.Context) {
|
||||||
globalHealStateLK.Lock()
|
globalHealStateLK.Lock()
|
||||||
// New global heal state
|
// New global heal state
|
||||||
globalAllHealState = newHealState(true)
|
globalAllHealState = newHealState(ctx, true)
|
||||||
globalBackgroundHealState = newHealState(false)
|
globalBackgroundHealState = newHealState(ctx, false)
|
||||||
globalHealStateLK.Unlock()
|
globalHealStateLK.Unlock()
|
||||||
|
|
||||||
// Initialize notification peer targets
|
// Initialize notification peer targets
|
||||||
@@ -263,7 +266,7 @@ func initAllSubsystems() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the bucket bandwidth monitor
|
// Create the bucket bandwidth monitor
|
||||||
globalBucketMonitor = bandwidth.NewMonitor(GlobalContext, totalNodeCount())
|
globalBucketMonitor = bandwidth.NewMonitor(ctx, totalNodeCount())
|
||||||
|
|
||||||
// Create a new config system.
|
// Create a new config system.
|
||||||
globalConfigSys = NewConfigSys()
|
globalConfigSys = NewConfigSys()
|
||||||
@@ -417,6 +420,24 @@ func initConfigSubsystem(ctx context.Context, newObject ObjectLayer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the list of address that MinIO server needs to listen on:
|
||||||
|
// - Returning 127.0.0.1 is necessary so Console will be able to send
|
||||||
|
// requests to the local S3 API.
|
||||||
|
// - The returned List needs to be deduplicated as well.
|
||||||
|
func getServerListenAddrs() []string {
|
||||||
|
// Use a string set to avoid duplication
|
||||||
|
addrs := set.NewStringSet()
|
||||||
|
// Listen on local interface to receive requests from Console
|
||||||
|
for _, ip := range mustGetLocalIPs() {
|
||||||
|
if ip != nil && ip.IsLoopback() {
|
||||||
|
addrs.Add(net.JoinHostPort(ip.String(), globalMinioPort))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the interface specified by the user
|
||||||
|
addrs.Add(globalMinioAddr)
|
||||||
|
return addrs.ToSlice()
|
||||||
|
}
|
||||||
|
|
||||||
// serverMain handler called for 'minio server' command.
|
// serverMain handler called for 'minio server' command.
|
||||||
func serverMain(ctx *cli.Context) {
|
func serverMain(ctx *cli.Context) {
|
||||||
signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
@@ -450,7 +471,7 @@ func serverMain(ctx *cli.Context) {
|
|||||||
initHelp()
|
initHelp()
|
||||||
|
|
||||||
// Initialize all sub-systems
|
// Initialize all sub-systems
|
||||||
initAllSubsystems()
|
initAllSubsystems(GlobalContext)
|
||||||
|
|
||||||
// Is distributed setup, error out if no certificates are found for HTTPS endpoints.
|
// Is distributed setup, error out if no certificates are found for HTTPS endpoints.
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
@@ -499,16 +520,7 @@ func serverMain(ctx *cli.Context) {
|
|||||||
getCert = globalTLSCerts.GetCertificate
|
getCert = globalTLSCerts.GetCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners := ctx.Int("listeners")
|
httpServer := xhttp.NewServer(getServerListenAddrs()).
|
||||||
if listeners == 0 {
|
|
||||||
listeners = 1
|
|
||||||
}
|
|
||||||
addrs := make([]string, 0, listeners)
|
|
||||||
for i := 0; i < listeners; i++ {
|
|
||||||
addrs = append(addrs, globalMinioAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpServer := xhttp.NewServer(addrs).
|
|
||||||
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
||||||
UseTLSConfig(newTLSConfig(getCert)).
|
UseTLSConfig(newTLSConfig(getCert)).
|
||||||
UseShutdownTimeout(ctx.Duration("shutdown-timeout")).
|
UseShutdownTimeout(ctx.Duration("shutdown-timeout")).
|
||||||
@@ -601,6 +613,8 @@ func serverMain(ctx *cli.Context) {
|
|||||||
initBackgroundReplication(GlobalContext, newObject)
|
initBackgroundReplication(GlobalContext, newObject)
|
||||||
initBackgroundTransition(GlobalContext, newObject)
|
initBackgroundTransition(GlobalContext, newObject)
|
||||||
|
|
||||||
|
globalBatchJobPool = newBatchJobPool(GlobalContext, newObject, 100)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := globalTierConfigMgr.Init(GlobalContext, newObject)
|
err := globalTierConfigMgr.Init(GlobalContext, newObject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -694,7 +708,7 @@ func newObjectLayer(ctx context.Context, endpointServerPools EndpointServerPools
|
|||||||
// For FS only, directly use the disk.
|
// For FS only, directly use the disk.
|
||||||
if endpointServerPools.NEndpoints() == 1 {
|
if endpointServerPools.NEndpoints() == 1 {
|
||||||
// Initialize new FS object layer.
|
// Initialize new FS object layer.
|
||||||
newObject, err = NewFSObjectLayer(endpointServerPools[0].Endpoints[0].Path)
|
newObject, err = NewFSObjectLayer(ctx, endpointServerPools[0].Endpoints[0].Path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return newObject, nil
|
return newObject, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ func oldLinux() bool {
|
|||||||
|
|
||||||
func setMaxResources() (err error) {
|
func setMaxResources() (err error) {
|
||||||
// Set the Go runtime max threads threshold to 90% of kernel setting.
|
// Set the Go runtime max threads threshold to 90% of kernel setting.
|
||||||
sysMaxThreads, mErr := sys.GetMaxThreads()
|
sysMaxThreads, err := sys.GetMaxThreads()
|
||||||
if mErr == nil {
|
if err == nil {
|
||||||
minioMaxThreads := (sysMaxThreads * 90) / 100
|
minioMaxThreads := (sysMaxThreads * 90) / 100
|
||||||
// Only set max threads if it is greater than the default one
|
// Only set max threads if it is greater than the default one
|
||||||
if minioMaxThreads > 10000 {
|
if minioMaxThreads > 10000 {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -71,7 +72,10 @@ func TestStripStandardPorts(t *testing.T) {
|
|||||||
|
|
||||||
// Test printing server common message.
|
// Test printing server common message.
|
||||||
func TestPrintServerCommonMessage(t *testing.T) {
|
func TestPrintServerCommonMessage(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -86,7 +90,10 @@ func TestPrintServerCommonMessage(t *testing.T) {
|
|||||||
|
|
||||||
// Tests print cli access message.
|
// Tests print cli access message.
|
||||||
func TestPrintCLIAccessMsg(t *testing.T) {
|
func TestPrintCLIAccessMsg(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -101,7 +108,10 @@ func TestPrintCLIAccessMsg(t *testing.T) {
|
|||||||
|
|
||||||
// Test print startup message.
|
// Test print startup message.
|
||||||
func TestPrintStartupMessage(t *testing.T) {
|
func TestPrintStartupMessage(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -102,6 +103,7 @@ func runAllTests(suite *TestSuiteCommon, c *check) {
|
|||||||
suite.TestContentTypePersists(c)
|
suite.TestContentTypePersists(c)
|
||||||
suite.TestPartialContent(c)
|
suite.TestPartialContent(c)
|
||||||
suite.TestListObjectsHandler(c)
|
suite.TestListObjectsHandler(c)
|
||||||
|
suite.TestListObjectVersionsOutputOrderHandler(c)
|
||||||
suite.TestListObjectsHandlerErrors(c)
|
suite.TestListObjectsHandlerErrors(c)
|
||||||
suite.TestPutBucketErrors(c)
|
suite.TestPutBucketErrors(c)
|
||||||
suite.TestGetObjectLarge10MiB(c)
|
suite.TestGetObjectLarge10MiB(c)
|
||||||
@@ -1636,6 +1638,70 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *check) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestListObjectVersionsHandler - checks the order of <Version>
|
||||||
|
// and <DeleteMarker> XML tags in a version listing
|
||||||
|
func (s *TestSuiteCommon) TestListObjectVersionsOutputOrderHandler(c *check) {
|
||||||
|
// generate a random bucket name.
|
||||||
|
bucketName := getRandomBucketName()
|
||||||
|
// HTTP request to create the bucket.
|
||||||
|
makeBucketRequest, err := newTestSignedRequest(http.MethodPut, getMakeBucketURL(s.endPoint, bucketName),
|
||||||
|
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
// execute the HTTP request to create bucket.
|
||||||
|
response, err := s.client.Do(makeBucketRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
// HTTP request to create the bucket.
|
||||||
|
enableVersioningBody := []byte("<VersioningConfiguration><Status>Enabled</Status></VersioningConfiguration>")
|
||||||
|
enableVersioningBucketRequest, err := newTestSignedRequest(http.MethodPut, getBucketVersioningConfigURL(s.endPoint, bucketName),
|
||||||
|
int64(len(enableVersioningBody)), bytes.NewReader(enableVersioningBody), s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
// execute the HTTP request to create bucket.
|
||||||
|
response, err = s.client.Do(enableVersioningBucketRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
for _, objectName := range []string{"file.1", "file.2"} {
|
||||||
|
buffer := bytes.NewReader([]byte("testcontent"))
|
||||||
|
putRequest, err := newTestSignedRequest(http.MethodPut, getPutObjectURL(s.endPoint, bucketName, objectName),
|
||||||
|
int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
response, err = s.client.Do(putRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
delRequest, err := newTestSignedRequest(http.MethodDelete, getDeleteObjectURL(s.endPoint, bucketName, objectName),
|
||||||
|
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
response, err = s.client.Do(delRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create listObjectsV1 request with valid parameters
|
||||||
|
request, err := newTestSignedRequest(http.MethodGet, getListObjectVersionsURL(s.endPoint, bucketName, "", "1000", ""),
|
||||||
|
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
// execute the HTTP request.
|
||||||
|
response, err = s.client.Do(request)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
getContent, err := io.ReadAll(response.Body)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
|
||||||
|
r := regexp.MustCompile(
|
||||||
|
`<ListVersionsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">.*` +
|
||||||
|
`<DeleteMarker><Key>file.1</Key>.*<IsLatest>true</IsLatest>.*</DeleteMarker>` +
|
||||||
|
`<Version><Key>file.1</Key>.*<IsLatest>false</IsLatest>.*</Version>` +
|
||||||
|
`<DeleteMarker><Key>file.2</Key>.*<IsLatest>true</IsLatest>.*</DeleteMarker>` +
|
||||||
|
`<Version><Key>file.2</Key>.*<IsLatest>false</IsLatest>.*</Version>` +
|
||||||
|
`</ListVersionsResult>`)
|
||||||
|
|
||||||
|
c.Assert(r.MatchString(string(getContent)), true)
|
||||||
|
}
|
||||||
|
|
||||||
// TestListObjectsSpecialCharactersHandler - Setting valid parameters to List Objects
|
// TestListObjectsSpecialCharactersHandler - Setting valid parameters to List Objects
|
||||||
// and then asserting the response with the expected one.
|
// and then asserting the response with the expected one.
|
||||||
func (s *TestSuiteCommon) TestListObjectsSpecialCharactersHandler(c *check) {
|
func (s *TestSuiteCommon) TestListObjectsSpecialCharactersHandler(c *check) {
|
||||||
|
|||||||
+2
-1
@@ -75,7 +75,8 @@ func handleSignals() {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-globalHTTPServerErrorCh:
|
case err := <-globalHTTPServerErrorCh:
|
||||||
|
logger.LogIf(context.Background(), err)
|
||||||
exit(stopProcess())
|
exit(stopProcess())
|
||||||
case osSignal := <-globalOSSignalCh:
|
case osSignal := <-globalOSSignalCh:
|
||||||
if !globalIsGateway {
|
if !globalIsGateway {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -41,7 +42,10 @@ func TestResourceListSorting(t *testing.T) {
|
|||||||
|
|
||||||
// Tests presigned v2 signature.
|
// Tests presigned v2 signature.
|
||||||
func TestDoesPresignedV2SignatureMatch(t *testing.T) {
|
func TestDoesPresignedV2SignatureMatch(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -161,7 +165,10 @@ func TestDoesPresignedV2SignatureMatch(t *testing.T) {
|
|||||||
|
|
||||||
// TestValidateV2AuthHeader - Tests validate the logic of V2 Authorization header validator.
|
// TestValidateV2AuthHeader - Tests validate the logic of V2 Authorization header validator.
|
||||||
func TestValidateV2AuthHeader(t *testing.T) {
|
func TestValidateV2AuthHeader(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -232,7 +239,10 @@ func TestValidateV2AuthHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDoesPolicySignatureV2Match(t *testing.T) {
|
func TestDoesPolicySignatureV2Match(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func TestCheckValid(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS()
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -42,8 +42,7 @@ func TestCheckValid(t *testing.T) {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -93,7 +94,10 @@ func TestDoesPolicySignatureMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDoesPresignedSignatureMatch(t *testing.T) {
|
func TestDoesPresignedSignatureMatch(t *testing.T) {
|
||||||
obj, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
obj, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-38
@@ -577,11 +577,11 @@ func (c *SiteReplicationSys) PeerJoinReq(ctx context.Context, arg madmin.SRPeerJ
|
|||||||
func (c *SiteReplicationSys) GetIDPSettings(ctx context.Context) madmin.IDPSettings {
|
func (c *SiteReplicationSys) GetIDPSettings(ctx context.Context) madmin.IDPSettings {
|
||||||
s := madmin.IDPSettings{}
|
s := madmin.IDPSettings{}
|
||||||
s.LDAP = madmin.LDAPSettings{
|
s.LDAP = madmin.LDAPSettings{
|
||||||
IsLDAPEnabled: globalLDAPConfig.Enabled,
|
IsLDAPEnabled: globalLDAPConfig.Enabled(),
|
||||||
LDAPUserDNSearchBase: globalLDAPConfig.UserDNSearchBaseDistName,
|
LDAPUserDNSearchBase: globalLDAPConfig.LDAP.UserDNSearchBaseDistName,
|
||||||
LDAPUserDNSearchFilter: globalLDAPConfig.UserDNSearchFilter,
|
LDAPUserDNSearchFilter: globalLDAPConfig.LDAP.UserDNSearchFilter,
|
||||||
LDAPGroupSearchBase: globalLDAPConfig.GroupSearchBaseDistName,
|
LDAPGroupSearchBase: globalLDAPConfig.LDAP.GroupSearchBaseDistName,
|
||||||
LDAPGroupSearchFilter: globalLDAPConfig.GroupSearchFilter,
|
LDAPGroupSearchFilter: globalLDAPConfig.LDAP.GroupSearchFilter,
|
||||||
}
|
}
|
||||||
s.OpenID = globalOpenIDConfig.GetSettings()
|
s.OpenID = globalOpenIDConfig.GetSettings()
|
||||||
if s.OpenID.Enabled {
|
if s.OpenID.Enabled {
|
||||||
@@ -1361,7 +1361,7 @@ func (c *SiteReplicationSys) PeerBucketPolicyHandler(ctx context.Context, bucket
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete the bucket policy
|
// Delete the bucket policy
|
||||||
_, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, nil)
|
_, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketPolicyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapSRErr(err)
|
return wrapSRErr(err)
|
||||||
}
|
}
|
||||||
@@ -1391,7 +1391,7 @@ func (c *SiteReplicationSys) PeerBucketTaggingHandler(ctx context.Context, bucke
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete the tags
|
// Delete the tags
|
||||||
_, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, nil)
|
_, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketTaggingConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapSRErr(err)
|
return wrapSRErr(err)
|
||||||
}
|
}
|
||||||
@@ -1445,7 +1445,7 @@ func (c *SiteReplicationSys) PeerBucketSSEConfigHandler(ctx context.Context, buc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete sse config
|
// Delete sse config
|
||||||
_, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, nil)
|
_, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketSSEConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapSRErr(err)
|
return wrapSRErr(err)
|
||||||
}
|
}
|
||||||
@@ -1475,7 +1475,7 @@ func (c *SiteReplicationSys) PeerBucketQuotaConfigHandler(ctx context.Context, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete the bucket policy
|
// Delete the bucket policy
|
||||||
_, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, nil)
|
_, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketQuotaConfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapSRErr(err)
|
return wrapSRErr(err)
|
||||||
}
|
}
|
||||||
@@ -2234,7 +2234,7 @@ func (c *SiteReplicationSys) RemoveRemoteTargetsForEndpoint(ctx context.Context,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := globalBucketMetadataSys.Update(ctx, b.Name, bucketReplicationConfig, nil); err != nil {
|
if _, err := globalBucketMetadataSys.Delete(ctx, b.Name, bucketReplicationConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3341,6 +3341,12 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
|
|||||||
if usrErr != nil {
|
if usrErr != nil {
|
||||||
return info, errSRBackendIssue(usrErr)
|
return info, errSRBackendIssue(usrErr)
|
||||||
}
|
}
|
||||||
|
globalIAMSys.store.rlock()
|
||||||
|
svcErr := globalIAMSys.store.loadMappedPolicies(ctx, svcUser, false, userPolicyMap)
|
||||||
|
globalIAMSys.store.runlock()
|
||||||
|
if svcErr != nil {
|
||||||
|
return info, errSRBackendIssue(svcErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
info.UserPolicies = make(map[string]madmin.SRPolicyMapping, len(userPolicyMap))
|
info.UserPolicies = make(map[string]madmin.SRPolicyMapping, len(userPolicyMap))
|
||||||
for user, mp := range userPolicyMap {
|
for user, mp := range userPolicyMap {
|
||||||
@@ -3357,33 +3363,42 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
|
|||||||
info.UserInfoMap[opts.EntityValue] = ui
|
info.UserInfoMap[opts.EntityValue] = ui
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// get users/group info on local.
|
userAccounts := make(map[string]UserIdentity)
|
||||||
userInfoMap, err := globalIAMSys.ListUsers(ctx)
|
globalIAMSys.store.rlock()
|
||||||
if err != nil {
|
uerr := globalIAMSys.store.loadUsers(ctx, regUser, userAccounts)
|
||||||
return info, errSRBackendIssue(err)
|
globalIAMSys.store.runlock()
|
||||||
|
if uerr != nil {
|
||||||
|
return info, errSRBackendIssue(uerr)
|
||||||
}
|
}
|
||||||
for user, ui := range userInfoMap {
|
|
||||||
info.UserInfoMap[user] = ui
|
globalIAMSys.store.rlock()
|
||||||
svcAccts, err := globalIAMSys.ListServiceAccounts(ctx, user)
|
serr := globalIAMSys.store.loadUsers(ctx, svcUser, userAccounts)
|
||||||
if err != nil {
|
globalIAMSys.store.runlock()
|
||||||
return info, errSRBackendIssue(err)
|
if serr != nil {
|
||||||
|
return info, errSRBackendIssue(serr)
|
||||||
|
}
|
||||||
|
|
||||||
|
globalIAMSys.store.rlock()
|
||||||
|
terr := globalIAMSys.store.loadUsers(ctx, stsUser, userAccounts)
|
||||||
|
globalIAMSys.store.runlock()
|
||||||
|
if terr != nil {
|
||||||
|
return info, errSRBackendIssue(terr)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range userAccounts {
|
||||||
|
if k == siteReplicatorSvcAcc {
|
||||||
|
// skip the site replicate svc account as it is
|
||||||
|
// already replicated.
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
for _, svcAcct := range svcAccts {
|
|
||||||
// report all non-root user accounts for syncing
|
if v.Credentials.ParentUser != "" && v.Credentials.ParentUser == globalActiveCred.AccessKey {
|
||||||
if svcAcct.ParentUser != "" && svcAcct.ParentUser != globalActiveCred.AccessKey {
|
// skip all root user service accounts.
|
||||||
info.UserInfoMap[svcAcct.AccessKey] = madmin.UserInfo{
|
continue
|
||||||
Status: madmin.AccountStatus(svcAcct.Status),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tempAccts, err := globalIAMSys.ListTempAccounts(ctx, user)
|
|
||||||
if err != nil {
|
info.UserInfoMap[k] = madmin.UserInfo{
|
||||||
return info, errSRBackendIssue(err)
|
Status: madmin.AccountStatus(v.Credentials.Status),
|
||||||
}
|
|
||||||
for _, tempAcct := range tempAccts {
|
|
||||||
info.UserInfoMap[tempAcct.Credentials.AccessKey] = madmin.UserInfo{
|
|
||||||
Status: madmin.AccountStatus(tempAcct.Credentials.Status),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3392,7 +3407,7 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
|
|||||||
if opts.Groups || opts.Entity == madmin.SRGroupEntity {
|
if opts.Groups || opts.Entity == madmin.SRGroupEntity {
|
||||||
// Replicate policy mappings on local to all peers.
|
// Replicate policy mappings on local to all peers.
|
||||||
groupPolicyMap := make(map[string]MappedPolicy)
|
groupPolicyMap := make(map[string]MappedPolicy)
|
||||||
if opts.Entity == madmin.SRUserEntity {
|
if opts.Entity == madmin.SRGroupEntity {
|
||||||
if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, true); ok {
|
if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, true); ok {
|
||||||
groupPolicyMap[opts.EntityValue] = mp
|
groupPolicyMap[opts.EntityValue] = mp
|
||||||
}
|
}
|
||||||
@@ -4387,7 +4402,6 @@ func (c *SiteReplicationSys) healIAMSystem(ctx context.Context, objAPI ObjectLay
|
|||||||
for policy := range info.PolicyStats {
|
for policy := range info.PolicyStats {
|
||||||
c.healPolicies(ctx, objAPI, policy, info)
|
c.healPolicies(ctx, objAPI, policy, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
for user := range info.UserStats {
|
for user := range info.UserStats {
|
||||||
c.healUsers(ctx, objAPI, user, info)
|
c.healUsers(ctx, objAPI, user, info)
|
||||||
}
|
}
|
||||||
@@ -4581,7 +4595,8 @@ func (c *SiteReplicationSys) healGroupPolicies(ctx context.Context, objAPI Objec
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// heal user accounts of local users that are present on this site, provided current cluster has the most recent update.
|
// heal all users and their service accounts that are present on this site,
|
||||||
|
// provided current cluster has the most recent update.
|
||||||
func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer, user string, info srStatusInfo) error {
|
func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer, user string, info srStatusInfo) error {
|
||||||
// create user if missing; fix user policy mapping if missing
|
// create user if missing; fix user policy mapping if missing
|
||||||
us := info.UserStats[user]
|
us := info.UserStats[user]
|
||||||
@@ -4632,7 +4647,6 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
creds := u.Credentials
|
creds := u.Credentials
|
||||||
// heal only the user accounts that are local users
|
|
||||||
if creds.IsServiceAccount() {
|
if creds.IsServiceAccount() {
|
||||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
|
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -48,7 +49,10 @@ func isNetworkError(err error) bool {
|
|||||||
if nerr, ok := err.(*rest.NetworkError); ok {
|
if nerr, ok := err.(*rest.NetworkError); ok {
|
||||||
return xnet.IsNetworkOrHostDown(nerr.Err, false)
|
return xnet.IsNetworkOrHostDown(nerr.Err, false)
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// A peer node can be in shut down phase and proactively
|
||||||
|
// return 503 server closed error,consider it as an offline node
|
||||||
|
return err.Error() == http.ErrServerClosed.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts network error to storageErr. This function is
|
// Converts network error to storageErr. This function is
|
||||||
|
|||||||
@@ -1052,6 +1052,13 @@ var poolBuf8k = sync.Pool{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var poolBuf128k = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
b := make([]byte, 128<<10)
|
||||||
|
return b
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// waitForHTTPStream will wait for responses where
|
// waitForHTTPStream will wait for responses where
|
||||||
// streamHTTPResponse has been used.
|
// streamHTTPResponse has been used.
|
||||||
// The returned reader contains the payload and must be closed if no error is returned.
|
// The returned reader contains the payload and must be closed if no error is returned.
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ func (s *TestSuiteIAM) TestSTSWithTags(c *check) {
|
|||||||
// Validate sts creds can access the object
|
// Validate sts creds can access the object
|
||||||
c.mustPutObjectWithTags(ctx, uClient, bucket, object)
|
c.mustPutObjectWithTags(ctx, uClient, bucket, object)
|
||||||
c.mustGetObject(ctx, uClient, bucket, object)
|
c.mustGetObject(ctx, uClient, bucket, object)
|
||||||
|
c.mustHeadObject(ctx, uClient, bucket, object, 2)
|
||||||
|
|
||||||
// Validate that the client can remove objects
|
// Validate that the client can remove objects
|
||||||
if err = minioClient.RemoveObjectTagging(ctx, bucket, object, minio.RemoveObjectTaggingOptions{}); err != nil {
|
if err = minioClient.RemoveObjectTagging(ctx, bucket, object, minio.RemoveObjectTaggingOptions{}); err != nil {
|
||||||
|
|||||||
+34
-15
@@ -184,7 +184,7 @@ func calculateStreamContentLength(dataLen, chunkSize int64) int64 {
|
|||||||
return streamLen
|
return streamLen
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareFS() (ObjectLayer, string, error) {
|
func prepareFS(ctx context.Context) (ObjectLayer, string, error) {
|
||||||
nDisks := 1
|
nDisks := 1
|
||||||
fsDirs, err := getRandomDisks(nDisks)
|
fsDirs, err := getRandomDisks(nDisks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,9 +195,9 @@ func prepareFS() (ObjectLayer, string, error) {
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
globalIAMSys.Init(context.Background(), obj, globalEtcdClient, 2*time.Second)
|
globalIAMSys.Init(ctx, obj, globalEtcdClient, 2*time.Second)
|
||||||
return obj, fsDirs[0], nil
|
return obj, fsDirs[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ func initFSObjects(disk string, t *testing.T) (obj ObjectLayer) {
|
|||||||
|
|
||||||
newTestConfig(globalMinioDefaultRegion, obj)
|
newTestConfig(globalMinioDefaultRegion, obj)
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(GlobalContext)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ func initTestServerWithBackend(ctx context.Context, t TestErrHandler, testServer
|
|||||||
globalMinioPort = port
|
globalMinioPort = port
|
||||||
globalMinioAddr = getEndpointsLocalAddr(testServer.Disks)
|
globalMinioAddr = getEndpointsLocalAddr(testServer.Disks)
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
globalEtcdClient = nil
|
globalEtcdClient = nil
|
||||||
|
|
||||||
@@ -446,7 +446,7 @@ func resetGlobalIsErasure() {
|
|||||||
func resetGlobalHealState() {
|
func resetGlobalHealState() {
|
||||||
// Init global heal state
|
// Init global heal state
|
||||||
if globalAllHealState == nil {
|
if globalAllHealState == nil {
|
||||||
globalAllHealState = newHealState(false)
|
globalAllHealState = newHealState(GlobalContext, false)
|
||||||
} else {
|
} else {
|
||||||
globalAllHealState.Lock()
|
globalAllHealState.Lock()
|
||||||
for _, v := range globalAllHealState.healSeqMap {
|
for _, v := range globalAllHealState.healSeqMap {
|
||||||
@@ -459,7 +459,7 @@ func resetGlobalHealState() {
|
|||||||
|
|
||||||
// Init background heal state
|
// Init background heal state
|
||||||
if globalBackgroundHealState == nil {
|
if globalBackgroundHealState == nil {
|
||||||
globalBackgroundHealState = newHealState(false)
|
globalBackgroundHealState = newHealState(GlobalContext, false)
|
||||||
} else {
|
} else {
|
||||||
globalBackgroundHealState.Lock()
|
globalBackgroundHealState.Lock()
|
||||||
for _, v := range globalBackgroundHealState.healSeqMap {
|
for _, v := range globalBackgroundHealState.healSeqMap {
|
||||||
@@ -1321,6 +1321,13 @@ func getMakeBucketURL(endPoint, bucketName string) string {
|
|||||||
return makeTestTargetURL(endPoint, bucketName, "", url.Values{})
|
return makeTestTargetURL(endPoint, bucketName, "", url.Values{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return URL for creating the bucket.
|
||||||
|
func getBucketVersioningConfigURL(endPoint, bucketName string) string {
|
||||||
|
vals := make(url.Values)
|
||||||
|
vals.Set("versioning", "")
|
||||||
|
return makeTestTargetURL(endPoint, bucketName, "", vals)
|
||||||
|
}
|
||||||
|
|
||||||
// return URL for listing buckets.
|
// return URL for listing buckets.
|
||||||
func getListBucketURL(endPoint string) string {
|
func getListBucketURL(endPoint string) string {
|
||||||
return makeTestTargetURL(endPoint, "", "", url.Values{})
|
return makeTestTargetURL(endPoint, "", "", url.Values{})
|
||||||
@@ -1369,6 +1376,19 @@ func getListObjectsV1URL(endPoint, bucketName, prefix, maxKeys, encodingType str
|
|||||||
return makeTestTargetURL(endPoint, bucketName, prefix, queryValue)
|
return makeTestTargetURL(endPoint, bucketName, prefix, queryValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return URL for listing objects in the bucket with V1 legacy API.
|
||||||
|
func getListObjectVersionsURL(endPoint, bucketName, prefix, maxKeys, encodingType string) string {
|
||||||
|
queryValue := url.Values{}
|
||||||
|
if maxKeys != "" {
|
||||||
|
queryValue.Set("max-keys", maxKeys)
|
||||||
|
}
|
||||||
|
if encodingType != "" {
|
||||||
|
queryValue.Set("encoding-type", encodingType)
|
||||||
|
}
|
||||||
|
queryValue.Set("versions", "")
|
||||||
|
return makeTestTargetURL(endPoint, bucketName, prefix, queryValue)
|
||||||
|
}
|
||||||
|
|
||||||
// return URL for listing objects in the bucket with V2 API.
|
// return URL for listing objects in the bucket with V2 API.
|
||||||
func getListObjectsV2URL(endPoint, bucketName, prefix, maxKeys, fetchOwner, encodingType string) string {
|
func getListObjectsV2URL(endPoint, bucketName, prefix, maxKeys, fetchOwner, encodingType string) string {
|
||||||
queryValue := url.Values{}
|
queryValue := url.Values{}
|
||||||
@@ -1472,7 +1492,7 @@ func getRandomDisks(N int) ([]string, error) {
|
|||||||
|
|
||||||
// Initialize object layer with the supplied disks, objectLayer is nil upon any error.
|
// Initialize object layer with the supplied disks, objectLayer is nil upon any error.
|
||||||
func newTestObjectLayer(ctx context.Context, endpointServerPools EndpointServerPools) (newObject ObjectLayer, err error) {
|
func newTestObjectLayer(ctx context.Context, endpointServerPools EndpointServerPools) (newObject ObjectLayer, err error) {
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
return newErasureServerPools(ctx, endpointServerPools)
|
return newErasureServerPools(ctx, endpointServerPools)
|
||||||
}
|
}
|
||||||
@@ -1516,7 +1536,7 @@ func removeDiskN(disks []string, n int) {
|
|||||||
// initialies the root and returns its path.
|
// initialies the root and returns its path.
|
||||||
// return credentials.
|
// return credentials.
|
||||||
func initAPIHandlerTest(ctx context.Context, obj ObjectLayer, endpoints []string) (string, http.Handler, error) {
|
func initAPIHandlerTest(ctx context.Context, obj ObjectLayer, endpoints []string) (string, http.Handler, error) {
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, obj)
|
initConfigSubsystem(ctx, obj)
|
||||||
|
|
||||||
@@ -1554,7 +1574,7 @@ func prepareTestBackend(ctx context.Context, instanceType string) (ObjectLayer,
|
|||||||
return prepareErasure16(ctx)
|
return prepareErasure16(ctx)
|
||||||
default:
|
default:
|
||||||
// return FS backend by default.
|
// return FS backend by default.
|
||||||
obj, disk, err := prepareFS()
|
obj, disk, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -1726,7 +1746,7 @@ func ExecObjectLayerAPITest(t *testing.T, objAPITest objAPITestType, endpoints [
|
|||||||
// this is to make sure that the tests are not affected by modified value.
|
// this is to make sure that the tests are not affected by modified value.
|
||||||
resetTestGlobals()
|
resetTestGlobals()
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS()
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Initialization of object layer failed for single node setup: %s", err)
|
t.Fatalf("Initialization of object layer failed for single node setup: %s", err)
|
||||||
}
|
}
|
||||||
@@ -1805,13 +1825,12 @@ func ExecObjectLayerTest(t TestErrHandler, objTest objTestType) {
|
|||||||
localMetacacheMgr.deleteAll()
|
localMetacacheMgr.deleteAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS()
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Initialization of object layer failed for single node setup: %s", err)
|
t.Fatalf("Initialization of object layer failed for single node setup: %s", err)
|
||||||
}
|
}
|
||||||
setObjectLayer(objLayer)
|
setObjectLayer(objLayer)
|
||||||
|
initAllSubsystems(ctx)
|
||||||
initAllSubsystems()
|
|
||||||
|
|
||||||
// initialize the server and obtain the credentials and root.
|
// initialize the server and obtain the credentials and root.
|
||||||
// credentials are necessary to sign the HTTP request.
|
// credentials are necessary to sign the HTTP request.
|
||||||
@@ -1837,7 +1856,7 @@ func ExecObjectLayerTest(t TestErrHandler, objTest objTestType) {
|
|||||||
localMetacacheMgr.deleteAll()
|
localMetacacheMgr.deleteAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
objLayer, fsDirs, err := prepareErasureSets32(ctx)
|
objLayer, fsDirs, err := prepareErasureSets32(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Initialization of object layer failed for Erasure setup: %s", err)
|
t.Fatalf("Initialization of object layer failed for Erasure setup: %s", err)
|
||||||
|
|||||||
+108
-12
@@ -22,16 +22,20 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/cosnicolaou/pbzip2"
|
"github.com/cosnicolaou/pbzip2"
|
||||||
"github.com/klauspost/compress/s2"
|
"github.com/klauspost/compress/s2"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
gzip "github.com/klauspost/pgzip"
|
gzip "github.com/klauspost/pgzip"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/pierrec/lz4"
|
"github.com/pierrec/lz4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +101,36 @@ var magicHeaders = []struct {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func untar(r io.Reader, putObject func(reader io.Reader, info os.FileInfo, name string) error) error {
|
type untarOptions struct {
|
||||||
|
ignoreDirs bool
|
||||||
|
ignoreErrs bool
|
||||||
|
prefixAll string
|
||||||
|
}
|
||||||
|
|
||||||
|
// disconnectReader will ensure that no reads can take place on
|
||||||
|
// the upstream reader after close has been called.
|
||||||
|
type disconnectReader struct {
|
||||||
|
r io.Reader
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *disconnectReader) Read(p []byte) (n int, err error) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
if d.r != nil {
|
||||||
|
return d.r.Read(p)
|
||||||
|
}
|
||||||
|
return 0, errors.New("reader closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *disconnectReader) Close() error {
|
||||||
|
d.mu.Lock()
|
||||||
|
d.r = nil
|
||||||
|
d.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func untar(ctx context.Context, r io.Reader, putObject func(reader io.Reader, info os.FileInfo, name string) error, o untarOptions) error {
|
||||||
bf := bufio.NewReader(r)
|
bf := bufio.NewReader(r)
|
||||||
switch f := detect(bf); f {
|
switch f := detect(bf); f {
|
||||||
case formatGzip:
|
case formatGzip:
|
||||||
@@ -117,7 +150,7 @@ func untar(r io.Reader, putObject func(reader io.Reader, info os.FileInfo, name
|
|||||||
defer dec.Close()
|
defer dec.Close()
|
||||||
r = dec
|
r = dec
|
||||||
case formatBZ2:
|
case formatBZ2:
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
r = pbzip2.NewReader(ctx, bf, pbzip2.DecompressionOptions(
|
r = pbzip2.NewReader(ctx, bf, pbzip2.DecompressionOptions(
|
||||||
pbzip2.BZConcurrency((runtime.GOMAXPROCS(0)+1)/2),
|
pbzip2.BZConcurrency((runtime.GOMAXPROCS(0)+1)/2),
|
||||||
@@ -131,17 +164,32 @@ func untar(r io.Reader, putObject func(reader io.Reader, info os.FileInfo, name
|
|||||||
}
|
}
|
||||||
tarReader := tar.NewReader(r)
|
tarReader := tar.NewReader(r)
|
||||||
n := 0
|
n := 0
|
||||||
for {
|
asyncWriters := make(chan struct{}, 16)
|
||||||
header, err := tarReader.Next()
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
var asyncErr error
|
||||||
|
var asyncErrMu sync.Mutex
|
||||||
|
for {
|
||||||
|
if !o.ignoreErrs {
|
||||||
|
asyncErrMu.Lock()
|
||||||
|
err := asyncErr
|
||||||
|
asyncErrMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := tarReader.Next()
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
// if no more files are found return
|
// if no more files are found return
|
||||||
case err == io.EOF:
|
case err == io.EOF:
|
||||||
return nil
|
wg.Wait()
|
||||||
|
return asyncErr
|
||||||
|
|
||||||
// return any other error
|
// return any other error
|
||||||
case err != nil:
|
case err != nil:
|
||||||
|
wg.Wait()
|
||||||
extra := ""
|
extra := ""
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
extra = fmt.Sprintf(" after %d successful object(s)", n)
|
extra = fmt.Sprintf(" after %d successful object(s)", n)
|
||||||
@@ -160,18 +208,66 @@ func untar(r io.Reader, putObject func(reader io.Reader, info os.FileInfo, name
|
|||||||
|
|
||||||
switch header.Typeflag {
|
switch header.Typeflag {
|
||||||
case tar.TypeDir: // = directory
|
case tar.TypeDir: // = directory
|
||||||
if err := putObject(tarReader, header.FileInfo(), trimLeadingSlash(pathJoin(name, slashSeparator))); err != nil {
|
if o.ignoreDirs {
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
n++
|
name = trimLeadingSlash(pathJoin(name, slashSeparator))
|
||||||
case tar.TypeReg, tar.TypeChar, tar.TypeBlock, tar.TypeFifo, tar.TypeGNUSparse: // = regular
|
case tar.TypeReg, tar.TypeChar, tar.TypeBlock, tar.TypeFifo, tar.TypeGNUSparse: // = regular
|
||||||
if err := putObject(tarReader, header.FileInfo(), trimLeadingSlash(path.Clean(name))); err != nil {
|
name = trimLeadingSlash(path.Clean(name))
|
||||||
return err
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
default:
|
default:
|
||||||
// ignore symlink'ed
|
// ignore symlink'ed
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if o.prefixAll != "" {
|
||||||
|
name = pathJoin(o.prefixAll, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do small files async
|
||||||
|
n++
|
||||||
|
if header.Size <= smallFileThreshold {
|
||||||
|
asyncWriters <- struct{}{}
|
||||||
|
b := poolBuf128k.Get().([]byte)
|
||||||
|
if cap(b) < int(header.Size) {
|
||||||
|
b = make([]byte, smallFileThreshold)
|
||||||
|
}
|
||||||
|
b = b[:header.Size]
|
||||||
|
if _, err := io.ReadFull(tarReader, b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func(name string, fi fs.FileInfo, b []byte) {
|
||||||
|
rc := disconnectReader{r: bytes.NewReader(b)}
|
||||||
|
defer func() {
|
||||||
|
rc.Close()
|
||||||
|
<-asyncWriters
|
||||||
|
wg.Done()
|
||||||
|
poolBuf128k.Put(b)
|
||||||
|
}()
|
||||||
|
if err := putObject(&rc, fi, name); err != nil {
|
||||||
|
if o.ignoreErrs {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
asyncErrMu.Lock()
|
||||||
|
if asyncErr == nil {
|
||||||
|
asyncErr = err
|
||||||
|
}
|
||||||
|
asyncErrMu.Unlock()
|
||||||
|
}
|
||||||
|
}(name, header.FileInfo(), b)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync upload.
|
||||||
|
rc := disconnectReader{r: tarReader}
|
||||||
|
if err := putObject(&rc, header.FileInfo(), name); err != nil {
|
||||||
|
rc.Close()
|
||||||
|
if o.ignoreErrs {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rc.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-41
@@ -124,40 +124,31 @@ func GetCurrentReleaseTime() (releaseTime time.Time, err error) {
|
|||||||
//
|
//
|
||||||
// "/.dockerenv": "file",
|
// "/.dockerenv": "file",
|
||||||
func IsDocker() bool {
|
func IsDocker() bool {
|
||||||
if !globalIsCICD {
|
_, err := os.Stat("/.dockerenv")
|
||||||
_, err := os.Stat("/.dockerenv")
|
if osIsNotExist(err) {
|
||||||
if osIsNotExist(err) {
|
return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log error, as we will not propagate it to caller
|
|
||||||
logger.LogIf(GlobalContext, err)
|
|
||||||
|
|
||||||
return err == nil
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// Log error, as we will not propagate it to caller
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
|
||||||
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDCOS returns true if minio is running in DCOS.
|
// IsDCOS returns true if minio is running in DCOS.
|
||||||
func IsDCOS() bool {
|
func IsDCOS() bool {
|
||||||
if !globalIsCICD {
|
// http://mesos.apache.org/documentation/latest/docker-containerizer/
|
||||||
// http://mesos.apache.org/documentation/latest/docker-containerizer/
|
// Mesos docker containerizer sets this value
|
||||||
// Mesos docker containerizer sets this value
|
return env.Get("MESOS_CONTAINER_NAME", "") != ""
|
||||||
return env.Get("MESOS_CONTAINER_NAME", "") != ""
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsKubernetes returns true if minio is running in kubernetes.
|
// IsKubernetes returns true if minio is running in kubernetes.
|
||||||
func IsKubernetes() bool {
|
func IsKubernetes() bool {
|
||||||
if !globalIsCICD {
|
// Kubernetes env used to validate if we are
|
||||||
// Kubernetes env used to validate if we are
|
// indeed running inside a kubernetes pod
|
||||||
// indeed running inside a kubernetes pod
|
// is KUBERNETES_SERVICE_HOST
|
||||||
// is KUBERNETES_SERVICE_HOST
|
// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_pods.go#L541
|
||||||
// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_pods.go#L541
|
return env.Get("KUBERNETES_SERVICE_HOST", "") != ""
|
||||||
return env.Get("KUBERNETES_SERVICE_HOST", "") != ""
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsBOSH returns true if minio is deployed from a bosh package
|
// IsBOSH returns true if minio is deployed from a bosh package
|
||||||
@@ -457,18 +448,6 @@ func getDownloadURL(releaseTag string) (downloadURL string) {
|
|||||||
return minioReleaseURL + "minio"
|
return minioReleaseURL + "minio"
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUpdateReaderFromFile(u *url.URL) (io.ReadCloser, error) {
|
|
||||||
r, err := os.Open(u.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, AdminError{
|
|
||||||
Code: AdminUpdateUnexpectedFailure,
|
|
||||||
Message: err.Error(),
|
|
||||||
StatusCode: http.StatusInternalServerError,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUpdateReaderFromURL(u *url.URL, transport http.RoundTripper, mode string) (io.ReadCloser, error) {
|
func getUpdateReaderFromURL(u *url.URL, transport http.RoundTripper, mode string) (io.ReadCloser, error) {
|
||||||
clnt := &http.Client{
|
clnt := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
@@ -514,10 +493,7 @@ func downloadBinary(u *url.URL, mode string) (readerReturn []byte, err error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reader, err = getUpdateReaderFromFile(u)
|
return nil, fmt.Errorf("unsupported protocol scheme: %s", u.Scheme)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert a Reader to bytes
|
// convert a Reader to bytes
|
||||||
|
|||||||
+18
-8
@@ -1026,28 +1026,38 @@ type AuditLogOptions struct {
|
|||||||
Event string
|
Event string
|
||||||
APIName string
|
APIName string
|
||||||
Status string
|
Status string
|
||||||
|
Bucket string
|
||||||
|
Object string
|
||||||
VersionID string
|
VersionID string
|
||||||
Error string
|
Error string
|
||||||
|
Tags map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sends audit logs for internal subsystem activity
|
// sends audit logs for internal subsystem activity
|
||||||
func auditLogInternal(ctx context.Context, bucket, object string, opts AuditLogOptions) {
|
func auditLogInternal(ctx context.Context, opts AuditLogOptions) {
|
||||||
|
if len(logger.AuditTargets()) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
entry := audit.NewEntry(globalDeploymentID)
|
entry := audit.NewEntry(globalDeploymentID)
|
||||||
entry.Trigger = opts.Event
|
entry.Trigger = opts.Event
|
||||||
entry.Event = opts.Event
|
entry.Event = opts.Event
|
||||||
entry.Error = opts.Error
|
entry.Error = opts.Error
|
||||||
entry.API.Name = opts.APIName
|
entry.API.Name = opts.APIName
|
||||||
entry.API.Bucket = bucket
|
entry.API.Bucket = opts.Bucket
|
||||||
entry.API.Object = object
|
entry.API.Objects = []audit.ObjectVersion{{ObjectName: opts.Object, VersionID: opts.VersionID}}
|
||||||
if opts.VersionID != "" {
|
|
||||||
entry.ReqQuery = make(map[string]string)
|
|
||||||
entry.ReqQuery[xhttp.VersionID] = opts.VersionID
|
|
||||||
}
|
|
||||||
entry.API.Status = opts.Status
|
entry.API.Status = opts.Status
|
||||||
|
entry.Tags = opts.Tags
|
||||||
// Merge tag information if found - this is currently needed for tags
|
// Merge tag information if found - this is currently needed for tags
|
||||||
// set during decommissioning.
|
// set during decommissioning.
|
||||||
if reqInfo := logger.GetReqInfo(ctx); reqInfo != nil {
|
if reqInfo := logger.GetReqInfo(ctx); reqInfo != nil {
|
||||||
entry.Tags = reqInfo.GetTagsMap()
|
if tags := reqInfo.GetTagsMap(); len(tags) > 0 {
|
||||||
|
if entry.Tags == nil {
|
||||||
|
entry.Tags = make(map[string]interface{}, len(tags))
|
||||||
|
}
|
||||||
|
for k, v := range tags {
|
||||||
|
entry.Tags[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx = logger.SetAuditEntry(ctx, &entry)
|
ctx = logger.SetAuditEntry(ctx, &entry)
|
||||||
logger.AuditLog(ctx, nil, nil, nil)
|
logger.AuditLog(ctx, nil, nil, nil)
|
||||||
|
|||||||
@@ -788,7 +788,8 @@ func (p *xlStorageDiskIDCheck) monitorDiskStatus() {
|
|||||||
Force: false,
|
Force: false,
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.Info("Able to read+write, bringing drive %s online.", p.storage.String())
|
logger.Info("Able to read+write+delete, bringing drive %s online. Drive was offline for %s.", p.storage.String(),
|
||||||
|
time.Since(time.Unix(0, atomic.LoadInt64(&p.health.lastSuccess))))
|
||||||
atomic.StoreInt32(&p.health.status, diskHealthOK)
|
atomic.StoreInt32(&p.health.status, diskHealthOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -899,6 +899,7 @@ func (s *xlStorage) ListDir(ctx context.Context, volume, dirPath string, count i
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis ...FileInfo) error {
|
func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis ...FileInfo) error {
|
||||||
|
var legacyJSON bool
|
||||||
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != errFileNotFound {
|
if err != errFileNotFound {
|
||||||
@@ -910,6 +911,7 @@ func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
legacyJSON = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
@@ -921,14 +923,14 @@ func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isXL2V1Format(buf) {
|
if legacyJSON {
|
||||||
// Delete the meta file, if there are no more versions the
|
// Delete the meta file, if there are no more versions the
|
||||||
// top level parent is automatically removed.
|
// top level parent is automatically removed.
|
||||||
return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true, false)
|
return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var xlMeta xlMetaV2
|
var xlMeta xlMetaV2
|
||||||
if err = xlMeta.Load(buf); err != nil {
|
if err = xlMeta.LoadOrConvert(buf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1038,6 +1040,7 @@ func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi F
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var legacyJSON bool
|
||||||
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != errFileNotFound {
|
if err != errFileNotFound {
|
||||||
@@ -1056,6 +1059,7 @@ func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi F
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
legacyJSON = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
@@ -1070,14 +1074,14 @@ func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi F
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isXL2V1Format(buf) {
|
if legacyJSON {
|
||||||
// Delete the meta file, if there are no more versions the
|
// Delete the meta file, if there are no more versions the
|
||||||
// top level parent is automatically removed.
|
// top level parent is automatically removed.
|
||||||
return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true, false)
|
return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var xlMeta xlMetaV2
|
var xlMeta xlMetaV2
|
||||||
if err := xlMeta.Load(buf); err != nil {
|
if err = xlMeta.LoadOrConvert(buf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
# MinIO Batch Job
|
||||||
|
MinIO Batch jobs is an MinIO object management feature that lets you manage objects at scale. Jobs currently supported by MinIO
|
||||||
|
|
||||||
|
- Replicate objects between buckets on multiple sites
|
||||||
|
|
||||||
|
Upcoming Jobs
|
||||||
|
|
||||||
|
- Copy objects from NAS to MinIO
|
||||||
|
- Copy objects from HDFS to MinIO
|
||||||
|
|
||||||
|
## Replication Job
|
||||||
|
To perform replication via batch jobs, you create a job. The job consists of a job description YAML that describes
|
||||||
|
|
||||||
|
- Source location from where the objects must be copied from
|
||||||
|
- Target location from where the objects must be copied to
|
||||||
|
- Fine grained filtering is available to pick relevant objects from source to copy from
|
||||||
|
|
||||||
|
MinIO batch jobs framework also provides
|
||||||
|
|
||||||
|
- Retrying a failed job automatically driven by user input
|
||||||
|
- Monitoring job progress in real-time
|
||||||
|
- Send notifications upon completion or failure to user configured target
|
||||||
|
|
||||||
|
Following YAML describes the structure of a replication job, each value is documented and self-describing.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
replicate:
|
||||||
|
apiVersion: v1
|
||||||
|
# source of the objects to be replicated
|
||||||
|
source:
|
||||||
|
type: TYPE # valid values are "minio"
|
||||||
|
bucket: BUCKET
|
||||||
|
prefix: PREFIX
|
||||||
|
# NOTE: if source is remote then target must be "local"
|
||||||
|
# endpoint: ENDPOINT
|
||||||
|
# credentials:
|
||||||
|
# accessKey: ACCESS-KEY
|
||||||
|
# secretKey: SECRET-KEY
|
||||||
|
# sessionToken: SESSION-TOKEN # Available when rotating credentials are used
|
||||||
|
|
||||||
|
# target where the objects must be replicated
|
||||||
|
target:
|
||||||
|
type: TYPE # valid values are "minio"
|
||||||
|
bucket: BUCKET
|
||||||
|
prefix: PREFIX
|
||||||
|
# NOTE: if target is remote then source must be "local"
|
||||||
|
# endpoint: ENDPOINT
|
||||||
|
# credentials:
|
||||||
|
# accessKey: ACCESS-KEY
|
||||||
|
# secretKey: SECRET-KEY
|
||||||
|
# sessionToken: SESSION-TOKEN # Available when rotating credentials are used
|
||||||
|
|
||||||
|
# optional flags based filtering criteria
|
||||||
|
# for all source objects
|
||||||
|
flags:
|
||||||
|
filter:
|
||||||
|
newerThan: "7d" # match objects newer than this value (e.g. 7d10h31s)
|
||||||
|
olderThan: "7d" # match objects older than this value (e.g. 7d10h31s)
|
||||||
|
createdAfter: "date" # match objects created after "date"
|
||||||
|
createdBefore: "date" # match objects created before "date"
|
||||||
|
|
||||||
|
## NOTE: tags are not supported when "source" is remote.
|
||||||
|
# tags:
|
||||||
|
# - key: "name"
|
||||||
|
# value: "pick*" # match objects with tag 'name', with all values starting with 'pick'
|
||||||
|
|
||||||
|
## NOTE: metadata filter not supported when "source" is non MinIO.
|
||||||
|
# metadata:
|
||||||
|
# - key: "content-type"
|
||||||
|
# value: "image/*" # match objects with 'content-type', with all values starting with 'image/'
|
||||||
|
|
||||||
|
notify:
|
||||||
|
endpoint: "https://notify.endpoint" # notification endpoint to receive job status events
|
||||||
|
token: "Bearer xxxxx" # optional authentication token for the notification endpoint
|
||||||
|
|
||||||
|
retry:
|
||||||
|
attempts: 10 # number of retries for the job before giving up
|
||||||
|
delay: "500ms" # least amount of delay between each retry
|
||||||
|
```
|
||||||
|
|
||||||
|
You can create and run multiple 'replication' jobs at a time there are no predefined limits set.
|
||||||
|
|
||||||
|
## Batch Jobs Terminology
|
||||||
|
|
||||||
|
### Job
|
||||||
|
A job is the basic unit of work for MinIO Batch Job. A job is a self describing YAML, once this YAML is submitted and evaluated - MinIO performs the requested actions on each of the objects obtained under the described criteria in job YAML file.
|
||||||
|
|
||||||
|
### Type
|
||||||
|
Type describes the job type, such as replicating objects between MinIO sites. Each job performs a single type of operation across all objects that match the job description criteria.
|
||||||
|
|
||||||
|
## Batch Jobs via Commandline
|
||||||
|
[mc](http://github.com/minio/mc) provides 'mc batch' command to create, start and manage submitted jobs.
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME:
|
||||||
|
mc batch - manage batch jobs
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
mc batch COMMAND [COMMAND FLAGS | -h] [ARGUMENTS...]
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
generate generate a new batch job definition
|
||||||
|
start start a new batch job
|
||||||
|
list, ls list all current batch jobs
|
||||||
|
status summarize job events on MinIO server in real-time
|
||||||
|
describe describe job definition for a job
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate a job yaml
|
||||||
|
```
|
||||||
|
mc batch generate alias/ replicate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start the batch job (returns back the JID)
|
||||||
|
```
|
||||||
|
mc batch start alias/ ./replicate.yaml
|
||||||
|
Successfully start 'replicate' job `E24HH4nNMcgY5taynaPfxu` on '2022-09-26 17:19:06.296974771 -0700 PDT'
|
||||||
|
```
|
||||||
|
|
||||||
|
### List all batch jobs
|
||||||
|
```
|
||||||
|
mc batch list alias/
|
||||||
|
ID TYPE USER STARTED
|
||||||
|
E24HH4nNMcgY5taynaPfxu replicate minioadmin 1 minute ago
|
||||||
|
```
|
||||||
|
|
||||||
|
### List all 'replicate' batch jobs
|
||||||
|
```
|
||||||
|
mc batch list alias/ --type replicate
|
||||||
|
ID TYPE USER STARTED
|
||||||
|
E24HH4nNMcgY5taynaPfxu replicate minioadmin 1 minute ago
|
||||||
|
```
|
||||||
|
|
||||||
|
### Real-time 'status' for a batch job
|
||||||
|
```
|
||||||
|
mc batch status myminio/ E24HH4nNMcgY5taynaPfxu
|
||||||
|
●∙∙
|
||||||
|
Objects: 28766
|
||||||
|
Versions: 28766
|
||||||
|
Throughput: 3.0 MiB/s
|
||||||
|
Transferred: 406 MiB
|
||||||
|
Elapsed: 2m14.227222868s
|
||||||
|
CurrObjName: share/doc/xml-core/examples/foo.xmlcatalogs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 'describe' the batch job yaml.
|
||||||
|
```
|
||||||
|
mc batch describe myminio/ E24HH4nNMcgY5taynaPfxu
|
||||||
|
replicate:
|
||||||
|
apiVersion: v1
|
||||||
|
...
|
||||||
|
```
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user