Compare commits

..

98 Commits

Author SHA1 Message Date
Krishna Srinivas f53c5a020e fix: heal object shards with ec.index and ec.distribution mismatches (#10773)
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-10-28 00:10:20 -07:00
Harshavardhana 5b30bbda92 fix: add more protection distribution to match EcIndex (#10772)
allows for more stricter validation in picking up the right
set of disks for reconstruction.
2020-10-28 00:09:15 -07:00
Shireesh Anjal 858e2a43df Remove logging info from OBDInfoHandler (#10727)
A lot of logging data is counterproductive. A better implementation with
precise useful log data can be introduced later.
2020-10-27 17:41:48 -07:00
Kaloyan Raev df9894e275 avoid caching http ranges in background goroutine (#10724) 2020-10-26 23:04:48 -07:00
Minio Trusted ca77ee1c0e Update yaml files to latest version RELEASE.2020-10-27T04-03-55Z 2020-10-27 04:20:10 +00:00
Krishna Srinivas 592f2f23a3 fix: heal rejects objects with disk re-ordering issue (#10766) 2020-10-26 18:48:47 -07:00
Krishna Srinivas c49a80db41 fix: use meta.Erasure.Index for GetObject() to reconstruct object (#10764) 2020-10-26 16:19:42 -07:00
Poorna Krishnamoorthy 46275c6547 cache: rename function declarations (#10763) 2020-10-26 15:41:24 -07:00
Poorna Krishnamoorthy 0994ed9783 cache: fix call in GetObjectNInfo (#10762)
Fixes: #10751
2020-10-26 12:30:40 -07:00
Anis Elleuch eb95353cb1 fix: Get/HeadObject return 404 on non quorum objects (#10753) 2020-10-26 10:30:46 -07:00
Harshavardhana 029758cb20 fix: retain the previous UUID for newly replaced drives (#10759)
only newly replaced drives get the new `format.json`,
this avoids disks reloading their in-memory reference
format, ensures that drives are online without
reloading the in-memory reference format.

keeping reference format in-tact means UUIDs
never change once they are formatted.
2020-10-26 10:29:29 -07:00
Harshavardhana 649035677f fix: arm64 Dockerfile with missing \ for multiple ENVs
fixes #10758
2020-10-25 13:52:55 -07:00
Harshavardhana 646d6917ed turn-off checking for updates completely if MINIO_UPDATE=off (#10752) 2020-10-24 22:39:44 -07:00
Harshavardhana d9db7f3308 expire lockers if lockers are offline (#10749)
lockers currently might leave stale lockers,
in unknown ways waiting for downed lockers.

locker check interval is high enough to safely
cleanup stale locks.
2020-10-24 13:23:16 -07:00
Harshavardhana 6a8c62f9fd make sure to preserve UUID from reference format (#10748)
reference format should be source of truth
for inconsistent drives which reconnect,
add them back to their original position

remove automatic fix for existing offline
disk uuids
2020-10-24 13:23:08 -07:00
Harshavardhana 4442382c16 update STS examples to use latest v7 APIs 2020-10-24 11:16:59 -07:00
Anis Elleuch 00124c56d9 erasure: Commit data before xl.meta in RenameData() (#10734)
This will reduce the chance to have updated xl.meta without data.
2020-10-23 21:54:58 -07:00
Anis Elleuch 2c32c2149e tests: Avoid running TestNSRace in short test mode (#10735) 2020-10-23 21:23:12 -07:00
Harshavardhana 734f258878 fix: slow down auto healing more aggressively (#10730)
Bonus fixes

- logging improvements to ensure that we don't use
  `go logger.LogIf` to avoid runtime.Caller missing
  the function name. log where necessary.
- remove unused code at erasure sets
2020-10-22 13:36:24 -07:00
Anis Elleuch 0e0c53bba4 tests: Lower expectation in addr selection in rand cache dialer (#10739)
Test TestDialContextWithDNSCacheRand was failing sometimes because it depends
on a random selection of addresses when testing random DNS resolution from cache.

Lower addr selection exception to 10%
2020-10-22 09:35:32 -07:00
Poorna Krishnamoorthy 5cc23ae052 validate if iam store is initialized (#10719)
Fixes panic - regression from d6d770c1b1
2020-10-20 21:28:24 -07:00
Anis Elleuch 6fd088f448 docs: Update ILM doc with versioning features (#10714) 2020-10-20 09:23:27 -07:00
Harshavardhana d6d770c1b1 initialize object layer right after config has loaded 2020-10-19 22:04:59 -07:00
Harshavardhana b07df5cae1 initialize IAM as soon as object layer is initialized (#10700)
Allow requests to come in for users as soon as object
layer and config are initialized, this allows users
to be authenticated sooner and would succeed automatically
on servers which are yet to fully initialize.
2020-10-19 09:54:40 -07:00
Harshavardhana c107728676 fix: s3 gateway DNS cache initialization (#10706)
fixes #10705
2020-10-19 01:34:23 -07:00
Minio Trusted ba5215561f Update yaml files to latest version RELEASE.2020-10-18T21-54-12Z 2020-10-18 22:15:30 +00:00
Márk Sági-Kazár 4eb45c9a0f fix: dex getting started guide URL (#10701) 2020-10-17 16:47:28 -07:00
Harshavardhana 187129a907 fix comment in bucket bandwidth package 2020-10-17 00:38:54 -07:00
Anis Elleuch 284a2b9021 ilm: Send delete marker creation event when appropriate (#10696)
Before this commit, the crawler ILM will always send object delete event
notification though this is wrong.
2020-10-16 21:22:12 -07:00
Ritesh H Shukla 0b53e30ecb Clean up monitor on delete bucket (#10698) 2020-10-16 17:59:31 -07:00
Harshavardhana bd2131ba34 add DNS cache support to avoid DNS flooding (#10693)
Go stdlib resolver doesn't support caching DNS
resolutions, since we compile with CGO disabled
we are more probe to DNS flooding for all network
calls to resolve for DNS from the DNS server.

Under various containerized environments such as
VMWare this becomes a problem because there are
no DNS caches available and we may end up overloading
the kube-dns resolver under concurrent I/O.

To circumvent this issue implement a DNSCache resolver
which resolves DNS and caches them for around 10secs
with every 3sec invalidation attempted.
2020-10-16 14:49:05 -07:00
Ritesh H Shukla 73a41a725a Always close response body (#10697) 2020-10-16 12:40:36 -07:00
ebozduman 1aec168c84 fix: azure gateway should reject bucket names with "." (#10635) 2020-10-16 09:30:18 -07:00
Klaus Post 21a549a83b fix: keep MRF channel open to avoid random CI crash (#10686)
There doesn't seem to be any benefit to closing the channel, so just keep 
it open and let it die with the server.
2020-10-16 09:08:51 -07:00
Ritesh H Shukla 8a16a1a1a9 fix: misc fixes for bandwidth reporting amd monitoring (#10683)
* Set peer for fetch bandwidth
* Fix the limit for bandwidth that is reported.
* Reduce CPU burn from bandwidth management.
2020-10-16 09:07:50 -07:00
Harshavardhana ad726b49b4 rename zones to serverSets to avoid terminology conflict (#10679)
we are bringing in availability zones, we should avoid
zones as per server expansion concept.
2020-10-15 14:28:50 -07:00
Anis Elleuch db2241066b heal: Enable removing dangling delete markers (#10688) 2020-10-15 13:06:40 -07:00
Harshavardhana f1cc16e788 fix: background heal rely on getOnlineDisks() (#10687) 2020-10-15 13:06:23 -07:00
Klaus Post 3820a905e0 in getOnlineDisks wait for disks to be populated (#10685) 2020-10-15 06:37:10 -07:00
Harshavardhana 2042d4873c rename crawler config option to heal (#10678) 2020-10-14 13:51:51 -07:00
Harshavardhana f9be783f3e fix: allow crawler to crawl on disks without usage constraints (#10677)
additionally also change the resolution usage wise
return of disks, allows to small byte level differences
to be masked.
2020-10-14 12:12:10 -07:00
Harshavardhana 23773bb32b update NTP package for accurate time resolution fixes (#10670) 2020-10-14 12:29:20 +05:30
Harshavardhana 2fd7545b6c fix: move to go1.15 (#10660) 2020-10-13 18:33:18 -07:00
Harshavardhana 71b97fd3ac fix: connect disks pre-emptively during startup (#10669)
connect disks pre-emptively upon startup, to ensure we have
enough disks are connected at startup rather than wait
for them.

we need to do this to avoid long wait times for server to
be online when we have servers come up in rolling upgrade
fashion
2020-10-13 18:28:42 -07:00
Klaus Post 03991c5d41 crawler: Remove waitForLowActiveIO (#10667)
Only use dynamic delays for the crawler. Even though the max wait was 1 second the number 
of waits could severely impact crawler speed.

Instead of relying on a global metric, we use the stateless local delays to keep the crawler 
running at a speed more adjusted to current conditions.

The only case we keep it is before bitrot checks when enabled.
2020-10-13 13:45:08 -07:00
Harshavardhana 9c042a503b remove deprecate readiness from healthcheck docs (#10659) 2020-10-12 18:56:03 -07:00
飞雪无情 614060764d fix: use the correct Action type for policy.Args and iampolicy.Args (#10650) 2020-10-12 15:18:22 -07:00
Minio Trusted 4a678ad70f Update yaml files to latest version RELEASE.2020-10-12T21-53-21Z 2020-10-12 22:15:06 +00:00
Harshavardhana a3ba8188d7 fix: allow locker to be niladic 2020-10-12 14:23:44 -07:00
Harshavardhana 2760fc86af Bump default idleConnsPerHost to control conns in time_wait (#10653)
This PR fixes a hang which occurs quite commonly at higher concurrency
by allowing following changes

- allowing lower connections in time_wait allows faster socket open's
- lower idle connection timeout to ensure that we let kernel
  reclaim the time_wait connections quickly
- increase somaxconn to 4096 instead of 2048 to allow larger tcp
  syn backlogs.

fixes #10413
2020-10-12 14:19:46 -07:00
P R abb14aeec1 Header row seperator for console Table (#10651) 2020-10-12 11:32:43 -07:00
Ritesh H Shukla 8ceb2a93fd fix: peer replication bandwidth monitoring in distributed setup (#10652) 2020-10-12 09:04:55 -07:00
Ritesh H Shukla c2f16ee846 Add basic bandwidth monitoring for replication. (#10501)
This change tracks bandwidth for a bucket and object

- [x] Add Admin API
- [x] Add Peer API
- [x] Add BW throttling
- [x] Admin APIs to set replication limit
- [x] Admin APIs for fetch bandwidth
2020-10-09 20:36:00 -07:00
Minio Trusted 071c004f8b Update yaml files to latest version RELEASE.2020-10-09T22-55-05Z 2020-10-09 23:16:30 +00:00
Harshavardhana 6484453fc6 optionally allow strict quorum listing (#10649)
```
export MINIO_API_LIST_STRICT_QUORUM=on
```

would enable listing in quorum if necessary
2020-10-09 15:40:46 -07:00
Harshavardhana a0d0645128 remove safeMode behavior in startup (#10645)
In almost all scenarios MinIO now is
mostly ready for all sub-systems
independently, safe-mode is not useful
anymore and do not serve its original
intended purpose.

allow server to be fully functional
even with config partially configured,
this is to cater for availability of actual
I/O v/s manually fixing the server.

In k8s like environments it will never make
sense to take pod into safe-mode state,
because there is no real access to perform
any remote operation on them.
2020-10-09 09:59:52 -07:00
miraculli 1738eb24b1 fix: caching doc README.md missing high watermark (#10646) 2020-10-09 08:32:51 -07:00
Harshavardhana 253194e491 do not hold write locks - if objects don't exist (#10644) 2020-10-08 17:47:21 -07:00
Harshavardhana 736e58dd68 fix: handle concurrent lockers with multiple optimizations (#10640)
- select lockers which are non-local and online to have
  affinity towards remote servers for lock contention

- optimize lock retry interval to avoid sending too many
  messages during lock contention, reduces average CPU
  usage as well

- if bucket is not set, when deleteObject fails make sure
  setPutObjHeaders() honors lifecycle only if bucket name
  is set.

- fix top locks to list out always the oldest lockers always,
  avoid getting bogged down into map's unordered nature.
2020-10-08 12:32:32 -07:00
Poorna Krishnamoorthy 907a171edd Generalize error messages for remote targets (#10638)
This is to allow remote targets to be generalized
for replication/ILM transition

Also adding a field in BucketTarget to identify
a remote target with a label.
2020-10-08 10:54:11 -07:00
Andreas Auernhammer ed6d2a100f logger: avoid writing audit log response header twice (#10642)
This commit fixes a misuse of the `http.ResponseWriter.WriteHeader`.
A caller should **either** call `WriteHeader` exactly once **or**
write to the response writer and causing an implicit 200 OK.

Writing the response headers more than once causes a `http: superfluous
response.WriteHeader call` log message. This commit fixes this
by preventing a 2nd `WriteHeader` call being forwarded to the underlying
`ResponseWriter`.

Updates #10587
2020-10-08 09:29:10 -07:00
Harshavardhana effe131090 fix: allow read unlocks to be defensive about split brains (#10637) 2020-10-07 09:15:01 -07:00
Poorna Krishnamoorthy 01498a3e34 fix: add docs for new event types in notification (#10636) 2020-10-06 13:33:23 -07:00
Harshavardhana 18063bf25c fix: cleanup old directory handling code (#10633)
we don't need them anymore, remove legacy code.
2020-10-06 12:03:57 -07:00
Ravind Kumar 57f0176759 Update KES table to include additional supported KMS providers (#10631) 2020-10-06 11:09:43 -07:00
Poorna Krishnamoorthy dbbed6f7f0 update minio-go dependency (#10634) 2020-10-06 08:37:09 -07:00
Poorna Krishnamoorthy 7fbfdceba3 Fix replication slowness (#10632)
- Increase channel buffer length
- Avoid blocking wait on replicaCh
2020-10-05 14:45:42 -07:00
Mark Clarkson 9dda9fb903 fix: https healthcheck mint test (#10622) 2020-10-05 08:21:41 -07:00
Shireesh Anjal f1418a50f0 add NVMe drive info [model num, serial num, drive temp. etc.] (#10613)
* add NVMe drive info [model num, serial num, drive temp. etc.]
* Ignore fuse partitions
* Add the nvme logic only for linux
* Move smart/nvme structs to a separate file

Co-authored-by: wlan0 <sidharthamn@gmail.com>
2020-10-04 10:18:46 -07:00
Minio Trusted 017954e7ea Update yaml files to latest version RELEASE.2020-10-03T02-19-42Z 2020-10-03 02:36:54 +00:00
Harshavardhana 806625cbff turn-off go mod 2020-10-02 18:57:01 -07:00
Krishna Srinivas 045e30f2c1 Set LastModified time from source for bucket replication (#10627) 2020-10-02 18:32:22 -07:00
Harshavardhana c6a9a94f94 fix: optimize ServerInfo() handler to avoid reading config (#10626)
fixes #10620
2020-10-02 16:19:44 -07:00
Harshavardhana 8e7c00f3d4 add missing request-id from DeleteObject events (#10623)
fixes #10621
2020-10-02 13:36:13 -07:00
Anis Elleuch d1ed1da8c6 build: check-gen should exit with some changes in generated code (#10590) 2020-10-02 11:10:39 -07:00
Harshavardhana 23e8390997 fix: Allow Walk to honor load balanced drives (#10610) 2020-10-01 20:24:34 -07:00
Anis Elleuch 71403be912 fix: consider partNumber in GET/HEAD requests (#10618) 2020-10-01 15:41:12 -07:00
Harshavardhana f28d02b7f2 fix: simplify obd how we calculate transferred bytes (#10617) 2020-10-01 14:34:51 -07:00
Harshavardhana e0cb814f3f fail if port is not accessible (#10616)
throw proper error when port is not accessible
for the regular user, this is possibly a regression.

```
ERROR Unable to start the server: Insufficient permissions to use specified port
   > Please ensure MinIO binary has 'cap_net_bind_service=+ep' permissions
   HINT:
     Use 'sudo setcap cap_net_bind_service=+ep /path/to/minio' to provide sufficient permissions
```
2020-10-01 13:23:31 -07:00
Harshavardhana 98a08e1644 fix: protect updating latencies/throughput slices in obd (#10611)
Additionally close the transferChan upon function exit.
2020-10-01 09:50:08 -07:00
Klaus Post 3047121255 dataupdate: Bump to force rescan (#10609)
After #10594 let's invalidate the bloom filters to force the next cycles to go through all data.

There is a small chance that the linked PR could have caused missing bloom filter data.

This will invalidate the current bloom filters and make the crawler go through everything.
2020-09-30 16:10:40 -07:00
Ritesh H Shukla 5a7f92481e fix: client errors for DNS service creation errors (#10584) 2020-09-30 14:09:41 -07:00
Anis Elleuch 0d45c38782 List v1/versions routes based on source IP if found (#10603)
Routing using on source IP if found. This should distribute
the listing load for V1 and versioning on multiple nodes
evenly between different clients.

If source IP is not found from the http request header, then falls back
to bucket name instead.
2020-09-30 13:38:27 -07:00
Poorna Krishnamoorthy 56d1b227cf Handle changes to versioning config for replication (#10598)
Disallow versioning suspension on a bucket with
pre-existing replication configuration

If versioning is suspended on the target,replication
should fail.
2020-09-30 13:36:37 -07:00
Harshavardhana 061fa0635c remove ignoring incorrect CVE (#10597) 2020-09-30 13:10:24 -07:00
Shireesh Anjal 6e138f955e Fix a couple of typos in json config (#10605)
Vault.Encrypt: encryp -> encrypt
SysOBDProcess.Uids: uidsomitempty -> uids,omitempty
2020-09-30 13:08:11 -07:00
Lenin Alevski bea87a5a20 fix: reading multiple TLS certificates when deployed in K8S (#10601)
Ignore all regular files, CAs directory and any 
directory that starts with `..` inside the
`.minio/certs` folder
2020-09-30 08:21:30 -07:00
Harshavardhana 2b4eb87d77 pick disks which are common maximally used (#10600)
further optimization to ensure that good disks
are always used for listing, other than healing
we only use disks that are maximally used.
2020-09-29 22:54:02 -07:00
sadegh 799758e54f fix: dropzone height to fill all screen height (#10547) 2020-09-29 22:27:41 -07:00
Harshavardhana 1f9abbee4d make sure to release locks upon timeout (#10596)
fixes #10418
2020-09-29 15:18:34 -07:00
Klaus Post fdf0ae9167 exit data update tracker only upon context completion (#10594)
The data update tracker saver would exit if data wasn't updated for between cycles.
2020-09-29 13:23:53 -07:00
Harshavardhana 00eb6f6bc9 cache DiskInfo at storage layer for performance (#10586)
`mc admin info` on busy setups will not move HDD
heads unnecessarily for repeated calls, provides
a better responsiveness for the call overall.

Bonus change allow listTolerancePerSet be N-1
for good entries, to avoid skipping entries
for some reason one of the disk went offline.
2020-09-29 09:54:41 -07:00
Harshavardhana 66174692a2 add '.healing.bin' for tracking currently healing disk (#10573)
add a hint on the disk to allow for tracking fresh disk
being healed, to allow for restartable heals, and also
use this as a way to track and remove disks.

There are more pending changes where we should move
all the disk formatting logic to backend drives, this
PR doesn't deal with this refactor instead makes it
easier to track healing in the future.
2020-09-28 19:39:32 -07:00
Harshavardhana 849fcf0127 block unlocks if there are quorum failures (#10582)
fixes #10418
2020-09-28 15:39:52 -07:00
飞雪无情 209680e89f Remove redundant http.HandlerFunc type conversion. (#10576) 2020-09-28 13:33:49 -07:00
Anis Elleuch e0c04a2da0 Makefile: Check for any non committed auto-generated code (#10579)
Always check if the auto-generated code is still compatible with the
existing written code to avoid a possible forgetting or sometimes a non
intentional change.
2020-09-28 13:33:34 -07:00
飞雪无情 27d9bd04e5 Handling unhandled errors in the InfoCannedPolicy method. (#10575) 2020-09-27 10:24:04 -07:00
Minio Trusted 511424a287 Update yaml files to latest version RELEASE.2020-09-26T03-44-56Z 2020-09-26 04:02:42 +00:00
206 changed files with 5154 additions and 2661 deletions
-51
View File
@@ -1,51 +0,0 @@
name: "Code scanning - action"
on:
push:
pull_request:
schedule:
- cron: '0 19 * * 0'
jobs:
CodeQL-Build:
# CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: go, javascript
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+2
View File
@@ -37,6 +37,8 @@ jobs:
GO111MODULE: on
MINIO_CI_CD: 1
run: |
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
sudo apt-get install devscripts shellcheck
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-linux.amd64-${nancy_version} && chmod +x nancy
-6
View File
@@ -1,6 +0,0 @@
CVE-2018-17075
CVE-2018-17848
CVE-2018-17846
CVE-2018-17847
CVE-2018-17143
CVE-2018-17142
+3 -3
View File
@@ -1,4 +1,4 @@
FROM golang:1.14-alpine as builder
FROM golang:1.15-alpine as builder
LABEL maintainer="MinIO Inc <dev@min.io>"
@@ -13,11 +13,11 @@ RUN \
FROM alpine:3.12
ENV MINIO_UPDATE off
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
EXPOSE 9000
+2 -2
View File
@@ -5,11 +5,11 @@ LABEL maintainer="MinIO Inc <dev@min.io>"
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
ENV MINIO_UPDATE off
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
RUN \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
+2 -2
View File
@@ -5,11 +5,11 @@ LABEL maintainer="MinIO Inc <dev@min.io>"
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
ENV MINIO_UPDATE off
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
RUN \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
+2 -2
View File
@@ -5,11 +5,11 @@ LABEL maintainer="MinIO Inc <dev@min.io>"
COPY --from=qemu /usr/bin/qemu-ppc64le-static /usr/bin
ENV MINIO_UPDATE off
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
RUN \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
+2 -2
View File
@@ -2,11 +2,11 @@ FROM alpine:3.12
LABEL maintainer="MinIO Inc <dev@min.io>"
ENV MINIO_UPDATE off
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
RUN \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
+2 -2
View File
@@ -5,11 +5,11 @@ LABEL maintainer="MinIO Inc <dev@min.io>"
COPY --from=qemu /usr/bin/qemu-s390x-static /usr/bin
ENV MINIO_UPDATE off
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
MINIO_SSE_MASTER_KEY_FILE=sse_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
RUN \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
+7 -1
View File
@@ -18,11 +18,17 @@ getdeps:
@mkdir -p ${GOPATH}/bin
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
@which ruleguard 1>/dev/null || (echo "Installing ruleguard" && GO111MODULE=off go get github.com/quasilyte/go-ruleguard/...)
@which msgp 1>/dev/null || (echo "Installing msgp" && GO111MODULE=off go get github.com/tinylib/msgp)
@which stringer 1>/dev/null || (echo "Installing stringer" && GO111MODULE=off go get golang.org/x/tools/cmd/stringer)
crosscompile:
@(env bash $(PWD)/buildscripts/cross-compile.sh)
verifiers: getdeps fmt lint ruleguard
verifiers: getdeps fmt lint ruleguard check-gen
check-gen:
@go generate ./... >/dev/null
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
fmt:
@echo "Running $@ check"
+1 -1
View File
@@ -88,7 +88,7 @@ service minio start
```
## Install from Source
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.14](https://golang.org/dl/#stable)
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.15](https://golang.org/dl/#stable)
```sh
GO111MODULE=on go get github.com/minio/minio
+1 -1
View File
@@ -36,7 +36,7 @@ export class Dropzone extends React.Component {
// Overwrite the default styling from react-dropzone; otherwise it
// won't handle child elements correctly.
const style = {
height: "100%",
flex: "1",
borderWidth: "0",
borderStyle: "dashed",
borderColor: "#fff"
+2 -1
View File
@@ -20,7 +20,8 @@
@media(max-width: @screen-sm-max) {
padding: 75px 0 80px;
}
display: flex;
flex-direction: column;
min-height:100vh;
overflow: auto;
}
+2 -2
View File
@@ -76,8 +76,8 @@ function start_minio_zone_erasure_sets_ipv6()
export MINIO_ACCESS_KEY=$ACCESS_KEY
export MINIO_SECRET_KEY=$SECRET_KEY
"${MINIO[@]}" server --address="[::1]:9000" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9000.log" 2>&1 &
"${MINIO[@]}" server --address="[::1]:9001" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9001.log" 2>&1 &
"${MINIO[@]}" server --address="[::1]:9000" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-ipv6-9000.log" 2>&1 &
"${MINIO[@]}" server --address="[::1]:9001" "http://[::1]:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://[::1]:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-ipv6-9001.log" 2>&1 &
sleep 40
}
+1 -1
View File
@@ -42,7 +42,7 @@ import (
func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *http.Request) (auth.Credentials, ObjectLayer) {
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
objectAPI := newObjectLayerFn()
if objectAPI == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return auth.Credentials{}, nil
+14 -11
View File
@@ -35,8 +35,8 @@ func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.R
var adminAPIErr APIErrorCode
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return nil, cred
}
@@ -386,8 +386,8 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
defer logger.AuditLog(w, r, "AddServiceAccount", mustGetClaimsFromToken(r))
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
@@ -465,8 +465,8 @@ func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Req
defer logger.AuditLog(w, r, "ListServiceAccounts", mustGetClaimsFromToken(r))
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
@@ -520,8 +520,8 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
defer logger.AuditLog(w, r, "DeleteServiceAccount", mustGetClaimsFromToken(r))
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
@@ -579,8 +579,8 @@ func (a adminAPIHandlers) AccountUsageInfoHandler(w http.ResponseWriter, r *http
defer logger.AuditLog(w, r, "AccountUsageInfo", mustGetClaimsFromToken(r))
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
@@ -722,7 +722,10 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
return
}
json.NewEncoder(w).Encode(policy)
if err = json.NewEncoder(w).Encode(policy); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
w.(http.Flusher).Flush()
}
+95 -86
View File
@@ -35,9 +35,7 @@ import (
"time"
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/config/notify"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
@@ -87,8 +85,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
}
if globalInplaceUpdateDisabled {
// if MINIO_UPDATE=off - inplace update is disabled, mostly
// in containers.
// if MINIO_UPDATE=off - inplace update is disabled, mostly in containers.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
@@ -295,6 +292,20 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
// ignores any errors here.
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
// Collect any disk healing.
healing, _ := getAggregatedBackgroundHealState(ctx)
healDisks := make(map[string]struct{}, len(healing.HealDisks))
for _, disk := range healing.HealDisks {
healDisks[disk] = struct{}{}
}
// find all disks which belong to each respective endpoints
for i, disk := range storageInfo.Disks {
if _, ok := healDisks[disk.Endpoint]; ok {
storageInfo.Disks[i].Healing = true
}
}
// Marshal API response
jsonBytes, err := json.Marshal(storageInfo)
if err != nil {
@@ -336,7 +347,7 @@ func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Re
writeSuccessResponseJSON(w, dataUsageInfoJSON)
}
func lriToLockEntry(l lockRequesterInfo, resource, server string, rquorum, wquorum int) *madmin.LockEntry {
func lriToLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEntry {
entry := &madmin.LockEntry{
Timestamp: l.Timestamp,
Resource: resource,
@@ -344,18 +355,17 @@ func lriToLockEntry(l lockRequesterInfo, resource, server string, rquorum, wquor
Source: l.Source,
Owner: l.Owner,
ID: l.UID,
Quorum: l.Quorum,
}
if l.Writer {
entry.Type = "WRITE"
entry.Quorum = wquorum
} else {
entry.Type = "READ"
entry.Quorum = rquorum
}
return entry
}
func topLockEntries(peerLocks []*PeerLocks, count int, rquorum, wquorum int, stale bool) madmin.LockEntries {
func topLockEntries(peerLocks []*PeerLocks, stale bool) madmin.LockEntries {
entryMap := make(map[string]*madmin.LockEntry)
for _, peerLock := range peerLocks {
if peerLock == nil {
@@ -367,7 +377,7 @@ func topLockEntries(peerLocks []*PeerLocks, count int, rquorum, wquorum int, sta
if val, ok := entryMap[lockReqInfo.UID]; ok {
val.ServerList = append(val.ServerList, peerLock.Addr)
} else {
entryMap[lockReqInfo.UID] = lriToLockEntry(lockReqInfo, k, peerLock.Addr, rquorum, wquorum)
entryMap[lockReqInfo.UID] = lriToLockEntry(lockReqInfo, k, peerLock.Addr)
}
}
}
@@ -375,9 +385,6 @@ func topLockEntries(peerLocks []*PeerLocks, count int, rquorum, wquorum int, sta
}
var lockEntries madmin.LockEntries
for _, v := range entryMap {
if len(lockEntries) == count {
break
}
if stale {
lockEntries = append(lockEntries, *v)
continue
@@ -420,12 +427,13 @@ func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request
peerLocks := globalNotificationSys.GetLocks(ctx, r)
rquorum := getReadQuorum(objectAPI.SetDriveCount())
wquorum := getWriteQuorum(objectAPI.SetDriveCount())
topLocks := topLockEntries(peerLocks, stale)
topLocks := topLockEntries(peerLocks, count, rquorum, wquorum, stale)
// Marshal API response upto requested count.
if len(topLocks) > count && count > 0 {
topLocks = topLocks[:count]
}
// Marshal API response
jsonBytes, err := json.Marshal(topLocks)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
@@ -885,7 +893,7 @@ func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Reques
var cred auth.Credentials
var adminAPIErr APIErrorCode
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return nil, cred
@@ -1286,12 +1294,6 @@ func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request)
go func() {
defer close(obdInfoCh)
if log := query.Get("log"); log == "true" {
obdInfo.Logging.ServersLog = append(obdInfo.Logging.ServersLog, getLocalLogOBD(deadlinedCtx, r))
obdInfo.Logging.ServersLog = append(obdInfo.Logging.ServersLog, globalNotificationSys.LogOBDInfo(deadlinedCtx)...)
partialWrite(obdInfo)
}
if cpu := query.Get("syscpu"); cpu == "true" {
cpuInfo := getLocalCPUOBDInfo(deadlinedCtx, r)
@@ -1411,6 +1413,31 @@ func (a adminAPIHandlers) OBDInfoHandler(w http.ResponseWriter, r *http.Request)
}
// BandwidthMonitorHandler - GET /minio/admin/v3/bandwidth
// ----------
// Get bandwidth consumption information
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "BandwidthMonitor")
// Validate request signature.
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.BandwidthMonitorAction, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return
}
setEventStreamHeaders(w)
bucketsRequestedString := r.URL.Query().Get("buckets")
bucketsRequested := strings.Split(bucketsRequestedString, ",")
consolidatedReport := globalNotificationSys.GetBandwidthReports(ctx, bucketsRequested...)
enc := json.NewEncoder(w)
err := enc.Encode(consolidatedReport)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), r.URL)
}
w.(http.Flusher).Flush()
}
// ServerInfoHandler - GET /minio/admin/v3/info
// ----------
// Get server information
@@ -1424,12 +1451,6 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
return
}
cfg, err := readServerConfig(ctx, objectAPI)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
buckets := madmin.Buckets{}
objects := madmin.Objects{}
usage := madmin.Usage{}
@@ -1441,7 +1462,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
}
vault := fetchVaultStatus(cfg)
vault := fetchVaultStatus()
ldap := madmin.LDAP{}
if globalLDAPConfig.Enabled {
@@ -1457,10 +1478,10 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
}
}
log, audit := fetchLoggerInfo(cfg)
log, audit := fetchLoggerInfo()
// Get the notification target info
notifyTarget := fetchLambdaInfo(cfg)
notifyTarget := fetchLambdaInfo()
// Fetching the Storage information, ignore any errors.
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
@@ -1482,11 +1503,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
}
}
mode := "safemode"
if newObjectLayerFn() != nil {
mode = "online"
}
mode := "online"
server := getLocalServerProperty(globalEndpoints, r)
servers := globalNotificationSys.ServerInfo()
servers = append(servers, server)
@@ -1500,20 +1517,10 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
Notifications: notifyTarget,
}
// Collect any disk healing.
healing, _ := getAggregatedBackgroundHealState(ctx)
healDisks := make(map[string]struct{}, len(healing.HealDisks))
for _, disk := range healing.HealDisks {
healDisks[disk] = struct{}{}
}
// find all disks which belong to each respective endpoints
for i := range servers {
for _, disk := range storageInfo.Disks {
if strings.Contains(disk.Endpoint, servers[i].Endpoint) {
if _, ok := healDisks[disk.Endpoint]; ok {
disk.Healing = true
}
servers[i].Disks = append(servers[i].Disks, disk)
}
}
@@ -1525,9 +1532,6 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
continue
}
if disk.Endpoint == disk.DrivePath {
if _, ok := healDisks[disk.Endpoint]; ok {
disk.Healing = true
}
servers[len(servers)-1].Disks = append(servers[len(servers)-1].Disks, disk)
}
}
@@ -1553,27 +1557,33 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
return
}
//Reply with storage information (across nodes in a
// Reply with storage information (across nodes in a
// distributed setup) as json.
writeSuccessResponseJSON(w, jsonBytes)
}
func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus {
// Fetch the configured targets
tr := NewGatewayHTTPTransport()
defer tr.CloseIdleConnections()
targetList, err := notify.FetchRegisteredTargets(GlobalContext, cfg, tr, true, false)
if err != nil && err != notify.ErrTargetsOffline {
logger.LogIf(GlobalContext, err)
return nil
}
func fetchLambdaInfo() []map[string][]madmin.TargetIDStatus {
lambdaMap := make(map[string][]madmin.TargetIDStatus)
for targetID, target := range targetList.TargetMap() {
for _, tgt := range globalConfigTargetList.Targets() {
targetIDStatus := make(map[string]madmin.Status)
active, _ := target.IsActive()
active, _ := tgt.IsActive()
targetID := tgt.ID()
if active {
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"}
} else {
targetIDStatus[targetID.ID] = madmin.Status{Status: "Offline"}
}
list := lambdaMap[targetID.Name]
list = append(list, targetIDStatus)
lambdaMap[targetID.Name] = list
}
for _, tgt := range globalEnvTargetList.Targets() {
targetIDStatus := make(map[string]madmin.Status)
active, _ := tgt.IsActive()
targetID := tgt.ID()
if active {
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"}
} else {
@@ -1582,8 +1592,6 @@ func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus {
list := lambdaMap[targetID.Name]
list = append(list, targetIDStatus)
lambdaMap[targetID.Name] = list
// Close any leaking connections
_ = target.Close()
}
notify := make([]map[string][]madmin.TargetIDStatus, len(lambdaMap))
@@ -1598,7 +1606,7 @@ func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus {
}
// fetchVaultStatus fetches Vault Info
func fetchVaultStatus(cfg config.Config) madmin.Vault {
func fetchVaultStatus() madmin.Vault {
vault := madmin.Vault{}
if GlobalKMS == nil {
vault.Status = "disabled"
@@ -1641,41 +1649,42 @@ func fetchVaultStatus(cfg config.Config) madmin.Vault {
}
// fetchLoggerDetails return log info
func fetchLoggerInfo(cfg config.Config) ([]madmin.Logger, []madmin.Audit) {
loggerCfg, _ := logger.LookupConfig(cfg)
var logger []madmin.Logger
var auditlogger []madmin.Audit
for log, l := range loggerCfg.HTTP {
if l.Enabled {
err := checkConnection(l.Endpoint, 15*time.Second)
func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
var loggerInfo []madmin.Logger
var auditloggerInfo []madmin.Audit
for _, target := range logger.Targets {
if target.Endpoint() != "" {
tgt := target.String()
err := checkConnection(target.Endpoint(), 15*time.Second)
if err == nil {
mapLog := make(map[string]madmin.Status)
mapLog[log] = madmin.Status{Status: "Online"}
logger = append(logger, mapLog)
mapLog[tgt] = madmin.Status{Status: "Online"}
loggerInfo = append(loggerInfo, mapLog)
} else {
mapLog := make(map[string]madmin.Status)
mapLog[log] = madmin.Status{Status: "offline"}
logger = append(logger, mapLog)
mapLog[tgt] = madmin.Status{Status: "offline"}
loggerInfo = append(loggerInfo, mapLog)
}
}
}
for audit, l := range loggerCfg.Audit {
if l.Enabled {
err := checkConnection(l.Endpoint, 15*time.Second)
for _, target := range logger.AuditTargets {
if target.Endpoint() != "" {
tgt := target.String()
err := checkConnection(target.Endpoint(), 15*time.Second)
if err == nil {
mapAudit := make(map[string]madmin.Status)
mapAudit[audit] = madmin.Status{Status: "Online"}
auditlogger = append(auditlogger, mapAudit)
mapAudit[tgt] = madmin.Status{Status: "Online"}
auditloggerInfo = append(auditloggerInfo, mapAudit)
} else {
mapAudit := make(map[string]madmin.Status)
mapAudit[audit] = madmin.Status{Status: "Offline"}
auditlogger = append(auditlogger, mapAudit)
mapAudit[tgt] = madmin.Status{Status: "Offline"}
auditloggerInfo = append(auditloggerInfo, mapAudit)
}
}
}
return logger, auditlogger
return loggerInfo, auditloggerInfo
}
// checkConnection - ping an endpoint , return err in case of no connection
+2 -2
View File
@@ -105,8 +105,8 @@ func initTestErasureObjLayer(ctx context.Context) (ObjectLayer, []string, error)
}
globalPolicySys = NewPolicySys()
objLayer := &erasureZones{zones: make([]*erasureSets, 1)}
objLayer.zones[0], err = newErasureSets(ctx, endpoints, storageDisks, format)
objLayer := &erasureServerSets{serverSets: make([]*erasureSets, 1)}
objLayer.serverSets[0], err = newErasureSets(ctx, endpoints, storageDisks, format)
if err != nil {
return nil, nil, err
}
+13 -13
View File
@@ -85,7 +85,7 @@ type healSequenceStatus struct {
// structure to hold state of all heal sequences in server memory
type allHealState struct {
sync.Mutex
sync.RWMutex
// map of heal path to heal sequence
healSeqMap map[string]*healSequence
@@ -105,21 +105,21 @@ func newHealState() *allHealState {
}
func (ahs *allHealState) healDriveCount() int {
ahs.Lock()
defer ahs.Unlock()
ahs.RLock()
defer ahs.RUnlock()
return len(ahs.healLocalDisks)
}
func (ahs *allHealState) getHealLocalDisks() Endpoints {
ahs.Lock()
defer ahs.Unlock()
ahs.RLock()
defer ahs.RUnlock()
var healLocalDisks Endpoints
var endpoints Endpoints
for ep := range ahs.healLocalDisks {
healLocalDisks = append(healLocalDisks, ep)
endpoints = append(endpoints, ep)
}
return healLocalDisks
return endpoints
}
func (ahs *allHealState) popHealLocalDisks(healLocalDisks ...Endpoint) {
@@ -808,7 +808,7 @@ func (h *healSequence) traverseAndHeal() {
func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
return func() error {
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
}
@@ -844,7 +844,7 @@ func (h *healSequence) healDiskFormat() error {
}
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
}
@@ -864,7 +864,7 @@ func (h *healSequence) healBuckets(bucketsOnly bool) error {
}
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
}
@@ -886,7 +886,7 @@ func (h *healSequence) healBuckets(bucketsOnly bool) error {
// healBucket - traverses and heals given bucket
func (h *healSequence) healBucket(bucket string, bucketsOnly bool) error {
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
}
@@ -932,7 +932,7 @@ func (h *healSequence) healBucket(bucket string, bucketsOnly bool) error {
// healObject - heal the given object and record result
func (h *healSequence) healObject(bucket, object, versionID string) error {
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
objectAPI := newObjectLayerFn()
if objectAPI == nil {
return errServerNotInitialized
}
+16 -14
View File
@@ -180,19 +180,19 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
// PutBucketQuotaConfig
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
}
// Bucket replication operations
// GetBucketTargetHandler
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
httpTraceHdrs(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
// SetRemoteTargetHandler
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
// SetRemoteTargetHandler
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
}
// Bucket replication operations
// GetBucketTargetHandler
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
httpTraceHdrs(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
// SetRemoteTargetHandler
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
// SetRemoteTargetHandler
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
}
}
// -- Top APIs --
// Top locks
if globalIsDistErasure {
@@ -214,10 +214,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
// -- OBD API --
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
HandlerFunc(httpTraceHdrs(adminAPI.OBDInfoHandler))
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
HandlerFunc(httpTraceHdrs(adminAPI.BandwidthMonitorHandler))
}
}
// If none of the routes match add default error handler routes
adminRouter.NotFoundHandler = http.HandlerFunc(httpTraceAll(errorResponseHandler))
adminRouter.MethodNotAllowedHandler = http.HandlerFunc(httpTraceAll(errorResponseHandler))
adminRouter.NotFoundHandler = httpTraceAll(errorResponseHandler)
adminRouter.MethodNotAllowedHandler = httpTraceAll(errorResponseHandler)
}
+3 -3
View File
@@ -24,13 +24,13 @@ import (
// getLocalServerProperty - returns madmin.ServerProperties for only the
// local endpoints from given list of endpoints
func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin.ServerProperties {
func getLocalServerProperty(endpointServerSets EndpointServerSets, r *http.Request) madmin.ServerProperties {
addr := r.Host
if globalIsDistErasure {
addr = GetLocalPeer(endpointZones)
addr = GetLocalPeer(endpointServerSets)
}
network := make(map[string]string)
for _, ep := range endpointZones {
for _, ep := range endpointServerSets {
for _, endpoint := range ep.Endpoints {
nodeName := endpoint.Host
if nodeName == "" {
+37 -21
View File
@@ -106,16 +106,17 @@ const (
ErrNoSuchCORSConfiguration
ErrNoSuchWebsiteConfiguration
ErrReplicationConfigurationNotFoundError
ErrReplicationDestinationNotFoundError
ErrRemoteDestinationNotFoundError
ErrReplicationDestinationMissingLock
ErrReplicationTargetNotFoundError
ErrRemoteTargetNotFoundError
ErrReplicationRemoteConnectionError
ErrBucketRemoteIdenticalToSource
ErrBucketRemoteAlreadyExists
ErrBucketRemoteLabelInUse
ErrBucketRemoteArnTypeInvalid
ErrBucketRemoteArnInvalid
ErrBucketRemoteRemoveDisallowed
ErrReplicationTargetNotVersionedError
ErrRemoteTargetNotVersionedError
ErrReplicationSourceNotVersionedError
ErrReplicationNeedsVersioningError
ErrReplicationBucketNeedsVersioningError
@@ -809,9 +810,9 @@ var errorCodes = errorCodeMap{
Description: "The replication configuration was not found",
HTTPStatusCode: http.StatusNotFound,
},
ErrReplicationDestinationNotFoundError: {
Code: "ReplicationDestinationNotFoundError",
Description: "The replication destination bucket does not exist",
ErrRemoteDestinationNotFoundError: {
Code: "RemoteDestinationNotFoundError",
Description: "The remote destination bucket does not exist",
HTTPStatusCode: http.StatusNotFound,
},
ErrReplicationDestinationMissingLock: {
@@ -819,29 +820,34 @@ var errorCodes = errorCodeMap{
Description: "The replication destination bucket does not have object locking enabled",
HTTPStatusCode: http.StatusBadRequest,
},
ErrReplicationTargetNotFoundError: {
Code: "XminioAdminReplicationTargetNotFoundError",
Description: "The replication target does not exist",
ErrRemoteTargetNotFoundError: {
Code: "XMinioAdminRemoteTargetNotFoundError",
Description: "The remote target does not exist",
HTTPStatusCode: http.StatusNotFound,
},
ErrReplicationRemoteConnectionError: {
Code: "XminioAdminReplicationRemoteConnectionError",
Code: "XMinioAdminReplicationRemoteConnectionError",
Description: "Remote service endpoint or target bucket not available",
HTTPStatusCode: http.StatusNotFound,
},
ErrBucketRemoteIdenticalToSource: {
Code: "XminioAdminRemoteIdenticalToSource",
Code: "XMinioAdminRemoteIdenticalToSource",
Description: "The remote target cannot be identical to source",
HTTPStatusCode: http.StatusBadRequest,
},
ErrBucketRemoteAlreadyExists: {
Code: "XminioAdminBucketRemoteAlreadyExists",
Code: "XMinioAdminBucketRemoteAlreadyExists",
Description: "The remote target already exists",
HTTPStatusCode: http.StatusBadRequest,
},
ErrBucketRemoteLabelInUse: {
Code: "XMinioAdminBucketRemoteLabelInUse",
Description: "The remote target with this label already exists",
HTTPStatusCode: http.StatusBadRequest,
},
ErrBucketRemoteRemoveDisallowed: {
Code: "XMinioAdminRemoteRemoveDisallowed",
Description: "Replication configuration exists with this ARN.",
Description: "This ARN is in use by an existing configuration",
HTTPStatusCode: http.StatusBadRequest,
},
ErrBucketRemoteArnTypeInvalid: {
@@ -854,9 +860,9 @@ var errorCodes = errorCodeMap{
Description: "The bucket remote ARN does not have correct format",
HTTPStatusCode: http.StatusBadRequest,
},
ErrReplicationTargetNotVersionedError: {
Code: "ReplicationTargetNotVersionedError",
Description: "The replication target does not have versioning enabled",
ErrRemoteTargetNotVersionedError: {
Code: "RemoteTargetNotVersionedError",
Description: "The remote target does not have versioning enabled",
HTTPStatusCode: http.StatusBadRequest,
},
ErrReplicationSourceNotVersionedError: {
@@ -1906,24 +1912,26 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrAdminNoSuchQuotaConfiguration
case BucketReplicationConfigNotFound:
apiErr = ErrReplicationConfigurationNotFoundError
case BucketReplicationDestinationNotFound:
apiErr = ErrReplicationDestinationNotFoundError
case BucketRemoteDestinationNotFound:
apiErr = ErrRemoteDestinationNotFoundError
case BucketReplicationDestinationMissingLock:
apiErr = ErrReplicationDestinationMissingLock
case BucketRemoteTargetNotFound:
apiErr = ErrReplicationTargetNotFoundError
apiErr = ErrRemoteTargetNotFoundError
case BucketRemoteConnectionErr:
apiErr = ErrReplicationRemoteConnectionError
case BucketRemoteAlreadyExists:
apiErr = ErrBucketRemoteAlreadyExists
case BucketRemoteLabelInUse:
apiErr = ErrBucketRemoteLabelInUse
case BucketRemoteArnTypeInvalid:
apiErr = ErrBucketRemoteArnTypeInvalid
case BucketRemoteArnInvalid:
apiErr = ErrBucketRemoteArnInvalid
case BucketRemoteRemoveDisallowed:
apiErr = ErrBucketRemoteRemoveDisallowed
case BucketReplicationTargetNotVersioned:
apiErr = ErrReplicationTargetNotVersionedError
case BucketRemoteTargetNotVersioned:
apiErr = ErrRemoteTargetNotVersionedError
case BucketReplicationSourceNotVersioned:
apiErr = ErrReplicationSourceNotVersionedError
case BucketQuotaExceeded:
@@ -1956,6 +1964,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrBackendDown
case ObjectNameTooLong:
apiErr = ErrKeyTooLongError
case dns.ErrInvalidBucketName:
apiErr = ErrInvalidBucketName
default:
var ie, iw int
// This work-around is to handle the issue golang/go#30648
@@ -1992,6 +2002,12 @@ func toAPIError(ctx context.Context, err error) APIError {
}
var apiErr = errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
e, ok := err.(dns.ErrInvalidBucketName)
if ok {
code := toAPIErrorCode(ctx, e)
apiErr = errorCodes.ToAPIErrWithErr(code, e)
}
if apiErr.Code == "InternalError" {
// If we see an internal error try to interpret
// any underlying errors if possible depending on
+16 -5
View File
@@ -84,7 +84,7 @@ func setPartsCountHeaders(w http.ResponseWriter, objInfo ObjectInfo) {
}
// Write object header
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec) (err error) {
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) {
// set common headers
setCommonHeaders(w)
@@ -147,15 +147,26 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
}
}
var start, rangeLen int64
totalObjectSize, err := objInfo.GetActualSize()
if err != nil {
return err
}
// for providing ranged content
start, rangeLen, err := rs.GetOffsetLength(totalObjectSize)
if err != nil {
return err
if opts.PartNumber > 0 {
var start, end int64
for i := 0; i < len(objInfo.Parts) && i < opts.PartNumber; i++ {
start = end
end = start + objInfo.Parts[i].ActualSize - 1
}
rs = &HTTPRangeSpec{Start: start, End: end}
rangeLen = end - start + 1
} else {
// for providing ranged content
start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
if err != nil {
return err
}
}
// Set content length.
-8
View File
@@ -703,10 +703,6 @@ func generateMultiDeleteResponse(quiet bool, deletedObjects []DeletedObject, err
}
func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
if newObjectLayerFn() == nil {
// Server still in safe mode.
w.Header().Set(xhttp.MinIOServerStatus, "safemode")
}
setCommonHeaders(w)
if mType != mimeNone {
w.Header().Set(xhttp.ContentType, string(mType))
@@ -773,10 +769,6 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
// The request is from browser and also if browser
// is enabled we need to redirect.
if browser && globalBrowserEnabled {
if newObjectLayerFn() == nil {
// server still in safe mode.
w.Header().Set(xhttp.MinIOServerStatus, "safemode")
}
w.Header().Set(xhttp.Location, minioReservedBucketPath+reqURL.Path)
w.WriteHeader(http.StatusTemporaryRedirect)
return
+2 -15
View File
@@ -32,28 +32,15 @@ func newHTTPServerFn() *xhttp.Server {
return globalHTTPServer
}
func newObjectLayerWithoutSafeModeFn() ObjectLayer {
globalObjLayerMutex.Lock()
defer globalObjLayerMutex.Unlock()
return globalObjectAPI
}
func newObjectLayerFn() ObjectLayer {
globalObjLayerMutex.Lock()
defer globalObjLayerMutex.Unlock()
if globalSafeMode {
return nil
}
return globalObjectAPI
}
func newCachedObjectLayerFn() CacheObjectLayer {
globalObjLayerMutex.Lock()
defer globalObjLayerMutex.Unlock()
if globalSafeMode {
return nil
}
return globalCacheObjectAPI
}
@@ -332,8 +319,8 @@ func registerAPIRouter(router *mux.Router) {
maxClients(collectAPIStats("listbuckets", httpTraceAll(api.ListBucketsHandler))))
// If none of the routes match add default error handler routes
apiRouter.NotFoundHandler = http.HandlerFunc(collectAPIStats("notfound", httpTraceAll(errorResponseHandler)))
apiRouter.MethodNotAllowedHandler = http.HandlerFunc(collectAPIStats("methodnotallowed", httpTraceAll(errorResponseHandler)))
apiRouter.NotFoundHandler = collectAPIStats("notfound", httpTraceAll(errorResponseHandler))
apiRouter.MethodNotAllowedHandler = collectAPIStats("methodnotallowed", httpTraceAll(errorResponseHandler))
}
+8 -6
View File
@@ -334,7 +334,8 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
}
if cred.AccessKey == "" {
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
// Anonymous checks are not meant for ListBuckets action
if globalPolicySys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Action: action,
@@ -378,12 +379,13 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
// Request is allowed return the appropriate access key.
return cred.AccessKey, owner, ErrNone
}
if action == policy.ListBucketVersionsAction {
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
// verify as a fallback.
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Action: iampolicy.Action(policy.ListBucketAction),
Action: iampolicy.ListBucketAction,
BucketName: bucketName,
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
ObjectName: objectName,
@@ -554,7 +556,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
if retMode == objectlock.RetGovernance && byPassSet {
byPassSet = globalPolicySys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Action: policy.Action(policy.BypassGovernanceRetentionAction),
Action: policy.BypassGovernanceRetentionAction,
BucketName: bucketName,
ConditionValues: conditions,
IsOwner: false,
@@ -563,7 +565,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
}
if globalPolicySys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Action: policy.Action(policy.PutObjectRetentionAction),
Action: policy.PutObjectRetentionAction,
BucketName: bucketName,
ConditionValues: conditions,
IsOwner: false,
@@ -586,7 +588,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
if retMode == objectlock.RetGovernance && byPassSet {
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Action: policy.BypassGovernanceRetentionAction,
Action: iampolicy.BypassGovernanceRetentionAction,
BucketName: bucketName,
ObjectName: objectName,
ConditionValues: conditions,
@@ -596,7 +598,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Action: policy.PutObjectRetentionAction,
Action: iampolicy.PutObjectRetentionAction,
BucketName: bucketName,
ConditionValues: conditions,
ObjectName: objectName,
+43 -31
View File
@@ -26,10 +26,20 @@ import (
"github.com/minio/minio/cmd/logger"
)
const defaultMonitorNewDiskInterval = time.Second * 10
const (
defaultMonitorNewDiskInterval = time.Second * 10
healingTrackerFilename = ".healing.bin"
)
//go:generate msgp -file $GOFILE -unexported
type healingTracker struct {
ID string
// future add more tracking capabilities
}
func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
z, ok := objAPI.(*erasureZones)
z, ok := objAPI.(*erasureServerSets)
if !ok {
return
}
@@ -47,9 +57,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
time.Sleep(time.Second)
}
for _, ep := range getLocalDisksToHeal() {
globalBackgroundHealState.pushHealLocalDisks(ep)
}
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
if drivesToHeal := globalBackgroundHealState.healDriveCount(); drivesToHeal > 0 {
logger.Info(fmt.Sprintf("Found drives to heal %d, waiting until %s to heal the content...",
@@ -76,9 +84,11 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
}
// Try to connect to the current endpoint
// and reformat if the current disk is not formatted
_, _, err := connectEndpoint(endpoint)
disk, _, err := connectEndpoint(endpoint)
if errors.Is(err, errUnformattedDisk) {
disksToHeal = append(disksToHeal, endpoint)
} else if err == nil && disk != nil && disk.Healing() {
disksToHeal = append(disksToHeal, disk.Endpoint())
}
}
}
@@ -97,7 +107,7 @@ func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
// monitorLocalDisksAndHeal - ensures that detected new disks are healed
// 1. Only the concerned erasure set will be listed and healed
// 2. Only the node hosting the disk is responsible to perform the heal
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureZones, bgSeq *healSequence) {
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerSets, bgSeq *healSequence) {
// Perform automatic disk healing when a disk is replaced locally.
for {
select {
@@ -106,7 +116,8 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureZones, bgSeq *healS
case <-time.After(defaultMonitorNewDiskInterval):
waitForLowHTTPReq(int32(globalEndpoints.NEndpoints()), time.Second)
var erasureSetInZoneEndpointToHeal []map[int]Endpoints
var erasureSetInZoneDisksToHeal []map[int][]StorageAPI
healDisks := globalBackgroundHealState.getHealLocalDisks()
if len(healDisks) > 0 {
// Reformat disks
@@ -118,59 +129,60 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureZones, bgSeq *healS
logger.Info(fmt.Sprintf("Found drives to heal %d, proceeding to heal content...",
len(healDisks)))
erasureSetInZoneEndpointToHeal = make([]map[int]Endpoints, len(z.zones))
for i := range z.zones {
erasureSetInZoneEndpointToHeal[i] = map[int]Endpoints{}
erasureSetInZoneDisksToHeal = make([]map[int][]StorageAPI, len(z.serverSets))
for i := range z.serverSets {
erasureSetInZoneDisksToHeal[i] = map[int][]StorageAPI{}
}
}
// heal only if new disks found.
for _, endpoint := range healDisks {
// Load the new format of this passed endpoint
_, format, err := connectEndpoint(endpoint)
disk, format, err := connectEndpoint(endpoint)
if err != nil {
printEndpointError(endpoint, err, true)
continue
}
zoneIdx := globalEndpoints.GetLocalZoneIdx(endpoint)
zoneIdx := globalEndpoints.GetLocalZoneIdx(disk.Endpoint())
if zoneIdx < 0 {
continue
}
// Calculate the set index where the current endpoint belongs
setIndex, _, err := findDiskIndex(z.zones[zoneIdx].format, format)
z.serverSets[zoneIdx].erasureDisksMu.RLock()
// Protect reading reference format.
setIndex, _, err := findDiskIndex(z.serverSets[zoneIdx].format, format)
z.serverSets[zoneIdx].erasureDisksMu.RUnlock()
if err != nil {
printEndpointError(endpoint, err, false)
continue
}
erasureSetInZoneEndpointToHeal[zoneIdx][setIndex] = append(erasureSetInZoneEndpointToHeal[zoneIdx][setIndex], endpoint)
erasureSetInZoneDisksToHeal[zoneIdx][setIndex] = append(erasureSetInZoneDisksToHeal[zoneIdx][setIndex], disk)
}
for i, setMap := range erasureSetInZoneEndpointToHeal {
for setIndex, endpoints := range setMap {
for _, ep := range endpoints {
logger.Info("Healing disk '%s' on %s zone", ep, humanize.Ordinal(i+1))
buckets, _ := z.ListBucketsHeal(ctx)
for i, setMap := range erasureSetInZoneDisksToHeal {
for setIndex, disks := range setMap {
for _, disk := range disks {
logger.Info("Healing disk '%s' on %s zone", disk, humanize.Ordinal(i+1))
buckets, err := z.ListBucketsHeal(ctx)
if err != nil {
lbDisks := z.serverSets[i].sets[setIndex].getOnlineDisks()
if err := healErasureSet(ctx, setIndex, buckets, lbDisks); err != nil {
logger.LogIf(ctx, err)
continue
}
if len(buckets) > 0 {
disks := z.zones[i].sets[setIndex].getLoadBalancedDisks()
if err := healErasureSet(ctx, setIndex, buckets, disks, z.zones[i].setDriveCount); err != nil {
logger.LogIf(ctx, err)
continue
}
logger.Info("Healing disk '%s' on %s zone complete", disk, humanize.Ordinal(i+1))
if err := disk.DeleteFile(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix),
healingTrackerFilename); err != nil && !errors.Is(err, errFileNotFound) {
logger.LogIf(ctx, err)
continue
}
logger.Info("Healing disk '%s' on %s zone complete", ep, humanize.Ordinal(i+1))
// Only upon success pop the healed disk.
globalBackgroundHealState.popHealLocalDisks(ep)
globalBackgroundHealState.popHealLocalDisks(disk.Endpoint())
}
}
}
+110
View File
@@ -0,0 +1,110 @@
package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *healingTracker) 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 "ID":
z.ID, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "ID")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 1
// write "ID"
err = en.Append(0x81, 0xa2, 0x49, 0x44)
if err != nil {
return
}
err = en.WriteString(z.ID)
if err != nil {
err = msgp.WrapError(err, "ID")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 1
// string "ID"
o = append(o, 0x81, 0xa2, 0x49, 0x44)
o = msgp.AppendString(o, z.ID)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *healingTracker) 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 "ID":
z.ID, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "ID")
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 healingTracker) Msgsize() (s int) {
s = 1 + 3 + msgp.StringPrefixSize + len(z.ID)
return
}
@@ -0,0 +1,123 @@
package cmd
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"bytes"
"testing"
"github.com/tinylib/msgp/msgp"
)
func TestMarshalUnmarshalhealingTracker(t *testing.T) {
v := healingTracker{}
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 BenchmarkMarshalMsghealingTracker(b *testing.B) {
v := healingTracker{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsghealingTracker(b *testing.B) {
v := healingTracker{}
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 BenchmarkUnmarshalhealingTracker(b *testing.B) {
v := healingTracker{}
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 TestEncodeDecodehealingTracker(t *testing.T) {
v := healingTracker{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodehealingTracker Msgsize() is inaccurate")
}
vn := healingTracker{}
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 BenchmarkEncodehealingTracker(b *testing.B) {
v := healingTracker{}
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 BenchmarkDecodehealingTracker(b *testing.B) {
v := healingTracker{}
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)
}
}
}
+7 -17
View File
@@ -20,7 +20,6 @@ import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@@ -54,7 +53,7 @@ type bootstrapRESTServer struct{}
type ServerSystemConfig struct {
MinioPlatform string
MinioRuntime string
MinioEndpoints EndpointZones
MinioEndpoints EndpointServerSets
}
// Diff - returns error on first difference found in two configs.
@@ -161,9 +160,9 @@ func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg ServerSyst
return srcCfg.Diff(recvCfg)
}
func verifyServerSystemConfig(ctx context.Context, endpointZones EndpointZones) error {
func verifyServerSystemConfig(ctx context.Context, endpointServerSets EndpointServerSets) error {
srcCfg := getServerSystemCfg()
clnts := newBootstrapRESTClients(endpointZones)
clnts := newBootstrapRESTClients(endpointServerSets)
var onlineServers int
var offlineEndpoints []string
var retries int
@@ -198,10 +197,10 @@ func verifyServerSystemConfig(ctx context.Context, endpointZones EndpointZones)
return nil
}
func newBootstrapRESTClients(endpointZones EndpointZones) []*bootstrapRESTClient {
func newBootstrapRESTClients(endpointServerSets EndpointServerSets) []*bootstrapRESTClient {
seenHosts := set.NewStringSet()
var clnts []*bootstrapRESTClient
for _, ep := range endpointZones {
for _, ep := range endpointServerSets {
for _, endpoint := range ep.Endpoints {
if seenHosts.Contains(endpoint.Host) {
continue
@@ -233,18 +232,9 @@ func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
}
}
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultTimeout)
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
restClient.HealthCheckFn = func() bool {
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
// Instantiate a new rest client for healthcheck
// to avoid recursive healthCheckFn()
respBody, err := rest.NewClient(serverURL, trFn, newAuthToken).Call(ctx, bootstrapRESTMethodHealth, nil, nil, -1)
xhttp.DrainBody(respBody)
cancel()
var ne *rest.NetworkError
return !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &ne)
}
restClient.HealthCheckFn = nil
return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
}
+1 -1
View File
@@ -34,7 +34,7 @@ func NewBucketSSEConfigSys() *BucketSSEConfigSys {
// Get - gets bucket encryption config for the given bucket.
func (sys *BucketSSEConfigSys) Get(bucket string) (*bucketsse.BucketSSEConfig, error) {
if globalIsGateway {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
+8 -6
View File
@@ -487,19 +487,21 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Notify deleted event for objects.
for _, dobj := range deletedObjects {
eventName := event.ObjectRemovedDelete
objInfo := ObjectInfo{
Name: dobj.ObjectName,
VersionID: dobj.VersionID,
}
if dobj.DeleteMarker {
objInfo = ObjectInfo{
Name: dobj.ObjectName,
DeleteMarker: dobj.DeleteMarker,
VersionID: dobj.DeleteMarkerVersionID,
}
objInfo.DeleteMarker = dobj.DeleteMarker
objInfo.VersionID = dobj.DeleteMarkerVersionID
eventName = event.ObjectRemovedDeleteMarkerCreated
}
sendEvent(eventArgs{
EventName: event.ObjectRemovedDelete,
EventName: eventName,
BucketName: bucket,
Object: objInfo,
ReqParams: extractReqParams(r),
+1 -1
View File
@@ -31,7 +31,7 @@ type LifecycleSys struct{}
// Get - gets lifecycle config associated to a given bucket name.
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
if globalIsGateway {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
+15 -4
View File
@@ -27,6 +27,7 @@ import (
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/handlers"
"github.com/minio/minio/pkg/sync/errgroup"
)
@@ -113,7 +114,12 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
return
}
if proxyRequestByBucket(ctx, w, r, bucket) {
// Forward the request using Source IP or bucket
forwardStr := handlers.GetSourceIPFromHeaders(r)
if forwardStr == "" {
forwardStr = bucket
}
if proxyRequestByStringHash(ctx, w, r, forwardStr) {
return
}
@@ -340,8 +346,8 @@ func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http
return proxyRequest(ctx, w, r, ep)
}
func proxyRequestByBucket(ctx context.Context, w http.ResponseWriter, r *http.Request, bucket string) (success bool) {
return proxyRequestByNodeIndex(ctx, w, r, crcHashMod(bucket, len(globalProxyEndpoints)))
func proxyRequestByStringHash(ctx context.Context, w http.ResponseWriter, r *http.Request, str string) (success bool) {
return proxyRequestByNodeIndex(ctx, w, r, crcHashMod(str, len(globalProxyEndpoints)))
}
// ListObjectsV1Handler - GET Bucket (List Objects) Version 1.
@@ -382,7 +388,12 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
return
}
if proxyRequestByBucket(ctx, w, r, bucket) {
// Forward the request using Source IP or bucket
forwardStr := handlers.GetSourceIPFromHeaders(r)
if forwardStr == "" {
forwardStr = bucket
}
if proxyRequestByStringHash(ctx, w, r, forwardStr) {
return
}
+26 -13
View File
@@ -49,6 +49,7 @@ func (sys *BucketMetadataSys) Remove(bucket string) {
}
sys.Lock()
delete(sys.metadataMap, bucket)
globalBucketMonitor.DeleteBucket(bucket)
sys.Unlock()
}
@@ -72,7 +73,7 @@ func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
// Update update bucket metadata for the specified config file.
// The configData data should not be modified after being sent here.
func (sys *BucketMetadataSys) Update(bucket string, configFile string, configData []byte) error {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return errServerNotInitialized
}
@@ -275,7 +276,7 @@ func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Life
func (sys *BucketMetadataSys) GetNotificationConfig(bucket string) (*event.Config, error) {
if globalIsGateway && globalGatewayName == NASBackendGateway {
// Only needed in case of NAS gateway.
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
@@ -313,7 +314,7 @@ func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEC
// The returned object may not be modified.
func (sys *BucketMetadataSys) GetPolicyConfig(bucket string) (*policy.Policy, error) {
if globalIsGateway {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
@@ -373,10 +374,24 @@ func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.Buc
return meta.bucketTargetConfig, nil
}
// GetBucketTarget returns the target for the bucket and arn.
func (sys *BucketMetadataSys) GetBucketTarget(bucket string, arn string) (madmin.BucketTarget, error) {
targets, err := sys.GetBucketTargetsConfig(bucket)
if err != nil {
return madmin.BucketTarget{}, err
}
for _, t := range targets.Targets {
if t.Arn == arn {
return t, nil
}
}
return madmin.BucketTarget{}, errConfigNotFound
}
// GetConfig returns a specific configuration from the bucket metadata.
// The returned object may not be modified.
func (sys *BucketMetadataSys) GetConfig(bucket string) (BucketMetadata, error) {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return newBucketMetadata(bucket), errServerNotInitialized
}
@@ -418,12 +433,12 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
}
// Load bucket metadata sys in background
go logger.LogIf(ctx, sys.load(ctx, buckets, objAPI))
go sys.load(ctx, buckets, objAPI)
return nil
}
// concurrently load bucket metadata to speed up loading bucket metadata.
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
g := errgroup.WithNErrs(len(buckets))
for index := range buckets {
index := index
@@ -440,22 +455,20 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
}
for _, err := range g.Wait() {
if err != nil {
return err
logger.LogIf(ctx, err)
}
}
return nil
}
// Loads bucket metadata for all buckets into BucketMetadataSys.
func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
count := 100 // load 100 bucket metadata at a time.
for {
if len(buckets) < count {
return sys.concurrentLoad(ctx, buckets, objAPI)
}
if err := sys.concurrentLoad(ctx, buckets[:count], objAPI); err != nil {
return err
sys.concurrentLoad(ctx, buckets, objAPI)
return
}
sys.concurrentLoad(ctx, buckets[:count], objAPI)
buckets = buckets[count:]
}
}
+8 -2
View File
@@ -107,6 +107,10 @@ func newBucketMetadata(name string) BucketMetadata {
// Load - loads the metadata of bucket by name from ObjectLayer api.
// If an error is returned the returned metadata will be default initialized.
func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error {
if name == "" {
logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
return errors.New("bucket name cannot be empty")
}
configFile := path.Join(bucketConfigPrefix, name, bucketMetadataFile)
data, err := readConfig(ctx, api, configFile)
if err != nil {
@@ -128,20 +132,22 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string)
}
// OK, parse data.
_, err = b.UnmarshalMsg(data[4:])
b.Name = name // in-case parsing failed for some reason, make sure bucket name is not empty.
return err
}
// loadBucketMetadata loads and migrates to bucket metadata.
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
b := newBucketMetadata(bucket)
err := b.Load(ctx, objectAPI, bucket)
err := b.Load(ctx, objectAPI, b.Name)
if err == nil {
return b, b.convertLegacyConfigs(ctx, objectAPI)
}
if err != errConfigNotFound {
if !errors.Is(err, errConfigNotFound) {
return b, err
}
// Old bucket without bucket metadata. Hence we migrate existing settings.
return b, b.convertLegacyConfigs(ctx, objectAPI)
}
+1 -1
View File
@@ -33,7 +33,7 @@ type BucketObjectLockSys struct{}
// Get - Get retention configuration.
func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) {
if globalIsGateway {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return r, errServerNotInitialized
}
+1 -1
View File
@@ -63,7 +63,7 @@ func parseBucketQuota(bucket string, data []byte) (quotaCfg *madmin.BucketQuota,
}
func (sys *BucketQuotaSys) check(ctx context.Context, bucket string, size int64) error {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return errServerNotInitialized
}
+38 -21
View File
@@ -30,6 +30,7 @@ import (
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bucket/bandwidth"
"github.com/minio/minio/pkg/bucket/replication"
"github.com/minio/minio/pkg/event"
iampolicy "github.com/minio/minio/pkg/iam/policy"
@@ -38,7 +39,7 @@ import (
// gets replication config associated to a given bucket name.
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
if globalIsGateway {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil, errServerNotInitialized
}
@@ -52,12 +53,12 @@ func getReplicationConfig(ctx context.Context, bucketName string) (rc *replicati
// validateReplicationDestination returns error if replication destination bucket missing or not configured
// It also returns true if replication destination is same as this server.
func validateReplicationDestination(ctx context.Context, bucket string, rCfg *replication.Config) (bool, error) {
clnt := globalBucketTargetSys.GetReplicationTargetClient(ctx, rCfg.RoleArn)
clnt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rCfg.RoleArn)
if clnt == nil {
return false, BucketRemoteTargetNotFound{Bucket: bucket}
}
if found, _ := clnt.BucketExists(ctx, rCfg.GetDestination().Bucket); !found {
return false, BucketReplicationDestinationNotFound{Bucket: rCfg.GetDestination().Bucket}
return false, BucketRemoteDestinationNotFound{Bucket: rCfg.GetDestination().Bucket}
}
if ret, err := globalBucketObjectLockSys.Get(bucket); err == nil {
if ret.LockEnabled {
@@ -119,7 +120,7 @@ func mustReplicater(ctx context.Context, r *http.Request, bucket, object string,
return cfg.Replicate(opts)
}
func putReplicationOpts(dest replication.Destination, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
func putReplicationOpts(ctx context.Context, dest replication.Destination, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) {
meta := make(map[string]string)
for k, v := range objInfo.UserDefined {
if k == xhttp.AmzBucketReplicationStatus {
@@ -139,15 +140,17 @@ func putReplicationOpts(dest replication.Destination, objInfo ObjectInfo) (putOp
sc = objInfo.StorageClass
}
putOpts = miniogo.PutObjectOptions{
UserMetadata: meta,
UserTags: tag.ToMap(),
ContentType: objInfo.ContentType,
ContentEncoding: objInfo.ContentEncoding,
StorageClass: sc,
ReplicationVersionID: objInfo.VersionID,
ReplicationStatus: miniogo.ReplicationStatusReplica,
ReplicationMTime: objInfo.ModTime,
ReplicationETag: objInfo.ETag,
UserMetadata: meta,
UserTags: tag.ToMap(),
ContentType: objInfo.ContentType,
ContentEncoding: objInfo.ContentEncoding,
StorageClass: sc,
Internal: miniogo.AdvancedPutOptions{
SourceVersionID: objInfo.VersionID,
ReplicationStatus: miniogo.ReplicationStatusReplica,
SourceMTime: objInfo.ModTime,
SourceETag: objInfo.ETag,
},
}
if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok {
rmode := miniogo.RetentionMode(mode)
@@ -166,6 +169,7 @@ func putReplicationOpts(dest replication.Destination, objInfo ObjectInfo) (putOp
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
putOpts.ServerSideEncryption = encrypt.NewSSE()
}
return
}
@@ -180,19 +184,17 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
logger.LogIf(ctx, err)
return
}
tgt := globalBucketTargetSys.GetReplicationTargetClient(ctx, cfg.RoleArn)
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, cfg.RoleArn)
if tgt == nil {
logger.LogIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, cfg.RoleArn))
return
}
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{
VersionID: objInfo.VersionID,
})
if err != nil {
return
}
objInfo = gr.ObjInfo
size, err := objInfo.GetActualSize()
if err != nil {
@@ -223,11 +225,26 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
return
}
}
putOpts := putReplicationOpts(dest, objInfo)
target, err := globalBucketMetadataSys.GetBucketTarget(bucket, cfg.RoleArn)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%s", bucket, cfg.RoleArn, err))
return
}
putOpts := putReplicationOpts(ctx, dest, objInfo)
replicationStatus := replication.Complete
_, err = tgt.PutObject(ctx, dest.Bucket, object, gr, size, "", "", putOpts)
gr.Close()
// Setup bandwidth throttling
totalNodesCount := len(GetRemotePeers(globalEndpoints)) + 1
b := target.BandwidthLimit / int64(totalNodesCount)
var headerSize int
for k, v := range putOpts.Header() {
headerSize += len(k) + len(v)
}
r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b, target.BandwidthLimit)
_, err = tgt.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts)
r.Close()
if err != nil {
replicationStatus = replication.Failed
}
@@ -310,7 +327,7 @@ func newReplicationState() *replicationState {
globalReplicationConcurrent = 1
}
rs := &replicationState{
replicaCh: make(chan ObjectInfo, globalReplicationConcurrent*2),
replicaCh: make(chan ObjectInfo, 10000),
}
go func() {
<-GlobalContext.Done()
+16 -12
View File
@@ -20,6 +20,7 @@ import (
"context"
"net/http"
"sync"
"time"
minio "github.com/minio/minio-go/v7"
miniogo "github.com/minio/minio-go/v7"
@@ -98,10 +99,9 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket}
}
if vcfg.Status != string(versioning.Enabled) {
return BucketReplicationTargetNotVersioned{Bucket: tgt.TargetBucket}
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
}
}
sys.Lock()
defer sys.Unlock()
@@ -113,6 +113,9 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
if t.Arn == tgt.Arn {
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
}
if t.Label == tgt.Label {
return BucketRemoteLabelInUse{Bucket: t.TargetBucket}
}
newtgts[idx] = *tgt
found = true
continue
@@ -173,8 +176,8 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
return nil
}
// GetReplicationTargetClient returns minio-go client for replication target instance
func (sys *BucketTargetSys) GetReplicationTargetClient(ctx context.Context, arn string) *miniogo.Core {
// GetRemoteTargetClient returns minio-go client for replication target instance
func (sys *BucketTargetSys) GetRemoteTargetClient(ctx context.Context, arn string) *miniogo.Core {
sys.RLock()
defer sys.RUnlock()
return sys.arnRemotesMap[arn]
@@ -205,14 +208,14 @@ func (sys *BucketTargetSys) Init(ctx context.Context, buckets []BucketInfo, objA
return nil
}
// UpdateTarget updates target to reflect metadata updates
func (sys *BucketTargetSys) UpdateTarget(bucket string, cfg *madmin.BucketTargets) {
// UpdateAllTargets updates target to reflect metadata updates
func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketTargets) {
if sys == nil {
return
}
sys.Lock()
defer sys.Unlock()
if cfg == nil || cfg.Empty() {
if tgts == nil || tgts.Empty() {
// remove target and arn association
if tgts, ok := sys.targetsMap[bucket]; ok {
for _, t := range tgts {
@@ -223,10 +226,10 @@ func (sys *BucketTargetSys) UpdateTarget(bucket string, cfg *madmin.BucketTarget
return
}
if len(cfg.Targets) > 0 {
sys.targetsMap[bucket] = cfg.Targets
if len(tgts.Targets) > 0 {
sys.targetsMap[bucket] = tgts.Targets
}
for _, tgt := range cfg.Targets {
for _, tgt := range tgts.Targets {
tgtClient, err := sys.getRemoteTargetClient(&tgt)
if err != nil {
continue
@@ -236,7 +239,7 @@ func (sys *BucketTargetSys) UpdateTarget(bucket string, cfg *madmin.BucketTarget
sys.clientsCache[tgtClient.EndpointURL().String()] = tgtClient
}
}
sys.targetsMap[bucket] = cfg.Targets
sys.targetsMap[bucket] = tgts.Targets
}
// create minio-go clients for buckets having remote targets
@@ -279,8 +282,9 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*m
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
getRemoteTargetInstanceTransportOnce.Do(func() {
getRemoteTargetInstanceTransport = NewGatewayHTTPTransport()
getRemoteTargetInstanceTransport = newGatewayHTTPTransport(1 * time.Hour)
})
core, err := miniogo.NewCore(tcfg.Endpoint, &miniogo.Options{
Creds: creds,
Secure: tcfg.Secure,
+8
View File
@@ -70,6 +70,14 @@ func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r
}, r.URL, guessIsBrowserReq(r))
return
}
if _, err := getReplicationConfig(ctx, bucket); err == nil && v.Suspended() {
writeErrorResponse(ctx, w, APIError{
Code: "InvalidBucketState",
Description: "A replication configuration is present on this bucket, so the versioning state cannot be changed.",
HTTPStatusCode: http.StatusConflict,
}, r.URL, guessIsBrowserReq(r))
return
}
configData, err := xml.Marshal(v)
if err != nil {
+23 -13
View File
@@ -21,6 +21,7 @@ import (
"encoding/gob"
"errors"
"fmt"
"math/rand"
"net"
"net/url"
"os"
@@ -33,6 +34,7 @@ import (
"github.com/minio/cli"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/cmd/config"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/certs"
@@ -43,6 +45,9 @@ func init() {
logger.Init(GOPATH, GOROOT)
logger.RegisterError(config.FmtError)
rand.Seed(time.Now().UTC().UnixNano())
globalDNSCache = xhttp.NewDNSCache(3*time.Second, 10*time.Second)
gob.Register(StorageErr(""))
}
@@ -100,11 +105,7 @@ func checkUpdate(mode string) {
return
}
if globalInplaceUpdateDisabled {
logStartupMessage(updateMsg)
} else {
logStartupMessage(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
}
logStartupMessage(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
}
func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) {
@@ -334,14 +335,23 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
return nil, nil, false, err
}
for _, file := range files {
// We exclude any regular file and the "CAs/" directory.
// The "CAs/" directory contains (root) CA certificates
// that MinIO adds to its list of trusted roots (tls.Config.RootCAs).
// Therefore, "CAs/" does not contain X.509 certificates that
// are meant to be served by MinIO.
if !file.IsDir() || file.Name() == "CAs" {
// Ignore all
// - regular files
// - "CAs" directory
// - any directory which starts with ".."
if file.Mode().IsRegular() || file.Name() == "CAs" || strings.HasPrefix(file.Name(), "..") {
continue
}
if file.Mode()&os.ModeSymlink == os.ModeSymlink {
file, err = os.Stat(filepath.Join(root.Name(), file.Name()))
if err != nil {
// not accessible ignore
continue
}
if !file.IsDir() {
continue
}
}
var (
certFile = filepath.Join(root.Name(), file.Name(), publicCertFile)
@@ -350,8 +360,8 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
if !isFile(certFile) || !isFile(keyFile) {
continue
}
if err := manager.AddCertificate(certFile, keyFile); err != nil {
err = fmt.Errorf("Failed to load TLS certificate '%s': %v", certFile, err)
if err = manager.AddCertificate(certFile, keyFile); err != nil {
err = fmt.Errorf("Unable to load TLS certificate '%s,%s': %w", certFile, keyFile, err)
logger.LogIf(GlobalContext, err, logger.Minio)
}
}
+16 -12
View File
@@ -25,9 +25,9 @@ import (
"github.com/minio/minio/cmd/config/api"
"github.com/minio/minio/cmd/config/cache"
"github.com/minio/minio/cmd/config/compress"
"github.com/minio/minio/cmd/config/crawler"
"github.com/minio/minio/cmd/config/dns"
"github.com/minio/minio/cmd/config/etcd"
"github.com/minio/minio/cmd/config/heal"
xldap "github.com/minio/minio/cmd/config/identity/ldap"
"github.com/minio/minio/cmd/config/identity/openid"
"github.com/minio/minio/cmd/config/notify"
@@ -56,7 +56,7 @@ func initHelp() {
config.KmsKesSubSys: crypto.DefaultKesKVS,
config.LoggerWebhookSubSys: logger.DefaultKVS,
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
config.CrawlerSubSys: crawler.DefaultKVS,
config.HealSubSys: heal.DefaultKVS,
}
for k, v := range notify.DefaultNotificationKVS {
kvs[k] = v
@@ -109,8 +109,8 @@ func initHelp() {
Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.",
},
config.HelpKV{
Key: config.CrawlerSubSys,
Description: "manage continuous disk crawling for bucket disk usage, lifecycle, quota and data integrity checks",
Key: config.HealSubSys,
Description: "manage object healing frequency and bitrot verification checks",
},
config.HelpKV{
Key: config.LoggerWebhookSubSys,
@@ -191,7 +191,7 @@ func initHelp() {
config.EtcdSubSys: etcd.Help,
config.CacheSubSys: cache.Help,
config.CompressionSubSys: compress.Help,
config.CrawlerSubSys: crawler.Help,
config.HealSubSys: heal.Help,
config.IdentityOpenIDSubSys: openid.Help,
config.IdentityLDAPSubSys: xldap.Help,
config.PolicyOPASubSys: opa.Help,
@@ -253,7 +253,7 @@ func validateConfig(s config.Config, setDriveCount int) error {
return err
}
if _, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default]); err != nil {
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
return err
}
@@ -438,9 +438,9 @@ func lookupConfigs(s config.Config, setDriveCount int) {
}
}
}
globalCrawlerConfig, err = crawler.LookupConfig(s[config.CrawlerSubSys][config.Default])
globalHealConfig, err = heal.LookupConfig(s[config.HealSubSys][config.Default])
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to read crawler config: %w", err))
logger.LogIf(ctx, fmt.Errorf("Unable to read heal config: %w", err))
}
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
@@ -493,11 +493,13 @@ func lookupConfigs(s config.Config, setDriveCount int) {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize logger: %w", err))
}
for _, l := range loggerCfg.HTTP {
for k, l := range loggerCfg.HTTP {
if l.Enabled {
// Enable http logging
if err = logger.AddTarget(
http.New(http.WithEndpoint(l.Endpoint),
http.New(
http.WithTargetName(k),
http.WithEndpoint(l.Endpoint),
http.WithAuthToken(l.AuthToken),
http.WithUserAgent(loggerUserAgent),
http.WithLogKind(string(logger.All)),
@@ -509,11 +511,13 @@ func lookupConfigs(s config.Config, setDriveCount int) {
}
}
for _, l := range loggerCfg.Audit {
for k, l := range loggerCfg.Audit {
if l.Enabled {
// Enable http audit logging
if err = logger.AddAuditTarget(
http.New(http.WithEndpoint(l.Endpoint),
http.New(
http.WithTargetName(k),
http.WithEndpoint(l.Endpoint),
http.WithAuthToken(l.AuthToken),
http.WithUserAgent(loggerUserAgent),
http.WithLogKind(string(logger.All)),
+2 -3
View File
@@ -19,7 +19,6 @@ package cmd
import (
"bytes"
"context"
"errors"
"fmt"
"time"
"unicode/utf8"
@@ -194,7 +193,7 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
// Config is already encrypted with right keys
continue
}
return errors.New("config data not in plain-text form or encrypted")
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
}
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
@@ -274,7 +273,7 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede
// Config is already encrypted with right keys
continue
}
return errors.New("config data not in plain-text form or encrypted")
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
}
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
+3 -3
View File
@@ -76,7 +76,7 @@ const (
KmsKesSubSys = "kms_kes"
LoggerWebhookSubSys = "logger_webhook"
AuditWebhookSubSys = "audit_webhook"
CrawlerSubSys = "crawler"
HealSubSys = "heal"
// Add new constants here if you add new fields to config.
)
@@ -113,7 +113,7 @@ var SubSystems = set.CreateStringSet([]string{
PolicyOPASubSys,
IdentityLDAPSubSys,
IdentityOpenIDSubSys,
CrawlerSubSys,
HealSubSys,
NotifyAMQPSubSys,
NotifyESSubSys,
NotifyKafkaSubSys,
@@ -140,7 +140,7 @@ var SubSystemsSingleTargets = set.CreateStringSet([]string{
PolicyOPASubSys,
IdentityLDAPSubSys,
IdentityOpenIDSubSys,
CrawlerSubSys,
HealSubSys,
}...)
// Constant separators
+18 -5
View File
@@ -22,10 +22,12 @@ import (
"crypto/x509"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
@@ -78,28 +80,39 @@ func (c *OperatorDNS) Put(bucket string) error {
defer cancel()
e, err := c.endpoint(bucket, false)
if err != nil {
return err
return newError(bucket, err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, e, nil)
if err != nil {
return err
return newError(bucket, err)
}
if err = c.addAuthHeader(req); err != nil {
return err
return newError(bucket, err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
if derr := c.Delete(bucket); derr != nil {
return derr
return newError(bucket, derr)
}
}
var errorStringBuilder strings.Builder
io.Copy(&errorStringBuilder, io.LimitReader(resp.Body, resp.ContentLength))
xhttp.DrainBody(resp.Body)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("request to create the service for bucket %s, failed with status %s", bucket, resp.Status)
errorString := errorStringBuilder.String()
return newError(bucket, fmt.Errorf("service create for bucket %s, failed with status %s, error %s", bucket, resp.Status, errorString))
}
return nil
}
func newError(bucket string, err error) error {
e := Error{bucket, err}
if strings.Contains(err.Error(), "invalid bucket name") {
return ErrInvalidBucketName(e)
}
return e
}
// Delete - Removes DNS entries added in Put().
func (c *OperatorDNS) Delete(bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultOperatorContextTimeout)
+16
View File
@@ -16,6 +16,22 @@
package dns
// Error - DNS related errors error.
type Error struct {
Bucket string
Err error
}
// ErrInvalidBucketName for buckets with invalid name
type ErrInvalidBucketName Error
func (e ErrInvalidBucketName) Error() string {
return "invalid bucket name error: " + e.Err.Error()
}
func (e Error) Error() string {
return "dns related error: " + e.Err.Error()
}
// Store dns record store
type Store interface {
Put(bucket string) error
+4 -7
View File
@@ -19,7 +19,6 @@ package config
import (
"errors"
"fmt"
"io"
"net"
"syscall"
@@ -111,18 +110,16 @@ func ErrorToErr(err error) Err {
case *net.OpError:
return ErrPortAccess(err).Msg("Insufficient permissions to use specified port")
}
return ErrNoPermissionsToAccessDirFiles(err).Msg("Insufficient permissions to access path")
} else if errors.Is(err, io.ErrUnexpectedEOF) {
return ErrUnexpectedDataContent(err)
} else {
// Failed to identify what type of error this, return a simple UI error
return Err{msg: err.Error()}
}
// Failed to identify what type of error this, return a simple UI error
return Err{msg: err.Error()}
}
// FmtError converts a fatal error message to a more clear error
// using some colors
func FmtError(introMsg string, err error, jsonFlag bool) string {
renderedTxt := ""
uiErr := ErrorToErr(err)
// JSON print
-12
View File
@@ -205,12 +205,6 @@ Example 1:
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/minio' to provide sufficient permissions`,
)
ErrNoPermissionsToAccessDirFiles = newErrFn(
"Missing permissions to access the specified path",
"Please ensure the specified path can be accessed",
"",
)
ErrSSLUnexpectedError = newErrFn(
"Invalid TLS certificate",
"Please check the content of your certificate data",
@@ -247,12 +241,6 @@ Example 1:
"",
)
ErrUnexpectedDataContent = newErrFn(
"Unexpected data content",
"Please contact MinIO at https://slack.min.io",
"",
)
ErrUnexpectedError = newErrFn(
"Unexpected error",
"Please contact MinIO at https://slack.min.io",
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package crawler
package heal
import (
"errors"
@@ -24,20 +24,20 @@ import (
// Compression environment variables
const (
BitrotScan = "bitrotscan"
Bitrot = "bitrotscan"
)
// Config represents the crawler settings.
// Config represents the heal settings.
type Config struct {
// Bitrot will perform bitrot scan on local disk when checking objects.
Bitrot bool `json:"bitrotscan"`
}
var (
// DefaultKVS - default KV config for crawler settings
// DefaultKVS - default KV config for heal settings
DefaultKVS = config.KVS{
config.KV{
Key: BitrotScan,
Key: Bitrot,
Value: config.EnableOff,
},
}
@@ -45,7 +45,7 @@ var (
// Help provides help for config values
Help = config.HelpKVS{
config.HelpKV{
Key: BitrotScan,
Key: Bitrot,
Description: `perform bitrot scan on disks when checking objects during crawl`,
Optional: true,
Type: "on|off",
@@ -55,12 +55,12 @@ var (
// LookupConfig - lookup config and override with valid environment settings if any.
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
if err = config.CheckValidKeys(config.CrawlerSubSys, kvs, DefaultKVS); err != nil {
if err = config.CheckValidKeys(config.HealSubSys, kvs, DefaultKVS); err != nil {
return cfg, err
}
bitrot := kvs.Get(BitrotScan)
bitrot := kvs.Get(Bitrot)
if bitrot != config.EnableOn && bitrot != config.EnableOff {
return cfg, errors.New(BitrotScan + ": must be 'on' or 'off'")
return cfg, errors.New(Bitrot + ": must be 'on' or 'off'")
}
cfg.Bitrot = bitrot == config.EnableOn
return cfg, nil
+14 -4
View File
@@ -40,8 +40,8 @@ type HTTPConsoleLoggerSys struct {
logBuf *ring.Ring
}
func mustGetNodeName(endpointZones EndpointZones) (nodeName string) {
host, err := xnet.ParseHost(GetLocalPeer(endpointZones))
func mustGetNodeName(endpointServerSets EndpointServerSets) (nodeName string) {
host, err := xnet.ParseHost(GetLocalPeer(endpointServerSets))
if err != nil {
logger.FatalIf(err, "Unable to start console logging subsystem")
}
@@ -63,8 +63,8 @@ func NewConsoleLogger(ctx context.Context) *HTTPConsoleLoggerSys {
}
// SetNodeName - sets the node name if any after distributed setup has initialized
func (sys *HTTPConsoleLoggerSys) SetNodeName(endpointZones EndpointZones) {
sys.nodeName = mustGetNodeName(endpointZones)
func (sys *HTTPConsoleLoggerSys) SetNodeName(endpointServerSets EndpointServerSets) {
sys.nodeName = mustGetNodeName(endpointServerSets)
}
// HasLogListeners returns true if console log listeners are registered
@@ -122,6 +122,16 @@ func (sys *HTTPConsoleLoggerSys) Validate() error {
return nil
}
// Endpoint - dummy function for interface compatibility
func (sys *HTTPConsoleLoggerSys) Endpoint() string {
return sys.console.Endpoint()
}
// String - stringer function for interface compatibility
func (sys *HTTPConsoleLoggerSys) String() string {
return "console+http"
}
// Content returns the console stdout log
func (sys *HTTPConsoleLoggerSys) Content() (logs []log.Entry) {
sys.RLock()
+28 -17
View File
@@ -29,7 +29,7 @@ import (
"time"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/config/crawler"
"github.com/minio/minio/cmd/config/heal"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/replication"
@@ -53,8 +53,8 @@ const (
)
var (
globalCrawlerConfig crawler.Config
dataCrawlerLeaderLockTimeout = newDynamicTimeout(1*time.Minute, 30*time.Second)
globalHealConfig heal.Config
dataCrawlerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
)
// initDataCrawler will start the crawler unless disabled.
@@ -135,12 +135,11 @@ type cachedFolder struct {
}
type folderScanner struct {
root string
getSize getSizeFn
oldCache dataUsageCache
newCache dataUsageCache
withFilter *bloomFilter
waitForLowActiveIO func()
root string
getSize getSizeFn
oldCache dataUsageCache
newCache dataUsageCache
withFilter *bloomFilter
dataUsageCrawlMult float64
dataUsageCrawlDebug bool
@@ -155,7 +154,7 @@ type folderScanner struct {
// The returned cache will always be valid, but may not be updated from the existing.
// Before each operation waitForLowActiveIO is called which can be used to temporarily halt the crawler.
// If the supplied context is canceled the function will return at the first chance.
func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache, waitForLowActiveIO func(), getSize getSizeFn) (dataUsageCache, error) {
func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache, getSize getSizeFn) (dataUsageCache, error) {
t := UTCNow()
logPrefix := color.Green("data-usage: ")
@@ -183,7 +182,6 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
getSize: getSize,
oldCache: cache,
newCache: dataUsageCache{Info: cache.Info},
waitForLowActiveIO: waitForLowActiveIO,
newFolders: nil,
existingFolders: nil,
dataUsageCrawlMult: delayMult,
@@ -376,7 +374,6 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
}
}
}
f.waitForLowActiveIO()
sleepDuration(dataCrawlSleepPerFolder, f.dataUsageCrawlMult)
cache := dataUsageEntry{}
@@ -424,7 +421,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
}
return nil
}
f.waitForLowActiveIO()
// Dynamic time delay.
t := UTCNow()
@@ -462,7 +459,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
continue
}
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
continue
}
@@ -484,14 +481,22 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
// If that doesn't bring it back we remove the folder and assume it was deleted.
// This means that the next run will not look for it.
for k := range existing {
f.waitForLowActiveIO()
bucket, prefix := path2BucketObject(k)
if f.dataUsageCrawlDebug {
logger.Info(color.Green("folder-scanner:")+" checking disappeared folder: %v/%v", bucket, prefix)
}
// Dynamic time delay.
t := UTCNow()
err = objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{Recursive: true, Remove: healDeleteDangling},
func(bucket, object, versionID string) error {
// Wait for each heal as per crawler frequency.
sleepDuration(time.Since(t), f.dataUsageCrawlMult)
defer func() {
t = UTCNow()
}()
return bgSeq.queueHealTask(healSource{
bucket: bucket,
object: object,
@@ -499,6 +504,8 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
}, madmin.HealItemObject)
})
sleepDuration(time.Since(t), f.dataUsageCrawlMult)
if f.dataUsageCrawlDebug && err != nil {
logger.Info(color.Green("healObjects:")+" checking returned value %v", err)
}
@@ -535,7 +542,6 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
default:
}
f.waitForLowActiveIO()
if typ&os.ModeDir != 0 {
dirStack = append(dirStack, entName)
err := readDirFn(path.Join(dirStack...), addDir)
@@ -749,9 +755,14 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
return size
}
eventName := event.ObjectRemovedDelete
if obj.DeleteMarker {
eventName = event.ObjectRemovedDeleteMarkerCreated
}
// Notify object deleted event.
sendEvent(eventArgs{
EventName: event.ObjectRemovedDelete,
EventName: eventName,
BucketName: i.bucket,
Object: obj,
Host: "Internal: [ILM-EXPIRY]",
+6 -3
View File
@@ -46,7 +46,7 @@ const (
dataUpdateTrackerQueueSize = 10000
dataUpdateTrackerFilename = dataUsageBucket + SlashSeparator + ".tracker.bin"
dataUpdateTrackerVersion = 2
dataUpdateTrackerVersion = 3
dataUpdateTrackerSaveInterval = 5 * time.Minute
)
@@ -236,7 +236,10 @@ func (d *dataUpdateTracker) startSaver(ctx context.Context, interval time.Durati
d.mu.Lock()
if !d.dirty {
d.mu.Unlock()
return
if exit {
return
}
continue
}
d.Saved = UTCNow()
err := d.serialize(&buf)
@@ -363,7 +366,7 @@ func (d *dataUpdateTracker) deserialize(src io.Reader, newerThan time.Time) erro
return err
}
switch tmp[0] {
case 1:
case 1, 2:
logger.Info(color.Green("dataUpdateTracker: ") + "deprecated data version, updating.")
return nil
case dataUpdateTrackerVersion:
+8
View File
@@ -42,6 +42,14 @@ type testingLogger struct {
t testLoggerI
}
func (t *testingLogger) Endpoint() string {
return ""
}
func (t *testingLogger) String() string {
return ""
}
func (t *testingLogger) Validate() error {
return nil
}
+7 -7
View File
@@ -62,7 +62,7 @@ func TestDataUsageUpdate(t *testing.T) {
return 0, nil
}
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, func() {}, getSize)
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
if err != nil {
t.Fatal(err)
}
@@ -183,7 +183,7 @@ func TestDataUsageUpdate(t *testing.T) {
},
}
createUsageTestFiles(t, base, bucket, files)
got, err = crawlDataFolder(context.Background(), base, got, func() {}, getSize)
got, err = crawlDataFolder(context.Background(), base, got, getSize)
if err != nil {
t.Fatal(err)
}
@@ -268,7 +268,7 @@ func TestDataUsageUpdate(t *testing.T) {
}
// Changed dir must be picked up in this many cycles.
for i := 0; i < dataUsageUpdateDirCycles; i++ {
got, err = crawlDataFolder(context.Background(), base, got, func() {}, getSize)
got, err = crawlDataFolder(context.Background(), base, got, getSize)
if err != nil {
t.Fatal(err)
}
@@ -355,7 +355,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
}
return 0, nil
}
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, func() {}, getSize)
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize)
if err != nil {
t.Fatal(err)
}
@@ -465,7 +465,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
},
}
createUsageTestFiles(t, base, "", files)
got, err = crawlDataFolder(context.Background(), base, got, func() {}, getSize)
got, err = crawlDataFolder(context.Background(), base, got, getSize)
if err != nil {
t.Fatal(err)
}
@@ -548,7 +548,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
}
// Changed dir must be picked up in this many cycles.
for i := 0; i < dataUsageUpdateDirCycles; i++ {
got, err = crawlDataFolder(context.Background(), base, got, func() {}, getSize)
got, err = crawlDataFolder(context.Background(), base, got, getSize)
if err != nil {
t.Fatal(err)
}
@@ -652,7 +652,7 @@ func TestDataUsageCacheSerialize(t *testing.T) {
}
return 0, nil
}
want, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, func() {}, getSize)
want, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
if err != nil {
t.Fatal(err)
}
+3 -3
View File
@@ -194,7 +194,7 @@ func (c *diskCache) diskUsageLow() bool {
logger.LogIf(ctx, err)
return false
}
usedPercent := (di.Total - di.Free) * 100 / di.Total
usedPercent := (di.Used / di.Total) * 100
low := int(usedPercent) < gcStopPct
atomic.StoreUint64(&c.stats.UsagePercent, usedPercent)
if low {
@@ -218,7 +218,7 @@ func (c *diskCache) diskSpaceAvailable(size int64) bool {
logger.Info("diskCache: Received 0 total disk size")
return false
}
usedPercent := float64(di.Total-di.Free) * 100 / float64(di.Total)
usedPercent := float64(di.Used) * 100 / float64(di.Total)
if usedPercent >= float64(gcTriggerPct) {
atomic.StoreInt32(&c.stats.UsageState, 1)
c.queueGC()
@@ -226,7 +226,7 @@ func (c *diskCache) diskSpaceAvailable(size int64) bool {
atomic.StoreUint64(&c.stats.UsagePercent, uint64(usedPercent))
// Recalculate percentage with provided size added.
usedPercent = float64(di.Total-di.Free+uint64(size)) * 100 / float64(di.Total)
usedPercent = float64(di.Used+uint64(size)) * 100 / float64(di.Total)
return usedPercent < float64(c.quotaPct)
}
+26 -28
View File
@@ -77,11 +77,11 @@ type cacheObjects struct {
// Cache stats
cacheStats *CacheStats
GetObjectNInfoFn func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObjectFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
PutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
CopyObjectFn func(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error)
InnerGetObjectNInfoFn func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
InnerGetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
InnerDeleteObjectFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
InnerPutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
InnerCopyObjectFn func(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error)
}
func (c *cacheObjects) incHitsToMeta(ctx context.Context, dcache *diskCache, bucket, object string, size int64, eTag string, rs *HTTPRangeSpec) error {
@@ -120,7 +120,7 @@ func (c *cacheObjects) updateMetadataIfChanged(ctx context.Context, dcache *disk
// DeleteObject clears cache entry if backend delete operation succeeds
func (c *cacheObjects) DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
if objInfo, err = c.DeleteObjectFn(ctx, bucket, object, opts); err != nil {
if objInfo, err = c.InnerDeleteObjectFn(ctx, bucket, object, opts); err != nil {
return
}
if c.isCacheExclude(bucket, object) || c.skipCache() {
@@ -188,14 +188,14 @@ func (c *cacheObjects) incCacheStats(size int64) {
func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
if c.isCacheExclude(bucket, object) || c.skipCache() {
return c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
}
var cc *cacheControl
var cacheObjSize int64
// fetch diskCache if object is currently cached or nearest available cache drive
dcache, err := c.getCacheToLoc(ctx, bucket, object)
if err != nil {
return c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
}
cacheReader, numCacheHits, cacheErr := dcache.Get(ctx, bucket, object, rs, h, opts)
@@ -224,14 +224,14 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
if cc != nil && cc.noStore {
cacheReader.Close()
c.cacheStats.incMiss()
bReader, err := c.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
bReader, err := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
bReader.ObjInfo.CacheLookupStatus = CacheHit
bReader.ObjInfo.CacheStatus = CacheMiss
return bReader, err
}
}
objInfo, err := c.GetObjectInfoFn(ctx, bucket, object, opts)
objInfo, err := c.InnerGetObjectInfoFn(ctx, bucket, object, opts)
if backendDownError(err) && cacheErr == nil {
c.incCacheStats(cacheObjSize)
return cacheReader, nil
@@ -255,7 +255,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
cacheReader.Close()
}
c.cacheStats.incMiss()
return c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
}
// skip cache for objects with locks
objRetention := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
@@ -265,7 +265,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
cacheReader.Close()
}
c.cacheStats.incMiss()
return c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
}
if cacheErr == nil {
// if ETag matches for stale cache entry, serve from cache
@@ -283,7 +283,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
// Reaching here implies cache miss
c.cacheStats.incMiss()
bkReader, bkErr := c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
bkReader, bkErr := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
if bkErr != nil {
return bkReader, bkErr
@@ -305,14 +305,12 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
return bkReader, bkErr
}
if rs != nil {
if rs != nil && !dcache.enableRange {
go func() {
// if range caching is disabled, download entire object.
if !dcache.enableRange {
rs = nil
}
rs = nil
// fill cache in the background for range GET requests
bReader, bErr := c.GetObjectNInfoFn(GlobalContext, bucket, object, rs, h, lockType, opts)
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, rs, h, lockType, opts)
if bErr != nil {
return
}
@@ -337,7 +335,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
go func() {
putErr := dcache.Put(ctx, bucket, object,
io.LimitReader(pipeReader, bkReader.ObjInfo.Size),
bkReader.ObjInfo.Size, nil, ObjectOptions{
bkReader.ObjInfo.Size, rs, ObjectOptions{
UserDefined: userDefined,
}, false)
// close the write end of the pipe, so the error gets
@@ -351,7 +349,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
// Returns ObjectInfo from cache if available.
func (c *cacheObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
getObjectInfoFn := c.GetObjectInfoFn
getObjectInfoFn := c.InnerGetObjectInfoFn
if c.isCacheExclude(bucket, object) || c.skipCache() {
return getObjectInfoFn(ctx, bucket, object, opts)
@@ -409,7 +407,7 @@ func (c *cacheObjects) GetObjectInfo(ctx context.Context, bucket, object string,
// CopyObject reverts to backend after evicting any stale cache entries
func (c *cacheObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error) {
copyObjectFn := c.CopyObjectFn
copyObjectFn := c.InnerCopyObjectFn
if c.isCacheExclude(srcBucket, srcObject) || c.skipCache() {
return copyObjectFn(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
}
@@ -596,7 +594,7 @@ func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) {
// PutObject - caches the uploaded object for single Put operations
func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
putObjectFn := c.PutObjectFn
putObjectFn := c.InnerPutObjectFn
dcache, err := c.getCacheToLoc(ctx, bucket, object)
if err != nil {
// disk cache could not be located,execute backend call.
@@ -637,7 +635,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
if err == nil {
go func() {
// fill cache in the background
bReader, bErr := c.GetObjectNInfoFn(GlobalContext, bucket, object, nil, http.Header{}, readLock, ObjectOptions{})
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, nil, http.Header{}, readLock, ObjectOptions{})
if bErr != nil {
return
}
@@ -668,19 +666,19 @@ func newServerCacheObjects(ctx context.Context, config cache.Config) (CacheObjec
migrating: migrateSw,
migMutex: sync.Mutex{},
cacheStats: newCacheStats(),
GetObjectInfoFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
InnerGetObjectInfoFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
return newObjectLayerFn().GetObjectInfo(ctx, bucket, object, opts)
},
GetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
InnerGetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
return newObjectLayerFn().GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
},
DeleteObjectFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
InnerDeleteObjectFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
return newObjectLayerFn().DeleteObject(ctx, bucket, object, opts)
},
PutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
InnerPutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
return newObjectLayerFn().PutObject(ctx, bucket, object, data, opts)
},
CopyObjectFn: func(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error) {
InnerCopyObjectFn: func(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error) {
return newObjectLayerFn().CopyObject(ctx, srcBucket, srcObject, destBucket, destObject, srcInfo, srcOpts, dstOpts)
},
}
+7 -7
View File
@@ -329,7 +329,7 @@ var (
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
// both ellipses and without ellipses transparently.
func createServerEndpoints(serverAddr string, args ...string) (
endpointZones EndpointZones, setupType SetupType, err error) {
endpointServerSets EndpointServerSets, setupType SetupType, err error) {
if len(args) == 0 {
return nil, -1, errInvalidArgument
@@ -352,13 +352,13 @@ func createServerEndpoints(serverAddr string, args ...string) (
if err != nil {
return nil, -1, err
}
endpointZones = append(endpointZones, ZoneEndpoints{
endpointServerSets = append(endpointServerSets, ZoneEndpoints{
SetCount: len(setArgs),
DrivesPerSet: len(setArgs[0]),
Endpoints: endpointList,
})
setupType = newSetupType
return endpointZones, setupType, nil
return endpointServerSets, setupType, nil
}
var prevSetupType SetupType
@@ -374,12 +374,12 @@ func createServerEndpoints(serverAddr string, args ...string) (
return nil, -1, err
}
if setDriveCount != 0 && setDriveCount != len(setArgs[0]) {
return nil, -1, fmt.Errorf("All zones should have same drive per set ratio - expected %d, got %d", setDriveCount, len(setArgs[0]))
return nil, -1, fmt.Errorf("All serverSets should have same drive per set ratio - expected %d, got %d", setDriveCount, len(setArgs[0]))
}
if prevSetupType != UnknownSetupType && prevSetupType != setupType {
return nil, -1, fmt.Errorf("All zones should be of the same setup-type to maintain the original SLA expectations - expected %s, got %s", prevSetupType, setupType)
return nil, -1, fmt.Errorf("All serverSets should be of the same setup-type to maintain the original SLA expectations - expected %s, got %s", prevSetupType, setupType)
}
if err = endpointZones.Add(ZoneEndpoints{
if err = endpointServerSets.Add(ZoneEndpoints{
SetCount: len(setArgs),
DrivesPerSet: len(setArgs[0]),
Endpoints: endpointList,
@@ -393,5 +393,5 @@ func createServerEndpoints(serverAddr string, args ...string) (
prevSetupType = setupType
}
return endpointZones, setupType, nil
return endpointServerSets, setupType, nil
}
+17 -20
View File
@@ -201,12 +201,12 @@ type ZoneEndpoints struct {
Endpoints Endpoints
}
// EndpointZones - list of list of endpoints
type EndpointZones []ZoneEndpoints
// EndpointServerSets - list of list of endpoints
type EndpointServerSets []ZoneEndpoints
// GetLocalZoneIdx returns the zone which endpoint belongs to locally.
// if ep is remote this code will return -1 zoneIndex
func (l EndpointZones) GetLocalZoneIdx(ep Endpoint) int {
func (l EndpointServerSets) GetLocalZoneIdx(ep Endpoint) int {
for i, zep := range l {
for _, cep := range zep.Endpoints {
if cep.IsLocal && ep.IsLocal {
@@ -220,14 +220,14 @@ func (l EndpointZones) GetLocalZoneIdx(ep Endpoint) int {
}
// Add add zone endpoints
func (l *EndpointZones) Add(zeps ZoneEndpoints) error {
func (l *EndpointServerSets) Add(zeps ZoneEndpoints) error {
existSet := set.NewStringSet()
for _, zep := range *l {
for _, ep := range zep.Endpoints {
existSet.Add(ep.String())
}
}
// Validate if there are duplicate endpoints across zones
// Validate if there are duplicate endpoints across serverSets
for _, ep := range zeps.Endpoints {
if existSet.Contains(ep.String()) {
return fmt.Errorf("duplicate endpoints found")
@@ -238,17 +238,17 @@ func (l *EndpointZones) Add(zeps ZoneEndpoints) error {
}
// FirstLocal returns true if the first endpoint is local.
func (l EndpointZones) FirstLocal() bool {
func (l EndpointServerSets) FirstLocal() bool {
return l[0].Endpoints[0].IsLocal
}
// HTTPS - returns true if secure for URLEndpointType.
func (l EndpointZones) HTTPS() bool {
func (l EndpointServerSets) HTTPS() bool {
return l[0].Endpoints.HTTPS()
}
// NEndpoints - returns all nodes count
func (l EndpointZones) NEndpoints() (count int) {
func (l EndpointServerSets) NEndpoints() (count int) {
for _, ep := range l {
count += len(ep.Endpoints)
}
@@ -256,7 +256,7 @@ func (l EndpointZones) NEndpoints() (count int) {
}
// Hostnames - returns list of unique hostnames
func (l EndpointZones) Hostnames() []string {
func (l EndpointServerSets) Hostnames() []string {
foundSet := set.NewStringSet()
for _, ep := range l {
for _, endpoint := range ep.Endpoints {
@@ -688,9 +688,9 @@ func CreateEndpoints(serverAddr string, foundLocal bool, args ...[]string) (Endp
// the first element from the set of peers which indicate that
// they are local. There is always one entry that is local
// even with repeated server endpoints.
func GetLocalPeer(endpointZones EndpointZones) (localPeer string) {
func GetLocalPeer(endpointServerSets EndpointServerSets) (localPeer string) {
peerSet := set.NewStringSet()
for _, ep := range endpointZones {
for _, ep := range endpointServerSets {
for _, endpoint := range ep.Endpoints {
if endpoint.Type() != URLEndpointType {
continue
@@ -713,9 +713,9 @@ func GetLocalPeer(endpointZones EndpointZones) (localPeer string) {
}
// GetRemotePeers - get hosts information other than this minio service.
func GetRemotePeers(endpointZones EndpointZones) []string {
func GetRemotePeers(endpointServerSets EndpointServerSets) []string {
peerSet := set.NewStringSet()
for _, ep := range endpointZones {
for _, ep := range endpointServerSets {
for _, endpoint := range ep.Endpoints {
if endpoint.Type() != URLEndpointType {
continue
@@ -745,12 +745,12 @@ func GetProxyEndpointLocalIndex(proxyEps []ProxyEndpoint) int {
}
// GetProxyEndpoints - get all endpoints that can be used to proxy list request.
func GetProxyEndpoints(endpointZones EndpointZones) ([]ProxyEndpoint, error) {
func GetProxyEndpoints(endpointServerSets EndpointServerSets) ([]ProxyEndpoint, error) {
var proxyEps []ProxyEndpoint
proxyEpSet := set.NewStringSet()
for _, ep := range endpointZones {
for _, ep := range endpointServerSets {
for _, endpoint := range ep.Endpoints {
if endpoint.Type() != URLEndpointType {
continue
@@ -770,11 +770,8 @@ func GetProxyEndpoints(endpointZones EndpointZones) ([]ProxyEndpoint, error) {
}
}
tr := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)()
// Allow more requests to be in flight with higher response header timeout.
tr.ResponseHeaderTimeout = 30 * time.Minute
tr.MaxIdleConns = 64
tr.MaxIdleConnsPerHost = 64
// allow transport to be HTTP/1.1 for proxying.
tr := newCustomHTTPProxyTransport(tlsConfig, rest.DefaultTimeout)()
proxyEps = append(proxyEps, ProxyEndpoint{
Endpoint: endpoint,
+31 -21
View File
@@ -18,6 +18,7 @@ package cmd
import (
"context"
"errors"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio/cmd/logger"
@@ -49,7 +50,7 @@ func (er erasureObjects) MakeBucketWithLocation(ctx context.Context, bucket stri
g.Go(func() error {
if storageDisks[index] != nil {
if err := storageDisks[index].MakeVol(ctx, bucket); err != nil {
if err != errVolumeExists {
if !errors.Is(err, errVolumeExists) {
logger.LogIf(ctx, err)
}
return err
@@ -85,31 +86,40 @@ func undoDeleteBucket(storageDisks []StorageAPI, bucket string) {
// getBucketInfo - returns the BucketInfo from one of the load balanced disks.
func (er erasureObjects) getBucketInfo(ctx context.Context, bucketName string) (bucketInfo BucketInfo, err error) {
var bucketErrs []error
for _, disk := range er.getLoadBalancedDisks() {
if disk == nil {
bucketErrs = append(bucketErrs, errDiskNotFound)
continue
}
volInfo, serr := disk.StatVol(ctx, bucketName)
if serr == nil {
return BucketInfo(volInfo), nil
}
err = serr
// For any reason disk went offline continue and pick the next one.
if IsErrIgnored(err, bucketMetadataOpIgnoredErrs...) {
bucketErrs = append(bucketErrs, err)
continue
}
// Any error which cannot be ignored, we return quickly.
return BucketInfo{}, err
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
var bucketsInfo = make([]BucketInfo, len(storageDisks))
// Undo previous make bucket entry on all underlying storage disks.
for index := range storageDisks {
index := index
g.Go(func() error {
if storageDisks[index] == nil {
return errDiskNotFound
}
volInfo, err := storageDisks[index].StatVol(ctx, bucketName)
if err != nil {
return err
}
bucketsInfo[index] = BucketInfo(volInfo)
return nil
}, index)
}
errs := g.Wait()
for i, err := range errs {
if err == nil {
return bucketsInfo[i], nil
}
}
// If all our errors were ignored, then we try to
// reduce to one error based on read quorum.
// `nil` is deliberately passed for ignoredErrs
// because these errors were already ignored.
readQuorum := getReadQuorum(len(er.getDisks()))
return BucketInfo{}, reduceReadQuorumErrs(ctx, bucketErrs, nil, readQuorum)
readQuorum := getReadQuorum(len(storageDisks))
return BucketInfo{}, reduceReadQuorumErrs(ctx, errs, nil, readQuorum)
}
// GetBucketInfo - returns BucketInfo for a bucket.
+85 -5
View File
@@ -19,6 +19,7 @@ package cmd
import (
"context"
"path"
"sync"
"github.com/minio/minio/pkg/sync/errgroup"
)
@@ -28,17 +29,51 @@ func (er erasureObjects) getLoadBalancedLocalDisks() (newDisks []StorageAPI) {
// Based on the random shuffling return back randomized disks.
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
if disks[i-1] != nil && disks[i-1].IsLocal() {
newDisks = append(newDisks, disks[i-1])
if !disks[i-1].Healing() && disks[i-1].IsOnline() {
newDisks = append(newDisks, disks[i-1])
}
}
}
return newDisks
}
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
disks := er.getDisks()
var wg sync.WaitGroup
var mu sync.Mutex
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
i := i
wg.Add(1)
go func() {
defer wg.Done()
if disks[i-1] == nil {
return
}
di, err := disks[i-1].DiskInfo(context.Background())
if err != nil || di.Healing {
// - Do not consume disks which are not reachable
// unformatted or simply not accessible for some reason.
//
// - Do not consume disks which are being healed
//
// - Future: skip busy disks
return
}
mu.Lock()
newDisks = append(newDisks, disks[i-1])
mu.Unlock()
}()
}
wg.Wait()
return newDisks
}
// getLoadBalancedNDisks - fetches load balanced (sufficiently randomized) disk slice
// with N disks online. If ndisks is zero or negative, then it will returns all disks,
// same if ndisks is greater than the number of all disks.
func (er erasureObjects) getLoadBalancedNDisks(ndisks int) (newDisks []StorageAPI) {
disks := er.getLoadBalancedDisks()
disks := er.getLoadBalancedDisks(ndisks != -1)
for _, disk := range disks {
if disk == nil {
continue
@@ -53,13 +88,58 @@ func (er erasureObjects) getLoadBalancedNDisks(ndisks int) (newDisks []StorageAP
}
// getLoadBalancedDisks - fetches load balanced (sufficiently randomized) disk slice.
func (er erasureObjects) getLoadBalancedDisks() (newDisks []StorageAPI) {
// ensures to skip disks if they are not healing and online.
func (er erasureObjects) getLoadBalancedDisks(optimized bool) []StorageAPI {
disks := er.getDisks()
if !optimized {
var newDisks []StorageAPI
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
newDisks = append(newDisks, disks[i-1])
}
return newDisks
}
var wg sync.WaitGroup
var mu sync.Mutex
var newDisks = map[uint64][]StorageAPI{}
// Based on the random shuffling return back randomized disks.
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
newDisks = append(newDisks, disks[i-1])
i := i
wg.Add(1)
go func() {
defer wg.Done()
if disks[i-1] == nil {
return
}
di, err := disks[i-1].DiskInfo(context.Background())
if err != nil || di.Healing {
// - Do not consume disks which are not reachable
// unformatted or simply not accessible for some reason.
//
// - Do not consume disks which are being healed
//
// - Future: skip busy disks
return
}
mu.Lock()
// Capture disks usage wise upto resolution of MiB
newDisks[di.Used/1024/1024] = append(newDisks[di.Used/1024/1024], disks[i-1])
mu.Unlock()
}()
}
return newDisks
wg.Wait()
var max uint64
for k := range newDisks {
if k > max {
max = k
}
}
// Return disks which have maximum disk usage common.
return newDisks[max]
}
// This function does the following check, suppose
+2 -2
View File
@@ -55,8 +55,8 @@ func TestErasureParentDirIsObject(t *testing.T) {
t.Fatalf("Unexpected object name returned got %s, expected %s", objInfo.Name, objectName)
}
z := obj.(*erasureZones)
xl := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
xl := z.serverSets[0].sets[0]
testCases := []struct {
parentIsObject bool
objectName string
+3 -2
View File
@@ -203,8 +203,9 @@ func TestErasureDecode(t *testing.T) {
// This test is t.Skip()ed as it a long time to run, hence should be run
// explicitly after commenting out t.Skip()
func TestErasureDecodeRandomOffsetLength(t *testing.T) {
// Comment the following line to run this test.
t.SkipNow()
if testing.Short() {
t.Skip()
}
// Initialize environment needed for the test.
dataBlocks := 7
parityBlocks := 7
+31
View File
@@ -160,6 +160,25 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
availableDisks := make([]StorageAPI, len(onlineDisks))
dataErrs := make([]error, len(onlineDisks))
inconsistent := 0
for i, meta := range partsMetadata {
if !meta.IsValid() {
// Since for majority of the cases erasure.Index matches with erasure.Distribution we can
// consider the offline disks as consistent.
continue
}
if meta.Erasure.Distribution[i] != meta.Erasure.Index {
inconsistent++
}
}
erasureDistributionReliable := true
if inconsistent > len(partsMetadata)/2 {
// If there are too many inconsistent files, then we can't trust erasure.Distribution (most likely
// because of bugs found in CopyObject/PutObjectTags) https://github.com/minio/minio/pull/10772
erasureDistributionReliable = false
}
for i, onlineDisk := range onlineDisks {
if errs[i] != nil {
dataErrs[i] = errs[i]
@@ -169,6 +188,18 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
dataErrs[i] = errDiskNotFound
continue
}
if erasureDistributionReliable {
meta := partsMetadata[i]
if !meta.IsValid() {
continue
}
// Since erasure.Distribution is trustable we can fix the mismatching erasure.Index
if meta.Erasure.Distribution[i] != meta.Erasure.Index {
partsMetadata[i] = FileInfo{}
dataErrs[i] = errFileCorrupt
continue
}
}
switch scanMode {
case madmin.HealDeepScan:
+4 -4
View File
@@ -178,8 +178,8 @@ func TestListOnlineDisks(t *testing.T) {
object := "object"
data := bytes.Repeat([]byte("a"), 1024)
z := obj.(*erasureZones)
erasureDisks := z.zones[0].sets[0].getDisks()
z := obj.(*erasureServerSets)
erasureDisks := z.serverSets[0].sets[0].getDisks()
for i, test := range testCases {
_, err = obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{})
if err != nil {
@@ -274,8 +274,8 @@ func TestDisksWithAllParts(t *testing.T) {
// make data with more than one part
partCount := 3
data := bytes.Repeat([]byte("a"), 6*1024*1024*partCount)
z := obj.(*erasureZones)
s := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
s := z.serverSets[0].sets[0]
erasureDisks := s.getDisks()
err = obj.MakeBucketWithLocation(ctx, "bucket", BucketOptions{})
if err != nil {
+3 -2
View File
@@ -449,7 +449,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
}
}
defer er.deleteObject(ctx, minioMetaTmpBucket, tmpID, len(storageDisks)/2+1)
defer er.deleteObject(context.Background(), minioMetaTmpBucket, tmpID, len(storageDisks)/2+1)
// Generate and write `xl.meta` generated from other disks.
outDatedDisks, err = writeUniqueFileInfo(ctx, outDatedDisks, minioMetaTmpBucket, tmpID,
@@ -711,7 +711,8 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
}
if validMeta.Deleted {
return validMeta, false
// notFoundParts is ignored since a delete marker does not have any parts
return validMeta, corruptedErasureMeta+notFoundErasureMeta > len(errs)/2
}
// We couldn't find any valid meta we are indeed corrupted, return true right away.
+13 -13
View File
@@ -42,8 +42,8 @@ func TestHealing(t *testing.T) {
defer obj.Shutdown(context.Background())
defer removeRoots(fsDirs)
z := obj.(*erasureZones)
er := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
er := z.serverSets[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", BucketOptions{})
@@ -67,7 +67,7 @@ func TestHealing(t *testing.T) {
}
disk := er.getDisks()[0]
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), bucket, object, "")
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", false)
if err != nil {
t.Fatal(err)
}
@@ -84,7 +84,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err)
}
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), bucket, object, "")
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", false)
if err != nil {
t.Fatal(err)
}
@@ -113,7 +113,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err)
}
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "")
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", false)
if err != nil {
t.Fatal(err)
}
@@ -197,8 +197,8 @@ func TestHealObjectCorrupted(t *testing.T) {
}
// Test 1: Remove the object backend files from the first disk.
z := objLayer.(*erasureZones)
er := z.zones[0].sets[0]
z := objLayer.(*erasureServerSets)
er := z.serverSets[0].sets[0]
erasureDisks := er.getDisks()
firstDisk := erasureDisks[0]
err = firstDisk.DeleteFile(context.Background(), bucket, pathJoin(object, xlStorageFormatFile))
@@ -342,8 +342,8 @@ func TestHealObjectErasure(t *testing.T) {
}
// Remove the object backend files from the first disk.
z := obj.(*erasureZones)
er := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
er := z.serverSets[0].sets[0]
firstDisk := er.getDisks()[0]
_, err = obj.CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, ObjectOptions{})
@@ -366,7 +366,7 @@ func TestHealObjectErasure(t *testing.T) {
}
erasureDisks := er.getDisks()
z.zones[0].erasureDisksMu.Lock()
z.serverSets[0].erasureDisksMu.Lock()
er.getDisks = func() []StorageAPI {
// Nil more than half the disks, to remove write quorum.
for i := 0; i <= len(erasureDisks)/2; i++ {
@@ -374,7 +374,7 @@ func TestHealObjectErasure(t *testing.T) {
}
return erasureDisks
}
z.zones[0].erasureDisksMu.Unlock()
z.serverSets[0].erasureDisksMu.Unlock()
// Try healing now, expect to receive errDiskNotFound.
_, err = obj.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealDeepScan})
@@ -419,8 +419,8 @@ func TestHealEmptyDirectoryErasure(t *testing.T) {
}
// Remove the object backend files from the first disk.
z := obj.(*erasureZones)
er := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
er := z.serverSets[0].sets[0]
firstDisk := er.getDisks()[0]
err = firstDisk.DeleteVol(context.Background(), pathJoin(bucket, encodeDirObject(object)), true)
if err != nil {
+63 -1
View File
@@ -113,9 +113,21 @@ func hashOrder(key string, cardinality int) []int {
return nums
}
// Reads all `xl.meta` metadata as a FileInfo slice and checks if the data dir exists as well,
// otherwise returns errFileNotFound (or errFileVersionNotFound)
func getAllObjectFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, versionID string) ([]FileInfo, []error) {
return readVersionFromDisks(ctx, disks, bucket, object, versionID, true)
}
// Reads all `xl.meta` metadata as a FileInfo slice.
// Returns error slice indicating the failed metadata reads.
func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, versionID string) ([]FileInfo, []error) {
return readVersionFromDisks(ctx, disks, bucket, object, versionID, false)
}
// Reads all `xl.meta` metadata as a FileInfo slice and checks if the data dir
// exists as well, if checkDataDir is set to true.
func readVersionFromDisks(ctx context.Context, disks []StorageAPI, bucket, object, versionID string, checkDataDir bool) ([]FileInfo, []error) {
metadataArray := make([]FileInfo, len(disks))
g := errgroup.WithNErrs(len(disks))
@@ -126,7 +138,7 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
if disks[index] == nil {
return errDiskNotFound
}
metadataArray[index], err = disks[index].ReadVersion(ctx, bucket, object, versionID)
metadataArray[index], err = disks[index].ReadVersion(ctx, bucket, object, versionID, checkDataDir)
if err != nil {
if err != errFileNotFound && err != errVolumeNotFound && err != errFileVersionNotFound {
logger.GetReqInfo(ctx).AppendTags("disk", disks[index].String())
@@ -141,6 +153,56 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
return metadataArray, g.Wait()
}
func shuffleDisksAndPartsMetadataByIndex(disks []StorageAPI, metaArr []FileInfo, distribution []int) (shuffledDisks []StorageAPI, shuffledPartsMetadata []FileInfo) {
shuffledDisks = make([]StorageAPI, len(disks))
shuffledPartsMetadata = make([]FileInfo, len(disks))
var inconsistent int
for i, meta := range metaArr {
if disks[i] == nil {
// Assuming offline drives as inconsistent,
// to be safe and fallback to original
// distribution order.
inconsistent++
continue
}
// check if erasure distribution order matches the index
// position if this is not correct we discard the disk
// and move to collect others
if distribution[i] != meta.Erasure.Index {
inconsistent++ // keep track of inconsistent entries
continue
}
shuffledDisks[meta.Erasure.Index-1] = disks[i]
shuffledPartsMetadata[meta.Erasure.Index-1] = metaArr[i]
}
// Inconsistent meta info is with in the limit of
// expected quorum, proceed with EcIndex based
// disk order.
if inconsistent < len(disks)/2 {
return shuffledDisks, shuffledPartsMetadata
}
// fall back to original distribution based order.
return shuffleDisksAndPartsMetadata(disks, metaArr, distribution)
}
// Return shuffled partsMetadata depending on distribution.
func shuffleDisksAndPartsMetadata(disks []StorageAPI, partsMetadata []FileInfo, distribution []int) (shuffledDisks []StorageAPI, shuffledPartsMetadata []FileInfo) {
if distribution == nil {
return disks, partsMetadata
}
shuffledDisks = make([]StorageAPI, len(disks))
shuffledPartsMetadata = make([]FileInfo, len(partsMetadata))
// Shuffle slice xl metadata for expected distribution.
for index := range partsMetadata {
blockIndex := distribution[index]
shuffledPartsMetadata[blockIndex-1] = partsMetadata[index]
shuffledDisks[blockIndex-1] = disks[index]
}
return shuffledDisks, shuffledPartsMetadata
}
// Return shuffled partsMetadata depending on distribution.
func shufflePartsMetadata(partsMetadata []FileInfo, distribution []int) (shuffledPartsMetadata []FileInfo) {
if distribution == nil {
+4 -4
View File
@@ -148,13 +148,13 @@ func TestShuffleDisks(t *testing.T) {
t.Fatal(err)
}
defer removeRoots(disks)
z := objLayer.(*erasureZones)
z := objLayer.(*erasureServerSets)
testShuffleDisks(t, z)
}
// Test shuffleDisks which returns shuffled slice of disks for their actual distribution.
func testShuffleDisks(t *testing.T, z *erasureZones) {
disks := z.zones[0].GetDisks(0)()
func testShuffleDisks(t *testing.T, z *erasureServerSets) {
disks := z.serverSets[0].GetDisks(0)()
distribution := []int{16, 14, 12, 10, 8, 6, 4, 2, 1, 3, 5, 7, 9, 11, 13, 15}
shuffledDisks := shuffleDisks(disks, distribution)
// From the "distribution" above you can notice that:
@@ -196,6 +196,6 @@ func TestEvalDisks(t *testing.T) {
t.Fatal(err)
}
defer removeRoots(disks)
z := objLayer.(*erasureZones)
z := objLayer.(*erasureServerSets)
testShuffleDisks(t, z)
}
+5 -2
View File
@@ -91,7 +91,8 @@ func (fi FileInfo) IsValid() bool {
dataBlocks := fi.Erasure.DataBlocks
parityBlocks := fi.Erasure.ParityBlocks
return ((dataBlocks >= parityBlocks) &&
(dataBlocks != 0) && (parityBlocks != 0))
(dataBlocks != 0) && (parityBlocks != 0) &&
(fi.Erasure.Index > 0 && fi.Erasure.Distribution != nil))
}
// ToObjectInfo - Converts metadata to object info.
@@ -213,13 +214,15 @@ func (fi FileInfo) ObjectToPartOffset(ctx context.Context, offset int64) (partIn
func findFileInfoInQuorum(ctx context.Context, metaArr []FileInfo, modTime time.Time, quorum int) (xmv FileInfo, e error) {
metaHashes := make([]string, len(metaArr))
h := sha256.New()
for i, meta := range metaArr {
if meta.IsValid() && meta.ModTime.Equal(modTime) {
h := sha256.New()
for _, part := range meta.Parts {
h.Write([]byte(fmt.Sprintf("part.%d", part.Number)))
}
h.Write([]byte(fmt.Sprintf("%v", meta.Erasure.Distribution)))
metaHashes[i] = hex.EncodeToString(h.Sum(nil))
h.Reset()
}
}
+49
View File
@@ -20,6 +20,7 @@ import (
"context"
"strconv"
"testing"
"time"
humanize "github.com/dustin/go-humanize"
)
@@ -47,6 +48,7 @@ func TestAddObjectPart(t *testing.T) {
// Setup.
fi := newFileInfo("test-object", 8, 8)
fi.Erasure.Index = 1
if !fi.IsValid() {
t.Fatalf("unable to get xl meta")
}
@@ -80,6 +82,7 @@ func TestObjectPartIndex(t *testing.T) {
// Setup.
fi := newFileInfo("test-object", 8, 8)
fi.Erasure.Index = 1
if !fi.IsValid() {
t.Fatalf("unable to get xl meta")
}
@@ -108,6 +111,7 @@ func TestObjectPartIndex(t *testing.T) {
func TestObjectToPartOffset(t *testing.T) {
// Setup.
fi := newFileInfo("test-object", 8, 8)
fi.Erasure.Index = 1
if !fi.IsValid() {
t.Fatalf("unable to get xl meta")
}
@@ -151,3 +155,48 @@ func TestObjectToPartOffset(t *testing.T) {
}
}
}
func TestFindFileInfoInQuorum(t *testing.T) {
getNFInfo := func(n int, quorum int, t int64) []FileInfo {
fi := newFileInfo("test", 8, 8)
fi.AddObjectPart(1, "etag", 100, 100)
fi.ModTime = time.Unix(t, 0)
fis := make([]FileInfo, n)
for i := range fis {
fis[i] = fi
fis[i].Erasure.Index = i + 1
quorum--
if quorum == 0 {
break
}
}
return fis
}
tests := []struct {
fis []FileInfo
modTime time.Time
expectedErr error
}{
{
fis: getNFInfo(16, 16, 1603863445),
modTime: time.Unix(1603863445, 0),
expectedErr: nil,
},
{
fis: getNFInfo(16, 7, 1603863445),
modTime: time.Unix(1603863445, 0),
expectedErr: errErasureReadQuorum,
},
}
for _, test := range tests {
test := test
t.Run("", func(t *testing.T) {
_, err := findFileInfoInQuorum(context.Background(), test.fis, test.modTime, 8)
if err != test.expectedErr {
t.Errorf("Expected %s, got %s", test.expectedErr, err)
}
})
}
}
+39 -33
View File
@@ -43,7 +43,25 @@ func (er erasureObjects) getMultipartSHADir(bucket, object string) string {
// checkUploadIDExists - verify if a given uploadID exists and is valid.
func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object, uploadID string) error {
_, _, _, err := er.getObjectFileInfo(ctx, minioMetaMultipartBucket, er.getUploadIDDir(bucket, object, uploadID), ObjectOptions{})
disks := er.getDisks()
// Read metadata associated with the object from all disks.
metaArr, errs := readAllFileInfo(ctx, disks, minioMetaMultipartBucket, er.getUploadIDDir(bucket, object, uploadID), "")
readQuorum, _, err := objectQuorumFromMeta(ctx, er, metaArr, errs)
if err != nil {
return err
}
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
return toObjectErr(reducedErr, bucket, object)
}
// List all online disks.
_, modTime := listOnlineDisks(disks, metaArr, errs)
// Pick latest valid metadata.
_, err = pickValidFileInfo(ctx, metaArr, modTime, readQuorum)
return err
}
@@ -110,7 +128,7 @@ func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk Sto
}
for _, uploadIDDir := range uploadIDDirs {
uploadIDPath := pathJoin(shaDir, uploadIDDir)
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, uploadIDPath, "")
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, uploadIDPath, "", false)
if err != nil {
continue
}
@@ -124,7 +142,7 @@ func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk Sto
return
}
for _, tmpDir := range tmpDirs {
fi, err := disk.ReadVersion(ctx, minioMetaTmpBucket, tmpDir, "")
fi, err := disk.ReadVersion(ctx, minioMetaTmpBucket, tmpDir, "", false)
if err != nil {
continue
}
@@ -148,10 +166,8 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
result.Delimiter = delimiter
var uploadIDs []string
for _, disk := range er.getLoadBalancedDisks() {
if disk == nil {
continue
}
var disk StorageAPI
for _, disk = range er.getLoadBalancedDisks(true) {
uploadIDs, err = disk.ListDir(ctx, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
if err != nil {
if err == errDiskNotFound {
@@ -176,30 +192,20 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
populatedUploadIds := set.NewStringSet()
retry:
for _, disk := range er.getLoadBalancedDisks() {
if disk == nil {
for _, uploadID := range uploadIDs {
if populatedUploadIds.Contains(uploadID) {
continue
}
for _, uploadID := range uploadIDs {
if populatedUploadIds.Contains(uploadID) {
continue
}
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, pathJoin(er.getUploadIDDir(bucket, object, uploadID)), "")
if err != nil {
if err == errDiskNotFound || err == errFileNotFound {
goto retry
}
return result, toObjectErr(err, bucket, object)
}
populatedUploadIds.Add(uploadID)
uploads = append(uploads, MultipartInfo{
Object: object,
UploadID: uploadID,
Initiated: fi.ModTime,
})
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, pathJoin(er.getUploadIDDir(bucket, object, uploadID)), "", false)
if err != nil {
return result, toObjectErr(err, bucket, object)
}
break
populatedUploadIds.Add(uploadID)
uploads = append(uploads, MultipartInfo{
Object: object,
UploadID: uploadID,
Initiated: fi.ModTime,
})
}
sort.Slice(uploads, func(i int, j int) bool {
@@ -289,13 +295,15 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string,
// Delete the tmp path later in case we fail to commit (ignore
// returned errors) - this will be a no-op in case of a commit
// success.
defer er.deleteObject(ctx, minioMetaTmpBucket, tempUploadIDPath, writeQuorum)
defer er.deleteObject(context.Background(), minioMetaTmpBucket, tempUploadIDPath, writeQuorum)
var partsMetadata = make([]FileInfo, len(onlineDisks))
for i := range onlineDisks {
partsMetadata[i] = fi
}
onlineDisks, partsMetadata = shuffleDisksAndPartsMetadata(onlineDisks, partsMetadata, fi.Erasure.Distribution)
var err error
// Write updated `xl.meta` to all disks.
onlineDisks, err = writeUniqueFileInfo(ctx, onlineDisks, minioMetaTmpBucket, tempUploadIDPath, partsMetadata, writeQuorum)
@@ -408,7 +416,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
tmpPartPath := pathJoin(tmpPart, partSuffix)
// Delete the temporary object part. If PutObjectPart succeeds there would be nothing to delete.
defer er.deleteObject(ctx, minioMetaTmpBucket, tmpPart, writeQuorum)
defer er.deleteObject(context.Background(), minioMetaTmpBucket, tmpPart, writeQuorum)
erasure, err := NewErasure(ctx, fi.Erasure.DataBlocks, fi.Erasure.ParityBlocks, fi.Erasure.BlockSize)
if err != nil {
@@ -737,10 +745,8 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
}
// Order online disks in accordance with distribution order.
onlineDisks = shuffleDisks(onlineDisks, fi.Erasure.Distribution)
// Order parts metadata in accordance with distribution order.
partsMetadata = shufflePartsMetadata(partsMetadata, fi.Erasure.Distribution)
onlineDisks, partsMetadata = shuffleDisksAndPartsMetadataByIndex(onlineDisks, partsMetadata, fi.Erasure.Distribution)
// Save current erasure metadata for validation.
var currentFI = fi
+54 -135
View File
@@ -17,7 +17,6 @@
package cmd
import (
"bytes"
"context"
"errors"
"fmt"
@@ -47,6 +46,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
if !srcInfo.metadataOnly {
return oi, NotImplemented{}
}
defer ObjectPathUpdated(path.Join(dstBucket, dstObject))
lk := er.NewNSLock(ctx, dstBucket, dstObject)
if err := lk.GetLock(globalOperationTimeout); err != nil {
@@ -73,6 +73,8 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
return oi, toObjectErr(err, srcBucket, srcObject)
}
onlineDisks, metaArr = shuffleDisksAndPartsMetadataByIndex(onlineDisks, metaArr, fi.Erasure.Distribution)
if fi.Deleted {
if srcOpts.VersionID == "" {
return oi, toObjectErr(errFileNotFound, srcBucket, srcObject)
@@ -123,10 +125,6 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
// GetObjectNInfo - returns object info and an object
// Read(Closer). When err != nil, the returned reader is always nil.
func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
if err = checkGetObjArgs(ctx, bucket, object); err != nil {
return nil, err
}
var unlockOnDefer bool
var nsUnlocker = func() {}
defer func() {
@@ -153,17 +151,6 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
unlockOnDefer = true
}
// Handler directory request by returning a reader that
// returns no bytes.
if HasSuffix(object, SlashSeparator) {
var objInfo ObjectInfo
if objInfo, err = er.getObjectInfoDir(ctx, bucket, object); err != nil {
return nil, toObjectErr(err, bucket, object)
}
unlockOnDefer = false
return NewGetObjectReaderFromReader(bytes.NewBuffer(nil), objInfo, opts, nsUnlocker)
}
fi, metaArr, onlineDisks, err := er.getObjectFileInfo(ctx, bucket, object, opts)
if err != nil {
return nil, toObjectErr(err, bucket, object)
@@ -208,10 +195,6 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
// startOffset indicates the starting read location of the object.
// length indicates the total length of the object.
func (er erasureObjects) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) error {
if err := checkGetObjArgs(ctx, bucket, object); err != nil {
return err
}
// Lock the object before reading.
lk := er.NewNSLock(ctx, bucket, object)
if err := lk.GetRLock(globalOperationTimeout); err != nil {
@@ -231,22 +214,14 @@ func (er erasureObjects) GetObject(ctx context.Context, bucket, object string, s
return errUnexpected
}
// If its a directory request, we return an empty body.
if HasSuffix(object, SlashSeparator) {
_, err := writer.Write([]byte(""))
logger.LogIf(ctx, err)
return toObjectErr(err, bucket, object)
}
return er.getObject(ctx, bucket, object, startOffset, length, writer, etag, opts)
}
func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions, fi FileInfo, metaArr []FileInfo, onlineDisks []StorageAPI) error {
// Reorder online disks based on erasure distribution order.
onlineDisks = shuffleDisks(onlineDisks, fi.Erasure.Distribution)
// Reorder online disks based on erasure distribution order.
// Reorder parts metadata based on erasure distribution order.
metaArr = shufflePartsMetadata(metaArr, fi.Erasure.Distribution)
onlineDisks, metaArr = shuffleDisksAndPartsMetadataByIndex(onlineDisks, metaArr, fi.Erasure.Distribution)
// For negative length read everything.
if length < 0 {
@@ -366,43 +341,8 @@ func (er erasureObjects) getObject(ctx context.Context, bucket, object string, s
return er.getObjectWithFileInfo(ctx, bucket, object, startOffset, length, writer, etag, opts, fi, metaArr, onlineDisks)
}
// getObjectInfoDir - This getObjectInfo is specific to object directory lookup.
func (er erasureObjects) getObjectInfoDir(ctx context.Context, bucket, object string) (ObjectInfo, error) {
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
// Prepare object creation in a all disks
for index, disk := range storageDisks {
if disk == nil {
continue
}
index := index
g.Go(func() error {
// Check if 'prefix' is an object on this 'disk'.
entries, err := storageDisks[index].ListDir(ctx, bucket, object, 1)
if err != nil {
return err
}
if len(entries) > 0 {
// Not a directory if not empty.
return errFileNotFound
}
return nil
}, index)
}
readQuorum := getReadQuorum(len(storageDisks))
err := reduceReadQuorumErrs(ctx, g.Wait(), objectOpIgnoredErrs, readQuorum)
return dirObjectInfo(bucket, object, 0, map[string]string{}), err
}
// GetObjectInfo - reads object metadata and replies back ObjectInfo.
func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (info ObjectInfo, err error) {
if err = checkGetObjArgs(ctx, bucket, object); err != nil {
return info, err
}
// Lock the object before reading.
lk := er.NewNSLock(ctx, bucket, object)
if err := lk.GetRLock(globalOperationTimeout); err != nil {
@@ -410,14 +350,6 @@ func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object strin
}
defer lk.RUnlock()
if HasSuffix(object, SlashSeparator) {
info, err = er.getObjectInfoDir(ctx, bucket, object)
if err != nil {
return info, toObjectErr(err, bucket, object)
}
return info, nil
}
return er.getObjectInfo(ctx, bucket, object, opts)
}
@@ -425,7 +357,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
disks := er.getDisks()
// Read metadata associated with the object from all disks.
metaArr, errs := readAllFileInfo(ctx, disks, bucket, object, opts.VersionID)
metaArr, errs := getAllObjectFileInfo(ctx, disks, bucket, object, opts.VersionID)
readQuorum, _, err := objectQuorumFromMeta(ctx, er, metaArr, errs)
if err != nil {
@@ -444,7 +376,6 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
if err != nil {
return fi, nil, nil, err
}
return fi, metaArr, onlineDisks, nil
}
@@ -584,11 +515,6 @@ func rename(ctx context.Context, disks []StorageAPI, srcBucket, srcEntry, dstBuc
// writes `xl.meta` which carries the necessary metadata for future
// object operations.
func (er erasureObjects) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
// Validate put object input args.
if err = checkPutObjectArgs(ctx, bucket, object, er, data.Size()); err != nil {
return ObjectInfo{}, err
}
return er.putObject(ctx, bucket, object, data, opts)
}
@@ -658,7 +584,8 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
}
// Order disks according to erasure distribution
onlineDisks := shuffleDisks(storageDisks, fi.Erasure.Distribution)
var onlineDisks []StorageAPI
onlineDisks, partsMetadata = shuffleDisksAndPartsMetadata(storageDisks, partsMetadata, fi.Erasure.Distribution)
erasure, err := NewErasure(ctx, fi.Erasure.DataBlocks, fi.Erasure.ParityBlocks, fi.Erasure.BlockSize)
if err != nil {
@@ -788,11 +715,7 @@ func (er erasureObjects) deleteObjectVersion(ctx context.Context, bucket, object
if disks[index] == nil {
return errDiskNotFound
}
err := disks[index].DeleteVersion(ctx, bucket, object, fi)
if err != nil && err != errVolumeNotFound {
return err
}
return nil
return disks[index].DeleteVersion(ctx, bucket, object, fi)
}, index)
}
@@ -853,16 +776,9 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
dobjects := make([]DeletedObject, len(objects))
writeQuorums := make([]int, len(objects))
for i, object := range objects {
errs[i] = checkDelObjArgs(ctx, bucket, object.ObjectName)
}
storageDisks := er.getDisks()
for i := range objects {
if errs[i] != nil {
continue
}
// Assume (N/2 + 1) quorums for all objects
// this is a theoretical assumption such that
// for delete's we do not need to honor storage
@@ -876,21 +792,19 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
for i := range objects {
if objects[i].VersionID == "" {
if opts.Versioned || opts.VersionSuspended {
if !HasSuffix(objects[i].ObjectName, SlashSeparator) {
fi := FileInfo{
Name: objects[i].ObjectName,
ModTime: UTCNow(),
Deleted: true, // delete marker
}
if opts.Versioned {
fi.VersionID = mustGetUUID()
}
// versioning suspended means we add `null`
// version as delete marker
versions[i] = fi
continue
fi := FileInfo{
Name: objects[i].ObjectName,
ModTime: UTCNow(),
Deleted: true, // delete marker
}
if opts.Versioned {
fi.VersionID = mustGetUUID()
}
// versioning suspended means we add `null`
// version as delete marker
versions[i] = fi
continue
}
}
versions[i] = FileInfo{
@@ -943,11 +857,11 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
dobjects[objIndex] = DeletedObject{
DeleteMarker: versions[objIndex].Deleted,
DeleteMarkerVersionID: versions[objIndex].VersionID,
ObjectName: versions[objIndex].Name,
ObjectName: decodeDirObject(versions[objIndex].Name),
}
} else {
dobjects[objIndex] = DeletedObject{
ObjectName: versions[objIndex].Name,
ObjectName: decodeDirObject(versions[objIndex].Name),
VersionID: versions[objIndex].VersionID,
}
}
@@ -978,13 +892,18 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
// any error as it is not necessary for the handler to reply back a
// response to the client request.
func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
if err = checkDelObjArgs(ctx, bucket, object); err != nil {
return objInfo, err
goi, gerr := er.GetObjectInfo(ctx, bucket, object, opts)
if gerr != nil && goi.Name == "" {
switch gerr.(type) {
case InsufficientReadQuorum:
return objInfo, InsufficientWriteQuorum{}
}
return objInfo, gerr
}
// Acquire a write lock before deleting the object.
lk := er.NewNSLock(ctx, bucket, object)
if err = lk.GetLock(globalOperationTimeout); err != nil {
if err = lk.GetLock(globalDeleteOperationTimeout); err != nil {
return ObjectInfo{}, err
}
defer lk.Unlock()
@@ -994,26 +913,24 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
if opts.VersionID == "" {
if opts.Versioned || opts.VersionSuspended {
if !HasSuffix(object, SlashSeparator) {
fi := FileInfo{
Name: object,
Deleted: true,
ModTime: UTCNow(),
}
if opts.Versioned {
fi.VersionID = mustGetUUID()
}
// versioning suspended means we add `null`
// version as delete marker
// Add delete marker, since we don't have any version specified explicitly.
if err = er.deleteObjectVersion(ctx, bucket, object, writeQuorum, fi); err != nil {
return objInfo, toObjectErr(err, bucket, object)
}
return fi.ToObjectInfo(bucket, object), nil
fi := FileInfo{
Name: object,
Deleted: true,
ModTime: UTCNow(),
}
if opts.Versioned {
fi.VersionID = mustGetUUID()
}
// versioning suspended means we add `null`
// version as delete marker
// Add delete marker, since we don't have any version specified explicitly.
if err = er.deleteObjectVersion(ctx, bucket, object, writeQuorum, fi); err != nil {
return objInfo, toObjectErr(err, bucket, object)
}
return fi.ToObjectInfo(bucket, object), nil
}
}
@@ -1032,7 +949,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
}
}
return ObjectInfo{Bucket: bucket, Name: object, VersionID: opts.VersionID}, nil
return ObjectInfo{Bucket: bucket, Name: decodeDirObject(object), VersionID: opts.VersionID}, nil
}
// Send the successful but partial upload/delete, however ignore
@@ -1057,7 +974,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
}
// List all online disks.
_, modTime := listOnlineDisks(disks, metaArr, errs)
onlineDisks, modTime := listOnlineDisks(disks, metaArr, errs)
// Pick latest valid metadata.
fi, err := pickValidFileInfo(ctx, metaArr, modTime, readQuorum)
@@ -1065,6 +982,8 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
return toObjectErr(err, bucket, object)
}
onlineDisks, metaArr = shuffleDisksAndPartsMetadataByIndex(onlineDisks, metaArr, fi.Erasure.Distribution)
if fi.Deleted {
if opts.VersionID == "" {
return toObjectErr(errFileNotFound, bucket, object)
@@ -1090,12 +1009,12 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
tempObj := mustGetUUID()
// Write unique `xl.meta` for each disk.
if disks, err = writeUniqueFileInfo(ctx, disks, minioMetaTmpBucket, tempObj, metaArr, writeQuorum); err != nil {
if onlineDisks, err = writeUniqueFileInfo(ctx, onlineDisks, minioMetaTmpBucket, tempObj, metaArr, writeQuorum); err != nil {
return toObjectErr(err, bucket, object)
}
// Atomically rename metadata from tmp location to destination for each disk.
if _, err = renameFileInfo(ctx, disks, minioMetaTmpBucket, tempObj, bucket, object, writeQuorum); err != nil {
if _, err = renameFileInfo(ctx, onlineDisks, minioMetaTmpBucket, tempObj, bucket, object, writeQuorum); err != nil {
return toObjectErr(err, bucket, object)
}
+18 -18
View File
@@ -132,8 +132,8 @@ func TestErasureDeleteObjectsErasureSet(t *testing.T) {
for _, dir := range fsDirs {
defer os.RemoveAll(dir)
}
z := obj.(*erasureZones)
xl := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
xl := z.serverSets[0].sets[0]
objs = append(objs, xl)
}
@@ -205,8 +205,8 @@ func TestErasureDeleteObjectDiskNotFound(t *testing.T) {
defer obj.Shutdown(context.Background())
defer removeRoots(fsDirs)
z := obj.(*erasureZones)
xl := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
xl := z.serverSets[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", BucketOptions{})
@@ -225,7 +225,7 @@ func TestErasureDeleteObjectDiskNotFound(t *testing.T) {
// for a 16 disk setup, quorum is 9. To simulate disks not found yet
// quorum is available, we remove disks leaving quorum disks behind.
erasureDisks := xl.getDisks()
z.zones[0].erasureDisksMu.Lock()
z.serverSets[0].erasureDisksMu.Lock()
xl.getDisks = func() []StorageAPI {
for i := range erasureDisks[:7] {
erasureDisks[i] = newNaughtyDisk(erasureDisks[i], nil, errFaultyDisk)
@@ -233,7 +233,7 @@ func TestErasureDeleteObjectDiskNotFound(t *testing.T) {
return erasureDisks
}
z.zones[0].erasureDisksMu.Unlock()
z.serverSets[0].erasureDisksMu.Unlock()
_, err = obj.DeleteObject(ctx, bucket, object, ObjectOptions{})
if err != nil {
t.Fatal(err)
@@ -247,14 +247,14 @@ func TestErasureDeleteObjectDiskNotFound(t *testing.T) {
// Remove one more disk to 'lose' quorum, by setting it to nil.
erasureDisks = xl.getDisks()
z.zones[0].erasureDisksMu.Lock()
z.serverSets[0].erasureDisksMu.Lock()
xl.getDisks = func() []StorageAPI {
erasureDisks[7] = nil
erasureDisks[8] = nil
return erasureDisks
}
z.zones[0].erasureDisksMu.Unlock()
z.serverSets[0].erasureDisksMu.Unlock()
_, err = obj.DeleteObject(ctx, bucket, object, ObjectOptions{})
// since majority of disks are not available, metaquorum is not achieved and hence errErasureWriteQuorum error
if err != toObjectErr(errErasureWriteQuorum, bucket, object) {
@@ -275,8 +275,8 @@ func TestGetObjectNoQuorum(t *testing.T) {
defer obj.Shutdown(context.Background())
defer removeRoots(fsDirs)
z := obj.(*erasureZones)
xl := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
xl := z.serverSets[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", BucketOptions{})
@@ -311,11 +311,11 @@ func TestGetObjectNoQuorum(t *testing.T) {
erasureDisks[i] = newNaughtyDisk(erasureDisks[i], diskErrors, errFaultyDisk)
}
}
z.zones[0].erasureDisksMu.Lock()
z.serverSets[0].erasureDisksMu.Lock()
xl.getDisks = func() []StorageAPI {
return erasureDisks
}
z.zones[0].erasureDisksMu.Unlock()
z.serverSets[0].erasureDisksMu.Unlock()
// Fetch object from store.
err = xl.GetObject(ctx, bucket, object, 0, int64(len("abcd")), ioutil.Discard, "", opts)
if err != toObjectErr(errErasureReadQuorum, bucket, object) {
@@ -338,8 +338,8 @@ func TestPutObjectNoQuorum(t *testing.T) {
defer obj.Shutdown(context.Background())
defer removeRoots(fsDirs)
z := obj.(*erasureZones)
xl := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
xl := z.serverSets[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", BucketOptions{})
@@ -374,11 +374,11 @@ func TestPutObjectNoQuorum(t *testing.T) {
erasureDisks[i] = newNaughtyDisk(erasureDisks[i], diskErrors, errFaultyDisk)
}
}
z.zones[0].erasureDisksMu.Lock()
z.serverSets[0].erasureDisksMu.Lock()
xl.getDisks = func() []StorageAPI {
return erasureDisks
}
z.zones[0].erasureDisksMu.Unlock()
z.serverSets[0].erasureDisksMu.Unlock()
// Upload new content to same object "object"
_, err = obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), opts)
if err != toObjectErr(errErasureWriteQuorum, bucket, object) {
@@ -404,8 +404,8 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
partCount := 3
data := bytes.Repeat([]byte("a"), 6*1024*1024*partCount)
z := obj.(*erasureZones)
xl := z.zones[0].sets[0]
z := obj.(*erasureServerSets)
xl := z.serverSets[0].sets[0]
erasureDisks := xl.getDisks()
ctx, cancel := context.WithCancel(GlobalContext)
File diff suppressed because it is too large Load Diff
+102 -235
View File
@@ -22,6 +22,7 @@ import (
"fmt"
"hash/crc32"
"io"
"math/rand"
"net/http"
"sort"
"sync"
@@ -30,10 +31,11 @@ import (
"github.com/dchest/siphash"
"github.com/google/uuid"
"github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/cmd/config/storageclass"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bpool"
"github.com/minio/minio/pkg/dsync"
"github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/sync/errgroup"
)
@@ -81,7 +83,8 @@ type erasureSets struct {
setCount, setDriveCount int
listTolerancePerSet int
disksConnectEvent chan diskConnectInfo
monitorContextCancel context.CancelFunc
disksConnectEvent chan diskConnectInfo
// Distribution algorithm of choice.
distributionAlgo string
@@ -130,7 +133,7 @@ func (s *erasureSets) getDiskMap() map[string]StorageAPI {
// Initializes a new StorageAPI from the endpoint argument, returns
// StorageAPI and also `format` which exists on the disk.
func connectEndpoint(endpoint Endpoint) (StorageAPI, *formatErasureV3, error) {
disk, err := newStorageAPI(endpoint)
disk, err := newStorageAPIWithoutHealthCheck(endpoint)
if err != nil {
return nil, nil, err
}
@@ -218,24 +221,35 @@ func (s *erasureSets) connectDisks() {
}
return
}
if disk.IsLocal() && disk.Healing() {
globalBackgroundHealState.pushHealLocalDisks(disk.Endpoint())
logger.Info(fmt.Sprintf("Found the drive %s that needs healing, attempting to heal...", disk))
}
s.erasureDisksMu.RLock()
setIndex, diskIndex, err := findDiskIndex(s.format, format)
s.erasureDisksMu.RUnlock()
if err != nil {
if endpoint.IsLocal {
globalBackgroundHealState.pushHealLocalDisks(endpoint)
logger.Info(fmt.Sprintf("Found inconsistent drive %s with format.json, attempting to heal...", endpoint))
} else {
printEndpointError(endpoint, err, false)
}
printEndpointError(endpoint, err, false)
return
}
disk.SetDiskID(format.Erasure.This)
s.erasureDisksMu.Lock()
if s.erasureDisks[setIndex][diskIndex] != nil {
s.erasureDisks[setIndex][diskIndex].Close()
}
s.erasureDisks[setIndex][diskIndex] = disk
if disk.IsLocal() {
disk.SetDiskID(format.Erasure.This)
s.erasureDisks[setIndex][diskIndex] = disk
} else {
// Enable healthcheck disk for remote endpoint.
disk, err = newStorageAPI(endpoint)
if err != nil {
printEndpointError(endpoint, err, false)
return
}
disk.SetDiskID(format.Erasure.This)
s.erasureDisks[setIndex][diskIndex] = disk
}
s.endpointStrings[setIndex*s.setDriveCount+diskIndex] = disk.String()
s.erasureDisksMu.Unlock()
go func(setIndex int) {
@@ -254,6 +268,13 @@ func (s *erasureSets) connectDisks() {
// endpoints by reconnecting them and making sure to place them into right position in
// the set topology, this monitoring happens at a given monitoring interval.
func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInterval time.Duration) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
time.Sleep(time.Duration(r.Float64() * float64(time.Second)))
// Pre-emptively connect the disks if possible.
s.connectDisks()
for {
select {
case <-ctx.Done():
@@ -264,10 +285,28 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
}
}
// GetAllLockers return a list of all lockers for all sets.
func (s *erasureSets) GetAllLockers() []dsync.NetLocker {
allLockers := make([]dsync.NetLocker, s.setDriveCount*s.setCount)
for i, lockers := range s.erasureLockers {
for j, locker := range lockers {
allLockers[i*s.setDriveCount+j] = locker
}
}
return allLockers
}
func (s *erasureSets) GetLockers(setIndex int) func() ([]dsync.NetLocker, string) {
return func() ([]dsync.NetLocker, string) {
lockers := make([]dsync.NetLocker, s.setDriveCount)
copy(lockers, s.erasureLockers[setIndex])
sort.Slice(lockers, func(i, j int) bool {
// re-order lockers with affinity for
// - non-local lockers
// - online lockers
// are used first
return !lockers[i].IsLocal() && lockers[i].IsOnline()
})
return lockers, s.erasureLockOwner
}
}
@@ -306,17 +345,24 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
setDriveCount := len(format.Erasure.Sets[0])
endpointStrings := make([]string, len(endpoints))
listTolerancePerSet := 3
// By default this is off
if env.Get("MINIO_API_LIST_STRICT_QUORUM", config.EnableOn) == config.EnableOn {
listTolerancePerSet = -1
}
// Initialize the erasure sets instance.
s := &erasureSets{
sets: make([]*erasureObjects, setCount),
erasureDisks: make([][]StorageAPI, setCount),
erasureLockers: make([][]dsync.NetLocker, setCount),
erasureLockOwner: mustGetUUID(),
erasureLockOwner: GetLocalPeer(globalEndpoints),
endpoints: endpoints,
endpointStrings: endpointStrings,
setCount: setCount,
setDriveCount: setDriveCount,
listTolerancePerSet: setDriveCount / 2,
listTolerancePerSet: listTolerancePerSet,
format: format,
disksConnectEvent: make(chan diskConnectInfo),
distributionAlgo: format.Erasure.DistributionAlgo,
@@ -373,8 +419,11 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
GlobalStaleUploadsCleanupInterval, GlobalStaleUploadsExpiry)
}
mctx, mctxCancel := context.WithCancel(ctx)
s.monitorContextCancel = mctxCancel
// Start the disk monitoring and connect routine.
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
go s.monitorAndConnectEndpoints(mctx, defaultMonitorConnectEndpointInterval)
go s.maintainMRFList()
go s.healMRFRoutine()
@@ -395,7 +444,7 @@ func (s *erasureSets) SetDriveCount() int {
}
// StorageUsageInfo - combines output of StorageInfo across all erasure coded object sets.
// This only returns disk usage info for Zones to perform placement decision, this call
// This only returns disk usage info for ServerSets to perform placement decision, this call
// is not implemented in Object interface and is not meant to be used by other object
// layer implementations.
func (s *erasureSets) StorageUsageInfo(ctx context.Context) StorageInfo {
@@ -443,7 +492,6 @@ func (s *erasureSets) StorageInfo(ctx context.Context, local bool) (StorageInfo,
storageInfos := make([]StorageInfo, len(s.sets))
storageInfoErrs := make([][]error, len(s.sets))
storageInfo.Backend.Type = BackendErasure
g := errgroup.WithNErrs(len(s.sets))
for index := range s.sets {
@@ -463,17 +511,6 @@ func (s *erasureSets) StorageInfo(ctx context.Context, local bool) (StorageInfo,
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
}
scParity := globalStorageClass.GetParityForSC(storageclass.STANDARD)
if scParity == 0 {
scParity = s.setDriveCount / 2
}
storageInfo.Backend.StandardSCData = s.setDriveCount - scParity
storageInfo.Backend.StandardSCParity = scParity
rrSCParity := globalStorageClass.GetParityForSC(storageclass.RRS)
storageInfo.Backend.RRSCData = s.setDriveCount - rrSCParity
storageInfo.Backend.RRSCParity = rrSCParity
if local {
// if local is true, we are not interested in the drive UUID info.
// this is called primarily by prometheus
@@ -762,6 +799,7 @@ func (s *erasureSets) CopyObject(ctx context.Context, srcBucket, srcObject, dstB
// Check if this request is only metadata update.
if cpSrcDstSame && srcInfo.metadataOnly {
// Version ID is set for the destination and source == destination version ID.
// perform an in-place update.
if dstOpts.VersionID != "" && srcOpts.VersionID == dstOpts.VersionID {
@@ -838,70 +876,6 @@ func (f *FileInfoCh) Push(fi FileInfo) {
f.Valid = true
}
// Calculate lexically least entry across multiple FileInfo channels,
// returns the lexically common entry and the total number of times
// we found this entry. Additionally also returns a boolean
// to indicate if the caller needs to call this function
// again to list the next entry. It is callers responsibility
// if the caller wishes to list N entries to call lexicallySortedEntry
// N times until this boolean is 'false'.
func lexicallySortedEntry(entryChs []FileInfoCh, entries []FileInfo, entriesValid []bool) (FileInfo, int, bool) {
for j := range entryChs {
entries[j], entriesValid[j] = entryChs[j].Pop()
}
var isTruncated = false
for _, valid := range entriesValid {
if !valid {
continue
}
isTruncated = true
break
}
var lentry FileInfo
var found bool
for i, valid := range entriesValid {
if !valid {
continue
}
if !found {
lentry = entries[i]
found = true
continue
}
if entries[i].Name < lentry.Name {
lentry = entries[i]
}
}
// We haven't been able to find any lexically least entry,
// this would mean that we don't have valid entry.
if !found {
return lentry, 0, isTruncated
}
lexicallySortedEntryCount := 0
for i, valid := range entriesValid {
if !valid {
continue
}
// Entries are duplicated across disks,
// we should simply skip such entries.
if lentry.Name == entries[i].Name && lentry.ModTime.Equal(entries[i].ModTime) {
lexicallySortedEntryCount++
continue
}
// Push all entries which are lexically higher
// and will be returned later in Pop()
entryChs[i].Push(entries[i])
}
return lentry, lexicallySortedEntryCount, isTruncated
}
// Calculate lexically least entry across multiple FileInfo channels,
// returns the lexically common entry and the total number of times
// we found this entry. Additionally also returns a boolean
@@ -986,6 +960,7 @@ func (s *erasureSets) startMergeWalksVersionsN(ctx context.Context, bucket, pref
wg.Add(1)
go func(disk StorageAPI) {
defer wg.Done()
entryCh, err := disk.WalkVersions(GlobalContext, bucket, prefix, marker, recursive, endWalkCh)
if err != nil {
return
@@ -1169,7 +1144,7 @@ func formatsToDrivesInfo(endpoints Endpoints, formats []*formatErasureV3, sErrs
// Reloads the format from the disk, usually called by a remote peer notifier while
// healing in a distributed setup.
func (s *erasureSets) ReloadFormat(ctx context.Context, dryRun bool) (err error) {
storageDisks, errs := initStorageDisksWithErrors(s.endpoints)
storageDisks, errs := initStorageDisksWithErrorsWithoutHealthCheck(s.endpoints)
for i, err := range errs {
if err != nil && err != errDiskNotFound {
return fmt.Errorf("Disk %s: %w", s.endpoints[i], err)
@@ -1191,6 +1166,8 @@ func (s *erasureSets) ReloadFormat(ctx context.Context, dryRun bool) (err error)
return err
}
s.monitorContextCancel() // turn-off disk monitoring and replace format.
s.erasureDisksMu.Lock()
// Replace with new reference format.
@@ -1217,11 +1194,25 @@ func (s *erasureSets) ReloadFormat(ctx context.Context, dryRun bool) (err error)
}
s.endpointStrings[m*s.setDriveCount+n] = disk.String()
if !disk.IsLocal() {
// Enable healthcheck disk for remote endpoint.
disk, err = newStorageAPI(disk.Endpoint())
if err != nil {
continue
}
disk.SetDiskID(diskID)
}
s.erasureDisks[m][n] = disk
}
s.erasureDisksMu.Unlock()
mctx, mctxCancel := context.WithCancel(GlobalContext)
s.monitorContextCancel = mctxCancel
go s.monitorAndConnectEndpoints(mctx, defaultMonitorConnectEndpointInterval)
return nil
}
@@ -1279,7 +1270,7 @@ func markRootDisksAsDown(storageDisks []StorageAPI, errs []error) {
// HealFormat - heals missing `format.json` on fresh unformatted disks.
func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.HealResultItem, err error) {
storageDisks, errs := initStorageDisksWithErrors(s.endpoints)
storageDisks, errs := initStorageDisksWithErrorsWithoutHealthCheck(s.endpoints)
for i, derr := range errs {
if derr != nil && derr != errDiskNotFound {
return madmin.HealResultItem{}, fmt.Errorf("Disk %s: %w", s.endpoints[i], derr)
@@ -1305,14 +1296,6 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
return res, err
}
for i, format := range formats {
if format != nil {
if ferr := formatErasureV3Check(refFormat, format); ferr != nil {
sErrs[i] = errUnformattedDisk
}
}
}
// Prepare heal-result
res = madmin.HealResultItem{
Type: madmin.HealItemMetadata,
@@ -1333,45 +1316,12 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
}
if countErrs(sErrs, errUnformattedDisk) == 0 {
// No unformatted disks found disks are either offline
// or online, no healing is required.
return res, errNoHealRequired
}
// Mark all UUIDs which might be offline, use list
// of formats to mark them appropriately.
markUUIDsOffline(refFormat, formats)
// Initialize a new set of set formats which will be written to disk.
newFormatSets := newHealFormatSets(refFormat, s.setCount, s.setDriveCount, formats, sErrs)
// Look for all offline/unformatted disks in our reference format,
// such that we can fill them up with new UUIDs, this looping also
// ensures that the replaced disks allocated evenly across all sets.
// Making sure that the redundancy is not lost.
for i := range refFormat.Erasure.Sets {
for j := range refFormat.Erasure.Sets[i] {
if refFormat.Erasure.Sets[i][j] == offlineDiskUUID {
for l := range newFormatSets[i] {
if newFormatSets[i][l] == nil {
continue
}
if newFormatSets[i][l].Erasure.This == "" {
newFormatSets[i][l].Erasure.This = mustGetUUID()
refFormat.Erasure.Sets[i][j] = newFormatSets[i][l].Erasure.This
for m, v := range res.After.Drives {
if v.Endpoint == s.endpoints.GetString(i*s.setDriveCount+l) {
res.After.Drives[m].UUID = newFormatSets[i][l].Erasure.This
res.After.Drives[m].State = madmin.DriveStateOk
}
}
break
}
}
}
}
}
if !dryRun {
var tmpNewFormats = make([]*formatErasureV3, s.setCount*s.setDriveCount)
for i := range newFormatSets {
@@ -1379,13 +1329,14 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
if newFormatSets[i][j] == nil {
continue
}
res.After.Drives[i*s.setDriveCount+j].UUID = newFormatSets[i][j].Erasure.This
res.After.Drives[i*s.setDriveCount+j].State = madmin.DriveStateOk
tmpNewFormats[i*s.setDriveCount+j] = newFormatSets[i][j]
tmpNewFormats[i*s.setDriveCount+j].Erasure.Sets = refFormat.Erasure.Sets
}
}
// Save formats `format.json` across all disks.
if err = saveFormatErasureAll(ctx, storageDisks, tmpNewFormats); err != nil {
if err = saveFormatErasureAllWithErrs(ctx, storageDisks, sErrs, tmpNewFormats); err != nil {
return madmin.HealResultItem{}, err
}
@@ -1394,6 +1345,8 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
return madmin.HealResultItem{}, err
}
s.monitorContextCancel() // turn-off disk monitoring and replace format.
s.erasureDisksMu.Lock()
// Replace with new reference format.
@@ -1420,10 +1373,23 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
}
s.endpointStrings[m*s.setDriveCount+n] = disk.String()
if !disk.IsLocal() {
// Enable healthcheck disk for remote endpoint.
disk, err = newStorageAPI(disk.Endpoint())
if err != nil {
continue
}
disk.SetDiskID(diskID)
}
s.erasureDisks[m][n] = disk
}
s.erasureDisksMu.Unlock()
mctx, mctxCancel := context.WithCancel(GlobalContext)
s.monitorContextCancel = mctxCancel
go s.monitorAndConnectEndpoints(mctx, defaultMonitorConnectEndpointInterval)
}
return res, nil
@@ -1480,105 +1446,6 @@ func (s *erasureSets) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error)
return listBuckets, nil
}
// Walk a bucket, optionally prefix recursively, until we have returned
// all the content to objectInfo channel, it is callers responsibility
// to allocate a receive channel for ObjectInfo, upon any unhandled
// error walker returns error. Optionally if context.Done() is received
// then Walk() stops the walker.
func (s *erasureSets) Walk(ctx context.Context, bucket, prefix string, results chan<- ObjectInfo, opts ObjectOptions) error {
if err := checkListObjsArgs(ctx, bucket, prefix, "", s); err != nil {
// Upon error close the channel.
close(results)
return err
}
if opts.WalkVersions {
entryChs := s.startMergeWalksVersions(ctx, bucket, prefix, "", true, ctx.Done())
entriesValid := make([]bool, len(entryChs))
entries := make([]FileInfoVersions, len(entryChs))
go func() {
defer close(results)
for {
entry, quorumCount, ok := lexicallySortedEntryVersions(entryChs, entries, entriesValid)
if !ok {
return
}
if quorumCount >= s.setDriveCount/2 {
// Read quorum exists proceed
for _, version := range entry.Versions {
results <- version.ToObjectInfo(bucket, version.Name)
}
}
// skip entries which do not have quorum
}
}()
return nil
}
entryChs := s.startMergeWalks(ctx, bucket, prefix, "", true, ctx.Done())
entriesValid := make([]bool, len(entryChs))
entries := make([]FileInfo, len(entryChs))
go func() {
defer close(results)
for {
entry, quorumCount, ok := lexicallySortedEntry(entryChs, entries, entriesValid)
if !ok {
return
}
if quorumCount >= s.setDriveCount/2 {
// Read quorum exists proceed
results <- entry.ToObjectInfo(bucket, entry.Name)
}
// skip entries which do not have quorum
}
}()
return nil
}
// HealObjects - Heal all objects recursively at a specified prefix, any
// dangling objects deleted as well automatically.
func (s *erasureSets) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObject HealObjectFn) error {
endWalkCh := make(chan struct{})
defer close(endWalkCh)
entryChs := s.startMergeWalksVersions(ctx, bucket, prefix, "", true, endWalkCh)
entriesValid := make([]bool, len(entryChs))
entries := make([]FileInfoVersions, len(entryChs))
for {
entry, quorumCount, ok := lexicallySortedEntryVersions(entryChs, entries, entriesValid)
if !ok {
break
}
if quorumCount == s.setDriveCount && opts.ScanMode == madmin.HealNormalScan {
// Skip good entries.
continue
}
for _, version := range entry.Versions {
// Wait and proceed if there are active requests
waitForLowHTTPReq(int32(s.setDriveCount), time.Second)
if err := healObject(bucket, version.Name, version.VersionID); err != nil {
return toObjectErr(err, bucket, version.Name)
}
}
}
return nil
}
// PutObjectTags - replace or add tags to an existing object
func (s *erasureSets) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) error {
return s.getHashedSet(object).PutObjectTags(ctx, bucket, object, tags, opts)
+2 -2
View File
@@ -213,8 +213,8 @@ func TestHashedLayer(t *testing.T) {
defer os.RemoveAll(dir)
}
z := obj.(*erasureZones)
objs = append(objs, z.zones[0].sets[0])
z := obj.(*erasureServerSets)
objs = append(objs, z.serverSets[0].sets[0])
}
sets := &erasureSets{sets: objs, distributionAlgo: "CRCMOD"}
+10 -50
View File
@@ -81,14 +81,6 @@ func (er erasureObjects) SetDriveCount() int {
func (er erasureObjects) Shutdown(ctx context.Context) error {
// Add any object layer shutdown activities here.
closeStorageDisks(er.getDisks())
select {
case _, ok := <-er.mrfOpCh:
if ok {
close(er.mrfOpCh)
}
default:
close(er.mrfOpCh)
}
return nil
}
@@ -149,16 +141,6 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
return errDiskNotFound
}
info, err := disks[index].DiskInfo(context.TODO())
if err != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("disk", disks[index].String())
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
logger.LogIf(ctx, err)
disksInfo[index] = madmin.Disk{
State: diskErrToDriveState(err),
Endpoint: endpoints[index],
}
return err
}
di := madmin.Disk{
Endpoint: endpoints[index],
DrivePath: info.MountPath,
@@ -167,6 +149,7 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
AvailableSpace: info.Free,
UUID: info.ID,
RootDisk: info.RootDisk,
Healing: info.Healing,
State: diskErrToDriveState(err),
}
if info.Total > 0 {
@@ -181,7 +164,7 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
// Wait for the routines.
for i, diskInfoErr := range errs {
ep := disksInfo[i].Endpoint
if diskInfoErr != nil {
if diskInfoErr != nil && !errors.Is(diskInfoErr, errUnformattedDisk) {
offlineDisks[ep]++
continue
}
@@ -195,7 +178,9 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
}
}
if len(disksInfo) == rootDiskCount {
// Count offline disks as well to ensure consistent
// reportability of offline drives on local setups.
if len(disksInfo) == (rootDiskCount + offlineDisks.Sum()) {
// Success.
return disksInfo, errs, onlineDisks, offlineDisks
}
@@ -256,46 +241,20 @@ func (er erasureObjects) StorageInfo(ctx context.Context, local bool) (StorageIn
// Updates are sent on a regular basis and the caller *must* consume them.
func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []BucketInfo, bf *bloomFilter, updates chan<- dataUsageCache) error {
if len(buckets) == 0 {
logger.Info(color.Green("data-crawl:") + " No buckets found, skipping crawl")
return nil
}
// Collect any disk healing.
healing, err := getAggregatedBackgroundHealState(ctx)
if err != nil {
return err
}
healDisks := make(map[string]struct{}, len(healing.HealDisks))
for _, disk := range healing.HealDisks {
healDisks[disk] = struct{}{}
}
// Collect disks we can use.
var disks []StorageAPI
for _, d := range er.getLoadBalancedDisks() {
if d == nil || !d.IsOnline() {
continue
}
di, err := d.DiskInfo(ctx)
if err != nil {
logger.LogIf(ctx, err)
continue
}
if _, ok := healDisks[di.Endpoint]; ok {
logger.Info(color.Green("data-crawl:")+" Disk %q is Healing, skipping disk.", di.Endpoint)
continue
}
disks = append(disks, d)
}
disks := er.getOnlineDisks()
if len(disks) == 0 {
logger.Info(color.Green("data-crawl:") + " No disks found, skipping crawl")
logger.Info(color.Green("data-crawl:") + " all disks are offline or being healed, skipping crawl")
return nil
}
// Load bucket totals
oldCache := dataUsageCache{}
err = oldCache.load(ctx, er, dataUsageCacheName)
if err != nil {
if err := oldCache.load(ctx, er, dataUsageCacheName); err != nil {
return err
}
@@ -403,6 +362,7 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
// Calc usage
before := cache.Info.LastUpdate
var err error
cache, err = disk.CrawlAndGetDataUsage(ctx, cache)
cache.Info.BloomFilter = nil
if err != nil {
+71 -75
View File
@@ -21,6 +21,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"reflect"
@@ -130,13 +131,17 @@ func (f *formatErasureV3) Clone() *formatErasureV3 {
}
// Returns formatErasure.Erasure.Version
func newFormatErasureV3(numSets int, setLen int) *formatErasureV3 {
func newFormatErasureV3(numSets int, setLen int, distributionAlgo string) *formatErasureV3 {
format := &formatErasureV3{}
format.Version = formatMetaVersionV1
format.Format = formatBackendErasure
format.ID = mustGetUUID()
format.Erasure.Version = formatErasureVersionV3
format.Erasure.DistributionAlgo = formatErasureVersionV3DistributionAlgo
if distributionAlgo == "" {
format.Erasure.DistributionAlgo = formatErasureVersionV3DistributionAlgo
} else {
format.Erasure.DistributionAlgo = distributionAlgo
}
format.Erasure.Sets = make([][]string, numSets)
for i := 0; i < numSets; i++ {
@@ -335,7 +340,7 @@ func loadFormatErasureAll(storageDisks []StorageAPI, heal bool) ([]*formatErasur
return formats, g.Wait()
}
func saveFormatErasure(disk StorageAPI, format *formatErasureV3) error {
func saveFormatErasure(disk StorageAPI, format *formatErasureV3, heal bool) error {
if disk == nil || format == nil {
return errDiskNotFound
}
@@ -368,6 +373,18 @@ func saveFormatErasure(disk StorageAPI, format *formatErasureV3) error {
}
disk.SetDiskID(diskID)
if heal {
htracker := healingTracker{
ID: diskID,
}
htrackerBytes, err := htracker.MarshalMsg(nil)
if err != nil {
return err
}
return disk.WriteAll(context.TODO(), minioMetaBucket,
pathJoin(bucketMetaPrefix, slashSeparator, healingTrackerFilename),
bytes.NewReader(htrackerBytes))
}
return nil
}
@@ -551,7 +568,8 @@ func formatErasureFixLocalDeploymentID(endpoints Endpoints, storageDisks []Stora
return nil
}
format.ID = refFormat.ID
if err := saveFormatErasure(storageDisks[index], format); err != nil {
// Heal the drive if we fixed its deployment ID.
if err := saveFormatErasure(storageDisks[index], format, true); err != nil {
logger.LogIf(GlobalContext, err)
return fmt.Errorf("Unable to save format.json, %w", err)
}
@@ -631,8 +649,8 @@ func formatErasureV3Check(reference *formatErasureV3, format *formatErasureV3) e
}
for j := range reference.Erasure.Sets[i] {
if reference.Erasure.Sets[i][j] != format.Erasure.Sets[i][j] {
return fmt.Errorf("UUID on positions %d:%d do not match with, expected %s got %s",
i, j, reference.Erasure.Sets[i][j], format.Erasure.Sets[i][j])
return fmt.Errorf("UUID on positions %d:%d do not match with, expected %s got %s: (%w)",
i, j, reference.Erasure.Sets[i][j], format.Erasure.Sets[i][j], errInconsistentDisk)
}
}
}
@@ -686,6 +704,30 @@ func initErasureMetaVolumesInLocalDisks(storageDisks []StorageAPI, formats []*fo
return nil
}
// saveFormatErasureAllWithErrs - populates `format.json` on disks in its order.
// also adds `.healing.bin` on the disks which are being actively healed.
func saveFormatErasureAllWithErrs(ctx context.Context, storageDisks []StorageAPI, fErrs []error, formats []*formatErasureV3) error {
g := errgroup.WithNErrs(len(storageDisks))
// Write `format.json` to all disks.
for index := range storageDisks {
index := index
g.Go(func() error {
if formats[index] == nil {
return errDiskNotFound
}
if errors.Is(fErrs[index], errUnformattedDisk) {
return saveFormatErasure(storageDisks[index], formats[index], true)
}
return nil
}, index)
}
writeQuorum := getWriteQuorum(len(storageDisks))
// Wait for the routines to finish.
return reduceWriteQuorumErrs(ctx, g.Wait(), nil, writeQuorum)
}
// saveFormatErasureAll - populates `format.json` on disks in its order.
func saveFormatErasureAll(ctx context.Context, storageDisks []StorageAPI, formats []*formatErasureV3) error {
g := errgroup.WithNErrs(len(storageDisks))
@@ -697,7 +739,7 @@ func saveFormatErasureAll(ctx context.Context, storageDisks []StorageAPI, format
if formats[index] == nil {
return errDiskNotFound
}
return saveFormatErasure(storageDisks[index], formats[index])
return saveFormatErasure(storageDisks[index], formats[index], false)
}, index)
}
@@ -716,6 +758,20 @@ func closeStorageDisks(storageDisks []StorageAPI) {
}
}
func initStorageDisksWithErrorsWithoutHealthCheck(endpoints Endpoints) ([]StorageAPI, []error) {
// Bootstrap disks.
storageDisks := make([]StorageAPI, len(endpoints))
g := errgroup.WithNErrs(len(endpoints))
for index := range endpoints {
index := index
g.Go(func() (err error) {
storageDisks[index], err = newStorageAPIWithoutHealthCheck(endpoints[index])
return err
}, index)
}
return storageDisks, g.Wait()
}
// Initialize storage disks for each endpoint.
// Errors are returned for each endpoint with matching index.
func initStorageDisksWithErrors(endpoints Endpoints) ([]StorageAPI, []error) {
@@ -771,7 +827,8 @@ func fixFormatErasureV3(storageDisks []StorageAPI, endpoints Endpoints, formats
}
if formats[i].Erasure.This == "" {
formats[i].Erasure.This = formats[i].Erasure.Sets[0][i]
if err := saveFormatErasure(storageDisks[i], formats[i]); err != nil {
// Heal the drive if drive has .This empty.
if err := saveFormatErasure(storageDisks[i], formats[i], true); err != nil {
return err
}
}
@@ -788,8 +845,8 @@ func fixFormatErasureV3(storageDisks []StorageAPI, endpoints Endpoints, formats
}
// initFormatErasure - save Erasure format configuration on all disks.
func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount, setDriveCount int, deploymentID string, sErrs []error) (*formatErasureV3, error) {
format := newFormatErasureV3(setCount, setDriveCount)
func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount, setDriveCount int, distributionAlgo string, deploymentID string, sErrs []error) (*formatErasureV3, error) {
format := newFormatErasureV3(setCount, setDriveCount, distributionAlgo)
formats := make([]*formatErasureV3, len(storageDisks))
wantAtMost := ecDrivesNoConfig(setDriveCount)
@@ -865,60 +922,6 @@ func makeFormatErasureMetaVolumes(disk StorageAPI) error {
return disk.MakeVolBulk(context.TODO(), minioMetaBucket, minioMetaTmpBucket, minioMetaMultipartBucket, dataUsageBucket)
}
// Get all UUIDs which are present in reference format should
// be present in the list of formats provided, those are considered
// as online UUIDs.
func getOnlineUUIDs(refFormat *formatErasureV3, formats []*formatErasureV3) (onlineUUIDs []string) {
for _, format := range formats {
if format == nil {
continue
}
for _, set := range refFormat.Erasure.Sets {
for _, uuid := range set {
if format.Erasure.This == uuid {
onlineUUIDs = append(onlineUUIDs, uuid)
}
}
}
}
return onlineUUIDs
}
// Look for all UUIDs which are not present in reference format
// but are present in the onlineUUIDs list, construct of list such
// offline UUIDs.
func getOfflineUUIDs(refFormat *formatErasureV3, formats []*formatErasureV3) (offlineUUIDs []string) {
onlineUUIDs := getOnlineUUIDs(refFormat, formats)
for i, set := range refFormat.Erasure.Sets {
for j, uuid := range set {
var found bool
for _, onlineUUID := range onlineUUIDs {
if refFormat.Erasure.Sets[i][j] == onlineUUID {
found = true
}
}
if !found {
offlineUUIDs = append(offlineUUIDs, uuid)
}
}
}
return offlineUUIDs
}
// Mark all UUIDs that are offline.
func markUUIDsOffline(refFormat *formatErasureV3, formats []*formatErasureV3) {
offlineUUIDs := getOfflineUUIDs(refFormat, formats)
for i, set := range refFormat.Erasure.Sets {
for j := range set {
for _, offlineUUID := range offlineUUIDs {
if refFormat.Erasure.Sets[i][j] == offlineUUID {
refFormat.Erasure.Sets[i][j] = offlineDiskUUID
}
}
}
}
}
// Initialize a new set of set formats which will be written to all disks.
func newHealFormatSets(refFormat *formatErasureV3, setCount, setDriveCount int, formats []*formatErasureV3, errs []error) [][]*formatErasureV3 {
newFormats := make([][]*formatErasureV3, setCount)
@@ -927,23 +930,16 @@ func newHealFormatSets(refFormat *formatErasureV3, setCount, setDriveCount int,
}
for i := range refFormat.Erasure.Sets {
for j := range refFormat.Erasure.Sets[i] {
if errs[i*setDriveCount+j] == errUnformattedDisk || errs[i*setDriveCount+j] == nil {
if errors.Is(errs[i*setDriveCount+j], errUnformattedDisk) {
newFormats[i][j] = &formatErasureV3{}
newFormats[i][j].Version = refFormat.Version
newFormats[i][j].ID = refFormat.ID
newFormats[i][j].Format = refFormat.Format
newFormats[i][j].Version = refFormat.Version
newFormats[i][j].Erasure.This = refFormat.Erasure.Sets[i][j]
newFormats[i][j].Erasure.Sets = refFormat.Erasure.Sets
newFormats[i][j].Erasure.Version = refFormat.Erasure.Version
newFormats[i][j].Erasure.DistributionAlgo = refFormat.Erasure.DistributionAlgo
}
if errs[i*setDriveCount+j] == errUnformattedDisk {
newFormats[i][j].Erasure.This = ""
newFormats[i][j].Erasure.Sets = nil
continue
}
if errs[i*setDriveCount+j] == nil {
newFormats[i][j].Erasure.This = formats[i*setDriveCount+j].Erasure.This
newFormats[i][j].Erasure.Sets = nil
}
}
}
return newFormats
+8 -55
View File
@@ -25,56 +25,6 @@ import (
"testing"
)
// Test get offline/online uuids.
func TestGetUUIDs(t *testing.T) {
fmtV2 := newFormatErasureV3(4, 16)
formats := make([]*formatErasureV3, 64)
for i := 0; i < 4; i++ {
for j := 0; j < 16; j++ {
newFormat := *fmtV2
newFormat.Erasure.This = fmtV2.Erasure.Sets[i][j]
formats[i*16+j] = &newFormat
}
}
gotCount := len(getOnlineUUIDs(fmtV2, formats))
if gotCount != 64 {
t.Errorf("Expected online count '64', got '%d'", gotCount)
}
for i := 0; i < 4; i++ {
for j := 0; j < 16; j++ {
if j < 4 {
formats[i*16+j] = nil
}
}
}
gotCount = len(getOnlineUUIDs(fmtV2, formats))
if gotCount != 48 {
t.Errorf("Expected online count '48', got '%d'", gotCount)
}
gotCount = len(getOfflineUUIDs(fmtV2, formats))
if gotCount != 16 {
t.Errorf("Expected offline count '16', got '%d'", gotCount)
}
markUUIDsOffline(fmtV2, formats)
gotCount = 0
for i := range fmtV2.Erasure.Sets {
for j := range fmtV2.Erasure.Sets[i] {
if fmtV2.Erasure.Sets[i][j] == offlineDiskUUID {
gotCount++
}
}
}
if gotCount != 16 {
t.Errorf("Expected offline count '16', got '%d'", gotCount)
}
}
// tests fixFormatErasureV3 - fix format.json on all disks.
func TestFixFormatV3(t *testing.T) {
erasureDirs, err := getRandomDisks(8)
@@ -93,7 +43,7 @@ func TestFixFormatV3(t *testing.T) {
}
}
format := newFormatErasureV3(1, 8)
format := newFormatErasureV3(1, 8, "CRCMOD")
formats := make([]*formatErasureV3, 8)
for j := 0; j < 8; j++ {
@@ -127,7 +77,7 @@ func TestFixFormatV3(t *testing.T) {
// tests formatErasureV3ThisEmpty conditions.
func TestFormatErasureEmpty(t *testing.T) {
format := newFormatErasureV3(1, 16)
format := newFormatErasureV3(1, 16, "CRCMOD")
formats := make([]*formatErasureV3, 16)
for j := 0; j < 16; j++ {
@@ -326,7 +276,7 @@ func TestGetFormatErasureInQuorumCheck(t *testing.T) {
setCount := 2
setDriveCount := 16
format := newFormatErasureV3(setCount, setDriveCount)
format := newFormatErasureV3(setCount, setDriveCount, "CRCMOD")
formats := make([]*formatErasureV3, 32)
for i := 0; i < setCount; i++ {
@@ -392,7 +342,7 @@ func TestGetErasureID(t *testing.T) {
setCount := 2
setDriveCount := 8
format := newFormatErasureV3(setCount, setDriveCount)
format := newFormatErasureV3(setCount, setDriveCount, "CRCMOD")
formats := make([]*formatErasureV3, 16)
for i := 0; i < setCount; i++ {
@@ -447,7 +397,7 @@ func TestNewFormatSets(t *testing.T) {
setCount := 2
setDriveCount := 16
format := newFormatErasureV3(setCount, setDriveCount)
format := newFormatErasureV3(setCount, setDriveCount, "CRCMOD")
formats := make([]*formatErasureV3, 32)
errs := make([]error, 32)
@@ -475,6 +425,9 @@ func TestNewFormatSets(t *testing.T) {
// Check if deployment IDs are preserved.
for i := range newFormats {
for j := range newFormats[i] {
if newFormats[i][j] == nil {
continue
}
if newFormats[i][j].ID != quorumFormat.ID {
t.Fatal("Deployment id in the new format is lost")
}
+4 -5
View File
@@ -216,12 +216,11 @@ func (fs *FSObjects) StorageInfo(ctx context.Context, _ bool) (StorageInfo, []er
if err != nil {
return StorageInfo{}, []error{err}
}
used := di.Total - di.Free
storageInfo := StorageInfo{
Disks: []madmin.Disk{
{
TotalSpace: di.Total,
UsedSpace: used,
UsedSpace: di.Used,
AvailableSpace: di.Free,
DrivePath: fs.fsPath,
},
@@ -328,7 +327,7 @@ func (fs *FSObjects) crawlBucket(ctx context.Context, bucket string, cache dataU
}
// Load bucket info.
cache, err = crawlDataFolder(ctx, fs.fsPath, cache, fs.waitForLowActiveIO, func(item crawlItem) (int64, error) {
cache, err = crawlDataFolder(ctx, fs.fsPath, cache, func(item crawlItem) (int64, error) {
bucket, object := item.bucket, item.objectPath()
fsMetaBytes, err := ioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
if err != nil && !os.IsNotExist(err) {
@@ -654,7 +653,7 @@ func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBu
return fsMeta.ToObjectInfo(srcBucket, srcObject, fi), nil
}
if err := checkPutObjectArgs(ctx, dstBucket, dstObject, fs, srcInfo.PutObjReader.Size()); err != nil {
if err := checkPutObjectArgs(ctx, dstBucket, dstObject, fs); err != nil {
return ObjectInfo{}, err
}
@@ -1098,7 +1097,7 @@ func (fs *FSObjects) PutObject(ctx context.Context, bucket string, object string
return objInfo, NotImplemented{}
}
if err := checkPutObjectArgs(ctx, bucket, object, fs, r.Size()); err != nil {
if err := checkPutObjectArgs(ctx, bucket, object, fs); err != nil {
return ObjectInfo{}, err
}
+12 -14
View File
@@ -153,6 +153,8 @@ func ValidateGatewayArguments(serverAddr, endpointAddr string) error {
// StartGateway - handler for 'minio gateway <name>'.
func StartGateway(ctx *cli.Context, gw Gateway) {
defer globalDNSCache.Stop()
// This is only to uniquely identify each gateway deployments.
globalDeploymentID = env.Get("MINIO_GATEWAY_DEPLOYMENT_ID", mustGetUUID())
logger.SetDeploymentID(globalDeploymentID)
@@ -289,7 +291,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
globalHTTPServer = httpServer
globalObjLayerMutex.Unlock()
signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM)
signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
newObject, err := gw.NewGatewayLayer(globalActiveCred)
if err != nil {
@@ -298,15 +300,14 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
}
newObject = NewGatewayLayerWithLocker(newObject)
// Once endpoints are finalized, initialize the new object api in safe mode.
globalObjLayerMutex.Lock()
globalSafeMode = true
globalObjectAPI = newObject
globalObjLayerMutex.Unlock()
// Calls all New() for all sub-systems.
newAllSubsystems()
// Once endpoints are finalized, initialize the new object api in safe mode.
globalObjLayerMutex.Lock()
globalObjectAPI = newObject
globalObjLayerMutex.Unlock()
if gatewayName == NASBackendGateway {
buckets, err := newObject.ListBuckets(GlobalContext)
if err != nil {
@@ -324,8 +325,10 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
}
if enableIAMOps {
// Initialize IAM sys.
startBackgroundIAMLoad(GlobalContext)
// Initialize users credentials and policies in background.
globalIAMSys.InitStore(newObject)
go globalIAMSys.Init(GlobalContext, newObject)
}
if globalCacheConfig.Enabled {
@@ -353,11 +356,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// - compression
verifyObjectLayerFeatures("gateway "+gatewayName, newObject)
// Disable safe mode operation, after all initialization is over.
globalObjLayerMutex.Lock()
globalSafeMode = false
globalObjLayerMutex.Unlock()
// Prints the formatted startup message once object layer is initialized.
if !globalCLIContext.Quiet {
mode := globalMinioModeGatewayPrefix + gatewayName
+2 -1
View File
@@ -559,7 +559,8 @@ func (a *azureObjects) StorageInfo(ctx context.Context, _ bool) (si minio.Storag
// MakeBucketWithLocation - Create a new container on azure backend.
func (a *azureObjects) MakeBucketWithLocation(ctx context.Context, bucket string, opts minio.BucketOptions) error {
if opts.LockEnabled || opts.VersioningEnabled {
// Filter out unsupported features in Azure and return immediately with NotImplemented error
if opts.LockEnabled || opts.VersioningEnabled || strings.ContainsAny(bucket, ".") {
return minio.NotImplemented{}
}
-1
View File
@@ -52,7 +52,6 @@ EXAMPLES:
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_AFTER{{.AssignmentOperator}}3
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_WATERMARK_LOW{{.AssignmentOperator}}75
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_WATERMARK_HIGH{{.AssignmentOperator}}85
{{.Prompt}} {{.HelpName}} /shared/nasvol
`
+8 -11
View File
@@ -97,7 +97,7 @@ func getLocalBackgroundHealStatus() (madmin.BgHealState, bool) {
}
// healErasureSet lists and heals all objects in a specific erasure set
func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, disks []StorageAPI, setDriveCount int) error {
func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, disks []StorageAPI) error {
// Get background heal sequence to send elements to heal
var bgSeq *healSequence
var ok bool
@@ -120,6 +120,12 @@ func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, dis
Name: pathJoin(minioMetaBucket, bucketConfigPrefix),
}) // add metadata .minio.sys/ bucket prefixes to heal
// Try to pro-actively heal backend-encrypted file.
bgSeq.sourceCh <- healSource{
bucket: minioMetaBucket,
object: backendEncryptedFile,
}
// Heal all buckets with all objects
for _, bucket := range buckets {
// Heal current bucket
@@ -131,10 +137,6 @@ func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, dis
var mu sync.Mutex
var wg sync.WaitGroup
for _, disk := range disks {
if disk == nil {
// Disk can be offline
continue
}
disk := disk
wg.Add(1)
go func() {
@@ -157,16 +159,11 @@ func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, dis
entries := make([]FileInfoVersions, len(entryChs))
for {
entry, quorumCount, ok := lexicallySortedEntryVersions(entryChs, entries, entriesValid)
entry, _, ok := lexicallySortedEntryVersions(entryChs, entries, entriesValid)
if !ok {
break
}
if quorumCount == setDriveCount {
// Skip good entries.
continue
}
for _, version := range entry.Versions {
bgSeq.sourceCh <- healSource{
bucket: bucket.Name,
+7 -7
View File
@@ -22,6 +22,7 @@ import (
"time"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/pkg/bucket/bandwidth"
humanize "github.com/dustin/go-humanize"
"github.com/minio/minio/cmd/config/cache"
@@ -149,6 +150,7 @@ var (
globalEnvTargetList *event.TargetList
globalBucketMetadataSys *BucketMetadataSys
globalBucketMonitor *bandwidth.Monitor
globalPolicySys *PolicySys
globalIAMSys *IAMSys
@@ -186,7 +188,7 @@ var (
// registered listeners
globalConsoleSys *HTTPConsoleLoggerSys
globalEndpoints EndpointZones
globalEndpoints EndpointServerSets
// Global server's network statistics
globalConnStats = newConnStats()
@@ -210,7 +212,8 @@ var (
globalDomainNames []string // Root domains for virtual host style requests
globalDomainIPs set.StringSet // Root domain IP address(s) for a distributed MinIO deployment
globalOperationTimeout = newDynamicTimeout(10*time.Minute, 5*time.Minute) // default timeout for general ops
globalOperationTimeout = newDynamicTimeout(10*time.Minute, 5*time.Minute) // default timeout for general ops
globalDeleteOperationTimeout = newDynamicTimeout(5*time.Minute, 1*time.Minute) // default time for delete ops
globalBucketObjectLockSys *BucketObjectLockSys
globalBucketQuotaSys *BucketQuotaSys
@@ -267,15 +270,12 @@ var (
globalBackgroundHealRoutine *healRoutine
globalBackgroundHealState *allHealState
// Only enabled when one of the sub-systems fail
// to initialize, this allows for administrators to
// fix the system.
globalSafeMode bool
// If writes to FS backend should be O_SYNC.
globalFSOSync bool
globalProxyEndpoints []ProxyEndpoint
globalDNSCache *xhttp.DNSCache
// Add new variable global values here.
)
+3 -1
View File
@@ -238,7 +238,9 @@ func extractReqParams(r *http.Request) map[string]string {
// Extract response elements to be sent with event notifiation.
func extractRespElements(w http.ResponseWriter) map[string]string {
if w == nil {
return map[string]string{}
}
return map[string]string{
"requestId": w.Header().Get(xhttp.AmzRequestID),
"content-length": w.Header().Get(xhttp.ContentLength),
+197
View File
@@ -0,0 +1,197 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http
import (
"context"
"log"
"math/rand"
"net"
"sync"
"time"
)
var randPerm = func(n int) []int {
return rand.Perm(n)
}
// DialContextWithDNSCache is a helper function which returns `net.DialContext` function.
// It randomly fetches an IP from the DNS cache and dials it by the given dial
// function. It dials one by one and returns first connected `net.Conn`.
// If it fails to dial all IPs from cache it returns first error. If no baseDialFunc
// is given, it sets default dial function.
//
// You can use returned dial function for `http.Transport.DialContext`.
//
// In this function, it uses functions from `rand` package. To make it really random,
// you MUST call `rand.Seed` and change the value from the default in your application
func DialContextWithDNSCache(cache *DNSCache, baseDialCtx DialContext) DialContext {
if baseDialCtx == nil {
// This is same as which `http.DefaultTransport` uses.
baseDialCtx = (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext
}
return func(ctx context.Context, network, host string) (net.Conn, error) {
h, p, err := net.SplitHostPort(host)
if err != nil {
return nil, err
}
// Fetch DNS result from cache.
//
// ctxLookup is only used for canceling DNS Lookup.
ctxLookup, cancelF := context.WithTimeout(ctx, cache.lookupTimeout)
defer cancelF()
addrs, err := cache.Fetch(ctxLookup, h)
if err != nil {
return nil, err
}
var firstErr error
for _, randomIndex := range randPerm(len(addrs)) {
conn, err := baseDialCtx(ctx, "tcp", net.JoinHostPort(addrs[randomIndex], p))
if err == nil {
return conn, nil
}
if firstErr == nil {
firstErr = err
}
}
return nil, firstErr
}
}
const (
// cacheSize is initial size of addr and IP list cache map.
cacheSize = 64
)
// defaultFreq is default frequency a resolver refreshes DNS cache.
var (
defaultFreq = 3 * time.Second
defaultLookupTimeout = 10 * time.Second
)
// DNSCache is DNS cache resolver which cache DNS resolve results in memory.
type DNSCache struct {
sync.RWMutex
lookupHostFn func(ctx context.Context, host string) ([]string, error)
lookupTimeout time.Duration
cache map[string][]string
closer func()
}
// NewDNSCache initializes DNS cache resolver and starts auto refreshing
// in a new goroutine. To stop auto refreshing, call `Stop()` function.
// Once `Stop()` is called auto refreshing cannot be resumed.
func NewDNSCache(freq time.Duration, lookupTimeout time.Duration) *DNSCache {
if freq <= 0 {
freq = defaultFreq
}
if lookupTimeout <= 0 {
lookupTimeout = defaultLookupTimeout
}
ticker := time.NewTicker(freq)
ch := make(chan struct{})
closer := func() {
ticker.Stop()
close(ch)
}
r := &DNSCache{
lookupHostFn: net.DefaultResolver.LookupHost,
lookupTimeout: lookupTimeout,
cache: make(map[string][]string, cacheSize),
closer: closer,
}
go func() {
for {
select {
case <-ticker.C:
r.Refresh()
case <-ch:
return
}
}
}()
return r
}
// LookupHost lookups address list from DNS server, persist the results
// in-memory cache. `Fetch` is used to obtain the values for a given host.
func (r *DNSCache) LookupHost(ctx context.Context, host string) ([]string, error) {
addrs, err := r.lookupHostFn(ctx, host)
if err != nil {
return nil, err
}
r.Lock()
r.cache[host] = addrs
r.Unlock()
return addrs, nil
}
// Fetch fetches IP list from the cache. If IP list of the given addr is not in the cache,
// then it lookups from DNS server by `Lookup` function.
func (r *DNSCache) Fetch(ctx context.Context, host string) ([]string, error) {
r.RLock()
addrs, ok := r.cache[host]
r.RUnlock()
if ok {
return addrs, nil
}
return r.LookupHost(ctx, host)
}
// Refresh refreshes IP list cache, automatically.
func (r *DNSCache) Refresh() {
r.RLock()
hosts := make([]string, 0, len(r.cache))
for host := range r.cache {
hosts = append(hosts, host)
}
r.RUnlock()
for _, host := range hosts {
ctx, cancelF := context.WithTimeout(context.Background(), r.lookupTimeout)
if _, err := r.LookupHost(ctx, host); err != nil {
log.Println("failed to refresh DNS cache, resolver is unavailable", err)
}
cancelF()
}
}
// Stop stops auto refreshing.
func (r *DNSCache) Stop() {
r.Lock()
defer r.Unlock()
if r.closer != nil {
r.closer()
r.closer = nil
}
}
+204
View File
@@ -0,0 +1,204 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http
import (
"context"
"errors"
"fmt"
"math/rand"
"net"
"testing"
"time"
)
var (
testFreq = 1 * time.Second
testDefaultLookupTimeout = 1 * time.Second
)
func testDNSCache(t *testing.T) *DNSCache {
t.Helper() // skip printing file and line information from this function
return NewDNSCache(testFreq, testDefaultLookupTimeout)
}
func TestDialContextWithDNSCache(t *testing.T) {
resolver := &DNSCache{
cache: map[string][]string{
"play.min.io": {
"127.0.0.1",
"127.0.0.2",
"127.0.0.3",
},
},
}
cases := []struct {
permF func(n int) []int
dialF DialContext
}{
{
permF: func(n int) []int {
return []int{0}
},
dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
if got, want := addr, net.JoinHostPort("127.0.0.1", "443"); got != want {
t.Fatalf("got addr %q, want %q", got, want)
}
return nil, nil
},
},
{
permF: func(n int) []int {
return []int{1}
},
dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
if got, want := addr, net.JoinHostPort("127.0.0.2", "443"); got != want {
t.Fatalf("got addr %q, want %q", got, want)
}
return nil, nil
},
},
{
permF: func(n int) []int {
return []int{2}
},
dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
if got, want := addr, net.JoinHostPort("127.0.0.3", "443"); got != want {
t.Fatalf("got addr %q, want %q", got, want)
}
return nil, nil
},
},
}
origFunc := randPerm
defer func() {
randPerm = origFunc
}()
for _, tc := range cases {
t.Run("", func(t *testing.T) {
randPerm = tc.permF
if _, err := DialContextWithDNSCache(resolver, tc.dialF)(context.Background(), "tcp", "play.min.io:443"); err != nil {
t.Fatalf("err: %s", err)
}
})
}
}
func TestDialContextWithDNSCacheRand(t *testing.T) {
rand.Seed(time.Now().UTC().UnixNano())
defer func() {
rand.Seed(1)
}()
resolver := &DNSCache{
cache: map[string][]string{
"play.min.io": {
"127.0.0.1",
"127.0.0.2",
"127.0.0.3",
},
},
}
count := make(map[string]int)
dialF := func(ctx context.Context, network, addr string) (net.Conn, error) {
count[addr]++
return nil, nil
}
for i := 0; i < 100; i++ {
if _, err := DialContextWithDNSCache(resolver, dialF)(context.Background(), "tcp", "play.min.io:443"); err != nil {
t.Fatalf("err: %s", err)
}
}
for _, c := range count {
got := float32(c) / float32(100)
if got < float32(0.1) {
t.Fatalf("expected 0.1 rate got %f", got)
}
}
}
// Verify without port Dial fails, Go stdlib net.Dial expects port
func TestDialContextWithDNSCacheScenario1(t *testing.T) {
resolver := testDNSCache(t)
if _, err := DialContextWithDNSCache(resolver, nil)(context.Background(), "tcp", "play.min.io"); err == nil {
t.Fatalf("expect to fail") // expected port
}
}
// Verify if the host lookup function failed to return addresses
func TestDialContextWithDNSCacheScenario2(t *testing.T) {
res := testDNSCache(t)
originalFunc := res.lookupHostFn
defer func() {
res.lookupHostFn = originalFunc
}()
res.lookupHostFn = func(ctx context.Context, host string) ([]string, error) {
return nil, fmt.Errorf("err")
}
if _, err := DialContextWithDNSCache(res, nil)(context.Background(), "tcp", "min.io:443"); err == nil {
t.Fatalf("exect to fail")
}
}
// Verify we always return the first error from net.Dial failure
func TestDialContextWithDNSCacheScenario3(t *testing.T) {
resolver := &DNSCache{
cache: map[string][]string{
"min.io": {
"1.1.1.1",
"2.2.2.2",
"3.3.3.3",
},
},
}
origFunc := randPerm
randPerm = func(n int) []int {
return []int{0, 1, 2}
}
defer func() {
randPerm = origFunc
}()
want := errors.New("error1")
dialF := func(ctx context.Context, network, addr string) (net.Conn, error) {
if addr == net.JoinHostPort("1.1.1.1", "443") {
return nil, want // first error should be returned
}
if addr == net.JoinHostPort("2.2.2.2", "443") {
return nil, fmt.Errorf("error2")
}
if addr == net.JoinHostPort("3.3.3.3", "443") {
return nil, fmt.Errorf("error3")
}
return nil, nil
}
_, got := DialContextWithDNSCache(resolver, dialF)(context.Background(), "tcp", "min.io:443")
if got != want {
t.Fatalf("got error %v, want %v", got, want)
}
}
+2 -2
View File
@@ -27,9 +27,9 @@ import (
var cfg = &tcplisten.Config{
DeferAccept: true,
FastOpen: true,
// Bump up the soMaxConn value from 128 to 2048 to
// Bump up the soMaxConn value from 128 to 4096 to
// handle large incoming concurrent requests.
Backlog: 2048,
Backlog: 4096,
}
// Unix listener with special TCP options.
+6 -3
View File
@@ -130,6 +130,9 @@ func (srv *Server) Shutdown() error {
srv.listenerMutex.Lock()
err := srv.listener.Close()
srv.listenerMutex.Unlock()
if err != nil {
return err
}
// Wait for opened connection to be closed up to Shutdown timeout.
shutdownTimeout := srv.ShutdownTimeout
@@ -144,12 +147,12 @@ func (srv *Server) Shutdown() error {
if err == nil {
_ = pprof.Lookup("goroutine").WriteTo(tmp, 1)
tmp.Close()
return errors.New("timed out. some connections are still active. doing abnormal shutdown. goroutines written to " + tmp.Name())
return errors.New("timed out. some connections are still active. goroutines written to " + tmp.Name())
}
return errors.New("timed out. some connections are still active. doing abnormal shutdown")
return errors.New("timed out. some connections are still active")
case <-ticker.C:
if atomic.LoadInt32(&srv.requestCount) <= 0 {
return err
return nil
}
}
}
+65 -61
View File
@@ -76,13 +76,11 @@ func etcdKvsToSetPolicyDB(prefix string, kvs []*mvccpb.KeyValue) set.StringSet {
type IAMEtcdStore struct {
sync.RWMutex
ctx context.Context
client *etcd.Client
}
func newIAMEtcdStore(ctx context.Context) *IAMEtcdStore {
return &IAMEtcdStore{client: globalEtcdClient, ctx: ctx}
func newIAMEtcdStore() *IAMEtcdStore {
return &IAMEtcdStore{client: globalEtcdClient}
}
func (ies *IAMEtcdStore) lock() {
@@ -101,7 +99,7 @@ func (ies *IAMEtcdStore) runlock() {
ies.RUnlock()
}
func (ies *IAMEtcdStore) saveIAMConfig(item interface{}, path string) error {
func (ies *IAMEtcdStore) saveIAMConfig(ctx context.Context, item interface{}, path string) error {
data, err := json.Marshal(item)
if err != nil {
return err
@@ -112,11 +110,11 @@ func (ies *IAMEtcdStore) saveIAMConfig(item interface{}, path string) error {
return err
}
}
return saveKeyEtcd(ies.ctx, ies.client, path, data)
return saveKeyEtcd(ctx, ies.client, path, data)
}
func (ies *IAMEtcdStore) loadIAMConfig(item interface{}, path string) error {
pdata, err := readKeyEtcd(ies.ctx, ies.client, path)
func (ies *IAMEtcdStore) loadIAMConfig(ctx context.Context, item interface{}, path string) error {
pdata, err := readKeyEtcd(ctx, ies.client, path)
if err != nil {
return err
}
@@ -131,8 +129,8 @@ func (ies *IAMEtcdStore) loadIAMConfig(item interface{}, path string) error {
return json.Unmarshal(pdata, item)
}
func (ies *IAMEtcdStore) deleteIAMConfig(path string) error {
return deleteKeyEtcd(ies.ctx, ies.client, path)
func (ies *IAMEtcdStore) deleteIAMConfig(ctx context.Context, path string) error {
return deleteKeyEtcd(ctx, ies.client, path)
}
func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context, isSTS bool) error {
@@ -154,7 +152,7 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context, isSTS bool)
// 1. check if there is a policy file in the old loc.
oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile)
var policyName string
err := ies.loadIAMConfig(&policyName, oldPolicyPath)
err := ies.loadIAMConfig(ctx, &policyName, oldPolicyPath)
if err != nil {
switch err {
case errConfigNotFound:
@@ -172,7 +170,7 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context, isSTS bool)
userType = stsUser
}
path := getMappedPolicyPath(user, userType, false)
if err := ies.saveIAMConfig(mp, path); err != nil {
if err := ies.saveIAMConfig(ctx, mp, path); err != nil {
return err
}
@@ -184,7 +182,7 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context, isSTS bool)
// 4. check if user identity has old format.
identityPath := pathJoin(basePrefix, user, iamIdentityFile)
var cred auth.Credentials
if err := ies.loadIAMConfig(&cred, identityPath); err != nil {
if err := ies.loadIAMConfig(ctx, &cred, identityPath); err != nil {
switch err {
case errConfigNotFound:
// This case should not happen.
@@ -207,7 +205,7 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context, isSTS bool)
// into new format and save it.
cred.AccessKey = user
u := newUserIdentity(cred)
if err := ies.saveIAMConfig(u, identityPath); err != nil {
if err := ies.saveIAMConfig(ctx, u, identityPath); err != nil {
logger.LogIf(ctx, err)
return err
}
@@ -221,7 +219,7 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context, isSTS bool)
func (ies *IAMEtcdStore) migrateToV1(ctx context.Context) error {
var iamFmt iamFormat
path := getIAMFormatFilePath()
if err := ies.loadIAMConfig(&iamFmt, path); err != nil {
if err := ies.loadIAMConfig(ctx, &iamFmt, path); err != nil {
switch err {
case errConfigNotFound:
// Need to migrate to V1.
@@ -250,7 +248,7 @@ func (ies *IAMEtcdStore) migrateToV1(ctx context.Context) error {
return err
}
// Save iam version file.
if err := ies.saveIAMConfig(newIAMFormatVersion1(), path); err != nil {
if err := ies.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
logger.LogIf(ctx, err)
return err
}
@@ -262,9 +260,9 @@ func (ies *IAMEtcdStore) migrateBackendFormat(ctx context.Context) error {
return ies.migrateToV1(ctx)
}
func (ies *IAMEtcdStore) loadPolicyDoc(policy string, m map[string]iampolicy.Policy) error {
func (ies *IAMEtcdStore) loadPolicyDoc(ctx context.Context, policy string, m map[string]iampolicy.Policy) error {
var p iampolicy.Policy
err := ies.loadIAMConfig(&p, getPolicyDocPath(policy))
err := ies.loadIAMConfig(ctx, &p, getPolicyDocPath(policy))
if err != nil {
return err
}
@@ -284,7 +282,7 @@ func (ies *IAMEtcdStore) loadPolicyDocs(ctx context.Context, m map[string]iampol
// Reload config and policies for all policys.
for _, policyName := range policies.ToSlice() {
err = ies.loadPolicyDoc(policyName, m)
err = ies.loadPolicyDoc(ctx, policyName, m)
if err != nil {
return err
}
@@ -292,9 +290,9 @@ func (ies *IAMEtcdStore) loadPolicyDocs(ctx context.Context, m map[string]iampol
return nil
}
func (ies *IAMEtcdStore) loadUser(user string, userType IAMUserType, m map[string]auth.Credentials) error {
func (ies *IAMEtcdStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error {
var u UserIdentity
err := ies.loadIAMConfig(&u, getUserIdentityPath(user, userType))
err := ies.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil {
if err == errConfigNotFound {
return errNoSuchUser
@@ -304,8 +302,8 @@ func (ies *IAMEtcdStore) loadUser(user string, userType IAMUserType, m map[strin
if u.Credentials.IsExpired() {
// Delete expired identity.
deleteKeyEtcd(ies.ctx, ies.client, getUserIdentityPath(user, userType))
deleteKeyEtcd(ies.ctx, ies.client, getMappedPolicyPath(user, userType, false))
deleteKeyEtcd(ctx, ies.client, getUserIdentityPath(user, userType))
deleteKeyEtcd(ctx, ies.client, getMappedPolicyPath(user, userType, false))
return nil
}
@@ -320,7 +318,7 @@ func (ies *IAMEtcdStore) loadUser(user string, userType IAMUserType, m map[strin
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))
if token, err := jwt.SignedString([]byte(globalActiveCred.SecretKey)); err == nil {
u.Credentials.SessionToken = token
err := ies.saveIAMConfig(&u, getUserIdentityPath(user, userType))
err := ies.saveIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil {
return err
}
@@ -348,9 +346,10 @@ func (ies *IAMEtcdStore) loadUsers(ctx context.Context, userType IAMUserType, m
basePrefix = iamConfigUsersPrefix
}
ctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
cctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
defer cancel()
r, err := ies.client.Get(ctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
r, err := ies.client.Get(cctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil {
return err
}
@@ -359,16 +358,16 @@ func (ies *IAMEtcdStore) loadUsers(ctx context.Context, userType IAMUserType, m
// Reload config for all users.
for _, user := range users.ToSlice() {
if err = ies.loadUser(user, userType, m); err != nil {
if err = ies.loadUser(ctx, user, userType, m); err != nil {
return err
}
}
return nil
}
func (ies *IAMEtcdStore) loadGroup(group string, m map[string]GroupInfo) error {
func (ies *IAMEtcdStore) loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error {
var gi GroupInfo
err := ies.loadIAMConfig(&gi, getGroupInfoPath(group))
err := ies.loadIAMConfig(ctx, &gi, getGroupInfoPath(group))
if err != nil {
if err == errConfigNotFound {
return errNoSuchGroup
@@ -381,9 +380,10 @@ func (ies *IAMEtcdStore) loadGroup(group string, m map[string]GroupInfo) error {
}
func (ies *IAMEtcdStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error {
ctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
cctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
defer cancel()
r, err := ies.client.Get(ctx, iamConfigGroupsPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
r, err := ies.client.Get(cctx, iamConfigGroupsPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil {
return err
}
@@ -392,7 +392,7 @@ func (ies *IAMEtcdStore) loadGroups(ctx context.Context, m map[string]GroupInfo)
// Reload config for all groups.
for _, group := range groups.ToSlice() {
if err = ies.loadGroup(group, m); err != nil {
if err = ies.loadGroup(ctx, group, m); err != nil {
return err
}
}
@@ -400,9 +400,9 @@ func (ies *IAMEtcdStore) loadGroups(ctx context.Context, m map[string]GroupInfo)
}
func (ies *IAMEtcdStore) loadMappedPolicy(name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
var p MappedPolicy
err := ies.loadIAMConfig(&p, getMappedPolicyPath(name, userType, isGroup))
err := ies.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
if err != nil {
if err == errConfigNotFound {
return errNoSuchPolicy
@@ -415,7 +415,7 @@ func (ies *IAMEtcdStore) loadMappedPolicy(name string, userType IAMUserType, isG
}
func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
ctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
cctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
defer cancel()
var basePrefix string
if isGroup {
@@ -430,7 +430,8 @@ func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUse
basePrefix = iamConfigPolicyDBUsersPrefix
}
}
r, err := ies.client.Get(ctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
r, err := ies.client.Get(cctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil {
return err
}
@@ -439,7 +440,7 @@ func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUse
// Reload config and policies for all users.
for _, user := range users.ToSlice() {
if err = ies.loadMappedPolicy(user, userType, isGroup, m); err != nil {
if err = ies.loadMappedPolicy(ctx, user, userType, isGroup, m); err != nil {
return err
}
}
@@ -531,7 +532,7 @@ func (ies *IAMEtcdStore) loadAll(ctx context.Context, sys *IAMSys) error {
if v.IsServiceAccount() {
for _, accessKey := range expiredEntries {
if v.ParentUser == accessKey {
_ = ies.deleteUserIdentity(v.AccessKey, srvAccUser)
_ = ies.deleteUserIdentity(ctx, v.AccessKey, srvAccUser)
delete(sys.iamUsersMap, v.AccessKey)
}
}
@@ -561,48 +562,48 @@ func (ies *IAMEtcdStore) loadAll(ctx context.Context, sys *IAMSys) error {
return nil
}
func (ies *IAMEtcdStore) savePolicyDoc(policyName string, p iampolicy.Policy) error {
return ies.saveIAMConfig(&p, getPolicyDocPath(policyName))
func (ies *IAMEtcdStore) savePolicyDoc(ctx context.Context, policyName string, p iampolicy.Policy) error {
return ies.saveIAMConfig(ctx, &p, getPolicyDocPath(policyName))
}
func (ies *IAMEtcdStore) saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
return ies.saveIAMConfig(mp, getMappedPolicyPath(name, userType, isGroup))
func (ies *IAMEtcdStore) saveMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
return ies.saveIAMConfig(ctx, mp, getMappedPolicyPath(name, userType, isGroup))
}
func (ies *IAMEtcdStore) saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error {
return ies.saveIAMConfig(u, getUserIdentityPath(name, userType))
func (ies *IAMEtcdStore) saveUserIdentity(ctx context.Context, name string, userType IAMUserType, u UserIdentity) error {
return ies.saveIAMConfig(ctx, u, getUserIdentityPath(name, userType))
}
func (ies *IAMEtcdStore) saveGroupInfo(name string, gi GroupInfo) error {
return ies.saveIAMConfig(gi, getGroupInfoPath(name))
func (ies *IAMEtcdStore) saveGroupInfo(ctx context.Context, name string, gi GroupInfo) error {
return ies.saveIAMConfig(ctx, gi, getGroupInfoPath(name))
}
func (ies *IAMEtcdStore) deletePolicyDoc(name string) error {
err := ies.deleteIAMConfig(getPolicyDocPath(name))
func (ies *IAMEtcdStore) deletePolicyDoc(ctx context.Context, name string) error {
err := ies.deleteIAMConfig(ctx, getPolicyDocPath(name))
if err == errConfigNotFound {
err = errNoSuchPolicy
}
return err
}
func (ies *IAMEtcdStore) deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error {
err := ies.deleteIAMConfig(getMappedPolicyPath(name, userType, isGroup))
func (ies *IAMEtcdStore) deleteMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool) error {
err := ies.deleteIAMConfig(ctx, getMappedPolicyPath(name, userType, isGroup))
if err == errConfigNotFound {
err = errNoSuchPolicy
}
return err
}
func (ies *IAMEtcdStore) deleteUserIdentity(name string, userType IAMUserType) error {
err := ies.deleteIAMConfig(getUserIdentityPath(name, userType))
func (ies *IAMEtcdStore) deleteUserIdentity(ctx context.Context, name string, userType IAMUserType) error {
err := ies.deleteIAMConfig(ctx, getUserIdentityPath(name, userType))
if err == errConfigNotFound {
err = errNoSuchUser
}
return err
}
func (ies *IAMEtcdStore) deleteGroupInfo(name string) error {
err := ies.deleteIAMConfig(getGroupInfoPath(name))
func (ies *IAMEtcdStore) deleteGroupInfo(ctx context.Context, name string) error {
err := ies.deleteIAMConfig(ctx, getGroupInfoPath(name))
if err == errConfigNotFound {
err = errNoSuchGroup
}
@@ -657,43 +658,46 @@ func (ies *IAMEtcdStore) reloadFromEvent(sys *IAMSys, event *etcd.Event) {
policyDBSTSUsersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPolicyDBSTSUsersPrefix)
policyDBGroupsPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPolicyDBGroupsPrefix)
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
switch {
case eventCreate:
switch {
case usersPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigUsersPrefix))
ies.loadUser(accessKey, regularUser, sys.iamUsersMap)
ies.loadUser(ctx, accessKey, regularUser, sys.iamUsersMap)
case stsPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigSTSPrefix))
ies.loadUser(accessKey, stsUser, sys.iamUsersMap)
ies.loadUser(ctx, accessKey, stsUser, sys.iamUsersMap)
case groupsPrefix:
group := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigGroupsPrefix))
ies.loadGroup(group, sys.iamGroupsMap)
ies.loadGroup(ctx, group, sys.iamGroupsMap)
gi := sys.iamGroupsMap[group]
sys.removeGroupFromMembershipsMap(group)
sys.updateGroupMembershipsMap(group, &gi)
case policyPrefix:
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigPoliciesPrefix))
ies.loadPolicyDoc(policyName, sys.iamPolicyDocsMap)
ies.loadPolicyDoc(ctx, policyName, sys.iamPolicyDocsMap)
case policyDBUsersPrefix:
policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
iamConfigPolicyDBUsersPrefix)
user := strings.TrimSuffix(policyMapFile, ".json")
ies.loadMappedPolicy(user, regularUser, false, sys.iamUserPolicyMap)
ies.loadMappedPolicy(ctx, user, regularUser, false, sys.iamUserPolicyMap)
case policyDBSTSUsersPrefix:
policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
iamConfigPolicyDBSTSUsersPrefix)
user := strings.TrimSuffix(policyMapFile, ".json")
ies.loadMappedPolicy(user, stsUser, false, sys.iamUserPolicyMap)
ies.loadMappedPolicy(ctx, user, stsUser, false, sys.iamUserPolicyMap)
case policyDBGroupsPrefix:
policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
iamConfigPolicyDBGroupsPrefix)
user := strings.TrimSuffix(policyMapFile, ".json")
ies.loadMappedPolicy(user, regularUser, true, sys.iamGroupPolicyMap)
ies.loadMappedPolicy(ctx, user, regularUser, true, sys.iamGroupPolicyMap)
}
case eventDelete:
switch {
+47 -48
View File
@@ -39,12 +39,11 @@ type IAMObjectStore struct {
// Protect assignment to objAPI
sync.RWMutex
ctx context.Context
objAPI ObjectLayer
}
func newIAMObjectStore(ctx context.Context, objAPI ObjectLayer) *IAMObjectStore {
return &IAMObjectStore{ctx: ctx, objAPI: objAPI}
func newIAMObjectStore(objAPI ObjectLayer) *IAMObjectStore {
return &IAMObjectStore{objAPI: objAPI}
}
func (iamOS *IAMObjectStore) lock() {
@@ -94,7 +93,7 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context, isSTS b
// 1. check if there is policy file in old location.
oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile)
var policyName string
if err := iamOS.loadIAMConfig(&policyName, oldPolicyPath); err != nil {
if err := iamOS.loadIAMConfig(ctx, &policyName, oldPolicyPath); err != nil {
switch err {
case errConfigNotFound:
// This case means it is already
@@ -115,19 +114,19 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context, isSTS b
if isSTS {
userType = stsUser
}
if err := iamOS.saveMappedPolicy(user, userType, false, mp); err != nil {
if err := iamOS.saveMappedPolicy(ctx, user, userType, false, mp); err != nil {
return err
}
// 3. delete policy file from old
// location. Ignore error.
iamOS.deleteIAMConfig(oldPolicyPath)
iamOS.deleteIAMConfig(ctx, oldPolicyPath)
}
next:
// 4. check if user identity has old format.
identityPath := pathJoin(basePrefix, user, iamIdentityFile)
var cred auth.Credentials
if err := iamOS.loadIAMConfig(&cred, identityPath); err != nil {
if err := iamOS.loadIAMConfig(ctx, &cred, identityPath); err != nil {
switch err {
case errConfigNotFound:
// This should not happen.
@@ -150,7 +149,7 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context, isSTS b
// into new format and save it.
cred.AccessKey = user
u := newUserIdentity(cred)
if err := iamOS.saveIAMConfig(u, identityPath); err != nil {
if err := iamOS.saveIAMConfig(ctx, u, identityPath); err != nil {
logger.LogIf(ctx, err)
return err
}
@@ -165,7 +164,7 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context, isSTS b
func (iamOS *IAMObjectStore) migrateToV1(ctx context.Context) error {
var iamFmt iamFormat
path := getIAMFormatFilePath()
if err := iamOS.loadIAMConfig(&iamFmt, path); err != nil {
if err := iamOS.loadIAMConfig(ctx, &iamFmt, path); err != nil {
switch err {
case errConfigNotFound:
// Need to migrate to V1.
@@ -193,7 +192,7 @@ func (iamOS *IAMObjectStore) migrateToV1(ctx context.Context) error {
return err
}
// Save iam format to version 1.
if err := iamOS.saveIAMConfig(newIAMFormatVersion1(), path); err != nil {
if err := iamOS.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
logger.LogIf(ctx, err)
return err
}
@@ -205,7 +204,7 @@ func (iamOS *IAMObjectStore) migrateBackendFormat(ctx context.Context) error {
return iamOS.migrateToV1(ctx)
}
func (iamOS *IAMObjectStore) saveIAMConfig(item interface{}, path string) error {
func (iamOS *IAMObjectStore) saveIAMConfig(ctx context.Context, item interface{}, path string) error {
data, err := json.Marshal(item)
if err != nil {
return err
@@ -216,11 +215,11 @@ func (iamOS *IAMObjectStore) saveIAMConfig(item interface{}, path string) error
return err
}
}
return saveConfig(GlobalContext, iamOS.objAPI, path, data)
return saveConfig(ctx, iamOS.objAPI, path, data)
}
func (iamOS *IAMObjectStore) loadIAMConfig(item interface{}, path string) error {
data, err := readConfig(iamOS.ctx, iamOS.objAPI, path)
func (iamOS *IAMObjectStore) loadIAMConfig(ctx context.Context, item interface{}, path string) error {
data, err := readConfig(ctx, iamOS.objAPI, path)
if err != nil {
return err
}
@@ -233,13 +232,13 @@ func (iamOS *IAMObjectStore) loadIAMConfig(item interface{}, path string) error
return json.Unmarshal(data, item)
}
func (iamOS *IAMObjectStore) deleteIAMConfig(path string) error {
return deleteConfig(iamOS.ctx, iamOS.objAPI, path)
func (iamOS *IAMObjectStore) deleteIAMConfig(ctx context.Context, path string) error {
return deleteConfig(ctx, iamOS.objAPI, path)
}
func (iamOS *IAMObjectStore) loadPolicyDoc(policy string, m map[string]iampolicy.Policy) error {
func (iamOS *IAMObjectStore) loadPolicyDoc(ctx context.Context, policy string, m map[string]iampolicy.Policy) error {
var p iampolicy.Policy
err := iamOS.loadIAMConfig(&p, getPolicyDocPath(policy))
err := iamOS.loadIAMConfig(ctx, &p, getPolicyDocPath(policy))
if err != nil {
if err == errConfigNotFound {
return errNoSuchPolicy
@@ -257,16 +256,16 @@ func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]ia
}
policyName := item.Item
if err := iamOS.loadPolicyDoc(policyName, m); err != nil && err != errNoSuchPolicy {
if err := iamOS.loadPolicyDoc(ctx, policyName, m); err != nil && err != errNoSuchPolicy {
return err
}
}
return nil
}
func (iamOS *IAMObjectStore) loadUser(user string, userType IAMUserType, m map[string]auth.Credentials) error {
func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error {
var u UserIdentity
err := iamOS.loadIAMConfig(&u, getUserIdentityPath(user, userType))
err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil {
if err == errConfigNotFound {
return errNoSuchUser
@@ -276,8 +275,8 @@ func (iamOS *IAMObjectStore) loadUser(user string, userType IAMUserType, m map[s
if u.Credentials.IsExpired() {
// Delete expired identity - ignoring errors here.
iamOS.deleteIAMConfig(getUserIdentityPath(user, userType))
iamOS.deleteIAMConfig(getMappedPolicyPath(user, userType, false))
iamOS.deleteIAMConfig(ctx, getUserIdentityPath(user, userType))
iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(user, userType, false))
return nil
}
@@ -292,7 +291,7 @@ func (iamOS *IAMObjectStore) loadUser(user string, userType IAMUserType, m map[s
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))
if token, err := jwt.SignedString([]byte(globalActiveCred.SecretKey)); err == nil {
u.Credentials.SessionToken = token
err := iamOS.saveIAMConfig(&u, getUserIdentityPath(user, userType))
err := iamOS.saveIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil {
return err
}
@@ -326,16 +325,16 @@ func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType
}
userName := item.Item
if err := iamOS.loadUser(userName, userType, m); err != nil && err != errNoSuchUser {
if err := iamOS.loadUser(ctx, userName, userType, m); err != nil && err != errNoSuchUser {
return err
}
}
return nil
}
func (iamOS *IAMObjectStore) loadGroup(group string, m map[string]GroupInfo) error {
func (iamOS *IAMObjectStore) loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error {
var g GroupInfo
err := iamOS.loadIAMConfig(&g, getGroupInfoPath(group))
err := iamOS.loadIAMConfig(ctx, &g, getGroupInfoPath(group))
if err != nil {
if err == errConfigNotFound {
return errNoSuchGroup
@@ -353,18 +352,18 @@ func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupI
}
group := item.Item
if err := iamOS.loadGroup(group, m); err != nil && err != errNoSuchGroup {
if err := iamOS.loadGroup(ctx, group, m); err != nil && err != errNoSuchGroup {
return err
}
}
return nil
}
func (iamOS *IAMObjectStore) loadMappedPolicy(name string, userType IAMUserType, isGroup bool,
func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool,
m map[string]MappedPolicy) error {
var p MappedPolicy
err := iamOS.loadIAMConfig(&p, getMappedPolicyPath(name, userType, isGroup))
err := iamOS.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
if err != nil {
if err == errConfigNotFound {
return errNoSuchPolicy
@@ -396,7 +395,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IA
policyFile := item.Item
userOrGroupName := strings.TrimSuffix(policyFile, ".json")
if err := iamOS.loadMappedPolicy(userOrGroupName, userType, isGroup, m); err != nil && err != errNoSuchPolicy {
if err := iamOS.loadMappedPolicy(ctx, userOrGroupName, userType, isGroup, m); err != nil && err != errNoSuchPolicy {
return err
}
}
@@ -489,7 +488,7 @@ func (iamOS *IAMObjectStore) loadAll(ctx context.Context, sys *IAMSys) error {
if v.IsServiceAccount() {
for _, accessKey := range expiredEntries {
if v.ParentUser == accessKey {
_ = iamOS.deleteUserIdentity(v.AccessKey, srvAccUser)
_ = iamOS.deleteUserIdentity(ctx, v.AccessKey, srvAccUser)
delete(sys.iamUsersMap, v.AccessKey)
}
}
@@ -510,48 +509,48 @@ func (iamOS *IAMObjectStore) loadAll(ctx context.Context, sys *IAMSys) error {
return nil
}
func (iamOS *IAMObjectStore) savePolicyDoc(policyName string, p iampolicy.Policy) error {
return iamOS.saveIAMConfig(&p, getPolicyDocPath(policyName))
func (iamOS *IAMObjectStore) savePolicyDoc(ctx context.Context, policyName string, p iampolicy.Policy) error {
return iamOS.saveIAMConfig(ctx, &p, getPolicyDocPath(policyName))
}
func (iamOS *IAMObjectStore) saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
return iamOS.saveIAMConfig(mp, getMappedPolicyPath(name, userType, isGroup))
func (iamOS *IAMObjectStore) saveMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
return iamOS.saveIAMConfig(ctx, mp, getMappedPolicyPath(name, userType, isGroup))
}
func (iamOS *IAMObjectStore) saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error {
return iamOS.saveIAMConfig(u, getUserIdentityPath(name, userType))
func (iamOS *IAMObjectStore) saveUserIdentity(ctx context.Context, name string, userType IAMUserType, u UserIdentity) error {
return iamOS.saveIAMConfig(ctx, u, getUserIdentityPath(name, userType))
}
func (iamOS *IAMObjectStore) saveGroupInfo(name string, gi GroupInfo) error {
return iamOS.saveIAMConfig(gi, getGroupInfoPath(name))
func (iamOS *IAMObjectStore) saveGroupInfo(ctx context.Context, name string, gi GroupInfo) error {
return iamOS.saveIAMConfig(ctx, gi, getGroupInfoPath(name))
}
func (iamOS *IAMObjectStore) deletePolicyDoc(name string) error {
err := iamOS.deleteIAMConfig(getPolicyDocPath(name))
func (iamOS *IAMObjectStore) deletePolicyDoc(ctx context.Context, name string) error {
err := iamOS.deleteIAMConfig(ctx, getPolicyDocPath(name))
if err == errConfigNotFound {
err = errNoSuchPolicy
}
return err
}
func (iamOS *IAMObjectStore) deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error {
err := iamOS.deleteIAMConfig(getMappedPolicyPath(name, userType, isGroup))
func (iamOS *IAMObjectStore) deleteMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool) error {
err := iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(name, userType, isGroup))
if err == errConfigNotFound {
err = errNoSuchPolicy
}
return err
}
func (iamOS *IAMObjectStore) deleteUserIdentity(name string, userType IAMUserType) error {
err := iamOS.deleteIAMConfig(getUserIdentityPath(name, userType))
func (iamOS *IAMObjectStore) deleteUserIdentity(ctx context.Context, name string, userType IAMUserType) error {
err := iamOS.deleteIAMConfig(ctx, getUserIdentityPath(name, userType))
if err == errConfigNotFound {
err = errNoSuchUser
}
return err
}
func (iamOS *IAMObjectStore) deleteGroupInfo(name string) error {
err := iamOS.deleteIAMConfig(getGroupInfoPath(name))
func (iamOS *IAMObjectStore) deleteGroupInfo(ctx context.Context, name string) error {
err := iamOS.deleteIAMConfig(ctx, getGroupInfoPath(name))
if err == errConfigNotFound {
err = errNoSuchGroup
}
+94 -115
View File
@@ -240,33 +240,33 @@ type IAMStorageAPI interface {
migrateBackendFormat(context.Context) error
loadPolicyDoc(policy string, m map[string]iampolicy.Policy) error
loadPolicyDoc(ctx context.Context, policy string, m map[string]iampolicy.Policy) error
loadPolicyDocs(ctx context.Context, m map[string]iampolicy.Policy) error
loadUser(user string, userType IAMUserType, m map[string]auth.Credentials) error
loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error
loadUsers(ctx context.Context, userType IAMUserType, m map[string]auth.Credentials) error
loadGroup(group string, m map[string]GroupInfo) error
loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error
loadGroups(ctx context.Context, m map[string]GroupInfo) error
loadMappedPolicy(name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
loadAll(context.Context, *IAMSys) error
saveIAMConfig(item interface{}, path string) error
loadIAMConfig(item interface{}, path string) error
deleteIAMConfig(path string) error
saveIAMConfig(ctx context.Context, item interface{}, path string) error
loadIAMConfig(ctx context.Context, item interface{}, path string) error
deleteIAMConfig(ctx context.Context, path string) error
savePolicyDoc(policyName string, p iampolicy.Policy) error
saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error
saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error
saveGroupInfo(group string, gi GroupInfo) error
savePolicyDoc(ctx context.Context, policyName string, p iampolicy.Policy) error
saveMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error
saveUserIdentity(ctx context.Context, name string, userType IAMUserType, u UserIdentity) error
saveGroupInfo(ctx context.Context, group string, gi GroupInfo) error
deletePolicyDoc(policyName string) error
deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error
deleteUserIdentity(name string, userType IAMUserType) error
deleteGroupInfo(name string) error
deletePolicyDoc(ctx context.Context, policyName string) error
deleteMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool) error
deleteUserIdentity(ctx context.Context, name string, userType IAMUserType) error
deleteGroupInfo(ctx context.Context, name string) error
watch(context.Context, *IAMSys)
}
@@ -289,7 +289,7 @@ func (sys *IAMSys) LoadGroup(objAPI ObjectLayer, group string) error {
sys.store.lock()
defer sys.store.unlock()
err := sys.store.loadGroup(group, sys.iamGroupsMap)
err := sys.store.loadGroup(context.Background(), group, sys.iamGroupsMap)
if err != nil && err != errNoSuchGroup {
return err
}
@@ -326,7 +326,7 @@ func (sys *IAMSys) LoadPolicy(objAPI ObjectLayer, policyName string) error {
defer sys.store.unlock()
if globalEtcdClient == nil {
return sys.store.loadPolicyDoc(policyName, sys.iamPolicyDocsMap)
return sys.store.loadPolicyDoc(context.Background(), policyName, sys.iamPolicyDocsMap)
}
// When etcd is set, we use watch APIs so this code is not needed.
@@ -346,9 +346,9 @@ func (sys *IAMSys) LoadPolicyMapping(objAPI ObjectLayer, userOrGroup string, isG
if globalEtcdClient == nil {
var err error
if isGroup {
err = sys.store.loadMappedPolicy(userOrGroup, regularUser, isGroup, sys.iamGroupPolicyMap)
err = sys.store.loadMappedPolicy(context.Background(), userOrGroup, regularUser, isGroup, sys.iamGroupPolicyMap)
} else {
err = sys.store.loadMappedPolicy(userOrGroup, regularUser, isGroup, sys.iamUserPolicyMap)
err = sys.store.loadMappedPolicy(context.Background(), userOrGroup, regularUser, isGroup, sys.iamUserPolicyMap)
}
// Ignore policy not mapped error
@@ -370,11 +370,11 @@ func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, userType IAMUs
defer sys.store.unlock()
if globalEtcdClient == nil {
err := sys.store.loadUser(accessKey, userType, sys.iamUsersMap)
err := sys.store.loadUser(context.Background(), accessKey, userType, sys.iamUsersMap)
if err != nil {
return err
}
err = sys.store.loadMappedPolicy(accessKey, userType, false, sys.iamUserPolicyMap)
err = sys.store.loadMappedPolicy(context.Background(), accessKey, userType, false, sys.iamUserPolicyMap)
// Ignore policy not mapped error
if err != nil && err != errNoSuchPolicy {
return err
@@ -394,7 +394,7 @@ func (sys *IAMSys) LoadServiceAccount(accessKey string) error {
defer sys.store.unlock()
if globalEtcdClient == nil {
err := sys.store.loadUser(accessKey, srvAccUser, sys.iamUsersMap)
err := sys.store.loadUser(context.Background(), accessKey, srvAccUser, sys.iamUsersMap)
if err != nil {
return err
}
@@ -408,30 +408,21 @@ func (sys *IAMSys) doIAMConfigMigration(ctx context.Context) error {
return sys.store.migrateBackendFormat(ctx)
}
// Loads IAM users and policies in background, any un-handled
// error means this code can potentially crash the server
// in such a situation manual intervention is necessary.
func startBackgroundIAMLoad(ctx context.Context) {
go globalIAMSys.Init(ctx, newObjectLayerWithoutSafeModeFn())
}
// Init - initializes config system by reading entries from config/iam
func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
if objAPI == nil {
logger.LogIf(ctx, errServerNotInitialized)
return
}
// InitStore initializes IAM stores
func (sys *IAMSys) InitStore(objAPI ObjectLayer) {
if globalEtcdClient == nil {
sys.store = newIAMObjectStore(ctx, objAPI)
sys.store = newIAMObjectStore(objAPI)
} else {
sys.store = newIAMEtcdStore(ctx)
sys.store = newIAMEtcdStore()
}
if globalLDAPConfig.Enabled {
sys.EnableLDAPSys()
}
}
// Init - initializes config system by reading entries from config/iam
func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
retryCtx, cancel := context.WithCancel(ctx)
// Indicate to our routine to exit cleanly upon return.
@@ -514,8 +505,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
// DeletePolicy - deletes a canned policy from backend or etcd.
func (sys *IAMSys) DeletePolicy(policyName string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -526,7 +516,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
sys.store.lock()
defer sys.store.unlock()
err := sys.store.deletePolicyDoc(policyName)
err := sys.store.deletePolicyDoc(context.Background(), policyName)
if err == errNoSuchPolicy {
// Ignore error if policy is already deleted.
err = nil
@@ -567,8 +557,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
// InfoPolicy - expands the canned policy into its JSON structure.
func (sys *IAMSys) InfoPolicy(policyName string) (iampolicy.Policy, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return iampolicy.Policy{}, errServerNotInitialized
}
@@ -585,8 +574,7 @@ func (sys *IAMSys) InfoPolicy(policyName string) (iampolicy.Policy, error) {
// ListPolicies - lists all canned policies.
func (sys *IAMSys) ListPolicies() (map[string]iampolicy.Policy, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return nil, errServerNotInitialized
}
@@ -607,8 +595,7 @@ func (sys *IAMSys) ListPolicies() (map[string]iampolicy.Policy, error) {
// SetPolicy - sets a new name policy.
func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -619,7 +606,7 @@ func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
sys.store.lock()
defer sys.store.unlock()
if err := sys.store.savePolicyDoc(policyName, p); err != nil {
if err := sys.store.savePolicyDoc(context.Background(), policyName, p); err != nil {
return err
}
@@ -629,8 +616,7 @@ func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
// DeleteUser - delete user (only for long-term users not STS users).
func (sys *IAMSys) DeleteUser(accessKey string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -659,15 +645,15 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
for _, u := range sys.iamUsersMap {
if u.IsServiceAccount() {
if u.ParentUser == accessKey {
_ = sys.store.deleteUserIdentity(u.AccessKey, srvAccUser)
_ = sys.store.deleteUserIdentity(context.Background(), u.AccessKey, srvAccUser)
delete(sys.iamUsersMap, u.AccessKey)
}
}
}
// It is ok to ignore deletion error on the mapped policy
sys.store.deleteMappedPolicy(accessKey, regularUser, false)
err := sys.store.deleteUserIdentity(accessKey, regularUser)
sys.store.deleteMappedPolicy(context.Background(), accessKey, regularUser, false)
err := sys.store.deleteUserIdentity(context.Background(), accessKey, regularUser)
if err == errNoSuchUser {
// ignore if user is already deleted.
err = nil
@@ -683,6 +669,9 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
// after validating if there are any current policies which exist
// on MinIO corresponding to the input.
func (sys *IAMSys) currentPolicies(policyName string) string {
if sys.store == nil {
return ""
}
sys.store.rlock()
defer sys.store.runlock()
@@ -699,8 +688,7 @@ func (sys *IAMSys) currentPolicies(policyName string) string {
// SetTempUser - set temporary user credentials, these credentials have an expiry.
func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyName string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -731,7 +719,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
return nil
}
if err := sys.store.saveMappedPolicy(accessKey, stsUser, false, mp); err != nil {
if err := sys.store.saveMappedPolicy(context.Background(), accessKey, stsUser, false, mp); err != nil {
return err
}
@@ -739,7 +727,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
}
u := newUserIdentity(cred)
if err := sys.store.saveUserIdentity(accessKey, stsUser, u); err != nil {
if err := sys.store.saveUserIdentity(context.Background(), accessKey, stsUser, u); err != nil {
return err
}
@@ -749,8 +737,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
// ListUsers - list all users.
func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return nil, errServerNotInitialized
}
@@ -786,8 +773,7 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
// IsTempUser - returns if given key is a temporary user.
func (sys *IAMSys) IsTempUser(name string) (bool, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return false, errServerNotInitialized
}
@@ -804,8 +790,7 @@ func (sys *IAMSys) IsTempUser(name string) (bool, error) {
// IsServiceAccount - returns if given key is a service account
func (sys *IAMSys) IsServiceAccount(name string) (bool, string, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return false, "", errServerNotInitialized
}
@@ -826,8 +811,7 @@ func (sys *IAMSys) IsServiceAccount(name string) (bool, string, error) {
// GetUserInfo - get info on a user.
func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return u, errServerNotInitialized
}
@@ -872,8 +856,8 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
// SetUserStatus - sets current user status, supports disabled or enabled.
func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -908,7 +892,7 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
}(),
})
if err := sys.store.saveUserIdentity(accessKey, regularUser, uinfo); err != nil {
if err := sys.store.saveUserIdentity(context.Background(), accessKey, regularUser, uinfo); err != nil {
return err
}
@@ -918,8 +902,8 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
// NewServiceAccount - create a new service account
func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, sessionPolicy *iampolicy.Policy) (auth.Credentials, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return auth.Credentials{}, errServerNotInitialized
}
@@ -974,7 +958,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, ses
u := newUserIdentity(cred)
if err := sys.store.saveUserIdentity(u.Credentials.AccessKey, srvAccUser, u); err != nil {
if err := sys.store.saveUserIdentity(context.Background(), u.Credentials.AccessKey, srvAccUser, u); err != nil {
return auth.Credentials{}, err
}
@@ -985,8 +969,8 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, ses
// ListServiceAccounts - lists all services accounts associated to a specific user
func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([]string, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return nil, errServerNotInitialized
}
@@ -1010,8 +994,8 @@ func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([
// GetServiceAccountParent - gets information about a service account
func (sys *IAMSys) GetServiceAccountParent(ctx context.Context, accessKey string) (string, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return "", errServerNotInitialized
}
@@ -1027,8 +1011,8 @@ func (sys *IAMSys) GetServiceAccountParent(ctx context.Context, accessKey string
// DeleteServiceAccount - delete a service account
func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -1041,7 +1025,7 @@ func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string) e
}
// It is ok to ignore deletion error on the mapped policy
err := sys.store.deleteUserIdentity(accessKey, srvAccUser)
err := sys.store.deleteUserIdentity(context.Background(), accessKey, srvAccUser)
if err != nil {
// ignore if user is already deleted.
if err == errNoSuchUser {
@@ -1056,8 +1040,8 @@ func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string) e
// SetUser - set user credentials and policy.
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -1079,7 +1063,7 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
return errIAMActionNotAllowed
}
if err := sys.store.saveUserIdentity(accessKey, regularUser, u); err != nil {
if err := sys.store.saveUserIdentity(context.Background(), accessKey, regularUser, u); err != nil {
return err
}
@@ -1094,8 +1078,8 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
// SetUserSecretKey - sets user secret key
func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -1113,7 +1097,7 @@ func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
cred.SecretKey = secretKey
u := newUserIdentity(cred)
if err := sys.store.saveUserIdentity(accessKey, regularUser, u); err != nil {
if err := sys.store.saveUserIdentity(context.Background(), accessKey, regularUser, u); err != nil {
return err
}
@@ -1123,8 +1107,7 @@ func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
// GetUser - get user credentials
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return cred, false
}
@@ -1135,34 +1118,37 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
sys.store.lock()
// If user is already found proceed.
if _, found := sys.iamUsersMap[accessKey]; !found {
sys.store.loadUser(accessKey, regularUser, sys.iamUsersMap)
sys.store.loadUser(context.Background(), accessKey, regularUser, sys.iamUsersMap)
if _, found = sys.iamUsersMap[accessKey]; found {
// found user, load its mapped policies
sys.store.loadMappedPolicy(accessKey, regularUser, false, sys.iamUserPolicyMap)
sys.store.loadMappedPolicy(context.Background(), accessKey, regularUser, false, sys.iamUserPolicyMap)
} else {
sys.store.loadUser(accessKey, srvAccUser, sys.iamUsersMap)
sys.store.loadUser(context.Background(), accessKey, srvAccUser, sys.iamUsersMap)
if svc, found := sys.iamUsersMap[accessKey]; found {
// Found service account, load its parent user and its mapped policies.
if sys.usersSysType == MinIOUsersSysType {
sys.store.loadUser(svc.ParentUser, regularUser, sys.iamUsersMap)
sys.store.loadUser(context.Background(), svc.ParentUser, regularUser, sys.iamUsersMap)
}
sys.store.loadMappedPolicy(svc.ParentUser, regularUser, false, sys.iamUserPolicyMap)
sys.store.loadMappedPolicy(context.Background(), svc.ParentUser, regularUser, false, sys.iamUserPolicyMap)
} else {
// None found fall back to STS users.
sys.store.loadUser(accessKey, stsUser, sys.iamUsersMap)
sys.store.loadUser(context.Background(), accessKey, stsUser, sys.iamUsersMap)
if _, found = sys.iamUsersMap[accessKey]; found {
// STS user found, load its mapped policy.
sys.store.loadMappedPolicy(accessKey, stsUser, false, sys.iamUserPolicyMap)
sys.store.loadMappedPolicy(context.Background(), accessKey, stsUser, false, sys.iamUserPolicyMap)
}
}
}
}
// Load associated policies if any.
for _, policy := range sys.iamUserPolicyMap[accessKey].toSlice() {
if _, found := sys.iamPolicyDocsMap[policy]; !found {
sys.store.loadPolicyDoc(policy, sys.iamPolicyDocsMap)
sys.store.loadPolicyDoc(context.Background(), policy, sys.iamPolicyDocsMap)
}
}
sys.buildUserGroupMemberships()
sys.store.unlock()
}
@@ -1187,8 +1173,7 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
// AddUsersToGroup - adds users to a group, creating the group if
// needed. No error if user(s) already are in the group.
func (sys *IAMSys) AddUsersToGroup(group string, members []string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -1225,7 +1210,7 @@ func (sys *IAMSys) AddUsersToGroup(group string, members []string) error {
gi.Members = uniqMembers
}
if err := sys.store.saveGroupInfo(group, gi); err != nil {
if err := sys.store.saveGroupInfo(context.Background(), group, gi); err != nil {
return err
}
@@ -1248,8 +1233,7 @@ func (sys *IAMSys) AddUsersToGroup(group string, members []string) error {
// RemoveUsersFromGroup - remove users from group. If no users are
// given, and the group is empty, deletes the group as well.
func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -1290,10 +1274,10 @@ func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
// Remove the group from storage. First delete the
// mapped policy. No-mapped-policy case is ignored.
if err := sys.store.deleteMappedPolicy(group, regularUser, true); err != nil && err != errNoSuchPolicy {
if err := sys.store.deleteMappedPolicy(context.Background(), group, regularUser, true); err != nil && err != errNoSuchPolicy {
return err
}
if err := sys.store.deleteGroupInfo(group); err != nil && err != errNoSuchGroup {
if err := sys.store.deleteGroupInfo(context.Background(), group); err != nil && err != errNoSuchGroup {
return err
}
@@ -1308,7 +1292,7 @@ func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
d := set.CreateStringSet(members...)
gi.Members = s.Difference(d).ToSlice()
err := sys.store.saveGroupInfo(group, gi)
err := sys.store.saveGroupInfo(context.Background(), group, gi)
if err != nil {
return err
}
@@ -1329,8 +1313,7 @@ func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
// SetGroupStatus - enable/disabled a group
func (sys *IAMSys) SetGroupStatus(group string, enabled bool) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -1356,7 +1339,7 @@ func (sys *IAMSys) SetGroupStatus(group string, enabled bool) error {
gi.Status = statusDisabled
}
if err := sys.store.saveGroupInfo(group, gi); err != nil {
if err := sys.store.saveGroupInfo(context.Background(), group, gi); err != nil {
return err
}
sys.iamGroupsMap[group] = gi
@@ -1365,8 +1348,7 @@ func (sys *IAMSys) SetGroupStatus(group string, enabled bool) error {
// GetGroupDescription - builds up group description
func (sys *IAMSys) GetGroupDescription(group string) (gd madmin.GroupDesc, err error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return gd, errServerNotInitialized
}
@@ -1406,8 +1388,7 @@ func (sys *IAMSys) GetGroupDescription(group string) (gd madmin.GroupDesc, err e
// ListGroups - lists groups.
func (sys *IAMSys) ListGroups() (r []string, err error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return r, errServerNotInitialized
}
@@ -1430,8 +1411,7 @@ func (sys *IAMSys) ListGroups() (r []string, err error) {
// PolicyDBSet - sets a policy for a user or group in the PolicyDB.
func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
@@ -1462,7 +1442,7 @@ func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, is
// Handle policy mapping removal
if policyName == "" {
if err := sys.store.deleteMappedPolicy(name, userType, isGroup); err != nil && err != errNoSuchPolicy {
if err := sys.store.deleteMappedPolicy(context.Background(), name, userType, isGroup); err != nil && err != errNoSuchPolicy {
return err
}
if !isGroup {
@@ -1482,7 +1462,7 @@ func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, is
}
// Handle policy mapping set/update
if err := sys.store.saveMappedPolicy(name, userType, isGroup, mp); err != nil {
if err := sys.store.saveMappedPolicy(context.Background(), name, userType, isGroup, mp); err != nil {
return err
}
if !isGroup {
@@ -1497,8 +1477,7 @@ func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, is
// be a member of multiple groups, this function returns an array of
// applicable policies (each group is mapped to at most one policy).
func (sys *IAMSys) PolicyDBGet(name string, isGroup bool) ([]string, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
if sys == nil || sys.store == nil {
return nil, errServerNotInitialized
}
-3
View File
@@ -104,9 +104,6 @@ func webTokenCallback(claims *xjwt.MapClaims) ([]byte, error) {
if claims.AccessKey == globalActiveCred.AccessKey {
return []byte(globalActiveCred.SecretKey), nil
}
if globalIAMSys == nil {
return nil, errInvalidAccessKeyID
}
ok, err := globalIAMSys.IsTempUser(claims.AccessKey)
if err != nil {
if err == errNoSuchUser {
+18 -8
View File
@@ -35,6 +35,8 @@ type lockRequesterInfo struct {
// Owner represents the UUID of the owner who originally requested the lock
// useful in expiry.
Owner string
// Quorum represents the quorum required for this lock to be active.
Quorum int
}
// isWriteLock returns whether the lock is a write or read lock.
@@ -96,6 +98,7 @@ func (l *localLocker) Lock(ctx context.Context, args dsync.LockArgs) (reply bool
UID: args.UID,
Timestamp: UTCNow(),
TimeLastCheck: UTCNow(),
Quorum: args.Quorum,
},
}
}
@@ -111,9 +114,9 @@ func (l *localLocker) Unlock(args dsync.LockArgs) (reply bool, err error) {
return reply, fmt.Errorf("Unlock attempted on a read locked entity: %s", args.Resources)
}
for _, resource := range args.Resources {
lri := l.lockMap[resource]
if !l.removeEntry(resource, args, &lri) {
return false, fmt.Errorf("Unlock unable to find corresponding lock for uid: %s on resource %s", args.UID, resource)
lri, ok := l.lockMap[resource]
if ok {
l.removeEntry(resource, args, &lri)
}
}
return true, nil
@@ -153,6 +156,7 @@ func (l *localLocker) RLock(ctx context.Context, args dsync.LockArgs) (reply boo
UID: args.UID,
Timestamp: UTCNow(),
TimeLastCheck: UTCNow(),
Quorum: args.Quorum,
}
resource := args.Resources[0]
if lri, ok := l.lockMap[resource]; ok {
@@ -176,15 +180,13 @@ func (l *localLocker) RUnlock(args dsync.LockArgs) (reply bool, err error) {
resource := args.Resources[0]
if lri, reply = l.lockMap[resource]; !reply {
// No lock is held on the given name
return reply, fmt.Errorf("RUnlock attempted on an unlocked entity: %s", resource)
return true, nil
}
if reply = !isWriteLock(lri); !reply {
// A write-lock is held, cannot release a read lock
return reply, fmt.Errorf("RUnlock attempted on a write locked entity: %s", resource)
}
if !l.removeEntry(resource, args, &lri) {
return false, fmt.Errorf("RUnlock unable to find corresponding read lock for uid: %s", args.UID)
}
l.removeEntry(resource, args, &lri)
return reply, nil
}
@@ -203,11 +205,16 @@ func (l *localLocker) Close() error {
return nil
}
// Local locker is always online.
// IsOnline - local locker is always online.
func (l *localLocker) IsOnline() bool {
return true
}
// IsLocal - local locker returns true.
func (l *localLocker) IsLocal() bool {
return true
}
func (l *localLocker) Expired(ctx context.Context, args dsync.LockArgs) (expired bool, err error) {
select {
case <-ctx.Done():
@@ -234,6 +241,9 @@ func (l *localLocker) Expired(ctx context.Context, args dsync.LockArgs) (expired
// Similar to removeEntry but only removes an entry only if the lock entry exists in map.
// Caller must hold 'l.mutex' lock.
func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) {
l.mutex.Lock()
defer l.mutex.Unlock()
// Check if entry is still in map (could have been removed altogether by 'concurrent' (R)Unlock of last entry)
if lri, ok := l.lockMap[nlrip.name]; ok {
// Even if the entry exists, it may not be the same entry which was
+8 -2
View File
@@ -23,7 +23,7 @@ import (
"errors"
"io"
"net/url"
"time"
"strconv"
"github.com/minio/minio/cmd/http"
xhttp "github.com/minio/minio/cmd/http"
@@ -77,6 +77,11 @@ func (client *lockRESTClient) IsOnline() bool {
return client.restClient.IsOnline()
}
// Not a local locker
func (client *lockRESTClient) IsLocal() bool {
return false
}
// Close - marks the client as closed.
func (client *lockRESTClient) Close() error {
client.restClient.Close()
@@ -89,6 +94,7 @@ func (client *lockRESTClient) restCall(ctx context.Context, call string, args ds
values.Set(lockRESTUID, args.UID)
values.Set(lockRESTOwner, args.Owner)
values.Set(lockRESTSource, args.Source)
values.Set(lockRESTQuorum, strconv.Itoa(args.Quorum))
var buffer bytes.Buffer
for _, resource := range args.Resources {
buffer.WriteString(resource)
@@ -154,7 +160,7 @@ func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
}
}
trFn := newInternodeHTTPTransport(tlsConfig, 10*time.Second)
trFn := newInternodeHTTPTransport(tlsConfig, rest.DefaultTimeout)
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
restClient.HealthCheckFn = func() bool {
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
+5 -1
View File
@@ -21,7 +21,7 @@ import (
)
const (
lockRESTVersion = "v3"
lockRESTVersion = "v4" // Add Quorum query param
lockRESTVersionPrefix = SlashSeparator + lockRESTVersion
lockRESTPrefix = minioReservedBucketPath + "/lock"
)
@@ -43,6 +43,10 @@ const (
// Source contains the line number, function and file name of the code
// on the client node that requested the lock.
lockRESTSource = "source"
// Quroum value to be saved along lock requester info, useful
// in verifying stale locks
lockRESTQuorum = "quorum"
)
var (
+22 -27
View File
@@ -24,6 +24,7 @@ import (
"net/http"
"path"
"sort"
"strconv"
"time"
"github.com/gorilla/mux"
@@ -35,7 +36,7 @@ const (
lockMaintenanceInterval = 30 * time.Second
// Lock validity check interval.
lockValidityCheckInterval = 2 * time.Minute
lockValidityCheckInterval = 5 * time.Second
)
// To abstract a node over network.
@@ -63,10 +64,16 @@ func (l *lockRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
}
func getLockArgs(r *http.Request) (args dsync.LockArgs, err error) {
quorum, err := strconv.Atoi(r.URL.Query().Get(lockRESTQuorum))
if err != nil {
return args, err
}
args = dsync.LockArgs{
Owner: r.URL.Query().Get(lockRESTOwner),
UID: r.URL.Query().Get(lockRESTUID),
Source: r.URL.Query().Get(lockRESTSource),
Quorum: quorum,
}
var resources []string
@@ -242,11 +249,16 @@ func getLongLivedLocks(interval time.Duration) map[Endpoint][]nameLockRequesterI
//
// We will ignore the error, and we will retry later to get a resolve on this lock
func lockMaintenance(ctx context.Context, interval time.Duration) error {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
return nil
}
z, ok := objAPI.(*erasureServerSets)
if !ok {
return nil
}
type nlock struct {
locks int
writer bool
@@ -265,18 +277,15 @@ func lockMaintenance(ctx context.Context, interval time.Duration) error {
}
}
allLockersFn := z.GetAllLockers
// Validate if long lived locks are indeed clean.
// Get list of long lived locks to check for staleness.
for lendpoint, nlrips := range getLongLivedLocks(interval) {
nlripsMap := make(map[string]nlock, len(nlrips))
for _, nlrip := range nlrips {
// Locks are only held on first zone, make sure that
// we only look for ownership of locks from endpoints
// on first zone.
for _, endpoint := range globalEndpoints[0].Endpoints {
c := newLockAPI(endpoint)
if !c.IsOnline() {
updateNlocks(nlripsMap, nlrip.name, nlrip.lri.Writer)
for _, c := range allLockersFn() {
if !c.IsOnline() || c == nil {
continue
}
@@ -292,32 +301,18 @@ func lockMaintenance(ctx context.Context, interval time.Duration) error {
cancel()
if err != nil {
updateNlocks(nlripsMap, nlrip.name, nlrip.lri.Writer)
c.Close()
continue
}
if !expired {
updateNlocks(nlripsMap, nlrip.name, nlrip.lri.Writer)
}
// Close the connection regardless of the call response.
c.Close()
}
// Read locks we assume quorum for be N/2 success
quorum := getReadQuorum(objAPI.SetDriveCount())
if nlrip.lri.Writer {
quorum = getWriteQuorum(objAPI.SetDriveCount())
}
// less than the quorum, we have locks expired.
if nlripsMap[nlrip.name].locks < quorum {
// The lock is no longer active at server that originated
// the lock, attempt to remove the lock.
globalLockServers[lendpoint].mutex.Lock()
if nlripsMap[nlrip.name].locks < nlrip.lri.Quorum {
// Purge the stale entry if it exists.
globalLockServers[lendpoint].removeEntryIfExists(nlrip)
globalLockServers[lendpoint].mutex.Unlock()
}
}
@@ -332,7 +327,7 @@ func startLockMaintenance(ctx context.Context) {
// no need to start the lock maintenance
// if ObjectAPI is not initialized.
for {
objAPI := newObjectLayerWithoutSafeModeFn()
objAPI := newObjectLayerFn()
if objAPI == nil {
time.Sleep(time.Second)
continue
@@ -366,8 +361,8 @@ func startLockMaintenance(ctx context.Context) {
}
// registerLockRESTHandlers - register lock rest router.
func registerLockRESTHandlers(router *mux.Router, endpointZones EndpointZones) {
for _, ep := range endpointZones {
func registerLockRESTHandlers(router *mux.Router, endpointServerSets EndpointServerSets) {
for _, ep := range endpointServerSets {
for _, endpoint := range ep.Endpoints {
if !endpoint.IsLocal {
continue
+7 -8
View File
@@ -60,17 +60,16 @@ func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
}
func (lrw *ResponseWriter) Write(p []byte) (int, error) {
if !lrw.headersLogged {
// We assume the response code to be '200 OK' when WriteHeader() is not called,
// that way following Golang HTTP response behavior.
lrw.WriteHeader(http.StatusOK)
}
n, err := lrw.ResponseWriter.Write(p)
lrw.bytesWritten += n
if lrw.TimeToFirstByte == 0 {
lrw.TimeToFirstByte = time.Now().UTC().Sub(lrw.StartTime)
}
if !lrw.headersLogged {
// We assume the response code to be '200 OK' when WriteHeader() is not called,
// that way following Golang HTTP response behavior.
lrw.writeHeaders(&lrw.headers, http.StatusOK, lrw.Header())
lrw.headersLogged = true
}
if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody {
// Always logging error responses.
lrw.body.Write(p)
@@ -107,12 +106,12 @@ func (lrw *ResponseWriter) Body() []byte {
// WriteHeader - writes http status code
func (lrw *ResponseWriter) WriteHeader(code int) {
lrw.StatusCode = code
if !lrw.headersLogged {
lrw.StatusCode = code
lrw.writeHeaders(&lrw.headers, code, lrw.ResponseWriter.Header())
lrw.headersLogged = true
lrw.ResponseWriter.WriteHeader(code)
}
lrw.ResponseWriter.WriteHeader(code)
}
// Flush - Calls the underlying Flush.

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