mirror of
https://github.com/pgsty/minio.git
synced 2026-11-12 10:35:33 +03:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9dc061d84 | |||
| 01a8c09920 | |||
| 4c8562bcec | |||
| f13c04629b | |||
| 80ff907d08 | |||
| 673df6d517 | |||
| 2d40433bc1 | |||
| 3bc39db34e | |||
| a17f14f73a | |||
| 6651c655cb | |||
| 3ae104edae | |||
| c87a489514 | |||
| a60267501d | |||
| 641a56da0d | |||
| 59788e25c7 | |||
| a16193bb50 | |||
| 132e7413ba | |||
| 1966668066 | |||
| 064f36ca5a | |||
| 15b609ecea | |||
| 4a1edfd9aa | |||
| b7f319b62a | |||
| 33c101544d | |||
| 21cf29330e | |||
| 3b21bb5be8 | |||
| 6fe2b3f901 | |||
| b368d4cc13 | |||
| 0680af7414 | |||
| 91805bcab6 | |||
| c0e2886e37 | |||
| 4f5dded4d4 | |||
| b3a94c4e85 | |||
| 8e618d45fc | |||
| 3ef59d2821 | |||
| 23db4958f5 | |||
| d9ee668b6d | |||
| 2e5d792f0c | |||
| b276651eaa | |||
| 3535197f99 | |||
| 95f076340a | |||
| 698bb93a46 | |||
| 2584430141 | |||
| ded373e600 | |||
| e8c54c3d6c | |||
| f944a42886 | |||
| eff0ea43aa | |||
| 3b602bb532 | |||
| 459985f0fa | |||
| d0080046c2 | |||
| 7fcb428622 | |||
| 4ea6f94ed8 | |||
| 83adc2eebf | |||
| 989c318a28 | |||
| ef802f2b2c | |||
| f5d2fbc84c | |||
| e139673969 | |||
| a8c6465f22 | |||
| 27538e2d22 |
@@ -3,6 +3,8 @@ extend-exclude = [
|
|||||||
".git/",
|
".git/",
|
||||||
"docs/",
|
"docs/",
|
||||||
"CREDITS",
|
"CREDITS",
|
||||||
|
"go.mod",
|
||||||
|
"go.sum",
|
||||||
]
|
]
|
||||||
ignore-hidden = false
|
ignore-hidden = false
|
||||||
|
|
||||||
@@ -17,6 +19,7 @@ extend-ignore-re = [
|
|||||||
"MIIDBTCCAe2gAwIBAgIQWHw7h.*",
|
"MIIDBTCCAe2gAwIBAgIQWHw7h.*",
|
||||||
'http\.Header\{"X-Amz-Server-Side-Encryptio":',
|
'http\.Header\{"X-Amz-Server-Side-Encryptio":',
|
||||||
"ZoEoZdLlzVbOlT9rbhD7ZN7TLyiYXSAlB79uGEge",
|
"ZoEoZdLlzVbOlT9rbhD7ZN7TLyiYXSAlB79uGEge",
|
||||||
|
"ERRO:",
|
||||||
]
|
]
|
||||||
|
|
||||||
[default.extend-words]
|
[default.extend-words]
|
||||||
|
|||||||
@@ -13275,376 +13275,6 @@ Mozilla Public License, version 2.0
|
|||||||
be used to construe this License against a Contributor.
|
be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
|
||||||
10. Versions of the License
|
|
||||||
|
|
||||||
10.1. New Versions
|
|
||||||
|
|
||||||
Mozilla Foundation is the license steward. Except as provided in Section
|
|
||||||
10.3, no one other than the license steward has the right to modify or
|
|
||||||
publish new versions of this License. Each version will be given a
|
|
||||||
distinguishing version number.
|
|
||||||
|
|
||||||
10.2. Effect of New Versions
|
|
||||||
|
|
||||||
You may distribute the Covered Software under the terms of the version
|
|
||||||
of the License under which You originally received the Covered Software,
|
|
||||||
or under the terms of any subsequent version published by the license
|
|
||||||
steward.
|
|
||||||
|
|
||||||
10.3. Modified Versions
|
|
||||||
|
|
||||||
If you create software not governed by this License, and you want to
|
|
||||||
create a new license for such software, you may create and use a
|
|
||||||
modified version of this License if you rename the license and remove
|
|
||||||
any references to the name of the license steward (except to note that
|
|
||||||
such modified license differs from this License).
|
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
|
||||||
Licenses If You choose to distribute Source Code Form that is
|
|
||||||
Incompatible With Secondary Licenses under the terms of this version of
|
|
||||||
the License, the notice described in Exhibit B of this License must be
|
|
||||||
attached.
|
|
||||||
|
|
||||||
Exhibit A - Source Code Form License Notice
|
|
||||||
|
|
||||||
This Source Code Form is subject to the
|
|
||||||
terms of the Mozilla Public License, v.
|
|
||||||
2.0. If a copy of the MPL was not
|
|
||||||
distributed with this file, You can
|
|
||||||
obtain one at
|
|
||||||
http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular file,
|
|
||||||
then You may include the notice in a location (such as a LICENSE file in a
|
|
||||||
relevant directory) where a recipient would be likely to look for such a
|
|
||||||
notice.
|
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership.
|
|
||||||
|
|
||||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|
||||||
|
|
||||||
This Source Code Form is "Incompatible
|
|
||||||
With Secondary Licenses", as defined by
|
|
||||||
the Mozilla Public License, v. 2.0.
|
|
||||||
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
github.com/hashicorp/golang-lru/v2
|
|
||||||
https://github.com/hashicorp/golang-lru/v2
|
|
||||||
----------------------------------------------------------------
|
|
||||||
Copyright (c) 2014 HashiCorp, Inc.
|
|
||||||
|
|
||||||
Mozilla Public License, version 2.0
|
|
||||||
|
|
||||||
1. Definitions
|
|
||||||
|
|
||||||
1.1. "Contributor"
|
|
||||||
|
|
||||||
means each individual or legal entity that creates, contributes to the
|
|
||||||
creation of, or owns Covered Software.
|
|
||||||
|
|
||||||
1.2. "Contributor Version"
|
|
||||||
|
|
||||||
means the combination of the Contributions of others (if any) used by a
|
|
||||||
Contributor and that particular Contributor's Contribution.
|
|
||||||
|
|
||||||
1.3. "Contribution"
|
|
||||||
|
|
||||||
means Covered Software of a particular Contributor.
|
|
||||||
|
|
||||||
1.4. "Covered Software"
|
|
||||||
|
|
||||||
means Source Code Form to which the initial Contributor has attached the
|
|
||||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
|
||||||
Modifications of such Source Code Form, in each case including portions
|
|
||||||
thereof.
|
|
||||||
|
|
||||||
1.5. "Incompatible With Secondary Licenses"
|
|
||||||
means
|
|
||||||
|
|
||||||
a. that the initial Contributor has attached the notice described in
|
|
||||||
Exhibit B to the Covered Software; or
|
|
||||||
|
|
||||||
b. that the Covered Software was made available under the terms of
|
|
||||||
version 1.1 or earlier of the License, but not also under the terms of
|
|
||||||
a Secondary License.
|
|
||||||
|
|
||||||
1.6. "Executable Form"
|
|
||||||
|
|
||||||
means any form of the work other than Source Code Form.
|
|
||||||
|
|
||||||
1.7. "Larger Work"
|
|
||||||
|
|
||||||
means a work that combines Covered Software with other material, in a
|
|
||||||
separate file or files, that is not Covered Software.
|
|
||||||
|
|
||||||
1.8. "License"
|
|
||||||
|
|
||||||
means this document.
|
|
||||||
|
|
||||||
1.9. "Licensable"
|
|
||||||
|
|
||||||
means having the right to grant, to the maximum extent possible, whether
|
|
||||||
at the time of the initial grant or subsequently, any and all of the
|
|
||||||
rights conveyed by this License.
|
|
||||||
|
|
||||||
1.10. "Modifications"
|
|
||||||
|
|
||||||
means any of the following:
|
|
||||||
|
|
||||||
a. any file in Source Code Form that results from an addition to,
|
|
||||||
deletion from, or modification of the contents of Covered Software; or
|
|
||||||
|
|
||||||
b. any new file in Source Code Form that contains any Covered Software.
|
|
||||||
|
|
||||||
1.11. "Patent Claims" of a Contributor
|
|
||||||
|
|
||||||
means any patent claim(s), including without limitation, method,
|
|
||||||
process, and apparatus claims, in any patent Licensable by such
|
|
||||||
Contributor that would be infringed, but for the grant of the License,
|
|
||||||
by the making, using, selling, offering for sale, having made, import,
|
|
||||||
or transfer of either its Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
1.12. "Secondary License"
|
|
||||||
|
|
||||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
|
||||||
General Public License, Version 2.1, the GNU Affero General Public
|
|
||||||
License, Version 3.0, or any later versions of those licenses.
|
|
||||||
|
|
||||||
1.13. "Source Code Form"
|
|
||||||
|
|
||||||
means the form of the work preferred for making modifications.
|
|
||||||
|
|
||||||
1.14. "You" (or "Your")
|
|
||||||
|
|
||||||
means an individual or a legal entity exercising rights under this
|
|
||||||
License. For legal entities, "You" includes any entity that controls, is
|
|
||||||
controlled by, or is under common control with You. For purposes of this
|
|
||||||
definition, "control" means (a) the power, direct or indirect, to cause
|
|
||||||
the direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
|
||||||
outstanding shares or beneficial ownership of such entity.
|
|
||||||
|
|
||||||
|
|
||||||
2. License Grants and Conditions
|
|
||||||
|
|
||||||
2.1. Grants
|
|
||||||
|
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
a. under intellectual property rights (other than patent or trademark)
|
|
||||||
Licensable by such Contributor to use, reproduce, make available,
|
|
||||||
modify, display, perform, distribute, and otherwise exploit its
|
|
||||||
Contributions, either on an unmodified basis, with Modifications, or
|
|
||||||
as part of a Larger Work; and
|
|
||||||
|
|
||||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
|
||||||
sale, have made, import, and otherwise transfer either its
|
|
||||||
Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
2.2. Effective Date
|
|
||||||
|
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution
|
|
||||||
become effective for each Contribution on the date the Contributor first
|
|
||||||
distributes such Contribution.
|
|
||||||
|
|
||||||
2.3. Limitations on Grant Scope
|
|
||||||
|
|
||||||
The licenses granted in this Section 2 are the only rights granted under
|
|
||||||
this License. No additional rights or licenses will be implied from the
|
|
||||||
distribution or licensing of Covered Software under this License.
|
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
|
||||||
Contributor:
|
|
||||||
|
|
||||||
a. for any code that a Contributor has removed from Covered Software; or
|
|
||||||
|
|
||||||
b. for infringements caused by: (i) Your and any other third party's
|
|
||||||
modifications of Covered Software, or (ii) the combination of its
|
|
||||||
Contributions with other software (except as part of its Contributor
|
|
||||||
Version); or
|
|
||||||
|
|
||||||
c. under Patent Claims infringed by Covered Software in the absence of
|
|
||||||
its Contributions.
|
|
||||||
|
|
||||||
This License does not grant any rights in the trademarks, service marks,
|
|
||||||
or logos of any Contributor (except as may be necessary to comply with
|
|
||||||
the notice requirements in Section 3.4).
|
|
||||||
|
|
||||||
2.4. Subsequent Licenses
|
|
||||||
|
|
||||||
No Contributor makes additional grants as a result of Your choice to
|
|
||||||
distribute the Covered Software under a subsequent version of this
|
|
||||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
|
||||||
permitted under the terms of Section 3.3).
|
|
||||||
|
|
||||||
2.5. Representation
|
|
||||||
|
|
||||||
Each Contributor represents that the Contributor believes its
|
|
||||||
Contributions are its original creation(s) or it has sufficient rights to
|
|
||||||
grant the rights to its Contributions conveyed by this License.
|
|
||||||
|
|
||||||
2.6. Fair Use
|
|
||||||
|
|
||||||
This License is not intended to limit any rights You have under
|
|
||||||
applicable copyright doctrines of fair use, fair dealing, or other
|
|
||||||
equivalents.
|
|
||||||
|
|
||||||
2.7. Conditions
|
|
||||||
|
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
|
||||||
Section 2.1.
|
|
||||||
|
|
||||||
|
|
||||||
3. Responsibilities
|
|
||||||
|
|
||||||
3.1. Distribution of Source Form
|
|
||||||
|
|
||||||
All distribution of Covered Software in Source Code Form, including any
|
|
||||||
Modifications that You create or to which You contribute, must be under
|
|
||||||
the terms of this License. You must inform recipients that the Source
|
|
||||||
Code Form of the Covered Software is governed by the terms of this
|
|
||||||
License, and how they can obtain a copy of this License. You may not
|
|
||||||
attempt to alter or restrict the recipients' rights in the Source Code
|
|
||||||
Form.
|
|
||||||
|
|
||||||
3.2. Distribution of Executable Form
|
|
||||||
|
|
||||||
If You distribute Covered Software in Executable Form then:
|
|
||||||
|
|
||||||
a. such Covered Software must also be made available in Source Code Form,
|
|
||||||
as described in Section 3.1, and You must inform recipients of the
|
|
||||||
Executable Form how they can obtain a copy of such Source Code Form by
|
|
||||||
reasonable means in a timely manner, at a charge no more than the cost
|
|
||||||
of distribution to the recipient; and
|
|
||||||
|
|
||||||
b. You may distribute such Executable Form under the terms of this
|
|
||||||
License, or sublicense it under different terms, provided that the
|
|
||||||
license for the Executable Form does not attempt to limit or alter the
|
|
||||||
recipients' rights in the Source Code Form under this License.
|
|
||||||
|
|
||||||
3.3. Distribution of a Larger Work
|
|
||||||
|
|
||||||
You may create and distribute a Larger Work under terms of Your choice,
|
|
||||||
provided that You also comply with the requirements of this License for
|
|
||||||
the Covered Software. If the Larger Work is a combination of Covered
|
|
||||||
Software with a work governed by one or more Secondary Licenses, and the
|
|
||||||
Covered Software is not Incompatible With Secondary Licenses, this
|
|
||||||
License permits You to additionally distribute such Covered Software
|
|
||||||
under the terms of such Secondary License(s), so that the recipient of
|
|
||||||
the Larger Work may, at their option, further distribute the Covered
|
|
||||||
Software under the terms of either this License or such Secondary
|
|
||||||
License(s).
|
|
||||||
|
|
||||||
3.4. Notices
|
|
||||||
|
|
||||||
You may not remove or alter the substance of any license notices
|
|
||||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
|
||||||
limitations of liability) contained within the Source Code Form of the
|
|
||||||
Covered Software, except that You may alter any license notices to the
|
|
||||||
extent required to remedy known factual inaccuracies.
|
|
||||||
|
|
||||||
3.5. Application of Additional Terms
|
|
||||||
|
|
||||||
You may choose to offer, and to charge a fee for, warranty, support,
|
|
||||||
indemnity or liability obligations to one or more recipients of Covered
|
|
||||||
Software. However, You may do so only on Your own behalf, and not on
|
|
||||||
behalf of any Contributor. You must make it absolutely clear that any
|
|
||||||
such warranty, support, indemnity, or liability obligation is offered by
|
|
||||||
You alone, and You hereby agree to indemnify every Contributor for any
|
|
||||||
liability incurred by such Contributor as a result of warranty, support,
|
|
||||||
indemnity or liability terms You offer. You may include additional
|
|
||||||
disclaimers of warranty and limitations of liability specific to any
|
|
||||||
jurisdiction.
|
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation
|
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this License
|
|
||||||
with respect to some or all of the Covered Software due to statute,
|
|
||||||
judicial order, or regulation then You must: (a) comply with the terms of
|
|
||||||
this License to the maximum extent possible; and (b) describe the
|
|
||||||
limitations and the code they affect. Such description must be placed in a
|
|
||||||
text file included with all distributions of the Covered Software under
|
|
||||||
this License. Except to the extent prohibited by statute or regulation,
|
|
||||||
such description must be sufficiently detailed for a recipient of ordinary
|
|
||||||
skill to be able to understand it.
|
|
||||||
|
|
||||||
5. Termination
|
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically if You
|
|
||||||
fail to comply with any of its terms. However, if You become compliant,
|
|
||||||
then the rights granted under this License from a particular Contributor
|
|
||||||
are reinstated (a) provisionally, unless and until such Contributor
|
|
||||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
|
||||||
basis, if such Contributor fails to notify You of the non-compliance by
|
|
||||||
some reasonable means prior to 60 days after You have come back into
|
|
||||||
compliance. Moreover, Your grants from a particular Contributor are
|
|
||||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
|
||||||
non-compliance by some reasonable means, this is the first time You have
|
|
||||||
received notice of non-compliance with this License from such
|
|
||||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
|
||||||
of the notice.
|
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent
|
|
||||||
infringement claim (excluding declaratory judgment actions,
|
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
|
||||||
directly or indirectly infringes any patent, then the rights granted to
|
|
||||||
You by any and all Contributors for the Covered Software under Section
|
|
||||||
2.1 of this License shall terminate.
|
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
|
||||||
license agreements (excluding distributors and resellers) which have been
|
|
||||||
validly granted by You or Your distributors under this License prior to
|
|
||||||
termination shall survive termination.
|
|
||||||
|
|
||||||
6. Disclaimer of Warranty
|
|
||||||
|
|
||||||
Covered Software is provided under this License on an "as is" basis,
|
|
||||||
without warranty of any kind, either expressed, implied, or statutory,
|
|
||||||
including, without limitation, warranties that the Covered Software is free
|
|
||||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
|
||||||
The entire risk as to the quality and performance of the Covered Software
|
|
||||||
is with You. Should any Covered Software prove defective in any respect,
|
|
||||||
You (not any Contributor) assume the cost of any necessary servicing,
|
|
||||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
|
||||||
part of this License. No use of any Covered Software is authorized under
|
|
||||||
this License except under this disclaimer.
|
|
||||||
|
|
||||||
7. Limitation of Liability
|
|
||||||
|
|
||||||
Under no circumstances and under no legal theory, whether tort (including
|
|
||||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
|
||||||
distributes Covered Software as permitted above, be liable to You for any
|
|
||||||
direct, indirect, special, incidental, or consequential damages of any
|
|
||||||
character including, without limitation, damages for lost profits, loss of
|
|
||||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses, even if such party shall have been
|
|
||||||
informed of the possibility of such damages. This limitation of liability
|
|
||||||
shall not apply to liability for death or personal injury resulting from
|
|
||||||
such party's negligence to the extent applicable law prohibits such
|
|
||||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
|
||||||
incidental or consequential damages, so this exclusion and limitation may
|
|
||||||
not apply to You.
|
|
||||||
|
|
||||||
8. Litigation
|
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the courts
|
|
||||||
of a jurisdiction where the defendant maintains its principal place of
|
|
||||||
business and such litigation shall be governed by laws of that
|
|
||||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
|
||||||
in this Section shall prevent a party's ability to bring cross-claims or
|
|
||||||
counter-claims.
|
|
||||||
|
|
||||||
9. Miscellaneous
|
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject
|
|
||||||
matter hereof. If any provision of this License is held to be
|
|
||||||
unenforceable, such provision shall be reformed only to the extent
|
|
||||||
necessary to make it enforceable. Any law or regulation which provides that
|
|
||||||
the language of a contract shall be construed against the drafter shall not
|
|
||||||
be used to construe this License against a Contributor.
|
|
||||||
|
|
||||||
|
|
||||||
10. Versions of the License
|
10. Versions of the License
|
||||||
|
|
||||||
10.1. New Versions
|
10.1. New Versions
|
||||||
@@ -16499,12 +16129,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
github.com/mattn/go-localereader
|
|
||||||
https://github.com/mattn/go-localereader
|
|
||||||
----------------------------------------------------------------
|
|
||||||
All rights reserved proprietary
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
github.com/mattn/go-runewidth
|
github.com/mattn/go-runewidth
|
||||||
https://github.com/mattn/go-runewidth
|
https://github.com/mattn/go-runewidth
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
@@ -16801,12 +16425,6 @@ SOFTWARE.
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
github.com/minio/colorjson
|
|
||||||
https://github.com/minio/colorjson
|
|
||||||
----------------------------------------------------------------
|
|
||||||
All rights reserved proprietary
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
github.com/minio/console
|
github.com/minio/console
|
||||||
https://github.com/minio/console
|
https://github.com/minio/console
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
@@ -17474,12 +17092,6 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
github.com/minio/csvparser
|
|
||||||
https://github.com/minio/csvparser
|
|
||||||
----------------------------------------------------------------
|
|
||||||
All rights reserved proprietary
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
github.com/minio/dnscache
|
github.com/minio/dnscache
|
||||||
https://github.com/minio/dnscache
|
https://github.com/minio/dnscache
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
@@ -18175,12 +17787,6 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
|||||||
|
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
github.com/minio/filepath
|
|
||||||
https://github.com/minio/filepath
|
|
||||||
----------------------------------------------------------------
|
|
||||||
All rights reserved proprietary
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
github.com/minio/highwayhash
|
github.com/minio/highwayhash
|
||||||
https://github.com/minio/highwayhash
|
https://github.com/minio/highwayhash
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ function fail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function check_online() {
|
function check_online() {
|
||||||
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
|
if ! grep -q 'API:' ${WORK_DIR}/dist-minio-*.log; then
|
||||||
echo "1"
|
echo "1"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ function start_minio_3_node() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function check_heal() {
|
function check_heal() {
|
||||||
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
|
if ! grep -q 'API:' ${WORK_DIR}/dist-minio-*.log; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -552,7 +552,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
|||||||
dnList = append(dnList, selfDN)
|
dnList = append(dnList, selfDN)
|
||||||
}
|
}
|
||||||
|
|
||||||
accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp)
|
var ldapUserList []string
|
||||||
if isAll {
|
if isAll {
|
||||||
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
|
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -560,7 +560,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for user := range ldapUsers {
|
for user := range ldapUsers {
|
||||||
accessKeyMap[user] = madmin.ListAccessKeysLDAPResp{}
|
ldapUserList = append(ldapUserList, user)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, userDN := range dnList {
|
for _, userDN := range dnList {
|
||||||
@@ -573,7 +573,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
|||||||
if foundResult == nil {
|
if foundResult == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
accessKeyMap[foundResult.NormDN] = madmin.ListAccessKeysLDAPResp{}
|
ldapUserList = append(ldapUserList, foundResult.NormDN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,9 +598,12 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for dn, accessKeys := range accessKeyMap {
|
accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp)
|
||||||
|
for _, internalDN := range ldapUserList {
|
||||||
|
externalDN := globalIAMSys.LDAPConfig.DecodeDN(internalDN)
|
||||||
|
accessKeys := madmin.ListAccessKeysLDAPResp{}
|
||||||
if listSTSKeys {
|
if listSTSKeys {
|
||||||
stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, dn)
|
stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, internalDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -614,13 +617,12 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
// if only STS keys, skip if user has no STS keys
|
// if only STS keys, skip if user has no STS keys
|
||||||
if !listServiceAccounts && len(stsKeys) == 0 {
|
if !listServiceAccounts && len(stsKeys) == 0 {
|
||||||
delete(accessKeyMap, dn)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if listServiceAccounts {
|
if listServiceAccounts {
|
||||||
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, dn)
|
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, internalDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -634,11 +636,10 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
// if only service accounts, skip if user has no service accounts
|
// if only service accounts, skip if user has no service accounts
|
||||||
if !listSTSKeys && len(serviceAccounts) == 0 {
|
if !listSTSKeys && len(serviceAccounts) == 0 {
|
||||||
delete(accessKeyMap, dn)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
accessKeyMap[dn] = accessKeys
|
accessKeyMap[externalDN] = accessKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(accessKeyMap)
|
data, err := json.Marshal(accessKeyMap)
|
||||||
|
|||||||
@@ -848,9 +848,10 @@ func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
func lriToLockEntry(l lockRequesterInfo, now time.Time, resource, server string) *madmin.LockEntry {
|
func lriToLockEntry(l lockRequesterInfo, now time.Time, resource, server string) *madmin.LockEntry {
|
||||||
|
t := time.Unix(0, l.Timestamp)
|
||||||
entry := &madmin.LockEntry{
|
entry := &madmin.LockEntry{
|
||||||
Timestamp: l.Timestamp,
|
Timestamp: t,
|
||||||
Elapsed: now.Sub(l.Timestamp),
|
Elapsed: now.Sub(t),
|
||||||
Resource: resource,
|
Resource: resource,
|
||||||
ServerList: []string{server},
|
ServerList: []string{server},
|
||||||
Source: l.Source,
|
Source: l.Source,
|
||||||
@@ -2186,7 +2187,7 @@ func (a adminAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMSKeyStatusHandler - GET /minio/admin/v3/kms/status
|
// KMSStatusHandler - GET /minio/admin/v3/kms/status
|
||||||
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ func TestTopLockEntries(t *testing.T) {
|
|||||||
Owner: lri.Owner,
|
Owner: lri.Owner,
|
||||||
ID: lri.UID,
|
ID: lri.UID,
|
||||||
Quorum: lri.Quorum,
|
Quorum: lri.Quorum,
|
||||||
|
Timestamp: time.Unix(0, lri.Timestamp),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -761,6 +761,15 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
countOKDrives := func(drives []madmin.HealDriveInfo) (count int) {
|
||||||
|
for _, drive := range drives {
|
||||||
|
if drive.State == madmin.DriveStateOk {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
// task queued, now wait for the response.
|
// task queued, now wait for the response.
|
||||||
select {
|
select {
|
||||||
case res := <-task.respCh:
|
case res := <-task.respCh:
|
||||||
@@ -781,6 +790,11 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
res.result.Detail = res.err.Error()
|
res.result.Detail = res.err.Error()
|
||||||
}
|
}
|
||||||
|
if res.result.ParityBlocks > 0 && res.result.DataBlocks > 0 && res.result.DataBlocks > res.result.ParityBlocks {
|
||||||
|
if got := countOKDrives(res.result.After.Drives); got < res.result.ParityBlocks {
|
||||||
|
res.result.Detail = fmt.Sprintf("quorum loss - expected %d minimum, got drive states in OK %d", res.result.ParityBlocks, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
return h.pushHealResultItem(res.result)
|
return h.pushHealResultItem(res.result)
|
||||||
case <-h.ctx.Done():
|
case <-h.ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
for _, disk := range localDrives {
|
for _, disk := range localDrives {
|
||||||
_, err := disk.DiskInfo(context.Background(), DiskInfoOptions{})
|
_, err := disk.DiskInfo(context.Background(), DiskInfoOptions{})
|
||||||
|
|||||||
+18
-14
@@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/minio/pkg/v3/env"
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/minio/pkg/v3/wildcard"
|
"github.com/minio/pkg/v3/wildcard"
|
||||||
"github.com/minio/pkg/v3/workers"
|
"github.com/minio/pkg/v3/workers"
|
||||||
|
"github.com/minio/pkg/v3/xtime"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ func (p BatchJobExpirePurge) Validate() error {
|
|||||||
// BatchJobExpireFilter holds all the filters currently supported for batch replication
|
// BatchJobExpireFilter holds all the filters currently supported for batch replication
|
||||||
type BatchJobExpireFilter struct {
|
type BatchJobExpireFilter struct {
|
||||||
line, col int
|
line, col int
|
||||||
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
OlderThan xtime.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
||||||
CreatedBefore *time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
CreatedBefore *time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
||||||
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
||||||
Metadata []BatchJobKV `yaml:"metadata,omitempty" json:"metadata"`
|
Metadata []BatchJobKV `yaml:"metadata,omitempty" json:"metadata"`
|
||||||
@@ -162,7 +163,7 @@ func (ef BatchJobExpireFilter) Matches(obj ObjectInfo, now time.Time) bool {
|
|||||||
if len(ef.Name) > 0 && !wildcard.Match(ef.Name, obj.Name) {
|
if len(ef.Name) > 0 && !wildcard.Match(ef.Name, obj.Name) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if ef.OlderThan > 0 && now.Sub(obj.ModTime) <= ef.OlderThan {
|
if ef.OlderThan > 0 && now.Sub(obj.ModTime) <= ef.OlderThan.D() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,22 +553,25 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
go func() {
|
go func() {
|
||||||
saveTicker := time.NewTicker(10 * time.Second)
|
saveTicker := time.NewTicker(10 * time.Second)
|
||||||
defer saveTicker.Stop()
|
defer saveTicker.Stop()
|
||||||
for {
|
quit := false
|
||||||
|
after := time.Minute
|
||||||
|
for !quit {
|
||||||
select {
|
select {
|
||||||
case <-saveTicker.C:
|
case <-saveTicker.C:
|
||||||
// persist in-memory state to disk after every 10secs.
|
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// persist in-memory state immediately before exiting due to context cancellation.
|
quit = true
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
|
||||||
return
|
|
||||||
|
|
||||||
case <-saverQuitCh:
|
case <-saverQuitCh:
|
||||||
// persist in-memory state immediately to disk.
|
quit = true
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if quit {
|
||||||
|
// save immediately if we are quitting
|
||||||
|
after = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(GlobalContext, 30*time.Second) // independent context
|
||||||
|
batchLogIf(ctx, ri.updateAfter(ctx, api, after, job))
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -584,7 +588,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
|||||||
versionsCount int
|
versionsCount int
|
||||||
toDel []expireObjInfo
|
toDel []expireObjInfo
|
||||||
)
|
)
|
||||||
failed := true
|
failed := false
|
||||||
for result := range results {
|
for result := range results {
|
||||||
if result.Err != nil {
|
if result.Err != nil {
|
||||||
failed = true
|
failed = true
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ func (z *BatchJobExpireFilter) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "OlderThan":
|
case "OlderThan":
|
||||||
z.OlderThan, err = dc.ReadDuration()
|
err = z.OlderThan.DecodeMsg(dc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OlderThan")
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
return
|
return
|
||||||
@@ -433,7 +433,7 @@ func (z *BatchJobExpireFilter) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteDuration(z.OlderThan)
|
err = z.OlderThan.EncodeMsg(en)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OlderThan")
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
return
|
return
|
||||||
@@ -544,7 +544,11 @@ func (z *BatchJobExpireFilter) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// map header, size 8
|
// map header, size 8
|
||||||
// string "OlderThan"
|
// string "OlderThan"
|
||||||
o = append(o, 0x88, 0xa9, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
o = append(o, 0x88, 0xa9, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
||||||
o = msgp.AppendDuration(o, z.OlderThan)
|
o, err = z.OlderThan.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
// string "CreatedBefore"
|
// string "CreatedBefore"
|
||||||
o = append(o, 0xad, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65)
|
o = append(o, 0xad, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65)
|
||||||
if z.CreatedBefore == nil {
|
if z.CreatedBefore == nil {
|
||||||
@@ -613,7 +617,7 @@ func (z *BatchJobExpireFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "OlderThan":
|
case "OlderThan":
|
||||||
z.OlderThan, bts, err = msgp.ReadDurationBytes(bts)
|
bts, err = z.OlderThan.UnmarshalMsg(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OlderThan")
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
return
|
return
|
||||||
@@ -734,7 +738,7 @@ func (z *BatchJobExpireFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *BatchJobExpireFilter) Msgsize() (s int) {
|
func (z *BatchJobExpireFilter) Msgsize() (s int) {
|
||||||
s = 1 + 10 + msgp.DurationSize + 14
|
s = 1 + 10 + z.OlderThan.Msgsize() + 14
|
||||||
if z.CreatedBefore == nil {
|
if z.CreatedBefore == nil {
|
||||||
s += msgp.NilSize
|
s += msgp.NilSize
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseBatchJobExpire(t *testing.T) {
|
func TestParseBatchJobExpire(t *testing.T) {
|
||||||
@@ -32,7 +32,7 @@ expire: # Expire objects that match a condition
|
|||||||
rules:
|
rules:
|
||||||
- type: object # regular objects with zero or more older versions
|
- type: object # regular objects with zero or more older versions
|
||||||
name: NAME # match object names that satisfy the wildcard expression.
|
name: NAME # match object names that satisfy the wildcard expression.
|
||||||
olderThan: 70h # match objects older than this value
|
olderThan: 7d10h # match objects older than this value
|
||||||
createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
||||||
tags:
|
tags:
|
||||||
- key: name
|
- key: name
|
||||||
@@ -64,7 +64,7 @@ expire: # Expire objects that match a condition
|
|||||||
delay: 500ms # least amount of delay between each retry
|
delay: 500ms # least amount of delay between each retry
|
||||||
`
|
`
|
||||||
var job BatchJobRequest
|
var job BatchJobRequest
|
||||||
err := yaml.UnmarshalStrict([]byte(expireYaml), &job)
|
err := yaml.Unmarshal([]byte(expireYaml), &job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to parse batch-job-expire yaml", err)
|
t.Fatal("Failed to parse batch-job-expire yaml", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-17
@@ -287,12 +287,12 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
|||||||
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
|
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
|
||||||
|
|
||||||
skip := func(oi ObjectInfo) (ok bool) {
|
skip := func(oi ObjectInfo) (ok bool) {
|
||||||
if r.Flags.Filter.OlderThan > 0 && time.Since(oi.ModTime) < r.Flags.Filter.OlderThan {
|
if r.Flags.Filter.OlderThan > 0 && time.Since(oi.ModTime) < r.Flags.Filter.OlderThan.D() {
|
||||||
// skip all objects that are newer than specified older duration
|
// skip all objects that are newer than specified older duration
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Flags.Filter.NewerThan > 0 && time.Since(oi.ModTime) >= r.Flags.Filter.NewerThan {
|
if r.Flags.Filter.NewerThan > 0 && time.Since(oi.ModTime) >= r.Flags.Filter.NewerThan.D() {
|
||||||
// skip all objects that are older than specified newer duration
|
// skip all objects that are older than specified newer duration
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -540,7 +540,7 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
|
|||||||
return oi
|
return oi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLayer, remoteClnt *minio.Client, entries []ObjectInfo) error {
|
func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLayer, remoteClnt *minio.Client, entries []ObjectInfo, prefix string) error {
|
||||||
input := make(chan minio.SnowballObject, 1)
|
input := make(chan minio.SnowballObject, 1)
|
||||||
opts := minio.SnowballOptions{
|
opts := minio.SnowballOptions{
|
||||||
Opts: minio.PutObjectOptions{},
|
Opts: minio.PutObjectOptions{},
|
||||||
@@ -562,6 +562,10 @@ func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLa
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
entry.Name = pathJoin(prefix, entry.Name)
|
||||||
|
}
|
||||||
|
|
||||||
snowballObj := minio.SnowballObject{
|
snowballObj := minio.SnowballObject{
|
||||||
// Create path to store objects within the bucket.
|
// Create path to store objects within the bucket.
|
||||||
Key: entry.Name,
|
Key: entry.Name,
|
||||||
@@ -725,7 +729,7 @@ const (
|
|||||||
|
|
||||||
batchReplJobAPIVersion = "v1"
|
batchReplJobAPIVersion = "v1"
|
||||||
batchReplJobDefaultRetries = 3
|
batchReplJobDefaultRetries = 3
|
||||||
batchReplJobDefaultRetryDelay = 250 * time.Millisecond
|
batchReplJobDefaultRetryDelay = time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func getJobPath(job BatchJobRequest) string {
|
func getJobPath(job BatchJobRequest) string {
|
||||||
@@ -1016,18 +1020,18 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
lastObject := ri.Object
|
lastObject := ri.Object
|
||||||
|
|
||||||
delay := job.Replicate.Flags.Retry.Delay
|
delay := job.Replicate.Flags.Retry.Delay
|
||||||
if delay == 0 {
|
if delay < time.Second {
|
||||||
delay = batchReplJobDefaultRetryDelay
|
delay = batchReplJobDefaultRetryDelay
|
||||||
}
|
}
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
selectObj := func(info FileInfo) (ok bool) {
|
selectObj := func(info FileInfo) (ok bool) {
|
||||||
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
|
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan.D() {
|
||||||
// skip all objects that are newer than specified older duration
|
// skip all objects that are newer than specified older duration
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Flags.Filter.NewerThan > 0 && time.Since(info.ModTime) >= r.Flags.Filter.NewerThan {
|
if r.Flags.Filter.NewerThan > 0 && time.Since(info.ModTime) >= r.Flags.Filter.NewerThan.D() {
|
||||||
// skip all objects that are older than specified newer duration
|
// skip all objects that are older than specified newer duration
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1115,7 +1119,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
slowCh = make(chan itemOrErr[ObjectInfo], 100)
|
slowCh = make(chan itemOrErr[ObjectInfo], 100)
|
||||||
)
|
)
|
||||||
|
|
||||||
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
if r.Source.Snowball.Disable != nil && !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
||||||
go func() {
|
go func() {
|
||||||
// Snowball currently needs the high level minio-go Client, not the Core one
|
// Snowball currently needs the high level minio-go Client, not the Core one
|
||||||
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
||||||
@@ -1125,7 +1129,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
BucketLookup: lookupStyle(r.Target.Path),
|
BucketLookup: lookupStyle(r.Target.Path),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
batchLogIf(ctx, err)
|
batchLogOnceIf(ctx, err, job.ID+"miniogo.New")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1135,8 +1139,8 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
batch := make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
batch := make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
||||||
writeFn := func(batch []ObjectInfo) {
|
writeFn := func(batch []ObjectInfo) {
|
||||||
if len(batch) > 0 {
|
if len(batch) > 0 {
|
||||||
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
if err := r.writeAsArchive(ctx, api, cl, batch, r.Target.Prefix); err != nil {
|
||||||
batchLogIf(ctx, err)
|
batchLogOnceIf(ctx, err, job.ID+"writeAsArchive")
|
||||||
for _, b := range batch {
|
for _, b := range batch {
|
||||||
slowCh <- itemOrErr[ObjectInfo]{Item: b}
|
slowCh <- itemOrErr[ObjectInfo]{Item: b}
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1148,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
|
ri.trackCurrentBucketBatch(r.Source.Bucket, batch)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogOnceIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job), job.ID+"updateAfter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1204,7 +1208,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
for res := range slowCh {
|
for res := range slowCh {
|
||||||
if res.Err != nil {
|
if res.Err != nil {
|
||||||
ri.Failed = true
|
ri.Failed = true
|
||||||
batchLogIf(ctx, res.Err)
|
batchLogOnceIf(ctx, res.Err, job.ID+"res.Err")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result := res.Item
|
result := res.Item
|
||||||
@@ -1231,7 +1235,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
stopFn(result, err)
|
stopFn(result, err)
|
||||||
batchLogIf(ctx, err)
|
batchLogOnceIf(ctx, err, job.ID+"ReplicateToTarget")
|
||||||
success = false
|
success = false
|
||||||
} else {
|
} else {
|
||||||
stopFn(result, nil)
|
stopFn(result, nil)
|
||||||
@@ -1239,7 +1243,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
ri.trackCurrentBucketObject(r.Source.Bucket, result, success, attempts)
|
ri.trackCurrentBucketObject(r.Source.Bucket, result, success, attempts)
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk after every 10secs.
|
// persist in-memory state to disk after every 10secs.
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
batchLogOnceIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job), job.ID+"updateAfter2")
|
||||||
|
|
||||||
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
if wait := globalBatchConfig.ReplicationWait(); wait > 0 {
|
||||||
time.Sleep(wait)
|
time.Sleep(wait)
|
||||||
@@ -1254,10 +1258,10 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
|
|
||||||
globalBatchJobsMetrics.save(job.ID, ri)
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
// persist in-memory state to disk.
|
// persist in-memory state to disk.
|
||||||
batchLogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
batchLogOnceIf(ctx, ri.updateAfter(ctx, api, 0, job), job.ID+"updateAfter3")
|
||||||
|
|
||||||
if err := r.Notify(ctx, ri); err != nil {
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
batchLogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
batchLogOnceIf(ctx, fmt.Errorf("unable to notify %v", err), job.ID+"notify")
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
@@ -1567,6 +1571,9 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
|
|||||||
writeErrorResponseJSON(ctx, w, toAPIError(ctx, result.Err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, result.Err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(result.Item.Name, batchJobReportsPrefix+slashSeparator) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
req := &BatchJobRequest{}
|
req := &BatchJobRequest{}
|
||||||
if err := req.load(ctx, objectAPI, result.Item.Name); err != nil {
|
if err := req.load(ctx, objectAPI, result.Item.Name); err != nil {
|
||||||
if !errors.Is(err, errNoSuchJob) {
|
if !errors.Is(err, errNoSuchJob) {
|
||||||
@@ -1883,6 +1890,9 @@ func (j *BatchJobPool) resume(randomWait func() time.Duration) {
|
|||||||
batchLogIf(j.ctx, result.Err)
|
batchLogIf(j.ctx, result.Err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(result.Item.Name, batchJobReportsPrefix+slashSeparator) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// ignore batch-replicate.bin and batch-rotate.bin entries
|
// ignore batch-replicate.bin and batch-rotate.bin entries
|
||||||
if strings.HasSuffix(result.Item.Name, slashSeparator) {
|
if strings.HasSuffix(result.Item.Name, slashSeparator) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
miniogo "github.com/minio/minio-go/v7"
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
|
"github.com/minio/pkg/v3/xtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate msgp -file $GOFILE
|
//go:generate msgp -file $GOFILE
|
||||||
@@ -65,8 +65,8 @@ import (
|
|||||||
|
|
||||||
// BatchReplicateFilter holds all the filters currently supported for batch replication
|
// BatchReplicateFilter holds all the filters currently supported for batch replication
|
||||||
type BatchReplicateFilter struct {
|
type BatchReplicateFilter struct {
|
||||||
NewerThan time.Duration `yaml:"newerThan,omitempty" json:"newerThan"`
|
NewerThan xtime.Duration `yaml:"newerThan,omitempty" json:"newerThan"`
|
||||||
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
OlderThan xtime.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
||||||
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
||||||
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
||||||
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
||||||
|
|||||||
@@ -1409,13 +1409,13 @@ func (z *BatchReplicateFilter) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "NewerThan":
|
case "NewerThan":
|
||||||
z.NewerThan, err = dc.ReadDuration()
|
err = z.NewerThan.DecodeMsg(dc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "NewerThan")
|
err = msgp.WrapError(err, "NewerThan")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "OlderThan":
|
case "OlderThan":
|
||||||
z.OlderThan, err = dc.ReadDuration()
|
err = z.OlderThan.DecodeMsg(dc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OlderThan")
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
return
|
return
|
||||||
@@ -1489,7 +1489,7 @@ func (z *BatchReplicateFilter) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteDuration(z.NewerThan)
|
err = z.NewerThan.EncodeMsg(en)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "NewerThan")
|
err = msgp.WrapError(err, "NewerThan")
|
||||||
return
|
return
|
||||||
@@ -1499,7 +1499,7 @@ func (z *BatchReplicateFilter) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteDuration(z.OlderThan)
|
err = z.OlderThan.EncodeMsg(en)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OlderThan")
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
return
|
return
|
||||||
@@ -1567,10 +1567,18 @@ func (z *BatchReplicateFilter) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// map header, size 6
|
// map header, size 6
|
||||||
// string "NewerThan"
|
// string "NewerThan"
|
||||||
o = append(o, 0x86, 0xa9, 0x4e, 0x65, 0x77, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
o = append(o, 0x86, 0xa9, 0x4e, 0x65, 0x77, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
||||||
o = msgp.AppendDuration(o, z.NewerThan)
|
o, err = z.NewerThan.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "NewerThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
// string "OlderThan"
|
// string "OlderThan"
|
||||||
o = append(o, 0xa9, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
o = append(o, 0xa9, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e)
|
||||||
o = msgp.AppendDuration(o, z.OlderThan)
|
o, err = z.OlderThan.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
|
return
|
||||||
|
}
|
||||||
// string "CreatedAfter"
|
// string "CreatedAfter"
|
||||||
o = append(o, 0xac, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x66, 0x74, 0x65, 0x72)
|
o = append(o, 0xac, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x66, 0x74, 0x65, 0x72)
|
||||||
o = msgp.AppendTime(o, z.CreatedAfter)
|
o = msgp.AppendTime(o, z.CreatedAfter)
|
||||||
@@ -1619,13 +1627,13 @@ func (z *BatchReplicateFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "NewerThan":
|
case "NewerThan":
|
||||||
z.NewerThan, bts, err = msgp.ReadDurationBytes(bts)
|
bts, err = z.NewerThan.UnmarshalMsg(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "NewerThan")
|
err = msgp.WrapError(err, "NewerThan")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "OlderThan":
|
case "OlderThan":
|
||||||
z.OlderThan, bts, err = msgp.ReadDurationBytes(bts)
|
bts, err = z.OlderThan.UnmarshalMsg(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OlderThan")
|
err = msgp.WrapError(err, "OlderThan")
|
||||||
return
|
return
|
||||||
@@ -1694,7 +1702,7 @@ func (z *BatchReplicateFilter) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *BatchReplicateFilter) Msgsize() (s int) {
|
func (z *BatchReplicateFilter) Msgsize() (s int) {
|
||||||
s = 1 + 10 + msgp.DurationSize + 10 + msgp.DurationSize + 13 + msgp.TimeSize + 14 + msgp.TimeSize + 5 + msgp.ArrayHeaderSize
|
s = 1 + 10 + z.NewerThan.Msgsize() + 10 + z.OlderThan.Msgsize() + 13 + msgp.TimeSize + 14 + msgp.TimeSize + 5 + msgp.ArrayHeaderSize
|
||||||
for za0001 := range z.Tags {
|
for za0001 := range z.Tags {
|
||||||
s += z.Tags[za0001].Msgsize()
|
s += z.Tags[za0001].Msgsize()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) 2015-2024 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseBatchJobReplicate(t *testing.T) {
|
||||||
|
replicateYaml := `
|
||||||
|
replicate:
|
||||||
|
apiVersion: v1
|
||||||
|
# source of the objects to be replicated
|
||||||
|
source:
|
||||||
|
type: minio # valid values are "s3" or "minio"
|
||||||
|
bucket: mytest
|
||||||
|
prefix: object-prefix1 # 'PREFIX' is optional
|
||||||
|
# If your source is the 'local' alias specified to 'mc batch start', then the 'endpoint' and 'credentials' fields are optional and can be omitted
|
||||||
|
# Either the 'source' or 'remote' *must* be the "local" deployment
|
||||||
|
# endpoint: "http://127.0.0.1:9000"
|
||||||
|
# # path: "on|off|auto" # "on" enables path-style bucket lookup. "off" enables virtual host (DNS)-style bucket lookup. Defaults to "auto"
|
||||||
|
# credentials:
|
||||||
|
# accessKey: minioadmin # Required
|
||||||
|
# secretKey: minioadmin # Required
|
||||||
|
# # sessionToken: SESSION-TOKEN # Optional only available when rotating credentials are used
|
||||||
|
snowball: # automatically activated if the source is local
|
||||||
|
disable: true # optionally turn-off snowball archive transfer
|
||||||
|
# batch: 100 # upto this many objects per archive
|
||||||
|
# inmemory: true # indicates if the archive must be staged locally or in-memory
|
||||||
|
# compress: false # S2/Snappy compressed archive
|
||||||
|
# smallerThan: 5MiB # create archive for all objects smaller than 5MiB
|
||||||
|
# skipErrs: false # skips any source side read() errors
|
||||||
|
|
||||||
|
# target where the objects must be replicated
|
||||||
|
target:
|
||||||
|
type: minio # valid values are "s3" or "minio"
|
||||||
|
bucket: mytest
|
||||||
|
prefix: stage # 'PREFIX' is optional
|
||||||
|
# If your source is the 'local' alias specified to 'mc batch start', then the 'endpoint' and 'credentials' fields are optional and can be omitted
|
||||||
|
|
||||||
|
# Either the 'source' or 'remote' *must* be the "local" deployment
|
||||||
|
endpoint: "http://127.0.0.1:9001"
|
||||||
|
# path: "on|off|auto" # "on" enables path-style bucket lookup. "off" enables virtual host (DNS)-style bucket lookup. Defaults to "auto"
|
||||||
|
credentials:
|
||||||
|
accessKey: minioadmin
|
||||||
|
secretKey: minioadmin
|
||||||
|
# sessionToken: SESSION-TOKEN # Optional only available when rotating credentials are used
|
||||||
|
|
||||||
|
# NOTE: All flags are optional
|
||||||
|
# - filtering criteria only applies for all source objects match the criteria
|
||||||
|
# - configurable notification endpoints
|
||||||
|
# - configurable retries for the job (each retry skips successfully previously replaced objects)
|
||||||
|
flags:
|
||||||
|
filter:
|
||||||
|
newerThan: "7d10h31s" # 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'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
`
|
||||||
|
var job BatchJobRequest
|
||||||
|
err := yaml.Unmarshal([]byte(replicateYaml), &job)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to parse batch-job-replicate yaml", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -389,6 +389,17 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
|||||||
stopFn(result, err)
|
stopFn(result, err)
|
||||||
batchLogIf(ctx, err)
|
batchLogIf(ctx, err)
|
||||||
success = false
|
success = false
|
||||||
|
if attempts >= retryAttempts {
|
||||||
|
auditOptions := AuditLogOptions{
|
||||||
|
Event: "KeyRotate",
|
||||||
|
APIName: "StartBatchJob",
|
||||||
|
Bucket: result.Bucket,
|
||||||
|
Object: result.Name,
|
||||||
|
VersionID: result.VersionID,
|
||||||
|
Error: err.Error(),
|
||||||
|
}
|
||||||
|
auditLogInternal(ctx, auditOptions)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
stopFn(result, nil)
|
stopFn(result, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWr
|
|||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, true)
|
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, &validateReplicationDestinationOptions{CheckRemoteBucket: true})
|
||||||
if apiErr != noError {
|
if apiErr != noError {
|
||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
@@ -559,7 +559,7 @@ func (api objectAPIHandlers) ValidateBucketReplicationCredsHandler(w http.Respon
|
|||||||
lockEnabled = lcfg.Enabled()
|
lockEnabled = lcfg.Enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, true)
|
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, &validateReplicationDestinationOptions{CheckRemoteBucket: true})
|
||||||
if apiErr != noError {
|
if apiErr != noError {
|
||||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
|
|||||||
+107
-58
@@ -91,7 +91,10 @@ func getReplicationConfig(ctx context.Context, bucketName string) (rc *replicati
|
|||||||
|
|
||||||
// validateReplicationDestination returns error if replication destination bucket missing or not configured
|
// validateReplicationDestination returns error if replication destination bucket missing or not configured
|
||||||
// It also returns true if replication destination is same as this server.
|
// It also returns true if replication destination is same as this server.
|
||||||
func validateReplicationDestination(ctx context.Context, bucket string, rCfg *replication.Config, checkRemote bool) (bool, APIError) {
|
func validateReplicationDestination(ctx context.Context, bucket string, rCfg *replication.Config, opts *validateReplicationDestinationOptions) (bool, APIError) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = &validateReplicationDestinationOptions{}
|
||||||
|
}
|
||||||
var arns []string
|
var arns []string
|
||||||
if rCfg.RoleArn != "" {
|
if rCfg.RoleArn != "" {
|
||||||
arns = append(arns, rCfg.RoleArn)
|
arns = append(arns, rCfg.RoleArn)
|
||||||
@@ -113,7 +116,7 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
|
|||||||
if clnt == nil {
|
if clnt == nil {
|
||||||
return sameTarget, toAPIError(ctx, BucketRemoteTargetNotFound{Bucket: bucket})
|
return sameTarget, toAPIError(ctx, BucketRemoteTargetNotFound{Bucket: bucket})
|
||||||
}
|
}
|
||||||
if checkRemote { // validate remote bucket
|
if opts.CheckRemoteBucket { // validate remote bucket
|
||||||
found, err := clnt.BucketExists(ctx, arn.Bucket)
|
found, err := clnt.BucketExists(ctx, arn.Bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sameTarget, errorCodes.ToAPIErrWithErr(ErrRemoteDestinationNotFoundError, err)
|
return sameTarget, errorCodes.ToAPIErrWithErr(ErrRemoteDestinationNotFoundError, err)
|
||||||
@@ -133,24 +136,30 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// validate replication ARN against target endpoint
|
// if checked bucket, then check the ready is unnecessary
|
||||||
c := globalBucketTargetSys.GetRemoteTargetClient(bucket, arnStr)
|
if !opts.CheckRemoteBucket && opts.CheckReady {
|
||||||
if c != nil {
|
endpoint := clnt.EndpointURL().String()
|
||||||
if err := checkRemoteEndpoint(ctx, c.EndpointURL()); err != nil {
|
if errInt, ok := opts.checkReadyErr.Load(endpoint); !ok {
|
||||||
|
err = checkRemoteEndpoint(ctx, clnt.EndpointURL())
|
||||||
|
opts.checkReadyErr.Store(endpoint, err)
|
||||||
|
} else {
|
||||||
|
if errInt == nil {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
err = errInt.(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case BucketRemoteIdenticalToSource:
|
case BucketRemoteIdenticalToSource:
|
||||||
return true, errorCodes.ToAPIErrWithErr(ErrBucketRemoteIdenticalToSource, fmt.Errorf("remote target endpoint %s is self referential", c.EndpointURL().String()))
|
return true, errorCodes.ToAPIErrWithErr(ErrBucketRemoteIdenticalToSource, fmt.Errorf("remote target endpoint %s is self referential", clnt.EndpointURL().String()))
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.EndpointURL().String() == clnt.EndpointURL().String() {
|
// validate replication ARN against target endpoint
|
||||||
selfTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort)
|
selfTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort)
|
||||||
if !sameTarget {
|
if !sameTarget {
|
||||||
sameTarget = selfTarget
|
sameTarget = selfTarget
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(arns) == 0 {
|
if len(arns) == 0 {
|
||||||
@@ -765,21 +774,6 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCRCMeta(oi ObjectInfo, partNum int, h http.Header) map[string]string {
|
|
||||||
meta := make(map[string]string)
|
|
||||||
cs := oi.decryptChecksums(partNum, h)
|
|
||||||
for k, v := range cs {
|
|
||||||
cksum := hash.NewChecksumString(k, v)
|
|
||||||
if cksum == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cksum.Valid() {
|
|
||||||
meta[cksum.Type.Key()] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, partNum int) (putOpts minio.PutObjectOptions, err error) {
|
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, partNum int) (putOpts minio.PutObjectOptions, err error) {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
|
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
|
||||||
@@ -788,11 +782,6 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
|
|||||||
// In case of SSE-C objects copy the allowed internal headers as well
|
// In case of SSE-C objects copy the allowed internal headers as well
|
||||||
if !isSSEC || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
if !isSSEC || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
if strings.EqualFold(k, ReservedMetadataPrefixLower+"crc") {
|
|
||||||
for k, v := range getCRCMeta(objInfo, partNum, nil) {
|
|
||||||
meta[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isStandardHeader(k) {
|
if isStandardHeader(k) {
|
||||||
@@ -811,11 +800,15 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
|
|||||||
if isSSEC {
|
if isSSEC {
|
||||||
meta[ReplicationSsecChecksumHeader] = base64.StdEncoding.EncodeToString(objInfo.Checksum)
|
meta[ReplicationSsecChecksumHeader] = base64.StdEncoding.EncodeToString(objInfo.Checksum)
|
||||||
} else {
|
} else {
|
||||||
for k, v := range getCRCMeta(objInfo, 0, nil) {
|
for _, pi := range objInfo.Parts {
|
||||||
|
if pi.Number == partNum {
|
||||||
|
for k, v := range pi.Checksums {
|
||||||
meta[k] = v
|
meta[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
||||||
sc = objInfo.StorageClass
|
sc = objInfo.StorageClass
|
||||||
@@ -1306,7 +1299,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
|||||||
HeaderSize: headerSize,
|
HeaderSize: headerSize,
|
||||||
}
|
}
|
||||||
newCtx := ctx
|
newCtx := ctx
|
||||||
if globalBucketMonitor.IsThrottled(bucket, tgt.ARN) {
|
if globalBucketMonitor.IsThrottled(bucket, tgt.ARN) && objInfo.Size < minLargeObjSize {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
newCtx, cancel = context.WithTimeout(ctx, throttleDeadline)
|
newCtx, cancel = context.WithTimeout(ctx, throttleDeadline)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -1581,7 +1574,7 @@ applyAction:
|
|||||||
HeaderSize: headerSize,
|
HeaderSize: headerSize,
|
||||||
}
|
}
|
||||||
newCtx := ctx
|
newCtx := ctx
|
||||||
if globalBucketMonitor.IsThrottled(bucket, tgt.ARN) {
|
if globalBucketMonitor.IsThrottled(bucket, tgt.ARN) && objInfo.Size < minLargeObjSize {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
newCtx, cancel = context.WithTimeout(ctx, throttleDeadline)
|
newCtx, cancel = context.WithTimeout(ctx, throttleDeadline)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -1666,8 +1659,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
|||||||
cHeader := http.Header{}
|
cHeader := http.Header{}
|
||||||
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
|
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
|
||||||
if !isSSEC {
|
if !isSSEC {
|
||||||
crc := getCRCMeta(objInfo, partInfo.Number, nil) // No SSE-C keys here.
|
for k, v := range partInfo.Checksums {
|
||||||
for k, v := range crc {
|
|
||||||
cHeader.Add(k, v)
|
cHeader.Add(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1803,12 +1795,15 @@ var (
|
|||||||
type ReplicationPool struct {
|
type ReplicationPool struct {
|
||||||
// atomic ops:
|
// atomic ops:
|
||||||
activeWorkers int32
|
activeWorkers int32
|
||||||
|
activeLrgWorkers int32
|
||||||
activeMRFWorkers int32
|
activeMRFWorkers int32
|
||||||
|
|
||||||
objLayer ObjectLayer
|
objLayer ObjectLayer
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
priority string
|
priority string
|
||||||
maxWorkers int
|
maxWorkers int
|
||||||
|
maxLWorkers int
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
mrfMU sync.Mutex
|
mrfMU sync.Mutex
|
||||||
resyncer *replicationResyncer
|
resyncer *replicationResyncer
|
||||||
@@ -1882,9 +1877,13 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
|||||||
if maxWorkers > 0 && failedWorkers > maxWorkers {
|
if maxWorkers > 0 && failedWorkers > maxWorkers {
|
||||||
failedWorkers = maxWorkers
|
failedWorkers = maxWorkers
|
||||||
}
|
}
|
||||||
|
maxLWorkers := LargeWorkerCount
|
||||||
|
if opts.MaxLWorkers > 0 {
|
||||||
|
maxLWorkers = opts.MaxLWorkers
|
||||||
|
}
|
||||||
pool := &ReplicationPool{
|
pool := &ReplicationPool{
|
||||||
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
||||||
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
|
lrgworkers: make([]chan ReplicationWorkerOperation, 0, maxLWorkers),
|
||||||
mrfReplicaCh: make(chan ReplicationWorkerOperation, 100000),
|
mrfReplicaCh: make(chan ReplicationWorkerOperation, 100000),
|
||||||
mrfWorkerKillCh: make(chan struct{}, failedWorkers),
|
mrfWorkerKillCh: make(chan struct{}, failedWorkers),
|
||||||
resyncer: newresyncer(),
|
resyncer: newresyncer(),
|
||||||
@@ -1894,9 +1893,10 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
|||||||
objLayer: o,
|
objLayer: o,
|
||||||
priority: priority,
|
priority: priority,
|
||||||
maxWorkers: maxWorkers,
|
maxWorkers: maxWorkers,
|
||||||
|
maxLWorkers: maxLWorkers,
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.AddLargeWorkers()
|
pool.ResizeLrgWorkers(maxLWorkers, 0)
|
||||||
pool.ResizeWorkers(workers, 0)
|
pool.ResizeWorkers(workers, 0)
|
||||||
pool.ResizeFailedWorkers(failedWorkers)
|
pool.ResizeFailedWorkers(failedWorkers)
|
||||||
go pool.resyncer.PersistToDisk(ctx, o)
|
go pool.resyncer.PersistToDisk(ctx, o)
|
||||||
@@ -1975,23 +1975,8 @@ func (p *ReplicationPool) AddWorker(input <-chan ReplicationWorkerOperation, opT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLargeWorkers adds a static number of workers to handle large uploads
|
|
||||||
func (p *ReplicationPool) AddLargeWorkers() {
|
|
||||||
for i := 0; i < LargeWorkerCount; i++ {
|
|
||||||
p.lrgworkers = append(p.lrgworkers, make(chan ReplicationWorkerOperation, 100000))
|
|
||||||
i := i
|
|
||||||
go p.AddLargeWorker(p.lrgworkers[i])
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
<-p.ctx.Done()
|
|
||||||
for i := 0; i < LargeWorkerCount; i++ {
|
|
||||||
xioutil.SafeClose(p.lrgworkers[i])
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLargeWorker adds a replication worker to the static pool for large uploads.
|
// AddLargeWorker adds a replication worker to the static pool for large uploads.
|
||||||
func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation) {
|
func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation, opTracker *int32) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
@@ -2002,11 +1987,23 @@ func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation
|
|||||||
}
|
}
|
||||||
switch v := oi.(type) {
|
switch v := oi.(type) {
|
||||||
case ReplicateObjectInfo:
|
case ReplicateObjectInfo:
|
||||||
|
if opTracker != nil {
|
||||||
|
atomic.AddInt32(opTracker, 1)
|
||||||
|
}
|
||||||
globalReplicationStats.incQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
globalReplicationStats.incQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
||||||
replicateObject(p.ctx, v, p.objLayer)
|
replicateObject(p.ctx, v, p.objLayer)
|
||||||
globalReplicationStats.decQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
globalReplicationStats.decQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
||||||
|
if opTracker != nil {
|
||||||
|
atomic.AddInt32(opTracker, -1)
|
||||||
|
}
|
||||||
case DeletedObjectReplicationInfo:
|
case DeletedObjectReplicationInfo:
|
||||||
|
if opTracker != nil {
|
||||||
|
atomic.AddInt32(opTracker, 1)
|
||||||
|
}
|
||||||
replicateDelete(p.ctx, v, p.objLayer)
|
replicateDelete(p.ctx, v, p.objLayer)
|
||||||
|
if opTracker != nil {
|
||||||
|
atomic.AddInt32(opTracker, -1)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bugLogIf(p.ctx, fmt.Errorf("unknown replication type: %T", oi), "unknown-replicate-type")
|
bugLogIf(p.ctx, fmt.Errorf("unknown replication type: %T", oi), "unknown-replicate-type")
|
||||||
}
|
}
|
||||||
@@ -2014,6 +2011,30 @@ func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResizeLrgWorkers sets replication workers pool for large transfers(>=128MiB) to new size.
|
||||||
|
// checkOld can be set to an expected value.
|
||||||
|
// If the worker count changed
|
||||||
|
func (p *ReplicationPool) ResizeLrgWorkers(n, checkOld int) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if (checkOld > 0 && len(p.lrgworkers) != checkOld) || n == len(p.lrgworkers) || n < 1 {
|
||||||
|
// Either already satisfied or worker count changed while we waited for the lock.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for len(p.lrgworkers) < n {
|
||||||
|
input := make(chan ReplicationWorkerOperation, 100000)
|
||||||
|
p.lrgworkers = append(p.lrgworkers, input)
|
||||||
|
|
||||||
|
go p.AddLargeWorker(input, &p.activeLrgWorkers)
|
||||||
|
}
|
||||||
|
for len(p.lrgworkers) > n {
|
||||||
|
worker := p.lrgworkers[len(p.lrgworkers)-1]
|
||||||
|
p.lrgworkers = p.lrgworkers[:len(p.lrgworkers)-1]
|
||||||
|
xioutil.SafeClose(worker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ActiveWorkers returns the number of active workers handling replication traffic.
|
// ActiveWorkers returns the number of active workers handling replication traffic.
|
||||||
func (p *ReplicationPool) ActiveWorkers() int {
|
func (p *ReplicationPool) ActiveWorkers() int {
|
||||||
return int(atomic.LoadInt32(&p.activeWorkers))
|
return int(atomic.LoadInt32(&p.activeWorkers))
|
||||||
@@ -2024,6 +2045,11 @@ func (p *ReplicationPool) ActiveMRFWorkers() int {
|
|||||||
return int(atomic.LoadInt32(&p.activeMRFWorkers))
|
return int(atomic.LoadInt32(&p.activeMRFWorkers))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActiveLrgWorkers returns the number of active workers handling traffic > 128MiB object size.
|
||||||
|
func (p *ReplicationPool) ActiveLrgWorkers() int {
|
||||||
|
return int(atomic.LoadInt32(&p.activeLrgWorkers))
|
||||||
|
}
|
||||||
|
|
||||||
// ResizeWorkers sets replication workers pool to new size.
|
// ResizeWorkers sets replication workers pool to new size.
|
||||||
// checkOld can be set to an expected value.
|
// checkOld can be set to an expected value.
|
||||||
// If the worker count changed
|
// If the worker count changed
|
||||||
@@ -2049,7 +2075,7 @@ func (p *ReplicationPool) ResizeWorkers(n, checkOld int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResizeWorkerPriority sets replication failed workers pool size
|
// ResizeWorkerPriority sets replication failed workers pool size
|
||||||
func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers int) {
|
func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers, maxLWorkers int) {
|
||||||
var workers, mrfWorkers int
|
var workers, mrfWorkers int
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
switch pri {
|
switch pri {
|
||||||
@@ -2076,11 +2102,15 @@ func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers int) {
|
|||||||
if maxWorkers > 0 && mrfWorkers > maxWorkers {
|
if maxWorkers > 0 && mrfWorkers > maxWorkers {
|
||||||
mrfWorkers = maxWorkers
|
mrfWorkers = maxWorkers
|
||||||
}
|
}
|
||||||
|
if maxLWorkers <= 0 {
|
||||||
|
maxLWorkers = LargeWorkerCount
|
||||||
|
}
|
||||||
p.priority = pri
|
p.priority = pri
|
||||||
p.maxWorkers = maxWorkers
|
p.maxWorkers = maxWorkers
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
p.ResizeWorkers(workers, 0)
|
p.ResizeWorkers(workers, 0)
|
||||||
p.ResizeFailedWorkers(mrfWorkers)
|
p.ResizeFailedWorkers(mrfWorkers)
|
||||||
|
p.ResizeLrgWorkers(maxLWorkers, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResizeFailedWorkers sets replication failed workers pool size
|
// ResizeFailedWorkers sets replication failed workers pool size
|
||||||
@@ -2124,9 +2154,18 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
|||||||
h := xxh3.HashString(ri.Bucket + ri.Name)
|
h := xxh3.HashString(ri.Bucket + ri.Name)
|
||||||
select {
|
select {
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
case p.lrgworkers[h%LargeWorkerCount] <- ri:
|
case p.lrgworkers[h%uint64(len(p.lrgworkers))] <- ri:
|
||||||
default:
|
default:
|
||||||
globalReplicationPool.queueMRFSave(ri.ToMRFEntry())
|
globalReplicationPool.queueMRFSave(ri.ToMRFEntry())
|
||||||
|
p.mu.RLock()
|
||||||
|
maxLWorkers := p.maxLWorkers
|
||||||
|
existing := len(p.lrgworkers)
|
||||||
|
p.mu.RUnlock()
|
||||||
|
maxLWorkers = min(maxLWorkers, LargeWorkerCount)
|
||||||
|
if p.ActiveLrgWorkers() < maxLWorkers {
|
||||||
|
workers := min(existing+1, maxLWorkers)
|
||||||
|
p.ResizeLrgWorkers(workers, existing)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2231,6 +2270,7 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
|||||||
type replicationPoolOpts struct {
|
type replicationPoolOpts struct {
|
||||||
Priority string
|
Priority string
|
||||||
MaxWorkers int
|
MaxWorkers int
|
||||||
|
MaxLWorkers int
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
||||||
@@ -3505,7 +3545,7 @@ func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntr
|
|||||||
}
|
}
|
||||||
|
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
for _, localDrive := range localDrives {
|
for _, localDrive := range localDrives {
|
||||||
@@ -3572,7 +3612,7 @@ func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
for _, localDrive := range localDrives {
|
for _, localDrive := range localDrives {
|
||||||
@@ -3693,3 +3733,12 @@ func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch <-chan
|
|||||||
|
|
||||||
return mrfCh, nil
|
return mrfCh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateReplicationDestinationOptions is used to configure the validation of the replication destination.
|
||||||
|
// validateReplicationDestination uses this to configure the validation.
|
||||||
|
type validateReplicationDestinationOptions struct {
|
||||||
|
CheckRemoteBucket bool
|
||||||
|
CheckReady bool
|
||||||
|
|
||||||
|
checkReadyErr sync.Map
|
||||||
|
}
|
||||||
|
|||||||
+6
-10
@@ -685,16 +685,6 @@ func loadEnvVarsFromFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if env.IsSet(kms.EnvKMSSecretKeyFile) {
|
|
||||||
kmsSecret, err := readFromSecret(env.Get(kms.EnvKMSSecretKeyFile, ""))
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err, "Unable to read the KMS secret key inherited from secret file")
|
|
||||||
}
|
|
||||||
if kmsSecret != "" {
|
|
||||||
os.Setenv(kms.EnvKMSSecretKey, kmsSecret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if env.IsSet(config.EnvConfigEnvFile) {
|
if env.IsSet(config.EnvConfigEnvFile) {
|
||||||
ekvs, err := minioEnvironFromFile(env.Get(config.EnvConfigEnvFile, ""))
|
ekvs, err := minioEnvironFromFile(env.Get(config.EnvConfigEnvFile, ""))
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
@@ -881,6 +871,12 @@ func loadRootCredentials() {
|
|||||||
} else {
|
} else {
|
||||||
globalActiveCred = auth.DefaultCredentials
|
globalActiveCred = auth.DefaultCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
globalNodeAuthToken, err = authenticateNode(globalActiveCred.AccessKey, globalActiveCred.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(err, "Unable to generate internode credentials")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize KMS global variable after valiadating and loading the configuration.
|
// Initialize KMS global variable after valiadating and loading the configuration.
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func initHelp() {
|
|||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.SubnetSubSys,
|
Key: config.SubnetSubSys,
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "register the cluster to MinIO SUBNET",
|
Description: "register Enterprise license for the cluster",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
|
|||||||
@@ -1172,6 +1172,14 @@ func (o *ObjectInfo) decryptChecksums(part int, h http.Header) map[string]string
|
|||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if part > 0 && !crypto.SSEC.IsEncrypted(o.UserDefined) {
|
||||||
|
// already decrypted in ToObjectInfo for multipart objects
|
||||||
|
for _, pi := range o.Parts {
|
||||||
|
if pi.Number == part {
|
||||||
|
return pi.Checksums
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
||||||
decrypted, err := o.metadataDecrypter(h)("object-checksum", data)
|
decrypted, err := o.metadataDecrypter(h)("object-checksum", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -147,6 +148,10 @@ func TestCommonTime(t *testing.T) {
|
|||||||
// TestListOnlineDisks - checks if listOnlineDisks and outDatedDisks
|
// TestListOnlineDisks - checks if listOnlineDisks and outDatedDisks
|
||||||
// are consistent with each other.
|
// are consistent with each other.
|
||||||
func TestListOnlineDisks(t *testing.T) {
|
func TestListOnlineDisks(t *testing.T) {
|
||||||
|
if runtime.GOOS == globalWindowsOSName {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -230,7 +235,7 @@ func TestListOnlineDisks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object := "object"
|
object := "object"
|
||||||
data := bytes.Repeat([]byte("a"), smallFileThreshold*16)
|
data := bytes.Repeat([]byte("a"), smallFileThreshold*32)
|
||||||
z := obj.(*erasureServerPools)
|
z := obj.(*erasureServerPools)
|
||||||
|
|
||||||
erasureDisks, err := z.GetDisks(0, 0)
|
erasureDisks, err := z.GetDisks(0, 0)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/amztime"
|
"github.com/minio/minio/internal/amztime"
|
||||||
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/hash/sha256"
|
"github.com/minio/minio/internal/hash/sha256"
|
||||||
@@ -173,6 +174,7 @@ func (fi FileInfo) ToObjectInfo(bucket, object string, versioned bool) ObjectInf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
objInfo.Checksum = fi.Checksum
|
objInfo.Checksum = fi.Checksum
|
||||||
|
objInfo.decryptPartsChecksums(nil)
|
||||||
objInfo.Inlined = fi.InlineData()
|
objInfo.Inlined = fi.InlineData()
|
||||||
// Success.
|
// Success.
|
||||||
return objInfo
|
return objInfo
|
||||||
@@ -456,6 +458,7 @@ func commonParity(parities []int, defaultParityCount int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int) {
|
func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int) {
|
||||||
|
totalShards := len(partsMetadata)
|
||||||
parities = make([]int, len(partsMetadata))
|
parities = make([]int, len(partsMetadata))
|
||||||
for index, metadata := range partsMetadata {
|
for index, metadata := range partsMetadata {
|
||||||
if errs[index] != nil {
|
if errs[index] != nil {
|
||||||
@@ -466,9 +469,13 @@ func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int)
|
|||||||
parities[index] = -1
|
parities[index] = -1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
//nolint:gocritic
|
||||||
// Delete marker or zero byte objects take highest parity.
|
// Delete marker or zero byte objects take highest parity.
|
||||||
if metadata.Deleted || metadata.Size == 0 {
|
if metadata.Deleted || metadata.Size == 0 {
|
||||||
parities[index] = len(partsMetadata) / 2
|
parities[index] = totalShards / 2
|
||||||
|
} else if metadata.TransitionStatus == lifecycle.TransitionComplete {
|
||||||
|
// For tiered objects, read quorum is N/2+1 to ensure simple majority on xl.meta. It is not equal to EcM because the data integrity is entrusted with the warm tier.
|
||||||
|
parities[index] = totalShards - (totalShards/2 + 1)
|
||||||
} else {
|
} else {
|
||||||
parities[index] = metadata.Erasure.ParityBlocks
|
parities[index] = metadata.Erasure.ParityBlocks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -359,3 +361,124 @@ func TestSkipTierFreeVersion(t *testing.T) {
|
|||||||
t.Fatal("Expected SkipTierFreeVersion to be set on FileInfo but wasn't")
|
t.Fatal("Expected SkipTierFreeVersion to be set on FileInfo but wasn't")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListObjectParities(t *testing.T) {
|
||||||
|
mkMetaArr := func(N, parity, agree int) []FileInfo {
|
||||||
|
fi := newFileInfo("obj-1", N-parity, parity)
|
||||||
|
fi.TransitionTier = "WARM-TIER"
|
||||||
|
fi.TransitionedObjName = mustGetUUID()
|
||||||
|
fi.TransitionStatus = "complete"
|
||||||
|
fi.Size = 1 << 20
|
||||||
|
|
||||||
|
metaArr := make([]FileInfo, N)
|
||||||
|
for i := range N {
|
||||||
|
fi.Erasure.Index = i + 1
|
||||||
|
metaArr[i] = fi
|
||||||
|
if i < agree {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metaArr[i].TransitionTier, metaArr[i].TransitionedObjName = "", ""
|
||||||
|
metaArr[i].TransitionStatus = ""
|
||||||
|
}
|
||||||
|
return metaArr
|
||||||
|
}
|
||||||
|
mkParities := func(N, agreedParity, disagreedParity, agree int) []int {
|
||||||
|
ps := make([]int, N)
|
||||||
|
for i := range N {
|
||||||
|
if i < agree {
|
||||||
|
ps[i] = agreedParity
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ps[i] = disagreedParity // disagree
|
||||||
|
}
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
mkTest := func(N, parity, agree int) (res struct {
|
||||||
|
metaArr []FileInfo
|
||||||
|
errs []error
|
||||||
|
parities []int
|
||||||
|
parity int
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
res.metaArr = mkMetaArr(N, parity, agree)
|
||||||
|
res.parities = mkParities(N, N-(N/2+1), parity, agree)
|
||||||
|
res.errs = make([]error, N)
|
||||||
|
if agree >= N/2+1 { // simple majority consensus
|
||||||
|
res.parity = N - (N/2 + 1)
|
||||||
|
} else {
|
||||||
|
res.parity = -1
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
nonTieredTest := func(N, parity, agree int) (res struct {
|
||||||
|
metaArr []FileInfo
|
||||||
|
errs []error
|
||||||
|
parities []int
|
||||||
|
parity int
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
fi := newFileInfo("obj-1", N-parity, parity)
|
||||||
|
fi.Size = 1 << 20
|
||||||
|
metaArr := make([]FileInfo, N)
|
||||||
|
parities := make([]int, N)
|
||||||
|
for i := range N {
|
||||||
|
fi.Erasure.Index = i + 1
|
||||||
|
metaArr[i] = fi
|
||||||
|
parities[i] = parity
|
||||||
|
if i < agree {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metaArr[i].Erasure.Index = 0 // creates invalid fi on remaining drives
|
||||||
|
parities[i] = -1 // invalid fi are assigned parity -1
|
||||||
|
}
|
||||||
|
res.metaArr = metaArr
|
||||||
|
res.parities = parities
|
||||||
|
res.errs = make([]error, N)
|
||||||
|
if agree >= N-parity {
|
||||||
|
res.parity = parity
|
||||||
|
} else {
|
||||||
|
res.parity = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
metaArr []FileInfo
|
||||||
|
errs []error
|
||||||
|
parities []int
|
||||||
|
parity int
|
||||||
|
}{
|
||||||
|
// More than simple majority consensus
|
||||||
|
mkTest(15, 3, 11),
|
||||||
|
// No simple majority consensus
|
||||||
|
mkTest(15, 3, 7),
|
||||||
|
// Exact simple majority consensus
|
||||||
|
mkTest(15, 3, 8),
|
||||||
|
// More than simple majority consensus
|
||||||
|
mkTest(16, 4, 11),
|
||||||
|
// No simple majority consensus
|
||||||
|
mkTest(16, 4, 8),
|
||||||
|
// Exact simple majority consensus
|
||||||
|
mkTest(16, 4, 9),
|
||||||
|
// non-tiered object require read quorum of EcM
|
||||||
|
nonTieredTest(15, 3, 12),
|
||||||
|
// non-tiered object with fewer than EcM in consensus
|
||||||
|
nonTieredTest(15, 3, 11),
|
||||||
|
// non-tiered object require read quorum of EcM
|
||||||
|
nonTieredTest(16, 4, 12),
|
||||||
|
// non-tiered object with fewer than EcM in consensus
|
||||||
|
nonTieredTest(16, 4, 11),
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("Test %d", i+1), func(t *testing.T) {
|
||||||
|
if got := listObjectParities(test.metaArr, test.errs); !slices.Equal(got, test.parities) {
|
||||||
|
t.Fatalf("Expected parities %v but got %v", test.parities, got)
|
||||||
|
}
|
||||||
|
if got := commonParity(test.parities, len(test.metaArr)/2); got != test.parity {
|
||||||
|
t.Fatalf("Expected common parity %v but got %v", test.parity, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+15
-49
@@ -103,10 +103,9 @@ func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object
|
|||||||
return fi, partsMetadata, err
|
return fi, partsMetadata, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes part.meta given by partName belonging to a multipart upload from minioMetaBucket
|
// cleanMultipartPath removes all extraneous files and parts from the multipart folder, this is used per CompleteMultipart.
|
||||||
func (er erasureObjects) removePartMeta(bucket, object, uploadID, dataDir string, partNumber int) {
|
// do not use this function outside of completeMultipartUpload()
|
||||||
uploadIDPath := er.getUploadIDDir(bucket, object, uploadID)
|
func (er erasureObjects) cleanupMultipartPath(ctx context.Context, paths ...string) {
|
||||||
curpartPath := pathJoin(uploadIDPath, dataDir, fmt.Sprintf("part.%d", partNumber))
|
|
||||||
storageDisks := er.getDisks()
|
storageDisks := er.getDisks()
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
g := errgroup.WithNErrs(len(storageDisks))
|
||||||
@@ -116,42 +115,7 @@ func (er erasureObjects) removePartMeta(bucket, object, uploadID, dataDir string
|
|||||||
}
|
}
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
_ = storageDisks[index].Delete(context.TODO(), minioMetaMultipartBucket, curpartPath+".meta", DeleteOptions{
|
_ = storageDisks[index].DeleteBulk(ctx, minioMetaMultipartBucket, paths...)
|
||||||
Recursive: false,
|
|
||||||
Immediate: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}, index)
|
|
||||||
}
|
|
||||||
g.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes part given by partName belonging to a multipart upload from minioMetaBucket
|
|
||||||
func (er erasureObjects) removeObjectPart(bucket, object, uploadID, dataDir string, partNumber int) {
|
|
||||||
uploadIDPath := er.getUploadIDDir(bucket, object, uploadID)
|
|
||||||
curpartPath := pathJoin(uploadIDPath, dataDir, fmt.Sprintf("part.%d", partNumber))
|
|
||||||
storageDisks := er.getDisks()
|
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(storageDisks))
|
|
||||||
for index, disk := range storageDisks {
|
|
||||||
if disk == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
index := index
|
|
||||||
g.Go(func() error {
|
|
||||||
// Ignoring failure to remove parts that weren't present in CompleteMultipartUpload
|
|
||||||
// requests. xl.meta is the authoritative source of truth on which parts constitute
|
|
||||||
// the object. The presence of parts that don't belong in the object doesn't affect correctness.
|
|
||||||
_ = storageDisks[index].Delete(context.TODO(), minioMetaMultipartBucket, curpartPath, DeleteOptions{
|
|
||||||
Recursive: false,
|
|
||||||
Immediate: false,
|
|
||||||
})
|
|
||||||
_ = storageDisks[index].Delete(context.TODO(), minioMetaMultipartBucket, curpartPath+".meta", DeleteOptions{
|
|
||||||
Recursive: false,
|
|
||||||
Immediate: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
@@ -159,7 +123,7 @@ func (er erasureObjects) removeObjectPart(bucket, object, uploadID, dataDir stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean-up the old multipart uploads. Should be run in a Go routine.
|
// Clean-up the old multipart uploads. Should be run in a Go routine.
|
||||||
func (er erasureObjects) cleanupStaleUploads(ctx context.Context, expiry time.Duration) {
|
func (er erasureObjects) cleanupStaleUploads(ctx context.Context) {
|
||||||
// run multiple cleanup's local to this server.
|
// run multiple cleanup's local to this server.
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for _, disk := range er.getLocalDisks() {
|
for _, disk := range er.getLocalDisks() {
|
||||||
@@ -167,7 +131,7 @@ func (er erasureObjects) cleanupStaleUploads(ctx context.Context, expiry time.Du
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(disk StorageAPI) {
|
go func(disk StorageAPI) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
er.cleanupStaleUploadsOnDisk(ctx, disk, expiry)
|
er.cleanupStaleUploadsOnDisk(ctx, disk)
|
||||||
}(disk)
|
}(disk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +157,7 @@ func (er erasureObjects) deleteAll(ctx context.Context, bucket, prefix string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the old multipart uploads on the given disk.
|
// Remove the old multipart uploads on the given disk.
|
||||||
func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk StorageAPI, expiry time.Duration) {
|
func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk StorageAPI) {
|
||||||
drivePath := disk.Endpoint().Path
|
drivePath := disk.Endpoint().Path
|
||||||
|
|
||||||
readDirFn(pathJoin(drivePath, minioMetaMultipartBucket), func(shaDir string, typ os.FileMode) error {
|
readDirFn(pathJoin(drivePath, minioMetaMultipartBucket), func(shaDir string, typ os.FileMode) error {
|
||||||
@@ -219,7 +183,7 @@ func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk Sto
|
|||||||
modTime = fi.ModTime
|
modTime = fi.ModTime
|
||||||
wait()
|
wait()
|
||||||
}
|
}
|
||||||
if time.Since(modTime) < expiry {
|
if time.Since(modTime) < globalAPIConfig.getStaleUploadsExpiry() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
||||||
@@ -238,7 +202,7 @@ func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk Sto
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Modtime is returned in the Created field. See (*xlStorage).StatVol
|
// Modtime is returned in the Created field. See (*xlStorage).StatVol
|
||||||
if time.Since(vi.Created) < expiry {
|
if time.Since(vi.Created) < globalAPIConfig.getStaleUploadsExpiry() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
||||||
@@ -267,7 +231,7 @@ func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk Sto
|
|||||||
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
||||||
return w.Run(func() error {
|
return w.Run(func() error {
|
||||||
wait := deleteMultipartCleanupSleeper.Timer(ctx)
|
wait := deleteMultipartCleanupSleeper.Timer(ctx)
|
||||||
if time.Since(vi.Created) > expiry {
|
if time.Since(vi.Created) > globalAPIConfig.getStaleUploadsExpiry() {
|
||||||
pathUUID := mustGetUUID()
|
pathUUID := mustGetUUID()
|
||||||
targetPath := pathJoin(drivePath, minioMetaTmpDeletedBucket, pathUUID)
|
targetPath := pathJoin(drivePath, minioMetaTmpDeletedBucket, pathUUID)
|
||||||
|
|
||||||
@@ -1359,10 +1323,10 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paths := make([]string, 0, len(currentFI.Parts))
|
||||||
// Remove parts that weren't present in CompleteMultipartUpload request.
|
// Remove parts that weren't present in CompleteMultipartUpload request.
|
||||||
for _, curpart := range currentFI.Parts {
|
for _, curpart := range currentFI.Parts {
|
||||||
// Remove part.meta which is not needed anymore.
|
paths = append(paths, pathJoin(uploadIDPath, currentFI.DataDir, fmt.Sprintf("part.%d.meta", curpart.Number)))
|
||||||
er.removePartMeta(bucket, object, uploadID, currentFI.DataDir, curpart.Number)
|
|
||||||
|
|
||||||
if objectPartIndex(fi.Parts, curpart.Number) == -1 {
|
if objectPartIndex(fi.Parts, curpart.Number) == -1 {
|
||||||
// Delete the missing part files. e.g,
|
// Delete the missing part files. e.g,
|
||||||
@@ -1371,10 +1335,12 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
// Request 3: PutObjectPart 2
|
// Request 3: PutObjectPart 2
|
||||||
// Request 4: CompleteMultipartUpload --part 2
|
// Request 4: CompleteMultipartUpload --part 2
|
||||||
// N.B. 1st part is not present. This part should be removed from the storage.
|
// N.B. 1st part is not present. This part should be removed from the storage.
|
||||||
er.removeObjectPart(bucket, object, uploadID, currentFI.DataDir, curpart.Number)
|
paths = append(paths, pathJoin(uploadIDPath, currentFI.DataDir, fmt.Sprintf("part.%d", curpart.Number)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
er.cleanupMultipartPath(ctx, paths...) // cleanup all part.N.meta, and skipped part.N's before final rename().
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
er.deleteAll(context.Background(), minioMetaMultipartBucket, uploadIDPath)
|
er.deleteAll(context.Background(), minioMetaMultipartBucket, uploadIDPath)
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if unlockOnDefer {
|
if unlockOnDefer {
|
||||||
unlockOnDefer = fi.InlineData()
|
unlockOnDefer = fi.InlineData() || len(fi.Data) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pr, pw := xioutil.WaitPipe()
|
pr, pw := xioutil.WaitPipe()
|
||||||
@@ -908,6 +908,8 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
}
|
}
|
||||||
|
|
||||||
rw.Lock()
|
rw.Lock()
|
||||||
|
// when its a versioned bucket and empty versionID - at totalResp == setDriveCount
|
||||||
|
// we must use rawFileInfo to resolve versions to figure out the latest version.
|
||||||
if opts.VersionID == "" && totalResp == er.setDriveCount {
|
if opts.VersionID == "" && totalResp == er.setDriveCount {
|
||||||
fi, onlineMeta, onlineDisks, modTime, etag, err = calcQuorum(pickLatestQuorumFilesInfo(ctx,
|
fi, onlineMeta, onlineDisks, modTime, etag, err = calcQuorum(pickLatestQuorumFilesInfo(ctx,
|
||||||
rawArr, errs, bucket, object, readData, opts.InclFreeVersions, true))
|
rawArr, errs, bucket, object, readData, opts.InclFreeVersions, true))
|
||||||
@@ -915,7 +917,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
fi, onlineMeta, onlineDisks, modTime, etag, err = calcQuorum(metaArr, errs)
|
fi, onlineMeta, onlineDisks, modTime, etag, err = calcQuorum(metaArr, errs)
|
||||||
}
|
}
|
||||||
rw.Unlock()
|
rw.Unlock()
|
||||||
if err == nil && fi.InlineData() {
|
if err == nil && (fi.InlineData() || len(fi.Data) > 0) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1399,7 +1401,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
writers := make([]io.Writer, len(onlineDisks))
|
writers := make([]io.Writer, len(onlineDisks))
|
||||||
var inlineBuffers []*bytes.Buffer
|
var inlineBuffers []*bytes.Buffer
|
||||||
if shardFileSize >= 0 {
|
if shardFileSize >= 0 {
|
||||||
if !opts.Versioned && shardFileSize < inlineBlock {
|
if !opts.Versioned && shardFileSize <= inlineBlock {
|
||||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||||
} else if shardFileSize < inlineBlock/8 {
|
} else if shardFileSize < inlineBlock/8 {
|
||||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||||
@@ -1407,7 +1409,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
} else {
|
} else {
|
||||||
// If compressed, use actual size to determine.
|
// If compressed, use actual size to determine.
|
||||||
if sz := erasure.ShardFileSize(data.ActualSize()); sz > 0 {
|
if sz := erasure.ShardFileSize(data.ActualSize()); sz > 0 {
|
||||||
if !opts.Versioned && sz < inlineBlock {
|
if !opts.Versioned && sz <= inlineBlock {
|
||||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||||
} else if sz < inlineBlock/8 {
|
} else if sz < inlineBlock/8 {
|
||||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||||
|
|||||||
+10
-11
@@ -168,7 +168,7 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
|||||||
|
|
||||||
if !globalIsDistErasure {
|
if !globalIsDistErasure {
|
||||||
globalLocalDrivesMu.Lock()
|
globalLocalDrivesMu.Lock()
|
||||||
globalLocalDrives = localDrives
|
globalLocalDrivesMap = make(map[string]StorageAPI, len(localDrives))
|
||||||
for _, drive := range localDrives {
|
for _, drive := range localDrives {
|
||||||
globalLocalDrivesMap[drive.Endpoint().String()] = drive
|
globalLocalDrivesMap[drive.Endpoint().String()] = drive
|
||||||
}
|
}
|
||||||
@@ -1526,15 +1526,13 @@ func (z *erasureServerPools) listObjectsGeneric(ctx context.Context, bucket, pre
|
|||||||
loi.NextMarker = last.Name
|
loi.NextMarker = last.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if merged.lastSkippedEntry != "" {
|
if loi.IsTruncated && merged.lastSkippedEntry > loi.NextMarker {
|
||||||
if merged.lastSkippedEntry > loi.NextMarker {
|
// An object hidden by ILM was found during a truncated listing. Since the number of entries
|
||||||
// An object hidden by ILM was found during listing. Since the number of entries
|
// fetched from drives is limited by max-keys, we should use the last ILM filtered entry
|
||||||
// fetched from drives is limited, set IsTruncated to true to ask the s3 client
|
// as a continuation token if it is lexially higher than the last visible object so that the
|
||||||
// to continue listing if it wishes in order to find if there is more objects.
|
// next call of WalkDir() with the max-keys can reach new objects not seen previously.
|
||||||
loi.IsTruncated = true
|
|
||||||
loi.NextMarker = merged.lastSkippedEntry
|
loi.NextMarker = merged.lastSkippedEntry
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if loi.NextMarker != "" {
|
if loi.NextMarker != "" {
|
||||||
loi.NextMarker = opts.encodeMarker(loi.NextMarker)
|
loi.NextMarker = opts.encodeMarker(loi.NextMarker)
|
||||||
@@ -2447,6 +2445,7 @@ const (
|
|||||||
type HealthOptions struct {
|
type HealthOptions struct {
|
||||||
Maintenance bool
|
Maintenance bool
|
||||||
DeploymentType string
|
DeploymentType string
|
||||||
|
NoLogging bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthResult returns the current state of the system, also
|
// HealthResult returns the current state of the system, also
|
||||||
@@ -2483,7 +2482,7 @@ func (hr HealthResult) String() string {
|
|||||||
if i == 0 {
|
if i == 0 {
|
||||||
str.WriteString(")")
|
str.WriteString(")")
|
||||||
} else {
|
} else {
|
||||||
str.WriteString("), ")
|
str.WriteString(") | ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return str.String()
|
return str.String()
|
||||||
@@ -2606,7 +2605,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
|||||||
})
|
})
|
||||||
|
|
||||||
healthy := erasureSetUpCount[poolIdx][setIdx].online >= poolWriteQuorums[poolIdx]
|
healthy := erasureSetUpCount[poolIdx][setIdx].online >= poolWriteQuorums[poolIdx]
|
||||||
if !healthy {
|
if !healthy && !opts.NoLogging {
|
||||||
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||||
fmt.Errorf("Write quorum could not be established on pool: %d, set: %d, expected write quorum: %d, drives-online: %d",
|
fmt.Errorf("Write quorum could not be established on pool: %d, set: %d, expected write quorum: %d, drives-online: %d",
|
||||||
poolIdx, setIdx, poolWriteQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online), logger.FatalKind)
|
poolIdx, setIdx, poolWriteQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online), logger.FatalKind)
|
||||||
@@ -2614,7 +2613,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
|||||||
result.Healthy = result.Healthy && healthy
|
result.Healthy = result.Healthy && healthy
|
||||||
|
|
||||||
healthyRead := erasureSetUpCount[poolIdx][setIdx].online >= poolReadQuorums[poolIdx]
|
healthyRead := erasureSetUpCount[poolIdx][setIdx].online >= poolReadQuorums[poolIdx]
|
||||||
if !healthyRead {
|
if !healthyRead && !opts.NoLogging {
|
||||||
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||||
fmt.Errorf("Read quorum could not be established on pool: %d, set: %d, expected read quorum: %d, drives-online: %d",
|
fmt.Errorf("Read quorum could not be established on pool: %d, set: %d, expected read quorum: %d, drives-online: %d",
|
||||||
poolIdx, setIdx, poolReadQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online))
|
poolIdx, setIdx, poolReadQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online))
|
||||||
|
|||||||
+8
-18
@@ -189,7 +189,7 @@ func (s *erasureSets) Legacy() (ok bool) {
|
|||||||
|
|
||||||
// connectDisks - attempt to connect all the endpoints, loads format
|
// connectDisks - attempt to connect all the endpoints, loads format
|
||||||
// and re-arranges the disks in proper position.
|
// and re-arranges the disks in proper position.
|
||||||
func (s *erasureSets) connectDisks() {
|
func (s *erasureSets) connectDisks(log bool) {
|
||||||
defer func() {
|
defer func() {
|
||||||
s.lastConnectDisksOpTime = time.Now()
|
s.lastConnectDisksOpTime = time.Now()
|
||||||
}()
|
}()
|
||||||
@@ -224,8 +224,10 @@ func (s *erasureSets) connectDisks() {
|
|||||||
if endpoint.IsLocal && errors.Is(err, errUnformattedDisk) {
|
if endpoint.IsLocal && errors.Is(err, errUnformattedDisk) {
|
||||||
globalBackgroundHealState.pushHealLocalDisks(endpoint)
|
globalBackgroundHealState.pushHealLocalDisks(endpoint)
|
||||||
} else if !errors.Is(err, errDriveIsRoot) {
|
} else if !errors.Is(err, errDriveIsRoot) {
|
||||||
|
if log {
|
||||||
printEndpointError(endpoint, err, true)
|
printEndpointError(endpoint, err, true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if disk.IsLocal() && disk.Healing() != nil {
|
if disk.IsLocal() && disk.Healing() != nil {
|
||||||
@@ -260,13 +262,7 @@ func (s *erasureSets) connectDisks() {
|
|||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
globalLocalSetDrives[s.poolIndex][setIndex][diskIndex] = disk
|
globalLocalSetDrives[s.poolIndex][setIndex][diskIndex] = disk
|
||||||
}
|
}
|
||||||
for i, ldisk := range globalLocalDrives {
|
globalLocalDrivesMap[disk.Endpoint().String()] = disk
|
||||||
_, k, l := ldisk.GetDiskLoc()
|
|
||||||
if k == setIndex && l == diskIndex {
|
|
||||||
globalLocalDrives[i] = disk
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
globalLocalDrivesMu.Unlock()
|
globalLocalDrivesMu.Unlock()
|
||||||
}
|
}
|
||||||
s.erasureDisksMu.Unlock()
|
s.erasureDisksMu.Unlock()
|
||||||
@@ -285,7 +281,7 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
|
|||||||
time.Sleep(time.Duration(r.Float64() * float64(time.Second)))
|
time.Sleep(time.Duration(r.Float64() * float64(time.Second)))
|
||||||
|
|
||||||
// Pre-emptively connect the disks if possible.
|
// Pre-emptively connect the disks if possible.
|
||||||
s.connectDisks()
|
s.connectDisks(false)
|
||||||
|
|
||||||
monitor := time.NewTimer(monitorInterval)
|
monitor := time.NewTimer(monitorInterval)
|
||||||
defer monitor.Stop()
|
defer monitor.Stop()
|
||||||
@@ -299,7 +295,7 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
|
|||||||
console.Debugln("running drive monitoring")
|
console.Debugln("running drive monitoring")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.connectDisks()
|
s.connectDisks(true)
|
||||||
|
|
||||||
// Reset the timer for next interval
|
// Reset the timer for next interval
|
||||||
monitor.Reset(monitorInterval)
|
monitor.Reset(monitorInterval)
|
||||||
@@ -529,7 +525,7 @@ func (s *erasureSets) cleanupStaleUploads(ctx context.Context) {
|
|||||||
if set == nil {
|
if set == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
set.cleanupStaleUploads(ctx, globalAPIConfig.getStaleUploadsExpiry())
|
set.cleanupStaleUploads(ctx)
|
||||||
}(set)
|
}(set)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -1133,13 +1129,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
|||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
globalLocalSetDrives[s.poolIndex][m][n] = disk
|
globalLocalSetDrives[s.poolIndex][m][n] = disk
|
||||||
}
|
}
|
||||||
for i, ldisk := range globalLocalDrives {
|
globalLocalDrivesMap[disk.Endpoint().String()] = disk
|
||||||
_, k, l := ldisk.GetDiskLoc()
|
|
||||||
if k == m && l == n {
|
|
||||||
globalLocalDrives[i] = disk
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
globalLocalDrivesMu.Unlock()
|
globalLocalDrivesMu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) 2015-2024 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/>.
|
||||||
|
|
||||||
|
//go:build fmtgen
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/zip"
|
||||||
|
"github.com/minio/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fmtGenFlags = []cli.Flag{
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "parity",
|
||||||
|
Usage: "specify erasure code parity",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "deployment-id",
|
||||||
|
Usage: "deployment-id of the MinIO cluster for which format.json is needed",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "address",
|
||||||
|
Value: ":" + GlobalMinioDefaultPort,
|
||||||
|
Usage: "bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname",
|
||||||
|
EnvVar: "MINIO_ADDRESS",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var fmtGenCmd = cli.Command{
|
||||||
|
Name: "fmt-gen",
|
||||||
|
Usage: "Generate format.json files for an erasure server pool",
|
||||||
|
Flags: append(fmtGenFlags, GlobalFlags...),
|
||||||
|
Action: fmtGenMain,
|
||||||
|
CustomHelpTemplate: `NAME:
|
||||||
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}DIR1 [DIR2..]
|
||||||
|
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}DIR{1...64}
|
||||||
|
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}DIR{1...64} DIR{65...128}
|
||||||
|
|
||||||
|
DIR:
|
||||||
|
DIR points to a directory on a filesystem. When you want to combine
|
||||||
|
multiple drives into a single large system, pass one directory per
|
||||||
|
filesystem separated by space. You may also use a '...' convention
|
||||||
|
to abbreviate the directory arguments. Remote directories in a
|
||||||
|
distributed setup are encoded as HTTP(s) URIs.
|
||||||
|
{{if .VisibleFlags}}
|
||||||
|
FLAGS:
|
||||||
|
{{range .VisibleFlags}}{{.}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
EXAMPLES:
|
||||||
|
1. Generate format.json.zip containing format.json files for all drives in a distributed MinIO server pool of 32 nodes with 32 drives each.
|
||||||
|
{{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32}
|
||||||
|
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func fmtGenMain(ctxt *cli.Context) {
|
||||||
|
deploymentID := ctxt.String("deployment-id")
|
||||||
|
err := buildServerCtxt(ctxt, &globalServerCtxt)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
handleCommonArgs(globalServerCtxt)
|
||||||
|
pools, _, err := createServerEndpoints(globalMinioAddr, globalServerCtxt.Layout.pools, globalServerCtxt.Layout.legacy)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zipFile, err := os.Create("format.json.zip")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create format.json.zip: %v", err)
|
||||||
|
}
|
||||||
|
defer zipFile.Close()
|
||||||
|
fmtZipW := zip.NewWriter(zipFile)
|
||||||
|
defer fmtZipW.Close()
|
||||||
|
for _, pool := range pools { // for each pool
|
||||||
|
setCount, setDriveCount := pool.SetCount, pool.DrivesPerSet
|
||||||
|
format := newFormatErasureV3(setCount, setDriveCount)
|
||||||
|
format.ID = deploymentID
|
||||||
|
for i := 0; i < setCount; i++ { // for each erasure set
|
||||||
|
for j := 0; j < setDriveCount; j++ {
|
||||||
|
newFormat := format.Clone()
|
||||||
|
newFormat.Erasure.This = format.Erasure.Sets[i][j]
|
||||||
|
if deploymentID != "" {
|
||||||
|
newFormat.ID = deploymentID
|
||||||
|
}
|
||||||
|
drive := pool.Endpoints[i*setDriveCount+j]
|
||||||
|
fmtBytes, err := json.Marshal(newFormat)
|
||||||
|
if err != nil {
|
||||||
|
//nolint:gocritic
|
||||||
|
log.Fatalf("failed to marshal format.json for %s: %v", drive.String(), err)
|
||||||
|
}
|
||||||
|
fmtJSON := filepath.Join(drive.Host, drive.Path, minioMetaBucket, "format.json")
|
||||||
|
embedFileInZip(fmtZipW, fmtJSON, fmtBytes, 0o600)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// # This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -15,9 +15,10 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//go:build go1.19
|
//go:build !fmtgen
|
||||||
// +build go1.19
|
|
||||||
|
|
||||||
package fips
|
package cmd
|
||||||
|
|
||||||
const go19 = true
|
import "github.com/minio/cli"
|
||||||
|
|
||||||
|
var fmtGenCmd cli.Command
|
||||||
@@ -247,11 +247,14 @@ func guessIsRPCReq(req *http.Request) bool {
|
|||||||
if req == nil {
|
if req == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if req.Method == http.MethodGet && req.URL != nil && req.URL.Path == grid.RoutePath {
|
if req.Method == http.MethodGet && req.URL != nil {
|
||||||
|
switch req.URL.Path {
|
||||||
|
case grid.RoutePath, grid.RouteLockPath:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return req.Method == http.MethodPost &&
|
return (req.Method == http.MethodPost || req.Method == http.MethodGet) &&
|
||||||
strings.HasPrefix(req.URL.Path, minioReservedBucketPath+SlashSeparator)
|
strings.HasPrefix(req.URL.Path, minioReservedBucketPath+SlashSeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +472,7 @@ func setBucketForwardingMiddleware(h http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
if globalDNSConfig == nil || !globalBucketFederation ||
|
if globalDNSConfig == nil || !globalBucketFederation ||
|
||||||
guessIsHealthCheckReq(r) || guessIsMetricsReq(r) ||
|
guessIsHealthCheckReq(r) || guessIsMetricsReq(r) ||
|
||||||
guessIsRPCReq(r) || guessIsLoginSTSReq(r) || isAdminReq(r) {
|
guessIsRPCReq(r) || guessIsLoginSTSReq(r) || isAdminReq(r) || isKMSReq(r) {
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,10 @@ func TestGuessIsRPC(t *testing.T) {
|
|||||||
r = &http.Request{
|
r = &http.Request{
|
||||||
Proto: "HTTP/1.1",
|
Proto: "HTTP/1.1",
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
|
URL: u,
|
||||||
}
|
}
|
||||||
if guessIsRPCReq(r) {
|
if !guessIsRPCReq(r) {
|
||||||
t.Fatal("Test shouldn't report as net/rpc for a non net/rpc request.")
|
t.Fatal("Test shouldn't fail for a possible net/rpc request.")
|
||||||
}
|
}
|
||||||
r = &http.Request{
|
r = &http.Request{
|
||||||
Proto: "HTTP/1.1",
|
Proto: "HTTP/1.1",
|
||||||
@@ -63,6 +64,14 @@ func TestGuessIsRPC(t *testing.T) {
|
|||||||
if !guessIsRPCReq(r) {
|
if !guessIsRPCReq(r) {
|
||||||
t.Fatal("Grid RPC path not detected")
|
t.Fatal("Grid RPC path not detected")
|
||||||
}
|
}
|
||||||
|
r = &http.Request{
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: &url.URL{Path: grid.RouteLockPath},
|
||||||
|
}
|
||||||
|
if !guessIsRPCReq(r) {
|
||||||
|
t.Fatal("Grid RPC path not detected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isHTTPHeaderSizeTooLargeTests = []struct {
|
var isHTTPHeaderSizeTooLargeTests = []struct {
|
||||||
|
|||||||
+12
-9
@@ -150,6 +150,11 @@ type healEntryResult struct {
|
|||||||
|
|
||||||
// healErasureSet lists and heals all objects in a specific erasure set
|
// healErasureSet lists and heals all objects in a specific erasure set
|
||||||
func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string, tracker *healingTracker) error {
|
func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string, tracker *healingTracker) error {
|
||||||
|
bgSeq, found := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
|
||||||
|
if !found {
|
||||||
|
return errors.New("no local healing sequence initialized, unable to heal the drive")
|
||||||
|
}
|
||||||
|
|
||||||
scanMode := madmin.HealNormalScan
|
scanMode := madmin.HealNormalScan
|
||||||
|
|
||||||
// Make sure to copy since `buckets slice`
|
// Make sure to copy since `buckets slice`
|
||||||
@@ -163,11 +168,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range healBuckets {
|
for _, bucket := range healBuckets {
|
||||||
_, err := objAPI.HealBucket(ctx, bucket, madmin.HealOpts{
|
if err := bgSeq.healBucket(objAPI, bucket, true); err != nil {
|
||||||
Recreate: true,
|
|
||||||
ScanMode: scanMode,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// Log bucket healing error if any, we shall retry again.
|
// Log bucket healing error if any, we shall retry again.
|
||||||
healingLogIf(ctx, err)
|
healingLogIf(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -264,10 +265,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
tracker.setBucket(bucket)
|
tracker.setBucket(bucket)
|
||||||
// Heal current bucket again in case if it is failed
|
// Heal current bucket again in case if it is failed
|
||||||
// in the beginning of erasure set healing
|
// in the beginning of erasure set healing
|
||||||
if _, err := objAPI.HealBucket(ctx, bucket, madmin.HealOpts{
|
if err := bgSeq.healBucket(objAPI, bucket, true); err != nil {
|
||||||
Recreate: true,
|
|
||||||
ScanMode: scanMode,
|
|
||||||
}); err != nil {
|
|
||||||
// Set this such that when we return this function
|
// Set this such that when we return this function
|
||||||
// we let the caller retry this disk again for the
|
// we let the caller retry this disk again for the
|
||||||
// buckets that failed healing.
|
// buckets that failed healing.
|
||||||
@@ -366,6 +364,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
case results <- result:
|
case results <- result:
|
||||||
|
bgSeq.countScanned(madmin.HealItemObject)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,8 +415,10 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
result = healEntryFailure(0)
|
result = healEntryFailure(0)
|
||||||
|
bgSeq.countFailed(madmin.HealItemObject)
|
||||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, entry.name, err))
|
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, entry.name, err))
|
||||||
} else {
|
} else {
|
||||||
|
bgSeq.countHealed(madmin.HealItemObject)
|
||||||
result = healEntrySuccess(uint64(res.ObjectSize))
|
result = healEntrySuccess(uint64(res.ObjectSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,8 +464,10 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if versionHealed {
|
if versionHealed {
|
||||||
|
bgSeq.countHealed(madmin.HealItemObject)
|
||||||
result = healEntrySuccess(uint64(version.Size))
|
result = healEntrySuccess(uint64(version.Size))
|
||||||
} else {
|
} else {
|
||||||
|
bgSeq.countFailed(madmin.HealItemObject)
|
||||||
result = healEntryFailure(uint64(version.Size))
|
result = healEntryFailure(uint64(version.Size))
|
||||||
if version.VersionID != "" {
|
if version.VersionID != "" {
|
||||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err))
|
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err))
|
||||||
|
|||||||
+4
-4
@@ -310,6 +310,7 @@ var (
|
|||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
|
|
||||||
globalActiveCred auth.Credentials
|
globalActiveCred auth.Credentials
|
||||||
|
globalNodeAuthToken string
|
||||||
globalSiteReplicatorCred siteReplicatorCred
|
globalSiteReplicatorCred siteReplicatorCred
|
||||||
|
|
||||||
// Captures if root credentials are set via ENV.
|
// Captures if root credentials are set via ENV.
|
||||||
@@ -413,10 +414,9 @@ var (
|
|||||||
globalServiceFreezeCnt int32
|
globalServiceFreezeCnt int32
|
||||||
globalServiceFreezeMu sync.Mutex // Updates.
|
globalServiceFreezeMu sync.Mutex // Updates.
|
||||||
|
|
||||||
// List of local drives to this node, this is only set during server startup,
|
// Map of local drives to this node, this is set during server startup,
|
||||||
// and is only mutated by HealFormat. Hold globalLocalDrivesMu to access.
|
// disk reconnect and mutated by HealFormat. Hold globalLocalDrivesMu to access.
|
||||||
globalLocalDrives []StorageAPI
|
globalLocalDrivesMap map[string]StorageAPI
|
||||||
globalLocalDrivesMap = make(map[string]StorageAPI)
|
|
||||||
globalLocalDrivesMu sync.RWMutex
|
globalLocalDrivesMu sync.RWMutex
|
||||||
|
|
||||||
globalDriveMonitoring = env.Get("_MINIO_DRIVE_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn
|
globalDriveMonitoring = env.Get("_MINIO_DRIVE_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn
|
||||||
|
|||||||
+40
@@ -31,9 +31,15 @@ import (
|
|||||||
// globalGrid is the global grid manager.
|
// globalGrid is the global grid manager.
|
||||||
var globalGrid atomic.Pointer[grid.Manager]
|
var globalGrid atomic.Pointer[grid.Manager]
|
||||||
|
|
||||||
|
// globalLockGrid is the global lock grid manager.
|
||||||
|
var globalLockGrid atomic.Pointer[grid.Manager]
|
||||||
|
|
||||||
// globalGridStart is a channel that will block startup of grid connections until closed.
|
// globalGridStart is a channel that will block startup of grid connections until closed.
|
||||||
var globalGridStart = make(chan struct{})
|
var globalGridStart = make(chan struct{})
|
||||||
|
|
||||||
|
// globalLockGridStart is a channel that will block startup of lock grid connections until closed.
|
||||||
|
var globalLockGridStart = make(chan struct{})
|
||||||
|
|
||||||
func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||||
hosts, local := eps.GridHosts()
|
hosts, local := eps.GridHosts()
|
||||||
lookupHost := globalDNSCache.LookupHost
|
lookupHost := globalDNSCache.LookupHost
|
||||||
@@ -58,6 +64,7 @@ func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
|||||||
Incoming: globalConnStats.incInternodeInputBytes,
|
Incoming: globalConnStats.incInternodeInputBytes,
|
||||||
Outgoing: globalConnStats.incInternodeOutputBytes,
|
Outgoing: globalConnStats.incInternodeOutputBytes,
|
||||||
TraceTo: globalTrace,
|
TraceTo: globalTrace,
|
||||||
|
RoutePath: grid.RoutePath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -65,3 +72,36 @@ func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
|||||||
globalGrid.Store(g)
|
globalGrid.Store(g)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initGlobalLockGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||||
|
hosts, local := eps.GridHosts()
|
||||||
|
lookupHost := globalDNSCache.LookupHost
|
||||||
|
g, err := grid.NewManager(ctx, grid.ManagerOptions{
|
||||||
|
// Pass Dialer for websocket grid, make sure we do not
|
||||||
|
// provide any DriveOPTimeout() function, as that is not
|
||||||
|
// useful over persistent connections.
|
||||||
|
Dialer: grid.ConnectWSWithRoutePath(
|
||||||
|
grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions.ForWebsocket()))),
|
||||||
|
newCachedAuthToken(),
|
||||||
|
&tls.Config{
|
||||||
|
RootCAs: globalRootCAs,
|
||||||
|
CipherSuites: fips.TLSCiphers(),
|
||||||
|
CurvePreferences: fips.TLSCurveIDs(),
|
||||||
|
}, grid.RouteLockPath),
|
||||||
|
Local: local,
|
||||||
|
Hosts: hosts,
|
||||||
|
AuthToken: validateStorageRequestToken,
|
||||||
|
AuthFn: newCachedAuthToken(),
|
||||||
|
BlockConnect: globalGridStart,
|
||||||
|
// Record incoming and outgoing bytes.
|
||||||
|
Incoming: globalConnStats.incInternodeInputBytes,
|
||||||
|
Outgoing: globalConnStats.incInternodeOutputBytes,
|
||||||
|
TraceTo: globalTrace,
|
||||||
|
RoutePath: grid.RouteLockPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
globalLockGrid.Store(g)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+6
-2
@@ -46,6 +46,7 @@ type apiConfig struct {
|
|||||||
corsAllowOrigins []string
|
corsAllowOrigins []string
|
||||||
replicationPriority string
|
replicationPriority string
|
||||||
replicationMaxWorkers int
|
replicationMaxWorkers int
|
||||||
|
replicationMaxLWorkers int
|
||||||
transitionWorkers int
|
transitionWorkers int
|
||||||
|
|
||||||
staleUploadsExpiry time.Duration
|
staleUploadsExpiry time.Duration
|
||||||
@@ -170,11 +171,12 @@ func (t *apiConfig) init(cfg api.Config, setDriveCounts []int, legacy bool) {
|
|||||||
}
|
}
|
||||||
t.listQuorum = listQuorum
|
t.listQuorum = listQuorum
|
||||||
if globalReplicationPool != nil &&
|
if globalReplicationPool != nil &&
|
||||||
(cfg.ReplicationPriority != t.replicationPriority || cfg.ReplicationMaxWorkers != t.replicationMaxWorkers) {
|
(cfg.ReplicationPriority != t.replicationPriority || cfg.ReplicationMaxWorkers != t.replicationMaxWorkers || cfg.ReplicationMaxLWorkers != t.replicationMaxLWorkers) {
|
||||||
globalReplicationPool.ResizeWorkerPriority(cfg.ReplicationPriority, cfg.ReplicationMaxWorkers)
|
globalReplicationPool.ResizeWorkerPriority(cfg.ReplicationPriority, cfg.ReplicationMaxWorkers, cfg.ReplicationMaxLWorkers)
|
||||||
}
|
}
|
||||||
t.replicationPriority = cfg.ReplicationPriority
|
t.replicationPriority = cfg.ReplicationPriority
|
||||||
t.replicationMaxWorkers = cfg.ReplicationMaxWorkers
|
t.replicationMaxWorkers = cfg.ReplicationMaxWorkers
|
||||||
|
t.replicationMaxLWorkers = cfg.ReplicationMaxLWorkers
|
||||||
|
|
||||||
// N B api.transition_workers will be deprecated
|
// N B api.transition_workers will be deprecated
|
||||||
if globalTransitionState != nil {
|
if globalTransitionState != nil {
|
||||||
@@ -383,12 +385,14 @@ func (t *apiConfig) getReplicationOpts() replicationPoolOpts {
|
|||||||
return replicationPoolOpts{
|
return replicationPoolOpts{
|
||||||
Priority: "auto",
|
Priority: "auto",
|
||||||
MaxWorkers: WorkerMaxLimit,
|
MaxWorkers: WorkerMaxLimit,
|
||||||
|
MaxLWorkers: LargeWorkerCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return replicationPoolOpts{
|
return replicationPoolOpts{
|
||||||
Priority: t.replicationPriority,
|
Priority: t.replicationPriority,
|
||||||
MaxWorkers: t.replicationMaxWorkers,
|
MaxWorkers: t.replicationMaxWorkers,
|
||||||
|
MaxLWorkers: t.replicationMaxLWorkers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-7
@@ -27,10 +27,6 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
apiGetObject = "GetObject"
|
|
||||||
)
|
|
||||||
|
|
||||||
// connStats - Network statistics
|
// connStats - Network statistics
|
||||||
// Count total input/output transferred bytes during
|
// Count total input/output transferred bytes during
|
||||||
// the server's life.
|
// the server's life.
|
||||||
@@ -132,7 +128,7 @@ func (bh *bucketHTTPStats) updateHTTPStats(bucket, api string, w *xhttp.Response
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if w != nil && api == apiGetObject {
|
if w != nil {
|
||||||
// Increment the prometheus http request response histogram with API, Bucket
|
// Increment the prometheus http request response histogram with API, Bucket
|
||||||
bucketHTTPRequestsDuration.With(prometheus.Labels{
|
bucketHTTPRequestsDuration.With(prometheus.Labels{
|
||||||
"api": api,
|
"api": api,
|
||||||
@@ -437,9 +433,7 @@ func (st *HTTPStats) updateStats(api string, w *xhttp.ResponseRecorder) {
|
|||||||
st.totalS3Requests.Inc(api)
|
st.totalS3Requests.Inc(api)
|
||||||
|
|
||||||
// Increment the prometheus http request response histogram with appropriate label
|
// Increment the prometheus http request response histogram with appropriate label
|
||||||
if api == apiGetObject {
|
|
||||||
httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(w.TimeToFirstByte.Seconds())
|
httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(w.TimeToFirstByte.Seconds())
|
||||||
}
|
|
||||||
|
|
||||||
code := w.StatusCode
|
code := w.StatusCode
|
||||||
|
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
for _, item := range regUsersList {
|
for _, item := range regUsersList {
|
||||||
userName := path.Dir(item)
|
userName := path.Dir(item)
|
||||||
if err := iamOS.loadUser(ctx, userName, regUser, cache.iamUsersMap); err != nil && err != errNoSuchUser {
|
if err := iamOS.loadUser(ctx, userName, regUser, cache.iamUsersMap); err != nil && err != errNoSuchUser {
|
||||||
return fmt.Errorf("unable to load the user `%s`: %w", userName, err)
|
return fmt.Errorf("unable to load the user: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if took := time.Since(regUsersLoadStartTime); took > maxIAMLoadOpTime {
|
if took := time.Since(regUsersLoadStartTime); took > maxIAMLoadOpTime {
|
||||||
@@ -510,7 +510,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
for _, item := range groupsList {
|
for _, item := range groupsList {
|
||||||
group := path.Dir(item)
|
group := path.Dir(item)
|
||||||
if err := iamOS.loadGroup(ctx, group, cache.iamGroupsMap); err != nil && err != errNoSuchGroup {
|
if err := iamOS.loadGroup(ctx, group, cache.iamGroupsMap); err != nil && err != errNoSuchGroup {
|
||||||
return fmt.Errorf("unable to load the group `%s`: %w", group, err)
|
return fmt.Errorf("unable to load the group: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if took := time.Since(groupsLoadStartTime); took > maxIAMLoadOpTime {
|
if took := time.Since(groupsLoadStartTime); took > maxIAMLoadOpTime {
|
||||||
@@ -524,7 +524,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
for _, item := range userPolicyMappingsList {
|
for _, item := range userPolicyMappingsList {
|
||||||
userName := strings.TrimSuffix(item, ".json")
|
userName := strings.TrimSuffix(item, ".json")
|
||||||
if err := iamOS.loadMappedPolicy(ctx, userName, regUser, false, cache.iamUserPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
if err := iamOS.loadMappedPolicy(ctx, userName, regUser, false, cache.iamUserPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||||
return fmt.Errorf("unable to load the policy mapping for the user `%s`: %w", userName, err)
|
return fmt.Errorf("unable to load the policy mapping for the user: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if took := time.Since(userPolicyMappingLoadStartTime); took > maxIAMLoadOpTime {
|
if took := time.Since(userPolicyMappingLoadStartTime); took > maxIAMLoadOpTime {
|
||||||
@@ -537,7 +537,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
for _, item := range groupPolicyMappingsList {
|
for _, item := range groupPolicyMappingsList {
|
||||||
groupName := strings.TrimSuffix(item, ".json")
|
groupName := strings.TrimSuffix(item, ".json")
|
||||||
if err := iamOS.loadMappedPolicy(ctx, groupName, regUser, true, cache.iamGroupPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
if err := iamOS.loadMappedPolicy(ctx, groupName, regUser, true, cache.iamGroupPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||||
return fmt.Errorf("unable to load the policy mapping for the group `%s`: %w", groupName, err)
|
return fmt.Errorf("unable to load the policy mapping for the group: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if took := time.Since(groupPolicyMappingLoadStartTime); took > maxIAMLoadOpTime {
|
if took := time.Since(groupPolicyMappingLoadStartTime); took > maxIAMLoadOpTime {
|
||||||
@@ -551,7 +551,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
for _, item := range svcAccList {
|
for _, item := range svcAccList {
|
||||||
userName := path.Dir(item)
|
userName := path.Dir(item)
|
||||||
if err := iamOS.loadUser(ctx, userName, svcUser, svcUsersMap); err != nil && err != errNoSuchUser {
|
if err := iamOS.loadUser(ctx, userName, svcUser, svcUsersMap); err != nil && err != errNoSuchUser {
|
||||||
return fmt.Errorf("unable to load the service account `%s`: %w", userName, err)
|
return fmt.Errorf("unable to load the service account: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if took := time.Since(svcAccLoadStartTime); took > maxIAMLoadOpTime {
|
if took := time.Since(svcAccLoadStartTime); took > maxIAMLoadOpTime {
|
||||||
@@ -580,7 +580,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
|
|||||||
// OIDC/AssumeRoleWithCustomToken/AssumeRoleWithCertificate).
|
// OIDC/AssumeRoleWithCustomToken/AssumeRoleWithCertificate).
|
||||||
err := iamOS.loadMappedPolicy(ctx, svcParent, stsUser, false, cache.iamSTSPolicyMap)
|
err := iamOS.loadMappedPolicy(ctx, svcParent, stsUser, false, cache.iamSTSPolicyMap)
|
||||||
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||||
return fmt.Errorf("unable to load the policy mapping for the STS user `%s`: %w", svcParent, err)
|
return fmt.Errorf("unable to load the policy mapping for the STS user: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-19
@@ -2090,7 +2090,7 @@ func (store *IAMStoreSys) GetAllSTSUserMappings(userPredicate func(string) bool)
|
|||||||
|
|
||||||
// Assumes store is locked by caller. If userMap is empty, returns all user mappings.
|
// Assumes store is locked by caller. If userMap is empty, returns all user mappings.
|
||||||
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[string]set.StringSet,
|
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[string]set.StringSet,
|
||||||
userPredicate func(string) bool,
|
userPredicate func(string) bool, decodeFunc func(string) string,
|
||||||
) []madmin.UserPolicyEntities {
|
) []madmin.UserPolicyEntities {
|
||||||
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||||
resMap := make(map[string]madmin.UserPolicyEntities, len(userMap))
|
resMap := make(map[string]madmin.UserPolicyEntities, len(userMap))
|
||||||
@@ -2098,9 +2098,13 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[st
|
|||||||
for user, groupSet := range userMap {
|
for user, groupSet := range userMap {
|
||||||
// Attempt to load parent user mapping for STS accounts
|
// Attempt to load parent user mapping for STS accounts
|
||||||
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
||||||
blankEntities := madmin.UserPolicyEntities{User: user}
|
decodeUser := user
|
||||||
|
if decodeFunc != nil {
|
||||||
|
decodeUser = decodeFunc(user)
|
||||||
|
}
|
||||||
|
blankEntities := madmin.UserPolicyEntities{User: decodeUser}
|
||||||
if !groupSet.IsEmpty() {
|
if !groupSet.IsEmpty() {
|
||||||
blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil)
|
blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil, decodeFunc)
|
||||||
}
|
}
|
||||||
resMap[user] = blankEntities
|
resMap[user] = blankEntities
|
||||||
}
|
}
|
||||||
@@ -2116,7 +2120,11 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[st
|
|||||||
if len(userMap) > 0 {
|
if len(userMap) > 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
entitiesWithMemberOf = madmin.UserPolicyEntities{User: user}
|
decodeUser := user
|
||||||
|
if decodeFunc != nil {
|
||||||
|
decodeUser = decodeFunc(user)
|
||||||
|
}
|
||||||
|
entitiesWithMemberOf = madmin.UserPolicyEntities{User: decodeUser}
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := mappedPolicy.toSlice()
|
ps := mappedPolicy.toSlice()
|
||||||
@@ -2155,7 +2163,7 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[st
|
|||||||
|
|
||||||
// Assumes store is locked by caller. If groups is empty, returns all group mappings.
|
// Assumes store is locked by caller. If groups is empty, returns all group mappings.
|
||||||
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set.StringSet,
|
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set.StringSet,
|
||||||
groupPredicate func(string) bool,
|
groupPredicate func(string) bool, decodeFunc func(string) string,
|
||||||
) []madmin.GroupPolicyEntities {
|
) []madmin.GroupPolicyEntities {
|
||||||
var r []madmin.GroupPolicyEntities
|
var r []madmin.GroupPolicyEntities
|
||||||
|
|
||||||
@@ -2168,10 +2176,15 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decodeGroup := group
|
||||||
|
if decodeFunc != nil {
|
||||||
|
decodeGroup = decodeFunc(group)
|
||||||
|
}
|
||||||
|
|
||||||
ps := mappedPolicy.toSlice()
|
ps := mappedPolicy.toSlice()
|
||||||
sort.Strings(ps)
|
sort.Strings(ps)
|
||||||
r = append(r, madmin.GroupPolicyEntities{
|
r = append(r, madmin.GroupPolicyEntities{
|
||||||
Group: group,
|
Group: decodeGroup,
|
||||||
Policies: ps,
|
Policies: ps,
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
@@ -2186,7 +2199,7 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set
|
|||||||
|
|
||||||
// Assumes store is locked by caller. If policies is empty, returns all policy mappings.
|
// Assumes store is locked by caller. If policies is empty, returns all policy mappings.
|
||||||
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.StringSet,
|
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.StringSet,
|
||||||
userPredicate, groupPredicate func(string) bool,
|
userPredicate, groupPredicate func(string) bool, decodeFunc func(string) string,
|
||||||
) []madmin.PolicyEntities {
|
) []madmin.PolicyEntities {
|
||||||
policyToUsersMap := make(map[string]set.StringSet)
|
policyToUsersMap := make(map[string]set.StringSet)
|
||||||
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||||
@@ -2194,6 +2207,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decodeUser := user
|
||||||
|
if decodeFunc != nil {
|
||||||
|
decodeUser = decodeFunc(user)
|
||||||
|
}
|
||||||
|
|
||||||
commonPolicySet := mappedPolicy.policySet()
|
commonPolicySet := mappedPolicy.policySet()
|
||||||
if !queryPolSet.IsEmpty() {
|
if !queryPolSet.IsEmpty() {
|
||||||
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
||||||
@@ -2201,9 +2219,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
for _, policy := range commonPolicySet.ToSlice() {
|
for _, policy := range commonPolicySet.ToSlice() {
|
||||||
s, ok := policyToUsersMap[policy]
|
s, ok := policyToUsersMap[policy]
|
||||||
if !ok {
|
if !ok {
|
||||||
policyToUsersMap[policy] = set.CreateStringSet(user)
|
policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
|
||||||
} else {
|
} else {
|
||||||
s.Add(user)
|
s.Add(decodeUser)
|
||||||
policyToUsersMap[policy] = s
|
policyToUsersMap[policy] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2217,6 +2235,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decodeUser := user
|
||||||
|
if decodeFunc != nil {
|
||||||
|
decodeUser = decodeFunc(user)
|
||||||
|
}
|
||||||
|
|
||||||
var mappedPolicy MappedPolicy
|
var mappedPolicy MappedPolicy
|
||||||
store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false))
|
store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false))
|
||||||
|
|
||||||
@@ -2227,9 +2250,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
for _, policy := range commonPolicySet.ToSlice() {
|
for _, policy := range commonPolicySet.ToSlice() {
|
||||||
s, ok := policyToUsersMap[policy]
|
s, ok := policyToUsersMap[policy]
|
||||||
if !ok {
|
if !ok {
|
||||||
policyToUsersMap[policy] = set.CreateStringSet(user)
|
policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
|
||||||
} else {
|
} else {
|
||||||
s.Add(user)
|
s.Add(decodeUser)
|
||||||
policyToUsersMap[policy] = s
|
policyToUsersMap[policy] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2244,6 +2267,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decodeUser := user
|
||||||
|
if decodeFunc != nil {
|
||||||
|
decodeUser = decodeFunc(user)
|
||||||
|
}
|
||||||
|
|
||||||
commonPolicySet := mappedPolicy.policySet()
|
commonPolicySet := mappedPolicy.policySet()
|
||||||
if !queryPolSet.IsEmpty() {
|
if !queryPolSet.IsEmpty() {
|
||||||
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
||||||
@@ -2251,9 +2279,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
for _, policy := range commonPolicySet.ToSlice() {
|
for _, policy := range commonPolicySet.ToSlice() {
|
||||||
s, ok := policyToUsersMap[policy]
|
s, ok := policyToUsersMap[policy]
|
||||||
if !ok {
|
if !ok {
|
||||||
policyToUsersMap[policy] = set.CreateStringSet(user)
|
policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
|
||||||
} else {
|
} else {
|
||||||
s.Add(user)
|
s.Add(decodeUser)
|
||||||
policyToUsersMap[policy] = s
|
policyToUsersMap[policy] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2268,6 +2296,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decodeGroup := group
|
||||||
|
if decodeFunc != nil {
|
||||||
|
decodeGroup = decodeFunc(group)
|
||||||
|
}
|
||||||
|
|
||||||
commonPolicySet := mappedPolicy.policySet()
|
commonPolicySet := mappedPolicy.policySet()
|
||||||
if !queryPolSet.IsEmpty() {
|
if !queryPolSet.IsEmpty() {
|
||||||
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
||||||
@@ -2275,9 +2308,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
for _, policy := range commonPolicySet.ToSlice() {
|
for _, policy := range commonPolicySet.ToSlice() {
|
||||||
s, ok := policyToGroupsMap[policy]
|
s, ok := policyToGroupsMap[policy]
|
||||||
if !ok {
|
if !ok {
|
||||||
policyToGroupsMap[policy] = set.CreateStringSet(group)
|
policyToGroupsMap[policy] = set.CreateStringSet(decodeGroup)
|
||||||
} else {
|
} else {
|
||||||
s.Add(group)
|
s.Add(decodeGroup)
|
||||||
policyToGroupsMap[policy] = s
|
policyToGroupsMap[policy] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2318,7 +2351,7 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
|
|||||||
|
|
||||||
// ListPolicyMappings - return users/groups mapped to policies.
|
// ListPolicyMappings - return users/groups mapped to policies.
|
||||||
func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery,
|
func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery,
|
||||||
userPredicate, groupPredicate func(string) bool,
|
userPredicate, groupPredicate func(string) bool, decodeFunc func(string) string,
|
||||||
) madmin.PolicyEntitiesResult {
|
) madmin.PolicyEntitiesResult {
|
||||||
cache := store.rlock()
|
cache := store.rlock()
|
||||||
defer store.runlock()
|
defer store.runlock()
|
||||||
@@ -2328,13 +2361,13 @@ func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery,
|
|||||||
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policies) == 0
|
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policies) == 0
|
||||||
|
|
||||||
if len(q.Users) > 0 {
|
if len(q.Users) > 0 {
|
||||||
result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate)
|
result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate, decodeFunc)
|
||||||
}
|
}
|
||||||
if len(q.Groups) > 0 {
|
if len(q.Groups) > 0 {
|
||||||
result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate)
|
result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate, decodeFunc)
|
||||||
}
|
}
|
||||||
if len(q.Policies) > 0 || isAllPoliciesQuery {
|
if len(q.Policies) > 0 || isAllPoliciesQuery {
|
||||||
result.PolicyMappings = store.listPolicyMappings(cache, q.Policies, userPredicate, groupPredicate)
|
result.PolicyMappings = store.listPolicyMappings(cache, q.Policies, userPredicate, groupPredicate, decodeFunc)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-23
@@ -217,6 +217,9 @@ func (sys *IAMSys) Load(ctx context.Context, firstTime bool) error {
|
|||||||
|
|
||||||
if firstTime {
|
if firstTime {
|
||||||
bootstrapTraceMsg(fmt.Sprintf("globalIAMSys.Load(): (duration: %s)", loadDuration))
|
bootstrapTraceMsg(fmt.Sprintf("globalIAMSys.Load(): (duration: %s)", loadDuration))
|
||||||
|
if globalIsDistErasure {
|
||||||
|
logger.Info("IAM load(startup) finished. (duration: %s)", loadDuration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -400,12 +403,12 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
|
|||||||
// Load all IAM items (except STS creds) periodically.
|
// Load all IAM items (except STS creds) periodically.
|
||||||
refreshStart := time.Now()
|
refreshStart := time.Now()
|
||||||
if err := sys.Load(ctx, false); err != nil {
|
if err := sys.Load(ctx, false); err != nil {
|
||||||
iamLogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (took %.2fs): %v", time.Since(refreshStart).Seconds(), err), logger.WarningKind)
|
iamLogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (duration: %s): %v", time.Since(refreshStart), err), logger.WarningKind)
|
||||||
} else {
|
} else {
|
||||||
took := time.Since(refreshStart).Seconds()
|
took := time.Since(refreshStart).Seconds()
|
||||||
if took > maxDurationSecondsForLog {
|
if took > maxDurationSecondsForLog {
|
||||||
// Log if we took a lot of time to load.
|
// Log if we took a lot of time to load.
|
||||||
logger.Info("IAM refresh took %.2fs", took)
|
logger.Info("IAM refresh took (duration: %s)", took)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,7 +844,7 @@ func (sys *IAMSys) createCleanEntitiesQuery(q madmin.PolicyEntitiesQuery, ldap b
|
|||||||
// Validate and normalize groups.
|
// Validate and normalize groups.
|
||||||
for _, group := range q.Groups {
|
for _, group := range q.Groups {
|
||||||
lookupRes, underDN, _ := sys.LDAPConfig.GetValidatedGroupDN(nil, group)
|
lookupRes, underDN, _ := sys.LDAPConfig.GetValidatedGroupDN(nil, group)
|
||||||
if lookupRes != nil && !underDN {
|
if lookupRes != nil && underDN {
|
||||||
cleanQ.Groups.Add(lookupRes.NormDN)
|
cleanQ.Groups.Add(lookupRes.NormDN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -871,7 +874,7 @@ func (sys *IAMSys) QueryLDAPPolicyEntities(ctx context.Context, q madmin.PolicyE
|
|||||||
select {
|
select {
|
||||||
case <-sys.configLoaded:
|
case <-sys.configLoaded:
|
||||||
cleanQuery := sys.createCleanEntitiesQuery(q, true)
|
cleanQuery := sys.createCleanEntitiesQuery(q, true)
|
||||||
pe := sys.store.ListPolicyMappings(cleanQuery, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN)
|
pe := sys.store.ListPolicyMappings(cleanQuery, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN, sys.LDAPConfig.DecodeDN)
|
||||||
pe.Timestamp = UTCNow()
|
pe.Timestamp = UTCNow()
|
||||||
return &pe, nil
|
return &pe, nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -955,7 +958,7 @@ func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntit
|
|||||||
return !sys.LDAPConfig.IsLDAPGroupDN(s)
|
return !sys.LDAPConfig.IsLDAPGroupDN(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate)
|
pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate, nil)
|
||||||
pe.Timestamp = UTCNow()
|
pe.Timestamp = UTCNow()
|
||||||
return &pe, nil
|
return &pe, nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -1439,11 +1442,13 @@ func (sys *IAMSys) updateGroupMembershipsForLDAP(ctx context.Context) {
|
|||||||
// 1. Collect all LDAP users with active creds.
|
// 1. Collect all LDAP users with active creds.
|
||||||
allCreds := sys.store.GetSTSAndServiceAccounts()
|
allCreds := sys.store.GetSTSAndServiceAccounts()
|
||||||
// List of unique LDAP (parent) user DNs that have active creds
|
// List of unique LDAP (parent) user DNs that have active creds
|
||||||
var parentUsers []string
|
var parentUserActualDNList []string
|
||||||
// Map of LDAP user to list of active credential objects
|
// Map of LDAP user (internal representation) to list of active credential objects
|
||||||
parentUserToCredsMap := make(map[string][]auth.Credentials)
|
parentUserToCredsMap := make(map[string][]auth.Credentials)
|
||||||
// DN to ldap username mapping for each LDAP user
|
// DN to ldap username mapping for each LDAP user
|
||||||
parentUserToLDAPUsernameMap := make(map[string]string)
|
actualDNToLDAPUsernameMap := make(map[string]string)
|
||||||
|
// External (actual) LDAP DN to internal normalized representation
|
||||||
|
actualDNToParentUserMap := make(map[string]string)
|
||||||
for _, cred := range allCreds {
|
for _, cred := range allCreds {
|
||||||
// Expired credentials don't need parent user updates.
|
// Expired credentials don't need parent user updates.
|
||||||
if cred.IsExpired() {
|
if cred.IsExpired() {
|
||||||
@@ -1486,25 +1491,28 @@ func (sys *IAMSys) updateGroupMembershipsForLDAP(ctx context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapUsername, ok := jwtClaims.Lookup(ldapUserN)
|
ldapUsername, okUserN := jwtClaims.Lookup(ldapUserN)
|
||||||
if !ok {
|
ldapActualDN, okDN := jwtClaims.Lookup(ldapActualUser)
|
||||||
|
if !okUserN || !okDN {
|
||||||
// skip this cred - we dont have the
|
// skip this cred - we dont have the
|
||||||
// username info needed
|
// username info needed
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect each new cred.ParentUser into parentUsers
|
// Collect each new cred.ParentUser into parentUsers
|
||||||
parentUsers = append(parentUsers, cred.ParentUser)
|
parentUserActualDNList = append(parentUserActualDNList, ldapActualDN)
|
||||||
|
|
||||||
// Update the ldapUsernameMap
|
// Update the ldapUsernameMap
|
||||||
parentUserToLDAPUsernameMap[cred.ParentUser] = ldapUsername
|
actualDNToLDAPUsernameMap[ldapActualDN] = ldapUsername
|
||||||
|
|
||||||
|
// Update the actualDNToParentUserMap
|
||||||
|
actualDNToParentUserMap[ldapActualDN] = cred.ParentUser
|
||||||
}
|
}
|
||||||
parentUserToCredsMap[cred.ParentUser] = append(parentUserToCredsMap[cred.ParentUser], cred)
|
parentUserToCredsMap[cred.ParentUser] = append(parentUserToCredsMap[cred.ParentUser], cred)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Query LDAP server for groups of the LDAP users collected.
|
// 2. Query LDAP server for groups of the LDAP users collected.
|
||||||
updatedGroups, err := sys.LDAPConfig.LookupGroupMemberships(parentUsers, parentUserToLDAPUsernameMap)
|
updatedGroups, err := sys.LDAPConfig.LookupGroupMemberships(parentUserActualDNList, actualDNToLDAPUsernameMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log and return on error - perhaps it'll work the next time.
|
// Log and return on error - perhaps it'll work the next time.
|
||||||
iamLogIf(GlobalContext, err)
|
iamLogIf(GlobalContext, err)
|
||||||
@@ -1512,8 +1520,9 @@ func (sys *IAMSys) updateGroupMembershipsForLDAP(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Update creds for those users whose groups are changed
|
// 3. Update creds for those users whose groups are changed
|
||||||
for _, parentUser := range parentUsers {
|
for _, parentActualDN := range parentUserActualDNList {
|
||||||
currGroupsSet := updatedGroups[parentUser]
|
currGroupsSet := updatedGroups[parentActualDN]
|
||||||
|
parentUser := actualDNToParentUserMap[parentActualDN]
|
||||||
currGroups := currGroupsSet.ToSlice()
|
currGroups := currGroupsSet.ToSlice()
|
||||||
for _, cred := range parentUserToCredsMap[parentUser] {
|
for _, cred := range parentUserToCredsMap[parentUser] {
|
||||||
gSet := set.CreateStringSet(cred.Groups...)
|
gSet := set.CreateStringSet(cred.Groups...)
|
||||||
@@ -1571,11 +1580,11 @@ func (sys *IAMSys) NormalizeLDAPAccessKeypairs(ctx context.Context, accessKeyMap
|
|||||||
// server and is under a configured base DN.
|
// server and is under a configured base DN.
|
||||||
validatedParent, isUnderBaseDN, err := sys.LDAPConfig.GetValidatedUserDN(conn, parent)
|
validatedParent, isUnderBaseDN, err := sys.LDAPConfig.GetValidatedUserDN(conn, parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
collectedErrors = append(collectedErrors, fmt.Errorf("could not validate `%s` exists in LDAP directory: %w", parent, err))
|
collectedErrors = append(collectedErrors, fmt.Errorf("could not validate parent exists in LDAP directory: %w", err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if validatedParent == nil || !isUnderBaseDN {
|
if validatedParent == nil || !isUnderBaseDN {
|
||||||
err := fmt.Errorf("DN `%s` was not found in the LDAP directory", parent)
|
err := fmt.Errorf("DN parent was not found in the LDAP directory")
|
||||||
collectedErrors = append(collectedErrors, err)
|
collectedErrors = append(collectedErrors, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1590,11 +1599,11 @@ func (sys *IAMSys) NormalizeLDAPAccessKeypairs(ctx context.Context, accessKeyMap
|
|||||||
// configured base DN.
|
// configured base DN.
|
||||||
validatedGroup, _, err := sys.LDAPConfig.GetValidatedGroupDN(conn, group)
|
validatedGroup, _, err := sys.LDAPConfig.GetValidatedGroupDN(conn, group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
collectedErrors = append(collectedErrors, fmt.Errorf("could not validate `%s` exists in LDAP directory: %w", group, err))
|
collectedErrors = append(collectedErrors, fmt.Errorf("could not validate group exists in LDAP directory: %w", err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if validatedGroup == nil {
|
if validatedGroup == nil {
|
||||||
err := fmt.Errorf("DN `%s` was not found in the LDAP directory", group)
|
err := fmt.Errorf("DN group was not found in the LDAP directory")
|
||||||
collectedErrors = append(collectedErrors, err)
|
collectedErrors = append(collectedErrors, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1684,7 +1693,7 @@ func (sys *IAMSys) NormalizeLDAPMappingImport(ctx context.Context, isGroup bool,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if validatedDN == nil || !underBaseDN {
|
if validatedDN == nil || !underBaseDN {
|
||||||
err := fmt.Errorf("DN `%s` was not found in the LDAP directory", k)
|
err := fmt.Errorf("DN was not found in the LDAP directory")
|
||||||
collectedErrors = append(collectedErrors, err)
|
collectedErrors = append(collectedErrors, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2027,7 +2036,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
|
|||||||
if dnResult == nil {
|
if dnResult == nil {
|
||||||
// dn not found - still attempt to detach if provided user is a DN.
|
// dn not found - still attempt to detach if provided user is a DN.
|
||||||
if !isAttach && sys.LDAPConfig.IsLDAPUserDN(r.User) {
|
if !isAttach && sys.LDAPConfig.IsLDAPUserDN(r.User) {
|
||||||
dn = r.User
|
dn = sys.LDAPConfig.QuickNormalizeDN(r.User)
|
||||||
} else {
|
} else {
|
||||||
err = errNoSuchUser
|
err = errNoSuchUser
|
||||||
return
|
return
|
||||||
@@ -2044,7 +2053,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
|
|||||||
}
|
}
|
||||||
if dnResult == nil || !underBaseDN {
|
if dnResult == nil || !underBaseDN {
|
||||||
if !isAttach {
|
if !isAttach {
|
||||||
dn = r.Group
|
dn = sys.LDAPConfig.QuickNormalizeDN(r.Group)
|
||||||
} else {
|
} else {
|
||||||
err = errNoSuchGroup
|
err = errNoSuchGroup
|
||||||
return
|
return
|
||||||
|
|||||||
+7
-34
@@ -24,10 +24,8 @@ import (
|
|||||||
|
|
||||||
jwtgo "github.com/golang-jwt/jwt/v4"
|
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||||
jwtreq "github.com/golang-jwt/jwt/v4/request"
|
jwtreq "github.com/golang-jwt/jwt/v4/request"
|
||||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
xjwt "github.com/minio/minio/internal/jwt"
|
xjwt "github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
|
||||||
"github.com/minio/pkg/v3/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,8 +35,8 @@ const (
|
|||||||
// Default JWT token for web handlers is one day.
|
// Default JWT token for web handlers is one day.
|
||||||
defaultJWTExpiry = 24 * time.Hour
|
defaultJWTExpiry = 24 * time.Hour
|
||||||
|
|
||||||
// Inter-node JWT token expiry is 15 minutes.
|
// Inter-node JWT token expiry is 100 years approx.
|
||||||
defaultInterNodeJWTExpiry = 15 * time.Minute
|
defaultInterNodeJWTExpiry = 100 * 365 * 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -50,17 +48,10 @@ var (
|
|||||||
errMalformedAuth = errors.New("Malformed authentication input")
|
errMalformedAuth = errors.New("Malformed authentication input")
|
||||||
)
|
)
|
||||||
|
|
||||||
type cacheKey struct {
|
func authenticateNode(accessKey, secretKey string) (string, error) {
|
||||||
accessKey, secretKey, audience string
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheLRU = expirable.NewLRU[cacheKey, string](1000, nil, 15*time.Second)
|
|
||||||
|
|
||||||
func authenticateNode(accessKey, secretKey, audience string) (string, error) {
|
|
||||||
claims := xjwt.NewStandardClaims()
|
claims := xjwt.NewStandardClaims()
|
||||||
claims.SetExpiry(UTCNow().Add(defaultInterNodeJWTExpiry))
|
claims.SetExpiry(UTCNow().Add(defaultInterNodeJWTExpiry))
|
||||||
claims.SetAccessKey(accessKey)
|
claims.SetAccessKey(accessKey)
|
||||||
claims.SetAudience(audience)
|
|
||||||
|
|
||||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
||||||
return jwt.SignedString([]byte(secretKey))
|
return jwt.SignedString([]byte(secretKey))
|
||||||
@@ -141,27 +132,9 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b
|
|||||||
return claims, groups, owner, nil
|
return claims, groups, owner, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCachedAuthToken returns a token that is cached up to 15 seconds.
|
// newCachedAuthToken returns the cached token.
|
||||||
// If globalActiveCred is updated it is reflected at once.
|
func newCachedAuthToken() func() string {
|
||||||
func newCachedAuthToken() func(audience string) string {
|
return func() string {
|
||||||
fn := func(accessKey, secretKey, audience string) (s string, err error) {
|
return globalNodeAuthToken
|
||||||
k := cacheKey{accessKey: accessKey, secretKey: secretKey, audience: audience}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
s, ok = cacheLRU.Get(k)
|
|
||||||
if !ok {
|
|
||||||
s, err = authenticateNode(accessKey, secretKey, audience)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
cacheLRU.Add(k, s)
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
return func(audience string) string {
|
|
||||||
cred := globalActiveCred
|
|
||||||
token, err := fn(cred.AccessKey, cred.SecretKey, audience)
|
|
||||||
logger.CriticalIf(GlobalContext, err)
|
|
||||||
return token
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -107,7 +107,7 @@ func BenchmarkParseJWTStandardClaims(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
creds := globalActiveCred
|
creds := globalActiveCred
|
||||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
|
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ func BenchmarkParseJWTMapClaims(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
creds := globalActiveCred
|
creds := globalActiveCred
|
||||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
|
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,7 @@ func BenchmarkAuthenticateNode(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
fn(creds.AccessKey, creds.SecretKey, "aud")
|
fn(creds.AccessKey, creds.SecretKey)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
b.Run("cached", func(b *testing.B) {
|
b.Run("cached", func(b *testing.B) {
|
||||||
@@ -184,7 +184,7 @@ func BenchmarkAuthenticateNode(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
fn("aud")
|
fn()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-7
@@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/kms-go/kes"
|
"github.com/minio/kms-go/kes"
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/v3/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
@@ -56,7 +57,7 @@ func (a kmsAPIHandlers) KMSStatusHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
writeSuccessResponseJSON(w, resp)
|
writeSuccessResponseJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMSMetricsHandler - POST /minio/kms/v1/metrics
|
// KMSMetricsHandler - GET /minio/kms/v1/metrics
|
||||||
func (a kmsAPIHandlers) KMSMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a kmsAPIHandlers) KMSMetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSMetrics")
|
ctx := newContext(r, w, "KMSMetrics")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
@@ -83,7 +84,7 @@ func (a kmsAPIHandlers) KMSMetricsHandler(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMSAPIsHandler - POST /minio/kms/v1/apis
|
// KMSAPIsHandler - GET /minio/kms/v1/apis
|
||||||
func (a kmsAPIHandlers) KMSAPIsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a kmsAPIHandlers) KMSAPIsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSAPIs")
|
ctx := newContext(r, w, "KMSAPIs")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
@@ -114,7 +115,7 @@ type versionResponse struct {
|
|||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMSVersionHandler - POST /minio/kms/v1/version
|
// KMSVersionHandler - GET /minio/kms/v1/version
|
||||||
func (a kmsAPIHandlers) KMSVersionHandler(w http.ResponseWriter, r *http.Request) {
|
func (a kmsAPIHandlers) KMSVersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "KMSVersion")
|
ctx := newContext(r, w, "KMSVersion")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
@@ -159,7 +160,20 @@ func (a kmsAPIHandlers) KMSCreateKeyHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := GlobalKMS.CreateKey(ctx, &kms.CreateKeyRequest{Name: r.Form.Get("key-id")}); err != nil {
|
keyID := r.Form.Get("key-id")
|
||||||
|
|
||||||
|
// Ensure policy allows the user to create this key name
|
||||||
|
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !checkKMSActionAllowed(r, owner, cred, policy.KMSCreateKeyAction, keyID) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := GlobalKMS.CreateKey(ctx, &kms.CreateKeyRequest{Name: keyID}); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -171,6 +185,9 @@ func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
ctx := newContext(r, w, "KMSListKeys")
|
ctx := newContext(r, w, "KMSListKeys")
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// This only checks if the action (kms:ListKeys) is allowed, it does not check
|
||||||
|
// each key name against the policy's Resources. We check that below, once
|
||||||
|
// we have the list of key names from the KMS.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListKeysAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.KMSListKeysAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
@@ -180,7 +197,7 @@ func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
names, _, err := GlobalKMS.ListKeyNames(ctx, &kms.ListRequest{
|
allKeyNames, _, err := GlobalKMS.ListKeyNames(ctx, &kms.ListRequest{
|
||||||
Prefix: r.Form.Get("pattern"),
|
Prefix: r.Form.Get("pattern"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -188,8 +205,24 @@ func (a kmsAPIHandlers) KMSListKeysHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
values := make([]kes.KeyInfo, 0, len(names))
|
// Get the cred and owner for checking authz below.
|
||||||
for _, name := range names {
|
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have all the key names, for each of them, check whether the policy grants permission for
|
||||||
|
// the user to list it.
|
||||||
|
keyNames := []string{}
|
||||||
|
for _, name := range allKeyNames {
|
||||||
|
if checkKMSActionAllowed(r, owner, cred, policy.KMSListKeysAction, name) {
|
||||||
|
keyNames = append(keyNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values := make([]kes.KeyInfo, 0, len(keyNames))
|
||||||
|
for _, name := range keyNames {
|
||||||
values = append(values, kes.KeyInfo{
|
values = append(values, kes.KeyInfo{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
@@ -224,6 +257,17 @@ func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
KeyID: keyID,
|
KeyID: keyID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure policy allows the user to get this key's status
|
||||||
|
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !checkKMSActionAllowed(r, owner, cred, policy.KMSKeyStatusAction, keyID) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
kmsContext := kms.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
|
kmsContext := kms.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
|
||||||
// 1. Generate a new key using the KMS.
|
// 1. Generate a new key using the KMS.
|
||||||
key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{Name: keyID, AssociatedData: kmsContext})
|
key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{Name: keyID, AssociatedData: kmsContext})
|
||||||
@@ -274,3 +318,16 @@ func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
writeSuccessResponseJSON(w, resp)
|
writeSuccessResponseJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkKMSActionAllowed checks for authorization for a specific action on a resource.
|
||||||
|
func checkKMSActionAllowed(r *http.Request, owner bool, cred auth.Credentials, action policy.KMSAction, resource string) bool {
|
||||||
|
return globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.Action(action),
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
BucketName: resource, // overloading BucketName as that's what the policy engine uses to assemble a Resource.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,851 @@
|
|||||||
|
// Copyright (c) 2015-2024 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 (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
|
"github.com/minio/pkg/v3/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// KMS API paths
|
||||||
|
// For example: /minio/kms/v1/key/list?pattern=*
|
||||||
|
kmsURL = kmsPathPrefix + kmsAPIVersionPrefix
|
||||||
|
kmsStatusPath = kmsURL + "/status"
|
||||||
|
kmsMetricsPath = kmsURL + "/metrics"
|
||||||
|
kmsAPIsPath = kmsURL + "/apis"
|
||||||
|
kmsVersionPath = kmsURL + "/version"
|
||||||
|
kmsKeyCreatePath = kmsURL + "/key/create"
|
||||||
|
kmsKeyListPath = kmsURL + "/key/list"
|
||||||
|
kmsKeyStatusPath = kmsURL + "/key/status"
|
||||||
|
|
||||||
|
// Admin API paths
|
||||||
|
// For example: /minio/admin/v3/kms/status
|
||||||
|
adminURL = adminPathPrefix + adminAPIVersionPrefix
|
||||||
|
kmsAdminStatusPath = adminURL + "/kms/status"
|
||||||
|
kmsAdminKeyStatusPath = adminURL + "/kms/key/status"
|
||||||
|
kmsAdminKeyCreate = adminURL + "/kms/key/create"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
userAccessKey = "miniofakeuseraccesskey"
|
||||||
|
userSecretKey = "miniofakeusersecret"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kmsTestCase struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
query map[string]string
|
||||||
|
|
||||||
|
// User credentials and policy for request
|
||||||
|
policy string
|
||||||
|
asRoot bool
|
||||||
|
|
||||||
|
// Wanted in response.
|
||||||
|
wantStatusCode int
|
||||||
|
wantKeyNames []string
|
||||||
|
wantResp []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKMSHandlersCreateKey(t *testing.T) {
|
||||||
|
adminTestBed, tearDown := setupKMSTest(t, true)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tests := []kmsTestCase{
|
||||||
|
// Create key test
|
||||||
|
{
|
||||||
|
name: "create key as user with no policy want forbidden",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "new-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create key as user with no resources specified want success",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "new-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:CreateKey"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create key as user set policy to allow want success",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "second-new-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:CreateKey"],
|
||||||
|
"Resource": ["arn:minio:kms:::second-new-test-*"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create key as user set policy to non matching resource want forbidden",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "third-new-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:CreateKey"],
|
||||||
|
"Resource": ["arn:minio:kms:::non-matching-key-name"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for testNum, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
|
||||||
|
execKMSTest(t, test, adminTestBed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKMSHandlersKeyStatus(t *testing.T) {
|
||||||
|
adminTestBed, tearDown := setupKMSTest(t, true)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tests := []kmsTestCase{
|
||||||
|
{
|
||||||
|
name: "create a first key root user",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key status as root want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyStatusPath,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"abc-test-key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key status as user no policy want forbidden",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyStatusPath,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key status as user legacy no resources specified want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyStatusPath,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:KeyStatus"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"abc-test-key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key status as user set policy to allow only one key",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyStatusPath,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:KeyStatus"],
|
||||||
|
"Resource": ["arn:minio:kms:::abc-test-*"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"abc-test-key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key status as user set policy to allow non-matching key",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyStatusPath,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:KeyStatus"],
|
||||||
|
"Resource": ["arn:minio:kms:::xyz-test-key"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for testNum, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
|
||||||
|
execKMSTest(t, test, adminTestBed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKMSHandlersAPIs(t *testing.T) {
|
||||||
|
adminTestBed, tearDown := setupKMSTest(t, true)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tests := []kmsTestCase{
|
||||||
|
// Version test
|
||||||
|
{
|
||||||
|
name: "version as root want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsVersionPath,
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"version"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version as user with no policy want forbidden",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsVersionPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version as user with policy ignores resource want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsVersionPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:Version"],
|
||||||
|
"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"version"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// APIs test
|
||||||
|
{
|
||||||
|
name: "apis as root want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsAPIsPath,
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"stub/path"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "apis as user with no policy want forbidden",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsAPIsPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "apis as user with policy ignores resource want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsAPIsPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:API"],
|
||||||
|
"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"stub/path"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Metrics test
|
||||||
|
{
|
||||||
|
name: "metrics as root want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsMetricsPath,
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"kms"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metrics as user with no policy want forbidden",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsMetricsPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metrics as user with policy ignores resource want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsMetricsPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:Metrics"],
|
||||||
|
"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"kms"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Status tests
|
||||||
|
{
|
||||||
|
name: "status as root want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsStatusPath,
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"MinIO builtin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status as user with no policy want forbidden",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsStatusPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status as user with policy ignores resource want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsStatusPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:Status"],
|
||||||
|
"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"]}`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"MinIO builtin"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for testNum, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
|
||||||
|
execKMSTest(t, test, adminTestBed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKMSHandlersListKeys(t *testing.T) {
|
||||||
|
adminTestBed, tearDown := setupKMSTest(t, true)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tests := []kmsTestCase{
|
||||||
|
{
|
||||||
|
name: "create a first key root user",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a second key root user",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "xyz-test-key"},
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
|
||||||
|
// List keys tests
|
||||||
|
{
|
||||||
|
name: "list keys as root want all to be returned",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "*"},
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantKeyNames: []string{"default-test-key", "abc-test-key", "xyz-test-key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list keys as user with no policy want forbidden",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "*"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list keys as user with no resources specified want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "*"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:ListKeys"]
|
||||||
|
}`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantKeyNames: []string{"default-test-key", "abc-test-key", "xyz-test-key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list keys as user set policy resource to allow only one key",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "*"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:ListKeys"],
|
||||||
|
"Resource": ["arn:minio:kms:::abc*"]}`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantKeyNames: []string{"abc-test-key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list keys as user set policy to allow only one key, use pattern that includes correct key",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "abc*"},
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:ListKeys"],
|
||||||
|
"Resource": ["arn:minio:kms:::abc*"]}`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantKeyNames: []string{"abc-test-key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list keys as user set policy to allow only one key, use pattern that excludes correct key",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "xyz*"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:ListKeys"],
|
||||||
|
"Resource": ["arn:minio:kms:::abc*"]}`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantKeyNames: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list keys as user set policy that has no matching key resources",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "*"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["kms:ListKeys"],
|
||||||
|
"Resource": ["arn:minio:kms:::nonematch*"]}`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantKeyNames: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list keys as user set policy that allows listing but denies specific keys",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "*"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
// It looks like this should allow listing any key that isn't "default-test-key", however
|
||||||
|
// the policy engine matches all Deny statements first, without regard to Resources (for KMS).
|
||||||
|
// This is for backwards compatibility where historically KMS statements ignored Resources.
|
||||||
|
policy: `{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": ["kms:ListKeys"]
|
||||||
|
},{
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Action": ["kms:ListKeys"],
|
||||||
|
"Resource": ["arn:minio:kms:::default-test-key"]
|
||||||
|
}`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testNum, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
|
||||||
|
execKMSTest(t, test, adminTestBed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKMSHandlerAdminAPI(t *testing.T) {
|
||||||
|
adminTestBed, tearDown := setupKMSTest(t, true)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tests := []kmsTestCase{
|
||||||
|
// Create key tests
|
||||||
|
{
|
||||||
|
name: "create a key root user",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsAdminKeyCreate,
|
||||||
|
query: map[string]string{"key-id": "abc-test-key"},
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create key as user with no policy want forbidden",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsAdminKeyCreate,
|
||||||
|
query: map[string]string{"key-id": "new-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create key as user with no resources specified want success",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsAdminKeyCreate,
|
||||||
|
query: map[string]string{"key-id": "new-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["admin:KMSCreateKey"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create key as user set policy to non matching resource want success",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsAdminKeyCreate,
|
||||||
|
query: map[string]string{"key-id": "third-new-test-key"},
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
// Admin actions ignore Resources
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["admin:KMSCreateKey"],
|
||||||
|
"Resource": ["arn:minio:kms:::this-is-disregarded"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Status tests
|
||||||
|
{
|
||||||
|
name: "status as root want success",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsAdminStatusPath,
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"MinIO builtin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status as user with no policy want forbidden",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsAdminStatusPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status as user with policy ignores resource want success",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsAdminStatusPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["admin:KMSKeyStatus"],
|
||||||
|
"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"MinIO builtin"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Key status tests
|
||||||
|
{
|
||||||
|
name: "key status as root want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsAdminKeyStatusPath,
|
||||||
|
asRoot: true,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"key-id"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key status as user with no policy want forbidden",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsAdminKeyStatusPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantResp: []string{"AccessDenied"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key status as user with policy ignores resource want success",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsAdminKeyStatusPath,
|
||||||
|
asRoot: false,
|
||||||
|
|
||||||
|
policy: `{"Effect": "Allow",
|
||||||
|
"Action": ["admin:KMSKeyStatus"],
|
||||||
|
"Resource": ["arn:minio:kms:::does-not-matter-it-is-ignored"] }`,
|
||||||
|
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantResp: []string{"key-id"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testNum, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d %s", testNum+1, test.name), func(t *testing.T) {
|
||||||
|
execKMSTest(t, test, adminTestBed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execKMSTest runs a single test case for KMS handlers
|
||||||
|
func execKMSTest(t *testing.T, test kmsTestCase, adminTestBed *adminErasureTestBed) {
|
||||||
|
var accessKey, secretKey string
|
||||||
|
if test.asRoot {
|
||||||
|
accessKey, secretKey = globalActiveCred.AccessKey, globalActiveCred.SecretKey
|
||||||
|
} else {
|
||||||
|
setupKMSUser(t, userAccessKey, userSecretKey, test.policy)
|
||||||
|
accessKey = userAccessKey
|
||||||
|
secretKey = userSecretKey
|
||||||
|
}
|
||||||
|
|
||||||
|
req := buildKMSRequest(t, test.method, test.path, accessKey, secretKey, test.query)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
adminTestBed.router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
t.Logf("HTTP req: %s, resp code: %d, resp body: %s", req.URL.String(), rec.Code, rec.Body.String())
|
||||||
|
|
||||||
|
// Check status code
|
||||||
|
if rec.Code != test.wantStatusCode {
|
||||||
|
t.Errorf("want status code %d, got %d", test.wantStatusCode, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check returned key list is correct
|
||||||
|
if test.wantKeyNames != nil {
|
||||||
|
gotKeyNames := keyNamesFromListKeysResp(t, rec.Body.Bytes())
|
||||||
|
if len(test.wantKeyNames) != len(gotKeyNames) {
|
||||||
|
t.Fatalf("want keys len: %d, got len: %d", len(test.wantKeyNames), len(gotKeyNames))
|
||||||
|
}
|
||||||
|
for i, wantKeyName := range test.wantKeyNames {
|
||||||
|
if gotKeyNames[i] != wantKeyName {
|
||||||
|
t.Fatalf("want key name %s, in position %d, got %s", wantKeyName, i, gotKeyNames[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check generic text in the response
|
||||||
|
if test.wantResp != nil {
|
||||||
|
for _, want := range test.wantResp {
|
||||||
|
if !strings.Contains(rec.Body.String(), want) {
|
||||||
|
t.Fatalf("want response to contain %s, got %s", want, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestKMSHandlerNotConfiguredOrInvalidCreds tests KMS handlers for situations where KMS is not configured
|
||||||
|
// or invalid credentials are provided.
|
||||||
|
func TestKMSHandlerNotConfiguredOrInvalidCreds(t *testing.T) {
|
||||||
|
adminTestBed, tearDown := setupKMSTest(t, false)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
query map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "GET status",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsStatusPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET metrics",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsMetricsPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET apis",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsAPIsPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET version",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsVersionPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST key create",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: kmsKeyCreatePath,
|
||||||
|
query: map[string]string{"key-id": "master-key-id"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET key list",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyListPath,
|
||||||
|
query: map[string]string{"pattern": "*"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET key status",
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: kmsKeyStatusPath,
|
||||||
|
query: map[string]string{"key-id": "master-key-id"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test when the GlobalKMS is not configured
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name+" not configured", func(t *testing.T) {
|
||||||
|
req := buildKMSRequest(t, test.method, test.path, "", "", test.query)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
adminTestBed.router.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusNotImplemented {
|
||||||
|
t.Errorf("want status code %d, got %d", http.StatusNotImplemented, rec.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test when the GlobalKMS is configured but the credentials are invalid
|
||||||
|
GlobalKMS = kms.NewStub("default-test-key")
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name+" invalid credentials", func(t *testing.T) {
|
||||||
|
req := buildKMSRequest(t, test.method, test.path, userAccessKey, userSecretKey, test.query)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
adminTestBed.router.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusForbidden {
|
||||||
|
t.Errorf("want status code %d, got %d", http.StatusForbidden, rec.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupKMSTest(t *testing.T, enableKMS bool) (*adminErasureTestBed, func()) {
|
||||||
|
adminTestBed, err := prepareAdminErasureTestBed(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
registerKMSRouter(adminTestBed.router)
|
||||||
|
|
||||||
|
if enableKMS {
|
||||||
|
GlobalKMS = kms.NewStub("default-test-key")
|
||||||
|
}
|
||||||
|
|
||||||
|
tearDown := func() {
|
||||||
|
adminTestBed.TearDown()
|
||||||
|
GlobalKMS = nil
|
||||||
|
}
|
||||||
|
return adminTestBed, tearDown
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildKMSRequest(t *testing.T, method, path, accessKey, secretKey string, query map[string]string) *http.Request {
|
||||||
|
if len(query) > 0 {
|
||||||
|
queryVal := url.Values{}
|
||||||
|
for k, v := range query {
|
||||||
|
queryVal.Add(k, v)
|
||||||
|
}
|
||||||
|
path = path + "?" + queryVal.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
if accessKey == "" && secretKey == "" {
|
||||||
|
accessKey = globalActiveCred.AccessKey
|
||||||
|
secretKey = globalActiveCred.SecretKey
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := newTestSignedRequestV4(method, path, 0, nil, accessKey, secretKey, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupKMSUser is a test helper that creates a new user with the provided access key and secret key
|
||||||
|
// and applies the given policy to the user.
|
||||||
|
func setupKMSUser(t *testing.T, accessKey, secretKey, p string) {
|
||||||
|
ctx := context.Background()
|
||||||
|
createUserParams := madmin.AddOrUpdateUserReq{
|
||||||
|
SecretKey: secretKey,
|
||||||
|
Status: madmin.AccountEnabled,
|
||||||
|
}
|
||||||
|
_, err := globalIAMSys.CreateUser(ctx, accessKey, createUserParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testKMSPolicyName := "testKMSPolicy"
|
||||||
|
if p != "" {
|
||||||
|
p = `{"Version":"2012-10-17","Statement":[` + p + `]}`
|
||||||
|
policyData, err := policy.ParseConfig(strings.NewReader(p))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = globalIAMSys.SetPolicy(ctx, testKMSPolicyName, *policyData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = globalIAMSys.PolicyDBSet(ctx, accessKey, testKMSPolicyName, regUser, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = globalIAMSys.DeletePolicy(ctx, testKMSPolicyName, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = globalIAMSys.PolicyDBSet(ctx, accessKey, "", regUser, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyNamesFromListKeysResp(t *testing.T, b []byte) []string {
|
||||||
|
var keyInfos []madmin.KMSKeyInfo
|
||||||
|
err := json.Unmarshal(b, &keyInfos)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot unmarshal '%s', err: %v", b, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var gotKeyNames []string
|
||||||
|
for _, keyInfo := range keyInfos {
|
||||||
|
gotKeyNames = append(gotKeyNames, keyInfo.Name)
|
||||||
|
}
|
||||||
|
return gotKeyNames
|
||||||
|
}
|
||||||
+16
-12
@@ -34,8 +34,8 @@ type lockRequesterInfo struct {
|
|||||||
Name string // name of the resource lock was requested for
|
Name string // name of the resource lock was requested for
|
||||||
Writer bool // Bool whether write or read lock.
|
Writer bool // Bool whether write or read lock.
|
||||||
UID string // UID to uniquely identify request of client.
|
UID string // UID to uniquely identify request of client.
|
||||||
Timestamp time.Time // Timestamp set at the time of initialization.
|
Timestamp int64 // Timestamp set at the time of initialization.
|
||||||
TimeLastRefresh time.Time // Timestamp for last lock refresh.
|
TimeLastRefresh int64 // Timestamp for last lock refresh.
|
||||||
Source string // Contains line, function and filename requesting the lock.
|
Source string // Contains line, function and filename requesting the lock.
|
||||||
Group bool // indicates if it was a group lock.
|
Group bool // indicates if it was a group lock.
|
||||||
Owner string // Owner represents the UUID of the owner who originally requested the lock.
|
Owner string // Owner represents the UUID of the owner who originally requested the lock.
|
||||||
@@ -87,6 +87,7 @@ func (l *localLocker) Lock(ctx context.Context, args dsync.LockArgs) (reply bool
|
|||||||
|
|
||||||
// No locks held on the all resources, so claim write
|
// No locks held on the all resources, so claim write
|
||||||
// lock on all resources at once.
|
// lock on all resources at once.
|
||||||
|
now := UTCNow()
|
||||||
for i, resource := range args.Resources {
|
for i, resource := range args.Resources {
|
||||||
l.lockMap[resource] = []lockRequesterInfo{
|
l.lockMap[resource] = []lockRequesterInfo{
|
||||||
{
|
{
|
||||||
@@ -95,10 +96,10 @@ func (l *localLocker) Lock(ctx context.Context, args dsync.LockArgs) (reply bool
|
|||||||
Source: args.Source,
|
Source: args.Source,
|
||||||
Owner: args.Owner,
|
Owner: args.Owner,
|
||||||
UID: args.UID,
|
UID: args.UID,
|
||||||
Timestamp: UTCNow(),
|
Timestamp: now.UnixNano(),
|
||||||
TimeLastRefresh: UTCNow(),
|
TimeLastRefresh: now.UnixNano(),
|
||||||
Group: len(args.Resources) > 1,
|
Group: len(args.Resources) > 1,
|
||||||
Quorum: args.Quorum,
|
Quorum: *args.Quorum,
|
||||||
idx: i,
|
idx: i,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -108,7 +109,7 @@ func (l *localLocker) Lock(ctx context.Context, args dsync.LockArgs) (reply bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
func formatUUID(s string, idx int) string {
|
func formatUUID(s string, idx int) string {
|
||||||
return s + strconv.Itoa(idx)
|
return concat(s, strconv.Itoa(idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localLocker) Unlock(_ context.Context, args dsync.LockArgs) (reply bool, err error) {
|
func (l *localLocker) Unlock(_ context.Context, args dsync.LockArgs) (reply bool, err error) {
|
||||||
@@ -167,17 +168,19 @@ func (l *localLocker) RLock(ctx context.Context, args dsync.LockArgs) (reply boo
|
|||||||
l.mutex.Lock()
|
l.mutex.Lock()
|
||||||
defer l.mutex.Unlock()
|
defer l.mutex.Unlock()
|
||||||
resource := args.Resources[0]
|
resource := args.Resources[0]
|
||||||
|
now := UTCNow()
|
||||||
lrInfo := lockRequesterInfo{
|
lrInfo := lockRequesterInfo{
|
||||||
Name: resource,
|
Name: resource,
|
||||||
Writer: false,
|
Writer: false,
|
||||||
Source: args.Source,
|
Source: args.Source,
|
||||||
Owner: args.Owner,
|
Owner: args.Owner,
|
||||||
UID: args.UID,
|
UID: args.UID,
|
||||||
Timestamp: UTCNow(),
|
Timestamp: now.UnixNano(),
|
||||||
TimeLastRefresh: UTCNow(),
|
TimeLastRefresh: now.UnixNano(),
|
||||||
Quorum: args.Quorum,
|
Quorum: *args.Quorum,
|
||||||
}
|
}
|
||||||
if lri, ok := l.lockMap[resource]; ok {
|
lri, ok := l.lockMap[resource]
|
||||||
|
if ok {
|
||||||
if reply = !isWriteLock(lri); reply {
|
if reply = !isWriteLock(lri); reply {
|
||||||
// Unless there is a write lock
|
// Unless there is a write lock
|
||||||
l.lockMap[resource] = append(l.lockMap[resource], lrInfo)
|
l.lockMap[resource] = append(l.lockMap[resource], lrInfo)
|
||||||
@@ -351,9 +354,10 @@ func (l *localLocker) Refresh(ctx context.Context, args dsync.LockArgs) (refresh
|
|||||||
delete(l.lockUID, formatUUID(args.UID, idx))
|
delete(l.lockUID, formatUUID(args.UID, idx))
|
||||||
return idx > 0, nil
|
return idx > 0, nil
|
||||||
}
|
}
|
||||||
|
now := UTCNow()
|
||||||
for i := range lris {
|
for i := range lris {
|
||||||
if lris[i].UID == args.UID {
|
if lris[i].UID == args.UID {
|
||||||
lris[i].TimeLastRefresh = UTCNow()
|
lris[i].TimeLastRefresh = now.UnixNano()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
idx++
|
idx++
|
||||||
@@ -376,7 +380,7 @@ func (l *localLocker) expireOldLocks(interval time.Duration) {
|
|||||||
modified := false
|
modified := false
|
||||||
for i := 0; i < len(lris); {
|
for i := 0; i < len(lris); {
|
||||||
lri := &lris[i]
|
lri := &lris[i]
|
||||||
if time.Since(lri.TimeLastRefresh) > interval {
|
if time.Since(time.Unix(0, lri.TimeLastRefresh)) > interval {
|
||||||
delete(l.lockUID, formatUUID(lri.UID, lri.idx))
|
delete(l.lockUID, formatUUID(lri.UID, lri.idx))
|
||||||
if len(lris) == 1 {
|
if len(lris) == 1 {
|
||||||
// Remove the write lock.
|
// Remove the write lock.
|
||||||
|
|||||||
@@ -204,13 +204,13 @@ func (z *lockRequesterInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Timestamp":
|
case "Timestamp":
|
||||||
z.Timestamp, err = dc.ReadTime()
|
z.Timestamp, err = dc.ReadInt64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Timestamp")
|
err = msgp.WrapError(err, "Timestamp")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "TimeLastRefresh":
|
case "TimeLastRefresh":
|
||||||
z.TimeLastRefresh, err = dc.ReadTime()
|
z.TimeLastRefresh, err = dc.ReadInt64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "TimeLastRefresh")
|
err = msgp.WrapError(err, "TimeLastRefresh")
|
||||||
return
|
return
|
||||||
@@ -288,7 +288,7 @@ func (z *lockRequesterInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteTime(z.Timestamp)
|
err = en.WriteInt64(z.Timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Timestamp")
|
err = msgp.WrapError(err, "Timestamp")
|
||||||
return
|
return
|
||||||
@@ -298,7 +298,7 @@ func (z *lockRequesterInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteTime(z.TimeLastRefresh)
|
err = en.WriteInt64(z.TimeLastRefresh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "TimeLastRefresh")
|
err = msgp.WrapError(err, "TimeLastRefresh")
|
||||||
return
|
return
|
||||||
@@ -361,10 +361,10 @@ func (z *lockRequesterInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
o = msgp.AppendString(o, z.UID)
|
o = msgp.AppendString(o, z.UID)
|
||||||
// string "Timestamp"
|
// string "Timestamp"
|
||||||
o = append(o, 0xa9, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70)
|
o = append(o, 0xa9, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70)
|
||||||
o = msgp.AppendTime(o, z.Timestamp)
|
o = msgp.AppendInt64(o, z.Timestamp)
|
||||||
// string "TimeLastRefresh"
|
// string "TimeLastRefresh"
|
||||||
o = append(o, 0xaf, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68)
|
o = append(o, 0xaf, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68)
|
||||||
o = msgp.AppendTime(o, z.TimeLastRefresh)
|
o = msgp.AppendInt64(o, z.TimeLastRefresh)
|
||||||
// string "Source"
|
// string "Source"
|
||||||
o = append(o, 0xa6, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65)
|
o = append(o, 0xa6, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65)
|
||||||
o = msgp.AppendString(o, z.Source)
|
o = msgp.AppendString(o, z.Source)
|
||||||
@@ -417,13 +417,13 @@ func (z *lockRequesterInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Timestamp":
|
case "Timestamp":
|
||||||
z.Timestamp, bts, err = msgp.ReadTimeBytes(bts)
|
z.Timestamp, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Timestamp")
|
err = msgp.WrapError(err, "Timestamp")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "TimeLastRefresh":
|
case "TimeLastRefresh":
|
||||||
z.TimeLastRefresh, bts, err = msgp.ReadTimeBytes(bts)
|
z.TimeLastRefresh, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "TimeLastRefresh")
|
err = msgp.WrapError(err, "TimeLastRefresh")
|
||||||
return
|
return
|
||||||
@@ -466,7 +466,7 @@ func (z *lockRequesterInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *lockRequesterInfo) Msgsize() (s int) {
|
func (z *lockRequesterInfo) Msgsize() (s int) {
|
||||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 7 + msgp.BoolSize + 4 + msgp.StringPrefixSize + len(z.UID) + 10 + msgp.TimeSize + 16 + msgp.TimeSize + 7 + msgp.StringPrefixSize + len(z.Source) + 6 + msgp.BoolSize + 6 + msgp.StringPrefixSize + len(z.Owner) + 7 + msgp.IntSize
|
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 7 + msgp.BoolSize + 4 + msgp.StringPrefixSize + len(z.UID) + 10 + msgp.Int64Size + 16 + msgp.Int64Size + 7 + msgp.StringPrefixSize + len(z.Source) + 6 + msgp.BoolSize + 6 + msgp.StringPrefixSize + len(z.Owner) + 7 + msgp.IntSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-11
@@ -32,6 +32,7 @@ import (
|
|||||||
func TestLocalLockerExpire(t *testing.T) {
|
func TestLocalLockerExpire(t *testing.T) {
|
||||||
wResources := make([]string, 1000)
|
wResources := make([]string, 1000)
|
||||||
rResources := make([]string, 1000)
|
rResources := make([]string, 1000)
|
||||||
|
quorum := 0
|
||||||
l := newLocker()
|
l := newLocker()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for i := range wResources {
|
for i := range wResources {
|
||||||
@@ -40,7 +41,7 @@ func TestLocalLockerExpire(t *testing.T) {
|
|||||||
Resources: []string{mustGetUUID()},
|
Resources: []string{mustGetUUID()},
|
||||||
Source: t.Name(),
|
Source: t.Name(),
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
}
|
}
|
||||||
ok, err := l.Lock(ctx, arg)
|
ok, err := l.Lock(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,14 +59,14 @@ func TestLocalLockerExpire(t *testing.T) {
|
|||||||
Resources: []string{name},
|
Resources: []string{name},
|
||||||
Source: t.Name(),
|
Source: t.Name(),
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
}
|
}
|
||||||
ok, err := l.RLock(ctx, arg)
|
ok, err := l.RLock(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("did not get write lock")
|
t.Fatal("did not get read lock")
|
||||||
}
|
}
|
||||||
// RLock twice
|
// RLock twice
|
||||||
ok, err = l.RLock(ctx, arg)
|
ok, err = l.RLock(ctx, arg)
|
||||||
@@ -112,6 +113,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
rUIDs := make([]string, 0, n*2)
|
rUIDs := make([]string, 0, n*2)
|
||||||
l := newLocker()
|
l := newLocker()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
quorum := 0
|
||||||
for i := range wResources {
|
for i := range wResources {
|
||||||
names := [m]string{}
|
names := [m]string{}
|
||||||
for j := range names {
|
for j := range names {
|
||||||
@@ -123,7 +125,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
Resources: names[:],
|
Resources: names[:],
|
||||||
Source: t.Name(),
|
Source: t.Name(),
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
}
|
}
|
||||||
ok, err := l.Lock(ctx, arg)
|
ok, err := l.Lock(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -144,7 +146,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
Resources: []string{name},
|
Resources: []string{name},
|
||||||
Source: t.Name(),
|
Source: t.Name(),
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
}
|
}
|
||||||
ok, err := l.RLock(ctx, arg)
|
ok, err := l.RLock(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -183,7 +185,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
Resources: []string{name},
|
Resources: []string{name},
|
||||||
Source: t.Name(),
|
Source: t.Name(),
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
}
|
}
|
||||||
ok, err := l.RUnlock(ctx, arg)
|
ok, err := l.RUnlock(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -203,6 +205,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
if len(l.lockUID) != len(rResources)+len(wResources)*m {
|
if len(l.lockUID) != len(rResources)+len(wResources)*m {
|
||||||
t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources)*m)
|
t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources)*m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RUnlock again, different uids
|
// RUnlock again, different uids
|
||||||
for i, name := range rResources {
|
for i, name := range rResources {
|
||||||
arg := dsync.LockArgs{
|
arg := dsync.LockArgs{
|
||||||
@@ -210,7 +213,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
Resources: []string{name},
|
Resources: []string{name},
|
||||||
Source: "minio",
|
Source: "minio",
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
}
|
}
|
||||||
ok, err := l.RUnlock(ctx, arg)
|
ok, err := l.RUnlock(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -238,7 +241,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
Resources: names[:],
|
Resources: names[:],
|
||||||
Source: "minio",
|
Source: "minio",
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
}
|
}
|
||||||
ok, err := l.Unlock(ctx, arg)
|
ok, err := l.Unlock(ctx, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -261,6 +264,7 @@ func TestLocalLockerUnlock(t *testing.T) {
|
|||||||
|
|
||||||
func Test_localLocker_expireOldLocksExpire(t *testing.T) {
|
func Test_localLocker_expireOldLocksExpire(t *testing.T) {
|
||||||
rng := rand.New(rand.NewSource(0))
|
rng := rand.New(rand.NewSource(0))
|
||||||
|
quorum := 0
|
||||||
// Numbers of unique locks
|
// Numbers of unique locks
|
||||||
for _, locks := range []int{100, 1000, 1e6} {
|
for _, locks := range []int{100, 1000, 1e6} {
|
||||||
if testing.Short() && locks > 100 {
|
if testing.Short() && locks > 100 {
|
||||||
@@ -289,7 +293,7 @@ func Test_localLocker_expireOldLocksExpire(t *testing.T) {
|
|||||||
Resources: res,
|
Resources: res,
|
||||||
Source: hex.EncodeToString(tmp[:8]),
|
Source: hex.EncodeToString(tmp[:8]),
|
||||||
Owner: hex.EncodeToString(tmp[8:]),
|
Owner: hex.EncodeToString(tmp[8:]),
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
})
|
})
|
||||||
if !ok || err != nil {
|
if !ok || err != nil {
|
||||||
t.Fatal("failed:", err, ok)
|
t.Fatal("failed:", err, ok)
|
||||||
@@ -311,7 +315,7 @@ func Test_localLocker_expireOldLocksExpire(t *testing.T) {
|
|||||||
for _, v := range l.lockMap {
|
for _, v := range l.lockMap {
|
||||||
for i := range v {
|
for i := range v {
|
||||||
if rng.Intn(2) == 0 {
|
if rng.Intn(2) == 0 {
|
||||||
v[i].TimeLastRefresh = expired
|
v[i].TimeLastRefresh = expired.UnixNano()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,6 +351,7 @@ func Test_localLocker_expireOldLocksExpire(t *testing.T) {
|
|||||||
|
|
||||||
func Test_localLocker_RUnlock(t *testing.T) {
|
func Test_localLocker_RUnlock(t *testing.T) {
|
||||||
rng := rand.New(rand.NewSource(0))
|
rng := rand.New(rand.NewSource(0))
|
||||||
|
quorum := 0
|
||||||
// Numbers of unique locks
|
// Numbers of unique locks
|
||||||
for _, locks := range []int{1, 100, 1000, 1e6} {
|
for _, locks := range []int{1, 100, 1000, 1e6} {
|
||||||
if testing.Short() && locks > 100 {
|
if testing.Short() && locks > 100 {
|
||||||
@@ -375,7 +380,7 @@ func Test_localLocker_RUnlock(t *testing.T) {
|
|||||||
Resources: res,
|
Resources: res,
|
||||||
Source: hex.EncodeToString(tmp[:8]),
|
Source: hex.EncodeToString(tmp[:8]),
|
||||||
Owner: hex.EncodeToString(tmp[8:]),
|
Owner: hex.EncodeToString(tmp[8:]),
|
||||||
Quorum: 0,
|
Quorum: &quorum,
|
||||||
})
|
})
|
||||||
if !ok || err != nil {
|
if !ok || err != nil {
|
||||||
t.Fatal("failed:", err, ok)
|
t.Fatal("failed:", err, ok)
|
||||||
|
|||||||
@@ -107,5 +107,5 @@ func newLockAPI(endpoint Endpoint) dsync.NetLocker {
|
|||||||
|
|
||||||
// Returns a lock rest client.
|
// Returns a lock rest client.
|
||||||
func newlockRESTClient(ep Endpoint) *lockRESTClient {
|
func newlockRESTClient(ep Endpoint) *lockRESTClient {
|
||||||
return &lockRESTClient{globalGrid.Load().Connection(ep.GridHost())}
|
return &lockRESTClient{globalLockGrid.Load().Connection(ep.GridHost())}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func TestLockRESTlient(t *testing.T) {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err = initGlobalGrid(ctx, []PoolEndpoints{{Endpoints: Endpoints{endpoint, endpointLocal}}})
|
err = initGlobalLockGrid(ctx, []PoolEndpoints{{Endpoints: Endpoints{endpoint, endpointLocal}}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func createLockTestServer(ctx context.Context, t *testing.T) (string, *lockRESTS
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
creds := globalActiveCred
|
creds := globalActiveCred
|
||||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
|
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -63,15 +63,15 @@ func TestLockRpcServerRemoveEntry(t *testing.T) {
|
|||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Writer: true,
|
Writer: true,
|
||||||
UID: "0123-4567",
|
UID: "0123-4567",
|
||||||
Timestamp: UTCNow(),
|
Timestamp: UTCNow().UnixNano(),
|
||||||
TimeLastRefresh: UTCNow(),
|
TimeLastRefresh: UTCNow().UnixNano(),
|
||||||
}
|
}
|
||||||
lockRequesterInfo2 := lockRequesterInfo{
|
lockRequesterInfo2 := lockRequesterInfo{
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Writer: true,
|
Writer: true,
|
||||||
UID: "89ab-cdef",
|
UID: "89ab-cdef",
|
||||||
Timestamp: UTCNow(),
|
Timestamp: UTCNow().UnixNano(),
|
||||||
TimeLastRefresh: UTCNow(),
|
TimeLastRefresh: UTCNow().UnixNano(),
|
||||||
}
|
}
|
||||||
|
|
||||||
locker.ll.lockMap["name"] = []lockRequesterInfo{
|
locker.ll.lockMap["name"] = []lockRequesterInfo{
|
||||||
|
|||||||
@@ -111,17 +111,17 @@ func newLockHandler(h grid.HandlerID) *grid.SingleHandler[*dsync.LockArgs, *dsyn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// registerLockRESTHandlers - register lock rest router.
|
// registerLockRESTHandlers - register lock rest router.
|
||||||
func registerLockRESTHandlers() {
|
func registerLockRESTHandlers(gm *grid.Manager) {
|
||||||
lockServer := &lockRESTServer{
|
lockServer := &lockRESTServer{
|
||||||
ll: newLocker(),
|
ll: newLocker(),
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.FatalIf(lockRPCForceUnlock.Register(globalGrid.Load(), lockServer.ForceUnlockHandler), "unable to register handler")
|
logger.FatalIf(lockRPCForceUnlock.Register(gm, lockServer.ForceUnlockHandler), "unable to register handler")
|
||||||
logger.FatalIf(lockRPCRefresh.Register(globalGrid.Load(), lockServer.RefreshHandler), "unable to register handler")
|
logger.FatalIf(lockRPCRefresh.Register(gm, lockServer.RefreshHandler), "unable to register handler")
|
||||||
logger.FatalIf(lockRPCLock.Register(globalGrid.Load(), lockServer.LockHandler), "unable to register handler")
|
logger.FatalIf(lockRPCLock.Register(gm, lockServer.LockHandler), "unable to register handler")
|
||||||
logger.FatalIf(lockRPCUnlock.Register(globalGrid.Load(), lockServer.UnlockHandler), "unable to register handler")
|
logger.FatalIf(lockRPCUnlock.Register(gm, lockServer.UnlockHandler), "unable to register handler")
|
||||||
logger.FatalIf(lockRPCRLock.Register(globalGrid.Load(), lockServer.RLockHandler), "unable to register handler")
|
logger.FatalIf(lockRPCRLock.Register(gm, lockServer.RLockHandler), "unable to register handler")
|
||||||
logger.FatalIf(lockRPCRUnlock.Register(globalGrid.Load(), lockServer.RUnlockHandler), "unable to register handler")
|
logger.FatalIf(lockRPCRUnlock.Register(gm, lockServer.RUnlockHandler), "unable to register handler")
|
||||||
|
|
||||||
globalLockServer = lockServer.ll
|
globalLockServer = lockServer.ll
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ func newApp(name string) *cli.App {
|
|||||||
|
|
||||||
// registerCommand registers a cli command.
|
// registerCommand registers a cli command.
|
||||||
registerCommand := func(command cli.Command) {
|
registerCommand := func(command cli.Command) {
|
||||||
|
// avoid registering commands which are not being built (via
|
||||||
|
// go:build tags)
|
||||||
|
if command.Name == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
commands = append(commands, command)
|
commands = append(commands, command)
|
||||||
commandsTree.Insert(command.Name)
|
commandsTree.Insert(command.Name)
|
||||||
}
|
}
|
||||||
@@ -134,6 +139,7 @@ func newApp(name string) *cli.App {
|
|||||||
|
|
||||||
// Register all commands.
|
// Register all commands.
|
||||||
registerCommand(serverCmd)
|
registerCommand(serverCmd)
|
||||||
|
registerCommand(fmtGenCmd)
|
||||||
|
|
||||||
// Set up app.
|
// Set up app.
|
||||||
cli.HelpFlag = cli.BoolFlag{
|
cli.HelpFlag = cli.BoolFlag{
|
||||||
|
|||||||
@@ -153,19 +153,7 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (
|
|||||||
} else {
|
} else {
|
||||||
// Continue listing
|
// Continue listing
|
||||||
o.ID = c.id
|
o.ID = c.id
|
||||||
go func(meta metacache) {
|
go c.keepAlive(ctx, rpc)
|
||||||
// Continuously update while we wait.
|
|
||||||
t := time.NewTicker(metacacheMaxClientWait / 10)
|
|
||||||
defer t.Stop()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
// Request is done, stop updating.
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
meta.lastHandout = time.Now()
|
|
||||||
meta, _ = rpc.UpdateMetacacheListing(ctx, meta)
|
|
||||||
}
|
|
||||||
}(*c)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,6 +207,9 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (
|
|||||||
o.ID = ""
|
o.ID = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if contextCanceled(ctx) {
|
||||||
|
return entries, ctx.Err()
|
||||||
|
}
|
||||||
// Do listing in-place.
|
// Do listing in-place.
|
||||||
// Create output for our results.
|
// Create output for our results.
|
||||||
// Create filter for results.
|
// Create filter for results.
|
||||||
@@ -449,5 +440,10 @@ func (z *erasureServerPools) listAndSave(ctx context.Context, o *listPathOptions
|
|||||||
xioutil.SafeClose(saveCh)
|
xioutil.SafeClose(saveCh)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return filteredResults()
|
entries, err = filteredResults()
|
||||||
|
if err == nil {
|
||||||
|
// Check if listing recorded an error.
|
||||||
|
err = meta.getErr()
|
||||||
|
}
|
||||||
|
return entries, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -805,6 +805,17 @@ func (m *metaCacheRPC) setErr(err string) {
|
|||||||
*m.meta = meta
|
*m.meta = meta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getErr will return an error if the listing failed.
|
||||||
|
// The error is not type safe.
|
||||||
|
func (m *metaCacheRPC) getErr() error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if m.meta.status == scanStateError {
|
||||||
|
return errors.New(m.meta.error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (er *erasureObjects) saveMetaCacheStream(ctx context.Context, mc *metaCacheRPC, entries <-chan metaCacheEntry) (err error) {
|
func (er *erasureObjects) saveMetaCacheStream(ctx context.Context, mc *metaCacheRPC, entries <-chan metaCacheEntry) (err error) {
|
||||||
o := mc.o
|
o := mc.o
|
||||||
o.debugf(color.Green("saveMetaCacheStream:")+" with options: %#v", o)
|
o.debugf(color.Green("saveMetaCacheStream:")+" with options: %#v", o)
|
||||||
|
|||||||
+42
-5
@@ -24,6 +24,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/pkg/v3/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
type scanStatus uint8
|
type scanStatus uint8
|
||||||
@@ -97,6 +99,37 @@ func (m *metacache) worthKeeping() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keepAlive will continuously update lastHandout until ctx is canceled.
|
||||||
|
func (m metacache) keepAlive(ctx context.Context, rpc *peerRESTClient) {
|
||||||
|
// we intentionally operate on a copy of m, so we can update without locks.
|
||||||
|
t := time.NewTicker(metacacheMaxClientWait / 10)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Request is done, stop updating.
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
m.lastHandout = time.Now()
|
||||||
|
|
||||||
|
if m2, err := rpc.UpdateMetacacheListing(ctx, m); err == nil {
|
||||||
|
if m2.status != scanStateStarted {
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugln("returning", m.id, "due to scan state", m2.status, time.Now().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m = m2
|
||||||
|
if serverDebugLog {
|
||||||
|
console.Debugln("refreshed", m.id, time.Now().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
} else if serverDebugLog {
|
||||||
|
console.Debugln("error refreshing", m.id, time.Now().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// baseDirFromPrefix will return the base directory given an object path.
|
// baseDirFromPrefix will return the base directory given an object path.
|
||||||
// For example an object with name prefix/folder/object.ext will return `prefix/folder/`.
|
// For example an object with name prefix/folder/object.ext will return `prefix/folder/`.
|
||||||
func baseDirFromPrefix(prefix string) string {
|
func baseDirFromPrefix(prefix string) string {
|
||||||
@@ -116,13 +149,17 @@ func baseDirFromPrefix(prefix string) string {
|
|||||||
// update cache with new status.
|
// update cache with new status.
|
||||||
// The updates are conditional so multiple callers can update with different states.
|
// The updates are conditional so multiple callers can update with different states.
|
||||||
func (m *metacache) update(update metacache) {
|
func (m *metacache) update(update metacache) {
|
||||||
m.lastUpdate = UTCNow()
|
now := UTCNow()
|
||||||
|
m.lastUpdate = now
|
||||||
|
|
||||||
if m.lastHandout.After(m.lastHandout) {
|
if update.lastHandout.After(m.lastHandout) {
|
||||||
m.lastHandout = UTCNow()
|
m.lastHandout = update.lastUpdate
|
||||||
|
if m.lastHandout.After(now) {
|
||||||
|
m.lastHandout = now
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if m.status == scanStateStarted && update.status == scanStateSuccess {
|
if m.status == scanStateStarted && update.status == scanStateSuccess {
|
||||||
m.ended = UTCNow()
|
m.ended = now
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.status == scanStateStarted && update.status != scanStateStarted {
|
if m.status == scanStateStarted && update.status != scanStateStarted {
|
||||||
@@ -138,7 +175,7 @@ func (m *metacache) update(update metacache) {
|
|||||||
if m.error == "" && update.error != "" {
|
if m.error == "" && update.error != "" {
|
||||||
m.error = update.error
|
m.error = update.error
|
||||||
m.status = scanStateError
|
m.status = scanStateError
|
||||||
m.ended = UTCNow()
|
m.ended = now
|
||||||
}
|
}
|
||||||
m.fileNotFound = m.fileNotFound || update.fileNotFound
|
m.fileNotFound = m.fileNotFound || update.fileNotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ func collectDriveMetrics(m madmin.RealtimeMetrics) {
|
|||||||
latestDriveStatsMu.Unlock()
|
latestDriveStatsMu.Unlock()
|
||||||
|
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
for _, d := range localDrives {
|
for _, d := range localDrives {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const (
|
|||||||
// Standard env prometheus auth type
|
// Standard env prometheus auth type
|
||||||
const (
|
const (
|
||||||
EnvPrometheusAuthType = "MINIO_PROMETHEUS_AUTH_TYPE"
|
EnvPrometheusAuthType = "MINIO_PROMETHEUS_AUTH_TYPE"
|
||||||
|
EnvPrometheusOpenMetrics = "MINIO_PROMETHEUS_OPEN_METRICS"
|
||||||
)
|
)
|
||||||
|
|
||||||
type prometheusAuthType string
|
type prometheusAuthType string
|
||||||
@@ -58,14 +59,15 @@ func registerMetricsRouter(router *mux.Router) {
|
|||||||
if authType == prometheusPublic {
|
if authType == prometheusPublic {
|
||||||
auth = NoAuthMiddleware
|
auth = NoAuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsRouter.Handle(prometheusMetricsPathLegacy, auth(metricsHandler()))
|
metricsRouter.Handle(prometheusMetricsPathLegacy, auth(metricsHandler()))
|
||||||
metricsRouter.Handle(prometheusMetricsV2ClusterPath, auth(metricsServerHandler()))
|
metricsRouter.Handle(prometheusMetricsV2ClusterPath, auth(metricsServerHandler()))
|
||||||
metricsRouter.Handle(prometheusMetricsV2BucketPath, auth(metricsBucketHandler()))
|
metricsRouter.Handle(prometheusMetricsV2BucketPath, auth(metricsBucketHandler()))
|
||||||
metricsRouter.Handle(prometheusMetricsV2NodePath, auth(metricsNodeHandler()))
|
metricsRouter.Handle(prometheusMetricsV2NodePath, auth(metricsNodeHandler()))
|
||||||
metricsRouter.Handle(prometheusMetricsV2ResourcePath, auth(metricsResourceHandler()))
|
metricsRouter.Handle(prometheusMetricsV2ResourcePath, auth(metricsResourceHandler()))
|
||||||
|
|
||||||
// Metrics v3!
|
// Metrics v3
|
||||||
metricsV3Server := newMetricsV3Server(authType)
|
metricsV3Server := newMetricsV3Server(auth)
|
||||||
|
|
||||||
// Register metrics v3 handler. It also accepts an optional query
|
// Register metrics v3 handler. It also accepts an optional query
|
||||||
// parameter `?list` - see handler for details.
|
// parameter `?list` - see handler for details.
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -127,7 +129,8 @@ func loadAPIRequestsHTTPMetrics(ctx context.Context, m MetricValues, _ *metricsC
|
|||||||
// This is a `MetricsLoaderFn`.
|
// This is a `MetricsLoaderFn`.
|
||||||
func loadAPIRequestsTTFBMetrics(ctx context.Context, m MetricValues, _ *metricsCache) error {
|
func loadAPIRequestsTTFBMetrics(ctx context.Context, m MetricValues, _ *metricsCache) error {
|
||||||
renameLabels := map[string]string{"api": "name"}
|
renameLabels := map[string]string{"api": "name"}
|
||||||
m.SetHistogram(apiRequestsTTFBSecondsDistribution, httpRequestsDuration, renameLabels, nil,
|
labelsFilter := map[string]set.StringSet{"api": set.CreateStringSet("GetObject")}
|
||||||
|
m.SetHistogram(apiRequestsTTFBSecondsDistribution, httpRequestsDuration, labelsFilter, renameLabels, nil,
|
||||||
"type", "s3")
|
"type", "s3")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -214,7 +217,8 @@ func loadBucketAPIHTTPMetrics(ctx context.Context, m MetricValues, _ *metricsCac
|
|||||||
// This is a `MetricsLoaderFn`.
|
// This is a `MetricsLoaderFn`.
|
||||||
func loadBucketAPITTFBMetrics(ctx context.Context, m MetricValues, _ *metricsCache, buckets []string) error {
|
func loadBucketAPITTFBMetrics(ctx context.Context, m MetricValues, _ *metricsCache, buckets []string) error {
|
||||||
renameLabels := map[string]string{"api": "name"}
|
renameLabels := map[string]string{"api": "name"}
|
||||||
m.SetHistogram(apiRequestsTTFBSecondsDistribution, bucketHTTPRequestsDuration, renameLabels,
|
labelsFilter := map[string]set.StringSet{"api": set.CreateStringSet("GetObject")}
|
||||||
|
m.SetHistogram(apiRequestsTTFBSecondsDistribution, bucketHTTPRequestsDuration, labelsFilter, renameLabels,
|
||||||
buckets, "type", "s3")
|
buckets, "type", "s3")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// loadClusterUsageBucketMetrics - `MetricsLoaderFn` to load bucket usage metrics.
|
// loadClusterUsageBucketMetrics - `MetricsLoaderFn` to load bucket usage metrics.
|
||||||
func loadClusterUsageBucketMetrics(ctx context.Context, m MetricValues, c *metricsCache, buckets []string) error {
|
func loadClusterUsageBucketMetrics(ctx context.Context, m MetricValues, c *metricsCache) error {
|
||||||
dataUsageInfo, err := c.dataUsageInfo.Get()
|
dataUsageInfo, err := c.dataUsageInfo.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
metricsLogIf(ctx, err)
|
metricsLogIf(ctx, err)
|
||||||
@@ -153,11 +153,7 @@ func loadClusterUsageBucketMetrics(ctx context.Context, m MetricValues, c *metri
|
|||||||
|
|
||||||
m.Set(usageSinceLastUpdateSeconds, float64(time.Since(dataUsageInfo.LastUpdate)))
|
m.Set(usageSinceLastUpdateSeconds, float64(time.Since(dataUsageInfo.LastUpdate)))
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for bucket, usage := range dataUsageInfo.BucketsUsage {
|
||||||
usage, ok := dataUsageInfo.BucketsUsage[bucket]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
quota, err := globalBucketQuotaSys.Get(ctx, bucket)
|
quota, err := globalBucketQuotaSys.Get(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log and continue if we are unable to retrieve metrics for this
|
// Log and continue if we are unable to retrieve metrics for this
|
||||||
|
|||||||
+21
-20
@@ -23,9 +23,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/mcontext"
|
"github.com/minio/minio/internal/mcontext"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
|
"github.com/minio/pkg/v3/env"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
@@ -33,41 +36,39 @@ import (
|
|||||||
type promLogger struct{}
|
type promLogger struct{}
|
||||||
|
|
||||||
func (p promLogger) Println(v ...interface{}) {
|
func (p promLogger) Println(v ...interface{}) {
|
||||||
s := make([]string, 0, len(v))
|
metricsLogIf(GlobalContext, fmt.Errorf("metrics handler error: %v", v))
|
||||||
for _, val := range v {
|
|
||||||
s = append(s, fmt.Sprintf("%v", val))
|
|
||||||
}
|
|
||||||
err := fmt.Errorf("metrics handler error: %v", strings.Join(s, " "))
|
|
||||||
metricsLogIf(GlobalContext, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type metricsV3Server struct {
|
type metricsV3Server struct {
|
||||||
registry *prometheus.Registry
|
registry *prometheus.Registry
|
||||||
opts promhttp.HandlerOpts
|
opts promhttp.HandlerOpts
|
||||||
authFn func(http.Handler) http.Handler
|
auth func(http.Handler) http.Handler
|
||||||
|
|
||||||
metricsData *metricsV3Collection
|
metricsData *metricsV3Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMetricsV3Server(authType prometheusAuthType) *metricsV3Server {
|
var (
|
||||||
|
globalMetricsV3CollectorPaths []collectorPath
|
||||||
|
globalMetricsV3Once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMetricsV3Server(auth func(h http.Handler) http.Handler) *metricsV3Server {
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
authFn := AuthMiddleware
|
|
||||||
if authType == prometheusPublic {
|
|
||||||
authFn = NoAuthMiddleware
|
|
||||||
}
|
|
||||||
|
|
||||||
metricGroups := newMetricGroups(registry)
|
metricGroups := newMetricGroups(registry)
|
||||||
|
globalMetricsV3Once.Do(func() {
|
||||||
|
globalMetricsV3CollectorPaths = metricGroups.collectorPaths
|
||||||
|
})
|
||||||
return &metricsV3Server{
|
return &metricsV3Server{
|
||||||
registry: registry,
|
registry: registry,
|
||||||
opts: promhttp.HandlerOpts{
|
opts: promhttp.HandlerOpts{
|
||||||
ErrorLog: promLogger{},
|
ErrorLog: promLogger{},
|
||||||
ErrorHandling: promhttp.HTTPErrorOnError,
|
ErrorHandling: promhttp.ContinueOnError,
|
||||||
Registry: registry,
|
Registry: registry,
|
||||||
MaxRequestsInFlight: 2,
|
MaxRequestsInFlight: 2,
|
||||||
|
EnableOpenMetrics: env.Get(EnvPrometheusOpenMetrics, config.EnableOff) == config.EnableOn,
|
||||||
|
ProcessStartTime: globalBootTime,
|
||||||
},
|
},
|
||||||
authFn: authFn,
|
auth: auth,
|
||||||
|
|
||||||
metricsData: metricGroups,
|
metricsData: metricGroups,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +164,7 @@ func (h *metricsV3Server) handle(path string, isListingRequest bool, buckets []s
|
|||||||
http.Error(w, "Metrics Resource Not found", http.StatusNotFound)
|
http.Error(w, "Metrics Resource Not found", http.StatusNotFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Require that metrics path has at least component.
|
// Require that metrics path has one component at least.
|
||||||
if path == "/" {
|
if path == "/" {
|
||||||
return notFoundHandler
|
return notFoundHandler
|
||||||
}
|
}
|
||||||
@@ -221,7 +222,7 @@ func (h *metricsV3Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
pathComponents := mux.Vars(r)["pathComps"]
|
pathComponents := mux.Vars(r)["pathComps"]
|
||||||
isListingRequest := r.Form.Has("list")
|
isListingRequest := r.Form.Has("list")
|
||||||
|
|
||||||
buckets := []string{}
|
var buckets []string
|
||||||
if strings.HasPrefix(pathComponents, "/bucket/") {
|
if strings.HasPrefix(pathComponents, "/bucket/") {
|
||||||
// bucket specific metrics, extract the bucket name from the path.
|
// bucket specific metrics, extract the bucket name from the path.
|
||||||
// it's the last part of the path. e.g. /bucket/api/<bucket-name>
|
// it's the last part of the path. e.g. /bucket/api/<bucket-name>
|
||||||
@@ -246,5 +247,5 @@ func (h *metricsV3Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Add authentication
|
// Add authentication
|
||||||
h.authFn(tracedHandler).ServeHTTP(w, r)
|
h.auth(tracedHandler).ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-11
@@ -23,6 +23,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -35,8 +36,8 @@ type collectorPath string
|
|||||||
// converted to snake-case (by replaced '/' and '-' with '_') and prefixed with
|
// converted to snake-case (by replaced '/' and '-' with '_') and prefixed with
|
||||||
// `minio_`.
|
// `minio_`.
|
||||||
func (cp collectorPath) metricPrefix() string {
|
func (cp collectorPath) metricPrefix() string {
|
||||||
s := strings.TrimPrefix(string(cp), "/")
|
s := strings.TrimPrefix(string(cp), SlashSeparator)
|
||||||
s = strings.ReplaceAll(s, "/", "_")
|
s = strings.ReplaceAll(s, SlashSeparator, "_")
|
||||||
s = strings.ReplaceAll(s, "-", "_")
|
s = strings.ReplaceAll(s, "-", "_")
|
||||||
return "minio_" + s
|
return "minio_" + s
|
||||||
}
|
}
|
||||||
@@ -56,8 +57,8 @@ func (cp collectorPath) isDescendantOf(arg string) bool {
|
|||||||
if len(arg) >= len(descendant) {
|
if len(arg) >= len(descendant) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(arg, "/") {
|
if !strings.HasSuffix(arg, SlashSeparator) {
|
||||||
arg += "/"
|
arg += SlashSeparator
|
||||||
}
|
}
|
||||||
return strings.HasPrefix(descendant, arg)
|
return strings.HasPrefix(descendant, arg)
|
||||||
}
|
}
|
||||||
@@ -72,10 +73,11 @@ const (
|
|||||||
GaugeMT
|
GaugeMT
|
||||||
// HistogramMT - represents a histogram metric.
|
// HistogramMT - represents a histogram metric.
|
||||||
HistogramMT
|
HistogramMT
|
||||||
// rangeL - represents a range label.
|
|
||||||
rangeL = "range"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// rangeL - represents a range label.
|
||||||
|
const rangeL = "range"
|
||||||
|
|
||||||
func (mt MetricType) String() string {
|
func (mt MetricType) String() string {
|
||||||
switch mt {
|
switch mt {
|
||||||
case CounterMT:
|
case CounterMT:
|
||||||
@@ -246,29 +248,39 @@ func (m *MetricValues) Set(name MetricName, value float64, labels ...string) {
|
|||||||
// SetHistogram - sets values for the given MetricName using the provided
|
// SetHistogram - sets values for the given MetricName using the provided
|
||||||
// histogram.
|
// histogram.
|
||||||
//
|
//
|
||||||
|
// `filterByLabels` is a map of label names to list of allowed label values to
|
||||||
|
// filter by. Note that this filtering happens before any renaming of labels.
|
||||||
|
//
|
||||||
// `renameLabels` is a map of label names to rename. The keys are the original
|
// `renameLabels` is a map of label names to rename. The keys are the original
|
||||||
// label names and the values are the new label names.
|
// label names and the values are the new label names.
|
||||||
//
|
//
|
||||||
// TODO: bucketFilter doc
|
// `bucketFilter` is a list of bucket values to filter. If this is non-empty,
|
||||||
|
// only metrics for the given buckets are added.
|
||||||
//
|
//
|
||||||
// `extraLabels` are additional labels to add to each metric. They are ordered
|
// `extraLabels` are additional labels to add to each metric. They are ordered
|
||||||
// label name and value pairs.
|
// label name and value pairs.
|
||||||
func (m *MetricValues) SetHistogram(name MetricName, hist *prometheus.HistogramVec,
|
func (m *MetricValues) SetHistogram(name MetricName, hist *prometheus.HistogramVec,
|
||||||
renameLabels map[string]string, bucketFilter []string, extraLabels ...string,
|
filterByLabels map[string]set.StringSet, renameLabels map[string]string, bucketFilter []string,
|
||||||
|
extraLabels ...string,
|
||||||
) {
|
) {
|
||||||
if _, ok := m.descriptors[name]; !ok {
|
if _, ok := m.descriptors[name]; !ok {
|
||||||
panic(fmt.Sprintf("metric has no description: %s", name))
|
panic(fmt.Sprintf("metric has no description: %s", name))
|
||||||
}
|
}
|
||||||
dummyDesc := MetricDescription{}
|
dummyDesc := MetricDescription{}
|
||||||
metricsV2 := getHistogramMetrics(hist, dummyDesc, false)
|
metricsV2 := getHistogramMetrics(hist, dummyDesc, false)
|
||||||
|
mainLoop:
|
||||||
for _, metric := range metricsV2 {
|
for _, metric := range metricsV2 {
|
||||||
|
for label, allowedValues := range filterByLabels {
|
||||||
|
if !allowedValues.Contains(metric.VariableLabels[label]) {
|
||||||
|
continue mainLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If a bucket filter is provided, only add metrics for the given
|
// If a bucket filter is provided, only add metrics for the given
|
||||||
// buckets.
|
// buckets.
|
||||||
if len(bucketFilter) > 0 {
|
if len(bucketFilter) > 0 && !slices.Contains(bucketFilter, metric.VariableLabels["bucket"]) {
|
||||||
if !slices.Contains(bucketFilter, metric.VariableLabels["bucket"]) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
labels := make([]string, 0, len(metric.VariableLabels)*2)
|
labels := make([]string, 0, len(metric.VariableLabels)*2)
|
||||||
for k, v := range metric.VariableLabels {
|
for k, v := range metric.VariableLabels {
|
||||||
|
|||||||
+1
-1
@@ -270,7 +270,7 @@ func newMetricGroups(r *prometheus.Registry) *metricsV3Collection {
|
|||||||
loadClusterUsageObjectMetrics,
|
loadClusterUsageObjectMetrics,
|
||||||
)
|
)
|
||||||
|
|
||||||
clusterUsageBucketsMG := NewBucketMetricsGroup(clusterUsageBucketsCollectorPath,
|
clusterUsageBucketsMG := NewMetricsGroup(clusterUsageBucketsCollectorPath,
|
||||||
[]MetricDescriptor{
|
[]MetricDescriptor{
|
||||||
usageSinceLastUpdateSecondsMD,
|
usageSinceLastUpdateSecondsMD,
|
||||||
usageBucketTotalBytesMD,
|
usageBucketTotalBytesMD,
|
||||||
|
|||||||
@@ -222,6 +222,13 @@ func (d *naughtyDisk) CheckParts(ctx context.Context, volume string, path string
|
|||||||
return d.disk.CheckParts(ctx, volume, path, fi)
|
return d.disk.CheckParts(ctx, volume, path, fi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *naughtyDisk) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) {
|
||||||
|
if err := d.calcError(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return d.disk.DeleteBulk(ctx, volume, paths...)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *naughtyDisk) Delete(ctx context.Context, volume string, path string, deleteOpts DeleteOptions) (err error) {
|
func (d *naughtyDisk) Delete(ctx context.Context, volume string, path string, deleteOpts DeleteOptions) (err error) {
|
||||||
if err := d.calcError(); err != nil {
|
if err := d.calcError(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -751,12 +751,6 @@ func isErrSignatureDoesNotMatch(err error) bool {
|
|||||||
return errors.As(err, &signatureDoesNotMatch)
|
return errors.As(err, &signatureDoesNotMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isErrObjectNameInvalid - Check if error type is ObjectNameInvalid.
|
|
||||||
func isErrObjectNameInvalid(err error) bool {
|
|
||||||
var invalidObject ObjectNameInvalid
|
|
||||||
return errors.As(err, &invalidObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreConditionFailed - Check if copy precondition failed
|
// PreConditionFailed - Check if copy precondition failed
|
||||||
type PreConditionFailed struct{}
|
type PreConditionFailed struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/klauspost/compress/s2"
|
"github.com/klauspost/compress/s2"
|
||||||
@@ -246,6 +247,24 @@ func pathsJoinPrefix(prefix string, elem ...string) (paths []string) {
|
|||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// string concat alternative to s1 + s2 with low overhead.
|
||||||
|
func concat(ss ...string) string {
|
||||||
|
length := len(ss)
|
||||||
|
if length == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// create & allocate the memory in advance.
|
||||||
|
n := 0
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
n += len(ss[i])
|
||||||
|
}
|
||||||
|
b := make([]byte, 0, n)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
b = append(b, ss[i]...)
|
||||||
|
}
|
||||||
|
return unsafe.String(unsafe.SliceData(b), n)
|
||||||
|
}
|
||||||
|
|
||||||
// pathJoin - like path.Join() but retains trailing SlashSeparator of the last element
|
// pathJoin - like path.Join() but retains trailing SlashSeparator of the last element
|
||||||
func pathJoin(elem ...string) string {
|
func pathJoin(elem ...string) string {
|
||||||
sb := bytebufferpool.Get()
|
sb := bytebufferpool.Get()
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path"
|
"path"
|
||||||
@@ -47,6 +49,43 @@ func pathJoinOld(elem ...string) string {
|
|||||||
return path.Join(elem...) + trailingSlash
|
return path.Join(elem...) + trailingSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func concatNaive(ss ...string) string {
|
||||||
|
rs := ss[0]
|
||||||
|
for i := 1; i < len(ss); i++ {
|
||||||
|
rs += ss[i]
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmark(b *testing.B, data []string) {
|
||||||
|
b.Run("concat naive", func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
concatNaive(data...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("concat fast", func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
concat(data...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkConcatImplementation(b *testing.B) {
|
||||||
|
data := make([]string, 2)
|
||||||
|
rng := rand.New(rand.NewSource(0))
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
var tmp [16]byte
|
||||||
|
rng.Read(tmp[:])
|
||||||
|
data[i] = hex.EncodeToString(tmp[:])
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
benchmark(b, data)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkPathJoinOld(b *testing.B) {
|
func BenchmarkPathJoinOld(b *testing.B) {
|
||||||
b.Run("PathJoin", func(b *testing.B) {
|
b.Run("PathJoin", func(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|||||||
@@ -248,11 +248,20 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the part number is correct.
|
// Check if the part number is correct.
|
||||||
if opts.PartNumber > 1 && opts.PartNumber > len(objInfo.Parts) {
|
if opts.PartNumber > 1 {
|
||||||
|
partFound := false
|
||||||
|
for _, pi := range objInfo.Parts {
|
||||||
|
if pi.Number == opts.PartNumber {
|
||||||
|
partFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !partFound {
|
||||||
// According to S3 we don't need to set any object information here.
|
// According to S3 we don't need to set any object information here.
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartNumber), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartNumber), r.URL)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If-None-Match : Return the object only if its entity tag (ETag) is different from the
|
// If-None-Match : Return the object only if its entity tag (ETag) is different from the
|
||||||
// one specified otherwise, return a 304 (not modified).
|
// one specified otherwise, return a 304 (not modified).
|
||||||
|
|||||||
@@ -501,8 +501,8 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
|||||||
reader *GetObjectReader
|
reader *GetObjectReader
|
||||||
perr error
|
perr error
|
||||||
)
|
)
|
||||||
// avoid proxying if version is a delete marker
|
|
||||||
if !isErrObjectNameInvalid(err) && !isErrMethodNotAllowed(err) && !(gr != nil && gr.ObjInfo.DeleteMarker) {
|
if (isErrObjectNotFound(err) || isErrVersionNotFound(err) || isErrReadQuorum(err)) && !(gr != nil && gr.ObjInfo.DeleteMarker) {
|
||||||
proxytgts := getProxyTargets(ctx, bucket, object, opts)
|
proxytgts := getProxyTargets(ctx, bucket, object, opts)
|
||||||
if !proxytgts.Empty() {
|
if !proxytgts.Empty() {
|
||||||
globalReplicationStats.incProxy(bucket, getObjectAPI, false)
|
globalReplicationStats.incProxy(bucket, getObjectAPI, false)
|
||||||
@@ -1028,7 +1028,7 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
|||||||
|
|
||||||
objInfo, err := getObjectInfo(ctx, bucket, object, opts)
|
objInfo, err := getObjectInfo(ctx, bucket, object, opts)
|
||||||
var proxy proxyResult
|
var proxy proxyResult
|
||||||
if err != nil && !objInfo.DeleteMarker && !isErrMethodNotAllowed(err) && !isErrObjectNameInvalid(err) {
|
if err != nil && !objInfo.DeleteMarker && (isErrObjectNotFound(err) || isErrVersionNotFound(err) || isErrReadQuorum(err)) {
|
||||||
// proxy HEAD to replication target if active-active replication configured on bucket
|
// proxy HEAD to replication target if active-active replication configured on bucket
|
||||||
proxytgts := getProxyTargets(ctx, bucket, object, opts)
|
proxytgts := getProxyTargets(ctx, bucket, object, opts)
|
||||||
if !proxytgts.Empty() {
|
if !proxytgts.Empty() {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"encoding/hex"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -33,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
levent "github.com/minio/minio/internal/config/lambda/event"
|
levent "github.com/minio/minio/internal/config/lambda/event"
|
||||||
|
"github.com/minio/minio/internal/hash/sha256"
|
||||||
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"
|
||||||
)
|
)
|
||||||
@@ -77,16 +79,13 @@ func getLambdaEventData(bucket, object string, cred auth.Credentials, r *http.Re
|
|||||||
return levent.Event{}, err
|
return levent.Event{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := authenticateNode(cred.AccessKey, cred.SecretKey, u.RawQuery)
|
ckSum := sha256.Sum256([]byte(cred.AccessKey + u.RawQuery))
|
||||||
if err != nil {
|
|
||||||
return levent.Event{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
eventData := levent.Event{
|
eventData := levent.Event{
|
||||||
GetObjectContext: &levent.GetObjectContext{
|
GetObjectContext: &levent.GetObjectContext{
|
||||||
InputS3URL: u.String(),
|
InputS3URL: u.String(),
|
||||||
OutputRoute: shortuuid.New(),
|
OutputRoute: shortuuid.New(),
|
||||||
OutputToken: token,
|
OutputToken: hex.EncodeToString(ckSum[:]),
|
||||||
},
|
},
|
||||||
UserRequest: levent.UserRequest{
|
UserRequest: levent.UserRequest{
|
||||||
URL: r.URL.String(),
|
URL: r.URL.String(),
|
||||||
|
|||||||
@@ -664,7 +664,7 @@ var errUnsupportedSignal = fmt.Errorf("unsupported signal")
|
|||||||
|
|
||||||
func waitingDrivesNode() map[string]madmin.DiskMetrics {
|
func waitingDrivesNode() map[string]madmin.DiskMetrics {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
errs := make([]error, len(localDrives))
|
errs := make([]error, len(localDrives))
|
||||||
|
|||||||
+11
-9
@@ -34,7 +34,7 @@ const (
|
|||||||
|
|
||||||
func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (res madmin.HealResultItem, err error) {
|
func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (res madmin.HealResultItem, err error) {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
// Initialize sync waitgroup.
|
// Initialize sync waitgroup.
|
||||||
@@ -158,7 +158,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
|||||||
|
|
||||||
func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
|
func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
quorum := (len(localDrives) / 2)
|
quorum := (len(localDrives) / 2)
|
||||||
@@ -204,15 +204,17 @@ func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []Bucket
|
|||||||
return buckets, nil
|
return buckets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneDrives(drives []StorageAPI) []StorageAPI {
|
func cloneDrives(drives map[string]StorageAPI) []StorageAPI {
|
||||||
newDrives := make([]StorageAPI, len(drives))
|
copyDrives := make([]StorageAPI, 0, len(drives))
|
||||||
copy(newDrives, drives)
|
for _, drive := range drives {
|
||||||
return newDrives
|
copyDrives = append(copyDrives, drive)
|
||||||
|
}
|
||||||
|
return copyDrives
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
||||||
@@ -261,7 +263,7 @@ func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions)
|
|||||||
|
|
||||||
func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
|
func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
||||||
@@ -299,7 +301,7 @@ func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOpti
|
|||||||
|
|
||||||
func makeBucketLocal(ctx context.Context, bucket string, opts MakeBucketOptions) error {
|
func makeBucketLocal(ctx context.Context, bucket string, opts MakeBucketOptions) error {
|
||||||
globalLocalDrivesMu.RLock()
|
globalLocalDrivesMu.RLock()
|
||||||
localDrives := cloneDrives(globalLocalDrives)
|
localDrives := cloneDrives(globalLocalDrivesMap)
|
||||||
globalLocalDrivesMu.RUnlock()
|
globalLocalDrivesMu.RUnlock()
|
||||||
|
|
||||||
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
||||||
|
|||||||
@@ -166,13 +166,13 @@ func connectLoadInitFormats(verboseLogging bool, firstDisk bool, storageDisks []
|
|||||||
if err != nil && !errors.Is(err, errXLBackend) && !errors.Is(err, errUnformattedDisk) {
|
if err != nil && !errors.Is(err, errXLBackend) && !errors.Is(err, errUnformattedDisk) {
|
||||||
if errors.Is(err, errDiskNotFound) && verboseLogging {
|
if errors.Is(err, errDiskNotFound) && verboseLogging {
|
||||||
if globalEndpoints.NEndpoints() > 1 {
|
if globalEndpoints.NEndpoints() > 1 {
|
||||||
logger.Error("Unable to connect to %s: %v", endpoints[i], isServerResolvable(endpoints[i], time.Second))
|
logger.Info("Unable to connect to %s: %v, will be retried", endpoints[i], isServerResolvable(endpoints[i], time.Second))
|
||||||
} else {
|
} else {
|
||||||
logger.Fatal(err, "Unable to connect to %s: %v", endpoints[i], isServerResolvable(endpoints[i], time.Second))
|
logger.Fatal(err, "Unable to connect to %s: %v", endpoints[i], isServerResolvable(endpoints[i], time.Second))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if globalEndpoints.NEndpoints() > 1 {
|
if globalEndpoints.NEndpoints() > 1 {
|
||||||
logger.Error("Unable to use the drive %s: %v", endpoints[i], err)
|
logger.Info("Unable to use the drive %s: %v, will be retried", endpoints[i], err)
|
||||||
} else {
|
} else {
|
||||||
logger.Fatal(errInvalidArgument, "Unable to use the drive %s: %v", endpoints[i], err)
|
logger.Fatal(errInvalidArgument, "Unable to use the drive %s: %v", endpoints[i], err)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -26,20 +26,28 @@ import (
|
|||||||
|
|
||||||
// Composed function registering routers for only distributed Erasure setup.
|
// Composed function registering routers for only distributed Erasure setup.
|
||||||
func registerDistErasureRouters(router *mux.Router, endpointServerPools EndpointServerPools) {
|
func registerDistErasureRouters(router *mux.Router, endpointServerPools EndpointServerPools) {
|
||||||
|
var (
|
||||||
|
lockGrid = globalLockGrid.Load()
|
||||||
|
commonGrid = globalGrid.Load()
|
||||||
|
)
|
||||||
|
|
||||||
// Register storage REST router only if its a distributed setup.
|
// Register storage REST router only if its a distributed setup.
|
||||||
registerStorageRESTHandlers(router, endpointServerPools, globalGrid.Load())
|
registerStorageRESTHandlers(router, endpointServerPools, commonGrid)
|
||||||
|
|
||||||
// Register peer REST router only if its a distributed setup.
|
// Register peer REST router only if its a distributed setup.
|
||||||
registerPeerRESTHandlers(router, globalGrid.Load())
|
registerPeerRESTHandlers(router, commonGrid)
|
||||||
|
|
||||||
// Register bootstrap REST router for distributed setups.
|
// Register bootstrap REST router for distributed setups.
|
||||||
registerBootstrapRESTHandlers(globalGrid.Load())
|
registerBootstrapRESTHandlers(commonGrid)
|
||||||
|
|
||||||
// Register distributed namespace lock routers.
|
// Register distributed namespace lock routers.
|
||||||
registerLockRESTHandlers()
|
registerLockRESTHandlers(lockGrid)
|
||||||
|
|
||||||
|
// Add lock grid to router
|
||||||
|
router.Handle(grid.RouteLockPath, adminMiddleware(lockGrid.Handler(storageServerRequestValidate), noGZFlag, noObjLayerFlag))
|
||||||
|
|
||||||
// Add grid to router
|
// Add grid to router
|
||||||
router.Handle(grid.RoutePath, adminMiddleware(globalGrid.Load().Handler(storageServerRequestValidate), noGZFlag, noObjLayerFlag))
|
router.Handle(grid.RoutePath, adminMiddleware(commonGrid.Handler(storageServerRequestValidate), noGZFlag, noObjLayerFlag))
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of some generic middlewares which are applied for all incoming requests.
|
// List of some generic middlewares which are applied for all incoming requests.
|
||||||
|
|||||||
+22
-19
@@ -841,13 +841,14 @@ func serverMain(ctx *cli.Context) {
|
|||||||
|
|
||||||
// Verify kernel release and version.
|
// Verify kernel release and version.
|
||||||
if oldLinux() {
|
if oldLinux() {
|
||||||
warnings = append(warnings, color.YellowBold("- Detected Linux kernel version older than 4.0.0 release, there are some known potential performance problems with this kernel version. MinIO recommends a minimum of 4.x.x linux kernel version for best performance"))
|
warnings = append(warnings, color.YellowBold("Detected Linux kernel version older than 4.0 release, there are some known potential performance problems with this kernel version. MinIO recommends a minimum of 4.x linux kernel version for best performance"))
|
||||||
}
|
}
|
||||||
|
|
||||||
maxProcs := runtime.GOMAXPROCS(0)
|
maxProcs := runtime.GOMAXPROCS(0)
|
||||||
cpuProcs := runtime.NumCPU()
|
cpuProcs := runtime.NumCPU()
|
||||||
if maxProcs < cpuProcs {
|
if maxProcs < cpuProcs {
|
||||||
warnings = append(warnings, color.YellowBold("- Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance", maxProcs, cpuProcs))
|
warnings = append(warnings, color.YellowBold("Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance",
|
||||||
|
maxProcs, cpuProcs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize grid
|
// Initialize grid
|
||||||
@@ -855,6 +856,11 @@ func serverMain(ctx *cli.Context) {
|
|||||||
logger.FatalIf(initGlobalGrid(GlobalContext, globalEndpoints), "Unable to configure server grid RPC services")
|
logger.FatalIf(initGlobalGrid(GlobalContext, globalEndpoints), "Unable to configure server grid RPC services")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Initialize lock grid
|
||||||
|
bootstrapTrace("initLockGrid", func() {
|
||||||
|
logger.FatalIf(initGlobalLockGrid(GlobalContext, globalEndpoints), "Unable to configure server lock grid RPC services")
|
||||||
|
})
|
||||||
|
|
||||||
// Configure server.
|
// Configure server.
|
||||||
bootstrapTrace("configureServer", func() {
|
bootstrapTrace("configureServer", func() {
|
||||||
handler, err := configureServerHandler(globalEndpoints)
|
handler, err := configureServerHandler(globalEndpoints)
|
||||||
@@ -862,7 +868,8 @@ func serverMain(ctx *cli.Context) {
|
|||||||
logger.Fatal(config.ErrUnexpectedError(err), "Unable to configure one of server's RPC services")
|
logger.Fatal(config.ErrUnexpectedError(err), "Unable to configure one of server's RPC services")
|
||||||
}
|
}
|
||||||
// Allow grid to start after registering all services.
|
// Allow grid to start after registering all services.
|
||||||
xioutil.SafeClose(globalGridStart)
|
close(globalGridStart)
|
||||||
|
close(globalLockGridStart)
|
||||||
|
|
||||||
httpServer := xhttp.NewServer(getServerListenAddrs()).
|
httpServer := xhttp.NewServer(getServerListenAddrs()).
|
||||||
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
||||||
@@ -921,16 +928,18 @@ func serverMain(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bootstrapTrace("waitForQuorum", func() {
|
bootstrapTrace("waitForQuorum", func() {
|
||||||
result := newObject.Health(context.Background(), HealthOptions{})
|
result := newObject.Health(context.Background(), HealthOptions{NoLogging: true})
|
||||||
for !result.HealthyRead {
|
for !result.HealthyRead {
|
||||||
if debugNoExit {
|
if debugNoExit {
|
||||||
logger.Info("Not waiting for quorum since we are debugging.. possible cause unhealthy sets (%s)", result)
|
logger.Info("Not waiting for quorum since we are debugging.. possible cause unhealthy sets")
|
||||||
|
logger.Info(result.String())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
d := time.Duration(r.Float64() * float64(time.Second))
|
d := time.Duration(r.Float64() * float64(time.Second))
|
||||||
logger.Info("Waiting for quorum READ healthcheck to succeed.. possible cause unhealthy sets (%s), retrying in %s", result, d)
|
logger.Info("Waiting for quorum READ healthcheck to succeed retrying in %s.. possible cause unhealthy sets", d)
|
||||||
|
logger.Info(result.String())
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
result = newObject.Health(context.Background(), HealthOptions{})
|
result = newObject.Health(context.Background(), HealthOptions{NoLogging: true})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -953,11 +962,11 @@ func serverMain(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !globalServerCtxt.StrictS3Compat {
|
if !globalServerCtxt.StrictS3Compat {
|
||||||
warnings = append(warnings, color.YellowBold("- Strict AWS S3 compatible incoming PUT, POST content payload validation is turned off, caution is advised do not use in production"))
|
warnings = append(warnings, color.YellowBold("Strict AWS S3 compatible incoming PUT, POST content payload validation is turned off, caution is advised do not use in production"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if globalActiveCred.Equal(auth.DefaultCredentials) {
|
if globalActiveCred.Equal(auth.DefaultCredentials) {
|
||||||
msg := fmt.Sprintf("- Detected default credentials '%s', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables",
|
msg := fmt.Sprintf("Detected default credentials '%s', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables",
|
||||||
globalActiveCred)
|
globalActiveCred)
|
||||||
warnings = append(warnings, color.YellowBold(msg))
|
warnings = append(warnings, color.YellowBold(msg))
|
||||||
}
|
}
|
||||||
@@ -1103,18 +1112,12 @@ func serverMain(ctx *cli.Context) {
|
|||||||
printStartupMessage(getAPIEndpoints(), err)
|
printStartupMessage(getAPIEndpoints(), err)
|
||||||
|
|
||||||
// Print a warning at the end of the startup banner so it is more noticeable
|
// Print a warning at the end of the startup banner so it is more noticeable
|
||||||
if newObject.BackendInfo().StandardSCParity == 0 {
|
if newObject.BackendInfo().StandardSCParity == 0 && !globalIsErasureSD {
|
||||||
warnings = append(warnings, color.YellowBold("- The standard parity is set to 0. This can lead to data loss."))
|
warnings = append(warnings, color.YellowBold("The standard parity is set to 0. This can lead to data loss."))
|
||||||
}
|
}
|
||||||
objAPI := newObjectLayerFn()
|
|
||||||
if objAPI != nil {
|
|
||||||
printStorageInfo(objAPI.StorageInfo(GlobalContext, true))
|
|
||||||
}
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
logger.Info(color.Yellow("STARTUP WARNINGS:"))
|
|
||||||
for _, warn := range warnings {
|
for _, warn := range warnings {
|
||||||
logger.Info(warn)
|
logger.Warning(warn)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
+21
-41
@@ -23,7 +23,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
|
||||||
"github.com/minio/minio/internal/color"
|
"github.com/minio/minio/internal/color"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
xnet "github.com/minio/pkg/v3/net"
|
xnet "github.com/minio/pkg/v3/net"
|
||||||
@@ -37,7 +36,11 @@ func getFormatStr(strLen int, padding int) string {
|
|||||||
|
|
||||||
// Prints the formatted startup message.
|
// Prints the formatted startup message.
|
||||||
func printStartupMessage(apiEndpoints []string, err error) {
|
func printStartupMessage(apiEndpoints []string, err error) {
|
||||||
logger.Info(color.Bold(MinioBannerName))
|
banner := strings.Repeat("-", len(MinioBannerName))
|
||||||
|
if globalIsDistErasure {
|
||||||
|
logger.Startup(color.Bold(banner))
|
||||||
|
}
|
||||||
|
logger.Startup(color.Bold(MinioBannerName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalConsoleSys != nil {
|
if globalConsoleSys != nil {
|
||||||
globalConsoleSys.Send(GlobalContext, fmt.Sprintf("Server startup failed with '%v', some features may be missing", err))
|
globalConsoleSys.Send(GlobalContext, fmt.Sprintf("Server startup failed with '%v', some features may be missing", err))
|
||||||
@@ -47,7 +50,7 @@ func printStartupMessage(apiEndpoints []string, err error) {
|
|||||||
if !globalSubnetConfig.Registered() {
|
if !globalSubnetConfig.Registered() {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
startupBanner(&builder)
|
startupBanner(&builder)
|
||||||
logger.Info(builder.String())
|
logger.Startup(builder.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
strippedAPIEndpoints := stripStandardPorts(apiEndpoints, globalMinioHost)
|
strippedAPIEndpoints := stripStandardPorts(apiEndpoints, globalMinioHost)
|
||||||
@@ -61,6 +64,9 @@ func printStartupMessage(apiEndpoints []string, err error) {
|
|||||||
|
|
||||||
// Prints documentation message.
|
// Prints documentation message.
|
||||||
printObjectAPIMsg()
|
printObjectAPIMsg()
|
||||||
|
if globalIsDistErasure {
|
||||||
|
logger.Startup(color.Bold(banner))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if input is IPv6
|
// Returns true if input is IPv6
|
||||||
@@ -113,21 +119,21 @@ func printServerCommonMsg(apiEndpoints []string) {
|
|||||||
|
|
||||||
apiEndpointStr := strings.TrimSpace(strings.Join(apiEndpoints, " "))
|
apiEndpointStr := strings.TrimSpace(strings.Join(apiEndpoints, " "))
|
||||||
// Colorize the message and print.
|
// Colorize the message and print.
|
||||||
logger.Info(color.Blue("API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
|
logger.Startup(color.Blue("API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
|
||||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON && globalAPIConfig.permitRootAccess()) {
|
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON && globalAPIConfig.permitRootAccess()) {
|
||||||
logger.Info(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
logger.Startup(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||||
logger.Info(color.Blue(" RootPass: ") + color.Bold("%s \n", cred.SecretKey))
|
logger.Startup(color.Blue(" RootPass: ") + color.Bold("%s \n", cred.SecretKey))
|
||||||
if region != "" {
|
if region != "" {
|
||||||
logger.Info(color.Blue(" Region: ") + color.Bold("%s", fmt.Sprintf(getFormatStr(len(region), 2), region)))
|
logger.Startup(color.Blue(" Region: ") + color.Bold("%s", fmt.Sprintf(getFormatStr(len(region), 2), region)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalBrowserEnabled {
|
if globalBrowserEnabled {
|
||||||
consoleEndpointStr := strings.Join(stripStandardPorts(getConsoleEndpoints(), globalMinioConsoleHost), " ")
|
consoleEndpointStr := strings.Join(stripStandardPorts(getConsoleEndpoints(), globalMinioConsoleHost), " ")
|
||||||
logger.Info(color.Blue("WebUI: ") + color.Bold(fmt.Sprintf("%s ", consoleEndpointStr)))
|
logger.Startup(color.Blue("WebUI: ") + color.Bold(fmt.Sprintf("%s ", consoleEndpointStr)))
|
||||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON && globalAPIConfig.permitRootAccess()) {
|
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON && globalAPIConfig.permitRootAccess()) {
|
||||||
logger.Info(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
logger.Startup(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||||
logger.Info(color.Blue(" RootPass: ") + color.Bold("%s ", cred.SecretKey))
|
logger.Startup(color.Blue(" RootPass: ") + color.Bold("%s ", cred.SecretKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +143,7 @@ func printServerCommonMsg(apiEndpoints []string) {
|
|||||||
|
|
||||||
// Prints startup message for Object API access, prints link to our SDK documentation.
|
// Prints startup message for Object API access, prints link to our SDK documentation.
|
||||||
func printObjectAPIMsg() {
|
func printObjectAPIMsg() {
|
||||||
logger.Info(color.Blue("\nDocs: ") + "https://min.io/docs/minio/linux/index.html")
|
logger.Startup(color.Blue("\nDocs: ") + "https://min.io/docs/minio/linux/index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printLambdaTargets() {
|
func printLambdaTargets() {
|
||||||
@@ -149,7 +155,7 @@ func printLambdaTargets() {
|
|||||||
for _, arn := range globalLambdaTargetList.List(globalSite.Region()) {
|
for _, arn := range globalLambdaTargetList.List(globalSite.Region()) {
|
||||||
arnMsg += color.Bold(fmt.Sprintf("%s ", arn))
|
arnMsg += color.Bold(fmt.Sprintf("%s ", arn))
|
||||||
}
|
}
|
||||||
logger.Info(arnMsg + "\n")
|
logger.Startup(arnMsg + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints bucket notification configurations.
|
// Prints bucket notification configurations.
|
||||||
@@ -168,7 +174,7 @@ func printEventNotifiers() {
|
|||||||
arnMsg += color.Bold(fmt.Sprintf("%s ", arn))
|
arnMsg += color.Bold(fmt.Sprintf("%s ", arn))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(arnMsg + "\n")
|
logger.Startup(arnMsg + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints startup message for command line access. Prints link to our documentation
|
// Prints startup message for command line access. Prints link to our documentation
|
||||||
@@ -181,35 +187,9 @@ func printCLIAccessMsg(endPoint string, alias string) {
|
|||||||
|
|
||||||
// Configure 'mc', following block prints platform specific information for minio client.
|
// Configure 'mc', following block prints platform specific information for minio client.
|
||||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && globalAPIConfig.permitRootAccess()) {
|
if color.IsTerminal() && (!globalServerCtxt.Anonymous && globalAPIConfig.permitRootAccess()) {
|
||||||
logger.Info(color.Blue("\nCLI: ") + mcQuickStartGuide)
|
logger.Startup(color.Blue("\nCLI: ") + mcQuickStartGuide)
|
||||||
mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias,
|
mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias,
|
||||||
endPoint, cred.AccessKey, cred.SecretKey)
|
endPoint, cred.AccessKey, cred.SecretKey)
|
||||||
logger.Info(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
|
logger.Startup(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get formatted disk/storage info message.
|
|
||||||
func getStorageInfoMsg(storageInfo StorageInfo) string {
|
|
||||||
var msg string
|
|
||||||
var mcMessage string
|
|
||||||
onlineDisks, offlineDisks := getOnlineOfflineDisksStats(storageInfo.Disks)
|
|
||||||
if storageInfo.Backend.Type == madmin.Erasure {
|
|
||||||
if offlineDisks.Sum() > 0 {
|
|
||||||
mcMessage = "Use `mc admin info` to look for latest server/drive info\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
diskInfo := fmt.Sprintf(" %d Online, %d Offline. ", onlineDisks.Sum(), offlineDisks.Sum())
|
|
||||||
msg += color.Blue("Status:") + fmt.Sprintf(getFormatStr(len(diskInfo), 8), diskInfo)
|
|
||||||
if len(mcMessage) > 0 {
|
|
||||||
msg = fmt.Sprintf("%s %s", mcMessage, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints startup message of storage capacity and erasure information.
|
|
||||||
func printStorageInfo(storageInfo StorageInfo) {
|
|
||||||
if msg := getStorageInfoMsg(storageInfo); msg != "" {
|
|
||||||
logger.Info(msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,32 +21,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests if we generate storage info.
|
|
||||||
func TestStorageInfoMsg(t *testing.T) {
|
|
||||||
infoStorage := StorageInfo{}
|
|
||||||
infoStorage.Disks = []madmin.Disk{
|
|
||||||
{Endpoint: "http://127.0.0.1:9000/data/1/", State: madmin.DriveStateOk},
|
|
||||||
{Endpoint: "http://127.0.0.1:9000/data/2/", State: madmin.DriveStateOk},
|
|
||||||
{Endpoint: "http://127.0.0.1:9000/data/3/", State: madmin.DriveStateOk},
|
|
||||||
{Endpoint: "http://127.0.0.1:9000/data/4/", State: madmin.DriveStateOk},
|
|
||||||
{Endpoint: "http://127.0.0.1:9001/data/1/", State: madmin.DriveStateOk},
|
|
||||||
{Endpoint: "http://127.0.0.1:9001/data/2/", State: madmin.DriveStateOk},
|
|
||||||
{Endpoint: "http://127.0.0.1:9001/data/3/", State: madmin.DriveStateOk},
|
|
||||||
{Endpoint: "http://127.0.0.1:9001/data/4/", State: madmin.DriveStateOffline},
|
|
||||||
}
|
|
||||||
infoStorage.Backend.Type = madmin.Erasure
|
|
||||||
|
|
||||||
if msg := getStorageInfoMsg(infoStorage); !strings.Contains(msg, "7 Online, 1 Offline") {
|
|
||||||
t.Fatal("Unexpected storage info message, found:", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests stripping standard ports from apiEndpoints.
|
// Tests stripping standard ports from apiEndpoints.
|
||||||
func TestStripStandardPorts(t *testing.T) {
|
func TestStripStandardPorts(t *testing.T) {
|
||||||
apiEndpoints := []string{"http://127.0.0.1:9000", "http://127.0.0.2:80", "https://127.0.0.3:443"}
|
apiEndpoints := []string{"http://127.0.0.1:9000", "http://127.0.0.2:80", "https://127.0.0.3:443"}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/pkg/v3/policy"
|
"github.com/minio/pkg/v3/policy"
|
||||||
@@ -122,6 +123,9 @@ func runAllTests(suite *TestSuiteCommon, c *check) {
|
|||||||
suite.TestObjectMultipartListError(c)
|
suite.TestObjectMultipartListError(c)
|
||||||
suite.TestObjectValidMD5(c)
|
suite.TestObjectValidMD5(c)
|
||||||
suite.TestObjectMultipart(c)
|
suite.TestObjectMultipart(c)
|
||||||
|
suite.TestMetricsV3Handler(c)
|
||||||
|
suite.TestBucketSQSNotificationWebHook(c)
|
||||||
|
suite.TestBucketSQSNotificationAMQP(c)
|
||||||
suite.TearDownSuite(c)
|
suite.TearDownSuite(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +193,36 @@ func (s *TestSuiteCommon) TearDownSuite(c *check) {
|
|||||||
s.testServer.Stop()
|
s.testServer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPrometheusJWTExpiry = 100 * 365 * 24 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *TestSuiteCommon) TestMetricsV3Handler(c *check) {
|
||||||
|
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.StandardClaims{
|
||||||
|
ExpiresAt: time.Now().UTC().Add(defaultPrometheusJWTExpiry).Unix(),
|
||||||
|
Subject: s.accessKey,
|
||||||
|
Issuer: "prometheus",
|
||||||
|
})
|
||||||
|
|
||||||
|
token, err := jwt.SignedString([]byte(s.secretKey))
|
||||||
|
c.Assert(err, nil)
|
||||||
|
|
||||||
|
for _, cpath := range globalMetricsV3CollectorPaths {
|
||||||
|
request, err := newTestSignedRequest(http.MethodGet, s.endPoint+minioReservedBucketPath+metricsV3Path+string(cpath),
|
||||||
|
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
|
||||||
|
request.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
|
||||||
|
// execute the request.
|
||||||
|
response, err := s.client.Do(request)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
|
||||||
|
// assert the http response status code.
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TestSuiteCommon) TestBucketSQSNotificationWebHook(c *check) {
|
func (s *TestSuiteCommon) TestBucketSQSNotificationWebHook(c *check) {
|
||||||
// Sample bucket notification.
|
// Sample bucket notification.
|
||||||
bucketNotificationBuf := `<NotificationConfiguration><QueueConfiguration><Event>s3:ObjectCreated:Put</Event><Filter><S3Key><FilterRule><Name>prefix</Name><Value>images/</Value></FilterRule></S3Key></Filter><Id>1</Id><Queue>arn:minio:sqs:us-east-1:444455556666:webhook</Queue></QueueConfiguration></NotificationConfiguration>`
|
bucketNotificationBuf := `<NotificationConfiguration><QueueConfiguration><Event>s3:ObjectCreated:Put</Event><Filter><S3Key><FilterRule><Name>prefix</Name><Value>images/</Value></FilterRule></S3Key></Filter><Id>1</Id><Queue>arn:minio:sqs:us-east-1:444455556666:webhook</Queue></QueueConfiguration></NotificationConfiguration>`
|
||||||
|
|||||||
+15
-14
@@ -1129,7 +1129,7 @@ func (c *SiteReplicationSys) PeerBucketConfigureReplHandler(ctx context.Context,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, newReplicationConfig, true)
|
sameTarget, apiErr := validateReplicationDestination(ctx, bucket, newReplicationConfig, &validateReplicationDestinationOptions{CheckRemoteBucket: true})
|
||||||
if apiErr != noError {
|
if apiErr != noError {
|
||||||
return fmt.Errorf("bucket replication config validation error: %#v", apiErr)
|
return fmt.Errorf("bucket replication config validation error: %#v", apiErr)
|
||||||
}
|
}
|
||||||
@@ -4453,6 +4453,7 @@ func (c *SiteReplicationSys) healBuckets(ctx context.Context, objAPI ObjectLayer
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ilmExpiryCfgHealed := false
|
ilmExpiryCfgHealed := false
|
||||||
|
opts := validateReplicationDestinationOptions{CheckReady: true}
|
||||||
for _, bi := range buckets {
|
for _, bi := range buckets {
|
||||||
bucket := bi.Name
|
bucket := bi.Name
|
||||||
info, err := c.siteReplicationStatus(ctx, objAPI, madmin.SRStatusOptions{
|
info, err := c.siteReplicationStatus(ctx, objAPI, madmin.SRStatusOptions{
|
||||||
@@ -4472,7 +4473,7 @@ func (c *SiteReplicationSys) healBuckets(ctx context.Context, objAPI ObjectLayer
|
|||||||
c.healVersioningMetadata(ctx, objAPI, bucket, info)
|
c.healVersioningMetadata(ctx, objAPI, bucket, info)
|
||||||
c.healOLockConfigMetadata(ctx, objAPI, bucket, info)
|
c.healOLockConfigMetadata(ctx, objAPI, bucket, info)
|
||||||
c.healSSEMetadata(ctx, objAPI, bucket, info)
|
c.healSSEMetadata(ctx, objAPI, bucket, info)
|
||||||
c.healBucketReplicationConfig(ctx, objAPI, bucket, info)
|
c.healBucketReplicationConfig(ctx, objAPI, bucket, info, &opts)
|
||||||
c.healBucketPolicies(ctx, objAPI, bucket, info)
|
c.healBucketPolicies(ctx, objAPI, bucket, info)
|
||||||
c.healTagMetadata(ctx, objAPI, bucket, info)
|
c.healTagMetadata(ctx, objAPI, bucket, info)
|
||||||
c.healBucketQuotaConfig(ctx, objAPI, bucket, info)
|
c.healBucketQuotaConfig(ctx, objAPI, bucket, info)
|
||||||
@@ -5172,7 +5173,7 @@ func (c *SiteReplicationSys) healBucket(ctx context.Context, objAPI ObjectLayer,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SiteReplicationSys) healBucketReplicationConfig(ctx context.Context, objAPI ObjectLayer, bucket string, info srStatusInfo) error {
|
func (c *SiteReplicationSys) healBucketReplicationConfig(ctx context.Context, objAPI ObjectLayer, bucket string, info srStatusInfo, opts *validateReplicationDestinationOptions) error {
|
||||||
bs := info.BucketStats[bucket]
|
bs := info.BucketStats[bucket]
|
||||||
|
|
||||||
c.RLock()
|
c.RLock()
|
||||||
@@ -5226,7 +5227,7 @@ func (c *SiteReplicationSys) healBucketReplicationConfig(ctx context.Context, ob
|
|||||||
|
|
||||||
if rcfg != nil && !replMismatch {
|
if rcfg != nil && !replMismatch {
|
||||||
// validate remote targets on current cluster for this bucket
|
// validate remote targets on current cluster for this bucket
|
||||||
_, apiErr := validateReplicationDestination(ctx, bucket, rcfg, false)
|
_, apiErr := validateReplicationDestination(ctx, bucket, rcfg, opts)
|
||||||
if apiErr != noError {
|
if apiErr != noError {
|
||||||
replMismatch = true
|
replMismatch = true
|
||||||
}
|
}
|
||||||
@@ -5390,7 +5391,7 @@ func (c *SiteReplicationSys) healUserPolicies(ctx context.Context, objAPI Object
|
|||||||
UpdatedAt: lastUpdate,
|
UpdatedAt: lastUpdate,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogOnceIf(ctx, fmt.Errorf("Unable to heal IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err),
|
replLogOnceIf(ctx, fmt.Errorf("Unable to heal IAM user policy mapping from peer site %s -> site %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-policy-%s", user))
|
fmt.Sprintf("heal-user-policy-%s", user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5455,7 +5456,7 @@ func (c *SiteReplicationSys) healGroupPolicies(ctx context.Context, objAPI Objec
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal IAM group policy mapping for from peer site %s -> site %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-group-policy-%s", group))
|
fmt.Sprintf("heal-group-policy-%s", group))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5518,7 +5519,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
|
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal service account from peer site %s -> %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-%s", user))
|
fmt.Sprintf("heal-user-%s", user))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -5526,7 +5527,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
_, policy, err := globalIAMSys.GetServiceAccount(ctx, creds.AccessKey)
|
_, policy, err := globalIAMSys.GetServiceAccount(ctx, creds.AccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal service account from peer site %s -> %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-%s", user))
|
fmt.Sprintf("heal-user-%s", user))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -5536,7 +5537,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
policyJSON, err = json.Marshal(policy)
|
policyJSON, err = json.Marshal(policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal service account from peer site %s -> %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-%s", user))
|
fmt.Sprintf("heal-user-%s", user))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -5561,7 +5562,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
UpdatedAt: lastUpdate,
|
UpdatedAt: lastUpdate,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal service account from peer site %s -> %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-%s", user))
|
fmt.Sprintf("heal-user-%s", user))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -5576,7 +5577,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
// be applied.
|
// be applied.
|
||||||
if !errors.Is(err, errNoSuchUser) {
|
if !errors.Is(err, errNoSuchUser) {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal temporary credentials from peer site %s -> %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-%s", user))
|
fmt.Sprintf("heal-user-%s", user))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -5596,7 +5597,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
UpdatedAt: lastUpdate,
|
UpdatedAt: lastUpdate,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal temporary credentials from peer site %s -> %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-%s", user))
|
fmt.Sprintf("heal-user-%s", user))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -5614,7 +5615,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
|||||||
UpdatedAt: lastUpdate,
|
UpdatedAt: lastUpdate,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal user from peer site %s -> %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-user-%s", user))
|
fmt.Sprintf("heal-user-%s", user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5680,7 +5681,7 @@ func (c *SiteReplicationSys) healGroups(ctx context.Context, objAPI ObjectLayer,
|
|||||||
UpdatedAt: lastUpdate,
|
UpdatedAt: lastUpdate,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
replLogOnceIf(ctx,
|
replLogOnceIf(ctx,
|
||||||
fmt.Errorf("Unable to heal group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err),
|
fmt.Errorf("Unable to heal group from peer site %s -> site %s : %w", latestPeerName, peerName, err),
|
||||||
fmt.Sprintf("heal-group-%s", group))
|
fmt.Sprintf("heal-group-%s", group))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-18
@@ -264,6 +264,24 @@ type FileInfo struct {
|
|||||||
Versioned bool `msg:"vs"`
|
Versioned bool `msg:"vs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fi FileInfo) shardSize() int64 {
|
||||||
|
return ceilFrac(fi.Erasure.BlockSize, int64(fi.Erasure.DataBlocks))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShardFileSize - returns final erasure size from original size.
|
||||||
|
func (fi FileInfo) ShardFileSize(totalLength int64) int64 {
|
||||||
|
if totalLength == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if totalLength == -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
numShards := totalLength / fi.Erasure.BlockSize
|
||||||
|
lastBlockSize := totalLength % fi.Erasure.BlockSize
|
||||||
|
lastShardSize := ceilFrac(lastBlockSize, int64(fi.Erasure.DataBlocks))
|
||||||
|
return numShards*fi.shardSize() + lastShardSize
|
||||||
|
}
|
||||||
|
|
||||||
// ShallowCopy - copies minimal information for READ MRF checks.
|
// ShallowCopy - copies minimal information for READ MRF checks.
|
||||||
func (fi FileInfo) ShallowCopy() (n FileInfo) {
|
func (fi FileInfo) ShallowCopy() (n FileInfo) {
|
||||||
n.Volume = fi.Volume
|
n.Volume = fi.Volume
|
||||||
@@ -374,24 +392,24 @@ func newFileInfo(object string, dataBlocks, parityBlocks int) (fi FileInfo) {
|
|||||||
|
|
||||||
// ReadMultipleReq contains information of multiple files to read from disk.
|
// ReadMultipleReq contains information of multiple files to read from disk.
|
||||||
type ReadMultipleReq struct {
|
type ReadMultipleReq struct {
|
||||||
Bucket string // Bucket. Can be empty if multiple buckets.
|
Bucket string `msg:"bk"` // Bucket. Can be empty if multiple buckets.
|
||||||
Prefix string // Shared prefix of all files. Can be empty. Will be joined to filename without modification.
|
Prefix string `msg:"pr,omitempty"` // Shared prefix of all files. Can be empty. Will be joined to filename without modification.
|
||||||
Files []string // Individual files to read.
|
Files []string `msg:"fl"` // Individual files to read.
|
||||||
MaxSize int64 // Return error if size is exceed.
|
MaxSize int64 `msg:"ms"` // Return error if size is exceed.
|
||||||
MetadataOnly bool // Read as XL meta and truncate data.
|
MetadataOnly bool `msg:"mo"` // Read as XL meta and truncate data.
|
||||||
AbortOn404 bool // Stop reading after first file not found.
|
AbortOn404 bool `msg:"ab"` // Stop reading after first file not found.
|
||||||
MaxResults int // Stop after this many successful results. <= 0 means all.
|
MaxResults int `msg:"mr"` // Stop after this many successful results. <= 0 means all.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMultipleResp contains a single response from a ReadMultipleReq.
|
// ReadMultipleResp contains a single response from a ReadMultipleReq.
|
||||||
type ReadMultipleResp struct {
|
type ReadMultipleResp struct {
|
||||||
Bucket string // Bucket as given by request.
|
Bucket string `msg:"bk"` // Bucket as given by request.
|
||||||
Prefix string // Prefix as given by request.
|
Prefix string `msg:"pr,omitempty"` // Prefix as given by request.
|
||||||
File string // File name as given in request.
|
File string `msg:"fl"` // File name as given in request.
|
||||||
Exists bool // Returns whether the file existed on disk.
|
Exists bool `msg:"ex"` // Returns whether the file existed on disk.
|
||||||
Error string // Returns any error when reading.
|
Error string `msg:"er,omitempty"` // Returns any error when reading.
|
||||||
Data []byte // Contains all data of file.
|
Data []byte `msg:"d"` // Contains all data of file.
|
||||||
Modtime time.Time // Modtime of file on disk.
|
Modtime time.Time `msg:"m"` // Modtime of file on disk.
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVersionHandlerParams are parameters for DeleteVersionHandler
|
// DeleteVersionHandlerParams are parameters for DeleteVersionHandler
|
||||||
@@ -498,8 +516,8 @@ type WriteAllHandlerParams struct {
|
|||||||
// only after as a 2-phase call, allowing the older dataDir to
|
// only after as a 2-phase call, allowing the older dataDir to
|
||||||
// hang-around in-case we need some form of recovery.
|
// hang-around in-case we need some form of recovery.
|
||||||
type RenameDataResp struct {
|
type RenameDataResp struct {
|
||||||
Sign []byte
|
Sign []byte `msg:"s"`
|
||||||
OldDataDir string // contains '<uuid>', it is designed to be passed as value to Delete(bucket, pathJoin(object, dataDir))
|
OldDataDir string `msg:"od"` // contains '<uuid>', it is designed to be passed as value to Delete(bucket, pathJoin(object, dataDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -516,15 +534,26 @@ const (
|
|||||||
|
|
||||||
// CheckPartsResp is a response of the storage CheckParts and VerifyFile APIs
|
// CheckPartsResp is a response of the storage CheckParts and VerifyFile APIs
|
||||||
type CheckPartsResp struct {
|
type CheckPartsResp struct {
|
||||||
Results []int
|
Results []int `msg:"r"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalDiskIDs - GetLocalIDs response.
|
// LocalDiskIDs - GetLocalIDs response.
|
||||||
type LocalDiskIDs struct {
|
type LocalDiskIDs struct {
|
||||||
IDs []string
|
IDs []string `msg:"i"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListDirResult - ListDir()'s response.
|
// ListDirResult - ListDir()'s response.
|
||||||
type ListDirResult struct {
|
type ListDirResult struct {
|
||||||
Entries []string `msg:"e"`
|
Entries []string `msg:"e"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteBulkReq - send multiple paths in same delete request.
|
||||||
|
type DeleteBulkReq struct {
|
||||||
|
Paths []string `msg:"p"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVersionsErrsResp - collection of delete errors
|
||||||
|
// for bulk version deletes
|
||||||
|
type DeleteVersionsErrsResp struct {
|
||||||
|
Errs []string `msg:"e"`
|
||||||
|
}
|
||||||
|
|||||||
+476
-118
@@ -291,7 +291,7 @@ func (z *CheckPartsResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Results":
|
case "r":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, err = dc.ReadArrayHeader()
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -324,8 +324,8 @@ func (z *CheckPartsResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *CheckPartsResp) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *CheckPartsResp) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 1
|
// map header, size 1
|
||||||
// write "Results"
|
// write "r"
|
||||||
err = en.Append(0x81, 0xa7, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73)
|
err = en.Append(0x81, 0xa1, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -348,8 +348,8 @@ func (z *CheckPartsResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
func (z *CheckPartsResp) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *CheckPartsResp) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 1
|
// map header, size 1
|
||||||
// string "Results"
|
// string "r"
|
||||||
o = append(o, 0x81, 0xa7, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73)
|
o = append(o, 0x81, 0xa1, 0x72)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(len(z.Results)))
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Results)))
|
||||||
for za0001 := range z.Results {
|
for za0001 := range z.Results {
|
||||||
o = msgp.AppendInt(o, z.Results[za0001])
|
o = msgp.AppendInt(o, z.Results[za0001])
|
||||||
@@ -375,7 +375,7 @@ func (z *CheckPartsResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Results":
|
case "r":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -408,7 +408,149 @@ func (z *CheckPartsResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *CheckPartsResp) Msgsize() (s int) {
|
func (z *CheckPartsResp) Msgsize() (s int) {
|
||||||
s = 1 + 8 + msgp.ArrayHeaderSize + (len(z.Results) * (msgp.IntSize))
|
s = 1 + 2 + msgp.ArrayHeaderSize + (len(z.Results) * (msgp.IntSize))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *DeleteBulkReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "p":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Paths")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Paths) >= int(zb0002) {
|
||||||
|
z.Paths = (z.Paths)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Paths = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Paths {
|
||||||
|
z.Paths[za0001], err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Paths", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *DeleteBulkReq) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 1
|
||||||
|
// write "p"
|
||||||
|
err = en.Append(0x81, 0xa1, 0x70)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.Paths)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Paths")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.Paths {
|
||||||
|
err = en.WriteString(z.Paths[za0001])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Paths", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *DeleteBulkReq) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 1
|
||||||
|
// string "p"
|
||||||
|
o = append(o, 0x81, 0xa1, 0x70)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Paths)))
|
||||||
|
for za0001 := range z.Paths {
|
||||||
|
o = msgp.AppendString(o, z.Paths[za0001])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *DeleteBulkReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "p":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Paths")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Paths) >= int(zb0002) {
|
||||||
|
z.Paths = (z.Paths)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Paths = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Paths {
|
||||||
|
z.Paths[za0001], bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Paths", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *DeleteBulkReq) Msgsize() (s int) {
|
||||||
|
s = 1 + 2 + msgp.ArrayHeaderSize
|
||||||
|
for za0001 := range z.Paths {
|
||||||
|
s += msgp.StringPrefixSize + len(z.Paths[za0001])
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1101,6 +1243,148 @@ func (z *DeleteVersionHandlerParams) Msgsize() (s int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *DeleteVersionsErrsResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "e":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Errs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Errs) >= int(zb0002) {
|
||||||
|
z.Errs = (z.Errs)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Errs = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Errs {
|
||||||
|
z.Errs[za0001], err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Errs", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *DeleteVersionsErrsResp) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 1
|
||||||
|
// write "e"
|
||||||
|
err = en.Append(0x81, 0xa1, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteArrayHeader(uint32(len(z.Errs)))
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Errs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for za0001 := range z.Errs {
|
||||||
|
err = en.WriteString(z.Errs[za0001])
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Errs", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *DeleteVersionsErrsResp) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 1
|
||||||
|
// string "e"
|
||||||
|
o = append(o, 0x81, 0xa1, 0x65)
|
||||||
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Errs)))
|
||||||
|
for za0001 := range z.Errs {
|
||||||
|
o = msgp.AppendString(o, z.Errs[za0001])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *DeleteVersionsErrsResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "e":
|
||||||
|
var zb0002 uint32
|
||||||
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Errs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(z.Errs) >= int(zb0002) {
|
||||||
|
z.Errs = (z.Errs)[:zb0002]
|
||||||
|
} else {
|
||||||
|
z.Errs = make([]string, zb0002)
|
||||||
|
}
|
||||||
|
for za0001 := range z.Errs {
|
||||||
|
z.Errs[za0001], bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Errs", za0001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *DeleteVersionsErrsResp) Msgsize() (s int) {
|
||||||
|
s = 1 + 2 + msgp.ArrayHeaderSize
|
||||||
|
for za0001 := range z.Errs {
|
||||||
|
s += msgp.StringPrefixSize + len(z.Errs[za0001])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeMsg implements msgp.Decodable
|
// DecodeMsg implements msgp.Decodable
|
||||||
func (z *DiskInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
func (z *DiskInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
var zb0001 uint32
|
var zb0001 uint32
|
||||||
@@ -3234,7 +3518,7 @@ func (z *LocalDiskIDs) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "IDs":
|
case "i":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, err = dc.ReadArrayHeader()
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3267,8 +3551,8 @@ func (z *LocalDiskIDs) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *LocalDiskIDs) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *LocalDiskIDs) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 1
|
// map header, size 1
|
||||||
// write "IDs"
|
// write "i"
|
||||||
err = en.Append(0x81, 0xa3, 0x49, 0x44, 0x73)
|
err = en.Append(0x81, 0xa1, 0x69)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3291,8 +3575,8 @@ func (z *LocalDiskIDs) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
func (z *LocalDiskIDs) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *LocalDiskIDs) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 1
|
// map header, size 1
|
||||||
// string "IDs"
|
// string "i"
|
||||||
o = append(o, 0x81, 0xa3, 0x49, 0x44, 0x73)
|
o = append(o, 0x81, 0xa1, 0x69)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(len(z.IDs)))
|
o = msgp.AppendArrayHeader(o, uint32(len(z.IDs)))
|
||||||
for za0001 := range z.IDs {
|
for za0001 := range z.IDs {
|
||||||
o = msgp.AppendString(o, z.IDs[za0001])
|
o = msgp.AppendString(o, z.IDs[za0001])
|
||||||
@@ -3318,7 +3602,7 @@ func (z *LocalDiskIDs) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "IDs":
|
case "i":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3351,7 +3635,7 @@ func (z *LocalDiskIDs) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *LocalDiskIDs) Msgsize() (s int) {
|
func (z *LocalDiskIDs) Msgsize() (s int) {
|
||||||
s = 1 + 4 + msgp.ArrayHeaderSize
|
s = 1 + 2 + msgp.ArrayHeaderSize
|
||||||
for za0001 := range z.IDs {
|
for za0001 := range z.IDs {
|
||||||
s += msgp.StringPrefixSize + len(z.IDs[za0001])
|
s += msgp.StringPrefixSize + len(z.IDs[za0001])
|
||||||
}
|
}
|
||||||
@@ -3944,19 +4228,19 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Bucket":
|
case "bk":
|
||||||
z.Bucket, err = dc.ReadString()
|
z.Bucket, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Bucket")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Prefix":
|
case "pr":
|
||||||
z.Prefix, err = dc.ReadString()
|
z.Prefix, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Files":
|
case "fl":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, err = dc.ReadArrayHeader()
|
zb0002, err = dc.ReadArrayHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3975,25 +4259,25 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "MaxSize":
|
case "ms":
|
||||||
z.MaxSize, err = dc.ReadInt64()
|
z.MaxSize, err = dc.ReadInt64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "MaxSize")
|
err = msgp.WrapError(err, "MaxSize")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "MetadataOnly":
|
case "mo":
|
||||||
z.MetadataOnly, err = dc.ReadBool()
|
z.MetadataOnly, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "MetadataOnly")
|
err = msgp.WrapError(err, "MetadataOnly")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "AbortOn404":
|
case "ab":
|
||||||
z.AbortOn404, err = dc.ReadBool()
|
z.AbortOn404, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "AbortOn404")
|
err = msgp.WrapError(err, "AbortOn404")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "MaxResults":
|
case "mr":
|
||||||
z.MaxResults, err = dc.ReadInt()
|
z.MaxResults, err = dc.ReadInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "MaxResults")
|
err = msgp.WrapError(err, "MaxResults")
|
||||||
@@ -4012,9 +4296,24 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 7
|
// check for omitted fields
|
||||||
// write "Bucket"
|
zb0001Len := uint32(7)
|
||||||
err = en.Append(0x87, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
var zb0001Mask uint8 /* 7 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
|
if z.Prefix == "" {
|
||||||
|
zb0001Len--
|
||||||
|
zb0001Mask |= 0x2
|
||||||
|
}
|
||||||
|
// variable map header, size zb0001Len
|
||||||
|
err = en.Append(0x80 | uint8(zb0001Len))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0001Len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "bk"
|
||||||
|
err = en.Append(0xa2, 0x62, 0x6b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4023,8 +4322,9 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Bucket")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Prefix"
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
err = en.Append(0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
// write "pr"
|
||||||
|
err = en.Append(0xa2, 0x70, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4033,8 +4333,9 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Files"
|
}
|
||||||
err = en.Append(0xa5, 0x46, 0x69, 0x6c, 0x65, 0x73)
|
// write "fl"
|
||||||
|
err = en.Append(0xa2, 0x66, 0x6c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4050,8 +4351,8 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// write "MaxSize"
|
// write "ms"
|
||||||
err = en.Append(0xa7, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65)
|
err = en.Append(0xa2, 0x6d, 0x73)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4060,8 +4361,8 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "MaxSize")
|
err = msgp.WrapError(err, "MaxSize")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "MetadataOnly"
|
// write "mo"
|
||||||
err = en.Append(0xac, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4f, 0x6e, 0x6c, 0x79)
|
err = en.Append(0xa2, 0x6d, 0x6f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4070,8 +4371,8 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "MetadataOnly")
|
err = msgp.WrapError(err, "MetadataOnly")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "AbortOn404"
|
// write "ab"
|
||||||
err = en.Append(0xaa, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x34, 0x30, 0x34)
|
err = en.Append(0xa2, 0x61, 0x62)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4080,8 +4381,8 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "AbortOn404")
|
err = msgp.WrapError(err, "AbortOn404")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "MaxResults"
|
// write "mr"
|
||||||
err = en.Append(0xaa, 0x4d, 0x61, 0x78, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73)
|
err = en.Append(0xa2, 0x6d, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4096,30 +4397,44 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *ReadMultipleReq) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *ReadMultipleReq) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 7
|
// check for omitted fields
|
||||||
// string "Bucket"
|
zb0001Len := uint32(7)
|
||||||
o = append(o, 0x87, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
var zb0001Mask uint8 /* 7 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
|
if z.Prefix == "" {
|
||||||
|
zb0001Len--
|
||||||
|
zb0001Mask |= 0x2
|
||||||
|
}
|
||||||
|
// variable map header, size zb0001Len
|
||||||
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
|
if zb0001Len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "bk"
|
||||||
|
o = append(o, 0xa2, 0x62, 0x6b)
|
||||||
o = msgp.AppendString(o, z.Bucket)
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
// string "Prefix"
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
// string "pr"
|
||||||
|
o = append(o, 0xa2, 0x70, 0x72)
|
||||||
o = msgp.AppendString(o, z.Prefix)
|
o = msgp.AppendString(o, z.Prefix)
|
||||||
// string "Files"
|
}
|
||||||
o = append(o, 0xa5, 0x46, 0x69, 0x6c, 0x65, 0x73)
|
// string "fl"
|
||||||
|
o = append(o, 0xa2, 0x66, 0x6c)
|
||||||
o = msgp.AppendArrayHeader(o, uint32(len(z.Files)))
|
o = msgp.AppendArrayHeader(o, uint32(len(z.Files)))
|
||||||
for za0001 := range z.Files {
|
for za0001 := range z.Files {
|
||||||
o = msgp.AppendString(o, z.Files[za0001])
|
o = msgp.AppendString(o, z.Files[za0001])
|
||||||
}
|
}
|
||||||
// string "MaxSize"
|
// string "ms"
|
||||||
o = append(o, 0xa7, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65)
|
o = append(o, 0xa2, 0x6d, 0x73)
|
||||||
o = msgp.AppendInt64(o, z.MaxSize)
|
o = msgp.AppendInt64(o, z.MaxSize)
|
||||||
// string "MetadataOnly"
|
// string "mo"
|
||||||
o = append(o, 0xac, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4f, 0x6e, 0x6c, 0x79)
|
o = append(o, 0xa2, 0x6d, 0x6f)
|
||||||
o = msgp.AppendBool(o, z.MetadataOnly)
|
o = msgp.AppendBool(o, z.MetadataOnly)
|
||||||
// string "AbortOn404"
|
// string "ab"
|
||||||
o = append(o, 0xaa, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x34, 0x30, 0x34)
|
o = append(o, 0xa2, 0x61, 0x62)
|
||||||
o = msgp.AppendBool(o, z.AbortOn404)
|
o = msgp.AppendBool(o, z.AbortOn404)
|
||||||
// string "MaxResults"
|
// string "mr"
|
||||||
o = append(o, 0xaa, 0x4d, 0x61, 0x78, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73)
|
o = append(o, 0xa2, 0x6d, 0x72)
|
||||||
o = msgp.AppendInt(o, z.MaxResults)
|
o = msgp.AppendInt(o, z.MaxResults)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4142,19 +4457,19 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Bucket":
|
case "bk":
|
||||||
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Bucket")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Prefix":
|
case "pr":
|
||||||
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Files":
|
case "fl":
|
||||||
var zb0002 uint32
|
var zb0002 uint32
|
||||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4173,25 +4488,25 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "MaxSize":
|
case "ms":
|
||||||
z.MaxSize, bts, err = msgp.ReadInt64Bytes(bts)
|
z.MaxSize, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "MaxSize")
|
err = msgp.WrapError(err, "MaxSize")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "MetadataOnly":
|
case "mo":
|
||||||
z.MetadataOnly, bts, err = msgp.ReadBoolBytes(bts)
|
z.MetadataOnly, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "MetadataOnly")
|
err = msgp.WrapError(err, "MetadataOnly")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "AbortOn404":
|
case "ab":
|
||||||
z.AbortOn404, bts, err = msgp.ReadBoolBytes(bts)
|
z.AbortOn404, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "AbortOn404")
|
err = msgp.WrapError(err, "AbortOn404")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "MaxResults":
|
case "mr":
|
||||||
z.MaxResults, bts, err = msgp.ReadIntBytes(bts)
|
z.MaxResults, bts, err = msgp.ReadIntBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "MaxResults")
|
err = msgp.WrapError(err, "MaxResults")
|
||||||
@@ -4211,11 +4526,11 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *ReadMultipleReq) Msgsize() (s int) {
|
func (z *ReadMultipleReq) Msgsize() (s int) {
|
||||||
s = 1 + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 6 + msgp.ArrayHeaderSize
|
s = 1 + 3 + msgp.StringPrefixSize + len(z.Bucket) + 3 + msgp.StringPrefixSize + len(z.Prefix) + 3 + msgp.ArrayHeaderSize
|
||||||
for za0001 := range z.Files {
|
for za0001 := range z.Files {
|
||||||
s += msgp.StringPrefixSize + len(z.Files[za0001])
|
s += msgp.StringPrefixSize + len(z.Files[za0001])
|
||||||
}
|
}
|
||||||
s += 8 + msgp.Int64Size + 13 + msgp.BoolSize + 11 + msgp.BoolSize + 11 + msgp.IntSize
|
s += 3 + msgp.Int64Size + 3 + msgp.BoolSize + 3 + msgp.BoolSize + 3 + msgp.IntSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4237,43 +4552,43 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Bucket":
|
case "bk":
|
||||||
z.Bucket, err = dc.ReadString()
|
z.Bucket, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Bucket")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Prefix":
|
case "pr":
|
||||||
z.Prefix, err = dc.ReadString()
|
z.Prefix, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "File":
|
case "fl":
|
||||||
z.File, err = dc.ReadString()
|
z.File, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "File")
|
err = msgp.WrapError(err, "File")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Exists":
|
case "ex":
|
||||||
z.Exists, err = dc.ReadBool()
|
z.Exists, err = dc.ReadBool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Exists")
|
err = msgp.WrapError(err, "Exists")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Error":
|
case "er":
|
||||||
z.Error, err = dc.ReadString()
|
z.Error, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "Error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Data":
|
case "d":
|
||||||
z.Data, err = dc.ReadBytes(z.Data)
|
z.Data, err = dc.ReadBytes(z.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Data")
|
err = msgp.WrapError(err, "Data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Modtime":
|
case "m":
|
||||||
z.Modtime, err = dc.ReadTime()
|
z.Modtime, err = dc.ReadTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Modtime")
|
err = msgp.WrapError(err, "Modtime")
|
||||||
@@ -4292,9 +4607,28 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 7
|
// check for omitted fields
|
||||||
// write "Bucket"
|
zb0001Len := uint32(7)
|
||||||
err = en.Append(0x87, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
var zb0001Mask uint8 /* 7 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
|
if z.Prefix == "" {
|
||||||
|
zb0001Len--
|
||||||
|
zb0001Mask |= 0x2
|
||||||
|
}
|
||||||
|
if z.Error == "" {
|
||||||
|
zb0001Len--
|
||||||
|
zb0001Mask |= 0x10
|
||||||
|
}
|
||||||
|
// variable map header, size zb0001Len
|
||||||
|
err = en.Append(0x80 | uint8(zb0001Len))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if zb0001Len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "bk"
|
||||||
|
err = en.Append(0xa2, 0x62, 0x6b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4303,8 +4637,9 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Bucket")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Prefix"
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
err = en.Append(0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
// write "pr"
|
||||||
|
err = en.Append(0xa2, 0x70, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4313,8 +4648,9 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "File"
|
}
|
||||||
err = en.Append(0xa4, 0x46, 0x69, 0x6c, 0x65)
|
// write "fl"
|
||||||
|
err = en.Append(0xa2, 0x66, 0x6c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4323,8 +4659,8 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "File")
|
err = msgp.WrapError(err, "File")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Exists"
|
// write "ex"
|
||||||
err = en.Append(0xa6, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73)
|
err = en.Append(0xa2, 0x65, 0x78)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4333,8 +4669,9 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Exists")
|
err = msgp.WrapError(err, "Exists")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Error"
|
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
||||||
err = en.Append(0xa5, 0x45, 0x72, 0x72, 0x6f, 0x72)
|
// write "er"
|
||||||
|
err = en.Append(0xa2, 0x65, 0x72)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4343,8 +4680,9 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "Error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Data"
|
}
|
||||||
err = en.Append(0xa4, 0x44, 0x61, 0x74, 0x61)
|
// write "d"
|
||||||
|
err = en.Append(0xa1, 0x64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4353,8 +4691,8 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Data")
|
err = msgp.WrapError(err, "Data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "Modtime"
|
// write "m"
|
||||||
err = en.Append(0xa7, 0x4d, 0x6f, 0x64, 0x74, 0x69, 0x6d, 0x65)
|
err = en.Append(0xa1, 0x6d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4369,27 +4707,47 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *ReadMultipleResp) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *ReadMultipleResp) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 7
|
// check for omitted fields
|
||||||
// string "Bucket"
|
zb0001Len := uint32(7)
|
||||||
o = append(o, 0x87, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
var zb0001Mask uint8 /* 7 bits */
|
||||||
|
_ = zb0001Mask
|
||||||
|
if z.Prefix == "" {
|
||||||
|
zb0001Len--
|
||||||
|
zb0001Mask |= 0x2
|
||||||
|
}
|
||||||
|
if z.Error == "" {
|
||||||
|
zb0001Len--
|
||||||
|
zb0001Mask |= 0x10
|
||||||
|
}
|
||||||
|
// variable map header, size zb0001Len
|
||||||
|
o = append(o, 0x80|uint8(zb0001Len))
|
||||||
|
if zb0001Len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// string "bk"
|
||||||
|
o = append(o, 0xa2, 0x62, 0x6b)
|
||||||
o = msgp.AppendString(o, z.Bucket)
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
// string "Prefix"
|
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||||
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
// string "pr"
|
||||||
|
o = append(o, 0xa2, 0x70, 0x72)
|
||||||
o = msgp.AppendString(o, z.Prefix)
|
o = msgp.AppendString(o, z.Prefix)
|
||||||
// string "File"
|
}
|
||||||
o = append(o, 0xa4, 0x46, 0x69, 0x6c, 0x65)
|
// string "fl"
|
||||||
|
o = append(o, 0xa2, 0x66, 0x6c)
|
||||||
o = msgp.AppendString(o, z.File)
|
o = msgp.AppendString(o, z.File)
|
||||||
// string "Exists"
|
// string "ex"
|
||||||
o = append(o, 0xa6, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73)
|
o = append(o, 0xa2, 0x65, 0x78)
|
||||||
o = msgp.AppendBool(o, z.Exists)
|
o = msgp.AppendBool(o, z.Exists)
|
||||||
// string "Error"
|
if (zb0001Mask & 0x10) == 0 { // if not omitted
|
||||||
o = append(o, 0xa5, 0x45, 0x72, 0x72, 0x6f, 0x72)
|
// string "er"
|
||||||
|
o = append(o, 0xa2, 0x65, 0x72)
|
||||||
o = msgp.AppendString(o, z.Error)
|
o = msgp.AppendString(o, z.Error)
|
||||||
// string "Data"
|
}
|
||||||
o = append(o, 0xa4, 0x44, 0x61, 0x74, 0x61)
|
// string "d"
|
||||||
|
o = append(o, 0xa1, 0x64)
|
||||||
o = msgp.AppendBytes(o, z.Data)
|
o = msgp.AppendBytes(o, z.Data)
|
||||||
// string "Modtime"
|
// string "m"
|
||||||
o = append(o, 0xa7, 0x4d, 0x6f, 0x64, 0x74, 0x69, 0x6d, 0x65)
|
o = append(o, 0xa1, 0x6d)
|
||||||
o = msgp.AppendTime(o, z.Modtime)
|
o = msgp.AppendTime(o, z.Modtime)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4412,43 +4770,43 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Bucket":
|
case "bk":
|
||||||
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Bucket")
|
err = msgp.WrapError(err, "Bucket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Prefix":
|
case "pr":
|
||||||
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Prefix")
|
err = msgp.WrapError(err, "Prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "File":
|
case "fl":
|
||||||
z.File, bts, err = msgp.ReadStringBytes(bts)
|
z.File, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "File")
|
err = msgp.WrapError(err, "File")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Exists":
|
case "ex":
|
||||||
z.Exists, bts, err = msgp.ReadBoolBytes(bts)
|
z.Exists, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Exists")
|
err = msgp.WrapError(err, "Exists")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Error":
|
case "er":
|
||||||
z.Error, bts, err = msgp.ReadStringBytes(bts)
|
z.Error, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Error")
|
err = msgp.WrapError(err, "Error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Data":
|
case "d":
|
||||||
z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data)
|
z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Data")
|
err = msgp.WrapError(err, "Data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "Modtime":
|
case "m":
|
||||||
z.Modtime, bts, err = msgp.ReadTimeBytes(bts)
|
z.Modtime, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Modtime")
|
err = msgp.WrapError(err, "Modtime")
|
||||||
@@ -4468,7 +4826,7 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *ReadMultipleResp) Msgsize() (s int) {
|
func (z *ReadMultipleResp) Msgsize() (s int) {
|
||||||
s = 1 + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 5 + msgp.StringPrefixSize + len(z.File) + 7 + msgp.BoolSize + 6 + msgp.StringPrefixSize + len(z.Error) + 5 + msgp.BytesPrefixSize + len(z.Data) + 8 + msgp.TimeSize
|
s = 1 + 3 + msgp.StringPrefixSize + len(z.Bucket) + 3 + msgp.StringPrefixSize + len(z.Prefix) + 3 + msgp.StringPrefixSize + len(z.File) + 3 + msgp.BoolSize + 3 + msgp.StringPrefixSize + len(z.Error) + 2 + msgp.BytesPrefixSize + len(z.Data) + 2 + msgp.TimeSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4946,13 +5304,13 @@ func (z *RenameDataResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Sign":
|
case "s":
|
||||||
z.Sign, err = dc.ReadBytes(z.Sign)
|
z.Sign, err = dc.ReadBytes(z.Sign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Sign")
|
err = msgp.WrapError(err, "Sign")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "OldDataDir":
|
case "od":
|
||||||
z.OldDataDir, err = dc.ReadString()
|
z.OldDataDir, err = dc.ReadString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OldDataDir")
|
err = msgp.WrapError(err, "OldDataDir")
|
||||||
@@ -4972,8 +5330,8 @@ func (z *RenameDataResp) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *RenameDataResp) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *RenameDataResp) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 2
|
// map header, size 2
|
||||||
// write "Sign"
|
// write "s"
|
||||||
err = en.Append(0x82, 0xa4, 0x53, 0x69, 0x67, 0x6e)
|
err = en.Append(0x82, 0xa1, 0x73)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4982,8 +5340,8 @@ func (z *RenameDataResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Sign")
|
err = msgp.WrapError(err, "Sign")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "OldDataDir"
|
// write "od"
|
||||||
err = en.Append(0xaa, 0x4f, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x61, 0x44, 0x69, 0x72)
|
err = en.Append(0xa2, 0x6f, 0x64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4999,11 +5357,11 @@ func (z *RenameDataResp) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
func (z *RenameDataResp) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *RenameDataResp) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 2
|
// map header, size 2
|
||||||
// string "Sign"
|
// string "s"
|
||||||
o = append(o, 0x82, 0xa4, 0x53, 0x69, 0x67, 0x6e)
|
o = append(o, 0x82, 0xa1, 0x73)
|
||||||
o = msgp.AppendBytes(o, z.Sign)
|
o = msgp.AppendBytes(o, z.Sign)
|
||||||
// string "OldDataDir"
|
// string "od"
|
||||||
o = append(o, 0xaa, 0x4f, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x61, 0x44, 0x69, 0x72)
|
o = append(o, 0xa2, 0x6f, 0x64)
|
||||||
o = msgp.AppendString(o, z.OldDataDir)
|
o = msgp.AppendString(o, z.OldDataDir)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -5026,13 +5384,13 @@ func (z *RenameDataResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msgp.UnsafeString(field) {
|
switch msgp.UnsafeString(field) {
|
||||||
case "Sign":
|
case "s":
|
||||||
z.Sign, bts, err = msgp.ReadBytesBytes(bts, z.Sign)
|
z.Sign, bts, err = msgp.ReadBytesBytes(bts, z.Sign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "Sign")
|
err = msgp.WrapError(err, "Sign")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "OldDataDir":
|
case "od":
|
||||||
z.OldDataDir, bts, err = msgp.ReadStringBytes(bts)
|
z.OldDataDir, bts, err = msgp.ReadStringBytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "OldDataDir")
|
err = msgp.WrapError(err, "OldDataDir")
|
||||||
@@ -5052,7 +5410,7 @@ func (z *RenameDataResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *RenameDataResp) Msgsize() (s int) {
|
func (z *RenameDataResp) Msgsize() (s int) {
|
||||||
s = 1 + 5 + msgp.BytesPrefixSize + len(z.Sign) + 11 + msgp.StringPrefixSize + len(z.OldDataDir)
|
s = 1 + 2 + msgp.BytesPrefixSize + len(z.Sign) + 3 + msgp.StringPrefixSize + len(z.OldDataDir)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -348,6 +348,119 @@ func BenchmarkDecodeCheckPartsResp(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalDeleteBulkReq(t *testing.T) {
|
||||||
|
v := DeleteBulkReq{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgDeleteBulkReq(b *testing.B) {
|
||||||
|
v := DeleteBulkReq{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgDeleteBulkReq(b *testing.B) {
|
||||||
|
v := DeleteBulkReq{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalDeleteBulkReq(b *testing.B) {
|
||||||
|
v := DeleteBulkReq{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeDeleteBulkReq(t *testing.T) {
|
||||||
|
v := DeleteBulkReq{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeDeleteBulkReq Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := DeleteBulkReq{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeDeleteBulkReq(b *testing.B) {
|
||||||
|
v := DeleteBulkReq{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeDeleteBulkReq(b *testing.B) {
|
||||||
|
v := DeleteBulkReq{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalDeleteFileHandlerParams(t *testing.T) {
|
func TestMarshalUnmarshalDeleteFileHandlerParams(t *testing.T) {
|
||||||
v := DeleteFileHandlerParams{}
|
v := DeleteFileHandlerParams{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
@@ -687,6 +800,119 @@ func BenchmarkDecodeDeleteVersionHandlerParams(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalDeleteVersionsErrsResp(t *testing.T) {
|
||||||
|
v := DeleteVersionsErrsResp{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgDeleteVersionsErrsResp(b *testing.B) {
|
||||||
|
v := DeleteVersionsErrsResp{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgDeleteVersionsErrsResp(b *testing.B) {
|
||||||
|
v := DeleteVersionsErrsResp{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalDeleteVersionsErrsResp(b *testing.B) {
|
||||||
|
v := DeleteVersionsErrsResp{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeDeleteVersionsErrsResp(t *testing.T) {
|
||||||
|
v := DeleteVersionsErrsResp{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeDeleteVersionsErrsResp Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := DeleteVersionsErrsResp{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeDeleteVersionsErrsResp(b *testing.B) {
|
||||||
|
v := DeleteVersionsErrsResp{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeDeleteVersionsErrsResp(b *testing.B) {
|
||||||
|
v := DeleteVersionsErrsResp{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalDiskInfo(t *testing.T) {
|
func TestMarshalUnmarshalDiskInfo(t *testing.T) {
|
||||||
v := DiskInfo{}
|
v := DiskInfo{}
|
||||||
bts, err := v.MarshalMsg(nil)
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ type StorageAPI interface {
|
|||||||
// Metadata operations
|
// Metadata operations
|
||||||
DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool, opts DeleteOptions) error
|
DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool, opts DeleteOptions) error
|
||||||
DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions, opts DeleteOptions) []error
|
DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions, opts DeleteOptions) []error
|
||||||
|
DeleteBulk(ctx context.Context, volume string, paths ...string) error
|
||||||
WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) error
|
WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) error
|
||||||
UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo, opts UpdateMetadataOpts) error
|
UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo, opts UpdateMetadataOpts) error
|
||||||
ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (FileInfo, error)
|
ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (FileInfo, error)
|
||||||
|
|||||||
+53
-15
@@ -173,14 +173,28 @@ func (client *storageRESTClient) GetDiskLoc() (poolIdx, setIdx, diskIdx int) {
|
|||||||
return client.endpoint.PoolIdx, client.endpoint.SetIdx, client.endpoint.DiskIdx
|
return client.endpoint.PoolIdx, client.endpoint.SetIdx, client.endpoint.DiskIdx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is disconnected
|
// Wrapper to restClient.CallWithMethod to handle network errors, in case of network error the connection is disconnected
|
||||||
// and a healthcheck routine gets invoked that would reconnect.
|
// and a healthcheck routine gets invoked that would reconnect.
|
||||||
func (client *storageRESTClient) call(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (io.ReadCloser, error) {
|
func (client *storageRESTClient) callGet(ctx context.Context, rpcMethod string, values url.Values, body io.Reader, length int64) (io.ReadCloser, error) {
|
||||||
if values == nil {
|
if values == nil {
|
||||||
values = make(url.Values)
|
values = make(url.Values)
|
||||||
}
|
}
|
||||||
values.Set(storageRESTDiskID, *client.diskID.Load())
|
values.Set(storageRESTDiskID, *client.diskID.Load())
|
||||||
respBody, err := client.restClient.Call(ctx, method, values, body, length)
|
respBody, err := client.restClient.CallWithHTTPMethod(ctx, http.MethodGet, rpcMethod, values, body, length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, toStorageErr(err)
|
||||||
|
}
|
||||||
|
return respBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is disconnected
|
||||||
|
// and a healthcheck routine gets invoked that would reconnect.
|
||||||
|
func (client *storageRESTClient) call(ctx context.Context, rpcMethod string, values url.Values, body io.Reader, length int64) (io.ReadCloser, error) {
|
||||||
|
if values == nil {
|
||||||
|
values = make(url.Values)
|
||||||
|
}
|
||||||
|
values.Set(storageRESTDiskID, *client.diskID.Load())
|
||||||
|
respBody, err := client.restClient.CallWithHTTPMethod(ctx, http.MethodPost, rpcMethod, values, body, length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, toStorageErr(err)
|
return nil, toStorageErr(err)
|
||||||
}
|
}
|
||||||
@@ -499,7 +513,7 @@ var readMsgpReaderPool = sync.Pool{New: func() interface{} { return &msgp.Reader
|
|||||||
func msgpNewReader(r io.Reader) *msgp.Reader {
|
func msgpNewReader(r io.Reader) *msgp.Reader {
|
||||||
p := readMsgpReaderPool.Get().(*msgp.Reader)
|
p := readMsgpReaderPool.Get().(*msgp.Reader)
|
||||||
if p.R == nil {
|
if p.R == nil {
|
||||||
p.R = xbufio.NewReaderSize(r, 4<<10)
|
p.R = xbufio.NewReaderSize(r, 32<<10)
|
||||||
} else {
|
} else {
|
||||||
p.R.Reset(r)
|
p.R.Reset(r)
|
||||||
}
|
}
|
||||||
@@ -526,7 +540,6 @@ func (client *storageRESTClient) ReadVersion(ctx context.Context, origvolume, vo
|
|||||||
storageRESTFilePath: path,
|
storageRESTFilePath: path,
|
||||||
storageRESTVersionID: versionID,
|
storageRESTVersionID: versionID,
|
||||||
storageRESTInclFreeVersions: strconv.FormatBool(opts.InclFreeVersions),
|
storageRESTInclFreeVersions: strconv.FormatBool(opts.InclFreeVersions),
|
||||||
storageRESTReadData: strconv.FormatBool(opts.ReadData),
|
|
||||||
storageRESTHealing: strconv.FormatBool(opts.Healing),
|
storageRESTHealing: strconv.FormatBool(opts.Healing),
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -541,10 +554,9 @@ func (client *storageRESTClient) ReadVersion(ctx context.Context, origvolume, vo
|
|||||||
values.Set(storageRESTFilePath, path)
|
values.Set(storageRESTFilePath, path)
|
||||||
values.Set(storageRESTVersionID, versionID)
|
values.Set(storageRESTVersionID, versionID)
|
||||||
values.Set(storageRESTInclFreeVersions, strconv.FormatBool(opts.InclFreeVersions))
|
values.Set(storageRESTInclFreeVersions, strconv.FormatBool(opts.InclFreeVersions))
|
||||||
values.Set(storageRESTReadData, strconv.FormatBool(opts.ReadData))
|
|
||||||
values.Set(storageRESTHealing, strconv.FormatBool(opts.Healing))
|
values.Set(storageRESTHealing, strconv.FormatBool(opts.Healing))
|
||||||
|
|
||||||
respBody, err := client.call(ctx, storageRESTMethodReadVersion, values, nil, -1)
|
respBody, err := client.callGet(ctx, storageRESTMethodReadVersion, values, nil, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fi, err
|
return fi, err
|
||||||
}
|
}
|
||||||
@@ -568,7 +580,6 @@ func (client *storageRESTClient) ReadXL(ctx context.Context, volume string, path
|
|||||||
storageRESTDiskID: *client.diskID.Load(),
|
storageRESTDiskID: *client.diskID.Load(),
|
||||||
storageRESTVolume: volume,
|
storageRESTVolume: volume,
|
||||||
storageRESTFilePath: path,
|
storageRESTFilePath: path,
|
||||||
storageRESTReadData: "false",
|
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rf, toStorageErr(err)
|
return rf, toStorageErr(err)
|
||||||
@@ -579,8 +590,8 @@ func (client *storageRESTClient) ReadXL(ctx context.Context, volume string, path
|
|||||||
values := make(url.Values)
|
values := make(url.Values)
|
||||||
values.Set(storageRESTVolume, volume)
|
values.Set(storageRESTVolume, volume)
|
||||||
values.Set(storageRESTFilePath, path)
|
values.Set(storageRESTFilePath, path)
|
||||||
values.Set(storageRESTReadData, strconv.FormatBool(readData))
|
|
||||||
respBody, err := client.call(ctx, storageRESTMethodReadXL, values, nil, -1)
|
respBody, err := client.callGet(ctx, storageRESTMethodReadXL, values, nil, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rf, toStorageErr(err)
|
return rf, toStorageErr(err)
|
||||||
}
|
}
|
||||||
@@ -617,9 +628,10 @@ func (client *storageRESTClient) ReadFileStream(ctx context.Context, volume, pat
|
|||||||
values.Set(storageRESTFilePath, path)
|
values.Set(storageRESTFilePath, path)
|
||||||
values.Set(storageRESTOffset, strconv.Itoa(int(offset)))
|
values.Set(storageRESTOffset, strconv.Itoa(int(offset)))
|
||||||
values.Set(storageRESTLength, strconv.Itoa(int(length)))
|
values.Set(storageRESTLength, strconv.Itoa(int(length)))
|
||||||
respBody, err := client.call(ctx, storageRESTMethodReadFileStream, values, nil, -1)
|
|
||||||
|
respBody, err := client.callGet(ctx, storageRESTMethodReadFileStream, values, nil, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, toStorageErr(err)
|
||||||
}
|
}
|
||||||
return respBody, nil
|
return respBody, nil
|
||||||
}
|
}
|
||||||
@@ -638,7 +650,7 @@ func (client *storageRESTClient) ReadFile(ctx context.Context, volume string, pa
|
|||||||
values.Set(storageRESTBitrotAlgo, "")
|
values.Set(storageRESTBitrotAlgo, "")
|
||||||
values.Set(storageRESTBitrotHash, "")
|
values.Set(storageRESTBitrotHash, "")
|
||||||
}
|
}
|
||||||
respBody, err := client.call(ctx, storageRESTMethodReadFile, values, nil, -1)
|
respBody, err := client.callGet(ctx, storageRESTMethodReadFile, values, nil, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -725,7 +737,9 @@ func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
dErrResp := &DeleteVersionsErrsResp{}
|
dErrResp := &DeleteVersionsErrsResp{}
|
||||||
if err = gob.NewDecoder(reader).Decode(dErrResp); err != nil {
|
decoder := msgpNewReader(reader)
|
||||||
|
defer readMsgpReaderPoolPut(decoder)
|
||||||
|
if err = dErrResp.DecodeMsg(decoder); err != nil {
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
errs[i] = toStorageErr(err)
|
errs[i] = toStorageErr(err)
|
||||||
}
|
}
|
||||||
@@ -733,7 +747,11 @@ func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, dErr := range dErrResp.Errs {
|
for i, dErr := range dErrResp.Errs {
|
||||||
errs[i] = toStorageErr(dErr)
|
if dErr != "" {
|
||||||
|
errs[i] = toStorageErr(errors.New(dErr))
|
||||||
|
} else {
|
||||||
|
errs[i] = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
@@ -783,6 +801,26 @@ func (client *storageRESTClient) VerifyFile(ctx context.Context, volume, path st
|
|||||||
return verifyResp, nil
|
return verifyResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client *storageRESTClient) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(storageRESTVolume, volume)
|
||||||
|
|
||||||
|
req := &DeleteBulkReq{Paths: paths}
|
||||||
|
body, err := req.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := client.call(ctx, storageRESTMethodDeleteBulk, values, bytes.NewReader(body), int64(len(body)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer xhttp.DrainBody(respBody)
|
||||||
|
|
||||||
|
_, err = waitForHTTPResponse(respBody)
|
||||||
|
return toStorageErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
func (client *storageRESTClient) StatInfoFile(ctx context.Context, volume, path string, glob bool) (stat []StatInfo, err error) {
|
func (client *storageRESTClient) StatInfoFile(ctx context.Context, volume, path string, glob bool) (stat []StatInfo, err error) {
|
||||||
values := make(url.Values)
|
values := make(url.Values)
|
||||||
values.Set(storageRESTVolume, volume)
|
values.Set(storageRESTVolume, volume)
|
||||||
|
|||||||
+36
-35
@@ -20,7 +20,7 @@ package cmd
|
|||||||
//go:generate msgp -file $GOFILE -unexported
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
|
|
||||||
const (
|
const (
|
||||||
storageRESTVersion = "v59" // Change ReadOptions inclFreeVersions
|
storageRESTVersion = "v62" // Introduce DeleteBulk internode API.
|
||||||
storageRESTVersionPrefix = SlashSeparator + storageRESTVersion
|
storageRESTVersionPrefix = SlashSeparator + storageRESTVersion
|
||||||
storageRESTPrefix = minioReservedBucketPath + "/storage"
|
storageRESTPrefix = minioReservedBucketPath + "/storage"
|
||||||
)
|
)
|
||||||
@@ -28,48 +28,49 @@ const (
|
|||||||
const (
|
const (
|
||||||
storageRESTMethodHealth = "/health"
|
storageRESTMethodHealth = "/health"
|
||||||
|
|
||||||
storageRESTMethodAppendFile = "/appendfile"
|
storageRESTMethodAppendFile = "/afile"
|
||||||
storageRESTMethodCreateFile = "/createfile"
|
storageRESTMethodCreateFile = "/cfile"
|
||||||
storageRESTMethodWriteAll = "/writeall"
|
storageRESTMethodWriteAll = "/wall"
|
||||||
storageRESTMethodReadVersion = "/readversion"
|
storageRESTMethodReadVersion = "/rver"
|
||||||
storageRESTMethodReadXL = "/readxl"
|
storageRESTMethodReadXL = "/rxl"
|
||||||
storageRESTMethodReadAll = "/readall"
|
storageRESTMethodReadAll = "/rall"
|
||||||
storageRESTMethodReadFile = "/readfile"
|
storageRESTMethodReadFile = "/rfile"
|
||||||
storageRESTMethodReadFileStream = "/readfilestream"
|
storageRESTMethodReadFileStream = "/rfilest"
|
||||||
storageRESTMethodListDir = "/listdir"
|
storageRESTMethodListDir = "/ls"
|
||||||
storageRESTMethodDeleteVersions = "/deleteverions"
|
storageRESTMethodDeleteVersions = "/dvers"
|
||||||
storageRESTMethodRenameFile = "/renamefile"
|
storageRESTMethodRenameFile = "/rfile"
|
||||||
storageRESTMethodVerifyFile = "/verifyfile"
|
storageRESTMethodVerifyFile = "/vfile"
|
||||||
storageRESTMethodStatInfoFile = "/statfile"
|
storageRESTMethodStatInfoFile = "/sfile"
|
||||||
storageRESTMethodReadMultiple = "/readmultiple"
|
storageRESTMethodReadMultiple = "/rmpl"
|
||||||
storageRESTMethodCleanAbandoned = "/cleanabandoned"
|
storageRESTMethodCleanAbandoned = "/cln"
|
||||||
|
storageRESTMethodDeleteBulk = "/dblk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
storageRESTVolume = "volume"
|
storageRESTVolume = "vol"
|
||||||
storageRESTVolumes = "volumes"
|
storageRESTVolumes = "vols"
|
||||||
storageRESTDirPath = "dir-path"
|
storageRESTDirPath = "dpath"
|
||||||
storageRESTFilePath = "file-path"
|
storageRESTFilePath = "fp"
|
||||||
storageRESTVersionID = "version-id"
|
storageRESTVersionID = "vid"
|
||||||
storageRESTReadData = "read-data"
|
storageRESTHealing = "heal"
|
||||||
storageRESTHealing = "healing"
|
storageRESTTotalVersions = "tvers"
|
||||||
storageRESTTotalVersions = "total-versions"
|
storageRESTSrcVolume = "svol"
|
||||||
storageRESTSrcVolume = "source-volume"
|
storageRESTSrcPath = "spath"
|
||||||
storageRESTSrcPath = "source-path"
|
storageRESTDstVolume = "dvol"
|
||||||
storageRESTDstVolume = "destination-volume"
|
storageRESTDstPath = "dpath"
|
||||||
storageRESTDstPath = "destination-path"
|
|
||||||
storageRESTOffset = "offset"
|
storageRESTOffset = "offset"
|
||||||
storageRESTLength = "length"
|
storageRESTLength = "length"
|
||||||
storageRESTCount = "count"
|
storageRESTCount = "count"
|
||||||
storageRESTBitrotAlgo = "bitrot-algo"
|
storageRESTBitrotAlgo = "balg"
|
||||||
storageRESTBitrotHash = "bitrot-hash"
|
storageRESTBitrotHash = "bhash"
|
||||||
storageRESTDiskID = "disk-id"
|
storageRESTDiskID = "did"
|
||||||
storageRESTForceDelete = "force-delete"
|
storageRESTForceDelete = "fdel"
|
||||||
storageRESTGlob = "glob"
|
storageRESTGlob = "glob"
|
||||||
storageRESTMetrics = "metrics"
|
storageRESTMetrics = "metrics"
|
||||||
storageRESTDriveQuorum = "drive-quorum"
|
storageRESTDriveQuorum = "dquorum"
|
||||||
storageRESTOrigVolume = "orig-volume"
|
storageRESTOrigVolume = "ovol"
|
||||||
storageRESTInclFreeVersions = "incl-free-versions"
|
storageRESTInclFreeVersions = "incl-fv"
|
||||||
|
storageRESTRange = "rng"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nsScannerOptions struct {
|
type nsScannerOptions struct {
|
||||||
|
|||||||
+54
-83
@@ -21,7 +21,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/gob"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -29,7 +28,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -110,7 +108,7 @@ func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error)
|
|||||||
const DefaultSkewTime = 15 * time.Minute
|
const DefaultSkewTime = 15 * time.Minute
|
||||||
|
|
||||||
// validateStorageRequestToken will validate the token against the provided audience.
|
// validateStorageRequestToken will validate the token against the provided audience.
|
||||||
func validateStorageRequestToken(token, audience string) error {
|
func validateStorageRequestToken(token string) error {
|
||||||
claims := xjwt.NewStandardClaims()
|
claims := xjwt.NewStandardClaims()
|
||||||
if err := xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
|
if err := xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
|
||||||
return errAuthentication
|
return errAuthentication
|
||||||
@@ -121,9 +119,6 @@ func validateStorageRequestToken(token, audience string) error {
|
|||||||
return errAuthentication
|
return errAuthentication
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims.Audience != audience {
|
|
||||||
return errAuthentication
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,20 +131,24 @@ func storageServerRequestValidate(r *http.Request) error {
|
|||||||
}
|
}
|
||||||
return errMalformedAuth
|
return errMalformedAuth
|
||||||
}
|
}
|
||||||
if err = validateStorageRequestToken(token, r.URL.RawQuery); err != nil {
|
|
||||||
|
if err = validateStorageRequestToken(token); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
requestTimeStr := r.Header.Get("X-Minio-Time")
|
nanoTime, err := strconv.ParseInt(r.Header.Get("X-Minio-Time"), 10, 64)
|
||||||
requestTime, err := time.Parse(time.RFC3339, requestTimeStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errMalformedAuth
|
return errMalformedAuth
|
||||||
}
|
}
|
||||||
utcNow := UTCNow()
|
|
||||||
delta := requestTime.Sub(utcNow)
|
localTime := UTCNow()
|
||||||
|
remoteTime := time.Unix(0, nanoTime)
|
||||||
|
|
||||||
|
delta := remoteTime.Sub(localTime)
|
||||||
if delta < 0 {
|
if delta < 0 {
|
||||||
delta *= -1
|
delta *= -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if delta > DefaultSkewTime {
|
if delta > DefaultSkewTime {
|
||||||
return errSkewedAuthTime
|
return errSkewedAuthTime
|
||||||
}
|
}
|
||||||
@@ -375,10 +374,6 @@ func (s *storageRESTServer) ReadVersionHandlerWS(params *grid.MSS) (*FileInfo, *
|
|||||||
volume := params.Get(storageRESTVolume)
|
volume := params.Get(storageRESTVolume)
|
||||||
filePath := params.Get(storageRESTFilePath)
|
filePath := params.Get(storageRESTFilePath)
|
||||||
versionID := params.Get(storageRESTVersionID)
|
versionID := params.Get(storageRESTVersionID)
|
||||||
readData, err := strconv.ParseBool(params.Get(storageRESTReadData))
|
|
||||||
if err != nil {
|
|
||||||
return nil, grid.NewRemoteErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
healing, err := strconv.ParseBool(params.Get(storageRESTHealing))
|
healing, err := strconv.ParseBool(params.Get(storageRESTHealing))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -392,7 +387,7 @@ func (s *storageRESTServer) ReadVersionHandlerWS(params *grid.MSS) (*FileInfo, *
|
|||||||
|
|
||||||
fi, err := s.getStorage().ReadVersion(context.Background(), origvolume, volume, filePath, versionID, ReadOptions{
|
fi, err := s.getStorage().ReadVersion(context.Background(), origvolume, volume, filePath, versionID, ReadOptions{
|
||||||
InclFreeVersions: inclFreeVersions,
|
InclFreeVersions: inclFreeVersions,
|
||||||
ReadData: readData,
|
ReadData: false,
|
||||||
Healing: healing,
|
Healing: healing,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -410,11 +405,6 @@ func (s *storageRESTServer) ReadVersionHandler(w http.ResponseWriter, r *http.Re
|
|||||||
volume := r.Form.Get(storageRESTVolume)
|
volume := r.Form.Get(storageRESTVolume)
|
||||||
filePath := r.Form.Get(storageRESTFilePath)
|
filePath := r.Form.Get(storageRESTFilePath)
|
||||||
versionID := r.Form.Get(storageRESTVersionID)
|
versionID := r.Form.Get(storageRESTVersionID)
|
||||||
readData, err := strconv.ParseBool(r.Form.Get(storageRESTReadData))
|
|
||||||
if err != nil {
|
|
||||||
s.writeErrorResponse(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
healing, err := strconv.ParseBool(r.Form.Get(storageRESTHealing))
|
healing, err := strconv.ParseBool(r.Form.Get(storageRESTHealing))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.writeErrorResponse(w, err)
|
s.writeErrorResponse(w, err)
|
||||||
@@ -429,7 +419,7 @@ func (s *storageRESTServer) ReadVersionHandler(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
fi, err := s.getStorage().ReadVersion(r.Context(), origvolume, volume, filePath, versionID, ReadOptions{
|
fi, err := s.getStorage().ReadVersion(r.Context(), origvolume, volume, filePath, versionID, ReadOptions{
|
||||||
InclFreeVersions: inclFreeVersions,
|
InclFreeVersions: inclFreeVersions,
|
||||||
ReadData: readData,
|
ReadData: true,
|
||||||
Healing: healing,
|
Healing: healing,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -506,15 +496,11 @@ func (s *storageRESTServer) ReadXLHandler(w http.ResponseWriter, r *http.Request
|
|||||||
if !s.IsValid(w, r) {
|
if !s.IsValid(w, r) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
volume := r.Form.Get(storageRESTVolume)
|
volume := r.Form.Get(storageRESTVolume)
|
||||||
filePath := r.Form.Get(storageRESTFilePath)
|
filePath := r.Form.Get(storageRESTFilePath)
|
||||||
readData, err := strconv.ParseBool(r.Form.Get(storageRESTReadData))
|
|
||||||
if err != nil {
|
|
||||||
s.writeErrorResponse(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rf, err := s.getStorage().ReadXL(r.Context(), volume, filePath, readData)
|
rf, err := s.getStorage().ReadXL(r.Context(), volume, filePath, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.writeErrorResponse(w, err)
|
s.writeErrorResponse(w, err)
|
||||||
return
|
return
|
||||||
@@ -528,14 +514,10 @@ func (s *storageRESTServer) ReadXLHandlerWS(params *grid.MSS) (*RawFileInfo, *gr
|
|||||||
if !s.checkID(params.Get(storageRESTDiskID)) {
|
if !s.checkID(params.Get(storageRESTDiskID)) {
|
||||||
return nil, grid.NewRemoteErr(errDiskNotFound)
|
return nil, grid.NewRemoteErr(errDiskNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
volume := params.Get(storageRESTVolume)
|
volume := params.Get(storageRESTVolume)
|
||||||
filePath := params.Get(storageRESTFilePath)
|
filePath := params.Get(storageRESTFilePath)
|
||||||
readData, err := strconv.ParseBool(params.Get(storageRESTReadData))
|
rf, err := s.getStorage().ReadXL(context.Background(), volume, filePath, false)
|
||||||
if err != nil {
|
|
||||||
return nil, grid.NewRemoteErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rf, err := s.getStorage().ReadXL(context.Background(), volume, filePath, readData)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, grid.NewRemoteErr(err)
|
return nil, grid.NewRemoteErr(err)
|
||||||
}
|
}
|
||||||
@@ -593,45 +575,24 @@ func (s *storageRESTServer) ReadFileStreamHandler(w http.ResponseWriter, r *http
|
|||||||
}
|
}
|
||||||
volume := r.Form.Get(storageRESTVolume)
|
volume := r.Form.Get(storageRESTVolume)
|
||||||
filePath := r.Form.Get(storageRESTFilePath)
|
filePath := r.Form.Get(storageRESTFilePath)
|
||||||
offset, err := strconv.Atoi(r.Form.Get(storageRESTOffset))
|
offset, err := strconv.ParseInt(r.Form.Get(storageRESTOffset), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.writeErrorResponse(w, err)
|
s.writeErrorResponse(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
length, err := strconv.Atoi(r.Form.Get(storageRESTLength))
|
length, err := strconv.ParseInt(r.Form.Get(storageRESTLength), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.writeErrorResponse(w, err)
|
s.writeErrorResponse(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(xhttp.ContentLength, strconv.Itoa(length))
|
rc, err := s.getStorage().ReadFileStream(r.Context(), volume, filePath, offset, length)
|
||||||
|
|
||||||
rc, err := s.getStorage().ReadFileStream(r.Context(), volume, filePath, int64(offset), int64(length))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.writeErrorResponse(w, err)
|
s.writeErrorResponse(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
rf, ok := w.(io.ReaderFrom)
|
|
||||||
if ok && runtime.GOOS != "windows" {
|
|
||||||
// Attempt to use splice/sendfile() optimization, A very specific behavior mentioned below is necessary.
|
|
||||||
// See https://github.com/golang/go/blob/f7c5cbb82087c55aa82081e931e0142783700ce8/src/net/sendfile_linux.go#L20
|
|
||||||
// Windows can lock up with this optimization, so we fall back to regular copy.
|
|
||||||
sr, ok := rc.(*sendFileReader)
|
|
||||||
if ok {
|
|
||||||
// Sendfile sends in 4MiB chunks per sendfile syscall which is more than enough
|
|
||||||
// for most setups.
|
|
||||||
_, err = rf.ReadFrom(sr.Reader)
|
|
||||||
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
|
|
||||||
storageLogIf(r.Context(), err)
|
|
||||||
}
|
|
||||||
if err == nil || !errors.Is(err, xhttp.ErrNotImplemented) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // Fallback to regular copy
|
|
||||||
|
|
||||||
_, err = xioutil.Copy(w, rc)
|
_, err = xioutil.Copy(w, rc)
|
||||||
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
|
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
|
||||||
storageLogIf(r.Context(), err)
|
storageLogIf(r.Context(), err)
|
||||||
@@ -667,12 +628,6 @@ func (s *storageRESTServer) DeleteFileHandler(p *DeleteFileHandlerParams) (grid.
|
|||||||
return grid.NewNPErr(s.getStorage().Delete(context.Background(), p.Volume, p.FilePath, p.Opts))
|
return grid.NewNPErr(s.getStorage().Delete(context.Background(), p.Volume, p.FilePath, p.Opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVersionsErrsResp - collection of delete errors
|
|
||||||
// for bulk version deletes
|
|
||||||
type DeleteVersionsErrsResp struct {
|
|
||||||
Errs []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteVersionsHandler - delete a set of a versions.
|
// DeleteVersionsHandler - delete a set of a versions.
|
||||||
func (s *storageRESTServer) DeleteVersionsHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *storageRESTServer) DeleteVersionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !s.IsValid(w, r) {
|
if !s.IsValid(w, r) {
|
||||||
@@ -697,21 +652,20 @@ func (s *storageRESTServer) DeleteVersionsHandler(w http.ResponseWriter, r *http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dErrsResp := &DeleteVersionsErrsResp{Errs: make([]error, totalVersions)}
|
|
||||||
|
|
||||||
setEventStreamHeaders(w)
|
|
||||||
encoder := gob.NewEncoder(w)
|
|
||||||
done := keepHTTPResponseAlive(w)
|
done := keepHTTPResponseAlive(w)
|
||||||
|
|
||||||
opts := DeleteOptions{}
|
opts := DeleteOptions{}
|
||||||
errs := s.getStorage().DeleteVersions(r.Context(), volume, versions, opts)
|
errs := s.getStorage().DeleteVersions(r.Context(), volume, versions, opts)
|
||||||
done(nil)
|
done(nil)
|
||||||
|
|
||||||
|
dErrsResp := &DeleteVersionsErrsResp{Errs: make([]string, totalVersions)}
|
||||||
for idx := range versions {
|
for idx := range versions {
|
||||||
if errs[idx] != nil {
|
if errs[idx] != nil {
|
||||||
dErrsResp.Errs[idx] = StorageErr(errs[idx].Error())
|
dErrsResp.Errs[idx] = errs[idx].Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
encoder.Encode(dErrsResp)
|
|
||||||
|
buf, _ := dErrsResp.MarshalMsg(nil)
|
||||||
|
w.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameDataHandler - renames a meta object and data dir to destination.
|
// RenameDataHandler - renames a meta object and data dir to destination.
|
||||||
@@ -1145,18 +1099,15 @@ func (s *storageRESTServer) VerifyFileHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setEventStreamHeaders(w)
|
|
||||||
encoder := gob.NewEncoder(w)
|
|
||||||
done := keepHTTPResponseAlive(w)
|
done := keepHTTPResponseAlive(w)
|
||||||
resp, err := s.getStorage().VerifyFile(r.Context(), volume, filePath, fi)
|
resp, err := s.getStorage().VerifyFile(r.Context(), volume, filePath, fi)
|
||||||
done(nil)
|
done(err)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.writeErrorResponse(w, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder.Encode(resp)
|
buf, _ := resp.MarshalMsg(nil)
|
||||||
|
w.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDiskFatalErrs(errs []error) error {
|
func checkDiskFatalErrs(errs []error) error {
|
||||||
@@ -1281,6 +1232,24 @@ func (s *storageRESTServer) StatInfoFile(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *storageRESTServer) DeleteBulkHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req DeleteBulkReq
|
||||||
|
mr := msgpNewReader(r.Body)
|
||||||
|
defer readMsgpReaderPoolPut(mr)
|
||||||
|
|
||||||
|
if err := req.DecodeMsg(mr); err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
volume := r.Form.Get(storageRESTVolume)
|
||||||
|
keepHTTPResponseAlive(w)(s.getStorage().DeleteBulk(r.Context(), volume, req.Paths...))
|
||||||
|
}
|
||||||
|
|
||||||
// ReadMultiple returns multiple files
|
// ReadMultiple returns multiple files
|
||||||
func (s *storageRESTServer) ReadMultiple(w http.ResponseWriter, r *http.Request) {
|
func (s *storageRESTServer) ReadMultiple(w http.ResponseWriter, r *http.Request) {
|
||||||
if !s.IsValid(w, r) {
|
if !s.IsValid(w, r) {
|
||||||
@@ -1335,6 +1304,7 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
|
|||||||
return collectInternodeStats(httpTraceHdrs(f))
|
return collectInternodeStats(httpTraceHdrs(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalLocalDrivesMap = make(map[string]StorageAPI)
|
||||||
globalLocalSetDrives = make([][][]StorageAPI, len(endpointServerPools))
|
globalLocalSetDrives = make([][][]StorageAPI, len(endpointServerPools))
|
||||||
for pool := range globalLocalSetDrives {
|
for pool := range globalLocalSetDrives {
|
||||||
globalLocalSetDrives[pool] = make([][]StorageAPI, endpointServerPools[pool].SetCount)
|
globalLocalSetDrives[pool] = make([][]StorageAPI, endpointServerPools[pool].SetCount)
|
||||||
@@ -1356,17 +1326,19 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
|
|||||||
|
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodHealth).HandlerFunc(h(server.HealthHandler))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodHealth).HandlerFunc(h(server.HealthHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodAppendFile).HandlerFunc(h(server.AppendFileHandler))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodAppendFile).HandlerFunc(h(server.AppendFileHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodReadVersion).HandlerFunc(h(server.ReadVersionHandler))
|
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodReadXL).HandlerFunc(h(server.ReadXLHandler))
|
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodCreateFile).HandlerFunc(h(server.CreateFileHandler))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodCreateFile).HandlerFunc(h(server.CreateFileHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodReadFile).HandlerFunc(h(server.ReadFileHandler))
|
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodReadFileStream).HandlerFunc(h(server.ReadFileStreamHandler))
|
|
||||||
|
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodDeleteVersions).HandlerFunc(h(server.DeleteVersionsHandler))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodDeleteVersions).HandlerFunc(h(server.DeleteVersionsHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodVerifyFile).HandlerFunc(h(server.VerifyFileHandler))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodVerifyFile).HandlerFunc(h(server.VerifyFileHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodStatInfoFile).HandlerFunc(h(server.StatInfoFile))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodStatInfoFile).HandlerFunc(h(server.StatInfoFile))
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodReadMultiple).HandlerFunc(h(server.ReadMultiple))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodReadMultiple).HandlerFunc(h(server.ReadMultiple))
|
||||||
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodCleanAbandoned).HandlerFunc(h(server.CleanAbandonedDataHandler))
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodCleanAbandoned).HandlerFunc(h(server.CleanAbandonedDataHandler))
|
||||||
|
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodDeleteBulk).HandlerFunc(h(server.DeleteBulkHandler))
|
||||||
|
|
||||||
|
subrouter.Methods(http.MethodGet).Path(storageRESTVersionPrefix + storageRESTMethodReadFileStream).HandlerFunc(h(server.ReadFileStreamHandler))
|
||||||
|
subrouter.Methods(http.MethodGet).Path(storageRESTVersionPrefix + storageRESTMethodReadVersion).HandlerFunc(h(server.ReadVersionHandler))
|
||||||
|
subrouter.Methods(http.MethodGet).Path(storageRESTVersionPrefix + storageRESTMethodReadXL).HandlerFunc(h(server.ReadXLHandler))
|
||||||
|
subrouter.Methods(http.MethodGet).Path(storageRESTVersionPrefix + storageRESTMethodReadFile).HandlerFunc(h(server.ReadFileHandler))
|
||||||
|
|
||||||
logger.FatalIf(storageListDirRPC.RegisterNoInput(gm, server.ListDirHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageListDirRPC.RegisterNoInput(gm, server.ListDirHandler, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageReadAllRPC.Register(gm, server.ReadAllHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageReadAllRPC.Register(gm, server.ReadAllHandler, endpoint.Path), "unable to register handler")
|
||||||
logger.FatalIf(storageWriteAllRPC.Register(gm, server.WriteAllHandler, endpoint.Path), "unable to register handler")
|
logger.FatalIf(storageWriteAllRPC.Register(gm, server.WriteAllHandler, endpoint.Path), "unable to register handler")
|
||||||
@@ -1408,7 +1380,6 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
|
|||||||
globalLocalDrivesMu.Lock()
|
globalLocalDrivesMu.Lock()
|
||||||
defer globalLocalDrivesMu.Unlock()
|
defer globalLocalDrivesMu.Unlock()
|
||||||
|
|
||||||
globalLocalDrives = append(globalLocalDrives, storage)
|
|
||||||
globalLocalDrivesMap[endpoint.String()] = storage
|
globalLocalDrivesMap[endpoint.String()] = storage
|
||||||
globalLocalSetDrives[endpoint.PoolIdx][endpoint.SetIdx][endpoint.DiskIdx] = storage
|
globalLocalSetDrives[endpoint.PoolIdx][endpoint.SetIdx][endpoint.DiskIdx] = storage
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ func newStorageRESTHTTPServerClient(t testing.TB) *storageRESTClient {
|
|||||||
url.Path = t.TempDir()
|
url.Path = t.TempDir()
|
||||||
|
|
||||||
globalMinioHost, globalMinioPort = mustSplitHostPort(url.Host)
|
globalMinioHost, globalMinioPort = mustSplitHostPort(url.Host)
|
||||||
|
globalNodeAuthToken, _ = authenticateNode(globalActiveCred.AccessKey, globalActiveCred.SecretKey)
|
||||||
|
|
||||||
endpoint, err := NewEndpoint(url.String())
|
endpoint, err := NewEndpoint(url.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -36,12 +36,13 @@ func _() {
|
|||||||
_ = x[storageMetricReadMultiple-25]
|
_ = x[storageMetricReadMultiple-25]
|
||||||
_ = x[storageMetricDeleteAbandonedParts-26]
|
_ = x[storageMetricDeleteAbandonedParts-26]
|
||||||
_ = x[storageMetricDiskInfo-27]
|
_ = x[storageMetricDiskInfo-27]
|
||||||
_ = x[storageMetricLast-28]
|
_ = x[storageMetricDeleteBulk-28]
|
||||||
|
_ = x[storageMetricLast-29]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _storageMetric_name = "MakeVolBulkMakeVolListVolsStatVolDeleteVolWalkDirListDirReadFileAppendFileCreateFileReadFileStreamRenameFileRenameDataCheckPartsDeleteDeleteVersionsVerifyFileWriteAllDeleteVersionWriteMetadataUpdateMetadataReadVersionReadXLReadAllStatInfoFileReadMultipleDeleteAbandonedPartsDiskInfoLast"
|
const _storageMetric_name = "MakeVolBulkMakeVolListVolsStatVolDeleteVolWalkDirListDirReadFileAppendFileCreateFileReadFileStreamRenameFileRenameDataCheckPartsDeleteDeleteVersionsVerifyFileWriteAllDeleteVersionWriteMetadataUpdateMetadataReadVersionReadXLReadAllStatInfoFileReadMultipleDeleteAbandonedPartsDiskInfoDeleteBulkLast"
|
||||||
|
|
||||||
var _storageMetric_index = [...]uint16{0, 11, 18, 26, 33, 42, 49, 56, 64, 74, 84, 98, 108, 118, 128, 134, 148, 158, 166, 179, 192, 206, 217, 223, 230, 242, 254, 274, 282, 286}
|
var _storageMetric_index = [...]uint16{0, 11, 18, 26, 33, 42, 49, 56, 64, 74, 84, 98, 108, 118, 128, 134, 148, 158, 166, 179, 192, 206, 217, 223, 230, 242, 254, 274, 282, 292, 296}
|
||||||
|
|
||||||
func (i storageMetric) String() string {
|
func (i storageMetric) String() string {
|
||||||
if i >= storageMetric(len(_storageMetric_index)-1) {
|
if i >= storageMetric(len(_storageMetric_index)-1) {
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ func TestMain(m *testing.M) {
|
|||||||
SecretKey: auth.DefaultSecretKey,
|
SecretKey: auth.DefaultSecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalNodeAuthToken, _ = authenticateNode(auth.DefaultAccessKey, auth.DefaultSecretKey)
|
||||||
|
|
||||||
// disable ENVs which interfere with tests.
|
// disable ENVs which interfere with tests.
|
||||||
for _, env := range []string{
|
for _, env := range []string{
|
||||||
crypto.EnvKMSAutoEncryption,
|
crypto.EnvKMSAutoEncryption,
|
||||||
@@ -100,7 +102,7 @@ func TestMain(m *testing.M) {
|
|||||||
// Disable printing console messages during tests.
|
// Disable printing console messages during tests.
|
||||||
color.Output = io.Discard
|
color.Output = io.Discard
|
||||||
// Disable Error logging in testing.
|
// Disable Error logging in testing.
|
||||||
logger.DisableErrorLog = true
|
logger.DisableLog = true
|
||||||
|
|
||||||
// Uncomment the following line to see trace logs during unit tests.
|
// Uncomment the following line to see trace logs during unit tests.
|
||||||
// logger.AddTarget(console.New())
|
// logger.AddTarget(console.New())
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const (
|
|||||||
storageMetricReadMultiple
|
storageMetricReadMultiple
|
||||||
storageMetricDeleteAbandonedParts
|
storageMetricDeleteAbandonedParts
|
||||||
storageMetricDiskInfo
|
storageMetricDiskInfo
|
||||||
|
storageMetricDeleteBulk
|
||||||
|
|
||||||
// .... add more
|
// .... add more
|
||||||
|
|
||||||
@@ -499,6 +500,16 @@ func (p *xlStorageDiskIDCheck) CheckParts(ctx context.Context, volume string, pa
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *xlStorageDiskIDCheck) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) {
|
||||||
|
ctx, done, err := p.TrackDiskHealth(ctx, storageMetricDeleteBulk, append([]string{volume}, paths...)...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer done(0, &err)
|
||||||
|
|
||||||
|
return p.storage.DeleteBulk(ctx, volume, paths...)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *xlStorageDiskIDCheck) Delete(ctx context.Context, volume string, path string, deleteOpts DeleteOptions) (err error) {
|
func (p *xlStorageDiskIDCheck) Delete(ctx context.Context, volume string, path string, deleteOpts DeleteOptions) (err error) {
|
||||||
ctx, done, err := p.TrackDiskHealth(ctx, storageMetricDelete, volume, path)
|
ctx, done, err := p.TrackDiskHealth(ctx, storageMetricDelete, volume, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+81
-45
@@ -1079,34 +1079,37 @@ func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
discard := true
|
|
||||||
|
|
||||||
var legacyJSON bool
|
|
||||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFile), discard)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, errFileNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.RLock()
|
s.RLock()
|
||||||
legacy := s.formatLegacy
|
legacy := s.formatLegacy
|
||||||
s.RUnlock()
|
s.RUnlock()
|
||||||
if legacy {
|
|
||||||
buf, _, err = s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFileV1), discard)
|
var legacyJSON bool
|
||||||
|
buf, err := xioutil.WithDeadline[[]byte](ctx, globalDriveConfig.GetMaxTimeout(), func(ctx context.Context) ([]byte, error) {
|
||||||
|
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFile))
|
||||||
|
if err != nil && !errors.Is(err, errFileNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, errFileNotFound) && legacy {
|
||||||
|
buf, _, err = s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFileV1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
legacyJSON = true
|
legacyJSON = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
if errors.Is(err, errFileNotFound) && !skipAccessChecks(volume) {
|
if errors.Is(err, errFileNotFound) && !skipAccessChecks(volume) {
|
||||||
if aerr := Access(volumeDir); aerr != nil && osIsNotExist(aerr) {
|
if aerr := Access(volumeDir); aerr != nil && osIsNotExist(aerr) {
|
||||||
return errVolumeNotFound
|
return nil, errVolumeNotFound
|
||||||
|
}
|
||||||
|
return nil, errFileNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errFileNotFound
|
return buf, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if legacyJSON {
|
if legacyJSON {
|
||||||
@@ -1180,10 +1183,7 @@ func (s *xlStorage) DeleteVersions(ctx context.Context, volume string, versions
|
|||||||
errs[i] = ctx.Err()
|
errs[i] = ctx.Err()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
errs[i] = s.deleteVersions(ctx, volume, fiv.Name, fiv.Versions...)
|
||||||
if err := w.Run(func() error { return s.deleteVersions(ctx, volume, fiv.Name, fiv.Versions...) }); err != nil {
|
|
||||||
errs[i] = err
|
|
||||||
}
|
|
||||||
diskHealthCheckOK(ctx, errs[i])
|
diskHealthCheckOK(ctx, errs[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1214,7 +1214,7 @@ func (s *xlStorage) diskAlmostFilled() bool {
|
|||||||
return (float64(info.Free)/float64(info.Used)) < almostFilledPercent || (float64(info.FreeInodes)/float64(info.UsedInodes)) < almostFilledPercent
|
return (float64(info.Free)/float64(info.Used)) < almostFilledPercent || (float64(info.FreeInodes)/float64(info.UsedInodes)) < almostFilledPercent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *xlStorage) moveToTrash(filePath string, recursive, immediatePurge bool) (err error) {
|
func (s *xlStorage) moveToTrashNoDeadline(filePath string, recursive, immediatePurge bool) (err error) {
|
||||||
pathUUID := mustGetUUID()
|
pathUUID := mustGetUUID()
|
||||||
targetPath := pathutil.Join(s.drivePath, minioMetaTmpDeletedBucket, pathUUID)
|
targetPath := pathutil.Join(s.drivePath, minioMetaTmpDeletedBucket, pathUUID)
|
||||||
|
|
||||||
@@ -1267,10 +1267,16 @@ func (s *xlStorage) moveToTrash(filePath string, recursive, immediatePurge bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *xlStorage) moveToTrash(filePath string, recursive, immediatePurge bool) (err error) {
|
||||||
|
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
||||||
|
return w.Run(func() (err error) {
|
||||||
|
return s.moveToTrashNoDeadline(filePath, recursive, immediatePurge)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteVersion - deletes FileInfo metadata for path at `xl.meta`. forceDelMarker
|
// DeleteVersion - deletes FileInfo metadata for path at `xl.meta`. forceDelMarker
|
||||||
// will force creating a new `xl.meta` to create a new delete marker
|
// will force creating a new `xl.meta` to create a new delete marker
|
||||||
func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool, opts DeleteOptions) (err error) {
|
func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool, opts DeleteOptions) (err error) {
|
||||||
@@ -1293,7 +1299,7 @@ func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi F
|
|||||||
}
|
}
|
||||||
|
|
||||||
var legacyJSON bool
|
var legacyJSON bool
|
||||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFile), true)
|
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errFileNotFound) {
|
if !errors.Is(err, errFileNotFound) {
|
||||||
return err
|
return err
|
||||||
@@ -1558,7 +1564,7 @@ func (s *xlStorage) readRaw(ctx context.Context, volume, volumeDir, filePath str
|
|||||||
|
|
||||||
xlPath := pathJoin(filePath, xlStorageFormatFile)
|
xlPath := pathJoin(filePath, xlStorageFormatFile)
|
||||||
if readData {
|
if readData {
|
||||||
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, xlPath, false)
|
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, xlPath)
|
||||||
} else {
|
} else {
|
||||||
buf, dmTime, err = s.readMetadataWithDMTime(ctx, xlPath)
|
buf, dmTime, err = s.readMetadataWithDMTime(ctx, xlPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1578,7 +1584,7 @@ func (s *xlStorage) readRaw(ctx context.Context, volume, volumeDir, filePath str
|
|||||||
s.RUnlock()
|
s.RUnlock()
|
||||||
|
|
||||||
if err != nil && errors.Is(err, errFileNotFound) && legacy {
|
if err != nil && errors.Is(err, errFileNotFound) && legacy {
|
||||||
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFileV1), false)
|
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFileV1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, time.Time{}, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
@@ -1703,26 +1709,31 @@ func (s *xlStorage) ReadVersion(ctx context.Context, origvolume, volume, path, v
|
|||||||
fi.Data = nil
|
fi.Data = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attemptInline := fi.TransitionStatus == "" && fi.DataDir != "" && len(fi.Parts) == 1
|
||||||
// Reading data for small objects when
|
// Reading data for small objects when
|
||||||
// - object has not yet transitioned
|
// - object has not yet transitioned
|
||||||
// - object size lesser than 128KiB
|
|
||||||
// - object has maximum of 1 parts
|
// - object has maximum of 1 parts
|
||||||
if fi.TransitionStatus == "" &&
|
if attemptInline {
|
||||||
fi.DataDir != "" && fi.Size <= smallFileThreshold &&
|
inlineBlock := globalStorageClass.InlineBlock()
|
||||||
len(fi.Parts) == 1 {
|
if inlineBlock <= 0 {
|
||||||
partPath := fmt.Sprintf("part.%d", fi.Parts[0].Number)
|
inlineBlock = 128 * humanize.KiByte
|
||||||
dataPath := pathJoin(volumeDir, path, fi.DataDir, partPath)
|
}
|
||||||
fi.Data, _, err = s.readAllData(ctx, volume, volumeDir, dataPath, false)
|
|
||||||
|
canInline := fi.ShardFileSize(fi.Parts[0].ActualSize) <= inlineBlock
|
||||||
|
if canInline {
|
||||||
|
dataPath := pathJoin(volumeDir, path, fi.DataDir, fmt.Sprintf("part.%d", fi.Parts[0].Number))
|
||||||
|
fi.Data, _, err = s.readAllData(ctx, volume, volumeDir, dataPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FileInfo{}, err
|
return FileInfo{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fi, nil
|
return fi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, filePath string, discard bool) (buf []byte, dmTime time.Time, err error) {
|
func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, filePath string) (buf []byte, dmTime time.Time, err error) {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, dmTime, errFileNotFound
|
return nil, dmTime, errFileNotFound
|
||||||
}
|
}
|
||||||
@@ -1766,14 +1777,6 @@ func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, f
|
|||||||
}
|
}
|
||||||
return nil, dmTime, err
|
return nil, dmTime, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if discard {
|
|
||||||
// This discard is mostly true for DELETEEs
|
|
||||||
// so we need to make sure we do not keep
|
|
||||||
// page-cache references after.
|
|
||||||
defer disk.Fdatasync(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Get size for precise allocation.
|
// Get size for precise allocation.
|
||||||
@@ -1825,7 +1828,7 @@ func (s *xlStorage) ReadAll(ctx context.Context, volume string, path string) (bu
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, _, err = s.readAllData(ctx, volume, volumeDir, filePath, false)
|
buf, _, err = s.readAllData(ctx, volume, volumeDir, filePath)
|
||||||
return buf, err
|
return buf, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2062,7 +2065,6 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &sendFileReader{Reader: io.LimitReader(file, length), Closer: file}, nil
|
return &sendFileReader{Reader: io.LimitReader(file, length), Closer: file}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2423,7 +2425,41 @@ func (s *xlStorage) deleteFile(basePath, deletePath string, recursive, immediate
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFile - delete a file at path.
|
// DeleteBulk - delete many files in bulk to trash.
|
||||||
|
// this delete does not recursively delete empty
|
||||||
|
// parents, if you need empty parent delete support
|
||||||
|
// please use Delete() instead. This API is meant as
|
||||||
|
// an optimization for Multipart operations.
|
||||||
|
func (s *xlStorage) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) {
|
||||||
|
volumeDir, err := s.getVolDir(volume)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipAccessChecks(volume) {
|
||||||
|
// Stat a volume entry.
|
||||||
|
if err = Access(volumeDir); err != nil {
|
||||||
|
return convertAccessError(err, errVolumeAccessDenied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fp := range paths {
|
||||||
|
// Following code is needed so that we retain SlashSeparator suffix if any in
|
||||||
|
// path argument.
|
||||||
|
filePath := pathJoin(volumeDir, fp)
|
||||||
|
if err = checkPathLength(filePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.moveToTrash(filePath, false, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete - delete a file at path.
|
||||||
func (s *xlStorage) Delete(ctx context.Context, volume string, path string, deleteOpts DeleteOptions) (err error) {
|
func (s *xlStorage) Delete(ctx context.Context, volume string, path string, deleteOpts DeleteOptions) (err error) {
|
||||||
volumeDir, err := s.getVolDir(volume)
|
volumeDir, err := s.getVolDir(volume)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2993,7 +3029,7 @@ func (s *xlStorage) ReadMultiple(ctx context.Context, req ReadMultipleReq, resp
|
|||||||
if req.MetadataOnly {
|
if req.MetadataOnly {
|
||||||
data, mt, err = s.readMetadataWithDMTime(ctx, fullPath)
|
data, mt, err = s.readMetadataWithDMTime(ctx, fullPath)
|
||||||
} else {
|
} else {
|
||||||
data, mt, err = s.readAllData(ctx, req.Bucket, volumeDir, fullPath, true)
|
data, mt, err = s.readAllData(ctx, req.Bucket, volumeDir, fullPath)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -3096,7 +3132,7 @@ func (s *xlStorage) CleanAbandonedData(ctx context.Context, volume string, path
|
|||||||
}
|
}
|
||||||
baseDir := pathJoin(volumeDir, path+slashSeparator)
|
baseDir := pathJoin(volumeDir, path+slashSeparator)
|
||||||
metaPath := pathutil.Join(baseDir, xlStorageFormatFile)
|
metaPath := pathutil.Join(baseDir, xlStorageFormatFile)
|
||||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, metaPath, true)
|
buf, _, err := s.readAllData(ctx, volume, volumeDir, metaPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build windows || darwin || freebsd
|
//go:build !unix || darwin || freebsd
|
||||||
// +build windows darwin freebsd
|
|
||||||
|
|
||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !windows && !darwin && !freebsd
|
//go:build unix && !darwin && !freebsd
|
||||||
// +build !windows,!darwin,!freebsd
|
|
||||||
|
|
||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||||
//
|
//
|
||||||
@@ -22,12 +21,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Disallow updating access times
|
// Disallow updating access times
|
||||||
readMode = os.O_RDONLY | 0x40000 // O_NOATIME
|
// Add non-block to avoid syscall to attempt to set epoll on files.
|
||||||
|
readMode = os.O_RDONLY | 0x40000 | syscall.O_NONBLOCK // O_NOATIME
|
||||||
|
|
||||||
// Write with data sync only used only for `xl.meta` writes
|
// Write with data sync only used only for `xl.meta` writes
|
||||||
writeMode = 0x1000 // O_DSYNC
|
writeMode = 0x1000 | syscall.O_NONBLOCK // O_DSYNC
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2530,7 +2530,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"unit": "bytes"
|
"unit": "none"
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -157,9 +157,9 @@ For deployments with [bucket](https://min.io/docs/minio/linux/administration/buc
|
|||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
|:-----------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------|
|
|:-----------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `minio_notify_current_send_in_progress` | Number of concurrent async Send calls active to all targets (deprecated, please use 'minio_notify_target_current_send_in_progress' instead) |
|
| `minio_notify_current_send_in_progress` | Number of concurrent async Send calls active to all targets (deprecated, please use `minio_notify_target_current_send_in_progress` instead) |
|
||||||
| `minio_notify_events_errors_total` | Events that were failed to be sent to the targets (deprecated, please use 'minio_notify_target_failed_events' instead) |
|
| `minio_notify_events_errors_total` | Events that were failed to be sent to the targets (deprecated, please use `minio_notify_target_failed_events` instead) |
|
||||||
| `minio_notify_events_sent_total` | Total number of events sent to the targets (deprecated, please use 'minio_notify_target_total_events' instead) |
|
| `minio_notify_events_sent_total` | Total number of events sent to the targets (deprecated, please use `minio_notify_target_total_events` instead) |
|
||||||
| `minio_notify_events_skipped_total` | Events that were skipped to be sent to the targets due to the in-memory queue being full |
|
| `minio_notify_events_skipped_total` | Events that were skipped to be sent to the targets due to the in-memory queue being full |
|
||||||
| `minio_notify_target_current_send_in_progress` | Number of concurrent async Send calls active to the target |
|
| `minio_notify_target_current_send_in_progress` | Number of concurrent async Send calls active to the target |
|
||||||
| `minio_notify_target_queue_length` | Number of events currently staged in the queue_dir configured for the target. |
|
| `minio_notify_target_queue_length` | Number of events currently staged in the queue_dir configured for the target. |
|
||||||
@@ -254,7 +254,7 @@ For deployments with [bucket](https://min.io/docs/minio/linux/administration/buc
|
|||||||
| `minio_node_io_read_bytes` | Total bytes read by the process from the underlying storage system, /proc/[pid]/io read_bytes. |
|
| `minio_node_io_read_bytes` | Total bytes read by the process from the underlying storage system, /proc/[pid]/io read_bytes. |
|
||||||
| `minio_node_io_wchar_bytes` | Total bytes written by the process to the underlying storage system including page cache, /proc/[pid]/io wchar. |
|
| `minio_node_io_wchar_bytes` | Total bytes written by the process to the underlying storage system including page cache, /proc/[pid]/io wchar. |
|
||||||
| `minio_node_io_write_bytes` | Total bytes written by the process to the underlying storage system, /proc/[pid]/io write_bytes. |
|
| `minio_node_io_write_bytes` | Total bytes written by the process to the underlying storage system, /proc/[pid]/io write_bytes. |
|
||||||
| `minio_node_process_cpu_total_seconds` | Total user and system CPU time spent in seconds. |
|
| `minio_node_process_cpu_total_seconds` | Total user and system CPU time spent in seconds by the process. |
|
||||||
| `minio_node_process_resident_memory_bytes` | Resident memory size in bytes. |
|
| `minio_node_process_resident_memory_bytes` | Resident memory size in bytes. |
|
||||||
| `minio_node_process_virtual_memory_bytes` | Virtual memory size in bytes. |
|
| `minio_node_process_virtual_memory_bytes` | Virtual memory size in bytes. |
|
||||||
| `minio_node_process_starttime_seconds` | Start time for MinIO process per node, time in seconds since Unix epoc. |
|
| `minio_node_process_starttime_seconds` | Start time for MinIO process per node, time in seconds since Unix epoc. |
|
||||||
|
|||||||
+340
-320
@@ -1,397 +1,417 @@
|
|||||||
# Metrics Version 3
|
# Metrics Version 3
|
||||||
|
|
||||||
In metrics version 3, all metrics are available under the endpoint:
|
In metrics version 3, all metrics are available under the following base endpoint:
|
||||||
|
|
||||||
```
|
```
|
||||||
/minio/metrics/v3
|
/minio/metrics/v3
|
||||||
```
|
```
|
||||||
|
|
||||||
however, a specific path under this is required.
|
To query metrics of a specific type, append the appropriate path to the base endpoint.
|
||||||
|
Querying the base endpoint returns "404 Not Found."
|
||||||
|
|
||||||
Metrics are organized into groups at paths **relative** to the top-level endpoint above.
|
Metrics are organized into groups at paths **relative** to the top-level endpoint above.
|
||||||
|
|
||||||
|
Metrics are also available using the [MinIO Admin Client](https://min.io/docs/minio/linux/reference/minio-mc-admin.html) and the `mc admin prometheus metrics` command. For more information, see [Metrics and Alerts](https://min.io/docs/minio/linux/operations/monitoring/metrics-and-alerts.html) in the MinIO Documentation.
|
||||||
|
|
||||||
## Metrics Request Handling
|
## Metrics Request Handling
|
||||||
|
|
||||||
Each endpoint below can be queried at different intervals as needed via a scrape configuration in Prometheus or a compatible metrics collection tool.
|
Each endpoint can be queried as needed via a scrape configuration in Prometheus or a compatible metrics collection tool. You should schedule scrape operations so a prior scrape completes before the next one begins.
|
||||||
|
|
||||||
For ease of configuration, each (non-empty) parent of the path serves all metric endpoints that are at descendant paths. For example, to query all system metrics one needs to only scrape `/minio/metrics/v3/system/`.
|
For ease of configuration, each (non-empty) parent of the path serves all the metric endpoints at its child paths. For example, to query all system metrics scrape `/minio/metrics/v3/system/`.
|
||||||
|
|
||||||
Some metrics are bucket specific. These will have a `/bucket` component in their path. As the number of buckets can be large, the metrics scrape operation needs to be provided with a specific list of buckets via the `bucket` query parameter. Only metrics for the given buckets will be returned (with the bucket label set). For example to query API metrics for buckets `test1` and `test2`, make a scrape request to `/minio/metrics/v3/api/bucket?buckets=test1,test2`.
|
Each metric endpoint may support multiple child endpoints. For example, the `/v3/system/` metric has multiple child groups as `/v3/system/<child>`. Querying the parent endpoint returns metrics for all child groups. Querying a child group returns only metrics for that child.
|
||||||
|
|
||||||
Instead of a metrics scrape, it is also possible to list the metrics that would be returned by a path. This is done by adding a `?list` query parameter. The MinIO server will then list all possible metrics that could be returned. During an actual metrics scrape, only available metrics are returned - not all of them. With the `list` query parameter, the output format can be selected - just set the request `Content-Type` to `application/json` for JSON output, or `text/plain` for a simple markdown formatted table. The latter is the default.
|
### Per-bucket Metrics
|
||||||
|
|
||||||
## Request, System and Cluster Metrics
|
Metrics with a `/bucket` component in the path return results for each specified bucket in the deployment. These endpoints **require** providing a list of buckets as a `bucket` query parameter. The endpoint then returns only metrics for the given buckets, with the bucket name in a `bucket` label.
|
||||||
|
|
||||||
At a high level metrics are grouped into three categories, listed in the following sub-sections. The path in each of the tables is relative to the top-level endpoint.
|
For example, to query API metrics for buckets `test1` and `test2`, make a scrape request to `/minio/metrics/v3/api/bucket?buckets=test1,test2`.
|
||||||
|
|
||||||
|
### List Available Metrics
|
||||||
|
|
||||||
|
Instead of a metrics scrape, you can list the metrics that would be returned by a path by adding a `list` query parameter. The MinIO server then lists all available metrics that could be returned. Note that during an actual metrics scrape only metrics with available _values_ are returned. Metrics with null values are omitted from the scrape results.
|
||||||
|
|
||||||
|
To set the output format, set the request `Content-Type` to the desired format. Accepted values are `application/json` for JSON output or `text/plain` for a Markdown-formatted table. The default is Markdown.
|
||||||
|
|
||||||
|
For example, the the following returns a list of all available bucket metrics:
|
||||||
|
```
|
||||||
|
/minio/metrics/v3/api/bucket?list
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metric Categories
|
||||||
|
|
||||||
|
At a high level, metrics are grouped into categories as described in the following sections. The path in each of the tables is relative to the top-level endpoint. Note that the standard GoCollector metrics are not shown.
|
||||||
|
|
||||||
### Request metrics
|
### Request metrics
|
||||||
|
|
||||||
These are metrics about requests served by the (current) node.
|
Metrics about requests served by the current node.
|
||||||
|
|
||||||
| Path | Description |
|
| Path | Description |
|
||||||
|-----------------|--------------------------------------------------|
|
|-----------------|-----------------------------------------------|
|
||||||
| `/api/requests` | Metrics over all requests |
|
| `/api/requests` | Metrics over all requests. |
|
||||||
| `/bucket/api` | Metrics over all requests for a given bucket |
|
| `/bucket/api` | Metrics over all requests for a given bucket. |
|
||||||
| | |
|
|
||||||
|
#### `/api/requests`
|
||||||
|
|
||||||
|
| Name | Description | Labels |
|
||||||
|
|------------------------------------------------|--------------------------------------------------------------------------------|----------------------------------------------|
|
||||||
|
| `minio_api_requests_rejected_auth_total` | Total number of requests rejected for auth failure. <br><br>Type: counter | `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_rejected_header_total` | Total number of requests rejected for invalid header. <br><br>Type: counter | `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_rejected_timestamp_total` | Total number of requests rejected for invalid timestamp. <br><br>Type: counter | `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_rejected_invalid_total` | Total number of invalid requests. <br><br>Type: counter | `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_waiting_total` | Total number of requests in the waiting queue. <br><br>Type: gauge | `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_incoming_total` | Total number of incoming requests. <br><br>Type: gauge | `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_inflight_total` | Total number of requests currently in flight. <br><br>Type: gauge | `name`, `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_total` | Total number of requests. <br><br>Type: counter | `name`, `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_errors_total` | Total number of requests with 4xx or 5xx errors. <br><br>Type: counter | `name`, `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_5xx_errors_total` | Total number of requests with 5xx errors. <br><br>Type: counter | `name`, `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_4xx_errors_total` | Total number of requests with 4xx errors. <br><br>Type: counter | `name`, `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_canceled_total` | Total number of requests canceled by the client. <br><br>Type: counter | `name`, `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_ttfb_seconds_distribution` | Distribution of time to first byte across API calls. <br><br>Type: counter | `name`, `type`, `le`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_traffic_sent_bytes` | Total number of bytes sent. <br><br>Type: counter | `type`, `pool_index`, `server` |
|
||||||
|
| `minio_api_requests_traffic_received_bytes` | Total number of bytes received. <br><br>Type: counter | `type`, `pool_index`, `server` |
|
||||||
|
|
||||||
|
#### `/bucket/api`
|
||||||
|
|
||||||
|
| Name | Description | Labels |
|
||||||
|
|----------------------------------------------|-----------------------------------------------------------------------------------------|--------------------------------------------------------|
|
||||||
|
| `minio_bucket_api_traffic_received_bytes` | Total number of bytes sent for a bucket. <br><br>Type: counter | `bucket`, `type`, `server`, `pool_index` |
|
||||||
|
| `minio_bucket_api_traffic_sent_bytes` | Total number of bytes received for a bucket. <br><br>Type: counter | `bucket`, `type`, `server`, `pool_index` |
|
||||||
|
| `minio_bucket_api_inflight_total` | Total number of requests currently in flight for a bucket. <br><br>Type: gauge | `bucket`, `name`, `type`, `server`, `pool_index` |
|
||||||
|
| `minio_bucket_api_total` | Total number of requests for a bucket. <br><br>Type: counter | `bucket`, `name`, `type`, `server`, `pool_index` |
|
||||||
|
| `minio_bucket_api_canceled_total` | Total number of requests canceled by the client for a bucket. <br><br>Type: counter | `bucket`, `name`, `type`, `server`, `pool_index` |
|
||||||
|
| `minio_bucket_api_4xx_errors_total` | Total number of requests with 4xx errors for a bucket. <br><br>Type: counter | `bucket`, `name`, `type`, `server`, `pool_index` |
|
||||||
|
| `minio_bucket_api_5xx_errors_total` | Total number of requests with 5xx errors for a bucket. <br><br>Type: counter | `bucket`, `name`, `type`, `server`, `pool_index` |
|
||||||
|
| `minio_bucket_api_ttfb_seconds_distribution` | Distribution of time to first byte across API calls for a bucket. <br><br>Type: counter | `bucket`, `name`, `le`, `type`, `server`, `pool_index` |
|
||||||
|
|
||||||
### Audit metrics
|
### Audit metrics
|
||||||
|
|
||||||
These are metrics about the minio audit functionality
|
Metrics about the MinIO audit functionality.
|
||||||
|
|
||||||
| Path | Description |
|
| Path | Description |
|
||||||
|----------|----------------------------------------|
|
|----------|-----------------------------------------|
|
||||||
| `/audit` | Metrics related to audit functionality |
|
| `/audit` | Metrics related to audit functionality. |
|
||||||
|
|
||||||
### ILM metrics
|
#### `/audit`
|
||||||
|
|
||||||
These are metrics about the minio ILM functionality
|
| Name | Description | Labels |
|
||||||
|
|-----------------------------------|---------------------------------------------------------------------------------|-----------------------|
|
||||||
| Path | Description |
|
| `minio_audit_failed_messages` | Total number of messages that failed to send since start. <br><br>Type: counter | `target_id`, `server` |
|
||||||
|--------|--------------------------------------|
|
| `minio_audit_target_queue_length` | Number of unsent messages in queue for target. <br><br>Type: gauge | `target_id`, `server` |
|
||||||
| `/ilm` | Metrics related to ILM functionality |
|
| `minio_audit_total_messages` | Total number of messages sent since start. <br><br>Type: counter | `target_id`, `server` |
|
||||||
|
|
||||||
### Logger webhook metrics
|
|
||||||
|
|
||||||
These are metrics about the minio logger webhooks
|
|
||||||
|
|
||||||
| Path | Description |
|
|
||||||
|-------------------|------------------------------------|
|
|
||||||
| `/logger/webhook` | Metrics related to logger webhooks |
|
|
||||||
|
|
||||||
### Notification metrics
|
|
||||||
|
|
||||||
These are metrics about the minio notification functionality
|
|
||||||
|
|
||||||
| Path | Description |
|
|
||||||
|----------|------------------------------------------------------|
|
|
||||||
| `/notification` | Metrics related to notification functionality |
|
|
||||||
|
|
||||||
### Scanner metrics
|
|
||||||
|
|
||||||
These are metrics about the minio scanner
|
|
||||||
|
|
||||||
| Path | Description |
|
|
||||||
|------------|--------------------------------------|
|
|
||||||
| `/scanner` | Metrics related to the MinIO scanner |
|
|
||||||
|
|
||||||
### System metrics
|
|
||||||
|
|
||||||
These are metrics about the minio process and the node.
|
|
||||||
|
|
||||||
| Path | Description |
|
|
||||||
|-----------------------------|---------------------------------------------------|
|
|
||||||
| `/system/drive` | Metrics about drives on the system |
|
|
||||||
| `/system/memory` | Metrics about memory on the system |
|
|
||||||
| `/system/network/internode` | Metrics about internode requests made by the node |
|
|
||||||
| `/system/process` | Standard process metrics |
|
|
||||||
| | |
|
|
||||||
|
|
||||||
### Debug metrics
|
|
||||||
|
|
||||||
These are metrics for debugging
|
|
||||||
|
|
||||||
| Path | Description |
|
|
||||||
|-----------------------------|---------------------------------------------------|
|
|
||||||
| `/debug/go` | Standard Go lang metrics |
|
|
||||||
| | |
|
|
||||||
|
|
||||||
### Cluster metrics
|
### Cluster metrics
|
||||||
|
|
||||||
These present metrics about the whole MinIO cluster.
|
Metrics about an entire MinIO cluster.
|
||||||
|
|
||||||
| Path | Description |
|
| Path | Description |
|
||||||
|--------------------------|-----------------------------|
|
|--------------------------|--------------------------------|
|
||||||
| `/cluster/health` | Cluster health metrics |
|
| `/cluster/config` | Cluster configuration metrics. |
|
||||||
| `/cluster/usage/objects` | Object statistics |
|
| `/cluster/erasure-set` | Erasure set metrics. |
|
||||||
| `/cluster/usage/buckets` | Object statistics by bucket |
|
| `/cluster/health` | Cluster health metrics. |
|
||||||
| `/cluster/erasure-set` | Erasure set metrics |
|
| `/cluster/iam` | Cluster iam metrics. |
|
||||||
| | |
|
| `/cluster/usage/buckets` | Object statistics by bucket. |
|
||||||
|
| `/cluster/usage/objects` | Object statistics. |
|
||||||
|
|
||||||
## Metrics Listing
|
#### `/cluster/config`
|
||||||
|
|
||||||
Each of the following sub-sections list metrics returned by each of the endpoints.
|
| Name | Description | Labels |
|
||||||
|
|----------------------------------------|--------------------------------------------------------------|--------|
|
||||||
|
| `minio_cluster_config_rrs_parity` | Reduced redundancy storage class parity. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_config_standard_parity` | Standard storage class parity. <br><br>Type: gauge | |
|
||||||
|
|
||||||
The standard metrics group for GoCollector is not shown below.
|
#### `/cluster/erasure-set`
|
||||||
|
|
||||||
### `/api/requests`
|
| Name | Description | Labels |
|
||||||
|
|--------------------------------------------------|---------------------------------------------------------------------------------------------------------|---------------------|
|
||||||
|
| `minio_cluster_erasure_set_overall_write_quorum` | Overall write quorum across pools and sets. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_erasure_set_overall_health` | Overall health across pools and sets (1=healthy, 0=unhealthy). <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_erasure_set_read_quorum` | Read quorum for the erasure set in a pool. <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_write_quorum` | Write quorum for the erasure set in a pool. <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_online_drives_count` | Count of online drives in the erasure set in a pool. <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_healing_drives_count` | Count of healing drives in the erasure set in a pool. <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_health` | Health of the erasure set in a pool (1=healthy, 0=unhealthy). <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_read_tolerance` | Number of drive failures that can be tolerated without disrupting read operations. <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_write_tolerance` | Number of drive failures that can be tolerated without disrupting write operations. <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_read_health` | Health of the erasure set in a pool for read operations (1=healthy, 0=unhealthy). <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
| `minio_cluster_erasure_set_write_health` | Health of the erasure set in a pool for write operations (1=healthy, 0=unhealthy). <br><br>Type: gauge | `pool_id`, `set_id` |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
#### `/cluster/health`
|
||||||
|------------------------------------------------|-----------|---------------------------------------------------------|----------------------------------|
|
|
||||||
| `minio_api_requests_rejected_auth_total` | `counter` | Total number of requests rejected for auth failure | `type,pool_index,server` |
|
|
||||||
| `minio_api_requests_rejected_header_total` | `counter` | Total number of requests rejected for invalid header | `type,pool_index,server` |
|
|
||||||
| `minio_api_requests_rejected_timestamp_total` | `counter` | Total number of requests rejected for invalid timestamp | `type,pool_index,server` |
|
|
||||||
| `minio_api_requests_rejected_invalid_total` | `counter` | Total number of invalid requests | `type,pool_index,server` |
|
|
||||||
| `minio_api_requests_waiting_total` | `gauge` | Total number of requests in the waiting queue | `type,pool_index,server` |
|
|
||||||
| `minio_api_requests_incoming_total` | `gauge` | Total number of incoming requests | `type,pool_index,server` |
|
|
||||||
| `minio_api_requests_inflight_total` | `gauge` | Total number of requests currently in flight | `name,type,pool_index,server` |
|
|
||||||
| `minio_api_requests_total` | `counter` | Total number of requests | `name,type,pool_index,server` |
|
|
||||||
| `minio_api_requests_errors_total` | `counter` | Total number of requests with (4xx and 5xx) errors | `name,type,pool_index,server` |
|
|
||||||
| `minio_api_requests_5xx_errors_total` | `counter` | Total number of requests with 5xx errors | `name,type,pool_index,server` |
|
|
||||||
| `minio_api_requests_4xx_errors_total` | `counter` | Total number of requests with 4xx errors | `name,type,pool_index,server` |
|
|
||||||
| `minio_api_requests_canceled_total` | `counter` | Total number of requests canceled by the client | `name,type,pool_index,server` |
|
|
||||||
| `minio_api_requests_ttfb_seconds_distribution` | `counter` | Distribution of time to first byte across API calls | `name,type,le,pool_index,server` |
|
|
||||||
| `minio_api_requests_traffic_sent_bytes` | `counter` | Total number of bytes sent | `type,pool_index,server` |
|
|
||||||
| `minio_api_requests_traffic_received_bytes` | `counter` | Total number of bytes received | `type,pool_index,server` |
|
|
||||||
|
|
||||||
### `/bucket/api`
|
| Name | Description | Labels |
|
||||||
|
|----------------------------------------------------|---------------------------------------------------------------------|--------|
|
||||||
|
| `minio_cluster_health_drives_offline_count` | Count of offline drives in the cluster. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_drives_online_count` | Count of online drives in the cluster. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_drives_count` | Count of all drives in the cluster. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_nodes_offline_count` | Count of offline nodes in the cluster. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_nodes_online_count` | Count of online nodes in the cluster. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_capacity_raw_total_bytes` | Total cluster raw storage capacity in bytes. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_capacity_raw_free_bytes` | Total cluster raw storage free in bytes. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_capacity_usable_total_bytes` | Total cluster usable storage capacity in bytes. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_health_capacity_usable_free_bytes` | Total cluster usable storage free in bytes. <br><br>Type: gauge | |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
#### `/cluster/iam`
|
||||||
|----------------------------------------------|-----------|------------------------------------------------------------------|-----------------------------------------|
|
|
||||||
| `minio_bucket_api_traffic_received_bytes` | `counter` | Total number of bytes sent for a bucket | `bucket,type,server,pool_index` |
|
|
||||||
| `minio_bucket_api_traffic_sent_bytes` | `counter` | Total number of bytes received for a bucket | `bucket,type,server,pool_index` |
|
|
||||||
| `minio_bucket_api_inflight_total` | `gauge` | Total number of requests currently in flight for a bucket | `bucket,name,type,server,pool_index` |
|
|
||||||
| `minio_bucket_api_total` | `counter` | Total number of requests for a bucket | `bucket,name,type,server,pool_index` |
|
|
||||||
| `minio_bucket_api_canceled_total` | `counter` | Total number of requests canceled by the client for a bucket | `bucket,name,type,server,pool_index` |
|
|
||||||
| `minio_bucket_api_4xx_errors_total` | `counter` | Total number of requests with 4xx errors for a bucket | `bucket,name,type,server,pool_index` |
|
|
||||||
| `minio_bucket_api_5xx_errors_total` | `counter` | Total number of requests with 5xx errors for a bucket | `bucket,name,type,server,pool_index` |
|
|
||||||
| `minio_bucket_api_ttfb_seconds_distribution` | `counter` | Distribution of time to first byte across API calls for a bucket | `bucket,name,le,type,server,pool_index` |
|
|
||||||
|
|
||||||
### `/bucket/replication`
|
| Name | Description | Labels |
|
||||||
|
|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|
|
||||||
|
| `minio_cluster_iam_last_sync_duration_millis` | Last successful IAM data sync duration in milliseconds. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_plugin_authn_service_failed_requests_minute` | When plugin authentication is configured, returns failed requests count in the last full minute. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_plugin_authn_service_last_fail_seconds` | When plugin authentication is configured, returns time (in seconds) since the last failed request to the service. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_plugin_authn_service_last_succ_seconds` | When plugin authentication is configured, returns time (in seconds) since the last successful request to the service. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_plugin_authn_service_succ_avg_rtt_ms_minute` | When plugin authentication is configured, returns average round-trip time of successful requests in the last full minute. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_plugin_authn_service_succ_max_rtt_ms_minute` | When plugin authentication is configured, returns maximum round-trip time of successful requests in the last full minute. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_plugin_authn_service_total_requests_minute` | When plugin authentication is configured, returns total requests count in the last full minute. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_since_last_sync_millis` | Time (in milliseconds) since last successful IAM data sync. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_sync_failures` | Number of failed IAM data syncs since server start. <br><br>Type: counter | |
|
||||||
|
| `minio_cluster_iam_sync_successes` | Number of successful IAM data syncs since server start. <br><br>Type: counter | |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
#### `/cluster/usage/buckets`
|
||||||
|---------------------------------------------------------------------|-----------|---------------------------------------------------------------------------------------------|-------------------------------------------|
|
|
||||||
| `minio_bucket_replication_last_hour_failed_bytes` | `gauge` | Total number of bytes failed at least once to replicate in the last hour on a bucket | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_last_hour_failed_count` | `gauge` | Total number of objects which failed replication in the last hour on a bucket | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_last_minute_failed_bytes` | `gauge` | Total number of bytes failed at least once to replicate in the last full minute on a bucket | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_last_minute_failed_count` | `gauge` | Total number of objects which failed replication in the last full minute on a bucket | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_latency_ms` | `gauge` | Replication latency on a bucket in milliseconds | `bucket,operation,range,targetArn,server` |
|
|
||||||
| `minio_bucket_replication_proxied_delete_tagging_requests_total` | `counter` | Number of DELETE tagging requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_get_requests_failures` | `counter` | Number of failures in GET requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_get_requests_total` | `counter` | Number of GET requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_get_tagging_requests_failures` | `counter` | Number of failures in GET tagging requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_get_tagging_requests_total` | `counter` | Number of GET tagging requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_head_requests_failures` | `counter` | Number of failures in HEAD requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_head_requests_total` | `counter` | Number of HEAD requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_put_tagging_requests_failures` | `counter` | Number of failures in PUT tagging requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_put_tagging_requests_total` | `counter` | Number of PUT tagging requests proxied to replication target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_sent_bytes` | `counter` | Total number of bytes replicated to the target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_sent_count` | `counter` | Total number of objects replicated to the target | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_total_failed_bytes` | `counter` | Total number of bytes failed at least once to replicate since server start | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_total_failed_count` | `counter` | Total number of objects which failed replication since server start | `bucket,server` |
|
|
||||||
| `minio_bucket_replication_proxied_delete_tagging_requests_failures` | `counter` | Number of failures in DELETE tagging requests proxied to replication target | `bucket,server` |
|
|
||||||
|
|
||||||
### `/audit`
|
| Name | Description | Labels |
|
||||||
|
|-----------------------------------------------------------------|--------------------------------------------------------------------------------------|-------------------|
|
||||||
|
| `minio_cluster_usage_buckets_since_last_update_seconds` | Time since last update of usage metrics in seconds. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_usage_buckets_total_bytes` | Total bucket size in bytes. <br><br>Type: gauge | `bucket` |
|
||||||
|
| `minio_cluster_usage_buckets_objects_count` | Total object count in bucket. <br><br>Type: gauge | `bucket` |
|
||||||
|
| `minio_cluster_usage_buckets_versions_count` | Total object versions count in bucket, including delete markers. <br><br>Type: gauge | `bucket` |
|
||||||
|
| `minio_cluster_usage_buckets_delete_markers_count` | Total delete markers count in bucket. <br><br>Type: gauge | `bucket` |
|
||||||
|
| `minio_cluster_usage_buckets_quota_total_bytes` | Total bucket quota in bytes. <br><br>Type: gauge | `bucket` |
|
||||||
|
| `minio_cluster_usage_buckets_object_size_distribution` | Bucket object size distribution. <br><br>Type: gauge | `range`, `bucket` |
|
||||||
|
| `minio_cluster_usage_buckets_object_version_count_distribution` | Bucket object version count distribution. <br><br>Type: gauge | `range`, `bucket` |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
#### `/cluster/usage/objects`
|
||||||
|-----------------------------------|-----------|----------------------------------------------------------|--------------------|
|
|
||||||
| `minio_audit_failed_messages` | `counter` | Total number of messages that failed to send since start | `target_id,server` |
|
|
||||||
| `minio_audit_target_queue_length` | `gauge` | Number of unsent messages in queue for target | `target_id,server` |
|
|
||||||
| `minio_audit_total_messages` | `counter` | Total number of messages sent since start | `target_id,server` |
|
|
||||||
|
|
||||||
### `/system/drive`
|
| Name | Description | Labels |
|
||||||
|
|----------------------------------------------------------|------------------------------------------------------------------------------------|---------|
|
||||||
|
| `minio_cluster_usage_objects_since_last_update_seconds` | Time since last update of usage metrics in seconds. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_usage_objects_total_bytes` | Total cluster usage in bytes. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_usage_objects_count` | Total cluster objects count. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_usage_objects_versions_count` | Total cluster object versions count, including delete markers. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_usage_objects_delete_markers_count` | Total cluster delete markers count. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_usage_objects_buckets_count` | Total cluster buckets count. <br><br>Type: gauge | |
|
||||||
|
| `minio_cluster_usage_objects_size_distribution` | Cluster object size distribution. <br><br>Type: gauge | `range` |
|
||||||
|
| `minio_cluster_usage_objects_version_count_distribution` | Cluster object version count distribution. <br><br>Type: gauge | `range` |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
### Debug metrics
|
||||||
|------------------------------------------------|-----------|--------------------------------------------------------------------|-----------------------------------------------------|
|
|
||||||
| `minio_system_drive_used_bytes` | `gauge` | Total storage used on a drive in bytes | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_free_bytes` | `gauge` | Total storage free on a drive in bytes | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_total_bytes` | `gauge` | Total storage available on a drive in bytes | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_used_inodes` | `gauge` | Total used inodes on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_free_inodes` | `gauge` | Total free inodes on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_total_inodes` | `gauge` | Total inodes available on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_timeout_errors_total` | `counter` | Total timeout errors on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_io_errors_total` | `counter` | Total I/O errors on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_availability_errors_total` | `counter` | Total availability errors (I/O errors, timeouts) on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_waiting_io` | `gauge` | Total waiting I/O operations on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_api_latency_micros` | `gauge` | Average last minute latency in µs for drive API storage operations | `drive,api,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_offline_count` | `gauge` | Count of offline drives | `pool_index,server` |
|
|
||||||
| `minio_system_drive_online_count` | `gauge` | Count of online drives | `pool_index,server` |
|
|
||||||
| `minio_system_drive_count` | `gauge` | Count of all drives | `pool_index,server` |
|
|
||||||
| `minio_system_drive_health` | `gauge` | Drive health (0 = offline, 1 = healthy, 2 = healing) | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_reads_per_sec` | `gauge` | Reads per second on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_reads_kb_per_sec` | `gauge` | Kilobytes read per second on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_reads_await` | `gauge` | Average time for read requests served on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_writes_per_sec` | `gauge` | Writes per second on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_writes_kb_per_sec` | `gauge` | Kilobytes written per second on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_writes_await` | `gauge` | Average time for write requests served on a drive | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
| `minio_system_drive_perc_util` | `gauge` | Percentage of time the disk was busy | `drive,set_index,drive_index,pool_index,server` |
|
|
||||||
|
|
||||||
### `/system/memory`
|
Standard Go runtime metrics.
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
| Path | Description |
|
||||||
|----------------------------------|---------|------------------------------------|----------|
|
|-------------|---------------------|
|
||||||
| `minio_system_memory_used` | `gauge` | Used memory on the node | `server` |
|
| `/debug/go` | Go runtime metrics. |
|
||||||
| `minio_system_memory_used_perc` | `gauge` | Used memory percentage on the node | `server` |
|
|
||||||
| `minio_system_memory_free` | `gauge` | Free memory on the node | `server` |
|
|
||||||
| `minio_system_memory_total` | `gauge` | Total memory on the node | `server` |
|
|
||||||
| `minio_system_memory_buffers` | `gauge` | Buffers memory on the node | `server` |
|
|
||||||
| `minio_system_memory_cache` | `gauge` | Cache memory on the node | `server` |
|
|
||||||
| `minio_system_memory_shared` | `gauge` | Shared memory on the node | `server` |
|
|
||||||
| `minio_system_memory_available` | `gauge` | Available memory on the node | `server` |
|
|
||||||
|
|
||||||
### `/system/cpu`
|
### ILM metrics
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
Metrics about the MinIO ILM functionality.
|
||||||
|-------------------------------|---------|------------------------------------|----------|
|
|
||||||
| `minio_system_cpu_avg_idle` | `gauge` | Average CPU idle time | `server` |
|
|
||||||
| `minio_system_cpu_avg_iowait` | `gauge` | Average CPU IOWait time | `server` |
|
|
||||||
| `minio_system_cpu_load` | `gauge` | CPU load average 1min | `server` |
|
|
||||||
| `minio_system_cpu_load_perc` | `gauge` | CPU load average 1min (percentage) | `server` |
|
|
||||||
| `minio_system_cpu_nice` | `gauge` | CPU nice time | `server` |
|
|
||||||
| `minio_system_cpu_steal` | `gauge` | CPU steal time | `server` |
|
|
||||||
| `minio_system_cpu_system` | `gauge` | CPU system time | `server` |
|
|
||||||
| `minio_system_cpu_user` | `gauge` | CPU user time | `server` |
|
|
||||||
|
|
||||||
### `/system/network/internode`
|
| Path | Description |
|
||||||
|
|--------|---------------------------------------|
|
||||||
|
| `/ilm` | Metrics related to ILM functionality. |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
#### `/ilm`
|
||||||
|------------------------------------------------------|-----------|----------------------------------------------------------|---------------------|
|
|
||||||
| `minio_system_network_internode_errors_total` | `counter` | Total number of failed internode calls | `server,pool_index` |
|
|
||||||
| `minio_system_network_internode_dial_errors_total` | `counter` | Total number of internode TCP dial timeouts and errors | `server,pool_index` |
|
|
||||||
| `minio_system_network_internode_dial_avg_time_nanos` | `gauge` | Average dial time of internodes TCP calls in nanoseconds | `server,pool_index` |
|
|
||||||
| `minio_system_network_internode_sent_bytes_total` | `counter` | Total number of bytes sent to other peer nodes | `server,pool_index` |
|
|
||||||
| `minio_system_network_internode_recv_bytes_total` | `counter` | Total number of bytes received from other peer nodes | `server,pool_index` |
|
|
||||||
|
|
||||||
### `/system/process`
|
| Name | Description | Labels |
|
||||||
|
|-------------------------------------------------------|---------------------------------------------------------------------------------------------------|----------|
|
||||||
|
| `minio_cluster_ilm_expiry_pending_tasks` | Number of pending ILM expiry tasks in the queue. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_cluster_ilm_transition_active_tasks` | Number of active ILM transition tasks. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_cluster_ilm_transition_pending_tasks` | Number of pending ILM transition tasks in the queue. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_cluster_ilm_transition_missed_immediate_tasks` | Number of missed immediate ILM transition tasks. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_cluster_ilm_versions_scanned` | Total number of object versions checked for ILM actions since server start. <br><br>Type: counter | `server` |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
### Logger webhook metrics
|
||||||
|----------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------------|----------|
|
|
||||||
| `minio_system_process_locks_read_total` | `gauge` | Number of current READ locks on this peer | `server` |
|
|
||||||
| `minio_system_process_locks_write_total` | `gauge` | Number of current WRITE locks on this peer | `server` |
|
|
||||||
| `minio_system_process_cpu_total_seconds` | `counter` | Total user and system CPU time spent in seconds | `server` |
|
|
||||||
| `minio_system_process_go_routine_total` | `gauge` | Total number of go routines running | `server` |
|
|
||||||
| `minio_system_process_io_rchar_bytes` | `counter` | Total bytes read by the process from the underlying storage system including cache, /proc/[pid]/io rchar | `server` |
|
|
||||||
| `minio_system_process_io_read_bytes` | `counter` | Total bytes read by the process from the underlying storage system, /proc/[pid]/io read_bytes | `server` |
|
|
||||||
| `minio_system_process_io_wchar_bytes` | `counter` | Total bytes written by the process to the underlying storage system including page cache, /proc/[pid]/io wchar | `server` |
|
|
||||||
| `minio_system_process_io_write_bytes` | `counter` | Total bytes written by the process to the underlying storage system, /proc/[pid]/io write_bytes | `server` |
|
|
||||||
| `minio_system_process_start_time_seconds` | `gauge` | Start time for MinIO process in seconds since Unix epoc | `server` |
|
|
||||||
| `minio_system_process_uptime_seconds` | `gauge` | Uptime for MinIO process in seconds | `server` |
|
|
||||||
| `minio_system_process_file_descriptor_limit_total` | `gauge` | Limit on total number of open file descriptors for the MinIO Server process | `server` |
|
|
||||||
| `minio_system_process_file_descriptor_open_total` | `gauge` | Total number of open file descriptors by the MinIO Server process | `server` |
|
|
||||||
| `minio_system_process_syscall_read_total` | `counter` | Total read SysCalls to the kernel. /proc/[pid]/io syscr | `server` |
|
|
||||||
| `minio_system_process_syscall_write_total` | `counter` | Total write SysCalls to the kernel. /proc/[pid]/io syscw | `server` |
|
|
||||||
| `minio_system_process_resident_memory_bytes` | `gauge` | Resident memory size in bytes | `server` |
|
|
||||||
| `minio_system_process_virtual_memory_bytes` | `gauge` | Virtual memory size in bytes | `server` |
|
|
||||||
| `minio_system_process_virtual_memory_max_bytes` | `gauge` | Maximum virtual memory size in bytes | `server` |
|
|
||||||
|
|
||||||
### `/cluster/health`
|
Metrics about MinIO logger webhooks.
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
| Path | Description |
|
||||||
|----------------------------------------------------|---------|------------------------------------------------|--------|
|
|-------------------|-------------------------------------|
|
||||||
| `minio_cluster_health_drives_offline_count` | `gauge` | Count of offline drives in the cluster | |
|
| `/logger/webhook` | Metrics related to logger webhooks. |
|
||||||
| `minio_cluster_health_drives_online_count` | `gauge` | Count of online drives in the cluster | |
|
|
||||||
| `minio_cluster_health_drives_count` | `gauge` | Count of all drives in the cluster | |
|
|
||||||
| `minio_cluster_health_nodes_offline_count` | `gauge` | Count of offline nodes in the cluster | |
|
|
||||||
| `minio_cluster_health_nodes_online_count` | `gauge` | Count of online nodes in the cluster | |
|
|
||||||
| `minio_cluster_health_capacity_raw_total_bytes` | `gauge` | Total cluster raw storage capacity in bytes | |
|
|
||||||
| `minio_cluster_health_capacity_raw_free_bytes` | `gauge` | Total cluster raw storage free in bytes | |
|
|
||||||
| `minio_cluster_health_capacity_usable_total_bytes` | `gauge` | Total cluster usable storage capacity in bytes | |
|
|
||||||
| `minio_cluster_health_capacity_usable_free_bytes` | `gauge` | Total cluster usable storage free in bytes | |
|
|
||||||
|
|
||||||
### `/cluster/config`
|
#### `/logger/webhook`
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
| Name | Description | Labels |
|
||||||
|----------------------------------------|---------|------------------------------------------------|--------|
|
|-----------------------------------------|---------------------------------------------------------------------|------------------------------|
|
||||||
| `minio_cluster_config_rrs_parity` | `gauge` | Reduced redundancy storage class parity | |
|
| `minio_logger_webhook_failed_messages` | Number of messages that failed to send. <br><br>Type: counter | `server`, `name`, `endpoint` |
|
||||||
| `minio_cluster_config_standard_parity` | `gauge` | Standard storage class parity | |
|
| `minio_logger_webhook_queue_length` | Webhook queue length. <br><br>Type: gauge | `server`, `name`, `endpoint` |
|
||||||
|
| `minio_logger_webhook_total_message` | Total number of messages sent to this target. <br><br>Type: counter | `server`, `name`, `endpoint` |
|
||||||
|
|
||||||
### `/cluster/usage/objects`
|
### Notification metrics
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
Metrics about the MinIO notification functionality.
|
||||||
|----------------------------------------------------------|---------|----------------------------------------------------------------|---------|
|
|
||||||
| `minio_cluster_usage_objects_since_last_update_seconds` | `gauge` | Time since last update of usage metrics in seconds | |
|
|
||||||
| `minio_cluster_usage_objects_total_bytes` | `gauge` | Total cluster usage in bytes | |
|
|
||||||
| `minio_cluster_usage_objects_count` | `gauge` | Total cluster objects count | |
|
|
||||||
| `minio_cluster_usage_objects_versions_count` | `gauge` | Total cluster object versions (including delete markers) count | |
|
|
||||||
| `minio_cluster_usage_objects_delete_markers_count` | `gauge` | Total cluster delete markers count | |
|
|
||||||
| `minio_cluster_usage_objects_buckets_count` | `gauge` | Total cluster buckets count | |
|
|
||||||
| `minio_cluster_usage_objects_size_distribution` | `gauge` | Cluster object size distribution | `range` |
|
|
||||||
| `minio_cluster_usage_objects_version_count_distribution` | `gauge` | Cluster object version count distribution | `range` |
|
|
||||||
|
|
||||||
### `/cluster/usage/buckets`
|
| Path | Description |
|
||||||
|
|-----------------|------------------------------------------------|
|
||||||
|
| `/notification` | Metrics related to notification functionality. |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
#### `/notification`
|
||||||
|-----------------------------------------------------------------|---------|------------------------------------------------------------------|----------------|
|
|
||||||
| `minio_cluster_usage_buckets_since_last_update_seconds` | `gauge` | Time since last update of usage metrics in seconds | |
|
|
||||||
| `minio_cluster_usage_buckets_total_bytes` | `gauge` | Total bucket size in bytes | `bucket` |
|
|
||||||
| `minio_cluster_usage_buckets_objects_count` | `gauge` | Total objects count in bucket | `bucket` |
|
|
||||||
| `minio_cluster_usage_buckets_versions_count` | `gauge` | Total object versions (including delete markers) count in bucket | `bucket` |
|
|
||||||
| `minio_cluster_usage_buckets_delete_markers_count` | `gauge` | Total delete markers count in bucket | `bucket` |
|
|
||||||
| `minio_cluster_usage_buckets_quota_total_bytes` | `gauge` | Total bucket quota in bytes | `bucket` |
|
|
||||||
| `minio_cluster_usage_buckets_object_size_distribution` | `gauge` | Bucket object size distribution | `range,bucket` |
|
|
||||||
| `minio_cluster_usage_buckets_object_version_count_distribution` | `gauge` | Bucket object version count distribution | `range,bucket` |
|
|
||||||
|
|
||||||
### `/cluster/erasure-set`
|
| Name | Description | Labels |
|
||||||
|
|-----------------------------------------------|-------------------------------------------------------------------------------------------------------|----------|
|
||||||
|
| `minio_notification_current_send_in_progress` | Number of concurrent async Send calls active to all targets. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_notification_events_errors_total` | Total number of events that failed to send to the targets. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_notification_events_sent_total` | Total number of events sent to the targets. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_notification_events_skipped_total` | Number of events not sent to the targets due to the in-memory queue being full. <br><br>Type: counter | `server` |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
### Replication metrics
|
||||||
|--------------------------------------------------|---------|-----------------------------------------------------------------------------------|------------------|
|
|
||||||
| `minio_cluster_erasure_set_overall_write_quorum` | `gauge` | Overall write quorum across pools and sets | |
|
|
||||||
| `minio_cluster_erasure_set_overall_health` | `gauge` | Overall health across pools and sets (1=healthy, 0=unhealthy) | |
|
|
||||||
| `minio_cluster_erasure_set_read_quorum` | `gauge` | Read quorum for the erasure set in a pool | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_write_quorum` | `gauge` | Write quorum for the erasure set in a pool | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_online_drives_count` | `gauge` | Count of online drives in the erasure set in a pool | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_healing_drives_count` | `gauge` | Count of healing drives in the erasure set in a pool | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_health` | `gauge` | Health of the erasure set in a pool (1=healthy, 0=unhealthy) | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_read_tolerance` | `gauge` | No of drive failures that can be tolerated without disrupting read operations | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_write_tolerance` | `gauge` | No of drive failures that can be tolerated without disrupting write operations | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_read_health` | `gauge` | Health of the erasure set in a pool for read operations (1=healthy, 0=unhealthy) | `pool_id,set_id` |
|
|
||||||
| `minio_cluster_erasure_set_write_health` | `gauge` | Health of the erasure set in a pool for write operations (1=healthy, 0=unhealthy) | `pool_id,set_id` |
|
|
||||||
|
|
||||||
### `/cluster/iam`
|
Metrics about MinIO site and bucket replication.
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
| Path | Description |
|
||||||
|-----------------------------------------------------------------|-----------|--------------------------------------------------------------------------------------------------------------------------|--------|
|
|-----------------------|----------------------------------------|
|
||||||
| `minio_cluster_iam_last_sync_duration_millis` | `counter` | Last successful IAM data sync duration in milliseconds | |
|
| `/bucket/replication` | Metrics related to bucket replication. |
|
||||||
| `minio_cluster_iam_plugin_authn_service_failed_requests_minute` | `counter` | When plugin authentication is configured, returns failed requests count in the last full minute | |
|
| `/replication` | Metrics related to site replication. |
|
||||||
| `minio_cluster_iam_plugin_authn_service_last_fail_seconds` | `counter` | When plugin authentication is configured, returns time (in seconds) since the last failed request to the service | |
|
|
||||||
| `minio_cluster_iam_plugin_authn_service_last_succ_seconds` | `counter` | When plugin authentication is configured, returns time (in seconds) since the last successful request to the service | |
|
|
||||||
| `minio_cluster_iam_plugin_authn_service_succ_avg_rtt_ms_minute` | `counter` | When plugin authentication is configured, returns average round-trip-time of successful requests in the last full minute | |
|
|
||||||
| `minio_cluster_iam_plugin_authn_service_succ_max_rtt_ms_minute` | `counter` | When plugin authentication is configured, returns maximum round-trip-time of successful requests in the last full minute | |
|
|
||||||
| `minio_cluster_iam_plugin_authn_service_total_requests_minute` | `counter` | When plugin authentication is configured, returns total requests count in the last full minute | |
|
|
||||||
| `minio_cluster_iam_since_last_sync_millis` | `counter` | Time (in milliseconds) since last successful IAM data sync | |
|
|
||||||
| `minio_cluster_iam_sync_failures` | `counter` | Number of failed IAM data syncs since server start | |
|
|
||||||
| `minio_cluster_iam_sync_successes` | `counter` | Number of successful IAM data syncs since server start | |
|
|
||||||
|
|
||||||
### `/logger/webhook`
|
#### `/replication`
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
| Name | Description | Labels |
|
||||||
|-----------------------------------------|-----------|----------------------------------------------|------------------------|
|
|---------------------------------------------------|---------------------------------------------------------------------------------------------|----------|
|
||||||
| `minio_logger_webhook_failed_messages` | `counter` | Number of messages that failed to send | `server,name,endpoint` |
|
| `minio_replication_average_active_workers` | Average number of active replication workers. <br><br>Type: gauge | `server` |
|
||||||
| `minio_logger_webhook_queue_length` | `gauge` | Webhook queue length | `server,name,endpoint` |
|
| `minio_replication_average_queued_bytes` | Average number of bytes queued for replication since server start. <br><br>Type: gauge | `server` |
|
||||||
| `minio_logger_webhook_total_message` | `counter` | Total number of messages sent to this target | `server,name,endpoint` |
|
| `minio_replication_average_queued_count` | Average number of objects queued for replication since server start. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_average_data_transfer_rate` | Average replication data transfer rate in bytes/sec. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_current_active_workers` | Total number of active replication workers. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_current_data_transfer_rate` | Current replication data transfer rate in bytes/sec. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_last_minute_queued_bytes` | Number of bytes queued for replication in the last full minute. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_last_minute_queued_count` | Number of objects queued for replication in the last full minute. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_max_active_workers` | Maximum number of active replication workers seen since server start. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_max_queued_bytes` | Maximum number of bytes queued for replication since server start. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_max_queued_count` | Maximum number of objects queued for replication since server start. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_replication_max_data_transfer_rate` | Maximum replication data transfer rate in bytes/sec since server start. <br><br>Type: gauge | `server` |
|
||||||
|
|
||||||
### `/replication`
|
#### `/bucket/replication`
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
| Name | Description | Labels |
|
||||||
|---------------------------------------------------|---------|-----------------------------------------------------------------------------|----------|
|
|---------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|-------------------------------------------------------|
|
||||||
| `minio_replication_average_active_workers` | `gauge` | Average number of active replication workers | `server` |
|
| `minio_bucket_replication_last_hour_failed_bytes` | Total number of bytes on a bucket which failed to replicate at least once in the last hour. <br><br>Type: gauge | `bucket`, `server` |
|
||||||
| `minio_replication_average_queued_bytes` | `gauge` | Average number of bytes queued for replication since server start | `server` |
|
| `minio_bucket_replication_last_hour_failed_count` | Total number of objects on a bucket which failed to replicate in the last hour. <br><br>Type: gauge | `bucket`, `server` |
|
||||||
| `minio_replication_average_queued_count` | `gauge` | Average number of objects queued for replication since server start | `server` |
|
| `minio_bucket_replication_last_minute_failed_bytes` | Total number of bytes on a bucket which failed at least once in the last full minute. <br><br>Type: gauge | `bucket`, `server` |
|
||||||
| `minio_replication_average_data_transfer_rate` | `gauge` | Average replication data transfer rate in bytes/sec | `server` |
|
| `minio_bucket_replication_last_minute_failed_count` | Total number of objects on a bucket which failed to replicate in the last full minute. <br><br>Type: gauge | `bucket`, `server` |
|
||||||
| `minio_replication_current_active_workers` | `gauge` | Total number of active replication workers | `server` |
|
| `minio_bucket_replication_latency_ms` | Replication latency on a bucket in milliseconds. <br><br>Type: gauge | `bucket`, `operation`, `range`, `targetArn`, `server` |
|
||||||
| `minio_replication_current_data_transfer_rate` | `gauge` | Current replication data transfer rate in bytes/sec | `server` |
|
| `minio_bucket_replication_proxied_delete_tagging_requests_total` | Number of DELETE tagging requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
| `minio_replication_last_minute_queued_bytes` | `gauge` | Number of bytes queued for replication in the last full minute | `server` |
|
| `minio_bucket_replication_proxied_get_requests_failures` | Number of failures in GET requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
| `minio_replication_last_minute_queued_count` | `gauge` | Number of objects queued for replication in the last full minute | `server` |
|
| `minio_bucket_replication_proxied_get_requests_total` | Number of GET requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
| `minio_replication_max_active_workers` | `gauge` | Maximum number of active replication workers seen since server start | `server` |
|
| `minio_bucket_replication_proxied_get_tagging_requests_failures` | Number of failures in GET tagging requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
| `minio_replication_max_queued_bytes` | `gauge` | Maximum number of bytes queued for replication since server start | `server` |
|
| `minio_bucket_replication_proxied_get_tagging_requests_total` | Number of GET tagging requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
| `minio_replication_max_queued_count` | `gauge` | Maximum number of objects queued for replication since server start | `server` |
|
| `minio_bucket_replication_proxied_head_requests_failures` | Number of failures in HEAD requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
| `minio_replication_max_data_transfer_rate` | `gauge` | Maximum replication data transfer rate in bytes/sec seen since server start | `server` |
|
| `minio_bucket_replication_proxied_head_requests_total` | Number of HEAD requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
| `minio_bucket_replication_proxied_put_tagging_requests_failures` | Number of failures in PUT tagging requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
| `minio_bucket_replication_proxied_put_tagging_requests_total` | Number of PUT tagging requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
| `minio_bucket_replication_sent_bytes` | Total number of bytes replicated to the target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
| `minio_bucket_replication_sent_count` | Total number of objects replicated to the target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
| `minio_bucket_replication_total_failed_bytes` | Total number of bytes failed to replicate at least once since server start. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
| `minio_bucket_replication_total_failed_count` | Total number of objects that failed to replicate since server start. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
| `minio_bucket_replication_proxied_delete_tagging_requests_failures` | Number of failures in DELETE tagging requests proxied to replication target. <br><br>Type: counter | `bucket`, `server` |
|
||||||
|
|
||||||
### `/notification`
|
### Scanner metrics
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
Metrics about the MinIO scanner.
|
||||||
|-----------------------------------------------|-----------|------------------------------------------------------------------------------------------|----------|
|
|
||||||
| `minio_notification_current_send_in_progress` | `counter` | Number of concurrent async Send calls active to all targets | `server` |
|
|
||||||
| `minio_notification_events_errors_total` | `counter` | Events that were failed to be sent to the targets | `server` |
|
|
||||||
| `minio_notification_events_sent_total` | `counter` | Total number of events sent to the targets | `server` |
|
|
||||||
| `minio_notification_events_skipped_total` | `counter` | Events that were skipped to be sent to the targets due to the in-memory queue being full | `server` |
|
|
||||||
|
|
||||||
### `/scanner`
|
| Path | Description |
|
||||||
|
|------------|---------------------------------------|
|
||||||
|
| `/scanner` | Metrics related to the MinIO scanner. |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
#### `/scanner`
|
||||||
|--------------------------------------------|-----------|------------------------------------------------------------|----------|
|
|
||||||
| `minio_scanner_bucket_scans_finished` | `counter` | Total number of bucket scans finished since server start | `server` |
|
|
||||||
| `minio_scanner_bucket_scans_started` | `counter` | Total number of bucket scans started since server start | `server` |
|
|
||||||
| `minio_scanner_directories_scanned` | `counter` | Total number of directories scanned since server start | `server` |
|
|
||||||
| `minio_scanner_last_activity_seconds` | `gauge` | Time elapsed (in seconds) since last scan activity | `server` |
|
|
||||||
| `minio_scanner_objects_scanned` | `counter` | Total number of unique objects scanned since server start | `server` |
|
|
||||||
| `minio_scanner_versions_scanned` | `counter` | Total number of object versions scanned since server start | `server` |
|
|
||||||
|
|
||||||
### `/ilm`
|
| Name | Description | Labels |
|
||||||
|
|--------------------------------------------|-----------------------------------------------------------------------------------|----------|
|
||||||
|
| `minio_scanner_bucket_scans_finished` | Total number of bucket scans completed since server start. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_scanner_bucket_scans_started` | Total number of bucket scans started since server start. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_scanner_directories_scanned` | Total number of directories scanned since server start. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_scanner_last_activity_seconds` | Time elapsed (in seconds) since last scan activity. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_scanner_objects_scanned` | Total number of unique objects scanned since server start. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_scanner_versions_scanned` | Total number of object versions scanned since server start. <br><br>Type: counter | `server` |
|
||||||
|
|
||||||
| Name | Type | Help | Labels |
|
### System metrics
|
||||||
|-------------------------------------------------------|-----------|----------------------------------------------------------------------------|----------|
|
|
||||||
| `minio_cluster_ilm_expiry_pending_tasks` | `gauge` | Number of pending ILM expiry tasks in the queue | `server` |
|
Metrics about the MinIO process and the node.
|
||||||
| `minio_cluster_ilm_transition_active_tasks` | `gauge` | Number of active ILM transition tasks | `server` |
|
|
||||||
| `minio_cluster_ilm_transition_pending_tasks` | `gauge` | Number of pending ILM transition tasks in the queue | `server` |
|
| Path | Description |
|
||||||
| `minio_cluster_ilm_transition_missed_immediate_tasks` | `counter` | Number of missed immediate ILM transition tasks | `server` |
|
|-----------------------------|----------------------------------------------------|
|
||||||
| `minio_cluster_ilm_versions_scanned` | `counter` | Total number of object versions checked for ILM actions since server start | `server` |
|
| `/system/cpu` | Metrics about CPUs on the system. |
|
||||||
|
| `/system/drive` | Metrics about drives on the system. |
|
||||||
|
| `/system/network/internode` | Metrics about internode requests made by the node. |
|
||||||
|
| `/system/memory` | Metrics about memory on the system. |
|
||||||
|
| `/system/process` | Standard process metrics. |
|
||||||
|
|
||||||
|
#### `/system/drive`
|
||||||
|
|
||||||
|
| Name | Description | Labels |
|
||||||
|
|------------------------------------------------|-----------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||||
|
| `minio_system_drive_used_bytes` | Total storage used on a drive in bytes. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_free_bytes` | Total storage free on a drive in bytes. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_total_bytes` | Total storage available on a drive in bytes. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_used_inodes` | Total used inodes on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_free_inodes` | Total free inodes on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_total_inodes` | Total inodes available on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_timeout_errors_total` | Total timeout errors on a drive. <br><br>Type: counter | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_io_errors_total` | Total I/O errors on a drive. <br><br>Type: counter | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_availability_errors_total` | Total availability errors (I/O errors, timeouts) on a drive. <br><br>Type: counter | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_waiting_io` | Total waiting I/O operations on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_api_latency_micros` | Average last minute latency in µs for drive API storage operations. <br><br>Type: gauge | `drive`, `api`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_offline_count` | Count of offline drives. <br><br>Type: gauge | `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_online_count` | Count of online drives. <br><br>Type: gauge | `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_count` | Count of all drives. <br><br>Type: gauge | `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_health` | Drive health (0 = offline, 1 = healthy, 2 = healing). <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_reads_per_sec` | Reads per second on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_reads_kb_per_sec` | Kilobytes read per second on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_reads_await` | Average time for read requests served on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_writes_per_sec` | Writes per second on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_writes_kb_per_sec` | Kilobytes written per second on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_writes_await` | Average time for write requests served on a drive. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
| `minio_system_drive_perc_util` | Percentage of time the disk was busy. <br><br>Type: gauge | `drive`, `set_index`, `drive_index`, `pool_index`, `server` |
|
||||||
|
|
||||||
|
#### `/system/memory`
|
||||||
|
|
||||||
|
| Name | Description | Labels |
|
||||||
|
|----------------------------------|---------------------------------------------------------|----------|
|
||||||
|
| `minio_system_memory_used` | Used memory on the node. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_memory_used_perc` | Used memory percentage on the node. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_memory_free` | Free memory on the node. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_memory_total` | Total memory on the node. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_memory_buffers` | Buffers memory on the node. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_memory_cache` | Cache memory on the node. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_memory_shared` | Shared memory on the node. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_memory_available` | Available memory on the node. <br><br>Type: gauge | `server` |
|
||||||
|
|
||||||
|
#### `/system/cpu`
|
||||||
|
|
||||||
|
| Name | Description | Labels |
|
||||||
|
|-------------------------------|---------------------------------------------------------|----------|
|
||||||
|
| `minio_system_cpu_avg_idle` | Average CPU idle time. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_cpu_avg_iowait` | Average CPU IOWait time. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_cpu_load` | CPU load average 1min. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_cpu_load_perc` | CPU load average 1min (percentage). <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_cpu_nice` | CPU nice time. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_cpu_steal` | CPU steal time. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_cpu_system` | CPU system time. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_cpu_user` | CPU user time. <br><br>Type: gauge | `server` |
|
||||||
|
|
||||||
|
#### `/system/network/internode`
|
||||||
|
|
||||||
|
| Name | Description | Labels |
|
||||||
|
|------------------------------------------------------|-------------------------------------------------------------------------------|------------------------|
|
||||||
|
| `minio_system_network_internode_errors_total` | Total number of failed internode calls. <br><br>Type: counter | `server`, `pool_index` |
|
||||||
|
| `minio_system_network_internode_dial_errors_total` | Total number of internode TCP dial timeouts and errors. <br><br>Type: counter | `server`, `pool_index` |
|
||||||
|
| `minio_system_network_internode_dial_avg_time_nanos` | Average dial time of internodes TCP calls in nanoseconds. <br><br>Type: gauge | `server`, `pool_index` |
|
||||||
|
| `minio_system_network_internode_sent_bytes_total` | Total number of bytes sent to other peer nodes. <br><br>Type: counter | `server`, `pool_index` |
|
||||||
|
| `minio_system_network_internode_recv_bytes_total` | Total number of bytes received from other peer nodes. <br><br>Type: counter | `server`, `pool_index` |
|
||||||
|
|
||||||
|
#### `/system/process`
|
||||||
|
|
||||||
|
| Name | Description | Labels |
|
||||||
|
|----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------|
|
||||||
|
| `minio_system_process_locks_read_total` | Number of current READ locks on this peer. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_locks_write_total` | Number of current WRITE locks on this peer. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_cpu_total_seconds` | Total user and system CPU time spent in seconds. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_system_process_go_routine_total` | Total number of go routines running. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_io_rchar_bytes` | Total bytes read by the process from the underlying storage system including cache, /proc/[pid]/io rchar. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_system_process_io_read_bytes` | Total bytes read by the process from the underlying storage system, /proc/[pid]/io read_bytes. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_system_process_io_wchar_bytes` | Total bytes written by the process to the underlying storage system including page cache, /proc/[pid]/io wchar. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_system_process_io_write_bytes` | Total bytes written by the process to the underlying storage system, /proc/[pid]/io write_bytes. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_system_process_start_time_seconds` | Start time for MinIO process in seconds since Unix epoch. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_uptime_seconds` | Uptime for MinIO process in seconds. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_file_descriptor_limit_total` | Limit on total number of open file descriptors for the MinIO Server process. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_file_descriptor_open_total` | Total number of open file descriptors by the MinIO Server process. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_syscall_read_total` | Total read SysCalls to the kernel. /proc/[pid]/io syscr. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_system_process_syscall_write_total` | Total write SysCalls to the kernel. /proc/[pid]/io syscw. <br><br>Type: counter | `server` |
|
||||||
|
| `minio_system_process_resident_memory_bytes` | Resident memory size in bytes. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_virtual_memory_bytes` | Virtual memory size in bytes. <br><br>Type: gauge | `server` |
|
||||||
|
| `minio_system_process_virtual_memory_max_bytes` | Maximum virtual memory size in bytes. <br><br>Type: gauge | `server` |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
# Settings and configurations that are common for all containers
|
# Settings and configurations that are common for all containers
|
||||||
x-minio-common: &minio-common
|
x-minio-common: &minio-common
|
||||||
image: quay.io/minio/minio:RELEASE.2024-07-04T14-25-45Z
|
image: quay.io/minio/minio:RELEASE.2024-07-29T22-14-52Z
|
||||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
||||||
expose:
|
expose:
|
||||||
- "9000"
|
- "9000"
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ echo "done"
|
|||||||
|
|
||||||
# Start MinIO instances
|
# Start MinIO instances
|
||||||
echo -n "Starting MinIO instances ..."
|
echo -n "Starting MinIO instances ..."
|
||||||
CI=on MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --certs-dir /tmp/certs --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
CI=on MINIO_KMS_SECRET_KEY=minio-default-key:IyqsU3kMFloCNup4BsZtf/rmfHVcTgznO2F25CkEH1g= MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --certs-dir /tmp/certs --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
||||||
CI=on MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --certs-dir /tmp/certs --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
CI=on MINIO_KMS_SECRET_KEY=minio-default-key:IyqsU3kMFloCNup4BsZtf/rmfHVcTgznO2F25CkEH1g= MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --certs-dir /tmp/certs --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
||||||
echo "done"
|
echo "done"
|
||||||
|
|
||||||
if [ ! -f ./mc ]; then
|
if [ ! -f ./mc ]; then
|
||||||
@@ -99,7 +99,7 @@ sleep 30
|
|||||||
echo "Create bucket in source MinIO instance"
|
echo "Create bucket in source MinIO instance"
|
||||||
./mc mb minio1/test-bucket --insecure
|
./mc mb minio1/test-bucket --insecure
|
||||||
|
|
||||||
# Load objects to source site
|
# Load objects to source site with checksum header
|
||||||
echo "Loading objects to source MinIO instance"
|
echo "Loading objects to source MinIO instance"
|
||||||
OBJ_CHKSUM=$(openssl dgst -sha256 -binary </tmp/data/obj | base64)
|
OBJ_CHKSUM=$(openssl dgst -sha256 -binary </tmp/data/obj | base64)
|
||||||
aws s3api --endpoint-url=https://localhost:9001 put-object --checksum-algorithm SHA256 --checksum-sha256 "${OBJ_CHKSUM}" --bucket test-bucket --key obj --body /tmp/data/obj --no-verify-ssl --profile enterprise
|
aws s3api --endpoint-url=https://localhost:9001 put-object --checksum-algorithm SHA256 --checksum-sha256 "${OBJ_CHKSUM}" --bucket test-bucket --key obj --body /tmp/data/obj --no-verify-ssl --profile enterprise
|
||||||
@@ -176,4 +176,87 @@ if [ "${SRC_OBJ_2_ETAG}" != "${DEST_OBJ_2_ETAG}" ]; then
|
|||||||
exit_1
|
exit_1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Set default encryption on "
|
||||||
|
|
||||||
|
# test if checksum header is replicated for encrypted objects
|
||||||
|
# Enable SSE KMS for test-bucket bucket
|
||||||
|
./mc encrypt set sse-kms minio-default-key minio1/test-bucket --insecure
|
||||||
|
|
||||||
|
# Load objects to source site with checksum header
|
||||||
|
echo "Loading objects to source MinIO instance"
|
||||||
|
OBJ_CHKSUM=$(openssl dgst -sha256 -binary </tmp/data/obj | base64)
|
||||||
|
aws s3api --endpoint-url=https://localhost:9001 put-object --checksum-algorithm SHA256 --checksum-sha256 "${OBJ_CHKSUM}" --bucket test-bucket --key obj2 --body /tmp/data/obj --no-verify-ssl --profile enterprise
|
||||||
|
|
||||||
|
split -n 10 /tmp/data/mpartobj
|
||||||
|
CREATE_MPART_OUT=$(aws s3api --endpoint-url=https://localhost:9001 create-multipart-upload --bucket test-bucket --key mpartobj2 --checksum-algorithm SHA256 --no-verify-ssl --profile enterprise)
|
||||||
|
UPLOAD_ID=$(echo "${CREATE_MPART_OUT}" | jq '.UploadId' | sed 's/"//g')
|
||||||
|
|
||||||
|
PARTS=""
|
||||||
|
for idx in {1..10}; do
|
||||||
|
F_SUFFIX=$(num_to_alpha "$idx")
|
||||||
|
PART_CHKSUM=$(openssl dgst -sha256 -binary <"xa${F_SUFFIX}" | base64)
|
||||||
|
UPLOAD_PART_OUT=$(aws s3api --endpoint-url=https://localhost:9001 upload-part --checksum-algorithm SHA256 --checksum-sha256 "${PART_CHKSUM}" --bucket test-bucket --key mpartobj2 --part-number "${idx}" --body "xa${F_SUFFIX}" --upload-id "${UPLOAD_ID}" --no-verify-ssl --profile enterprise)
|
||||||
|
PART_ETAG=$(echo "${UPLOAD_PART_OUT}" | jq '.ETag')
|
||||||
|
if [ "${idx}" == 10 ]; then
|
||||||
|
PARTS="${PARTS}{\"ETag\": ${PART_ETAG}, \"ChecksumSHA256\": \"${PART_CHKSUM}\", \"PartNumber\": ${idx}}"
|
||||||
|
else
|
||||||
|
PARTS="${PARTS}{\"ETag\": ${PART_ETAG}, \"ChecksumSHA256\": \"${PART_CHKSUM}\", \"PartNumber\": ${idx}},"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "{\"Parts\":[${PARTS}]}" >fileparts.json
|
||||||
|
jq <fileparts.json
|
||||||
|
aws s3api --endpoint-url=https://localhost:9001 complete-multipart-upload --multipart-upload file://fileparts.json --bucket test-bucket --key mpartobj2 --upload-id "${UPLOAD_ID}" --no-verify-ssl --profile enterprise
|
||||||
|
sleep 120
|
||||||
|
|
||||||
|
# List the objects from replicated site
|
||||||
|
echo "Objects from replicated site"
|
||||||
|
./mc ls minio2/test-bucket --insecure
|
||||||
|
count1=$(./mc ls minio2/test-bucket/obj2 --insecure | wc -l)
|
||||||
|
if [ "${count1}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/obj2 not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
count2=$(./mc ls minio2/test-bucket/mpartobj2 --insecure | wc -l)
|
||||||
|
if [ "${count2}" -ne 1 ]; then
|
||||||
|
echo "BUG: object minio1/test-bucket/mpartobj2 not replicated"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stat the objects from source site
|
||||||
|
echo "Stat minio1/test-bucket/obj2"
|
||||||
|
SRC_OUT_1=$(aws s3api --endpoint-url https://localhost:9001 head-object --bucket test-bucket --key obj2 --checksum-mode ENABLED --no-verify-ssl --profile enterprise)
|
||||||
|
SRC_OUT_2=$(aws s3api --endpoint-url https://localhost:9001 head-object --bucket test-bucket --key mpartobj2 --checksum-mode ENABLED --no-verify-ssl --profile enterprise)
|
||||||
|
SRC_OBJ_1_CHKSUM=$(echo "${SRC_OUT_1}" | jq '.ChecksumSHA256')
|
||||||
|
SRC_OBJ_2_CHKSUM=$(echo "${SRC_OUT_2}" | jq '.ChecksumSHA256')
|
||||||
|
SRC_OBJ_1_ETAG=$(echo "${SRC_OUT_1}" | jq '.ETag')
|
||||||
|
SRC_OBJ_2_ETAG=$(echo "${SRC_OUT_2}" | jq '.ETag')
|
||||||
|
|
||||||
|
# Stat the objects from replicated site
|
||||||
|
echo "Stat minio2/test-bucket/obj2"
|
||||||
|
DEST_OUT_1=$(aws s3api --endpoint-url https://localhost:9002 head-object --bucket test-bucket --key obj2 --checksum-mode ENABLED --no-verify-ssl --profile enterprise)
|
||||||
|
DEST_OUT_2=$(aws s3api --endpoint-url https://localhost:9002 head-object --bucket test-bucket --key mpartobj2 --checksum-mode ENABLED --no-verify-ssl --profile enterprise)
|
||||||
|
DEST_OBJ_1_CHKSUM=$(echo "${DEST_OUT_1}" | jq '.ChecksumSHA256')
|
||||||
|
DEST_OBJ_2_CHKSUM=$(echo "${DEST_OUT_2}" | jq '.ChecksumSHA256')
|
||||||
|
DEST_OBJ_1_ETAG=$(echo "${DEST_OUT_1}" | jq '.ETag')
|
||||||
|
DEST_OBJ_2_ETAG=$(echo "${DEST_OUT_2}" | jq '.ETag')
|
||||||
|
|
||||||
|
# Check the replication of checksums and etags
|
||||||
|
if [ "${SRC_OBJ_1_CHKSUM}" != "${DEST_OBJ_1_CHKSUM}" ]; then
|
||||||
|
echo "BUG: Checksums dont match for 'obj2'. Source: ${SRC_OBJ_1_CHKSUM}, Destination: ${DEST_OBJ_1_CHKSUM}"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${SRC_OBJ_2_CHKSUM}" != "${DEST_OBJ_2_CHKSUM}" ]; then
|
||||||
|
echo "BUG: Checksums dont match for 'mpartobj2'. Source: ${SRC_OBJ_2_CHKSUM}, Destination: ${DEST_OBJ_2_CHKSUM}"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${SRC_OBJ_1_ETAG}" != "${DEST_OBJ_1_ETAG}" ]; then
|
||||||
|
echo "BUG: Etags dont match for 'obj2'. Source: ${SRC_OBJ_1_ETAG}, Destination: ${DEST_OBJ_1_ETAG}"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
if [ "${SRC_OBJ_2_ETAG}" != "${DEST_OBJ_2_ETAG}" ]; then
|
||||||
|
echo "BUG: Etags dont match for 'mpartobj2'. Source: ${SRC_OBJ_2_ETAG}, Destination: ${DEST_OBJ_2_ETAG}"
|
||||||
|
exit_1
|
||||||
|
fi
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ MINIO_IDENTITY_OPENID_CLAIM_USERINFO (on|off) Enable fetching claims f
|
|||||||
MINIO_IDENTITY_OPENID_KEYCLOAK_REALM (string) Specify Keycloak 'realm' name, only honored if vendor was set to 'keycloak' as value, if no realm is specified 'master' is default
|
MINIO_IDENTITY_OPENID_KEYCLOAK_REALM (string) Specify Keycloak 'realm' name, only honored if vendor was set to 'keycloak' as value, if no realm is specified 'master' is default
|
||||||
MINIO_IDENTITY_OPENID_KEYCLOAK_ADMIN_URL (string) Specify Keycloak 'admin' REST API endpoint e.g. http://localhost:8080/auth/admin/
|
MINIO_IDENTITY_OPENID_KEYCLOAK_ADMIN_URL (string) Specify Keycloak 'admin' REST API endpoint e.g. http://localhost:8080/auth/admin/
|
||||||
MINIO_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC (on|off) Enable 'Host' header based dynamic redirect URI (default: 'off')
|
MINIO_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC (on|off) Enable 'Host' header based dynamic redirect URI (default: 'off')
|
||||||
MINIO_IDENTITY_OPENID_CLAIM_PREFIX (string) [DEPRECATED use 'claim_name'] JWT claim namespace prefix e.g. "customer1/"
|
|
||||||
MINIO_IDENTITY_OPENID_REDIRECT_URI (string) [DEPRECATED use env 'MINIO_BROWSER_REDIRECT_URL'] Configure custom redirect_uri for OpenID login flow callback
|
|
||||||
MINIO_IDENTITY_OPENID_COMMENT (sentence) optionally add a comment to this setting
|
MINIO_IDENTITY_OPENID_COMMENT (sentence) optionally add a comment to this setting
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ sudo mv tuned.conf /usr/lib/tuned/minio
|
|||||||
|
|
||||||
#### Step 3 - to enable minio performance profile on all the nodes
|
#### Step 3 - to enable minio performance profile on all the nodes
|
||||||
```
|
```
|
||||||
sudo tuned-admin profile minio
|
sudo tuned-adm profile minio
|
||||||
```
|
```
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user