Compare commits

..

96 Commits

Author SHA1 Message Date
Harshavardhana 59d3639396 fix: inherit heal opts globally, including bitrot settings (#11166)
Bonus re-use ReadFileStream internal io.Copy buffers, fixes
lots of chatty allocations when reading metacache readers
with many sustained concurrent listing operations

```
   17.30GB  1.27% 84.80%    35.26GB  2.58%  io.copyBuffer
```
2020-12-24 23:04:03 -08:00
Harshavardhana 027e17468a fix: discarding results do not attempt in-memory metacache writer (#11163)
Optimizations include

- do not write the metacache block if the size of the
  block is '0' and it is the first block - where listing
  is attempted for a transient prefix, this helps to
  avoid creating lots of empty metacache entries for
  `minioMetaBucket`

- avoid the entire initialization sequence of cacheCh
  , metacacheBlockWriter if we are simply going to skip
  them when discardResults is set to true.

- No need to hold write locks while writing metacache
  blocks - each block is unique, per bucket, per prefix
  and also is written by a single node.
2020-12-24 15:02:02 -08:00
Harshavardhana 45ea161f8d webUI: change listing to 1000 keys from browser UI (#11159)
gateway implementations do not handle maxKeys being
`-1` properly unlike MinIO implementation, handle it
by setting an appropriate value.

fixes #11158
2020-12-23 19:58:15 -08:00
Poorna Krishnamoorthy 7b8a456f68 Update lifecycle README docs (#11160)
Removing reference to transition feature in docs as this
feature is being revamped to provide better extensibility
across different cloud targets.
2020-12-23 19:56:55 -08:00
Harshavardhana b43906f6ee fix: docs typos and keywords 2020-12-23 11:59:20 -08:00
Harshavardhana 6a66f142d4 fix: strict quorum in list should list on all drives (#11157)
current implementation was incorrect, it in-fact
assumed only read quorum number of disks. in-fact
that value is only meant for read quorum good entries
from all online disks.

This PR fixes this behavior properly.
2020-12-23 09:26:40 -08:00
Harshavardhana 5982965839 fix: re-use bytes.Buffer using sync.Pool (#11156) 2020-12-22 23:22:37 -08:00
Minio Trusted bfb92a27b7 Update yaml files to latest version RELEASE.2020-12-23T02-24-12Z 2020-12-23 02:43:25 +00:00
Harshavardhana 8565cefe4e fix: allow HTTP2.0 to be always configured 2020-12-22 16:32:58 -08:00
Andreas Auernhammer 8cdf2106b0 refactor cmd/crypto code for SSE handling and parsing (#11045)
This commit refactors the code in `cmd/crypto`
and separates SSE-S3, SSE-C and SSE-KMS.

This commit should not cause any behavior change
except for:
  - `IsRequested(http.Header)`

which now returns the requested type {SSE-C, SSE-S3,
SSE-KMS} and does not consider SSE-C copy headers.

However, SSE-C copy headers alone are anyway not valid.
2020-12-22 09:19:32 -08:00
Harshavardhana 35fafb837b fix: issues with handling delete markers in metacache (#11150)
Additional cases handled

- fix address situations where healing is not
  triggered on failed writes and deletes.

- consider object exists during listing when
  metadata can be successfully decoded.
2020-12-22 09:16:43 -08:00
Harshavardhana 274bbad5cb fix: select always online peers for remote listing (#11153)
always find the right set of online peers for remote listing,
this may have an effect on listing if the server is down - we
should do this to avoid always performing transient operations
on bucket->peerClient that is permanently or down for a long
period.
2020-12-22 09:16:07 -08:00
Harshavardhana 5c451d1690 update x/net/http2 to address few bugs (#11144)
additionally also configure http2 healthcheck
values to quickly detect unstable connections
and let them timeout.

also use single transport for proxying requests
2020-12-21 21:42:38 -08:00
Poorna Krishnamoorthy c987313431 Encrypt remote target if kms is configured (#11034)
Co-authored-by: Poorna Krishnamoorthy <poorna@minio.io>
2020-12-21 16:21:33 -08:00
Anis Elleuch 2ecaab55a6 admin: ServerInfo returns info without object layer initialized (#11142) 2020-12-21 09:35:19 -08:00
Harshavardhana 3e792ae2a2 fix: change defaults for DNS cache dialer (#11145) 2020-12-21 09:33:29 -08:00
Yingrong Zhao 6df6ac0f34 fix testMultipartUploadFailure to properly cleanup (#11137) 2020-12-21 08:23:27 -08:00
Harshavardhana 4cc500a041 normalize users with double // in accessKeys (#11143)
Bonus fix, use constant time compare for secret keys  in web-handlers.go:SetAuth()
2020-12-20 10:09:51 -08:00
Harshavardhana d8e28830cf fix: allow STS creds for admin accounts to add users (#11138)
Allow rotating creds with privileges to add users

fixes https://github.com/minio/console/issues/529
2020-12-19 13:24:21 -08:00
Harshavardhana 3e16ec457a fix: support user/groups with '/' character (#11127)
NOTE: user/groups with `//` shall be normalized to `/`

fixes #11126
2020-12-19 09:36:37 -08:00
Harshavardhana e5d378931d fix: delimiter based listing was broken without marker (#11136)
with missing nextMarker with delimiter based listing,
top level prefixes beyond 4500 or max-keys value
wouldn't be sent back for client to ask for the next
batch.

reproduced at a customer deployment, create prefixes
as shown below

```
for year in $(seq 2017 2020)
do
    for month in {01..12}
    do for day in {01..31}
       do
           mc -q cp file myminio/testbucket/dir/day_id=$year-$month-$day/;
       done
    done
done
```

Then perform

```
aws s3api --profile minio --endpoint-url http://localhost:9000 list-objects \
    --bucket testbucket --prefix dir/ --delimiter / --max-keys 1000
```

You shall see missing NextMarker, this would disallow listing beyond max-keys
requested and also disallow beyond 4500 (maxKeyObjectList) prefixes being listed
because client wouldn't know the NextMarker available.

This PR addresses this situation properly by making the implementation
more spec compatible. i.e NextMarker in-fact can be either an object, a prefix
with delimiter depending on the input operation.

This issue was introduced after the list caching changes and has been present
for a while.
2020-12-19 09:36:04 -08:00
Harshavardhana 6128304f6e fix: regression introduced in aws-sdk-go tests
regression was introduced by cce5d7152a
which incorrectly implemented the tests and fixed a wrong requirement,
CompleteMultipart should never succeed in the tests.
2020-12-18 21:14:27 -08:00
Anis Elleuch e63a10e505 Profiling does not required object layer to be initialized (#11133) 2020-12-18 11:51:15 -08:00
Anis Elleuch 5434088c51 replication: Ensure to always use nano precision source modtime (#11135) 2020-12-18 11:37:28 -08:00
Harshavardhana a773cf48d8 fix: overlapping object and prefix rejected (#11130)
fixes #11129
2020-12-18 08:51:09 -08:00
Minio Trusted 386dd56856 Update yaml files to latest version RELEASE.2020-12-18T03-27-42Z 2020-12-18 03:45:13 +00:00
Harshavardhana f714840da7 add _MINIO_SERVER_DEBUG env for enabling debug messages (#11128) 2020-12-17 16:52:47 -08:00
Harshavardhana 7c9ef76f66 fix: timer deadlock on expired timers (#11124)
issue was introduced in #11106 the following
pattern

<-t.C // timer fired

if !t.Stop() {
   <-t.C // timer hangs
}

Seems to hang at the last `t.C` line, this
issue happens because a fired timer cannot be
Stopped() anymore and t.Stop() returns `false`
leading to confusing state of usage.

Refactor the code such that use timers appropriately
with exact requirements in place.
2020-12-17 12:35:02 -08:00
Anis Elleuch cffdb01279 azure/s3 gateways: Pass ETag during GET call to avoid data corruption (#11024)
Both Azure & S3 gateways call for object information before returning
the stream of the object, however, the object content/length could be
modified meanwhile, which means it can return a corrupted object.

Use ETag to ensure that the object was not modified during the GET call
2020-12-17 09:11:14 -08:00
Harshavardhana 970ddb424b feat: treat /var/run/secrets/ on k8s as system cert directory (#11123)
consider `/var/run/secrets/kubernetes.io/serviceaccount`
as system cert directory for container platform.
2020-12-16 18:24:12 -08:00
Harshavardhana b390a2a0b9 fix: reuser timers in erasure set hotpaths (#11106)
reuser timers in

 - connectDisks() monitoring
 - healMRFRoutine() channel timeouts
2020-12-16 14:33:05 -08:00
Yingrong Zhao cce5d7152a fix testListMultipartUpload in aws-sdk-go (#11122)
Currently, the test doesn't complete nor abort the multipart upload
after tesing the list operation. It caused the `cleanup()` to fail since
the object can't not be deleted which causes the bucket deletion to
fail.

This PR adds a `CompleteMultipartUpload` call to commit the object so
the cleanup can successfully delete the object and bucket.
2020-12-16 13:24:10 -08:00
Harshavardhana 90158f1e33 fix: avoid logging for Heal APIs in FS mode (#11121)
fixes #11120
2020-12-16 09:46:13 -08:00
Minio Trusted 26624552be Update yaml files to latest version RELEASE.2020-12-16T05-05-17Z 2020-12-16 05:23:19 +00:00
Harshavardhana c606c76323 fix: prioritized latest buckets for crawler to finish the scans faster (#11115)
crawler should only ListBuckets once not for each serverPool,
buckets are same across all pools, across sets and ListBuckets
always returns an unified view, once list buckets returns
sort it by create time to scan the latest buckets earlier
with the assumption that latest buckets would have lesser
content than older buckets allowing them to be scanned faster
and also to be able to provide more closer to latest view.
2020-12-15 17:34:54 -08:00
Harshavardhana d674263eb7 fix: remove inode free as part of SameDisk check (#11107) 2020-12-15 11:39:38 -08:00
Klaus Post e7d3b49a20 metacache: Make very small requests transient (#11109) 2020-12-15 11:25:36 -08:00
Harshavardhana 5df61ab96b fix: remove gorilla/rpc/ deps fully after our fork (#11108) 2020-12-15 11:18:06 -08:00
Poorna Krishnamoorthy 3456b03b12 Ignore ObjectNotFound errors in delete api while enforcing locking (#11114)
AWS does not report this or version not found as errors in the response.
2020-12-15 11:15:49 -08:00
Klaus Post f6fb27e8f0 Don't copy interesting ids, clean up logging (#11102)
When searching the caches don't copy the ids, instead inline the loop.

```
Benchmark_bucketMetacache_findCache-32    	   19200	     63490 ns/op	    8303 B/op	       5 allocs/op
Benchmark_bucketMetacache_findCache-32    	   20338	     58609 ns/op	     111 B/op	       4 allocs/op
```

Add a reasonable, but still the simplistic benchmark.

Bonus - make nicer zero alloc logging
2020-12-14 13:13:33 -08:00
Harshavardhana 8368ab76aa fix: remove the requirement for healing buckets in ListBucketsHeal (#11098)
With new refactor of bucket healing, healing bucket happens
automatically including its metadata, there is no need to
redundant heal buckets also in ListBucketsHeal remove
it.
2020-12-14 12:07:07 -08:00
Harshavardhana 3e83643320 lifecycle improvements and additional debug logging (#11096)
Bonus change fix browser assets
2020-12-13 12:05:54 -08:00
Harshavardhana 2eb52ca5f4 fix: heal bucket metadata right before healing bucket (#11097)
optimization mainly to avoid listing the entire
`.minio.sys/buckets/.minio.sys` directory, this
can get really huge and comes in the way of startup
routines, contents inside `.minio.sys/buckets/.minio.sys`
are rather transient and not necessary to be healed.
2020-12-13 11:57:08 -08:00
Harshavardhana 705e196b6c upgrade event notification dependencies (#11095) 2020-12-12 20:31:28 -08:00
Andreas Auernhammer 7b5223d83d add vulnerability report policy (#11084) 2020-12-12 17:38:37 -08:00
Anis Elleuch f164085227 xl: Always set root disk to true in test environment (#11094)
Tests environments (go test or manual testing) should always consider
the passed disks are root disks and should not rely on disk.IsRootDisk()
function. The reason is that this latter can return a false negative
when called in a busy system. However, returning a false negative will
only occur in a testing environment and not in a production, so we can
accept this trade-off for now.
2020-12-12 16:10:07 -08:00
Minio Trusted 31bf6f0c25 Update yaml files to latest version RELEASE.2020-12-12T08-39-07Z 2020-12-12 08:56:30 +00:00
Harshavardhana 48191dd748 return NoSuchVersion if invalid version-id is specified (#11091) 2020-12-11 20:44:08 -08:00
Anis Elleuch c4f29d24da metacache: Ask all disks when drive count is 4 (#11087) 2020-12-11 17:54:31 -08:00
Harshavardhana db7890660e fix: a crash when disk is nil, safe access on erasureDisks (#11089)
fixes #11088
2020-12-11 16:58:36 -08:00
Poorna Krishnamoorthy 9adc33efbb Return version-id header in DeleteObject response (#11090)
even when the object version is non-existent

To make this consistent with aws behavior.

Co-authored-by: Poorna Krishnamoorthy <poorna@minio.io>
2020-12-11 16:58:15 -08:00
Poorna Krishnamoorthy 8f65aba04b ignore NoSuchVersion error in DeleteObjects API (#11086)
Currently, the error response reports NoSuchVersion
for a non-existent version-id, whereas AWS ignores it.
2020-12-11 12:39:09 -08:00
Harshavardhana 3a0082f0f1 fix: TTFB prometheus metrics calculation (#11082)
until now metrics was reporting entire call
duration instead of ttfb's this PR fixes it
2020-12-10 23:02:25 -08:00
Harshavardhana 14792cdbc6 docs: fix the metrics formatting (#11081) 2020-12-10 18:15:47 -08:00
Harshavardhana 4939987eb8 update deps to latest for some vulnerable deps (#11080)
- github.com/miekg/dns
- update elasticsearch library deps
  to circumvent some aws-sdk-go deps
2020-12-10 13:23:06 -08:00
Klaus Post 4bca62a0bd crawler: Stream bucket usage cache data (#11068)
Stream bucket caches to storage and through RPC calls.
2020-12-10 13:03:22 -08:00
Klaus Post 82e2be4239 metacache: Speed up cleanup operation (#11078)
Perform cleanup operations on copied data. Avoids read locking
data while determining which caches to keep.

Also, reduce the log(N*N) operation to log(N*M) where M caches 
with the same root or below when checking potential replacements.
2020-12-10 12:30:28 -08:00
Harshavardhana 4550ac6fff fix: refactor locks to apply them uniquely per node (#11052)
This refactor is done for few reasons below

- to avoid deadlocks in scenarios when number
  of nodes are smaller < actual erasure stripe
  count where in N participating local lockers
  can lead to deadlocks across systems.

- avoids expiry routines to run 1000 of separate
  network operations and routes per disk where
  as each of them are still accessing one single
  local entity.

- it is ideal to have since globalLockServer
  per instance.

- In a 32node deployment however, each server
  group is still concentrated towards the
  same set of lockers that partipicate during
  the write/read phase, unlike previous minio/dsync
  implementation - this potentially avoids send
  32 requests instead we will still send at max
  requests of unique nodes participating in a
  write/read phase.

- reduces overall chattiness on smaller setups.
2020-12-10 07:28:37 -08:00
Harshavardhana 97856bfebf fix: grafana double counting for bucket usage, histrogram and objects (#11070) 2020-12-09 20:30:37 -08:00
Harshavardhana a60a0e52bb ubi-minimal doesn't support arm32, remove from build manifest 2020-12-09 18:56:11 -08:00
Minio Trusted 83a67a1d21 Update yaml files to latest version RELEASE.2020-12-10T01-54-29Z 2020-12-10 02:12:01 +00:00
Harshavardhana 12391ec4ba update nats.io/jwt dependency (#11066)
primarily for a vulnerability report https://nvd.nist.gov/vuln/detail/CVE-2020-26521
2020-12-09 14:30:35 -08:00
Klaus Post e65ed2e44f listcache: Add path index (#11063)
Add a root path index.

```
Before:
Benchmark_bucketMetacache_findCache-32    	   10000	    730737 ns/op

With excluded prints:
Benchmark_bucketMetacache_findCache-32    	   10000	    207100 ns/op

With the root path:
Benchmark_bucketMetacache_findCache-32    	  705765	      1943 ns/op
```

Benchmark used (not linear):

```Go
func Benchmark_bucketMetacache_findCache(b *testing.B) {
	bm := newBucketMetacache("", false)
	for i := 0; i < b.N; i++ {
		bm.findCache(listPathOptions{
			ID:           mustGetUUID(),
			Bucket:       "",
			BaseDir:      "prefix/" + mustGetUUID(),
			Prefix:       "",
			FilterPrefix: "",
			Marker:       "",
			Limit:        0,
			AskDisks:     0,
			Recursive:    false,
			Separator:    slashSeparator,
			Create:       true,
			CurrentCycle: 0,
			OldestCycle:  0,
		})
	}
}
```

Replaces #11058
2020-12-09 08:37:43 -08:00
Anis Elleuch d90044b847 federation: Redirect Lifecycle PUT request by bucket name (#11062)
The bucket forwarder handler considers MakeBucket to be always local but
it mistakenly thinks that PUT bucket lifecycle to be a MakeBucket call.

Fix the check of the MakeBucket call by ensuring that the query is empty
in the PUT url.
2020-12-09 07:25:26 -08:00
Harshavardhana d8c1f93de6 reject mixed drive situations with drives on root disks (#11057)
till now we used to match the inode number of the root
drive and the drive path minio would use, if they match
we knew that its a root disk.

this may not be true in all situations such as running
inside a container environment where the container might
be mounted from a different partition altogether, root
disk detection might fail.
2020-12-09 00:27:02 -08:00
Nitish Tiwari 54d243cd98 fix: grafana dashboard calculating online nodes (#11041)
Also use a generic name instead of diff names per revision
2020-12-09 00:26:42 -08:00
Harshavardhana d74e4642e3 avoid updating nsswitch.conf for redhat UBI images (#11056) 2020-12-08 14:28:12 -08:00
Anis Elleuch a51488cbaa s3: Fix reading GET with partNumber specified (#11032)
partNumber was miscalculting the start and end of parts when partNumber
query is specified in the GET request. This commit fixes it and also
fixes the ContentRange header in that case.
2020-12-08 13:12:42 -08:00
Ritesh H Shukla 04848dfa1c Add documentation for bucket replication related metrics (#11055) 2020-12-08 12:48:10 -08:00
Nitish Tiwari 78d18d8fc8 Remove alpine based image in favour or RedHat UBI (#11006) 2020-12-08 11:14:06 -08:00
Harshavardhana dc819afa44 fix: auto update crawler meta version
PR 038bcd9079 introduced
version '3', we need to make sure that we do not
print an unexpected error instead log a message to
indicate we will auto update the version.
2020-12-08 10:40:51 -08:00
Harshavardhana 4a564336fe Revert "Add metrics for nodes online and offline (#11050)"
This reverts commit f60bbdf86b.
2020-12-08 09:23:35 -08:00
Anis Elleuch 6b7ced80fe make: Add hotfix target to generate hotfix binaries (#11053)
hotfix target will fetch the release tag prior to the latest commit and create a binary
with the same release tag plus '.hotfix' suffix

e.g.   RELEASE.2020-12-03T05-49-24Z.hotfix
2020-12-08 08:12:13 -08:00
Ritesh H Shukla f60bbdf86b Add metrics for nodes online and offline (#11050) 2020-12-08 01:06:27 -08:00
Harshavardhana 8c79f87f02 add dynamic config docs (#11048)
Co-authored-by: Eco <41090896+eco-minio@users.noreply.github.com>
2020-12-07 19:02:20 -08:00
Poorna Krishnamoorthy f3beb1236a Add cache usage, total capacity to prometheus metrics (#11026) 2020-12-07 16:35:11 -08:00
Poorna Krishnamoorthy 934bed47fa Add transition event notification (#11047)
This is a MinIO specific extension to allow monitoring of transition events.
2020-12-07 13:53:28 -08:00
Ritesh H Shukla 038bcd9079 Add replication capacity metrics support in crawler (#10786) 2020-12-07 13:47:48 -08:00
Justin Page 6d70f6a4ac fix: README with missing word (#11035)
Include "to" for clarity.
2020-12-07 11:11:58 -08:00
Harshavardhana ce93b2681b fix: re-use er.getDisks() properly in certain calls (#11043) 2020-12-07 10:04:07 -08:00
Harshavardhana 8d036ed6d8 fix: allow sub-admin to modify password for other users (#11039)
fixes #11037
2020-12-06 20:36:34 -08:00
Harshavardhana 9c53cc1b83 fix: heal multiple buckets in bulk (#11029)
makes server startup, orders of magnitude
faster with large number of buckets
2020-12-05 13:00:44 -08:00
Harshavardhana 3514e89eb3 support envs as well for new crawler sub-system (#11033) 2020-12-04 21:54:24 -08:00
Nitish Tiwari 6ff12f5f01 Add the dashboard json file (#11028)
This will allow users to contribute to the dashboard as needed.
2020-12-04 16:27:41 -08:00
Harshavardhana ee2a436a5b fix: release locks if the client timedout (#11030)
situations where client indeed timedout there was
a potential to falsely think that lock is still
active.
2020-12-04 11:33:56 -08:00
Klaus Post a896125490 Add crawler delay config + dynamic config values (#11018) 2020-12-04 09:32:35 -08:00
Harshavardhana e083471ec4 use argon2 with sync.Pool for better memory management (#11019) 2020-12-03 19:23:19 -08:00
Nitish Tiwari de9b64834e fix: update grafana dashboard docs (#11023)
Refer to the official Grafana dashboard
2020-12-03 15:56:15 -08:00
Harshavardhana 919441d9c4 update gocredits with new updated dependencies 2020-12-03 11:39:10 -08:00
Harshavardhana 80d31113e5 fix: etcd import paths again depend on v3.4.14 release (#11020)
Due to botched upstream renames of project repositories
and incomplete migration to go.mod support, our current
dependency version of `go.mod` had bugs i.e it was
using commits from master branch which didn't have
the required fixes present in release-3.4 branches

which leads to some rare bugs

https://github.com/etcd-io/etcd/pull/11477 provides
a workaround for now and we should migrate to this.

release-3.5 eventually claims to fix all of this
properly until then we cannot use /v3 import right now
2020-12-03 11:35:18 -08:00
Ritesh H Shukla 7e2b79984e Stream bucket bandwidth measurements (#11014) 2020-12-03 11:34:42 -08:00
Minio Trusted d54cf77356 Update yaml files to latest version RELEASE.2020-12-03T05-49-24Z 2020-12-03 06:05:56 +00:00
Harshavardhana 951b6b203b skip metacache entries healing to speed up startup 2020-12-02 21:30:54 -08:00
Harshavardhana 44e23b7f4f fix: startup being slow - wait only if IOCount > 0 2020-12-02 21:06:17 -08:00
Harshavardhana c22a387695 fix: building docker images 2020-12-02 17:34:42 -08:00
Minio Trusted 1ab4d6a6aa Update yaml files to latest version RELEASE.2020-12-03T00-03-10Z 2020-12-03 00:24:16 +00:00
184 changed files with 7755 additions and 3947 deletions
-2
View File
@@ -26,7 +26,6 @@ jobs:
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
GO111MODULE: on GO111MODULE: on
MINIO_CI_CD: 1
run: | run: |
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
go test -v --timeout 50m ./... go test -v --timeout 50m ./...
@@ -35,7 +34,6 @@ jobs:
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
GO111MODULE: on GO111MODULE: on
MINIO_CI_CD: 1
run: | run: |
sudo sysctl net.ipv6.conf.all.disable_ipv6=0 sudo sysctl net.ipv6.conf.all.disable_ipv6=0
sudo sysctl net.ipv6.conf.default.disable_ipv6=0 sudo sysctl net.ipv6.conf.default.disable_ipv6=0
+1
View File
@@ -27,6 +27,7 @@ issues:
run: run:
skip-dirs: skip-dirs:
- pkg/rpc - pkg/rpc
- pkg/argon2
service: service:
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
+642 -351
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -11,7 +11,7 @@ RUN \
git clone https://github.com/minio/minio && cd minio && \ git clone https://github.com/minio/minio && cd minio && \
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
FROM alpine:3.12 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
ENV MINIO_ACCESS_KEY_FILE=access_key \ ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \ MINIO_SECRET_KEY_FILE=secret_key \
@@ -22,11 +22,14 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
EXPOSE 9000 EXPOSE 9000
COPY --from=builder /go/bin/minio /usr/bin/minio COPY --from=builder /go/bin/minio /usr/bin/minio
COPY --from=builder /go/minio/CREDITS /third_party/ COPY --from=builder /go/minio/CREDITS /licenses/CREDITS
COPY --from=builder /go/minio/LICENSE /licenses/LICENSE
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/ COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
RUN \ RUN \
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \ microdnf update --nodocs && \
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
microdnf clean all && \
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
+16 -12
View File
@@ -7,30 +7,34 @@ ENV CGO_ENABLED 0
ENV GO111MODULE on ENV GO111MODULE on
RUN \ RUN \
apk add --no-cache git && \ apk add --no-cache git && \
git clone https://github.com/minio/minio && cd minio && \ git clone https://github.com/minio/minio && cd minio && \
git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" git checkout master && go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
FROM alpine:3.12 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
ARG TARGETARCH
ENV MINIO_ACCESS_KEY_FILE=access_key \ ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \ MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_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" MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
EXPOSE 9000 EXPOSE 9000
COPY --from=builder /go/bin/minio /usr/bin/minio COPY --from=builder /go/bin/minio /usr/bin/minio
COPY --from=builder /go/minio/CREDITS /third_party/ COPY --from=builder /go/minio/CREDITS /licenses/CREDITS
COPY --from=builder /go/minio/LICENSE /licenses/LICENSE
COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/ COPY --from=builder /go/minio/dockerscripts/docker-entrypoint.sh /usr/bin/
RUN \ RUN \
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \ microdnf update --nodocs && \
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
microdnf clean all
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"] VOLUME ["/data"]
CMD ["minio", "server", "/data"] CMD ["minio", "server", "/data"]
+13 -9
View File
@@ -1,22 +1,26 @@
FROM alpine:3.12 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
ARG TARGETARCH
LABEL maintainer="MinIO Inc <dev@min.io>" LABEL maintainer="MinIO Inc <dev@min.io>"
COPY dockerscripts/docker-entrypoint.sh /usr/bin/ COPY dockerscripts/docker-entrypoint.sh /usr/bin/
COPY minio /usr/bin/ COPY minio /usr/bin/
COPY CREDITS /third_party/ COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE
ENV MINIO_UPDATE off ENV MINIO_UPDATE=off \
ENV MINIO_ACCESS_KEY_FILE=access_key \ MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \ MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_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
RUN \ RUN \
apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' && \ microdnf update --nodocs && \
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
chmod +x /usr/bin/minio && \ microdnf clean all && \
chmod +x /usr/bin/docker-entrypoint.sh chmod +x /usr/bin/minio && \
chmod +x /usr/bin/docker-entrypoint.sh
EXPOSE 9000 EXPOSE 9000
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu FROM ubuntu:20.04
LABEL maintainer="MinIO Inc <dev@min.io>" LABEL maintainer="MinIO Inc <dev@min.io>"
+14 -7
View File
@@ -1,14 +1,20 @@
FROM alpine:3.12 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
ARG TARGETARCH ARG TARGETARCH
LABEL maintainer="MinIO Inc <dev@min.io>" LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="RELEASE.2020-11-25T22-36-25Z" \
release="RELEASE.2020-11-25T22-36-25Z" \
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
ENV MINIO_ACCESS_KEY_FILE=access_key \ ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \ MINIO_SECRET_KEY_FILE=secret_key \
MINIO_KMS_MASTER_KEY_FILE=kms_master_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" MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
@@ -16,16 +22,17 @@ COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE COPY LICENSE /licenses/LICENSE
RUN \ RUN \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \ microdnf update --nodocs && \
apk update && apk add --no-cache ca-certificates 'curl>7.61.0' 'su-exec>=0.2' minisign && \ microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
microdnf install minisign --nodocs && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio -o /usr/bin/minio && \ curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio -o /usr/bin/minio && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.sha256sum -o /usr/bin/minio.sha256sum && \ curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.sha256sum -o /usr/bin/minio.sha256sum && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.minisig -o /usr/bin/minio.minisig && \ curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.minisig -o /usr/bin/minio.minisig && \
microdnf clean all && \
chmod +x /usr/bin/minio && \ chmod +x /usr/bin/minio && \
chmod +x /usr/bin/docker-entrypoint.sh && \ chmod +x /usr/bin/docker-entrypoint.sh && \
chmod +x /usr/bin/verify-minio.sh && \ chmod +x /usr/bin/verify-minio.sh && \
curl -s -q -O https://raw.githubusercontent.com/minio/minio/master/CREDITS && \
/usr/bin/verify-minio.sh /usr/bin/verify-minio.sh
EXPOSE 9000 EXPOSE 9000
-49
View File
@@ -1,49 +0,0 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
ARG TARGETARCH
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="RELEASE.2020-11-25T22-36-25Z" \
release="RELEASE.2020-11-25T22-36-25Z" \
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
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_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
COPY dockerscripts/docker-entrypoint.ubi.sh /usr/bin/docker-entrypoint.ubi.sh
COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE
RUN \
microdnf update --nodocs && \
microdnf install curl ca-certificates shadow-utils --nodocs && \
curl -s -q https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm -o epel-release.rpm && \
rpm -ivh epel-release.rpm && microdnf install minisign --nodocs && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio -o /usr/bin/minio && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.sha256sum -o /usr/bin/minio.sha256sum && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.minisig -o /usr/bin/minio.minisig && \
microdnf clean all && \
chmod +x /usr/bin/minio && \
chmod +x /usr/bin/docker-entrypoint.ubi.sh && \
chmod +x /usr/bin/verify-minio.sh && \
/usr/bin/verify-minio.sh && \
groupadd --gid 1000 minio && \
useradd -M --uid 1000 --gid 1000 --home /usr/share/minio minio && \
mkdir -p /data && chown -R minio:minio /usr/bin /data
EXPOSE 9000
USER minio
ENTRYPOINT ["/usr/bin/docker-entrypoint.ubi.sh"]
VOLUME ["/data"]
CMD ["minio"]
+5 -1
View File
@@ -57,7 +57,7 @@ test-race: verifiers build
# Verify minio binary # Verify minio binary
verify: verify:
@echo "Verifying build with race" @echo "Verifying build with race"
@GO111MODULE=on CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null @GO111MODULE=on CGO_ENABLED=1 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@(env bash $(PWD)/buildscripts/verify-build.sh) @(env bash $(PWD)/buildscripts/verify-build.sh)
# Verify healing of disks with minio binary # Verify healing of disks with minio binary
@@ -71,6 +71,10 @@ build: checks
@echo "Building minio binary to './minio'" @echo "Building minio binary to './minio'"
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null @GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
hotfix: LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#'))
hotfix: install
docker: checks docker: checks
@echo "Building minio docker image '$(TAG)'" @echo "Building minio docker image '$(TAG)'"
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null @GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
+1 -1
View File
@@ -150,7 +150,7 @@ service iptables restart
``` ```
## Test using MinIO Browser ## Test using MinIO Browser
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully. MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 to ensure your server has started successfully.
![Screenshot](https://github.com/minio/minio/blob/master/docs/screenshots/minio-browser.png?raw=true) ![Screenshot](https://github.com/minio/minio/blob/master/docs/screenshots/minio-browser.png?raw=true)
+39
View File
@@ -0,0 +1,39 @@
## Vulnerability Management Policy
This document formally describes the process of addressing and managing a
reported vulnerability that has been found in the MinIO server code base,
any directly connected ecosystem component or a direct / indirect dependency
of the code base.
### Scope
The vulnerability management policy described in this document covers the
process of investigating, assessing and resolving a vulnerability report
opened by a MinIO employee or an external third party.
Therefore, it lists pre-conditions and actions that should be performed to
resolve and fix a reported vulnerability.
### Vulnerability Management Process
The vulnerability management process requires that the vulnerability report
contains the following information:
- The project / component that contains the reported vulnerability.
- A description of the vulnerability. In particular, the type of the
reported vulnerability and how it might be exploited. Alternatively,
a well-established vulnerability identifier, e.g. CVE number, can be
used instead.
Based on the description mentioned above, a MinIO engineer or security team
member investigates:
- Whether the reported vulnerability exists.
- The conditions that are required such that the vulnerability can be exploited.
- The steps required to fix the vulnerability.
In general, if the vulnerability exists in one of the MinIO code bases
itself - not in a code dependency - then MinIO will, if possible, fix
the vulnerability or implement reasonable countermeasures such that the
vulnerability cannot be exploited anymore.
+231 -315
View File
@@ -2271,7 +2271,9 @@
"asap": { "asap": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
"dev": true,
"optional": true
}, },
"asn1": { "asn1": {
"version": "0.2.4", "version": "0.2.4",
@@ -2283,19 +2285,20 @@
} }
}, },
"asn1.js": { "asn1.js": {
"version": "4.10.1", "version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"requires": { "requires": {
"bn.js": "^4.0.0", "bn.js": "^4.0.0",
"inherits": "^2.0.1", "inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0" "minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
} }
} }
}, },
@@ -3541,9 +3544,9 @@
} }
}, },
"base64-js": { "base64-js": {
"version": "1.3.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
}, },
"batch": { "batch": {
"version": "0.6.1", "version": "0.6.1",
@@ -3574,13 +3577,12 @@
"bluebird": { "bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
"dev": true
}, },
"bn.js": { "bn.js": {
"version": "5.1.1", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
"integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==" "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
}, },
"body-parser": { "body-parser": {
"version": "1.19.0", "version": "1.19.0",
@@ -3754,31 +3756,24 @@
} }
}, },
"browserify-rsa": { "browserify-rsa": {
"version": "4.0.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
"requires": { "requires": {
"bn.js": "^4.1.0", "bn.js": "^5.0.0",
"randombytes": "^2.0.1" "randombytes": "^2.0.1"
},
"dependencies": {
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
}
} }
}, },
"browserify-sign": { "browserify-sign": {
"version": "4.2.0", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
"integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
"requires": { "requires": {
"bn.js": "^5.1.1", "bn.js": "^5.1.1",
"browserify-rsa": "^4.0.1", "browserify-rsa": "^4.0.1",
"create-hash": "^1.2.0", "create-hash": "^1.2.0",
"create-hmac": "^1.1.7", "create-hmac": "^1.1.7",
"elliptic": "^6.5.2", "elliptic": "^6.5.3",
"inherits": "^2.0.4", "inherits": "^2.0.4",
"parse-asn1": "^5.1.5", "parse-asn1": "^5.1.5",
"readable-stream": "^3.6.0", "readable-stream": "^3.6.0",
@@ -4022,9 +4017,9 @@
} }
}, },
"chokidar": { "chokidar": {
"version": "3.4.0", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
"integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
"optional": true, "optional": true,
"requires": { "requires": {
"anymatch": "~3.1.1", "anymatch": "~3.1.1",
@@ -4034,7 +4029,7 @@
"is-binary-path": "~2.1.0", "is-binary-path": "~2.1.0",
"is-glob": "~4.0.1", "is-glob": "~4.0.1",
"normalize-path": "~3.0.0", "normalize-path": "~3.0.0",
"readdirp": "~3.4.0" "readdirp": "~3.5.0"
}, },
"dependencies": { "dependencies": {
"anymatch": { "anymatch": {
@@ -4048,9 +4043,9 @@
} }
}, },
"binary-extensions": { "binary-extensions": {
"version": "2.0.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
"optional": true "optional": true
}, },
"braces": { "braces": {
@@ -4123,9 +4118,9 @@
"optional": true "optional": true
}, },
"readdirp": { "readdirp": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"optional": true, "optional": true,
"requires": { "requires": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
@@ -4143,9 +4138,9 @@
} }
}, },
"chownr": { "chownr": {
"version": "1.1.1", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
}, },
"chrome-trace-event": { "chrome-trace-event": {
"version": "1.0.2", "version": "1.0.2",
@@ -4713,18 +4708,18 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
}, },
"create-ecdh": { "create-ecdh": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
"requires": { "requires": {
"bn.js": "^4.1.0", "bn.js": "^4.1.0",
"elliptic": "^6.0.0" "elliptic": "^6.5.3"
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
} }
} }
}, },
@@ -4925,9 +4920,9 @@
} }
}, },
"cyclist": { "cyclist": {
"version": "0.2.2", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
}, },
"dashdash": { "dashdash": {
"version": "1.14.1", "version": "1.14.1",
@@ -5242,9 +5237,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
} }
} }
}, },
@@ -5420,9 +5415,9 @@
} }
}, },
"duplexify": { "duplexify": {
"version": "3.6.1", "version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
"integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
"requires": { "requires": {
"end-of-stream": "^1.0.0", "end-of-stream": "^1.0.0",
"inherits": "^2.0.1", "inherits": "^2.0.1",
@@ -5471,9 +5466,9 @@
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
"version": "6.5.2", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.4.0",
"brorand": "^1.0.1", "brorand": "^1.0.1",
@@ -5485,9 +5480,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
} }
} }
}, },
@@ -5509,14 +5504,6 @@
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"dev": true "dev": true
}, },
"encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"requires": {
"iconv-lite": "~0.4.13"
}
},
"end-of-stream": { "end-of-stream": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
@@ -5526,9 +5513,9 @@
} }
}, },
"enhanced-resolve": { "enhanced-resolve": {
"version": "4.1.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
"integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
"requires": { "requires": {
"graceful-fs": "^4.1.2", "graceful-fs": "^4.1.2",
"memory-fs": "^0.5.0", "memory-fs": "^0.5.0",
@@ -5886,9 +5873,9 @@
}, },
"dependencies": { "dependencies": {
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true "dev": true
}, },
"react-is": { "react-is": {
@@ -6162,11 +6149,18 @@
"dev": true "dev": true
}, },
"esrecurse": { "esrecurse": {
"version": "4.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"requires": { "requires": {
"estraverse": "^4.1.0" "estraverse": "^5.2.0"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ=="
}
} }
}, },
"estraverse": { "estraverse": {
@@ -6193,9 +6187,9 @@
"dev": true "dev": true
}, },
"events": { "events": {
"version": "3.1.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
"integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==" "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg=="
}, },
"eventsource": { "eventsource": {
"version": "1.0.7", "version": "1.0.7",
@@ -6722,27 +6716,6 @@
"bser": "2.1.1" "bser": "2.1.1"
} }
}, },
"fbjs": {
"version": "0.8.16",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
"integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
"requires": {
"core-js": "^1.0.0",
"isomorphic-fetch": "^2.1.1",
"loose-envify": "^1.0.0",
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.9"
},
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
}
}
},
"figgy-pudding": { "figgy-pudding": {
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@@ -7013,12 +6986,12 @@
} }
}, },
"flush-write-stream": { "flush-write-stream": {
"version": "1.0.3", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
"integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
"requires": { "requires": {
"inherits": "^2.0.1", "inherits": "^2.0.3",
"readable-stream": "^2.0.4" "readable-stream": "^2.3.6"
} }
}, },
"follow-redirects": { "follow-redirects": {
@@ -8376,9 +8349,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true "dev": true
}, },
"tapable": { "tapable": {
@@ -8651,14 +8624,6 @@
"resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz", "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
"integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=" "integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ="
}, },
"iconv-lite": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"icss-utils": { "icss-utils": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
@@ -8677,9 +8642,9 @@
} }
}, },
"ieee754": { "ieee754": {
"version": "1.1.13", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
}, },
"iferr": { "iferr": {
"version": "0.1.5", "version": "0.1.5",
@@ -8746,9 +8711,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true "dev": true
}, },
"internal-ip": { "internal-ip": {
@@ -9013,7 +8978,8 @@
"is-stream": { "is-stream": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
}, },
"is-subset": { "is-subset": {
"version": "0.1.1", "version": "0.1.1",
@@ -9068,15 +9034,6 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
}, },
"isomorphic-fetch": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
"requires": {
"node-fetch": "^1.0.1",
"whatwg-fetch": ">=0.10.0"
}
},
"isstream": { "isstream": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -10573,9 +10530,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.14", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true "dev": true
}, },
"lodash._baseisequal": { "lodash._baseisequal": {
@@ -10682,6 +10639,14 @@
"tslib": "^1.10.0" "tslib": "^1.10.0"
} }
}, },
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": {
"yallist": "^3.0.2"
}
},
"make-dir": { "make-dir": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
@@ -10868,9 +10833,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
} }
} }
}, },
@@ -10993,6 +10958,23 @@
} }
} }
}, },
"mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
"requires": {
"concat-stream": "^1.5.0",
"duplexify": "^3.4.2",
"end-of-stream": "^1.1.0",
"flush-write-stream": "^1.0.0",
"from2": "^2.1.0",
"parallel-transform": "^1.1.0",
"pump": "^3.0.0",
"pumpify": "^1.3.3",
"stream-each": "^1.1.0",
"through2": "^2.0.0"
}
},
"mixin-deep": { "mixin-deep": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
@@ -11165,19 +11147,10 @@
"tslib": "^1.10.0" "tslib": "^1.10.0"
} }
}, },
"node-fetch": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz",
"integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=",
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
},
"node-forge": { "node-forge": {
"version": "0.9.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
"dev": true "dev": true
}, },
"node-int64": { "node-int64": {
@@ -11768,11 +11741,11 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
}, },
"parallel-transform": { "parallel-transform": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
"integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
"requires": { "requires": {
"cyclist": "~0.2.2", "cyclist": "^1.0.1",
"inherits": "^2.0.3", "inherits": "^2.0.3",
"readable-stream": "^2.1.5" "readable-stream": "^2.1.5"
} }
@@ -11788,13 +11761,12 @@
} }
}, },
"parse-asn1": { "parse-asn1": {
"version": "5.1.5", "version": "5.1.6",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
"integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
"requires": { "requires": {
"asn1.js": "^4.0.0", "asn1.js": "^5.2.0",
"browserify-aes": "^1.0.0", "browserify-aes": "^1.0.0",
"create-hash": "^1.1.0",
"evp_bytestokey": "^1.0.0", "evp_bytestokey": "^1.0.0",
"pbkdf2": "^3.0.3", "pbkdf2": "^3.0.3",
"safe-buffer": "^5.1.1" "safe-buffer": "^5.1.1"
@@ -11913,9 +11885,9 @@
} }
}, },
"pbkdf2": { "pbkdf2": {
"version": "3.0.17", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
"integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
"requires": { "requires": {
"create-hash": "^1.1.2", "create-hash": "^1.1.2",
"create-hmac": "^1.1.4", "create-hmac": "^1.1.4",
@@ -12218,6 +12190,8 @@
"version": "7.3.1", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"dev": true,
"optional": true,
"requires": { "requires": {
"asap": "~2.0.3" "asap": "~2.0.3"
} }
@@ -12238,13 +12212,23 @@
} }
}, },
"prop-types": { "prop-types": {
"version": "15.6.0", "version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": { "requires": {
"fbjs": "^0.8.16", "loose-envify": "^1.4.0",
"loose-envify": "^1.3.1", "object-assign": "^4.1.1",
"object-assign": "^4.1.1" "react-is": "^16.8.1"
},
"dependencies": {
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
}
} }
}, },
"prop-types-exact": { "prop-types-exact": {
@@ -12330,16 +12314,16 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.8", "version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
} }
} }
}, },
"pump": { "pump": {
"version": "2.0.1", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": { "requires": {
"end-of-stream": "^1.1.0", "end-of-stream": "^1.1.0",
"once": "^1.3.1" "once": "^1.3.1"
@@ -12353,6 +12337,17 @@
"duplexify": "^3.6.0", "duplexify": "^3.6.0",
"inherits": "^2.0.3", "inherits": "^2.0.3",
"pump": "^2.0.0" "pump": "^2.0.0"
},
"dependencies": {
"pump": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
}
} }
}, },
"punycode": { "punycode": {
@@ -13492,9 +13487,9 @@
}, },
"dependencies": { "dependencies": {
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true "dev": true
} }
} }
@@ -13958,12 +13953,12 @@
"dev": true "dev": true
}, },
"selfsigned": { "selfsigned": {
"version": "1.10.7", "version": "1.10.8",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
"dev": true, "dev": true,
"requires": { "requires": {
"node-forge": "0.9.0" "node-forge": "^0.10.0"
} }
}, },
"semver": { "semver": {
@@ -14001,10 +13996,13 @@
} }
}, },
"serialize-javascript": { "serialize-javascript": {
"version": "3.0.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
"integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==", "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
"dev": true "dev": true,
"requires": {
"randombytes": "^2.1.0"
}
}, },
"serializerr": { "serializerr": {
"version": "1.0.3", "version": "1.0.3",
@@ -14644,9 +14642,9 @@
} }
}, },
"stream-shift": { "stream-shift": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
}, },
"strict-uri-encode": { "strict-uri-encode": {
"version": "2.0.0", "version": "2.0.0",
@@ -15356,26 +15354,21 @@
} }
}, },
"terser-webpack-plugin": { "terser-webpack-plugin": {
"version": "1.4.3", "version": "1.4.5",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
"requires": { "requires": {
"cacache": "^12.0.2", "cacache": "^12.0.2",
"find-cache-dir": "^2.1.0", "find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0", "is-wsl": "^1.1.0",
"schema-utils": "^1.0.0", "schema-utils": "^1.0.0",
"serialize-javascript": "^2.1.2", "serialize-javascript": "^4.0.0",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"terser": "^4.1.2", "terser": "^4.1.2",
"webpack-sources": "^1.4.0", "webpack-sources": "^1.4.0",
"worker-farm": "^1.7.0" "worker-farm": "^1.7.0"
}, },
"dependencies": { "dependencies": {
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"cacache": { "cacache": {
"version": "12.0.4", "version": "12.0.4",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
@@ -15443,14 +15436,6 @@
"path-exists": "^3.0.0" "path-exists": "^3.0.0"
} }
}, },
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": {
"yallist": "^3.0.2"
}
},
"make-dir": { "make-dir": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -15460,23 +15445,6 @@
"semver": "^5.6.0" "semver": "^5.6.0"
} }
}, },
"mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
"requires": {
"concat-stream": "^1.5.0",
"duplexify": "^3.4.2",
"end-of-stream": "^1.1.0",
"flush-write-stream": "^1.0.0",
"from2": "^2.1.0",
"parallel-transform": "^1.1.0",
"pump": "^3.0.0",
"pumpify": "^1.3.3",
"stream-each": "^1.1.0",
"through2": "^2.0.0"
}
},
"p-limit": { "p-limit": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -15511,24 +15479,13 @@
"find-up": "^3.0.0" "find-up": "^3.0.0"
} }
}, },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"serialize-javascript": { "serialize-javascript": {
"version": "2.1.2", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
}, "requires": {
"source-list-map": { "randombytes": "^2.1.0"
"version": "2.0.1", }
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
@@ -15542,15 +15499,6 @@
"requires": { "requires": {
"figgy-pudding": "^3.5.1" "figgy-pudding": "^3.5.1"
} }
},
"webpack-sources": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
"requires": {
"source-list-map": "^2.0.0",
"source-map": "~0.6.1"
}
} }
} }
}, },
@@ -15589,9 +15537,9 @@
"dev": true "dev": true
}, },
"timers-browserify": { "timers-browserify": {
"version": "2.0.11", "version": "2.0.12",
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
"integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
"requires": { "requires": {
"setimmediate": "^1.0.4" "setimmediate": "^1.0.4"
} }
@@ -15790,11 +15738,6 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
}, },
"ua-parser-js": {
"version": "0.7.20",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz",
"integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw=="
},
"uglify-js": { "uglify-js": {
"version": "3.9.3", "version": "3.9.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz",
@@ -16172,20 +16115,20 @@
} }
}, },
"watchpack": { "watchpack": {
"version": "1.7.2", "version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
"integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
"requires": { "requires": {
"chokidar": "^3.4.0", "chokidar": "^3.4.1",
"graceful-fs": "^4.1.2", "graceful-fs": "^4.1.2",
"neo-async": "^2.5.0", "neo-async": "^2.5.0",
"watchpack-chokidar2": "^2.0.0" "watchpack-chokidar2": "^2.0.1"
} }
}, },
"watchpack-chokidar2": { "watchpack-chokidar2": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz",
"integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==",
"optional": true, "optional": true,
"requires": { "requires": {
"chokidar": "^2.1.8" "chokidar": "^2.1.8"
@@ -16447,9 +16390,9 @@
"dev": true "dev": true
}, },
"webpack": { "webpack": {
"version": "4.43.0", "version": "4.44.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz",
"integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==",
"requires": { "requires": {
"@webassemblyjs/ast": "1.9.0", "@webassemblyjs/ast": "1.9.0",
"@webassemblyjs/helper-module-context": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0",
@@ -16459,7 +16402,7 @@
"ajv": "^6.10.2", "ajv": "^6.10.2",
"ajv-keywords": "^3.4.1", "ajv-keywords": "^3.4.1",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^4.1.0", "enhanced-resolve": "^4.3.0",
"eslint-scope": "^4.0.3", "eslint-scope": "^4.0.3",
"json-parse-better-errors": "^1.0.2", "json-parse-better-errors": "^1.0.2",
"loader-runner": "^2.4.0", "loader-runner": "^2.4.0",
@@ -16472,14 +16415,14 @@
"schema-utils": "^1.0.0", "schema-utils": "^1.0.0",
"tapable": "^1.1.3", "tapable": "^1.1.3",
"terser-webpack-plugin": "^1.4.3", "terser-webpack-plugin": "^1.4.3",
"watchpack": "^1.6.1", "watchpack": "^1.7.4",
"webpack-sources": "^1.4.1" "webpack-sources": "^1.4.1"
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
"version": "6.12.2", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@@ -16578,9 +16521,9 @@
} }
}, },
"fast-deep-equal": { "fast-deep-equal": {
"version": "3.1.1", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
}, },
"is-accessor-descriptor": { "is-accessor-descriptor": {
"version": "0.1.6", "version": "0.1.6",
@@ -16661,29 +16604,10 @@
"to-regex": "^3.0.2" "to-regex": "^3.0.2"
} }
}, },
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"tapable": { "tapable": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
},
"webpack-sources": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
"requires": {
"source-list-map": "^2.0.0",
"source-map": "~0.6.1"
}
} }
} }
}, },
@@ -17605,7 +17529,6 @@
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
"dev": true,
"requires": { "requires": {
"source-list-map": "^2.0.0", "source-list-map": "^2.0.0",
"source-map": "~0.6.1" "source-map": "~0.6.1"
@@ -17614,14 +17537,12 @@
"source-list-map": { "source-list-map": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
"dev": true
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
"dev": true
} }
} }
}, },
@@ -17660,11 +17581,6 @@
} }
} }
}, },
"whatwg-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
},
"whatwg-mimetype": { "whatwg-mimetype": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
@@ -17780,9 +17696,9 @@
"dev": true "dev": true
}, },
"xtend": { "xtend": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}, },
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
+32 -32
View File
File diff suppressed because one or more lines are too long
+20 -2
View File
@@ -44,10 +44,21 @@ func releaseTag(version string) string {
relPrefix = prefix relPrefix = prefix
} }
relSuffix := ""
if hotfix := os.Getenv("MINIO_HOTFIX"); hotfix != "" {
relSuffix = hotfix
}
relTag := strings.Replace(version, " ", "-", -1) relTag := strings.Replace(version, " ", "-", -1)
relTag = strings.Replace(relTag, ":", "-", -1) relTag = strings.Replace(relTag, ":", "-", -1)
relTag = strings.Replace(relTag, ",", "", -1) relTag = strings.Replace(relTag, ",", "", -1)
return relPrefix + "." + relTag relTag = relPrefix + "." + relTag
if relSuffix != "" {
relTag += "." + relSuffix
}
return relTag
} }
// commitID returns the abbreviated commit-id hash of the last commit. // commitID returns the abbreviated commit-id hash of the last commit.
@@ -68,5 +79,12 @@ func commitID() string {
} }
func main() { func main() {
fmt.Println(genLDFlags(time.Now().UTC().Format(time.RFC3339))) var version string
if len(os.Args) > 1 {
version = os.Args[1]
} else {
version = time.Now().UTC().Format(time.RFC3339)
}
fmt.Println(genLDFlags(version))
} }
+27 -25
View File
@@ -56,28 +56,29 @@ function start_minio_erasure()
function start_minio_erasure_sets() function start_minio_erasure_sets()
{ {
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk-sets{1...32}" >"$WORK_DIR/erasure-minio-sets.log" 2>&1 & export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
"${MINIO[@]}" server > "$WORK_DIR/erasure-minio-sets.log" 2>&1 &
sleep 15 sleep 15
} }
function start_minio_zone_erasure_sets() function start_minio_pool_erasure_sets()
{ {
export MINIO_ACCESS_KEY=$ACCESS_KEY export MINIO_ACCESS_KEY=$ACCESS_KEY
export MINIO_SECRET_KEY=$SECRET_KEY export MINIO_SECRET_KEY=$SECRET_KEY
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9000.log" 2>&1 & "${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/zone-disk-sets{1...4}" "http://127.0.0.1:9001${WORK_DIR}/zone-disk-sets{5...8}" >"$WORK_DIR/zone-minio-9001.log" 2>&1 & "${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
sleep 40 sleep 40
} }
function start_minio_zone_erasure_sets_ipv6() function start_minio_pool_erasure_sets_ipv6()
{ {
export MINIO_ACCESS_KEY=$ACCESS_KEY export MINIO_ACCESS_KEY=$ACCESS_KEY
export MINIO_SECRET_KEY=$SECRET_KEY export MINIO_SECRET_KEY=$SECRET_KEY
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets{5...8}"
"${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]:9000" > "$WORK_DIR/pool-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 & "${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
sleep 40 sleep 40
} }
@@ -86,10 +87,10 @@ function start_minio_dist_erasure()
{ {
export MINIO_ACCESS_KEY=$ACCESS_KEY export MINIO_ACCESS_KEY=$ACCESS_KEY
export MINIO_SECRET_KEY=$SECRET_KEY export MINIO_SECRET_KEY=$SECRET_KEY
"${MINIO[@]}" server --address=:9000 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9000.log" 2>&1 & export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
"${MINIO[@]}" server --address=:9001 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9001.log" 2>&1 & for i in $(seq 0 3); do
"${MINIO[@]}" server --address=:9002 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9002.log" 2>&1 & "${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
"${MINIO[@]}" server --address=:9003 "http://127.0.0.1:9000${WORK_DIR}/dist-disk1" "http://127.0.0.1:9001${WORK_DIR}/dist-disk2" "http://127.0.0.1:9002${WORK_DIR}/dist-disk3" "http://127.0.0.1:9003${WORK_DIR}/dist-disk4" >"$WORK_DIR/dist-minio-9003.log" 2>&1 & done
sleep 40 sleep 40
} }
@@ -112,7 +113,8 @@ function run_test_fs()
return "$rv" return "$rv"
} }
function run_test_erasure_sets() { function run_test_erasure_sets()
{
start_minio_erasure_sets start_minio_erasure_sets
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS") (cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
@@ -129,9 +131,9 @@ function run_test_erasure_sets() {
return "$rv" return "$rv"
} }
function run_test_zone_erasure_sets() function run_test_pool_erasure_sets()
{ {
start_minio_zone_erasure_sets start_minio_pool_erasure_sets
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS") (cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
rv=$? rv=$?
@@ -142,20 +144,20 @@ function run_test_zone_erasure_sets()
if [ "$rv" -ne 0 ]; then if [ "$rv" -ne 0 ]; then
for i in $(seq 0 1); do for i in $(seq 0 1); do
echo "server$i log:" echo "server$i log:"
cat "$WORK_DIR/zone-minio-900$i.log" cat "$WORK_DIR/pool-minio-900$i.log"
done done
fi fi
for i in $(seq 0 1); do for i in $(seq 0 1); do
rm -f "$WORK_DIR/zone-minio-900$i.log" rm -f "$WORK_DIR/pool-minio-900$i.log"
done done
return "$rv" return "$rv"
} }
function run_test_zone_erasure_sets_ipv6() function run_test_pool_erasure_sets_ipv6()
{ {
start_minio_zone_erasure_sets_ipv6 start_minio_pool_erasure_sets_ipv6
export SERVER_ENDPOINT="[::1]:9000" export SERVER_ENDPOINT="[::1]:9000"
@@ -168,12 +170,12 @@ function run_test_zone_erasure_sets_ipv6()
if [ "$rv" -ne 0 ]; then if [ "$rv" -ne 0 ]; then
for i in $(seq 0 1); do for i in $(seq 0 1); do
echo "server$i log:" echo "server$i log:"
cat "$WORK_DIR/zone-minio-ipv6-900$i.log" cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
done done
fi fi
for i in $(seq 0 1); do for i in $(seq 0 1); do
rm -f "$WORK_DIR/zone-minio-ipv6-900$i.log" rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
done done
return "$rv" return "$rv"
@@ -220,7 +222,7 @@ function run_test_dist_erasure()
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log" rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
return "$rv" return "$rv"
} }
function purge() function purge()
@@ -293,14 +295,14 @@ function main()
fi fi
echo "Testing in Distributed Eraure expanded setup" echo "Testing in Distributed Eraure expanded setup"
if ! run_test_zone_erasure_sets; then if ! run_test_pool_erasure_sets; then
echo "FAILED" echo "FAILED"
purge "$WORK_DIR" purge "$WORK_DIR"
exit 1 exit 1
fi fi
echo "Testing in Distributed Erasure expanded setup with ipv6" echo "Testing in Distributed Erasure expanded setup with ipv6"
if ! run_test_zone_erasure_sets_ipv6; then if ! run_test_pool_erasure_sets_ipv6; then
echo "FAILED" echo "FAILED"
purge "$WORK_DIR" purge "$WORK_DIR"
exit 1 exit 1
+33 -25
View File
@@ -29,44 +29,52 @@ MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server ) MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
function start_minio_3_node() { function start_minio_3_node() {
declare -a minio_pids
declare -a ARGS
export MINIO_ACCESS_KEY=minio export MINIO_ACCESS_KEY=minio
export MINIO_SECRET_KEY=minio123 export MINIO_SECRET_KEY=minio123
export MINIO_ERASURE_SET_DRIVE_COUNT=6 export MINIO_ERASURE_SET_DRIVE_COUNT=6
start_port=$(shuf -i 10000-65000 -n 1) start_port=$(shuf -i 10000-65000 -n 1)
args=""
for i in $(seq 1 3); do for i in $(seq 1 3); do
ARGS+=("http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/") args="$args http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/"
done done
"${MINIO[@]}" --address ":$[$start_port+1]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server1.log" 2>&1 & "${MINIO[@]}" --address ":$[$start_port+1]" $args > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
minio_pids[0]=$! disown $!
disown "${minio_pids[0]}"
"${MINIO[@]}" --address ":$[$start_port+2]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server2.log" 2>&1 & "${MINIO[@]}" --address ":$[$start_port+2]" $args > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
minio_pids[1]=$! disown $!
disown "${minio_pids[1]}"
"${MINIO[@]}" --address ":$[$start_port+3]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server3.log" 2>&1 & "${MINIO[@]}" --address ":$[$start_port+3]" $args > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
minio_pids[2]=$! disown $!
disown "${minio_pids[2]}"
sleep "$1" sleep "$1"
for pid in "${minio_pids[@]}"; do if [ "$(pgrep -c minio)" -ne 3 ]; then
if ! kill "$pid"; then for i in $(seq 1 3); do
for i in $(seq 1 3); do echo "server$i log:"
echo "server$i log:" cat "${WORK_DIR}/dist-minio-server$i.log"
cat "${WORK_DIR}/dist-minio-server$i.log" done
done echo "FAILED"
echo "FAILED" purge "$WORK_DIR"
purge "$WORK_DIR" exit 1
exit 1 fi
fi if ! pkill minio; then
for i in $(seq 1 3); do
echo "server$i log:"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi
sleep 1;
if pgrep minio; then
# forcibly killing, to proceed further properly. # forcibly killing, to proceed further properly.
kill -9 "$pid" if ! pkill -9 minio; then
sleep 1 # wait 1sec per pid echo "no minio process running anymore, proceed."
done fi
fi
} }
-1
View File
@@ -194,7 +194,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
return return
} }
if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil { if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
+16 -5
View File
@@ -49,7 +49,7 @@ func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *htt
} }
// Validate request signature. // Validate request signature.
cred, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.ConfigUpdateAdminAction, "") cred, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ConfigUpdateAdminAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return cred, nil return cred, nil
@@ -130,8 +130,8 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@@ -158,6 +158,17 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete) saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
} }
// Apply dynamic values.
if err := applyDynamicConfig(GlobalContext, cfg); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
globalNotificationSys.SignalService(serviceReloadDynamic)
// If all values were dynamic, tell the client.
if dynamic {
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
}
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }
@@ -266,7 +277,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
return return
} }
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil { if _, err = cfg.ReadConfig(bytes.NewReader(kvBytes)); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
@@ -378,7 +389,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
} }
cfg := newServerConfig() cfg := newServerConfig()
if _, err = cfg.ReadFrom(bytes.NewReader(kvBytes)); err != nil { if _, err = cfg.ReadConfig(bytes.NewReader(kvBytes)); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
+16 -9
View File
@@ -22,6 +22,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"path"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@@ -42,7 +43,7 @@ func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.R
} }
// Validate request signature. // Validate request signature.
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "") cred, adminAPIErr = checkAdminRequestAuth(ctx, r, action, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return nil, cred return nil, cred
@@ -358,7 +359,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "AddUser", mustGetClaimsFromToken(r))
vars := mux.Vars(r) vars := mux.Vars(r)
accessKey := vars["accessKey"] accessKey := path.Clean(vars["accessKey"])
// Get current object layer instance. // Get current object layer instance.
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
@@ -373,23 +374,29 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
return return
} }
if cred.IsTemp() || cred.IsServiceAccount() {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccountNotEligible), r.URL)
return
}
// Not allowed to add a user with same access key as root credential // Not allowed to add a user with same access key as root credential
if owner && accessKey == cred.AccessKey { if owner && accessKey == cred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
return return
} }
if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
// Incoming access key matches parent user then we should
// reject password change requests.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
return
}
implicitPerm := accessKey == cred.AccessKey implicitPerm := accessKey == cred.AccessKey
if !implicitPerm { if !implicitPerm {
parentUser := cred.ParentUser
if parentUser == "" {
parentUser = cred.AccessKey
}
if !globalIAMSys.IsAllowed(iampolicy.Args{ if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: accessKey, AccountName: parentUser,
Action: iampolicy.CreateUserAdminAction, Action: iampolicy.CreateUserAdminAction,
ConditionValues: getConditionValues(r, "", accessKey, claims), ConditionValues: getConditionValues(r, "", parentUser, claims),
IsOwner: owner, IsOwner: owner,
Claims: claims, Claims: claims,
}) { }) {
+114 -77
View File
@@ -41,6 +41,7 @@ import (
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/logger/message/log" "github.com/minio/minio/cmd/logger/message/log"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/bandwidth"
"github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/handlers"
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
@@ -204,9 +205,10 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
} }
var objectAPI ObjectLayer var objectAPI ObjectLayer
if serviceSig == serviceRestart { switch serviceSig {
case serviceRestart:
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction) objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceRestartAdminAction)
} else { case serviceStop:
objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction) objectAPI, _ = validateAdminReq(ctx, w, r, iampolicy.ServiceStopAdminAction)
} }
if objectAPI == nil { if objectAPI == nil {
@@ -371,14 +373,12 @@ func topLockEntries(peerLocks []*PeerLocks, stale bool) madmin.LockEntries {
if peerLock == nil { if peerLock == nil {
continue continue
} }
for _, locks := range peerLock.Locks { for k, v := range peerLock.Locks {
for k, v := range locks { for _, lockReqInfo := range v {
for _, lockReqInfo := range v { if val, ok := entryMap[lockReqInfo.UID]; ok {
if val, ok := entryMap[lockReqInfo.UID]; ok { val.ServerList = append(val.ServerList, peerLock.Addr)
val.ServerList = append(val.ServerList, peerLock.Addr) } else {
} else { entryMap[lockReqInfo.UID] = lriToLockEntry(lockReqInfo, k, peerLock.Addr)
entryMap[lockReqInfo.UID] = lriToLockEntry(lockReqInfo, k, peerLock.Addr)
}
} }
} }
} }
@@ -400,7 +400,7 @@ func topLockEntries(peerLocks []*PeerLocks, stale bool) madmin.LockEntries {
// PeerLocks holds server information result of one node // PeerLocks holds server information result of one node
type PeerLocks struct { type PeerLocks struct {
Addr string Addr string
Locks GetLocksResp Locks map[string][]lockRequesterInfo
} }
// TopLocksHandler Get list of locks in use // TopLocksHandler Get list of locks in use
@@ -461,8 +461,15 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R
defer logger.AuditLog(w, r, "StartProfiling", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "StartProfiling", mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ProfilingAdminAction) // Validate request signature.
if objectAPI == nil { _, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return
}
if globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return return
} }
@@ -559,8 +566,15 @@ func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *htt
defer logger.AuditLog(w, r, "DownloadProfiling", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "DownloadProfiling", mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ProfilingAdminAction) // Validate request signature.
if objectAPI == nil { _, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return
}
if globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return return
} }
@@ -900,7 +914,7 @@ func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Reques
} }
// Validate request signature. // Validate request signature.
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "") cred, adminAPIErr = checkAdminRequestAuth(ctx, r, action, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return nil, cred return nil, cred
@@ -1032,7 +1046,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
trcErr := r.URL.Query().Get("err") == "true" trcErr := r.URL.Query().Get("err") == "true"
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.TraceAdminAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.TraceAdminAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
@@ -1428,23 +1442,51 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "BandwidthMonitor") ctx := newContext(r, w, "BandwidthMonitor")
defer logger.AuditLog(w, r, "BandwidthMonitor", mustGetClaimsFromToken(r))
// Validate request signature. // Validate request signature.
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.BandwidthMonitorAction, "") _, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.BandwidthMonitorAction, "")
if adminAPIErr != ErrNone { if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
} }
setEventStreamHeaders(w) setEventStreamHeaders(w)
reportCh := make(chan bandwidth.Report, 1)
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
defer keepAliveTicker.Stop()
bucketsRequestedString := r.URL.Query().Get("buckets") bucketsRequestedString := r.URL.Query().Get("buckets")
bucketsRequested := strings.Split(bucketsRequestedString, ",") bucketsRequested := strings.Split(bucketsRequestedString, ",")
consolidatedReport := globalNotificationSys.GetBandwidthReports(ctx, bucketsRequested...) go func() {
enc := json.NewEncoder(w) for {
err := enc.Encode(consolidatedReport) reportCh <- globalNotificationSys.GetBandwidthReports(ctx, bucketsRequested...)
if err != nil { select {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), r.URL) case <-ctx.Done():
return
default:
time.Sleep(2 * time.Second)
}
}
}()
for {
select {
case report := <-reportCh:
enc := json.NewEncoder(w)
err := enc.Encode(report)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), r.URL)
return
}
w.(http.Flusher).Flush()
case <-keepAliveTicker.C:
if _, err := w.Write([]byte(" ")); err != nil {
return
}
w.(http.Flusher).Flush()
case <-ctx.Done():
return
}
} }
w.(http.Flusher).Flush()
} }
// ServerInfoHandler - GET /minio/admin/v3/info // ServerInfoHandler - GET /minio/admin/v3/info
@@ -1455,22 +1497,13 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
defer logger.AuditLog(w, r, "ServerInfo", mustGetClaimsFromToken(r)) defer logger.AuditLog(w, r, "ServerInfo", mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction) // Validate request signature.
if objectAPI == nil { _, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ServerInfoAdminAction, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return return
} }
buckets := madmin.Buckets{}
objects := madmin.Objects{}
usage := madmin.Usage{}
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
if err == nil {
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
}
vault := fetchVaultStatus() vault := fetchVaultStatus()
ldap := madmin.LDAP{} ldap := madmin.LDAP{}
@@ -1492,31 +1525,54 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
// Get the notification target info // Get the notification target info
notifyTarget := fetchLambdaInfo() notifyTarget := fetchLambdaInfo()
// Fetching the Storage information, ignore any errors.
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
var backend interface{}
if storageInfo.Backend.Type == BackendType(madmin.Erasure) {
backend = madmin.ErasureBackend{
Type: madmin.ErasureType,
OnlineDisks: storageInfo.Backend.OnlineDisks.Sum(),
OfflineDisks: storageInfo.Backend.OfflineDisks.Sum(),
StandardSCData: storageInfo.Backend.StandardSCData,
StandardSCParity: storageInfo.Backend.StandardSCParity,
RRSCData: storageInfo.Backend.RRSCData,
RRSCParity: storageInfo.Backend.RRSCParity,
}
} else {
backend = madmin.FSBackend{
Type: madmin.FsType,
}
}
mode := "online"
server := getLocalServerProperty(globalEndpoints, r) server := getLocalServerProperty(globalEndpoints, r)
servers := globalNotificationSys.ServerInfo() servers := globalNotificationSys.ServerInfo()
servers = append(servers, server) servers = append(servers, server)
var backend interface{}
mode := madmin.ObjectLayerInitializing
buckets := madmin.Buckets{}
objects := madmin.Objects{}
usage := madmin.Usage{}
objectAPI := newObjectLayerFn()
if objectAPI != nil {
mode = madmin.ObjectLayerOnline
// Load data usage
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
if err == nil {
buckets = madmin.Buckets{Count: dataUsageInfo.BucketsCount}
objects = madmin.Objects{Count: dataUsageInfo.ObjectsTotalCount}
usage = madmin.Usage{Size: dataUsageInfo.ObjectsTotalSize}
}
// Fetching the backend information
backendInfo := objectAPI.BackendInfo()
if backendInfo.Type == BackendType(madmin.Erasure) {
// Calculate the number of online/offline disks of all nodes
var allDisks []madmin.Disk
for _, s := range servers {
allDisks = append(allDisks, s.Disks...)
}
onlineDisks, offlineDisks := getOnlineOfflineDisksStats(allDisks)
backend = madmin.ErasureBackend{
Type: madmin.ErasureType,
OnlineDisks: onlineDisks.Sum(),
OfflineDisks: offlineDisks.Sum(),
StandardSCData: backendInfo.StandardSCData,
StandardSCParity: backendInfo.StandardSCParity,
RRSCData: backendInfo.RRSCData,
RRSCParity: backendInfo.RRSCParity,
}
} else {
backend = madmin.FSBackend{
Type: madmin.FsType,
}
}
}
domain := globalDomainNames domain := globalDomainNames
services := madmin.Services{ services := madmin.Services{
Vault: vault, Vault: vault,
@@ -1526,25 +1582,6 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
Notifications: notifyTarget, Notifications: notifyTarget,
} }
// 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) {
servers[i].Disks = append(servers[i].Disks, disk)
}
}
}
// add all the disks local to this server.
for _, disk := range storageInfo.Disks {
if disk.DrivePath == "" && disk.Endpoint == "" {
continue
}
if disk.Endpoint == disk.DrivePath {
servers[len(servers)-1].Disks = append(servers[len(servers)-1].Disks, disk)
}
}
infoMsg := madmin.InfoMessage{ infoMsg := madmin.InfoMessage{
Mode: mode, Mode: mode,
Domain: domain, Domain: domain,
+39 -17
View File
@@ -21,6 +21,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"sort"
"strings"
"sync" "sync"
"time" "time"
@@ -93,15 +95,15 @@ type allHealState struct {
} }
// newHealState - initialize global heal state management // newHealState - initialize global heal state management
func newHealState() *allHealState { func newHealState(cleanup bool) *allHealState {
healState := &allHealState{ hstate := &allHealState{
healSeqMap: make(map[string]*healSequence), healSeqMap: make(map[string]*healSequence),
healLocalDisks: map[Endpoint]struct{}{}, healLocalDisks: map[Endpoint]struct{}{},
} }
if cleanup {
go healState.periodicHealSeqsClean(GlobalContext) go hstate.periodicHealSeqsClean(GlobalContext)
}
return healState return hstate
} }
func (ahs *allHealState) healDriveCount() int { func (ahs *allHealState) healDriveCount() int {
@@ -143,9 +145,13 @@ func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) { func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
// Launch clean-up routine to remove this heal sequence (after // Launch clean-up routine to remove this heal sequence (after
// it ends) from the global state after timeout has elapsed. // it ends) from the global state after timeout has elapsed.
periodicTimer := time.NewTimer(time.Minute * 5)
defer periodicTimer.Stop()
for { for {
select { select {
case <-time.After(time.Minute * 5): case <-periodicTimer.C:
periodicTimer.Reset(time.Minute * 5)
now := UTCNow() now := UTCNow()
ahs.Lock() ahs.Lock()
for path, h := range ahs.healSeqMap { for path, h := range ahs.healSeqMap {
@@ -657,7 +663,9 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
} }
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error { func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
globalHealConfigMu.Lock()
opts := globalHealConfig opts := globalHealConfig
globalHealConfigMu.Unlock()
// Send heal request // Send heal request
task := healTask{ task := healTask{
@@ -669,10 +677,9 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
} }
if source.opts != nil { if source.opts != nil {
task.opts = *source.opts task.opts = *source.opts
} else { }
if opts.Bitrot { if opts.Bitrot {
task.opts.ScanMode = madmin.HealDeepScan task.opts.ScanMode = madmin.HealDeepScan
}
} }
// Wait and proceed if there are active requests // Wait and proceed if there are active requests
@@ -776,13 +783,18 @@ func (h *healSequence) healFromSourceCh() {
} }
func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error { func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
// Start healing the config prefix. // Try to pro-actively heal backend-encrypted file.
if err := h.healMinioSysMeta(objAPI, minioConfigPrefix)(); err != nil { if err := h.queueHealTask(healSource{
return err bucket: minioMetaBucket,
object: backendEncryptedFile,
}, madmin.HealItemBucketMetadata); err != nil {
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
return err
}
} }
// Start healing the bucket config prefix. // Start healing the config prefix.
return h.healMinioSysMeta(objAPI, bucketConfigPrefix)() return h.healMinioSysMeta(objAPI, minioConfigPrefix)()
} }
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error { func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
@@ -819,6 +831,11 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
return errHealStopSignalled return errHealStopSignalled
} }
// Skip metacache entries healing
if strings.HasPrefix(object, "buckets/.minio.sys/.metacache/") {
return nil
}
err := h.queueHealTask(healSource{ err := h.queueHealTask(healSource{
bucket: bucket, bucket: bucket,
object: object, object: object,
@@ -855,11 +872,16 @@ func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
return h.healBucket(objAPI, h.bucket, bucketsOnly) return h.healBucket(objAPI, h.bucket, bucketsOnly)
} }
buckets, err := objAPI.ListBucketsHeal(h.ctx) buckets, err := objAPI.ListBuckets(h.ctx)
if err != nil { if err != nil {
return errFnHealFromAPIErr(h.ctx, err) return errFnHealFromAPIErr(h.ctx, err)
} }
// Heal latest buckets first.
sort.Slice(buckets, func(i, j int) bool {
return buckets[i].Created.After(buckets[j].Created)
})
for _, bucket := range buckets { for _, bucket := range buckets {
if err = h.healBucket(objAPI, bucket.Name, bucketsOnly); err != nil { if err = h.healBucket(objAPI, bucket.Name, bucketsOnly); err != nil {
return err return err
+8
View File
@@ -25,6 +25,7 @@ import (
// getLocalServerProperty - returns madmin.ServerProperties for only the // getLocalServerProperty - returns madmin.ServerProperties for only the
// local endpoints from given list of endpoints // local endpoints from given list of endpoints
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties { func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
var localEndpoints Endpoints
addr := r.Host addr := r.Host
if globalIsDistErasure { if globalIsDistErasure {
addr = GetLocalPeer(endpointServerPools) addr = GetLocalPeer(endpointServerPools)
@@ -39,6 +40,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
if endpoint.IsLocal { if endpoint.IsLocal {
// Only proceed for local endpoints // Only proceed for local endpoints
network[nodeName] = "online" network[nodeName] = "online"
localEndpoints = append(localEndpoints, endpoint)
continue continue
} }
_, present := network[nodeName] _, present := network[nodeName]
@@ -52,6 +54,11 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
} }
} }
localDisks, _ := initStorageDisksWithErrors(localEndpoints)
defer closeStorageDisks(localDisks)
storageInfo, _ := getStorageInfo(localDisks, localEndpoints.GetAllStrings())
return madmin.ServerProperties{ return madmin.ServerProperties{
State: "ok", State: "ok",
Endpoint: addr, Endpoint: addr,
@@ -59,5 +66,6 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
Version: Version, Version: Version,
CommitID: CommitID, CommitID: CommitID,
Network: network, Network: network,
Disks: storageInfo.Disks,
} }
} }
+7 -13
View File
@@ -157,19 +157,13 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
} }
if opts.PartNumber > 0 { if opts.PartNumber > 0 {
var start, end int64 rs = partNumberToRangeSpec(objInfo, opts.PartNumber)
for i := 0; i < len(objInfo.Parts) && i < opts.PartNumber; i++ { }
start = end
end = start + objInfo.Parts[i].ActualSize - 1 // For providing ranged content
} start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
rs = &HTTPRangeSpec{Start: start, End: end} if err != nil {
rangeLen = end - start + 1 return err
} else {
// for providing ranged content
start, rangeLen, err = rs.GetOffsetLength(totalObjectSize)
if err != nil {
return err
}
} }
// Set content length. // Set content length.
+1 -1
View File
@@ -388,7 +388,7 @@ func getObjectLocation(r *http.Request, domains []string, bucket, object string)
} }
proto := handlers.GetSourceScheme(r) proto := handlers.GetSourceScheme(r)
if proto == "" { if proto == "" {
proto = getURLScheme(globalIsSSL) proto = getURLScheme(globalIsTLS)
} }
u := &url.URL{ u := &url.URL{
Host: r.Host, Host: r.Host,
+4 -3
View File
@@ -151,9 +151,10 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
return cred, claims, owner, ErrNone return cred, claims, owner, ErrNone
} }
// checkAdminRequestAuthType checks whether the request is a valid signature V2 or V4 request. // checkAdminRequestAuth checks for authentication and authorization for the incoming
// It does not accept presigned or JWT or anonymous requests. // request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) { // are automatically rejected.
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region) cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
if s3Err != ErrNone { if s3Err != ErrNone {
return cred, s3Err return cred, s3Err
+1 -1
View File
@@ -421,7 +421,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
} }
ctx := context.Background() ctx := context.Background()
for i, testCase := range testCases { for i, testCase := range testCases {
if _, s3Error := checkAdminRequestAuthType(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode { if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode {
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error) t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
} }
} }
+10 -5
View File
@@ -54,20 +54,25 @@ func (h *healRoutine) queueHealTask(task healTask) {
h.tasks <- task h.tasks <- task
} }
func waitForLowHTTPReq(tolerance int, maxWait time.Duration) { func waitForLowHTTPReq(maxIO int, maxWait time.Duration) {
// No need to wait run at full speed.
if maxIO <= 0 {
return
}
// At max 10 attempts to wait with 100 millisecond interval before proceeding // At max 10 attempts to wait with 100 millisecond interval before proceeding
waitCount := 10 waitCount := 10
waitTick := 100 * time.Millisecond waitTick := 100 * time.Millisecond
// Bucket notification and http trace are not costly, it is okay to ignore them // Bucket notification and http trace are not costly, it is okay to ignore them
// while counting the number of concurrent connections // while counting the number of concurrent connections
toleranceFn := func() int { maxIOFn := func() int {
return tolerance + globalHTTPListen.NumSubscribers() + globalHTTPTrace.NumSubscribers() return maxIO + globalHTTPListen.NumSubscribers() + globalHTTPTrace.NumSubscribers()
} }
if httpServer := newHTTPServerFn(); httpServer != nil { if httpServer := newHTTPServerFn(); httpServer != nil {
// Any requests in progress, delay the heal. // Any requests in progress, delay the heal.
for httpServer.GetRequestCount() >= toleranceFn() { for httpServer.GetRequestCount() >= maxIOFn() {
time.Sleep(waitTick) time.Sleep(waitTick)
waitCount-- waitCount--
if waitCount == 0 { if waitCount == 0 {
@@ -97,7 +102,7 @@ func (h *healRoutine) run(ctx context.Context, objAPI ObjectLayer) {
case task.bucket == SlashSeparator: case task.bucket == SlashSeparator:
res, err = healDiskFormat(ctx, objAPI, task.opts) res, err = healDiskFormat(ctx, objAPI, task.opts)
case task.bucket != "" && task.object == "": case task.bucket != "" && task.object == "":
res, err = objAPI.HealBucket(ctx, task.bucket, task.opts.DryRun, task.opts.Remove) res, err = objAPI.HealBucket(ctx, task.bucket, task.opts)
case task.bucket != "" && task.object != "": case task.bucket != "" && task.object != "":
res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts) res, err = objAPI.HealObject(ctx, task.bucket, task.object, task.versionID, task.opts)
} }
+20 -12
View File
@@ -20,10 +20,12 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"sort"
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/console"
) )
const ( const (
@@ -46,16 +48,7 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
initBackgroundHealing(ctx, objAPI) // start quick background healing initBackgroundHealing(ctx, objAPI) // start quick background healing
var bgSeq *healSequence bgSeq := mustGetHealSequence(ctx)
var found bool
for {
bgSeq, found = globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
if found {
break
}
time.Sleep(time.Second)
}
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...) globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
@@ -118,12 +111,17 @@ func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
// 2. Only the node hosting the disk is responsible to perform the heal // 2. Only the node hosting the disk is responsible to perform the heal
func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq *healSequence) { func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq *healSequence) {
// Perform automatic disk healing when a disk is replaced locally. // Perform automatic disk healing when a disk is replaced locally.
diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval)
defer diskCheckTimer.Stop()
wait: wait:
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-time.After(defaultMonitorNewDiskInterval): case <-diskCheckTimer.C:
// Reset to next interval.
diskCheckTimer.Reset(defaultMonitorNewDiskInterval)
var erasureSetInZoneDisksToHeal []map[int][]StorageAPI var erasureSetInZoneDisksToHeal []map[int][]StorageAPI
healDisks := globalBackgroundHealState.getHealLocalDisks() healDisks := globalBackgroundHealState.getHealLocalDisks()
@@ -143,6 +141,10 @@ wait:
} }
} }
if serverDebugLog {
console.Debugf("disk check timer fired, attempting to heal %d drives\n", len(healDisks))
}
// heal only if new disks found. // heal only if new disks found.
for _, endpoint := range healDisks { for _, endpoint := range healDisks {
disk, format, err := connectEndpoint(endpoint) disk, format, err := connectEndpoint(endpoint)
@@ -169,7 +171,13 @@ wait:
erasureSetInZoneDisksToHeal[zoneIdx][setIndex] = append(erasureSetInZoneDisksToHeal[zoneIdx][setIndex], disk) erasureSetInZoneDisksToHeal[zoneIdx][setIndex] = append(erasureSetInZoneDisksToHeal[zoneIdx][setIndex], disk)
} }
buckets, _ := z.ListBucketsHeal(ctx) buckets, _ := z.ListBuckets(ctx)
// Heal latest buckets first.
sort.Slice(buckets, func(i, j int) bool {
return buckets[i].Created.After(buckets[j].Created)
})
for i, setMap := range erasureSetInZoneDisksToHeal { for i, setMap := range erasureSetInZoneDisksToHeal {
for setIndex, disks := range setMap { for setIndex, disks := range setMap {
for _, disk := range disks { for _, disk := range disks {
+19 -4
View File
@@ -28,6 +28,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/google/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
@@ -433,6 +434,20 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
} }
continue continue
} }
if object.VersionID != "" && object.VersionID != nullVersionID {
if _, err := uuid.Parse(object.VersionID); err != nil {
logger.LogIf(ctx, fmt.Errorf("invalid version-id specified %w", err))
apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion)
dErrs[index] = DeleteError{
Code: apiErr.Code,
Message: apiErr.Description,
Key: object.ObjectName,
VersionID: object.VersionID,
}
continue
}
}
if replicateDeletes || hasLockEnabled || hasLifecycleConfig { if replicateDeletes || hasLockEnabled || hasLifecycleConfig {
goi, gerr = getObjectInfoFn(ctx, bucket, object.ObjectName, ObjectOptions{ goi, gerr = getObjectInfoFn(ctx, bucket, object.ObjectName, ObjectOptions{
VersionID: object.VersionID, VersionID: object.VersionID,
@@ -503,7 +518,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus, DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus,
PurgeTransitioned: dObjects[i].PurgeTransitioned, PurgeTransitioned: dObjects[i].PurgeTransitioned,
}] }]
if errs[i] == nil || isErrObjectNotFound(errs[i]) { if errs[i] == nil || isErrObjectNotFound(errs[i]) || isErrVersionNotFound(errs[i]) {
if replicateDeletes { if replicateDeletes {
dObjects[i].DeleteMarkerReplicationStatus = deleteList[i].DeleteMarkerReplicationStatus dObjects[i].DeleteMarkerReplicationStatus = deleteList[i].DeleteMarkerReplicationStatus
dObjects[i].VersionPurgeStatus = deleteList[i].VersionPurgeStatus dObjects[i].VersionPurgeStatus = deleteList[i].VersionPurgeStatus
@@ -733,7 +748,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -880,7 +895,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption { if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption {
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if !crypto.SSEC.IsRequested(r.Header) { if !crypto.SSEC.IsRequested(r.Header) {
r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
} }
} }
@@ -892,7 +907,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
if crypto.IsRequested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests if _, ok := crypto.IsRequested(formValues); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests
if crypto.SSECopy.IsRequested(r.Header) { if crypto.SSECopy.IsRequested(r.Header) {
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
return return
+17 -6
View File
@@ -28,7 +28,6 @@ import (
miniogo "github.com/minio/minio-go/v7" miniogo "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
sse "github.com/minio/minio/pkg/bucket/encryption" sse "github.com/minio/minio/pkg/bucket/encryption"
@@ -356,11 +355,23 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, objInfo Object
opts.Versioned = globalBucketVersioningSys.Enabled(oi.Bucket) opts.Versioned = globalBucketVersioningSys.Enabled(oi.Bucket)
opts.VersionID = oi.VersionID opts.VersionID = oi.VersionID
opts.TransitionStatus = lifecycle.TransitionComplete opts.TransitionStatus = lifecycle.TransitionComplete
eventName := event.ObjectTransitionComplete
if _, err = objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts); err != nil { _, err = objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts)
return err if err != nil {
eventName = event.ObjectTransitionFailed
} }
return nil // Notify object deleted event.
sendEvent(eventArgs{
EventName: eventName,
BucketName: oi.Bucket,
Object: ObjectInfo{
Name: oi.Name,
VersionID: opts.VersionID,
},
Host: "Internal: [ILM-Transition]",
})
return err
} }
// getLifecycleTransitionTargetArn returns transition ARN for storage class specified in the config. // getLifecycleTransitionTargetArn returns transition ARN for storage class specified in the config.
@@ -535,7 +546,7 @@ func (r *RestoreObjectRequest) validate(ctx context.Context, objAPI ObjectLayer)
if r.OutputLocation.S3.Prefix == "" { if r.OutputLocation.S3.Prefix == "" {
return fmt.Errorf("Prefix is a required parameter in OutputLocation") return fmt.Errorf("Prefix is a required parameter in OutputLocation")
} }
if r.OutputLocation.S3.Encryption.EncryptionType != crypto.SSEAlgorithmAES256 { if r.OutputLocation.S3.Encryption.EncryptionType != xhttp.AmzEncryptionAES {
return NotImplemented{} return NotImplemented{}
} }
} }
@@ -561,7 +572,7 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
} }
meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String() meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String()
if rreq.OutputLocation.S3.Encryption.EncryptionType != "" { if rreq.OutputLocation.S3.Encryption.EncryptionType != "" {
meta[crypto.SSEHeader] = crypto.SSEAlgorithmAES256 meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
} }
return ObjectOptions{ return ObjectOptions{
Versioned: globalBucketVersioningSys.Enabled(bucket), Versioned: globalBucketVersioningSys.Enabled(bucket),
+9 -2
View File
@@ -24,6 +24,7 @@ import (
"sync" "sync"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
bucketsse "github.com/minio/minio/pkg/bucket/encryption" bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle" "github.com/minio/minio/pkg/bucket/lifecycle"
@@ -168,7 +169,10 @@ func (sys *BucketMetadataSys) Update(bucket string, configFile string, configDat
} }
meta.ReplicationConfigXML = configData meta.ReplicationConfigXML = configData
case bucketTargetsFile: case bucketTargetsFile:
meta.BucketTargetsConfigJSON = configData meta.BucketTargetsConfigJSON, meta.BucketTargetsConfigMetaJSON, err = encryptBucketMetadata(meta.Name, configData, crypto.Context{bucket: meta.Name, bucketTargetsFile: bucketTargetsFile})
if err != nil {
return fmt.Errorf("Error encrypting bucket target metadata %w", err)
}
default: default:
return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile) return fmt.Errorf("Unknown bucket %s metadata update requested %s", bucket, configFile)
} }
@@ -191,7 +195,6 @@ func (sys *BucketMetadataSys) Update(bucket string, configFile string, configDat
// This function should only be used with // This function should only be used with
// - GetBucketInfo // - GetBucketInfo
// - ListBuckets // - ListBuckets
// - ListBucketsHeal (only in case of erasure coding mode)
// For all other bucket specific metadata, use the relevant // For all other bucket specific metadata, use the relevant
// calls implemented specifically for each of those features. // calls implemented specifically for each of those features.
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) { func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
@@ -440,6 +443,10 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
for index := range buckets { for index := range buckets {
index := index index := index
g.Go(func() error { g.Go(func() error {
_, _ = objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
// Ensure heal opts for bucket metadata be deep healed all the time.
ScanMode: madmin.HealDeepScan,
})
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name) meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name)
if err != nil { if err != nil {
return err return err
+112 -31
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
@@ -28,6 +29,7 @@ import (
"time" "time"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
bucketsse "github.com/minio/minio/pkg/bucket/encryption" bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle" "github.com/minio/minio/pkg/bucket/lifecycle"
@@ -37,6 +39,7 @@ import (
"github.com/minio/minio/pkg/bucket/versioning" "github.com/minio/minio/pkg/bucket/versioning"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
"github.com/minio/sio"
) )
const ( const (
@@ -61,31 +64,33 @@ var (
// bucketMetadataFormat refers to the format. // bucketMetadataFormat refers to the format.
// bucketMetadataVersion can be used to track a rolling upgrade of a field. // bucketMetadataVersion can be used to track a rolling upgrade of a field.
type BucketMetadata struct { type BucketMetadata struct {
Name string Name string
Created time.Time Created time.Time
LockEnabled bool // legacy not used anymore. LockEnabled bool // legacy not used anymore.
PolicyConfigJSON []byte PolicyConfigJSON []byte
NotificationConfigXML []byte NotificationConfigXML []byte
LifecycleConfigXML []byte LifecycleConfigXML []byte
ObjectLockConfigXML []byte ObjectLockConfigXML []byte
VersioningConfigXML []byte VersioningConfigXML []byte
EncryptionConfigXML []byte EncryptionConfigXML []byte
TaggingConfigXML []byte TaggingConfigXML []byte
QuotaConfigJSON []byte QuotaConfigJSON []byte
ReplicationConfigXML []byte ReplicationConfigXML []byte
BucketTargetsConfigJSON []byte BucketTargetsConfigJSON []byte
BucketTargetsConfigMetaJSON []byte
// Unexported fields. Must be updated atomically. // Unexported fields. Must be updated atomically.
policyConfig *policy.Policy policyConfig *policy.Policy
notificationConfig *event.Config notificationConfig *event.Config
lifecycleConfig *lifecycle.Lifecycle lifecycleConfig *lifecycle.Lifecycle
objectLockConfig *objectlock.Config objectLockConfig *objectlock.Config
versioningConfig *versioning.Versioning versioningConfig *versioning.Versioning
sseConfig *bucketsse.BucketSSEConfig sseConfig *bucketsse.BucketSSEConfig
taggingConfig *tags.Tags taggingConfig *tags.Tags
quotaConfig *madmin.BucketQuota quotaConfig *madmin.BucketQuota
replicationConfig *replication.Config replicationConfig *replication.Config
bucketTargetConfig *madmin.BucketTargets bucketTargetConfig *madmin.BucketTargets
bucketTargetConfigMeta map[string]string
} }
// newBucketMetadata creates BucketMetadata with the supplied name and Created to Now. // newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
@@ -100,7 +105,8 @@ func newBucketMetadata(name string) BucketMetadata {
versioningConfig: &versioning.Versioning{ versioningConfig: &versioning.Versioning{
XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
}, },
bucketTargetConfig: &madmin.BucketTargets{}, bucketTargetConfig: &madmin.BucketTargets{},
bucketTargetConfigMeta: make(map[string]string),
} }
} }
@@ -140,16 +146,16 @@ func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string)
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) { func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
b := newBucketMetadata(bucket) b := newBucketMetadata(bucket)
err := b.Load(ctx, objectAPI, b.Name) err := b.Load(ctx, objectAPI, b.Name)
if err == nil { if err != nil && !errors.Is(err, errConfigNotFound) {
return b, b.convertLegacyConfigs(ctx, objectAPI)
}
if !errors.Is(err, errConfigNotFound) {
return b, err return b, err
} }
// Old bucket without bucket metadata. Hence we migrate existing settings. // Old bucket without bucket metadata. Hence we migrate existing settings.
return b, b.convertLegacyConfigs(ctx, objectAPI) if err := b.convertLegacyConfigs(ctx, objectAPI); err != nil {
return b, err
}
// migrate unencrypted remote targets
return b, b.migrateTargetConfig(ctx, objectAPI)
} }
// parseAllConfigs will parse all configs and populate the private fields. // parseAllConfigs will parse all configs and populate the private fields.
@@ -234,7 +240,8 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa
} }
if len(b.BucketTargetsConfigJSON) != 0 { if len(b.BucketTargetsConfigJSON) != 0 {
if err = json.Unmarshal(b.BucketTargetsConfigJSON, b.bucketTargetConfig); err != nil { b.bucketTargetConfig, err = parseBucketTargetConfig(b.Name, b.BucketTargetsConfigJSON, b.BucketTargetsConfigMetaJSON)
if err != nil {
return err return err
} }
} else { } else {
@@ -354,6 +361,7 @@ func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
if err != nil { if err != nil {
return err return err
} }
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile) configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile)
return saveConfig(ctx, api, configFile, data) return saveConfig(ctx, api, configFile, data)
} }
@@ -373,3 +381,76 @@ func deleteBucketMetadata(ctx context.Context, obj objectDeleter, bucket string)
} }
return nil return nil
} }
// migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error {
var err error
// early return if no targets or already encrypted
if len(b.BucketTargetsConfigJSON) == 0 || GlobalKMS == nil || len(b.BucketTargetsConfigMetaJSON) != 0 {
return nil
}
encBytes, metaBytes, err := encryptBucketMetadata(b.Name, b.BucketTargetsConfigJSON, crypto.Context{b.Name: b.Name, bucketTargetsFile: bucketTargetsFile})
if err != nil {
return err
}
b.BucketTargetsConfigJSON = encBytes
b.BucketTargetsConfigMetaJSON = metaBytes
return b.Save(ctx, objectAPI)
}
// encrypt bucket metadata if kms is configured.
func encryptBucketMetadata(bucket string, input []byte, kmsContext crypto.Context) (output, metabytes []byte, err error) {
var sealedKey crypto.SealedKey
if GlobalKMS == nil {
output = input
return
}
var (
key [32]byte
encKey []byte
)
metadata := make(map[string]string)
key, encKey, err = GlobalKMS.GenerateKey(GlobalKMS.DefaultKeyID(), kmsContext)
if err != nil {
return
}
outbuf := bytes.NewBuffer(nil)
objectKey := crypto.GenerateKey(key, rand.Reader)
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
crypto.S3.CreateMetadata(metadata, GlobalKMS.DefaultKeyID(), encKey, sealedKey)
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20})
if err != nil {
return output, metabytes, err
}
metabytes, err = json.Marshal(metadata)
if err != nil {
return
}
return outbuf.Bytes(), metabytes, nil
}
// decrypt bucket metadata if kms is configured.
func decryptBucketMetadata(input []byte, bucket string, meta map[string]string, kmsContext crypto.Context) ([]byte, error) {
if GlobalKMS == nil {
return nil, errKMSNotConfigured
}
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(meta)
if err != nil {
return nil, err
}
extKey, err := GlobalKMS.UnsealKey(keyID, kmsKey, kmsContext)
if err != nil {
return nil, err
}
var objectKey crypto.ObjectKey
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, ""); err != nil {
return nil, err
}
outbuf := bytes.NewBuffer(nil)
_, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20})
return outbuf.Bytes(), err
}
+30 -5
View File
@@ -102,6 +102,12 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "BucketTargetsConfigJSON") err = msgp.WrapError(err, "BucketTargetsConfigJSON")
return return
} }
case "BucketTargetsConfigMetaJSON":
z.BucketTargetsConfigMetaJSON, err = dc.ReadBytes(z.BucketTargetsConfigMetaJSON)
if err != nil {
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
return
}
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@@ -115,9 +121,9 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) { func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 13 // map header, size 14
// write "Name" // write "Name"
err = en.Append(0x8d, 0xa4, 0x4e, 0x61, 0x6d, 0x65) err = en.Append(0x8e, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
if err != nil { if err != nil {
return return
} }
@@ -246,15 +252,25 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "BucketTargetsConfigJSON") err = msgp.WrapError(err, "BucketTargetsConfigJSON")
return return
} }
// write "BucketTargetsConfigMetaJSON"
err = en.Append(0xbb, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x4a, 0x53, 0x4f, 0x4e)
if err != nil {
return
}
err = en.WriteBytes(z.BucketTargetsConfigMetaJSON)
if err != nil {
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
return
}
return return
} }
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) { func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 13 // map header, size 14
// string "Name" // string "Name"
o = append(o, 0x8d, 0xa4, 0x4e, 0x61, 0x6d, 0x65) o = append(o, 0x8e, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
o = msgp.AppendString(o, z.Name) o = msgp.AppendString(o, z.Name)
// string "Created" // string "Created"
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64) o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64)
@@ -292,6 +308,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
// string "BucketTargetsConfigJSON" // string "BucketTargetsConfigJSON"
o = append(o, 0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e) o = append(o, 0xb7, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x53, 0x4f, 0x4e)
o = msgp.AppendBytes(o, z.BucketTargetsConfigJSON) o = msgp.AppendBytes(o, z.BucketTargetsConfigJSON)
// string "BucketTargetsConfigMetaJSON"
o = append(o, 0xbb, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x4a, 0x53, 0x4f, 0x4e)
o = msgp.AppendBytes(o, z.BucketTargetsConfigMetaJSON)
return return
} }
@@ -391,6 +410,12 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "BucketTargetsConfigJSON") err = msgp.WrapError(err, "BucketTargetsConfigJSON")
return return
} }
case "BucketTargetsConfigMetaJSON":
z.BucketTargetsConfigMetaJSON, bts, err = msgp.ReadBytesBytes(bts, z.BucketTargetsConfigMetaJSON)
if err != nil {
err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON")
return
}
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@@ -405,6 +430,6 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *BucketMetadata) Msgsize() (s int) { func (z *BucketMetadata) Msgsize() (s int) {
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON)
return return
} }
+3
View File
@@ -97,6 +97,9 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
return ErrNone return ErrNone
} }
} }
if isErrObjectNotFound(gerr) || isErrVersionNotFound(gerr) {
return ErrNone
}
return toAPIErrorCode(ctx, gerr) return toAPIErrorCode(ctx, gerr)
} }
+2 -2
View File
@@ -287,7 +287,7 @@ func getCopyObjMetadata(oi ObjectInfo, dest replication.Destination) map[string]
if oi.UserTags != "" { if oi.UserTags != "" {
meta[xhttp.AmzObjectTagging] = oi.UserTags meta[xhttp.AmzObjectTagging] = oi.UserTags
} }
meta[xhttp.MinIOSourceMTime] = oi.ModTime.Format(time.RFC3339) meta[xhttp.MinIOSourceMTime] = oi.ModTime.Format(time.RFC3339Nano)
meta[xhttp.MinIOSourceETag] = oi.ETag meta[xhttp.MinIOSourceETag] = oi.ETag
meta[xhttp.AmzBucketReplicationStatus] = replication.Replica.String() meta[xhttp.AmzBucketReplicationStatus] = replication.Replica.String()
return meta return meta
@@ -330,7 +330,7 @@ func putReplicationOpts(ctx context.Context, dest replication.Destination, objIn
putOpts.Mode = rmode putOpts.Mode = rmode
} }
if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok { if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok {
rdate, err := time.Parse(time.RFC3339, retainDateStr) rdate, err := time.Parse(time.RFC3339Nano, retainDateStr)
if err != nil { if err != nil {
return return
} }
+32 -1
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
@@ -27,6 +28,7 @@ import (
minio "github.com/minio/minio-go/v7" minio "github.com/minio/minio-go/v7"
miniogo "github.com/minio/minio-go/v7" miniogo "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/pkg/bucket/versioning" "github.com/minio/minio/pkg/bucket/versioning"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
sha256 "github.com/minio/sha256-simd" sha256 "github.com/minio/sha256-simd"
@@ -348,7 +350,7 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*m
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "") creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
getRemoteTargetInstanceTransportOnce.Do(func() { getRemoteTargetInstanceTransportOnce.Do(func() {
getRemoteTargetInstanceTransport = newGatewayHTTPTransport(1 * time.Hour) getRemoteTargetInstanceTransport = newGatewayHTTPTransport(10 * time.Minute)
}) })
core, err := miniogo.NewCore(tcfg.URL().Host, &miniogo.Options{ core, err := miniogo.NewCore(tcfg.URL().Host, &miniogo.Options{
@@ -391,3 +393,32 @@ func generateARN(t *madmin.BucketTarget) string {
} }
return arn.String() return arn.String()
} }
// Returns parsed target config. If KMS is configured, remote target is decrypted
func parseBucketTargetConfig(bucket string, cdata, cmetadata []byte) (*madmin.BucketTargets, error) {
var (
data []byte
err error
t madmin.BucketTargets
meta map[string]string
)
if len(cdata) == 0 {
return nil, nil
}
data = cdata
if len(cmetadata) != 0 {
if err := json.Unmarshal(cmetadata, &meta); err != nil {
return nil, err
}
if crypto.S3.IsEncrypted(meta) {
if data, err = decryptBucketMetadata(cdata, bucket, meta, crypto.Context{bucket: bucket, bucketTargetsFile: bucketTargetsFile}); err != nil {
return nil, err
}
}
}
if err = json.Unmarshal(data, &t); err != nil {
return nil, err
}
return &t, nil
}
+15 -1
View File
@@ -30,6 +30,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/fatih/color"
dns2 "github.com/miekg/dns" dns2 "github.com/miekg/dns"
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
@@ -38,15 +39,26 @@ import (
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/certs"
"github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
) )
// serverDebugLog will enable debug printing
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
func init() { func init() {
logger.Init(GOPATH, GOROOT) logger.Init(GOPATH, GOROOT)
logger.RegisterError(config.FmtError) logger.RegisterError(config.FmtError)
rand.Seed(time.Now().UTC().UnixNano()) rand.Seed(time.Now().UTC().UnixNano())
globalDNSCache = xhttp.NewDNSCache(3*time.Second, 10*time.Second) globalDNSCache = xhttp.NewDNSCache(10*time.Second, 3*time.Second)
initGlobalContext()
globalReplicationState = newReplicationState()
globalTransitionState = newTransitionState()
console.SetColor("Debug", color.New())
gob.Register(StorageErr("")) gob.Register(StorageErr(""))
} }
@@ -64,10 +76,12 @@ func verifyObjectLayerFeatures(name string, objAPI ObjectLayer) {
} }
} }
globalCompressConfigMu.Lock()
if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() { if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() {
logger.Fatal(errInvalidArgument, logger.Fatal(errInvalidArgument,
"Compression support is requested but '%s' does not support compression", name) "Compression support is requested but '%s' does not support compression", name)
} }
globalCompressConfigMu.Unlock()
} }
// Check for updates and print a notification message // Check for updates and print a notification message
+85 -10
View File
@@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
@@ -25,6 +26,7 @@ import (
"github.com/minio/minio/cmd/config/api" "github.com/minio/minio/cmd/config/api"
"github.com/minio/minio/cmd/config/cache" "github.com/minio/minio/cmd/config/cache"
"github.com/minio/minio/cmd/config/compress" "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/dns"
"github.com/minio/minio/cmd/config/etcd" "github.com/minio/minio/cmd/config/etcd"
"github.com/minio/minio/cmd/config/heal" "github.com/minio/minio/cmd/config/heal"
@@ -57,6 +59,7 @@ func initHelp() {
config.LoggerWebhookSubSys: logger.DefaultKVS, config.LoggerWebhookSubSys: logger.DefaultKVS,
config.AuditWebhookSubSys: logger.DefaultAuditKVS, config.AuditWebhookSubSys: logger.DefaultAuditKVS,
config.HealSubSys: heal.DefaultKVS, config.HealSubSys: heal.DefaultKVS,
config.CrawlerSubSys: crawler.DefaultKVS,
} }
for k, v := range notify.DefaultNotificationKVS { for k, v := range notify.DefaultNotificationKVS {
kvs[k] = v kvs[k] = v
@@ -112,6 +115,10 @@ func initHelp() {
Key: config.HealSubSys, Key: config.HealSubSys,
Description: "manage object healing frequency and bitrot verification checks", Description: "manage object healing frequency and bitrot verification checks",
}, },
config.HelpKV{
Key: config.CrawlerSubSys,
Description: "manage crawling for usage calculation, lifecycle, healing and more",
},
config.HelpKV{ config.HelpKV{
Key: config.LoggerWebhookSubSys, Key: config.LoggerWebhookSubSys,
Description: "send server logs to webhook endpoints", Description: "send server logs to webhook endpoints",
@@ -192,6 +199,7 @@ func initHelp() {
config.CacheSubSys: cache.Help, config.CacheSubSys: cache.Help,
config.CompressionSubSys: compress.Help, config.CompressionSubSys: compress.Help,
config.HealSubSys: heal.Help, config.HealSubSys: heal.Help,
config.CrawlerSubSys: crawler.Help,
config.IdentityOpenIDSubSys: openid.Help, config.IdentityOpenIDSubSys: openid.Help,
config.IdentityLDAPSubSys: xldap.Help, config.IdentityLDAPSubSys: xldap.Help,
config.PolicyOPASubSys: opa.Help, config.PolicyOPASubSys: opa.Help,
@@ -221,6 +229,9 @@ var (
) )
func validateConfig(s config.Config, setDriveCount int) error { func validateConfig(s config.Config, setDriveCount int) error {
// We must have a global lock for this so nobody else modifies env while we do.
defer env.LockSetEnv()()
// Disable merging env values with config for validation. // Disable merging env values with config for validation.
env.SetEnvOff() env.SetEnvOff()
@@ -249,14 +260,25 @@ func validateConfig(s config.Config, setDriveCount int) error {
return err return err
} }
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil { compCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
if err != nil {
return err return err
} }
objAPI := newObjectLayerFn()
if objAPI != nil {
if compCfg.Enabled && !objAPI.IsCompressionSupported() {
return fmt.Errorf("Backend does not support compression")
}
}
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil { if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
return err return err
} }
if _, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default]); err != nil {
return err
}
{ {
etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs) etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs)
if err != nil { if err != nil {
@@ -438,10 +460,6 @@ func lookupConfigs(s config.Config, setDriveCount int) {
} }
} }
} }
globalHealConfig, err = heal.LookupConfig(s[config.HealSubSys][config.Default])
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to read heal config: %w", err))
}
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport()) kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
if err != nil { if err != nil {
@@ -459,11 +477,6 @@ func lookupConfigs(s config.Config, setDriveCount int) {
logger.LogIf(ctx, fmt.Errorf("%s env is deprecated please migrate to using `mc encrypt` at bucket level", crypto.EnvKMSAutoEncryption)) logger.LogIf(ctx, fmt.Errorf("%s env is deprecated please migrate to using `mc encrypt` at bucket level", crypto.EnvKMSAutoEncryption))
} }
globalCompressConfig, err = compress.LookupConfig(s[config.CompressionSubSys][config.Default])
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to setup Compression: %w", err))
}
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default], globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
NewGatewayHTTPTransport(), xhttp.DrainBody) NewGatewayHTTPTransport(), xhttp.DrainBody)
if err != nil { if err != nil {
@@ -538,6 +551,68 @@ func lookupConfigs(s config.Config, setDriveCount int) {
if err != nil { if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err)) logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
} }
// Apply dynamic config values
logger.LogIf(ctx, applyDynamicConfig(ctx, s))
}
// applyDynamicConfig will apply dynamic config values.
// Dynamic systems should be in config.SubSystemsDynamic as well.
func applyDynamicConfig(ctx context.Context, s config.Config) error {
// Read all dynamic configs.
// API
apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default])
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err))
}
// Compression
cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
if err != nil {
return fmt.Errorf("Unable to setup Compression: %w", err)
}
objAPI := newObjectLayerFn()
if objAPI != nil {
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
return fmt.Errorf("Backend does not support compression")
}
}
// Heal
healCfg, err := heal.LookupConfig(s[config.HealSubSys][config.Default])
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to apply heal config: %w", err))
}
// Crawler
crawlerCfg, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default])
if err != nil {
return fmt.Errorf("Unable to apply crawler config: %w", err)
}
// Apply configurations.
// We should not fail after this.
globalAPIConfig.init(apiConfig, globalAPIConfig.setDriveCount)
globalCompressConfigMu.Lock()
globalCompressConfig = cmpCfg
globalCompressConfigMu.Unlock()
globalHealConfigMu.Lock()
globalHealConfig = healCfg
globalHealConfigMu.Unlock()
logger.LogIf(ctx, crawlerSleeper.Update(crawlerCfg.Delay, crawlerCfg.MaxWait))
// Update all dynamic config values in memory.
globalServerConfigMu.Lock()
defer globalServerConfigMu.Unlock()
if globalServerConfig != nil {
for k := range config.SubSystemsDynamic {
globalServerConfig[k] = s[k]
}
}
return nil
} }
// Help - return sub-system level help // Help - return sub-system level help
+1 -1
View File
@@ -27,7 +27,7 @@ import (
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
etcd "go.etcd.io/etcd/v3/clientv3" etcd "go.etcd.io/etcd/clientv3"
) )
func handleEncryptedConfigBackend(objAPI ObjectLayer) error { func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
+1 -3
View File
@@ -113,8 +113,6 @@ func (sCfg *Config) UnmarshalJSON(data []byte) error {
// acceptable quorum expected for list operations // acceptable quorum expected for list operations
func (sCfg Config) GetListQuorum() int { func (sCfg Config) GetListQuorum() int {
switch sCfg.ListQuorum { switch sCfg.ListQuorum {
case "optimal":
return 3
case "reduced": case "reduced":
return 2 return 2
case "disk": case "disk":
@@ -123,7 +121,7 @@ func (sCfg Config) GetListQuorum() int {
case "strict": case "strict":
return -1 return -1
} }
// Defaults to 3 drives per set. // Defaults to 3 drives per set, defaults to "optimal" value
return 3 return 3
} }
+34 -18
View File
@@ -77,6 +77,7 @@ const (
LoggerWebhookSubSys = "logger_webhook" LoggerWebhookSubSys = "logger_webhook"
AuditWebhookSubSys = "audit_webhook" AuditWebhookSubSys = "audit_webhook"
HealSubSys = "heal" HealSubSys = "heal"
CrawlerSubSys = "crawler"
// Add new constants here if you add new fields to config. // Add new constants here if you add new fields to config.
) )
@@ -98,7 +99,7 @@ const (
) )
// SubSystems - all supported sub-systems // SubSystems - all supported sub-systems
var SubSystems = set.CreateStringSet([]string{ var SubSystems = set.CreateStringSet(
CredentialsSubSys, CredentialsSubSys,
RegionSubSys, RegionSubSys,
EtcdSubSys, EtcdSubSys,
@@ -113,6 +114,7 @@ var SubSystems = set.CreateStringSet([]string{
PolicyOPASubSys, PolicyOPASubSys,
IdentityLDAPSubSys, IdentityLDAPSubSys,
IdentityOpenIDSubSys, IdentityOpenIDSubSys,
CrawlerSubSys,
HealSubSys, HealSubSys,
NotifyAMQPSubSys, NotifyAMQPSubSys,
NotifyESSubSys, NotifyESSubSys,
@@ -124,7 +126,15 @@ var SubSystems = set.CreateStringSet([]string{
NotifyPostgresSubSys, NotifyPostgresSubSys,
NotifyRedisSubSys, NotifyRedisSubSys,
NotifyWebhookSubSys, NotifyWebhookSubSys,
}...) )
// SubSystemsDynamic - all sub-systems that have dynamic config.
var SubSystemsDynamic = set.CreateStringSet(
APISubSys,
CompressionSubSys,
CrawlerSubSys,
HealSubSys,
)
// SubSystemsSingleTargets - subsystems which only support single target. // SubSystemsSingleTargets - subsystems which only support single target.
var SubSystemsSingleTargets = set.CreateStringSet([]string{ var SubSystemsSingleTargets = set.CreateStringSet([]string{
@@ -141,6 +151,7 @@ var SubSystemsSingleTargets = set.CreateStringSet([]string{
IdentityLDAPSubSys, IdentityLDAPSubSys,
IdentityOpenIDSubSys, IdentityOpenIDSubSys,
HealSubSys, HealSubSys,
CrawlerSubSys,
}...) }...)
// Constant separators // Constant separators
@@ -309,25 +320,29 @@ func (c Config) DelFrom(r io.Reader) error {
return nil return nil
} }
// ReadFrom - implements io.ReaderFrom interface // ReadConfig - read content from input and write into c.
func (c Config) ReadFrom(r io.Reader) (int64, error) { // Returns whether all parameters were dynamic.
func (c Config) ReadConfig(r io.Reader) (dynOnly bool, err error) {
var n int var n int
scanner := bufio.NewScanner(r) scanner := bufio.NewScanner(r)
dynOnly = true
for scanner.Scan() { for scanner.Scan() {
// Skip any empty lines, or comment like characters // Skip any empty lines, or comment like characters
text := scanner.Text() text := scanner.Text()
if text == "" || strings.HasPrefix(text, KvComment) { if text == "" || strings.HasPrefix(text, KvComment) {
continue continue
} }
if err := c.SetKVS(text, DefaultKVS); err != nil { dynamic, err := c.SetKVS(text, DefaultKVS)
return 0, err if err != nil {
return false, err
} }
dynOnly = dynOnly && dynamic
n += len(text) n += len(text)
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
return 0, err return false, err
} }
return int64(n), nil return dynOnly, nil
} }
type configWriteTo struct { type configWriteTo struct {
@@ -618,26 +633,27 @@ func (c Config) Clone() Config {
} }
// SetKVS - set specific key values per sub-system. // SetKVS - set specific key values per sub-system.
func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error { func (c Config) SetKVS(s string, defaultKVS map[string]KVS) (dynamic bool, err error) {
if len(s) == 0 { if len(s) == 0 {
return Errorf("input arguments cannot be empty") return false, Errorf("input arguments cannot be empty")
} }
inputs := strings.SplitN(s, KvSpaceSeparator, 2) inputs := strings.SplitN(s, KvSpaceSeparator, 2)
if len(inputs) <= 1 { if len(inputs) <= 1 {
return Errorf("invalid number of arguments '%s'", s) return false, Errorf("invalid number of arguments '%s'", s)
} }
subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2) subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
if len(subSystemValue) == 0 { if len(subSystemValue) == 0 {
return Errorf("invalid number of arguments %s", s) return false, Errorf("invalid number of arguments %s", s)
} }
if !SubSystems.Contains(subSystemValue[0]) { if !SubSystems.Contains(subSystemValue[0]) {
return Errorf("unknown sub-system %s", s) return false, Errorf("unknown sub-system %s", s)
} }
if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 { if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
return Errorf("sub-system '%s' only supports single target", subSystemValue[0]) return false, Errorf("sub-system '%s' only supports single target", subSystemValue[0])
} }
dynamic = SubSystemsDynamic.Contains(subSystemValue[0])
tgt := Default tgt := Default
subSys := subSystemValue[0] subSys := subSystemValue[0]
@@ -647,7 +663,7 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys()) fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys())
if len(fields) == 0 { if len(fields) == 0 {
return Errorf("sub-system '%s' cannot have empty keys", subSys) return false, Errorf("sub-system '%s' cannot have empty keys", subSys)
} }
var kvs = KVS{} var kvs = KVS{}
@@ -670,7 +686,7 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
kvs.Set(prevK, madmin.SanitizeValue(kv[1])) kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
continue continue
} }
return Errorf("key '%s', cannot have empty value", kv[0]) return false, Errorf("key '%s', cannot have empty value", kv[0])
} }
_, ok := kvs.Lookup(Enable) _, ok := kvs.Lookup(Enable)
@@ -720,11 +736,11 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
// Return error only if the // Return error only if the
// key is enabled, for state=off // key is enabled, for state=off
// let it be empty. // let it be empty.
return Errorf( return false, Errorf(
"'%s' is not optional for '%s' sub-system, please check '%s' documentation", "'%s' is not optional for '%s' sub-system, please check '%s' documentation",
hkv.Key, subSys, subSys) hkv.Key, subSys, subSys)
} }
} }
c[subSys][tgt] = currKVS c[subSys][tgt] = currKVS
return nil return dynamic, nil
} }
+88
View File
@@ -0,0 +1,88 @@
/*
* 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 crawler
import (
"strconv"
"time"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/pkg/env"
)
// Compression environment variables
const (
Delay = "delay"
MaxWait = "max_wait"
EnvDelay = "MINIO_CRAWLER_DELAY"
EnvMaxWait = "MINIO_CRAWLER_MAX_WAIT"
)
// Config represents the heal settings.
type Config struct {
// Delay is the sleep multiplier.
Delay float64 `json:"delay"`
// MaxWait is maximum wait time between operations
MaxWait time.Duration
}
var (
// DefaultKVS - default KV config for heal settings
DefaultKVS = config.KVS{
config.KV{
Key: Delay,
Value: "10",
},
config.KV{
Key: MaxWait,
Value: "15s",
},
}
// Help provides help for config values
Help = config.HelpKVS{
config.HelpKV{
Key: Delay,
Description: `crawler delay multiplier, defaults to '10.0'`,
Optional: true,
Type: "float",
},
config.HelpKV{
Key: MaxWait,
Description: `maximum wait time between operations, defaults to '15s'`,
Optional: true,
Type: "duration",
},
}
)
// 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 {
return cfg, err
}
cfg.Delay, err = strconv.ParseFloat(env.Get(EnvDelay, kvs.Get(Delay)), 64)
if err != nil {
return cfg, err
}
cfg.MaxWait, err = time.ParseDuration(env.Get(EnvMaxWait, kvs.Get(MaxWait)))
if err != nil {
return cfg, err
}
return cfg, nil
}
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/etcd/msg"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"go.etcd.io/etcd/v3/clientv3" "go.etcd.io/etcd/clientv3"
) )
// ErrNoEntriesFound - Indicates no entries were found for the given key (directory) // ErrNoEntriesFound - Indicates no entries were found for the given key (directory)
+2 -2
View File
@@ -25,8 +25,8 @@ import (
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
"go.etcd.io/etcd/v3/clientv3" "go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/v3/clientv3/namespace" "go.etcd.io/etcd/clientv3/namespace"
) )
const ( const (
+10 -179
View File
@@ -19,231 +19,62 @@ import (
"crypto/md5" "crypto/md5"
"encoding/base64" "encoding/base64"
"net/http" "net/http"
"strings"
jsoniter "github.com/json-iterator/go"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
) )
// SSEHeader is the general AWS SSE HTTP header key.
const SSEHeader = "X-Amz-Server-Side-Encryption"
const (
// SSEKmsID is the HTTP header key referencing the SSE-KMS
// key ID.
SSEKmsID = SSEHeader + "-Aws-Kms-Key-Id"
// SSEKmsContext is the HTTP header key referencing the
// SSE-KMS encryption context.
SSEKmsContext = SSEHeader + "-Context"
)
const (
// SSECAlgorithm is the HTTP header key referencing
// the SSE-C algorithm.
SSECAlgorithm = SSEHeader + "-Customer-Algorithm"
// SSECKey is the HTTP header key referencing the
// SSE-C client-provided key..
SSECKey = SSEHeader + "-Customer-Key"
// SSECKeyMD5 is the HTTP header key referencing
// the MD5 sum of the client-provided key.
SSECKeyMD5 = SSEHeader + "-Customer-Key-Md5"
)
const (
// SSECopyAlgorithm is the HTTP header key referencing
// the SSE-C algorithm for SSE-C copy requests.
SSECopyAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
// SSECopyKey is the HTTP header key referencing the SSE-C
// client-provided key for SSE-C copy requests.
SSECopyKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
// SSECopyKeyMD5 is the HTTP header key referencing the
// MD5 sum of the client key for SSE-C copy requests.
SSECopyKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5"
)
const (
// SSEAlgorithmAES256 is the only supported value for the SSE-S3 or SSE-C algorithm header.
// For SSE-S3 see: https://docs.aws.amazon.com/AmazonS3/latest/dev/SSEUsingRESTAPI.html
// For SSE-C see: https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
SSEAlgorithmAES256 = "AES256"
// SSEAlgorithmKMS is the value of 'X-Amz-Server-Side-Encryption' for SSE-KMS.
// See: https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
SSEAlgorithmKMS = "aws:kms"
)
// RemoveSensitiveHeaders removes confidential encryption // RemoveSensitiveHeaders removes confidential encryption
// information - e.g. the SSE-C key - from the HTTP headers. // information - e.g. the SSE-C key - from the HTTP headers.
// It has the same semantics as RemoveSensitiveEntires. // It has the same semantics as RemoveSensitiveEntires.
func RemoveSensitiveHeaders(h http.Header) { func RemoveSensitiveHeaders(h http.Header) {
h.Del(SSECKey) h.Del(xhttp.AmzServerSideEncryptionCustomerKey)
h.Del(SSECopyKey) h.Del(xhttp.AmzServerSideEncryptionCopyCustomerKey)
h.Del(xhttp.AmzMetaUnencryptedContentLength) h.Del(xhttp.AmzMetaUnencryptedContentLength)
h.Del(xhttp.AmzMetaUnencryptedContentMD5) h.Del(xhttp.AmzMetaUnencryptedContentMD5)
} }
// IsRequested returns true if the HTTP headers indicates
// that any form server-side encryption (SSE-C, SSE-S3 or SSE-KMS)
// is requested.
func IsRequested(h http.Header) bool {
return S3.IsRequested(h) || SSEC.IsRequested(h) || SSECopy.IsRequested(h) || S3KMS.IsRequested(h)
}
// S3 represents AWS SSE-S3. It provides functionality to handle
// SSE-S3 requests.
var S3 = s3{}
type s3 struct{}
// IsRequested returns true if the HTTP headers indicates that
// the S3 client requests SSE-S3.
func (s3) IsRequested(h http.Header) bool {
_, ok := h[SSEHeader]
return ok && strings.ToLower(h.Get(SSEHeader)) != SSEAlgorithmKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value
}
// ParseHTTP parses the SSE-S3 related HTTP headers and checks
// whether they contain valid values.
func (s3) ParseHTTP(h http.Header) (err error) {
if h.Get(SSEHeader) != SSEAlgorithmAES256 {
err = ErrInvalidEncryptionMethod
}
return
}
// S3KMS represents AWS SSE-KMS. It provides functionality to
// handle SSE-KMS requests.
var S3KMS = s3KMS{}
type s3KMS struct{}
// IsRequested returns true if the HTTP headers indicates that
// the S3 client requests SSE-KMS.
func (s3KMS) IsRequested(h http.Header) bool {
if _, ok := h[SSEKmsID]; ok {
return true
}
if _, ok := h[SSEKmsContext]; ok {
return true
}
if _, ok := h[SSEHeader]; ok {
return strings.ToUpper(h.Get(SSEHeader)) != SSEAlgorithmAES256 // Return only true if the SSE header is specified and does not contain the SSE-S3 value
}
return false
}
// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID
// and context, if present, on success.
func (s3KMS) ParseHTTP(h http.Header) (string, interface{}, error) {
algorithm := h.Get(SSEHeader)
if algorithm != SSEAlgorithmKMS {
return "", nil, ErrInvalidEncryptionMethod
}
contextStr, ok := h[SSEKmsContext]
if ok {
var context map[string]interface{}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err := json.Unmarshal([]byte(contextStr[0]), &context); err != nil {
return "", nil, err
}
return h.Get(SSEKmsID), context, nil
}
return h.Get(SSEKmsID), nil, nil
}
var ( var (
// SSEC represents AWS SSE-C. It provides functionality to handle
// SSE-C requests.
SSEC = ssec{}
// SSECopy represents AWS SSE-C for copy requests. It provides // SSECopy represents AWS SSE-C for copy requests. It provides
// functionality to handle SSE-C copy requests. // functionality to handle SSE-C copy requests.
SSECopy = ssecCopy{} SSECopy = ssecCopy{}
) )
type ssec struct{}
type ssecCopy struct{} type ssecCopy struct{}
// IsRequested returns true if the HTTP headers contains
// at least one SSE-C header. SSE-C copy headers are ignored.
func (ssec) IsRequested(h http.Header) bool {
if _, ok := h[SSECAlgorithm]; ok {
return true
}
if _, ok := h[SSECKey]; ok {
return true
}
if _, ok := h[SSECKeyMD5]; ok {
return true
}
return false
}
// IsRequested returns true if the HTTP headers contains // IsRequested returns true if the HTTP headers contains
// at least one SSE-C copy header. Regular SSE-C headers // at least one SSE-C copy header. Regular SSE-C headers
// are ignored. // are ignored.
func (ssecCopy) IsRequested(h http.Header) bool { func (ssecCopy) IsRequested(h http.Header) bool {
if _, ok := h[SSECopyAlgorithm]; ok { if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm]; ok {
return true return true
} }
if _, ok := h[SSECopyKey]; ok { if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKey]; ok {
return true return true
} }
if _, ok := h[SSECopyKeyMD5]; ok { if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5]; ok {
return true return true
} }
return false return false
} }
// ParseHTTP parses the SSE-C headers and returns the SSE-C client key
// on success. SSE-C copy headers are ignored.
func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) {
if h.Get(SSECAlgorithm) != SSEAlgorithmAES256 {
return key, ErrInvalidCustomerAlgorithm
}
if h.Get(SSECKey) == "" {
return key, ErrMissingCustomerKey
}
if h.Get(SSECKeyMD5) == "" {
return key, ErrMissingCustomerKeyMD5
}
clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECKey))
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
return key, ErrInvalidCustomerKey
}
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECKeyMD5))
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
return key, ErrCustomerKeyMD5Mismatch
}
copy(key[:], clientKey)
return key, nil
}
// ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key // ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
// on success. Regular SSE-C headers are ignored. // on success. Regular SSE-C headers are ignored.
func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) { func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) {
if h.Get(SSECopyAlgorithm) != SSEAlgorithmAES256 { if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) != xhttp.AmzEncryptionAES {
return key, ErrInvalidCustomerAlgorithm return key, ErrInvalidCustomerAlgorithm
} }
if h.Get(SSECopyKey) == "" { if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) == "" {
return key, ErrMissingCustomerKey return key, ErrMissingCustomerKey
} }
if h.Get(SSECopyKeyMD5) == "" { if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) == "" {
return key, ErrMissingCustomerKeyMD5 return key, ErrMissingCustomerKeyMD5
} }
clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKey)) clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey))
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
return key, ErrInvalidCustomerKey return key, ErrInvalidCustomerKey
} }
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKeyMD5)) keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5))
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) { if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
return key, ErrCustomerKeyMD5Mismatch return key, ErrCustomerKeyMD5Mismatch
} }
+39 -36
View File
@@ -18,26 +18,29 @@ import (
"net/http" "net/http"
"sort" "sort"
"testing" "testing"
xhttp "github.com/minio/minio/cmd/http"
) )
func TestIsRequested(t *testing.T) { func TestIsRequested(t *testing.T) {
for i, test := range kmsIsRequestedTests { for i, test := range kmsIsRequestedTests {
if got := IsRequested(test.Header) && S3KMS.IsRequested(test.Header); got != test.Expected { _, got := IsRequested(test.Header)
got = got && S3KMS.IsRequested(test.Header)
if got != test.Expected {
t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got) t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got)
} }
} }
for i, test := range s3IsRequestedTests { for i, test := range s3IsRequestedTests {
if got := IsRequested(test.Header) && S3.IsRequested(test.Header); got != test.Expected { _, got := IsRequested(test.Header)
got = got && S3.IsRequested(test.Header)
if got != test.Expected {
t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got) t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got)
} }
} }
for i, test := range ssecIsRequestedTests { for i, test := range ssecIsRequestedTests {
if got := IsRequested(test.Header) && SSEC.IsRequested(test.Header); got != test.Expected { _, got := IsRequested(test.Header)
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got) got = got && SSEC.IsRequested(test.Header)
} if got != test.Expected {
}
for i, test := range ssecCopyIsRequestedTests {
if got := IsRequested(test.Header) && SSECopy.IsRequested(test.Header); got != test.Expected {
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got) t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got)
} }
} }
@@ -131,11 +134,11 @@ var s3IsRequestedTests = []struct {
Header http.Header Header http.Header
Expected bool Expected bool
}{ }{
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES256"}}, Expected: true}, // 0 {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES256"}}, Expected: true}, // 0
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1 {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2 {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2
{Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3 {Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{SSEAlgorithmKMS}}, Expected: false}, // 4 {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{xhttp.AmzEncryptionKMS}}, Expected: false}, // 4
} }
func TestS3IsRequested(t *testing.T) { func TestS3IsRequested(t *testing.T) {
@@ -403,7 +406,7 @@ func TestSSECopyParse(t *testing.T) {
if err == nil && key == zeroKey { if err == nil && key == zeroKey {
t.Errorf("Test %d: parsed client key is zero key", i) t.Errorf("Test %d: parsed client key is zero key", i)
} }
if _, ok := test.Header[SSECKey]; ok { if _, ok := test.Header[xhttp.AmzServerSideEncryptionCustomerKey]; ok {
t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i) t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i)
} }
} }
@@ -414,47 +417,47 @@ var removeSensitiveHeadersTests = []struct {
}{ }{
{ {
Header: http.Header{ Header: http.Header{
SSECKey: []string{""}, xhttp.AmzServerSideEncryptionCustomerKey: []string{""},
SSECopyKey: []string{""}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{""},
}, },
ExpectedHeader: http.Header{}, ExpectedHeader: http.Header{},
}, },
{ // Standard SSE-C request headers { // Standard SSE-C request headers
Header: http.Header{ Header: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
ExpectedHeader: http.Header{ ExpectedHeader: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
}, },
{ // Standard SSE-C + SSE-C-copy request headers { // Standard SSE-C + SSE-C-copy request headers
Header: http.Header{ Header: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
ExpectedHeader: http.Header{ ExpectedHeader: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
}, },
{ // Standard SSE-C + metadata request headers { // Standard SSE-C + metadata request headers
Header: http.Header{ Header: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
"X-Amz-Meta-Test-1": []string{"Test-1"}, "X-Amz-Meta-Test-1": []string{"Test-1"},
}, },
ExpectedHeader: http.Header{ ExpectedHeader: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
"X-Amz-Meta-Test-1": []string{"Test-1"}, "X-Amz-Meta-Test-1": []string{"Test-1"},
}, },
}, },
{ // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w { // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
+55 -196
View File
@@ -15,19 +15,42 @@
package crypto package crypto
import ( import (
"context"
"encoding/base64"
"errors"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" )
const (
// MetaMultipart indicates that the object has been uploaded
// in multiple parts - via the S3 multipart API.
MetaMultipart = "X-Minio-Internal-Encrypted-Multipart"
// MetaIV is the random initialization vector (IV) used for
// the MinIO-internal key derivation.
MetaIV = "X-Minio-Internal-Server-Side-Encryption-Iv"
// MetaAlgorithm is the algorithm used to derive internal keys
// and encrypt the objects.
MetaAlgorithm = "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm"
// MetaSealedKeySSEC is the sealed object encryption key in case of SSE-C.
MetaSealedKeySSEC = "X-Minio-Internal-Server-Side-Encryption-Sealed-Key"
// MetaSealedKeyS3 is the sealed object encryption key in case of SSE-S3
MetaSealedKeyS3 = "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key"
// MetaSealedKeyKMS is the sealed object encryption key in case of SSE-KMS
MetaSealedKeyKMS = "X-Minio-Internal-Server-Side-Encryption-Kms-Sealed-Key"
// MetaKeyID is the KMS master key ID used to generate/encrypt the data
// encryption key (DEK).
MetaKeyID = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id"
// MetaDataEncryptionKey is the sealed data encryption key (DEK) received from
// the KMS.
MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
) )
// IsMultiPart returns true if the object metadata indicates // IsMultiPart returns true if the object metadata indicates
// that it was uploaded using some form of server-side-encryption // that it was uploaded using some form of server-side-encryption
// and the S3 multipart API. // and the S3 multipart API.
func IsMultiPart(metadata map[string]string) bool { func IsMultiPart(metadata map[string]string) bool {
if _, ok := metadata[SSEMultipart]; ok { if _, ok := metadata[MetaMultipart]; ok {
return true return true
} }
return false return false
@@ -37,8 +60,8 @@ func IsMultiPart(metadata map[string]string) bool {
// information - e.g. the SSE-C key - from the metadata map. // information - e.g. the SSE-C key - from the metadata map.
// It has the same semantics as RemoveSensitiveHeaders. // It has the same semantics as RemoveSensitiveHeaders.
func RemoveSensitiveEntries(metadata map[string]string) { // The functions is tested in TestRemoveSensitiveHeaders for compatibility reasons func RemoveSensitiveEntries(metadata map[string]string) { // The functions is tested in TestRemoveSensitiveHeaders for compatibility reasons
delete(metadata, SSECKey) delete(metadata, xhttp.AmzServerSideEncryptionCustomerKey)
delete(metadata, SSECopyKey) delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKey)
delete(metadata, xhttp.AmzMetaUnencryptedContentLength) delete(metadata, xhttp.AmzMetaUnencryptedContentLength)
delete(metadata, xhttp.AmzMetaUnencryptedContentMD5) delete(metadata, xhttp.AmzMetaUnencryptedContentMD5)
} }
@@ -46,31 +69,36 @@ func RemoveSensitiveEntries(metadata map[string]string) { // The functions is te
// RemoveSSEHeaders removes all crypto-specific SSE // RemoveSSEHeaders removes all crypto-specific SSE
// header entries from the metadata map. // header entries from the metadata map.
func RemoveSSEHeaders(metadata map[string]string) { func RemoveSSEHeaders(metadata map[string]string) {
delete(metadata, SSEHeader) delete(metadata, xhttp.AmzServerSideEncryption)
delete(metadata, SSEKmsID) delete(metadata, xhttp.AmzServerSideEncryptionKmsID)
delete(metadata, SSEKmsContext) delete(metadata, xhttp.AmzServerSideEncryptionKmsContext)
delete(metadata, SSECKeyMD5) delete(metadata, xhttp.AmzServerSideEncryptionCustomerAlgorithm)
delete(metadata, SSECAlgorithm) delete(metadata, xhttp.AmzServerSideEncryptionCustomerKey)
delete(metadata, xhttp.AmzServerSideEncryptionCustomerKeyMD5)
delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm)
delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKey)
delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5)
} }
// RemoveInternalEntries removes all crypto-specific internal // RemoveInternalEntries removes all crypto-specific internal
// metadata entries from the metadata map. // metadata entries from the metadata map.
func RemoveInternalEntries(metadata map[string]string) { func RemoveInternalEntries(metadata map[string]string) {
delete(metadata, SSEMultipart) delete(metadata, MetaMultipart)
delete(metadata, SSEIV) delete(metadata, MetaAlgorithm)
delete(metadata, SSESealAlgorithm) delete(metadata, MetaIV)
delete(metadata, SSECSealedKey) delete(metadata, MetaSealedKeySSEC)
delete(metadata, S3SealedKey) delete(metadata, MetaSealedKeyS3)
delete(metadata, S3KMSKeyID) delete(metadata, MetaSealedKeyKMS)
delete(metadata, S3KMSSealedKey) delete(metadata, MetaKeyID)
delete(metadata, MetaDataEncryptionKey)
} }
// IsSourceEncrypted returns true if the source is encrypted // IsSourceEncrypted returns true if the source is encrypted
func IsSourceEncrypted(metadata map[string]string) bool { func IsSourceEncrypted(metadata map[string]string) bool {
if _, ok := metadata[SSECAlgorithm]; ok { if _, ok := metadata[xhttp.AmzServerSideEncryptionCustomerAlgorithm]; ok {
return true return true
} }
if _, ok := metadata[SSEHeader]; ok { if _, ok := metadata[xhttp.AmzServerSideEncryption]; ok {
return true return true
} }
return false return false
@@ -82,10 +110,10 @@ func IsSourceEncrypted(metadata map[string]string) bool {
// IsEncrypted only checks whether the metadata contains at least // IsEncrypted only checks whether the metadata contains at least
// one entry indicating SSE-C or SSE-S3. // one entry indicating SSE-C or SSE-S3.
func IsEncrypted(metadata map[string]string) bool { func IsEncrypted(metadata map[string]string) bool {
if _, ok := metadata[SSEIV]; ok { if _, ok := metadata[MetaIV]; ok {
return true return true
} }
if _, ok := metadata[SSESealAlgorithm]; ok { if _, ok := metadata[MetaAlgorithm]; ok {
return true return true
} }
if IsMultiPart(metadata) { if IsMultiPart(metadata) {
@@ -97,28 +125,7 @@ func IsEncrypted(metadata map[string]string) bool {
if SSEC.IsEncrypted(metadata) { if SSEC.IsEncrypted(metadata) {
return true return true
} }
return false if S3KMS.IsEncrypted(metadata) {
}
// IsEncrypted returns true if the object metadata indicates
// that the object was uploaded using SSE-S3.
func (s3) IsEncrypted(metadata map[string]string) bool {
if _, ok := metadata[S3SealedKey]; ok {
return true
}
if _, ok := metadata[S3KMSKeyID]; ok {
return true
}
if _, ok := metadata[S3KMSSealedKey]; ok {
return true
}
return false
}
// IsEncrypted returns true if the object metadata indicates
// that the object was uploaded using SSE-C.
func (ssec) IsEncrypted(metadata map[string]string) bool {
if _, ok := metadata[SSECSealedKey]; ok {
return true return true
} }
return false return false
@@ -129,159 +136,11 @@ func (ssec) IsEncrypted(metadata map[string]string) bool {
// metadata is nil. // metadata is nil.
func CreateMultipartMetadata(metadata map[string]string) map[string]string { func CreateMultipartMetadata(metadata map[string]string) map[string]string {
if metadata == nil { if metadata == nil {
return map[string]string{SSEMultipart: ""} return map[string]string{MetaMultipart: ""}
} }
metadata[SSEMultipart] = "" metadata[MetaMultipart] = ""
return metadata return metadata
} }
// CreateMetadata encodes the sealed object key into the metadata and returns
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
// both into the metadata as well. It allocates a new metadata map if metadata
// is nil.
func (s3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
if sealedKey.Algorithm != SealAlgorithm {
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
}
// There are two possibilites:
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the caller has passed an invalid argument combination.
if keyID == "" && len(kmsKey) != 0 {
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
}
if keyID != "" && len(kmsKey) == 0 {
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
}
if metadata == nil {
metadata = make(map[string]string, 5)
}
metadata[SSESealAlgorithm] = sealedKey.Algorithm
metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[S3SealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
metadata[S3KMSKeyID] = keyID
metadata[S3KMSSealedKey] = base64.StdEncoding.EncodeToString(kmsKey)
}
return metadata
}
// ParseMetadata extracts all SSE-S3 related values from the object metadata
// and checks whether they are well-formed. It returns the sealed object key
// on success. If the metadata contains both, a KMS master key ID and a sealed
// KMS data key it returns both. If the metadata does not contain neither a
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
// KMS data key. Otherwise, it returns an error.
func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
// Extract all required values from object metadata
b64IV, ok := metadata[SSEIV]
if !ok {
return keyID, kmsKey, sealedKey, errMissingInternalIV
}
algorithm, ok := metadata[SSESealAlgorithm]
if !ok {
return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
}
b64SealedKey, ok := metadata[S3SealedKey]
if !ok {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
}
// There are two possibilites:
// - We use a KMS -> There must be a key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the metadata is corrupted.
keyID, idPresent := metadata[S3KMSKeyID]
b64KMSSealedKey, kmsKeyPresent := metadata[S3KMSSealedKey]
if !idPresent && kmsKeyPresent {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
}
if idPresent && !kmsKeyPresent {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
}
// Check whether all extracted values are well-formed
var iv [32]byte
n, err := base64.StdEncoding.Decode(iv[:], []byte(b64IV))
if err != nil || n != 32 {
return keyID, kmsKey, sealedKey, errInvalidInternalIV
}
if algorithm != SealAlgorithm {
return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
}
var encryptedKey [64]byte
n, err = base64.StdEncoding.Decode(encryptedKey[:], []byte(b64SealedKey))
if err != nil || n != 64 {
return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
}
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
if err != nil {
return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
}
}
sealedKey.Algorithm = algorithm
sealedKey.IV = iv
sealedKey.Key = encryptedKey
return keyID, kmsKey, sealedKey, nil
}
// CreateMetadata encodes the sealed key into the metadata and returns the modified metadata.
// It allocates a new metadata map if metadata is nil.
func (ssec) CreateMetadata(metadata map[string]string, sealedKey SealedKey) map[string]string {
if sealedKey.Algorithm != SealAlgorithm {
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-C", sealedKey.Algorithm))
}
if metadata == nil {
metadata = make(map[string]string, 3)
}
metadata[SSESealAlgorithm] = SealAlgorithm
metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[SSECSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
return metadata
}
// ParseMetadata extracts all SSE-C related values from the object metadata
// and checks whether they are well-formed. It returns the sealed object key
// on success.
func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err error) {
// Extract all required values from object metadata
b64IV, ok := metadata[SSEIV]
if !ok {
return sealedKey, errMissingInternalIV
}
algorithm, ok := metadata[SSESealAlgorithm]
if !ok {
return sealedKey, errMissingInternalSealAlgorithm
}
b64SealedKey, ok := metadata[SSECSealedKey]
if !ok {
return sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-C")
}
// Check whether all extracted values are well-formed
iv, err := base64.StdEncoding.DecodeString(b64IV)
if err != nil || len(iv) != 32 {
return sealedKey, errInvalidInternalIV
}
if algorithm != SealAlgorithm && algorithm != InsecureSealAlgorithm {
return sealedKey, errInvalidInternalSealAlgorithm
}
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
if err != nil || len(encryptedKey) != 64 {
return sealedKey, Errorf("The internal sealed key for SSE-C is invalid")
}
sealedKey.Algorithm = algorithm
copy(sealedKey.IV[:], iv)
copy(sealedKey.Key[:], encryptedKey)
return sealedKey, nil
}
// IsETagSealed returns true if the etag seems to be encrypted. // IsETagSealed returns true if the etag seems to be encrypted.
func IsETagSealed(etag []byte) bool { return len(etag) > 16 } func IsETagSealed(etag []byte) bool { return len(etag) > 16 }
+60 -60
View File
@@ -27,9 +27,9 @@ var isMultipartTests = []struct {
Metadata map[string]string Metadata map[string]string
Multipart bool Multipart bool
}{ }{
{Multipart: true, Metadata: map[string]string{SSEMultipart: ""}}, // 0 {Multipart: true, Metadata: map[string]string{MetaMultipart: ""}}, // 0
{Multipart: true, Metadata: map[string]string{"X-Minio-Internal-Encrypted-Multipart": ""}}, // 1 {Multipart: true, Metadata: map[string]string{"X-Minio-Internal-Encrypted-Multipart": ""}}, // 1
{Multipart: true, Metadata: map[string]string{SSEMultipart: "some-value"}}, // 2 {Multipart: true, Metadata: map[string]string{MetaMultipart: "some-value"}}, // 2
{Multipart: false, Metadata: map[string]string{"": ""}}, // 3 {Multipart: false, Metadata: map[string]string{"": ""}}, // 3
{Multipart: false, Metadata: map[string]string{"X-Minio-Internal-EncryptedMultipart": ""}}, // 4 {Multipart: false, Metadata: map[string]string{"X-Minio-Internal-EncryptedMultipart": ""}}, // 4
} }
@@ -46,13 +46,13 @@ var isEncryptedTests = []struct {
Metadata map[string]string Metadata map[string]string
Encrypted bool Encrypted bool
}{ }{
{Encrypted: true, Metadata: map[string]string{SSEMultipart: ""}}, // 0 {Encrypted: true, Metadata: map[string]string{MetaMultipart: ""}}, // 0
{Encrypted: true, Metadata: map[string]string{SSEIV: ""}}, // 1 {Encrypted: true, Metadata: map[string]string{MetaIV: ""}}, // 1
{Encrypted: true, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 {Encrypted: true, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
{Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 {Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
{Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4 {Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
{Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 {Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5
{Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 {Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
} }
@@ -69,13 +69,13 @@ var s3IsEncryptedTests = []struct {
Metadata map[string]string Metadata map[string]string
Encrypted bool Encrypted bool
}{ }{
{Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0 {Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0
{Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1 {Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1
{Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 {Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
{Encrypted: false, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 {Encrypted: false, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
{Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4 {Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
{Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 {Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5
{Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 {Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
} }
@@ -92,13 +92,13 @@ var ssecIsEncryptedTests = []struct {
Metadata map[string]string Metadata map[string]string
Encrypted bool Encrypted bool
}{ }{
{Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0 {Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0
{Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1 {Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1
{Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 {Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
{Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 {Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
{Encrypted: false, Metadata: map[string]string{S3SealedKey: ""}}, // 4 {Encrypted: false, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
{Encrypted: false, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 {Encrypted: false, Metadata: map[string]string{MetaKeyID: ""}}, // 5
{Encrypted: false, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 {Encrypted: false, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
} }
@@ -121,65 +121,65 @@ var s3ParseMetadataTests = []struct {
}{ }{
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}}, // 0 {ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}}, // 0
{ {
ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{MetaIV: ""},
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 1 }, // 1
{ {
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-S3"), ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-S3"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, Metadata: map[string]string{MetaIV: "", MetaAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 2 }, // 2
{ {
ExpectedErr: Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3"), ExpectedErr: Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSSealedKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaDataEncryptionKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 3 }, // 3
{ {
ExpectedErr: Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3"), ExpectedErr: Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: ""}, Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: ""},
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 4 }, // 4
{ {
ExpectedErr: errInvalidInternalIV, ExpectedErr: errInvalidInternalIV,
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: "", S3KMSSealedKey: ""}, Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: "", MetaDataEncryptionKey: ""},
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 5 }, // 5
{ {
ExpectedErr: errInvalidInternalSealAlgorithm, ExpectedErr: errInvalidInternalSealAlgorithm,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: "", S3KMSSealedKey: "", MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: "", MetaDataEncryptionKey: "",
}, },
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 6 }, // 6
{ {
ExpectedErr: Errorf("The internal sealed key for SSE-S3 is invalid"), ExpectedErr: Errorf("The internal sealed key for SSE-S3 is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, S3SealedKey: "", MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, MetaSealedKeyS3: "",
S3KMSKeyID: "", S3KMSSealedKey: "", MetaKeyID: "", MetaDataEncryptionKey: "",
}, },
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 7 }, // 7
{ {
ExpectedErr: Errorf("The internal sealed KMS data key for SSE-S3 is invalid"), ExpectedErr: Errorf("The internal sealed KMS data key for SSE-S3 is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "key-1", MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "key-1",
S3KMSSealedKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64 MetaDataEncryptionKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64
}, },
DataKey: []byte{}, KeyID: "key-1", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "key-1", SealedKey: SealedKey{},
}, // 8 }, // 8
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "", S3KMSSealedKey: "", MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "", MetaDataEncryptionKey: "",
}, },
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{Algorithm: SealAlgorithm}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{Algorithm: SealAlgorithm},
}, // 9 }, // 9
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: SealAlgorithm,
S3SealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), S3KMSKeyID: "key-1", MetaSealedKeyS3: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), MetaKeyID: "key-1",
S3KMSSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 48)), MetaDataEncryptionKey: base64.StdEncoding.EncodeToString(make([]byte, 48)),
}, },
DataKey: make([]byte, 48), KeyID: "key-1", SealedKey: SealedKey{Algorithm: SealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}}, DataKey: make([]byte, 48), KeyID: "key-1", SealedKey: SealedKey{Algorithm: SealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
}, // 10 }, // 10
@@ -223,43 +223,43 @@ var ssecParseMetadataTests = []struct {
SealedKey SealedKey SealedKey SealedKey
}{ }{
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0 {ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0
{ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, SealedKey: SealedKey{}}, // 1 {ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{MetaIV: ""}, SealedKey: SealedKey{}}, // 1
{ {
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-C"), ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-C"),
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, SealedKey: SealedKey{}, Metadata: map[string]string{MetaIV: "", MetaAlgorithm: ""}, SealedKey: SealedKey{},
}, // 2 }, // 2
{ {
ExpectedErr: errInvalidInternalIV, ExpectedErr: errInvalidInternalIV,
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", SSECSealedKey: ""}, SealedKey: SealedKey{}, Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeySSEC: ""}, SealedKey: SealedKey{},
}, // 3 }, // 3
{ {
ExpectedErr: errInvalidInternalSealAlgorithm, ExpectedErr: errInvalidInternalSealAlgorithm,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: "", SSECSealedKey: "", MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: "", MetaSealedKeySSEC: "",
}, },
SealedKey: SealedKey{}, SealedKey: SealedKey{},
}, // 4 }, // 4
{ {
ExpectedErr: Errorf("The internal sealed key for SSE-C is invalid"), ExpectedErr: Errorf("The internal sealed key for SSE-C is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, SSECSealedKey: "", MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, MetaSealedKeySSEC: "",
}, },
SealedKey: SealedKey{}, SealedKey: SealedKey{},
}, // 5 }, // 5
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
SSECSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(make([]byte, 64)),
}, },
SealedKey: SealedKey{Algorithm: SealAlgorithm}, SealedKey: SealedKey{Algorithm: SealAlgorithm},
}, // 6 }, // 6
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: InsecureSealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: InsecureSealAlgorithm,
SSECSealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)),
}, },
SealedKey: SealedKey{Algorithm: InsecureSealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}}, SealedKey: SealedKey{Algorithm: InsecureSealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
}, // 7 }, // 7
@@ -267,8 +267,8 @@ var ssecParseMetadataTests = []struct {
func TestCreateMultipartMetadata(t *testing.T) { func TestCreateMultipartMetadata(t *testing.T) {
metadata := CreateMultipartMetadata(nil) metadata := CreateMultipartMetadata(nil)
if v, ok := metadata[SSEMultipart]; !ok || v != "" { if v, ok := metadata[MetaMultipart]; !ok || v != "" {
t.Errorf("Metadata is missing the correct value for '%s': got '%s' - want '%s'", SSEMultipart, v, "") t.Errorf("Metadata is missing the correct value for '%s': got '%s' - want '%s'", MetaMultipart, v, "")
} }
} }
@@ -411,20 +411,20 @@ var removeInternalEntriesTests = []struct {
}{ }{
{ // 0 { // 0
Metadata: map[string]string{ Metadata: map[string]string{
SSEMultipart: "", MetaMultipart: "",
SSEIV: "", MetaIV: "",
SSESealAlgorithm: "", MetaAlgorithm: "",
SSECSealedKey: "", MetaSealedKeySSEC: "",
S3SealedKey: "", MetaSealedKeyS3: "",
S3KMSKeyID: "", MetaKeyID: "",
S3KMSSealedKey: "", MetaDataEncryptionKey: "",
}, },
Expected: map[string]string{}, Expected: map[string]string{},
}, },
{ // 1 { // 1
Metadata: map[string]string{ Metadata: map[string]string{
SSEMultipart: "", MetaMultipart: "",
SSEIV: "", MetaIV: "",
"X-Amz-Meta-A": "X", "X-Amz-Meta-A": "X",
"X-Minio-Internal-B": "Y", "X-Minio-Internal-B": "Y",
}, },
+157
View File
@@ -0,0 +1,157 @@
/*
* Minio Cloud Storage, (C) 2019-2020 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 crypto
import (
"bytes"
"context"
"crypto/md5"
"encoding/base64"
"net/http"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
)
type ssec struct{}
var (
// SSEC represents AWS SSE-C. It provides functionality to handle
// SSE-C requests.
SSEC = ssec{}
_ Type = SSEC
)
// String returns the SSE domain as string. For SSE-C the
// domain is "SSE-C".
func (ssec) String() string { return "SSE-C" }
// IsRequested returns true if the HTTP headers contains
// at least one SSE-C header. SSE-C copy headers are ignored.
func (ssec) IsRequested(h http.Header) bool {
if _, ok := h[xhttp.AmzServerSideEncryptionCustomerAlgorithm]; ok {
return true
}
if _, ok := h[xhttp.AmzServerSideEncryptionCustomerKey]; ok {
return true
}
if _, ok := h[xhttp.AmzServerSideEncryptionCustomerKeyMD5]; ok {
return true
}
return false
}
// IsEncrypted returns true if the metadata contains an SSE-C
// entry inidicating that the object has been encrypted using
// SSE-C.
func (ssec) IsEncrypted(metadata map[string]string) bool {
if _, ok := metadata[MetaSealedKeySSEC]; ok {
return true
}
return false
}
// ParseHTTP parses the SSE-C headers and returns the SSE-C client key
// on success. SSE-C copy headers are ignored.
func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) {
if h.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm) != xhttp.AmzEncryptionAES {
return key, ErrInvalidCustomerAlgorithm
}
if h.Get(xhttp.AmzServerSideEncryptionCustomerKey) == "" {
return key, ErrMissingCustomerKey
}
if h.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5) == "" {
return key, ErrMissingCustomerKeyMD5
}
clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCustomerKey))
if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
return key, ErrInvalidCustomerKey
}
keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
return key, ErrCustomerKeyMD5Mismatch
}
copy(key[:], clientKey)
return key, nil
}
// UnsealObjectKey extracts and decrypts the sealed object key
// from the metadata using the SSE-C client key of the HTTP headers
// and returns the decrypted object key.
func (s3 ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
clientKey, err := s3.ParseHTTP(h)
if err != nil {
return
}
return unsealObjectKey(clientKey, metadata, bucket, object)
}
// CreateMetadata encodes the sealed key into the metadata
// and returns the modified metadata. It allocates a new
// metadata map if metadata is nil.
func (ssec) CreateMetadata(metadata map[string]string, sealedKey SealedKey) map[string]string {
if sealedKey.Algorithm != SealAlgorithm {
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-C", sealedKey.Algorithm))
}
if metadata == nil {
metadata = make(map[string]string, 3)
}
metadata[MetaAlgorithm] = SealAlgorithm
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[MetaSealedKeySSEC] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
return metadata
}
// ParseMetadata extracts all SSE-C related values from the object metadata
// and checks whether they are well-formed. It returns the sealed object key
// on success.
func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err error) {
// Extract all required values from object metadata
b64IV, ok := metadata[MetaIV]
if !ok {
return sealedKey, errMissingInternalIV
}
algorithm, ok := metadata[MetaAlgorithm]
if !ok {
return sealedKey, errMissingInternalSealAlgorithm
}
b64SealedKey, ok := metadata[MetaSealedKeySSEC]
if !ok {
return sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-C")
}
// Check whether all extracted values are well-formed
iv, err := base64.StdEncoding.DecodeString(b64IV)
if err != nil || len(iv) != 32 {
return sealedKey, errInvalidInternalIV
}
if algorithm != SealAlgorithm && algorithm != InsecureSealAlgorithm {
return sealedKey, errInvalidInternalSealAlgorithm
}
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
if err != nil || len(encryptedKey) != 64 {
return sealedKey, Errorf("The internal sealed key for SSE-C is invalid")
}
sealedKey.Algorithm = algorithm
copy(sealedKey.IV[:], iv)
copy(sealedKey.Key[:], encryptedKey)
return sealedKey, nil
}
+201
View File
@@ -0,0 +1,201 @@
/*
* Minio Cloud Storage, (C) 2019-2020 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 crypto
import (
"context"
"encoding/base64"
"errors"
"net/http"
"path"
"strings"
jsoniter "github.com/json-iterator/go"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
)
type ssekms struct{}
var (
// S3KMS represents AWS SSE-KMS. It provides functionality to
// handle SSE-KMS requests.
S3KMS = ssekms{}
_ Type = S3KMS
)
// String returns the SSE domain as string. For SSE-KMS the
// domain is "SSE-KMS".
func (ssekms) String() string { return "SSE-KMS" }
// IsRequested returns true if the HTTP headers contains
// at least one SSE-KMS header.
func (ssekms) IsRequested(h http.Header) bool {
if _, ok := h[xhttp.AmzServerSideEncryptionKmsID]; ok {
return true
}
if _, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok {
return true
}
if _, ok := h[xhttp.AmzServerSideEncryption]; ok {
return strings.ToUpper(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionAES // Return only true if the SSE header is specified and does not contain the SSE-S3 value
}
return false
}
// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID
// and the KMS context on success.
func (ssekms) ParseHTTP(h http.Header) (string, Context, error) {
algorithm := h.Get(xhttp.AmzServerSideEncryption)
if algorithm != xhttp.AmzEncryptionKMS {
return "", nil, ErrInvalidEncryptionMethod
}
var ctx Context
if context, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err := json.Unmarshal([]byte(context[0]), &ctx); err != nil {
return "", nil, err
}
}
return h.Get(xhttp.AmzServerSideEncryptionKmsID), ctx, nil
}
// IsEncrypted returns true if the object metadata indicates
// that the object was uploaded using SSE-KMS.
func (ssekms) IsEncrypted(metadata map[string]string) bool {
if _, ok := metadata[MetaSealedKeyKMS]; ok {
return true
}
if _, ok := metadata[MetaKeyID]; ok {
return true
}
if _, ok := metadata[MetaDataEncryptionKey]; ok {
return true
}
return false
}
// UnsealObjectKey extracts and decrypts the sealed object key
// from the metadata using KMS and returns the decrypted object
// key.
func (s3 ssekms) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata)
if err != nil {
return key, err
}
unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
if err != nil {
return key, err
}
err = key.Unseal(unsealKey, sealedKey, s3.String(), bucket, object)
return key, err
}
// CreateMetadata encodes the sealed object key into the metadata and returns
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
// both into the metadata as well. It allocates a new metadata map if metadata
// is nil.
func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
if sealedKey.Algorithm != SealAlgorithm {
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
}
// There are two possibilites:
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the caller has passed an invalid argument combination.
if keyID == "" && len(kmsKey) != 0 {
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
}
if keyID != "" && len(kmsKey) == 0 {
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
}
if metadata == nil {
metadata = make(map[string]string, 5)
}
metadata[MetaAlgorithm] = sealedKey.Algorithm
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
metadata[MetaKeyID] = keyID
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)
}
return metadata
}
// ParseMetadata extracts all SSE-KMS related values from the object metadata
// and checks whether they are well-formed. It returns the sealed object key
// on success. If the metadata contains both, a KMS master key ID and a sealed
// KMS data key it returns both. If the metadata does not contain neither a
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
// KMS data key. Otherwise, it returns an error.
func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
// Extract all required values from object metadata
b64IV, ok := metadata[MetaIV]
if !ok {
return keyID, kmsKey, sealedKey, errMissingInternalIV
}
algorithm, ok := metadata[MetaAlgorithm]
if !ok {
return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
}
b64SealedKey, ok := metadata[MetaSealedKeyKMS]
if !ok {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
}
// There are two possibilites:
// - We use a KMS -> There must be a key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the metadata is corrupted.
keyID, idPresent := metadata[MetaKeyID]
b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey]
if !idPresent && kmsKeyPresent {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
}
if idPresent && !kmsKeyPresent {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
}
// Check whether all extracted values are well-formed
iv, err := base64.StdEncoding.DecodeString(b64IV)
if err != nil || len(iv) != 32 {
return keyID, kmsKey, sealedKey, errInvalidInternalIV
}
if algorithm != SealAlgorithm {
return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
}
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
if err != nil || len(encryptedKey) != 64 {
return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
}
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
if err != nil {
return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
}
}
sealedKey.Algorithm = algorithm
copy(sealedKey.IV[:], iv)
copy(sealedKey.Key[:], encryptedKey)
return keyID, kmsKey, sealedKey, nil
}
+181
View File
@@ -0,0 +1,181 @@
/*
* Minio Cloud Storage, (C) 2019-2020 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 crypto
import (
"context"
"encoding/base64"
"errors"
"net/http"
"path"
"strings"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
)
type sses3 struct{}
var (
// S3 represents AWS SSE-S3. It provides functionality to handle
// SSE-S3 requests.
S3 = sses3{}
_ Type = S3
)
// String returns the SSE domain as string. For SSE-S3 the
// domain is "SSE-S3".
func (sses3) String() string { return "SSE-S3" }
func (sses3) IsRequested(h http.Header) bool {
_, ok := h[xhttp.AmzServerSideEncryption]
return ok && strings.ToLower(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value
}
// ParseHTTP parses the SSE-S3 related HTTP headers and checks
// whether they contain valid values.
func (sses3) ParseHTTP(h http.Header) error {
if h.Get(xhttp.AmzServerSideEncryption) != xhttp.AmzEncryptionAES {
return ErrInvalidEncryptionMethod
}
return nil
}
// IsEncrypted returns true if the object metadata indicates
// that the object was uploaded using SSE-S3.
func (sses3) IsEncrypted(metadata map[string]string) bool {
if _, ok := metadata[MetaSealedKeyS3]; ok {
return true
}
if _, ok := metadata[MetaKeyID]; ok {
return true
}
if _, ok := metadata[MetaDataEncryptionKey]; ok {
return true
}
return false
}
// UnsealObjectKey extracts and decrypts the sealed object key
// from the metadata using KMS and returns the decrypted object
// key.
func (s3 sses3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata)
if err != nil {
return key, err
}
unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
if err != nil {
return key, err
}
err = key.Unseal(unsealKey, sealedKey, s3.String(), bucket, object)
return key, err
}
// CreateMetadata encodes the sealed object key into the metadata and returns
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
// both into the metadata as well. It allocates a new metadata map if metadata
// is nil.
func (sses3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
if sealedKey.Algorithm != SealAlgorithm {
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
}
// There are two possibilites:
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the caller has passed an invalid argument combination.
if keyID == "" && len(kmsKey) != 0 {
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
}
if keyID != "" && len(kmsKey) == 0 {
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
}
if metadata == nil {
metadata = make(map[string]string, 5)
}
metadata[MetaAlgorithm] = sealedKey.Algorithm
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[MetaSealedKeyS3] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
metadata[MetaKeyID] = keyID
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)
}
return metadata
}
// ParseMetadata extracts all SSE-S3 related values from the object metadata
// and checks whether they are well-formed. It returns the sealed object key
// on success. If the metadata contains both, a KMS master key ID and a sealed
// KMS data key it returns both. If the metadata does not contain neither a
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
// KMS data key. Otherwise, it returns an error.
func (sses3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) {
// Extract all required values from object metadata
b64IV, ok := metadata[MetaIV]
if !ok {
return keyID, kmsKey, sealedKey, errMissingInternalIV
}
algorithm, ok := metadata[MetaAlgorithm]
if !ok {
return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm
}
b64SealedKey, ok := metadata[MetaSealedKeyS3]
if !ok {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3")
}
// There are two possibilites:
// - We use a KMS -> There must be a key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the metadata is corrupted.
keyID, idPresent := metadata[MetaKeyID]
b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey]
if !idPresent && kmsKeyPresent {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3")
}
if idPresent && !kmsKeyPresent {
return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3")
}
// Check whether all extracted values are well-formed
iv, err := base64.StdEncoding.DecodeString(b64IV)
if err != nil || len(iv) != 32 {
return keyID, kmsKey, sealedKey, errInvalidInternalIV
}
if algorithm != SealAlgorithm {
return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm
}
encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey)
if err != nil || len(encryptedKey) != 64 {
return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid")
}
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey)
if err != nil {
return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid")
}
}
sealedKey.Algorithm = algorithm
copy(sealedKey.IV[:], iv)
copy(sealedKey.Key[:], encryptedKey)
return keyID, kmsKey, sealedKey, nil
}
+25 -59
View File
@@ -17,44 +17,15 @@ package crypto
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"path"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/ioutil" "github.com/minio/minio/pkg/ioutil"
"github.com/minio/sio" "github.com/minio/sio"
) )
const (
// SSEMultipart is the metadata key indicating that the object
// was uploaded using the S3 multipart API and stored using
// some from of server-side-encryption.
SSEMultipart = "X-Minio-Internal-Encrypted-Multipart"
// SSEIV is the metadata key referencing the random initialization
// vector (IV) used for SSE-S3 and SSE-C key derivation.
SSEIV = "X-Minio-Internal-Server-Side-Encryption-Iv"
// SSESealAlgorithm is the metadata key referencing the algorithm
// used by SSE-C and SSE-S3 to encrypt the object.
SSESealAlgorithm = "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm"
// SSECSealedKey is the metadata key referencing the sealed object-key for SSE-C.
SSECSealedKey = "X-Minio-Internal-Server-Side-Encryption-Sealed-Key"
// S3SealedKey is the metadata key referencing the sealed object-key for SSE-S3.
S3SealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key"
// S3KMSKeyID is the metadata key referencing the KMS key-id used to
// generate/decrypt the S3-KMS-Sealed-Key. It is only used for SSE-S3 + KMS.
S3KMSKeyID = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id"
// S3KMSSealedKey is the metadata key referencing the encrypted key generated
// by KMS. It is only used for SSE-S3 + KMS.
S3KMSSealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
)
const ( const (
// SealAlgorithm is the encryption/sealing algorithm used to derive & seal // SealAlgorithm is the encryption/sealing algorithm used to derive & seal
// the key-encryption-key and to en/decrypt the object data. // the key-encryption-key and to en/decrypt the object data.
@@ -67,39 +38,34 @@ const (
InsecureSealAlgorithm = "DARE-SHA256" InsecureSealAlgorithm = "DARE-SHA256"
) )
// String returns the SSE domain as string. For SSE-S3 the // Type represents an AWS SSE type:
// domain is "SSE-S3". // • SSE-C
func (s3) String() string { return "SSE-S3" } // • SSE-S3
// • SSE-KMS
type Type interface {
fmt.Stringer
// UnsealObjectKey extracts and decrypts the sealed object key IsRequested(http.Header) bool
// from the metadata using KMS and returns the decrypted object
// key. IsEncrypted(map[string]string) bool
func (sse s3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) {
keyID, kmsKey, sealedKey, err := sse.ParseMetadata(metadata)
if err != nil {
return
}
unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)})
if err != nil {
return
}
err = key.Unseal(unsealKey, sealedKey, sse.String(), bucket, object)
return
} }
// String returns the SSE domain as string. For SSE-C the // IsRequested returns true and the SSE Type if the HTTP headers
// domain is "SSE-C". // indicate that some form server-side encryption is requested.
func (ssec) String() string { return "SSE-C" } //
// If no SSE headers are present then IsRequested returns false
// UnsealObjectKey extracts and decrypts the sealed object key // and no Type.
// from the metadata using the SSE-C client key of the HTTP headers func IsRequested(h http.Header) (Type, bool) {
// and returns the decrypted object key. switch {
func (sse ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { case S3.IsRequested(h):
clientKey, err := sse.ParseHTTP(h) return S3, true
if err != nil { case S3KMS.IsRequested(h):
return return S3KMS, true
case SSEC.IsRequested(h):
return SSEC, true
default:
return nil, false
} }
return unsealObjectKey(clientKey, metadata, bucket, object)
} }
// UnsealObjectKey extracts and decrypts the sealed object key // UnsealObjectKey extracts and decrypts the sealed object key
+234 -80
View File
@@ -21,11 +21,12 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"errors" "errors"
"math"
"math/rand" "math/rand"
"os" "os"
"path" "path"
"strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
@@ -34,6 +35,7 @@ import (
"github.com/minio/minio/pkg/bucket/lifecycle" "github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/replication" "github.com/minio/minio/pkg/bucket/replication"
"github.com/minio/minio/pkg/color" "github.com/minio/minio/pkg/color"
"github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
@@ -43,7 +45,6 @@ import (
const ( const (
dataCrawlSleepPerFolder = time.Millisecond // Time to wait between folders. dataCrawlSleepPerFolder = time.Millisecond // Time to wait between folders.
dataCrawlSleepDefMult = 10.0 // Default multiplier for waits between operations.
dataCrawlStartDelay = 5 * time.Minute // Time to wait on startup and between cycles. dataCrawlStartDelay = 5 * time.Minute // Time to wait on startup and between cycles.
dataUsageUpdateDirCycles = 16 // Visit all folders every n cycles. dataUsageUpdateDirCycles = 16 // Visit all folders every n cycles.
@@ -53,8 +54,12 @@ const (
) )
var ( var (
globalHealConfig heal.Config globalHealConfig heal.Config
globalHealConfigMu sync.Mutex
dataCrawlerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second) dataCrawlerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
// Sleeper values are updated when config is loaded.
crawlerSleeper = newDynamicSleeper(10, 10*time.Second)
) )
// initDataCrawler will start the crawler unless disabled. // initDataCrawler will start the crawler unless disabled.
@@ -95,11 +100,21 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
} }
} }
crawlTimer := time.NewTimer(dataCrawlStartDelay)
defer crawlTimer.Stop()
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-time.NewTimer(dataCrawlStartDelay).C: case <-crawlTimer.C:
// Reset the timer for next cycle.
crawlTimer.Reset(dataCrawlStartDelay)
if intDataUpdateTracker.debug {
console.Debugln("starting crawler cycle")
}
// Wait before starting next cycle and wait on startup. // Wait before starting next cycle and wait on startup.
results := make(chan DataUsageInfo, 1) results := make(chan DataUsageInfo, 1)
go storeDataUsageInBackend(ctx, objAPI, results) go storeDataUsageInBackend(ctx, objAPI, results)
@@ -141,7 +156,6 @@ type folderScanner struct {
newCache dataUsageCache newCache dataUsageCache
withFilter *bloomFilter withFilter *bloomFilter
dataUsageCrawlMult float64
dataUsageCrawlDebug bool dataUsageCrawlDebug bool
healFolderInclude uint32 // Include a clean folder one in n cycles. healFolderInclude uint32 // Include a clean folder one in n cycles.
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
@@ -172,11 +186,6 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
return cache, errors.New("internal error: root scan attempted") return cache, errors.New("internal error: root scan attempted")
} }
delayMult, err := strconv.ParseFloat(env.Get(envDataUsageCrawlDelay, "10.0"), 64)
if err != nil {
logger.LogIf(ctx, err)
delayMult = dataCrawlSleepDefMult
}
s := folderScanner{ s := folderScanner{
root: basePath, root: basePath,
getSize: getSize, getSize: getSize,
@@ -184,7 +193,6 @@ func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache,
newCache: dataUsageCache{Info: cache.Info}, newCache: dataUsageCache{Info: cache.Info},
newFolders: nil, newFolders: nil,
existingFolders: nil, existingFolders: nil,
dataUsageCrawlMult: delayMult,
dataUsageCrawlDebug: intDataUpdateTracker.debug, dataUsageCrawlDebug: intDataUpdateTracker.debug,
healFolderInclude: 0, healFolderInclude: 0,
healObjectSelect: 0, healObjectSelect: 0,
@@ -357,16 +365,14 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
// If there are lifecycle rules for the prefix, remove the filter. // If there are lifecycle rules for the prefix, remove the filter.
filter := f.withFilter filter := f.withFilter
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name)
var activeLifeCycle *lifecycle.Lifecycle var activeLifeCycle *lifecycle.Lifecycle
if f.oldCache.Info.lifeCycle != nil { if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) {
_, prefix := path2BucketObjectWithBasePath(f.root, folder.name) if f.dataUsageCrawlDebug {
if f.oldCache.Info.lifeCycle.HasActiveRules(prefix, true) { logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
if f.dataUsageCrawlDebug {
logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
}
activeLifeCycle = f.oldCache.Info.lifeCycle
filter = nil
} }
activeLifeCycle = f.oldCache.Info.lifeCycle
filter = nil
} }
if _, ok := f.oldCache.Cache[thisHash.Key()]; filter != nil && ok { if _, ok := f.oldCache.Cache[thisHash.Key()]; filter != nil && ok {
// If folder isn't in filter and we have data, skip it completely. // If folder isn't in filter and we have data, skip it completely.
@@ -386,7 +392,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
} }
} }
} }
sleepDuration(dataCrawlSleepPerFolder, f.dataUsageCrawlMult) crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
cache := dataUsageEntry{} cache := dataUsageEntry{}
@@ -435,7 +441,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
} }
// Dynamic time delay. // Dynamic time delay.
t := UTCNow() wait := crawlerSleeper.Timer(ctx)
// Get file size, ignore errors. // Get file size, ignore errors.
item := crawlItem{ item := crawlItem{
@@ -446,18 +452,18 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
objectName: path.Base(entName), objectName: path.Base(entName),
debug: f.dataUsageCrawlDebug, debug: f.dataUsageCrawlDebug,
lifeCycle: activeLifeCycle, lifeCycle: activeLifeCycle,
heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv), heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
} }
size, err := f.getSize(item) sizeSummary, err := f.getSize(item)
sleepDuration(time.Since(t), f.dataUsageCrawlMult) wait()
if err == errSkipFile { if err == errSkipFile {
return nil return nil
} }
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
cache.Size += size cache.addSizes(sizeSummary)
cache.Objects++ cache.Objects++
cache.ObjSizes.add(size) cache.ObjSizes.add(sizeSummary.totalSize)
return nil return nil
}) })
@@ -506,8 +512,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
} }
// Dynamic time delay. // Dynamic time delay.
t := UTCNow() wait := crawlerSleeper.Timer(ctx)
resolver.bucket = bucket resolver.bucket = bucket
foundObjs := false foundObjs := false
@@ -535,8 +540,8 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
logger.Info(color.Green("healObjects:")+" got partial, %d agreed, errs: %v", nAgreed, errs) logger.Info(color.Green("healObjects:")+" got partial, %d agreed, errs: %v", nAgreed, errs)
} }
// Sleep and reset. // Sleep and reset.
sleepDuration(time.Since(t), f.dataUsageCrawlMult) wait()
t = UTCNow() wait = crawlerSleeper.Timer(ctx)
entry, ok := entries.resolve(&resolver) entry, ok := entries.resolve(&resolver)
if !ok { if !ok {
for _, err := range errs { for _, err := range errs {
@@ -566,20 +571,24 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
object: entry.name, object: entry.name,
versionID: "", versionID: "",
}, madmin.HealItemObject) }, madmin.HealItemObject)
logger.LogIf(ctx, err) if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
foundObjs = foundObjs || err == nil foundObjs = foundObjs || err == nil
return return
} }
for _, ver := range fiv.Versions { for _, ver := range fiv.Versions {
// Sleep and reset. // Sleep and reset.
sleepDuration(time.Since(t), f.dataUsageCrawlMult) wait()
t = UTCNow() wait = crawlerSleeper.Timer(ctx)
err := bgSeq.queueHealTask(healSource{ err := bgSeq.queueHealTask(healSource{
bucket: bucket, bucket: bucket,
object: fiv.Name, object: fiv.Name,
versionID: ver.VersionID, versionID: ver.VersionID,
}, madmin.HealItemObject) }, madmin.HealItemObject)
logger.LogIf(ctx, err) if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
foundObjs = foundObjs || err == nil foundObjs = foundObjs || err == nil
} }
}, },
@@ -605,6 +614,9 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
// If we have quorum, found directories, but no objects, issue heal to delete the dangling. // If we have quorum, found directories, but no objects, issue heal to delete the dangling.
objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{Recursive: true, Remove: true}, objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{Recursive: true, Remove: true},
func(bucket, object, versionID string) error { func(bucket, object, versionID string) error {
// Wait for each heal as per crawler frequency.
wait()
wait = crawlerSleeper.Timer(ctx)
return bgSeq.queueHealTask(healSource{ return bgSeq.queueHealTask(healSource{
bucket: bucket, bucket: bucket,
object: object, object: object,
@@ -613,7 +625,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
}) })
} }
sleepDuration(time.Since(t), f.dataUsageCrawlMult) wait()
// Add unless healing returned an error. // Add unless healing returned an error.
if foundObjs { if foundObjs {
@@ -651,12 +663,12 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
dirStack = append(dirStack, entName) dirStack = append(dirStack, entName)
err := readDirFn(path.Join(dirStack...), addDir) err := readDirFn(path.Join(dirStack...), addDir)
dirStack = dirStack[:len(dirStack)-1] dirStack = dirStack[:len(dirStack)-1]
sleepDuration(dataCrawlSleepPerFolder, f.dataUsageCrawlMult) crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
return err return err
} }
// Dynamic time delay. // Dynamic time delay.
t := UTCNow() wait := crawlerSleeper.Timer(ctx)
// Get file size, ignore errors. // Get file size, ignore errors.
dirStack = append(dirStack, entName) dirStack = append(dirStack, entName)
@@ -665,16 +677,14 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
bucket, prefix := path2BucketObjectWithBasePath(f.root, fileName) bucket, prefix := path2BucketObjectWithBasePath(f.root, fileName)
var activeLifeCycle *lifecycle.Lifecycle var activeLifeCycle *lifecycle.Lifecycle
if f.oldCache.Info.lifeCycle != nil { if f.oldCache.Info.lifeCycle != nil && f.oldCache.Info.lifeCycle.HasActiveRules(prefix, false) {
if f.oldCache.Info.lifeCycle.HasActiveRules(prefix, false) { if f.dataUsageCrawlDebug {
if f.dataUsageCrawlDebug { logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
logger.Info(color.Green("folder-scanner:")+" Prefix %q has active rules", prefix)
}
activeLifeCycle = f.oldCache.Info.lifeCycle
} }
activeLifeCycle = f.oldCache.Info.lifeCycle
} }
size, err := f.getSize( sizeSummary, err := f.getSize(
crawlItem{ crawlItem{
Path: fileName, Path: fileName,
Typ: typ, Typ: typ,
@@ -683,19 +693,19 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder)
objectName: path.Base(entName), objectName: path.Base(entName),
debug: f.dataUsageCrawlDebug, debug: f.dataUsageCrawlDebug,
lifeCycle: activeLifeCycle, lifeCycle: activeLifeCycle,
heal: hashPath(path.Join(prefix, entName)).mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv), heal: hashPath(path.Join(prefix, entName)).mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
}) })
// Don't sleep for really small amount of time // Wait to throttle IO
sleepDuration(time.Since(t), f.dataUsageCrawlMult) wait()
if err == errSkipFile { if err == errSkipFile {
return nil return nil
} }
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
cache.Size += size cache.addSizes(sizeSummary)
cache.Objects++ cache.Objects++
cache.ObjSizes.add(size) cache.ObjSizes.add(sizeSummary.totalSize)
return nil return nil
} }
err := readDirFn(path.Join(dirStack...), addDir) err := readDirFn(path.Join(dirStack...), addDir)
@@ -718,7 +728,15 @@ type crawlItem struct {
debug bool debug bool
} }
type getSizeFn func(item crawlItem) (int64, error) type sizeSummary struct {
totalSize int64
replicatedSize int64
pendingSize int64
failedSize int64
replicaSize int64
}
type getSizeFn func(item crawlItem) (sizeSummary, error)
// transformMetaDir will transform a directory to prefix/file.ext // transformMetaDir will transform a directory to prefix/file.ext
func (i *crawlItem) transformMetaDir() { func (i *crawlItem) transformMetaDir() {
@@ -750,7 +768,11 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
} }
if i.heal { if i.heal {
if i.debug { if i.debug {
logger.Info(color.Green("applyActions:")+" heal checking: %v/%v v%s", i.bucket, i.objectPath(), meta.oi.VersionID) if meta.oi.VersionID != "" {
logger.Info(color.Green("applyActions:")+" heal checking: %v/%v v%s", i.bucket, i.objectPath(), meta.oi.VersionID)
} else {
logger.Info(color.Green("applyActions:")+" heal checking: %v/%v", i.bucket, i.objectPath())
}
} }
res, err := o.HealObject(ctx, i.bucket, i.objectPath(), meta.oi.VersionID, madmin.HealOpts{Remove: healDeleteDangling}) res, err := o.HealObject(ctx, i.bucket, i.objectPath(), meta.oi.VersionID, madmin.HealOpts{Remove: healDeleteDangling})
if isErrObjectNotFound(err) || isErrVersionNotFound(err) { if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
@@ -763,6 +785,9 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
size = res.ObjectSize size = res.ObjectSize
} }
if i.lifeCycle == nil { if i.lifeCycle == nil {
if i.debug {
logger.Info(color.Green("applyActions:")+" no lifecycle rules to apply: %q", i.objectPath())
}
return size return size
} }
@@ -782,7 +807,11 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
TransitionStatus: meta.oi.TransitionStatus, TransitionStatus: meta.oi.TransitionStatus,
}) })
if i.debug { if i.debug {
logger.Info(color.Green("applyActions:")+" lifecycle: %q (version-id=%s), Initial scan: %v", i.objectPath(), versionID, action) if versionID != "" {
logger.Info(color.Green("applyActions:")+" lifecycle: %q (version-id=%s), Initial scan: %v", i.objectPath(), versionID, action)
} else {
logger.Info(color.Green("applyActions:")+" lifecycle: %q Initial scan: %v", i.objectPath(), action)
}
} }
switch action { switch action {
case lifecycle.DeleteAction, lifecycle.DeleteVersionAction: case lifecycle.DeleteAction, lifecycle.DeleteVersionAction:
@@ -790,6 +819,9 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction: case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
default: default:
// No action. // No action.
if i.debug {
logger.Info(color.Green("applyActions:")+" object not expirable: %q", i.objectPath())
}
return size return size
} }
@@ -830,7 +862,6 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
TransitionStatus: obj.TransitionStatus, TransitionStatus: obj.TransitionStatus,
} }
action = i.lifeCycle.ComputeAction(lcOpts) action = i.lifeCycle.ComputeAction(lcOpts)
if i.debug { if i.debug {
logger.Info(color.Green("applyActions:")+" lifecycle: Secondary scan: %v", action) logger.Info(color.Green("applyActions:")+" lifecycle: Secondary scan: %v", action)
} }
@@ -854,7 +885,11 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
locked := enforceRetentionForDeletion(ctx, obj) locked := enforceRetentionForDeletion(ctx, obj)
if locked { if locked {
if i.debug { if i.debug {
logger.Info(color.Green("applyActions:")+" lifecycle: %s is locked, not deleting", i.objectPath()) if obj.VersionID != "" {
logger.Info(color.Green("applyActions:")+" lifecycle: %s v%s is locked, not deleting", i.objectPath(), obj.VersionID)
} else {
logger.Info(color.Green("applyActions:")+" lifecycle: %s is locked, not deleting", i.objectPath())
}
} }
return size return size
} }
@@ -876,18 +911,21 @@ func (i *crawlItem) applyActions(ctx context.Context, o ObjectLayer, meta action
globalTransitionState.queueTransitionTask(obj) globalTransitionState.queueTransitionTask(obj)
return 0 return 0
} }
if obj.TransitionStatus != "" { if obj.TransitionStatus != "" {
if err := deleteTransitionedObject(ctx, o, i.bucket, i.objectPath(), lcOpts, action, false); err != nil { if err := deleteTransitionedObject(ctx, o, i.bucket, i.objectPath(), lcOpts, action, false); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return size return size
} }
} else { // Notification already sent at *deleteTransitionedObject*, return '0' here.
obj, err = o.DeleteObject(ctx, i.bucket, i.objectPath(), opts) return 0
if err != nil { }
// Assume it is still there.
logger.LogIf(ctx, err) obj, err = o.DeleteObject(ctx, i.bucket, i.objectPath(), opts)
return size if err != nil {
} // Assume it is still there.
logger.LogIf(ctx, err)
return size
} }
eventName := event.ObjectRemovedDelete eventName := event.ObjectRemovedDelete
@@ -910,25 +948,10 @@ func (i *crawlItem) objectPath() string {
return path.Join(i.prefix, i.objectName) return path.Join(i.prefix, i.objectName)
} }
// sleepDuration multiplies the duration d by x
// and sleeps if is more than 100 micro seconds.
// Sleep is limited to max 15 seconds.
func sleepDuration(d time.Duration, x float64) {
const maxWait = 15 * time.Second
const minWait = 100 * time.Microsecond
// Don't sleep for really small amount of time
if d := time.Duration(float64(d) * x); d > minWait {
if d > maxWait {
d = maxWait
}
time.Sleep(d)
}
}
// healReplication will heal a scanned item that has failed replication. // healReplication will heal a scanned item that has failed replication.
func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, meta actionMeta) { func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, meta actionMeta, sizeS *sizeSummary) {
if meta.oi.DeleteMarker || !meta.oi.VersionPurgeStatus.Empty() { if meta.oi.DeleteMarker || !meta.oi.VersionPurgeStatus.Empty() {
//heal delete marker replication failure or versioned delete replication failure // heal delete marker replication failure or versioned delete replication failure
if meta.oi.ReplicationStatus == replication.Pending || if meta.oi.ReplicationStatus == replication.Pending ||
meta.oi.ReplicationStatus == replication.Failed || meta.oi.ReplicationStatus == replication.Failed ||
meta.oi.VersionPurgeStatus == Failed || meta.oi.VersionPurgeStatus == Pending { meta.oi.VersionPurgeStatus == Failed || meta.oi.VersionPurgeStatus == Pending {
@@ -936,9 +959,17 @@ func (i *crawlItem) healReplication(ctx context.Context, o ObjectLayer, meta act
return return
} }
} }
if meta.oi.ReplicationStatus == replication.Pending || switch meta.oi.ReplicationStatus {
meta.oi.ReplicationStatus == replication.Failed { case replication.Pending:
sizeS.pendingSize += meta.oi.Size
globalReplicationState.queueReplicaTask(meta.oi) globalReplicationState.queueReplicaTask(meta.oi)
case replication.Failed:
sizeS.failedSize += meta.oi.Size
globalReplicationState.queueReplicaTask(meta.oi)
case replication.Complete:
sizeS.replicatedSize += meta.oi.Size
case replication.Replica:
sizeS.replicaSize += meta.oi.Size
} }
} }
@@ -967,3 +998,126 @@ func (i *crawlItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, m
}) })
} }
} }
type dynamicSleeper struct {
mu sync.RWMutex
// Sleep factor
factor float64
// maximum sleep cap,
// set to <= 0 to disable.
maxSleep time.Duration
// Don't sleep at all, if time taken is below this value.
// This is to avoid too small costly sleeps.
minSleep time.Duration
// cycle will be closed
cycle chan struct{}
}
// newDynamicSleeper
func newDynamicSleeper(factor float64, maxWait time.Duration) *dynamicSleeper {
return &dynamicSleeper{
factor: factor,
cycle: make(chan struct{}),
maxSleep: maxWait,
minSleep: 100 * time.Microsecond,
}
}
// Timer returns a timer that has started.
// When the returned function is called it will wait.
func (d *dynamicSleeper) Timer(ctx context.Context) func() {
t := time.Now()
return func() {
doneAt := time.Now()
for {
// Grab current values
d.mu.RLock()
minWait, maxWait := d.minSleep, d.maxSleep
factor := d.factor
cycle := d.cycle
d.mu.RUnlock()
elapsed := doneAt.Sub(t)
// Don't sleep for really small amount of time
wantSleep := time.Duration(float64(elapsed) * factor)
if wantSleep <= minWait {
return
}
if maxWait > 0 && wantSleep > maxWait {
wantSleep = maxWait
}
timer := time.NewTimer(wantSleep)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return
case <-timer.C:
return
case <-cycle:
if !timer.Stop() {
// We expired.
<-timer.C
return
}
}
}
}
}
// Sleep sleeps the specified time multiplied by the sleep factor.
// If the factor is updated the sleep will be done again with the new factor.
func (d *dynamicSleeper) Sleep(ctx context.Context, base time.Duration) {
for {
// Grab current values
d.mu.RLock()
minWait, maxWait := d.minSleep, d.maxSleep
factor := d.factor
cycle := d.cycle
d.mu.RUnlock()
// Don't sleep for really small amount of time
wantSleep := time.Duration(float64(base) * factor)
if wantSleep <= minWait {
return
}
if maxWait > 0 && wantSleep > maxWait {
wantSleep = maxWait
}
timer := time.NewTimer(wantSleep)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return
case <-timer.C:
return
case <-cycle:
if !timer.Stop() {
// We expired.
<-timer.C
return
}
}
}
}
// Update the current settings and cycle all waiting.
// Parameters are the same as in the contructor.
func (d *dynamicSleeper) Update(factor float64, maxWait time.Duration) error {
d.mu.Lock()
defer d.mu.Unlock()
if math.Abs(d.factor-factor) < 1e-10 && d.maxSleep == maxWait {
return nil
}
// Update values and cycle waiting.
close(d.cycle)
d.factor = factor
d.maxSleep = maxWait
d.cycle = make(chan struct{})
return nil
}
+1 -1
View File
@@ -79,7 +79,7 @@ func newDataUpdateTracker() *dataUpdateTracker {
Current: dataUpdateFilter{ Current: dataUpdateFilter{
idx: 1, idx: 1,
}, },
debug: env.Get(envDataUsageCrawlDebug, config.EnableOff) == config.EnableOn, debug: env.Get(envDataUsageCrawlDebug, config.EnableOff) == config.EnableOn || serverDebugLog,
input: make(chan string, dataUpdateTrackerQueueSize), input: make(chan string, dataUpdateTrackerQueueSize),
save: make(chan struct{}, 1), save: make(chan struct{}, 1),
saveExited: make(chan struct{}), saveExited: make(chan struct{}),
+67 -35
View File
@@ -46,11 +46,14 @@ type sizeHistogram [dataUsageBucketLen]uint64
//msgp:tuple dataUsageEntry //msgp:tuple dataUsageEntry
type dataUsageEntry struct { type dataUsageEntry struct {
// These fields do no include any children. // These fields do no include any children.
Size int64 Size int64
Objects uint64 ReplicatedSize uint64
ObjSizes sizeHistogram ReplicationPendingSize uint64
ReplicationFailedSize uint64
Children dataUsageHashMap ReplicaSize uint64
Objects uint64
ObjSizes sizeHistogram
Children dataUsageHashMap
} }
// dataUsageCache contains a cache of data usage entries. // dataUsageCache contains a cache of data usage entries.
@@ -76,10 +79,23 @@ type dataUsageCacheInfo struct {
lifeCycle *lifecycle.Lifecycle `msg:"-"` lifeCycle *lifecycle.Lifecycle `msg:"-"`
} }
func (e *dataUsageEntry) addSizes(summary sizeSummary) {
e.Size += summary.totalSize
e.ReplicatedSize += uint64(summary.replicatedSize)
e.ReplicationFailedSize += uint64(summary.failedSize)
e.ReplicationPendingSize += uint64(summary.pendingSize)
e.ReplicaSize += uint64(summary.replicaSize)
}
// merge other data usage entry into this, excluding children. // merge other data usage entry into this, excluding children.
func (e *dataUsageEntry) merge(other dataUsageEntry) { func (e *dataUsageEntry) merge(other dataUsageEntry) {
e.Objects += other.Objects e.Objects += other.Objects
e.Size += other.Size e.Size += other.Size
e.ReplicationPendingSize += other.ReplicationPendingSize
e.ReplicationFailedSize += other.ReplicationFailedSize
e.ReplicatedSize += other.ReplicatedSize
e.ReplicaSize += other.ReplicaSize
for i, v := range other.ObjSizes[:] { for i, v := range other.ObjSizes[:] {
e.ObjSizes[i] += v e.ObjSizes[i] += v
} }
@@ -213,11 +229,15 @@ func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo {
} }
flat := d.flatten(*e) flat := d.flatten(*e)
return DataUsageInfo{ return DataUsageInfo{
LastUpdate: d.Info.LastUpdate, LastUpdate: d.Info.LastUpdate,
ObjectsTotalCount: flat.Objects, ObjectsTotalCount: flat.Objects,
ObjectsTotalSize: uint64(flat.Size), ObjectsTotalSize: uint64(flat.Size),
BucketsCount: uint64(len(e.Children)), ReplicatedSize: flat.ReplicatedSize,
BucketsUsage: d.bucketsUsageInfo(buckets), ReplicationFailedSize: flat.ReplicationFailedSize,
ReplicationPendingSize: flat.ReplicationPendingSize,
ReplicaSize: flat.ReplicaSize,
BucketsCount: uint64(len(e.Children)),
BucketsUsage: d.bucketsUsageInfo(buckets),
} }
} }
@@ -343,9 +363,13 @@ func (d *dataUsageCache) bucketsUsageInfo(buckets []BucketInfo) map[string]Bucke
} }
flat := d.flatten(*e) flat := d.flatten(*e)
dst[bucket.Name] = BucketUsageInfo{ dst[bucket.Name] = BucketUsageInfo{
Size: uint64(flat.Size), Size: uint64(flat.Size),
ObjectsCount: flat.Objects, ObjectsCount: flat.Objects,
ObjectSizesHistogram: flat.ObjSizes.toMap(), ReplicationPendingSize: flat.ReplicationPendingSize,
ReplicatedSize: flat.ReplicatedSize,
ReplicationFailedSize: flat.ReplicationFailedSize,
ReplicaSize: flat.ReplicaSize,
ObjectSizesHistogram: flat.ObjSizes.toMap(),
} }
} }
return dst return dst
@@ -360,9 +384,13 @@ func (d *dataUsageCache) bucketUsageInfo(bucket string) BucketUsageInfo {
} }
flat := d.flatten(*e) flat := d.flatten(*e)
return BucketUsageInfo{ return BucketUsageInfo{
Size: uint64(flat.Size), Size: uint64(flat.Size),
ObjectsCount: flat.Objects, ObjectsCount: flat.Objects,
ObjectSizesHistogram: flat.ObjSizes.toMap(), ReplicationPendingSize: flat.ReplicationPendingSize,
ReplicatedSize: flat.ReplicatedSize,
ReplicationFailedSize: flat.ReplicationFailedSize,
ReplicaSize: flat.ReplicaSize,
ObjectSizesHistogram: flat.ObjSizes.toMap(),
} }
} }
@@ -461,9 +489,12 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name. // save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error { func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string) error {
b := d.serialize() pr, pw := io.Pipe()
size := int64(len(b)) go func() {
r, err := hash.NewReader(bytes.NewReader(b), size, "", "", size, false) pw.CloseWithError(d.serializeTo(pw))
}()
defer pr.Close()
r, err := hash.NewReader(pr, -1, "", "", -1, false)
if err != nil { if err != nil {
return err return err
} }
@@ -482,35 +513,36 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
// dataUsageCacheVer indicates the cache version. // dataUsageCacheVer indicates the cache version.
// Bumping the cache version will drop data from previous versions // Bumping the cache version will drop data from previous versions
// and write new data with the new version. // and write new data with the new version.
const dataUsageCacheVer = 2 const dataUsageCacheVer = 3
// serialize the contents of the cache. // serialize the contents of the cache.
func (d *dataUsageCache) serialize() []byte { func (d *dataUsageCache) serializeTo(dst io.Writer) error {
// Prepend version and compress. // Add version and compress.
dst := make([]byte, 0, d.Msgsize()+1) _, err := dst.Write([]byte{dataUsageCacheVer})
dst = append(dst, dataUsageCacheVer) if err != nil {
buf := bytes.NewBuffer(dst) return err
enc, err := zstd.NewWriter(buf, }
enc, err := zstd.NewWriter(dst,
zstd.WithEncoderLevel(zstd.SpeedFastest), zstd.WithEncoderLevel(zstd.SpeedFastest),
zstd.WithWindowSize(1<<20), zstd.WithWindowSize(1<<20),
zstd.WithEncoderConcurrency(2)) zstd.WithEncoderConcurrency(2))
if err != nil { if err != nil {
logger.LogIf(GlobalContext, err) return err
return nil
} }
mEnc := msgp.NewWriter(enc) mEnc := msgp.NewWriter(enc)
err = d.EncodeMsg(mEnc) err = d.EncodeMsg(mEnc)
if err != nil { if err != nil {
logger.LogIf(GlobalContext, err) return err
return nil }
err = mEnc.Flush()
if err != nil {
return err
} }
mEnc.Flush()
err = enc.Close() err = enc.Close()
if err != nil { if err != nil {
logger.LogIf(GlobalContext, err) return err
return nil
} }
return buf.Bytes() return nil
} }
// deserialize the supplied byte slice into the cache. // deserialize the supplied byte slice into the cache.
@@ -521,7 +553,7 @@ func (d *dataUsageCache) deserialize(r io.Reader) error {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
switch b[0] { switch b[0] {
case 1: case 1, 2:
return errors.New("cache version deprecated (will autoupdate)") return errors.New("cache version deprecated (will autoupdate)")
case dataUsageCacheVer: case dataUsageCacheVer:
default: default:
+73 -9
View File
@@ -492,8 +492,8 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
if zb0001 != 4 { if zb0001 != 8 {
err = msgp.ArrayError{Wanted: 4, Got: zb0001} err = msgp.ArrayError{Wanted: 8, Got: zb0001}
return return
} }
z.Size, err = dc.ReadInt64() z.Size, err = dc.ReadInt64()
@@ -501,6 +501,26 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Size") err = msgp.WrapError(err, "Size")
return return
} }
z.ReplicatedSize, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "ReplicatedSize")
return
}
z.ReplicationPendingSize, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "ReplicationPendingSize")
return
}
z.ReplicationFailedSize, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "ReplicationFailedSize")
return
}
z.ReplicaSize, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "ReplicaSize")
return
}
z.Objects, err = dc.ReadUint64() z.Objects, err = dc.ReadUint64()
if err != nil { if err != nil {
err = msgp.WrapError(err, "Objects") err = msgp.WrapError(err, "Objects")
@@ -533,8 +553,8 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) { func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 4 // array header, size 8
err = en.Append(0x94) err = en.Append(0x98)
if err != nil { if err != nil {
return return
} }
@@ -543,6 +563,26 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Size") err = msgp.WrapError(err, "Size")
return return
} }
err = en.WriteUint64(z.ReplicatedSize)
if err != nil {
err = msgp.WrapError(err, "ReplicatedSize")
return
}
err = en.WriteUint64(z.ReplicationPendingSize)
if err != nil {
err = msgp.WrapError(err, "ReplicationPendingSize")
return
}
err = en.WriteUint64(z.ReplicationFailedSize)
if err != nil {
err = msgp.WrapError(err, "ReplicationFailedSize")
return
}
err = en.WriteUint64(z.ReplicaSize)
if err != nil {
err = msgp.WrapError(err, "ReplicaSize")
return
}
err = en.WriteUint64(z.Objects) err = en.WriteUint64(z.Objects)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Objects") err = msgp.WrapError(err, "Objects")
@@ -571,9 +611,13 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) { func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// array header, size 4 // array header, size 8
o = append(o, 0x94) o = append(o, 0x98)
o = msgp.AppendInt64(o, z.Size) o = msgp.AppendInt64(o, z.Size)
o = msgp.AppendUint64(o, z.ReplicatedSize)
o = msgp.AppendUint64(o, z.ReplicationPendingSize)
o = msgp.AppendUint64(o, z.ReplicationFailedSize)
o = msgp.AppendUint64(o, z.ReplicaSize)
o = msgp.AppendUint64(o, z.Objects) o = msgp.AppendUint64(o, z.Objects)
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen)) o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
for za0001 := range z.ObjSizes { for za0001 := range z.ObjSizes {
@@ -595,8 +639,8 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
if zb0001 != 4 { if zb0001 != 8 {
err = msgp.ArrayError{Wanted: 4, Got: zb0001} err = msgp.ArrayError{Wanted: 8, Got: zb0001}
return return
} }
z.Size, bts, err = msgp.ReadInt64Bytes(bts) z.Size, bts, err = msgp.ReadInt64Bytes(bts)
@@ -604,6 +648,26 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Size") err = msgp.WrapError(err, "Size")
return return
} }
z.ReplicatedSize, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "ReplicatedSize")
return
}
z.ReplicationPendingSize, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "ReplicationPendingSize")
return
}
z.ReplicationFailedSize, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "ReplicationFailedSize")
return
}
z.ReplicaSize, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "ReplicaSize")
return
}
z.Objects, bts, err = msgp.ReadUint64Bytes(bts) z.Objects, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Objects") err = msgp.WrapError(err, "Objects")
@@ -637,7 +701,7 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *dataUsageEntry) Msgsize() (s int) { func (z *dataUsageEntry) Msgsize() (s int) {
s = 1 + msgp.Int64Size + msgp.Uint64Size + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + z.Children.Msgsize() s = 1 + msgp.Int64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.Uint64Size + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + z.Children.Msgsize()
return return
} }
+2 -4
View File
@@ -28,7 +28,6 @@ import (
const ( const (
envDataUsageCrawlConf = "MINIO_DISK_USAGE_CRAWL_ENABLE" envDataUsageCrawlConf = "MINIO_DISK_USAGE_CRAWL_ENABLE"
envDataUsageCrawlDelay = "MINIO_DISK_USAGE_CRAWL_DELAY"
envDataUsageCrawlDebug = "MINIO_DISK_USAGE_CRAWL_DEBUG" envDataUsageCrawlDebug = "MINIO_DISK_USAGE_CRAWL_DEBUG"
dataUsageRoot = SlashSeparator dataUsageRoot = SlashSeparator
@@ -40,8 +39,8 @@ const (
) )
// storeDataUsageInBackend will store all objects sent on the gui channel until closed. // storeDataUsageInBackend will store all objects sent on the gui channel until closed.
func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, gui <-chan DataUsageInfo) { func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) {
for dataUsageInfo := range gui { for dataUsageInfo := range dui {
dataUsageJSON, err := json.Marshal(dataUsageInfo) dataUsageJSON, err := json.Marshal(dataUsageInfo)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
@@ -53,7 +52,6 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, gui <-chan
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
continue continue
} }
_, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageObjName, NewPutObjReader(r, nil, nil), ObjectOptions{}) _, err = objAPI.PutObject(ctx, dataUsageBucket, dataUsageObjName, NewPutObjReader(r, nil, nil), ObjectOptions{})
if !isErrBucketNotFound(err) { if !isErrBucketNotFound(err) {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
+29 -20
View File
@@ -51,15 +51,17 @@ func TestDataUsageUpdate(t *testing.T) {
} }
createUsageTestFiles(t, base, bucket, files) createUsageTestFiles(t, base, bucket, files)
getSize := func(item crawlItem) (i int64, err error) { getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
if item.Typ&os.ModeDir == 0 { if item.Typ&os.ModeDir == 0 {
s, err := os.Stat(item.Path) var s os.FileInfo
s, err = os.Stat(item.Path)
if err != nil { if err != nil {
return 0, err return
} }
return s.Size(), nil sizeS.totalSize = s.Size()
return sizeS, nil
} }
return 0, nil return
} }
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize) got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
@@ -345,15 +347,17 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
} }
createUsageTestFiles(t, base, "", files) createUsageTestFiles(t, base, "", files)
getSize := func(item crawlItem) (i int64, err error) { getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
if item.Typ&os.ModeDir == 0 { if item.Typ&os.ModeDir == 0 {
s, err := os.Stat(item.Path) var s os.FileInfo
s, err = os.Stat(item.Path)
if err != nil { if err != nil {
return 0, err return
} }
return s.Size(), nil sizeS.totalSize = s.Size()
return
} }
return 0, nil return
} }
got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize) got, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize)
if err != nil { if err != nil {
@@ -642,28 +646,33 @@ func TestDataUsageCacheSerialize(t *testing.T) {
} }
createUsageTestFiles(t, base, bucket, files) createUsageTestFiles(t, base, bucket, files)
getSize := func(item crawlItem) (i int64, err error) { getSize := func(item crawlItem) (sizeS sizeSummary, err error) {
if item.Typ&os.ModeDir == 0 { if item.Typ&os.ModeDir == 0 {
s, err := os.Stat(item.Path) var s os.FileInfo
s, err = os.Stat(item.Path)
if err != nil { if err != nil {
return 0, err return
} }
return s.Size(), nil sizeS.totalSize = s.Size()
return
} }
return 0, nil return
} }
want, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize) want, err := crawlDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
var buf bytes.Buffer
b := want.serialize() err = want.serializeTo(&buf)
var got dataUsageCache if err != nil {
err = got.deserialize(bytes.NewBuffer(b)) t.Fatal(err)
}
t.Log("serialized size:", buf.Len(), "bytes")
var got dataUsageCache
err = got.deserialize(&buf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log("serialized size:", len(b), "bytes")
if got.Info.LastUpdate.IsZero() { if got.Info.LastUpdate.IsZero() {
t.Error("lastupdate not set") t.Error("lastupdate not set")
} }
+1 -1
View File
@@ -962,7 +962,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
} }
if globalCacheKMS != nil { if globalCacheKMS != nil {
// clean up internal SSE cache metadata // clean up internal SSE cache metadata
delete(gr.ObjInfo.UserDefined, crypto.SSEHeader) delete(gr.ObjInfo.UserDefined, xhttp.AmzServerSideEncryption)
} }
if !rngInfo.Empty() { if !rngInfo.Empty() {
// overlay Size with actual object size and not the range size // overlay Size with actual object size and not the range size
+4
View File
@@ -23,6 +23,10 @@ import (
// CacheDiskStats represents cache disk statistics // CacheDiskStats represents cache disk statistics
// such as current disk usage and available. // such as current disk usage and available.
type CacheDiskStats struct { type CacheDiskStats struct {
// used cache size
UsageSize uint64
// total cache disk capacity
TotalCapacity uint64
// indicates if usage is high or low, if high value is '1', if low its '0' // indicates if usage is high or low, if high value is '1', if low its '0'
UsageState int32 UsageState int32
// indicates the current usage percentage of this cache disk // indicates the current usage percentage of this cache disk
+4
View File
@@ -781,6 +781,10 @@ func newServerCacheObjects(ctx context.Context, config cache.Config) (CacheObjec
dcache := c.cache[i] dcache := c.cache[i]
cacheDiskStats[i] = CacheDiskStats{} cacheDiskStats[i] = CacheDiskStats{}
if dcache != nil { if dcache != nil {
info, err := getDiskInfo(dcache.dir)
logger.LogIf(ctx, err)
cacheDiskStats[i].UsageSize = info.Used
cacheDiskStats[i].TotalCapacity = info.Total
cacheDiskStats[i].Dir = dcache.stats.Dir cacheDiskStats[i].Dir = dcache.stats.Dir
atomic.StoreInt32(&cacheDiskStats[i].UsageState, atomic.LoadInt32(&dcache.stats.UsageState)) atomic.StoreInt32(&cacheDiskStats[i].UsageState, atomic.LoadInt32(&dcache.stats.UsageState))
atomic.StoreUint64(&cacheDiskStats[i].UsagePercent, atomic.LoadUint64(&dcache.stats.UsagePercent)) atomic.StoreUint64(&cacheDiskStats[i].UsagePercent, atomic.LoadUint64(&dcache.stats.UsagePercent))
+4 -4
View File
@@ -386,13 +386,13 @@ func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, offset,
header: h, header: h,
bucket: bucket, bucket: bucket,
object: object, object: object,
customerKeyHeader: h.Get(crypto.SSECKey), customerKeyHeader: h.Get(xhttp.AmzServerSideEncryptionCustomerKey),
copySource: copySource, copySource: copySource,
metadata: cloneMSS(oi.UserDefined), metadata: cloneMSS(oi.UserDefined),
} }
if w.copySource { if w.copySource {
w.customerKeyHeader = h.Get(crypto.SSECopyKey) w.customerKeyHeader = h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey)
} }
if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil { if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil {
@@ -434,12 +434,12 @@ func (d *DecryptBlocksReader) buildDecrypter(partID int) error {
var err error var err error
if d.copySource { if d.copySource {
if crypto.SSEC.IsEncrypted(d.metadata) { if crypto.SSEC.IsEncrypted(d.metadata) {
d.header.Set(crypto.SSECopyKey, d.customerKeyHeader) d.header.Set(xhttp.AmzServerSideEncryptionCopyCustomerKey, d.customerKeyHeader)
key, err = ParseSSECopyCustomerRequest(d.header, d.metadata) key, err = ParseSSECopyCustomerRequest(d.header, d.metadata)
} }
} else { } else {
if crypto.SSEC.IsEncrypted(d.metadata) { if crypto.SSEC.IsEncrypted(d.metadata) {
d.header.Set(crypto.SSECKey, d.customerKeyHeader) d.header.Set(xhttp.AmzServerSideEncryptionCustomerKey, d.customerKeyHeader)
key, err = ParseSSECustomerHeader(d.header) key, err = ParseSSECustomerHeader(d.header)
} }
} }
+51 -50
View File
@@ -29,6 +29,7 @@ import (
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"github.com/minio/minio-go/v7/pkg/encrypt" "github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/sio" "github.com/minio/sio"
) )
@@ -38,27 +39,27 @@ var encryptRequestTests = []struct {
}{ }{
{ {
header: map[string]string{ header: map[string]string{
crypto.SSECAlgorithm: "AES256", xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
}, },
metadata: map[string]string{}, metadata: map[string]string{},
}, },
{ {
header: map[string]string{ header: map[string]string{
crypto.SSECAlgorithm: "AES256", xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
}, },
metadata: map[string]string{ metadata: map[string]string{
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
}, },
}, },
} }
func TestEncryptRequest(t *testing.T) { func TestEncryptRequest(t *testing.T) {
defer func(flag bool) { globalIsSSL = flag }(globalIsSSL) defer func(flag bool) { globalIsTLS = flag }(globalIsTLS)
globalIsSSL = true globalIsTLS = true
for i, test := range encryptRequestTests { for i, test := range encryptRequestTests {
content := bytes.NewReader(make([]byte, 64)) content := bytes.NewReader(make([]byte, 64))
req := &http.Request{Header: http.Header{}} req := &http.Request{Header: http.Header{}}
@@ -70,13 +71,13 @@ func TestEncryptRequest(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) t.Fatalf("Test %d: Failed to encrypt request: %v", i, err)
} }
if kdf, ok := test.metadata[crypto.SSESealAlgorithm]; !ok { if kdf, ok := test.metadata[crypto.MetaAlgorithm]; !ok {
t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf) t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf)
} }
if iv, ok := test.metadata[crypto.SSEIV]; !ok { if iv, ok := test.metadata[crypto.MetaIV]; !ok {
t.Errorf("Test %d: crypto.SSEIV must be part of metadata: %v", i, iv) t.Errorf("Test %d: crypto.SSEIV must be part of metadata: %v", i, iv)
} }
if mac, ok := test.metadata[crypto.SSECSealedKey]; !ok { if mac, ok := test.metadata[crypto.MetaSealedKeySSEC]; !ok {
t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac) t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac)
} }
} }
@@ -93,33 +94,33 @@ var decryptObjectInfoTests = []struct {
expErr: nil, expErr: nil,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
expErr: nil, expErr: nil,
}, },
{ {
info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
expErr: nil, expErr: nil,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSECSealedKey: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}}, info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaSealedKeySSEC: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}},
request: &http.Request{Header: http.Header{}}, request: &http.Request{Header: http.Header{}},
expErr: errEncryptedObject, expErr: errEncryptedObject,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}}, info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
request: &http.Request{Method: http.MethodGet, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Method: http.MethodGet, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
expErr: errInvalidEncryptionParameters, expErr: errInvalidEncryptionParameters,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}}, info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
request: &http.Request{Method: http.MethodHead, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Method: http.MethodHead, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
expErr: errInvalidEncryptionParameters, expErr: errInvalidEncryptionParameters,
}, },
{ {
info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
expErr: errObjectTampered, expErr: errObjectTampered,
}, },
} }
@@ -221,10 +222,10 @@ func TestGetDecryptedRange_Issue50(t *testing.T) {
Name: "object", Name: "object",
Size: 595160760, Size: 595160760,
UserDefined: map[string]string{ UserDefined: map[string]string{
crypto.SSEMultipart: "", crypto.MetaMultipart: "",
crypto.SSEIV: "HTexa=", crypto.MetaIV: "HTexa=",
crypto.SSESealAlgorithm: "DAREv2-HMAC-SHA256", crypto.MetaAlgorithm: "DAREv2-HMAC-SHA256",
crypto.SSECSealedKey: "IAA8PGAA==", crypto.MetaSealedKeySSEC: "IAA8PGAA==",
ReservedMetadataPrefix + "actual-size": "594870264", ReservedMetadataPrefix + "actual-size": "594870264",
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "166b1545b4c1535294ee0686678bea8c-2", "etag": "166b1545b4c1535294ee0686678bea8c-2",
@@ -276,11 +277,11 @@ func TestGetDecryptedRange(t *testing.T) {
} }
udMap = func(isMulti bool) map[string]string { udMap = func(isMulti bool) map[string]string {
m := map[string]string{ m := map[string]string{
crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm, crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm,
crypto.SSEMultipart: "1", crypto.MetaMultipart: "1",
} }
if !isMulti { if !isMulti {
delete(m, crypto.SSEMultipart) delete(m, crypto.MetaMultipart)
} }
return m return m
} }
@@ -553,56 +554,56 @@ var getDefaultOptsTests = []struct {
encryptionType encrypt.Type encryptionType encrypt.Type
err error err error
}{ }{
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: encrypt.SSEC, encryptionType: encrypt.SSEC,
err: nil}, // 0 err: nil}, // 0
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: true, copySource: true,
metadata: nil, metadata: nil,
encryptionType: "", encryptionType: "",
err: nil}, // 1 err: nil}, // 1
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
crypto.SSECKey: []string{"Mz"}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"Mz"},
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: "", encryptionType: "",
err: crypto.ErrInvalidCustomerKey}, // 2 err: crypto.ErrInvalidCustomerKey}, // 2
{headers: http.Header{crypto.SSEHeader: []string{"AES256"}}, {headers: http.Header{xhttp.AmzServerSideEncryption: []string{"AES256"}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: encrypt.S3, encryptionType: encrypt.S3,
err: nil}, // 3 err: nil}, // 3
{headers: http.Header{}, {headers: http.Header{},
copySource: false, copySource: false,
metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
crypto.S3KMSKeyID: "kms-key", crypto.MetaKeyID: "kms-key",
crypto.S3KMSSealedKey: "m-key"}, crypto.MetaDataEncryptionKey: "m-key"},
encryptionType: encrypt.S3, encryptionType: encrypt.S3,
err: nil}, // 4 err: nil}, // 4
{headers: http.Header{}, {headers: http.Header{},
copySource: true, copySource: true,
metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
crypto.S3KMSKeyID: "kms-key", crypto.MetaKeyID: "kms-key",
crypto.S3KMSSealedKey: "m-key"}, crypto.MetaDataEncryptionKey: "m-key"},
encryptionType: "", encryptionType: "",
err: nil}, // 5 err: nil}, // 5
{headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: true, copySource: true,
metadata: nil, metadata: nil,
encryptionType: encrypt.SSEC, encryptionType: encrypt.SSEC,
err: nil}, // 6 err: nil}, // 6
{headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: "", encryptionType: "",
+26 -15
View File
@@ -18,7 +18,6 @@ package cmd
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@@ -38,7 +37,6 @@ import (
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/rest"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/mountinfo" "github.com/minio/minio/pkg/mountinfo"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
@@ -59,7 +57,7 @@ const (
// See proxyRequest() for details. // See proxyRequest() for details.
type ProxyEndpoint struct { type ProxyEndpoint struct {
Endpoint Endpoint
Transport *http.Transport Transport http.RoundTripper
} }
// Endpoint - any type of endpoint. // Endpoint - any type of endpoint.
@@ -242,6 +240,22 @@ func (l *EndpointServerPools) Add(zeps ZoneEndpoints) error {
return nil return nil
} }
// Localhost - returns the local hostname from list of endpoints
func (l EndpointServerPools) Localhost() string {
for _, ep := range l {
for _, endpoint := range ep.Endpoints {
if endpoint.IsLocal {
u := &url.URL{
Scheme: endpoint.Scheme,
Host: endpoint.Host,
}
return u.String()
}
}
}
return ""
}
// FirstLocal returns true if the first endpoint is local. // FirstLocal returns true if the first endpoint is local.
func (l EndpointServerPools) FirstLocal() bool { func (l EndpointServerPools) FirstLocal() bool {
return l[0].Endpoints[0].IsLocal return l[0].Endpoints[0].IsLocal
@@ -337,6 +351,14 @@ func (endpoints Endpoints) GetString(i int) string {
return endpoints[i].String() return endpoints[i].String()
} }
// GetAllStrings - returns allstring of all endpoints
func (endpoints Endpoints) GetAllStrings() (all []string) {
for _, e := range endpoints {
all = append(all, e.String())
}
return
}
func hostResolveToLocalhost(endpoint Endpoint) bool { func hostResolveToLocalhost(endpoint Endpoint) bool {
hostIPs, err := getHostIP(endpoint.Hostname()) hostIPs, err := getHostIP(endpoint.Hostname())
if err != nil { if err != nil {
@@ -857,20 +879,9 @@ func GetProxyEndpoints(endpointServerPools EndpointServerPools) []ProxyEndpoint
} }
proxyEpSet.Add(host) proxyEpSet.Add(host)
var tlsConfig *tls.Config
if globalIsSSL {
tlsConfig = &tls.Config{
ServerName: endpoint.Hostname(),
RootCAs: globalRootCAs,
}
}
// allow transport to be HTTP/1.1 for proxying.
tr := newCustomHTTPProxyTransport(tlsConfig, rest.DefaultTimeout)()
proxyEps = append(proxyEps, ProxyEndpoint{ proxyEps = append(proxyEps, ProxyEndpoint{
Endpoint: endpoint, Endpoint: endpoint,
Transport: tr, Transport: globalProxyTransport,
}) })
} }
} }
+3 -3
View File
@@ -32,9 +32,9 @@ import (
// Heals a bucket if it doesn't exist on one of the disks, additionally // Heals a bucket if it doesn't exist on one of the disks, additionally
// also heals the missing entries for bucket metadata files // also heals the missing entries for bucket metadata files
// `policy.json, notification.xml, listeners.json`. // `policy.json, notification.xml, listeners.json`.
func (er erasureObjects) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) ( func (er erasureObjects) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (
result madmin.HealResultItem, err error) { result madmin.HealResultItem, err error) {
if !dryRun { if !opts.DryRun {
defer ObjectPathUpdated(bucket) defer ObjectPathUpdated(bucket)
} }
@@ -45,7 +45,7 @@ func (er erasureObjects) HealBucket(ctx context.Context, bucket string, dryRun,
writeQuorum := getWriteQuorum(len(storageDisks)) writeQuorum := getWriteQuorum(len(storageDisks))
// Heal bucket. // Heal bucket.
return healBucket(ctx, storageDisks, storageEndpoints, bucket, writeQuorum, dryRun) return healBucket(ctx, storageDisks, storageEndpoints, bucket, writeQuorum, opts.DryRun)
} }
// Heal bucket - create buckets on disks where it does not exist. // Heal bucket - create buckets on disks where it does not exist.
+4 -1
View File
@@ -130,7 +130,10 @@ func TestHealing(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// This would create the bucket. // This would create the bucket.
_, err = er.HealBucket(ctx, bucket, false, false) _, err = er.HealBucket(ctx, bucket, madmin.HealOpts{
DryRun: false,
Remove: false,
})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+19 -32
View File
@@ -89,27 +89,12 @@ func (er erasureObjects) removeObjectPart(bucket, object, uploadID, dataDir stri
} }
// Clean-up the old multipart uploads. Should be run in a Go routine. // Clean-up the old multipart uploads. Should be run in a Go routine.
func (er erasureObjects) cleanupStaleUploads(ctx context.Context, cleanupInterval, expiry time.Duration) { func (er erasureObjects) cleanupStaleUploads(ctx context.Context, expiry time.Duration) {
ticker := time.NewTicker(cleanupInterval) // run multiple cleanup's local to this server.
defer ticker.Stop() for _, disk := range er.getLoadBalancedLocalDisks() {
if disk != nil {
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
var disk StorageAPI
// run multiple cleanup's local to this server.
for _, d := range er.getLoadBalancedLocalDisks() {
if d != nil {
disk = d
break
}
}
if disk == nil {
continue
}
er.cleanupStaleUploadsOnDisk(ctx, disk, expiry) er.cleanupStaleUploadsOnDisk(ctx, disk, expiry)
return
} }
} }
} }
@@ -382,8 +367,10 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
return pi, toObjectErr(err, bucket, object, uploadID) return pi, toObjectErr(err, bucket, object, uploadID)
} }
storageDisks := er.getDisks()
// Read metadata associated with the object from all disks. // Read metadata associated with the object from all disks.
partsMetadata, errs = readAllFileInfo(ctx, er.getDisks(), minioMetaMultipartBucket, partsMetadata, errs = readAllFileInfo(ctx, storageDisks, minioMetaMultipartBucket,
uploadIDPath, "") uploadIDPath, "")
// get Quorum for this object // get Quorum for this object
@@ -398,7 +385,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
} }
// List all online disks. // List all online disks.
onlineDisks, modTime := listOnlineDisks(er.getDisks(), partsMetadata, errs) onlineDisks, modTime := listOnlineDisks(storageDisks, partsMetadata, errs)
// Pick one from the first valid metadata. // Pick one from the first valid metadata.
fi, err := pickValidFileInfo(ctx, partsMetadata, modTime, writeQuorum) fi, err := pickValidFileInfo(ctx, partsMetadata, modTime, writeQuorum)
@@ -859,21 +846,21 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
} }
// Check if there is any offline disk and add it to the MRF list // Check if there is any offline disk and add it to the MRF list
for i, disk := range onlineDisks { for _, disk := range onlineDisks {
if disk == nil || storageDisks[i] == nil { if disk != nil && disk.IsOnline() {
er.addPartial(bucket, object, fi.VersionID) continue
break
} }
er.addPartial(bucket, object, fi.VersionID)
break
} }
for i := 0; i < len(onlineDisks); i++ { for i := 0; i < len(onlineDisks); i++ {
if onlineDisks[i] == nil { if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
continue // Object info is the same in all disks, so we can pick
// the first meta from online disk
fi = partsMetadata[i]
break
} }
// Object info is the same in all disks, so we can pick
// the first meta from online disk
fi = partsMetadata[i]
break
} }
// Success, return object info. // Success, return object info.
+31 -30
View File
@@ -608,7 +608,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
} }
// Initialize parts metadata // Initialize parts metadata
partsMetadata := make([]FileInfo, len(er.getDisks())) partsMetadata := make([]FileInfo, len(storageDisks))
fi := newFileInfo(object, dataDrives, parityDrives) fi := newFileInfo(object, dataDrives, parityDrives)
@@ -674,11 +674,13 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
return ObjectInfo{}, IncompleteBody{Bucket: bucket, Object: object} return ObjectInfo{}, IncompleteBody{Bucket: bucket, Object: object}
} }
lk := er.NewNSLock(bucket, object) if !opts.NoLock {
if err := lk.GetLock(ctx, globalOperationTimeout); err != nil { lk := er.NewNSLock(bucket, object)
return ObjectInfo{}, err if err := lk.GetLock(ctx, globalOperationTimeout); err != nil {
return ObjectInfo{}, err
}
defer lk.Unlock()
} }
defer lk.Unlock()
for i, w := range writers { for i, w := range writers {
if w == nil { if w == nil {
@@ -727,20 +729,20 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
// Whether a disk was initially or becomes offline // Whether a disk was initially or becomes offline
// during this upload, send it to the MRF list. // during this upload, send it to the MRF list.
for i := 0; i < len(onlineDisks); i++ { for i := 0; i < len(onlineDisks); i++ {
if onlineDisks[i] == nil || storageDisks[i] == nil { if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
er.addPartial(bucket, object, fi.VersionID) continue
break
} }
er.addPartial(bucket, object, fi.VersionID)
break
} }
for i := 0; i < len(onlineDisks); i++ { for i := 0; i < len(onlineDisks); i++ {
if onlineDisks[i] == nil { if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
continue // Object info is the same in all disks, so we can pick
// the first meta from online disk
fi = partsMetadata[i]
break
} }
// Object info is the same in all disks, so we can pick
// the first meta from online disk
fi = partsMetadata[i]
break
} }
return fi.ToObjectInfo(bucket, object), nil return fi.ToObjectInfo(bucket, object), nil
@@ -767,19 +769,18 @@ func (er erasureObjects) deleteObjectVersion(ctx context.Context, bucket, object
// all the disks in parallel, including `xl.meta` associated with the // all the disks in parallel, including `xl.meta` associated with the
// object. // object.
func (er erasureObjects) deleteObject(ctx context.Context, bucket, object string, writeQuorum int) error { func (er erasureObjects) deleteObject(ctx context.Context, bucket, object string, writeQuorum int) error {
var disks []StorageAPI
var err error var err error
defer ObjectPathUpdated(pathJoin(bucket, object)) defer ObjectPathUpdated(pathJoin(bucket, object))
tmpObj := mustGetUUID() tmpObj := mustGetUUID()
disks := er.getDisks()
if bucket == minioMetaTmpBucket { if bucket == minioMetaTmpBucket {
tmpObj = object tmpObj = object
disks = er.getDisks()
} else { } else {
// Rename the current object while requiring write quorum, but also consider // Rename the current object while requiring write quorum, but also consider
// that a non found object in a given disk as a success since it already // that a non found object in a given disk as a success since it already
// confirms that the object doesn't have a part in that disk (already removed) // confirms that the object doesn't have a part in that disk (already removed)
disks, err = rename(ctx, er.getDisks(), bucket, object, minioMetaTmpBucket, tmpObj, true, writeQuorum, disks, err = rename(ctx, disks, bucket, object, minioMetaTmpBucket, tmpObj, true, writeQuorum,
[]error{errFileNotFound}) []error{errFileNotFound})
if err != nil { if err != nil {
return toObjectErr(err, bucket, object) return toObjectErr(err, bucket, object)
@@ -787,7 +788,6 @@ func (er erasureObjects) deleteObject(ctx context.Context, bucket, object string
} }
g := errgroup.WithNErrs(len(disks)) g := errgroup.WithNErrs(len(disks))
for index := range disks { for index := range disks {
index := index index := index
g.Go(func() error { g.Go(func() error {
@@ -924,16 +924,15 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
for _, version := range versions { for _, version := range versions {
// Check if there is any offline disk and add it to the MRF list // Check if there is any offline disk and add it to the MRF list
for _, disk := range storageDisks { for _, disk := range storageDisks {
if disk == nil { if disk != nil && disk.IsOnline() {
// ignore delete markers for quorum // Skip attempted heal on online disks.
if version.Deleted { continue
continue
}
// all other direct versionId references we should
// ensure no dangling file is left over.
er.addPartial(bucket, version.Name, version.VersionID)
break
} }
// all other direct versionId references we should
// ensure no dangling file is left over.
er.addPartial(bucket, version.Name, version.VersionID)
break
} }
} }
@@ -945,6 +944,7 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
// response to the client request. // response to the client request.
func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) { func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
versionFound := true versionFound := true
objInfo = ObjectInfo{VersionID: opts.VersionID} // version id needed in Delete API response.
goi, gerr := er.GetObjectInfo(ctx, bucket, object, opts) goi, gerr := er.GetObjectInfo(ctx, bucket, object, opts)
if gerr != nil && goi.Name == "" { if gerr != nil && goi.Name == "" {
switch gerr.(type) { switch gerr.(type) {
@@ -1043,10 +1043,11 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
} }
for _, disk := range storageDisks { for _, disk := range storageDisks {
if disk == nil { if disk != nil && disk.IsOnline() {
er.addPartial(bucket, object, opts.VersionID) continue
break
} }
er.addPartial(bucket, object, opts.VersionID)
break
} }
return ObjectInfo{ return ObjectInfo{
+76 -65
View File
@@ -23,6 +23,7 @@ import (
"io" "io"
"math/rand" "math/rand"
"net/http" "net/http"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -99,12 +100,18 @@ func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI {
idMap[id] = struct{}{} idMap[id] = struct{}{}
} }
res := make([]StorageAPI, 0, len(idMap)) res := make([]StorageAPI, 0, len(idMap))
for _, ss := range z.serverPools { for _, s := range z.serverPools {
for _, disks := range ss.erasureDisks { s.erasureDisksMu.RLock()
defer s.erasureDisksMu.RUnlock()
for _, disks := range s.erasureDisks {
for _, disk := range disks { for _, disk := range disks {
id, _ := disk.GetDiskID() if disk == OfflineDisk {
if _, ok := idMap[id]; ok { continue
res = append(res, disk) }
if id, _ := disk.GetDiskID(); id != "" {
if _, ok := idMap[id]; ok {
res = append(res, disk)
}
} }
} }
} }
@@ -266,9 +273,24 @@ func (z *erasureServerPools) Shutdown(ctx context.Context) error {
return nil return nil
} }
func (z *erasureServerPools) BackendInfo() (b BackendInfo) {
b.Type = BackendErasure
scParity := globalStorageClass.GetParityForSC(storageclass.STANDARD)
if scParity == 0 {
scParity = z.SetDriveCount() / 2
}
b.StandardSCData = z.SetDriveCount() - scParity
b.StandardSCParity = scParity
rrSCParity := globalStorageClass.GetParityForSC(storageclass.RRS)
b.RRSCData = z.SetDriveCount() - rrSCParity
b.RRSCParity = rrSCParity
return
}
func (z *erasureServerPools) StorageInfo(ctx context.Context, local bool) (StorageInfo, []error) { func (z *erasureServerPools) StorageInfo(ctx context.Context, local bool) (StorageInfo, []error) {
var storageInfo StorageInfo var storageInfo StorageInfo
storageInfo.Backend.Type = BackendErasure
storageInfos := make([]StorageInfo, len(z.serverPools)) storageInfos := make([]StorageInfo, len(z.serverPools))
storageInfosErrs := make([][]error, len(z.serverPools)) storageInfosErrs := make([][]error, len(z.serverPools))
@@ -284,23 +306,11 @@ func (z *erasureServerPools) StorageInfo(ctx context.Context, local bool) (Stora
// Wait for the go routines. // Wait for the go routines.
g.Wait() g.Wait()
storageInfo.Backend = z.BackendInfo()
for _, lstorageInfo := range storageInfos { for _, lstorageInfo := range storageInfos {
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...) storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
} }
scParity := globalStorageClass.GetParityForSC(storageclass.STANDARD)
if scParity == 0 {
scParity = z.SetDriveCount() / 2
}
storageInfo.Backend.StandardSCData = z.SetDriveCount() - scParity
storageInfo.Backend.StandardSCParity = scParity
rrSCParity := globalStorageClass.GetParityForSC(storageclass.RRS)
storageInfo.Backend.RRSCData = z.SetDriveCount() - rrSCParity
storageInfo.Backend.RRSCParity = rrSCParity
var errs []error var errs []error
for i := range z.serverPools { for i := range z.serverPools {
errs = append(errs, storageInfosErrs[i]...) errs = append(errs, storageInfosErrs[i]...)
@@ -316,23 +326,19 @@ func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloom
var mu sync.Mutex var mu sync.Mutex
var results []dataUsageCache var results []dataUsageCache
var firstErr error var firstErr error
var knownBuckets = make(map[string]struct{}) // used to deduplicate buckets.
var allBuckets []BucketInfo allBuckets, err := z.ListBuckets(ctx)
if err != nil {
return err
}
// Crawl latest allBuckets first.
sort.Slice(allBuckets, func(i, j int) bool {
return allBuckets[i].Created.After(allBuckets[j].Created)
})
// Collect for each set in serverPools. // Collect for each set in serverPools.
for _, z := range z.serverPools { for _, z := range z.serverPools {
buckets, err := z.ListBuckets(ctx)
if err != nil {
return err
}
// Add new buckets.
for _, b := range buckets {
if _, ok := knownBuckets[b.Name]; ok {
continue
}
allBuckets = append(allBuckets, b)
knownBuckets[b.Name] = struct{}{}
}
for _, erObj := range z.sets { for _, erObj := range z.sets {
wg.Add(1) wg.Add(1)
results = append(results, dataUsageCache{}) results = append(results, dataUsageCache{})
@@ -349,7 +355,7 @@ func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloom
} }
}() }()
// Start crawler. Blocks until done. // Start crawler. Blocks until done.
err := erObj.crawlAndGetDataUsage(ctx, buckets, bf, updates) err := erObj.crawlAndGetDataUsage(ctx, allBuckets, bf, updates)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
mu.Lock() mu.Lock()
@@ -701,7 +707,7 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
// more objects matching the prefix. // more objects matching the prefix.
ri := logger.GetReqInfo(ctx) ri := logger.GetReqInfo(ctx)
if ri != nil && strings.Contains(ri.UserAgent, `1.0 Veeam/1.0 Backup`) && strings.HasSuffix(prefix, ".blk") { if ri != nil && strings.Contains(ri.UserAgent, `1.0 Veeam/1.0 Backup`) && strings.HasSuffix(prefix, ".blk") {
opts.singleObject = true opts.discardResult = true
opts.Transient = true opts.Transient = true
} }
@@ -709,14 +715,21 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return loi, err return loi, err
} }
loi.Objects, loi.Prefixes = merged.fileInfoVersions(bucket, prefix, delimiter, versionMarker) objects := merged.fileInfoVersions(bucket, prefix, delimiter, versionMarker)
loi.IsTruncated = err == nil && len(loi.Objects) > 0 loi.IsTruncated = err == nil && len(objects) > 0
if maxKeys > 0 && len(loi.Objects) > maxKeys { if maxKeys > 0 && len(objects) > maxKeys {
loi.Objects = loi.Objects[:maxKeys] objects = objects[:maxKeys]
loi.IsTruncated = true loi.IsTruncated = true
} }
for _, obj := range objects {
if obj.IsDir && delimiter != "" {
loi.Prefixes = append(loi.Prefixes, obj.Name)
} else {
loi.Objects = append(loi.Objects, obj)
}
}
if loi.IsTruncated { if loi.IsTruncated {
last := loi.Objects[len(loi.Objects)-1] last := objects[len(objects)-1]
loi.NextMarker = encodeMarker(last.Name, merged.listID) loi.NextMarker = encodeMarker(last.Name, merged.listID)
loi.NextVersionIDMarker = last.VersionID loi.NextVersionIDMarker = last.VersionID
} }
@@ -725,6 +738,7 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) { func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
var loi ListObjectsInfo var loi ListObjectsInfo
merged, err := z.listPath(ctx, listPathOptions{ merged, err := z.listPath(ctx, listPathOptions{
Bucket: bucket, Bucket: bucket,
Prefix: prefix, Prefix: prefix,
@@ -738,11 +752,24 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return loi, err return loi, err
} }
// Default is recursive, if delimiter is set then list non recursive. // Default is recursive, if delimiter is set then list non recursive.
loi.Objects, loi.Prefixes = merged.fileInfos(bucket, prefix, delimiter) objects := merged.fileInfos(bucket, prefix, delimiter)
loi.IsTruncated = err == nil && len(loi.Objects) > 0 loi.IsTruncated = err == nil && len(objects) > 0
if maxKeys > 0 && len(objects) > maxKeys {
objects = objects[:maxKeys]
loi.IsTruncated = true
}
for _, obj := range objects {
if obj.IsDir && delimiter != "" {
loi.Prefixes = append(loi.Prefixes, obj.Name)
} else {
loi.Objects = append(loi.Objects, obj)
}
}
if loi.IsTruncated { if loi.IsTruncated {
loi.NextMarker = encodeMarker(loi.Objects[len(loi.Objects)-1].Name, merged.listID) last := objects[len(objects)-1]
loi.NextMarker = encodeMarker(last.Name, merged.listID)
} }
return loi, nil return loi, nil
} }
@@ -1147,14 +1174,17 @@ func (z *erasureServerPools) HealFormat(ctx context.Context, dryRun bool) (madmi
return r, nil return r, nil
} }
func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error) { func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
var r = madmin.HealResultItem{ var r = madmin.HealResultItem{
Type: madmin.HealItemBucket, Type: madmin.HealItemBucket,
Bucket: bucket, Bucket: bucket,
} }
// Attempt heal on the bucket metadata, ignore any failures
_, _ = z.HealObject(ctx, minioMetaBucket, pathJoin(bucketConfigPrefix, bucket, bucketMetadataFile), "", opts)
for _, zone := range z.serverPools { for _, zone := range z.serverPools {
result, err := zone.HealBucket(ctx, bucket, dryRun, remove) result, err := zone.HealBucket(ctx, bucket, opts)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case BucketNotFound: case BucketNotFound:
@@ -1167,6 +1197,7 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, dryR
r.Before.Drives = append(r.Before.Drives, result.Before.Drives...) r.Before.Drives = append(r.Before.Drives, result.Before.Drives...)
r.After.Drives = append(r.After.Drives, result.After.Drives...) r.After.Drives = append(r.After.Drives, result.After.Drives...)
} }
return r, nil return r, nil
} }
@@ -1336,26 +1367,6 @@ func (z *erasureServerPools) HealObject(ctx context.Context, bucket, object, ver
} }
} }
func (z *erasureServerPools) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
var healBuckets []BucketInfo
for _, zone := range z.serverPools {
bucketsInfo, err := zone.ListBucketsHeal(ctx)
if err != nil {
continue
}
healBuckets = append(healBuckets, bucketsInfo...)
}
for i := range healBuckets {
meta, err := globalBucketMetadataSys.Get(healBuckets[i].Name)
if err == nil {
healBuckets[i].Created = meta.Created
}
}
return healBuckets, nil
}
// GetMetrics - no op // GetMetrics - no op
func (z *erasureServerPools) GetMetrics(ctx context.Context) (*Metrics, error) { func (z *erasureServerPools) GetMetrics(ctx context.Context) (*Metrics, error) {
logger.LogIf(ctx, NotImplemented{}) logger.LogIf(ctx, NotImplemented{})
+117 -69
View File
@@ -30,9 +30,11 @@ import (
"github.com/dchest/siphash" "github.com/dchest/siphash"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bpool" "github.com/minio/minio/pkg/bpool"
"github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/dsync" "github.com/minio/minio/pkg/dsync"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/sync/errgroup" "github.com/minio/minio/pkg/sync/errgroup"
@@ -244,10 +246,13 @@ func (s *erasureSets) connectDisks() {
s.endpointStrings[setIndex*s.setDriveCount+diskIndex] = disk.String() s.endpointStrings[setIndex*s.setDriveCount+diskIndex] = disk.String()
s.erasureDisksMu.Unlock() s.erasureDisksMu.Unlock()
go func(setIndex int) { go func(setIndex int) {
idler := time.NewTimer(100 * time.Millisecond)
defer idler.Stop()
// Send a new disk connect event with a timeout // Send a new disk connect event with a timeout
select { select {
case s.disksConnectEvent <- diskConnectInfo{setIndex: setIndex}: case s.disksConnectEvent <- diskConnectInfo{setIndex: setIndex}:
case <-time.After(100 * time.Millisecond): case <-idler.C:
} }
}(setIndex) }(setIndex)
}(endpoint) }(endpoint)
@@ -266,11 +271,21 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
// Pre-emptively connect the disks if possible. // Pre-emptively connect the disks if possible.
s.connectDisks() s.connectDisks()
monitor := time.NewTimer(monitorInterval)
defer monitor.Stop()
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-time.After(monitorInterval): case <-monitor.C:
// Reset the timer once fired for required interval.
monitor.Reset(monitorInterval)
if serverDebugLog {
console.Debugln("running disk monitoring")
}
s.connectDisks() s.connectDisks()
} }
} }
@@ -278,10 +293,20 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
// GetAllLockers return a list of all lockers for all sets. // GetAllLockers return a list of all lockers for all sets.
func (s *erasureSets) GetAllLockers() []dsync.NetLocker { func (s *erasureSets) GetAllLockers() []dsync.NetLocker {
allLockers := make([]dsync.NetLocker, s.setDriveCount*s.setCount) var allLockers []dsync.NetLocker
for i, lockers := range s.erasureLockers { lockEpSet := set.NewStringSet()
for j, locker := range lockers { for _, lockers := range s.erasureLockers {
allLockers[i*s.setDriveCount+j] = locker for _, locker := range lockers {
if locker == nil || !locker.IsOnline() {
// Skip any offline lockers.
continue
}
if lockEpSet.Contains(locker.String()) {
// Skip duplicate lockers.
continue
}
lockEpSet.Add(locker.String())
allLockers = append(allLockers, locker)
} }
} }
return allLockers return allLockers
@@ -289,15 +314,8 @@ func (s *erasureSets) GetAllLockers() []dsync.NetLocker {
func (s *erasureSets) GetLockers(setIndex int) func() ([]dsync.NetLocker, string) { func (s *erasureSets) GetLockers(setIndex int) func() ([]dsync.NetLocker, string) {
return func() ([]dsync.NetLocker, string) { return func() ([]dsync.NetLocker, string) {
lockers := make([]dsync.NetLocker, s.setDriveCount) lockers := make([]dsync.NetLocker, len(s.erasureLockers[setIndex]))
copy(lockers, s.erasureLockers[setIndex]) 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 return lockers, s.erasureLockOwner
} }
} }
@@ -362,14 +380,24 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
for i := 0; i < setCount; i++ { for i := 0; i < setCount; i++ {
s.erasureDisks[i] = make([]StorageAPI, setDriveCount) s.erasureDisks[i] = make([]StorageAPI, setDriveCount)
s.erasureLockers[i] = make([]dsync.NetLocker, setDriveCount) }
var erasureLockers = map[string]dsync.NetLocker{}
for _, endpoint := range endpoints {
if _, ok := erasureLockers[endpoint.Host]; !ok {
erasureLockers[endpoint.Host] = newLockAPI(endpoint)
}
} }
for i := 0; i < setCount; i++ { for i := 0; i < setCount; i++ {
var lockerEpSet = set.NewStringSet()
for j := 0; j < setDriveCount; j++ { for j := 0; j < setDriveCount; j++ {
endpoint := endpoints[i*setDriveCount+j] endpoint := endpoints[i*setDriveCount+j]
// Rely on endpoints list to initialize, init lockers and available disks. // Only add lockers only one per endpoint and per erasure set.
s.erasureLockers[i][j] = newLockAPI(endpoint) if locker, ok := erasureLockers[endpoint.Host]; ok && !lockerEpSet.Contains(endpoint.Host) {
lockerEpSet.Add(endpoint.Host)
s.erasureLockers[i] = append(s.erasureLockers[i], locker)
}
disk := storageDisks[i*setDriveCount+j] disk := storageDisks[i*setDriveCount+j]
if disk == nil { if disk == nil {
continue continue
@@ -388,18 +416,19 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
// Initialize erasure objects for a given set. // Initialize erasure objects for a given set.
s.sets[i] = &erasureObjects{ s.sets[i] = &erasureObjects{
getDisks: s.GetDisks(i), setDriveCount: setDriveCount,
getLockers: s.GetLockers(i), getDisks: s.GetDisks(i),
getEndpoints: s.GetEndpoints(i), getLockers: s.GetLockers(i),
nsMutex: mutex, getEndpoints: s.GetEndpoints(i),
bp: bp, nsMutex: mutex,
mrfOpCh: make(chan partialOperation, 10000), bp: bp,
mrfOpCh: make(chan partialOperation, 10000),
} }
go s.sets[i].cleanupStaleUploads(ctx,
GlobalStaleUploadsCleanupInterval, GlobalStaleUploadsExpiry)
} }
// start cleanup stale uploads go-routine.
go s.cleanupStaleUploads(ctx, GlobalStaleUploadsCleanupInterval, GlobalStaleUploadsExpiry)
// Start the disk monitoring and connect routine. // Start the disk monitoring and connect routine.
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval) go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
go s.maintainMRFList() go s.maintainMRFList()
@@ -408,6 +437,22 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
return s, nil return s, nil
} }
func (s *erasureSets) cleanupStaleUploads(ctx context.Context, cleanupInterval, expiry time.Duration) {
ticker := time.NewTicker(cleanupInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
for _, set := range s.sets {
set.cleanupStaleUploads(ctx, expiry)
}
}
}
}
// NewNSLock - initialize a new namespace RWLocker instance. // NewNSLock - initialize a new namespace RWLocker instance.
func (s *erasureSets) NewNSLock(bucket string, objects ...string) RWLocker { func (s *erasureSets) NewNSLock(bucket string, objects ...string) RWLocker {
if len(objects) == 1 { if len(objects) == 1 {
@@ -446,8 +491,6 @@ func (s *erasureSets) StorageUsageInfo(ctx context.Context) StorageInfo {
for _, lstorageInfo := range storageInfos { for _, lstorageInfo := range storageInfos {
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...) storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
} }
return storageInfo return storageInfo
@@ -485,8 +528,6 @@ func (s *erasureSets) StorageInfo(ctx context.Context, local bool) (StorageInfo,
for _, lstorageInfo := range storageInfos { for _, lstorageInfo := range storageInfos {
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...) storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
} }
if local { if local {
@@ -683,8 +724,24 @@ func undoDeleteBucketSets(ctx context.Context, bucket string, sets []*erasureObj
// sort here just for simplification. As per design it is assumed // sort here just for simplification. As per design it is assumed
// that all buckets are present on all sets. // that all buckets are present on all sets.
func (s *erasureSets) ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) { func (s *erasureSets) ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) {
// Always lists from the same set signified by the empty string. var listBuckets []BucketInfo
return s.ListBucketsHeal(ctx) var healBuckets = map[string]VolInfo{}
for _, set := range s.sets {
// lists all unique buckets across drives.
if err := listAllBuckets(ctx, set.getDisks(), healBuckets); err != nil {
return nil, err
}
}
for _, v := range healBuckets {
listBuckets = append(listBuckets, BucketInfo(v))
}
sort.Slice(listBuckets, func(i, j int) bool {
return listBuckets[i].Name < listBuckets[j].Name
})
return listBuckets, nil
} }
// --- Object Operations --- // --- Object Operations ---
@@ -1230,7 +1287,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
} }
// HealBucket - heals inconsistent buckets and bucket metadata on all sets. // HealBucket - heals inconsistent buckets and bucket metadata on all sets.
func (s *erasureSets) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (result madmin.HealResultItem, err error) { func (s *erasureSets) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
// Initialize heal result info // Initialize heal result info
result = madmin.HealResultItem{ result = madmin.HealResultItem{
Type: madmin.HealItemBucket, Type: madmin.HealItemBucket,
@@ -1241,7 +1298,7 @@ func (s *erasureSets) HealBucket(ctx context.Context, bucket string, dryRun, rem
for _, s := range s.sets { for _, s := range s.sets {
var healResult madmin.HealResultItem var healResult madmin.HealResultItem
healResult, err = s.HealBucket(ctx, bucket, dryRun, remove) healResult, err = s.HealBucket(ctx, bucket, opts)
if err != nil { if err != nil {
return result, err return result, err
} }
@@ -1263,23 +1320,6 @@ func (s *erasureSets) HealObject(ctx context.Context, bucket, object, versionID
return s.getHashedSet(object).HealObject(ctx, bucket, object, versionID, opts) return s.getHashedSet(object).HealObject(ctx, bucket, object, versionID, opts)
} }
// Lists all buckets which need healing.
func (s *erasureSets) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
var listBuckets []BucketInfo
var healBuckets = map[string]VolInfo{}
for _, set := range s.sets {
// lists all unique buckets across drives.
if err := listAllBuckets(ctx, set.getDisks(), healBuckets); err != nil {
return nil, err
}
}
for _, v := range healBuckets {
listBuckets = append(listBuckets, BucketInfo(v))
}
sort.Sort(byBucketName(listBuckets))
return listBuckets, nil
}
// PutObjectTags - replace or add tags to an existing object // PutObjectTags - replace or add tags to an existing object
func (s *erasureSets) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) error { 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) return s.getHashedSet(object).PutObjectTags(ctx, bucket, object, tags, opts)
@@ -1327,23 +1367,34 @@ func (s *erasureSets) maintainMRFList() {
} }
} }
func toSourceChTimed(t *time.Timer, sourceCh chan healSource, u healSource) {
t.Reset(100 * time.Millisecond)
// No defer, as we don't know which
// case will be selected
select {
case sourceCh <- u:
case <-t.C:
return
}
// We still need to check the return value
// of Stop, because t could have fired
// between the send on sourceCh and this line.
if !t.Stop() {
<-t.C
}
}
// healMRFRoutine monitors new disks connection, sweep the MRF list // healMRFRoutine monitors new disks connection, sweep the MRF list
// to find objects related to the new disk that needs to be healed. // to find objects related to the new disk that needs to be healed.
func (s *erasureSets) healMRFRoutine() { func (s *erasureSets) healMRFRoutine() {
// Wait until background heal state is initialized // Wait until background heal state is initialized
var bgSeq *healSequence bgSeq := mustGetHealSequence(GlobalContext)
for {
if globalBackgroundHealState == nil { idler := time.NewTimer(100 * time.Millisecond)
time.Sleep(time.Second) defer idler.Stop()
continue
}
var ok bool
bgSeq, ok = globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
if ok {
break
}
time.Sleep(time.Second)
}
for e := range s.disksConnectEvent { for e := range s.disksConnectEvent {
// Get the list of objects related the er.set // Get the list of objects related the er.set
@@ -1359,11 +1410,8 @@ func (s *erasureSets) healMRFRoutine() {
// Heal objects // Heal objects
for _, u := range mrfOperations { for _, u := range mrfOperations {
// Send an object to be healed with a timeout // Send an object to background heal
select { toSourceChTimed(idler, bgSeq.sourceCh, u)
case bgSeq.sourceCh <- u:
case <-time.After(100 * time.Millisecond):
}
s.mrfMU.Lock() s.mrfMU.Lock()
delete(s.mrfOperations, u) delete(s.mrfOperations, u)
+55 -51
View File
@@ -48,6 +48,8 @@ type partialOperation struct {
type erasureObjects struct { type erasureObjects struct {
GatewayUnsupported GatewayUnsupported
setDriveCount int
// getDisks returns list of storageAPIs. // getDisks returns list of storageAPIs.
getDisks func() []StorageAPI getDisks func() []StorageAPI
@@ -72,11 +74,6 @@ func (er erasureObjects) NewNSLock(bucket string, objects ...string) RWLocker {
return er.nsMutex.NewNSLock(er.getLockers, bucket, objects...) return er.nsMutex.NewNSLock(er.getLockers, bucket, objects...)
} }
// SetDriveCount returns the current drives per set.
func (er erasureObjects) SetDriveCount() int {
return len(er.getDisks())
}
// Shutdown function for object storage interface. // Shutdown function for object storage interface.
func (er erasureObjects) Shutdown(ctx context.Context) error { func (er erasureObjects) Shutdown(ctx context.Context) error {
// Add any object layer shutdown activities here. // Add any object layer shutdown activities here.
@@ -112,13 +109,12 @@ func diskErrToDriveState(err error) (state string) {
return return
} }
// getDisksInfo - fetch disks info across all other storage API. func getOnlineOfflineDisksStats(disksInfo []madmin.Disk) (onlineDisks, offlineDisks madmin.BackendDisks) {
func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Disk, errs []error, onlineDisks, offlineDisks madmin.BackendDisks) {
disksInfo = make([]madmin.Disk, len(disks))
onlineDisks = make(madmin.BackendDisks) onlineDisks = make(madmin.BackendDisks)
offlineDisks = make(madmin.BackendDisks) offlineDisks = make(madmin.BackendDisks)
for _, ep := range endpoints { for _, disk := range disksInfo {
ep := disk.Endpoint
if _, ok := offlineDisks[ep]; !ok { if _, ok := offlineDisks[ep]; !ok {
offlineDisks[ep] = 0 offlineDisks[ep] = 0
} }
@@ -127,6 +123,47 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
} }
} }
// Wait for the routines.
for _, disk := range disksInfo {
ep := disk.Endpoint
state := disk.State
if state != madmin.DriveStateOk && state != madmin.DriveStateUnformatted {
offlineDisks[ep]++
continue
}
onlineDisks[ep]++
}
rootDiskCount := 0
for _, di := range disksInfo {
if di.RootDisk {
rootDiskCount++
}
}
// Count offline disks as well to ensure consistent
// reportability of offline drives on local setups.
if len(disksInfo) == (rootDiskCount + offlineDisks.Sum()) {
// Success.
return onlineDisks, offlineDisks
}
// Root disk should be considered offline
for i := range disksInfo {
ep := disksInfo[i].Endpoint
if disksInfo[i].RootDisk {
offlineDisks[ep]++
onlineDisks[ep]--
}
}
return onlineDisks, offlineDisks
}
// getDisksInfo - fetch disks info across all other storage API.
func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Disk, errs []error) {
disksInfo = make([]madmin.Disk, len(disks))
g := errgroup.WithNErrs(len(disks)) g := errgroup.WithNErrs(len(disks))
for index := range disks { for index := range disks {
index := index index := index
@@ -160,46 +197,12 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Di
}, index) }, index)
} }
errs = g.Wait() return disksInfo, g.Wait()
// Wait for the routines.
for i, diskInfoErr := range errs {
ep := disksInfo[i].Endpoint
if diskInfoErr != nil && !errors.Is(diskInfoErr, errUnformattedDisk) {
offlineDisks[ep]++
continue
}
onlineDisks[ep]++
}
rootDiskCount := 0
for _, di := range disksInfo {
if di.RootDisk {
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
}
// Root disk should be considered offline
for i := range disksInfo {
ep := disksInfo[i].Endpoint
if disksInfo[i].RootDisk {
offlineDisks[ep]++
onlineDisks[ep]--
}
}
return disksInfo, errs, onlineDisks, offlineDisks
} }
// Get an aggregated storage info across all disks. // Get an aggregated storage info across all disks.
func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []error) { func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []error) {
disksInfo, errs, onlineDisks, offlineDisks := getDisksInfo(disks, endpoints) disksInfo, errs := getDisksInfo(disks, endpoints)
// Sort so that the first element is the smallest. // Sort so that the first element is the smallest.
sort.Sort(byDiskTotal(disksInfo)) sort.Sort(byDiskTotal(disksInfo))
@@ -209,9 +212,6 @@ func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []erro
} }
storageInfo.Backend.Type = BackendErasure storageInfo.Backend.Type = BackendErasure
storageInfo.Backend.OnlineDisks = onlineDisks
storageInfo.Backend.OfflineDisks = offlineDisks
return storageInfo, errs return storageInfo, errs
} }
@@ -254,10 +254,14 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
// Collect disks for healing. // Collect disks for healing.
allDisks := er.getDisks() allDisks := er.getDisks()
allDiskIDs := make([]string, len(allDisks)) allDiskIDs := make([]string, 0, len(allDisks))
for i, disk := range allDisks { for _, disk := range allDisks {
if disk == OfflineDisk {
// its possible that disk is OfflineDisk
continue
}
id, _ := disk.GetDiskID() id, _ := disk.GetDiskID()
allDiskIDs[i] = id allDiskIDs = append(allDiskIDs, id)
} }
// Load bucket totals // Load bucket totals
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
etcd "go.etcd.io/etcd/v3/clientv3" etcd "go.etcd.io/etcd/clientv3"
) )
var errEtcdUnreachable = errors.New("etcd is unreachable, please check your endpoints") var errEtcdUnreachable = errors.New("etcd is unreachable, please check your endpoints")
+20 -18
View File
@@ -200,6 +200,11 @@ func (fs *FSObjects) Shutdown(ctx context.Context) error {
return fsRemoveAll(ctx, pathJoin(fs.fsPath, minioMetaTmpBucket, fs.fsUUID)) return fsRemoveAll(ctx, pathJoin(fs.fsPath, minioMetaTmpBucket, fs.fsUUID))
} }
// BackendInfo - returns backend information
func (fs *FSObjects) BackendInfo() BackendInfo {
return BackendInfo{Type: BackendFS}
}
// StorageInfo - returns underlying storage statistics. // StorageInfo - returns underlying storage statistics.
func (fs *FSObjects) StorageInfo(ctx context.Context, _ bool) (StorageInfo, []error) { func (fs *FSObjects) StorageInfo(ctx context.Context, _ bool) (StorageInfo, []error) {
@@ -317,11 +322,14 @@ func (fs *FSObjects) crawlBucket(ctx context.Context, bucket string, cache dataU
} }
// Load bucket info. // Load bucket info.
cache, err = crawlDataFolder(ctx, fs.fsPath, cache, func(item crawlItem) (int64, error) { cache, err = crawlDataFolder(ctx, fs.fsPath, cache, func(item crawlItem) (sizeSummary, error) {
bucket, object := item.bucket, item.objectPath() bucket, object := item.bucket, item.objectPath()
fsMetaBytes, err := ioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile)) fsMetaBytes, err := ioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
if err != nil && !osIsNotExist(err) { if err != nil && !osIsNotExist(err) {
return 0, errSkipFile if intDataUpdateTracker.debug {
logger.Info(color.Green("crawlBucket:")+" object return unexpected error: %v/%v: %w", item.bucket, item.objectPath(), err)
}
return sizeSummary{}, errSkipFile
} }
fsMeta := newFSMetaV1() fsMeta := newFSMetaV1()
@@ -339,18 +347,19 @@ func (fs *FSObjects) crawlBucket(ctx context.Context, bucket string, cache dataU
// Stat the file. // Stat the file.
fi, fiErr := os.Stat(item.Path) fi, fiErr := os.Stat(item.Path)
if fiErr != nil { if fiErr != nil {
return 0, errSkipFile if intDataUpdateTracker.debug {
logger.Info(color.Green("crawlBucket:")+" object path missing: %v: %w", item.Path, fiErr)
}
return sizeSummary{}, errSkipFile
} }
// We cannot heal in FS mode.
item.heal = false
oi := fsMeta.ToObjectInfo(bucket, object, fi) oi := fsMeta.ToObjectInfo(bucket, object, fi)
sz := item.applyActions(ctx, fs, actionMeta{oi: oi}) sz := item.applyActions(ctx, fs, actionMeta{oi: oi})
if sz >= 0 { if sz >= 0 {
return sz, nil return sizeSummary{totalSize: sz}, nil
} }
return fi.Size(), nil return sizeSummary{totalSize: fi.Size()}, nil
}) })
return cache, err return cache, err
@@ -527,7 +536,9 @@ func (fs *FSObjects) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
} }
// Sort bucket infos by bucket name. // Sort bucket infos by bucket name.
sort.Sort(byBucketName(bucketInfos)) sort.Slice(bucketInfos, func(i, j int) bool {
return bucketInfos[i].Name < bucketInfos[j].Name
})
// Succes. // Succes.
return bucketInfos, nil return bucketInfos, nil
@@ -1528,21 +1539,18 @@ func (fs *FSObjects) DeleteObjectTags(ctx context.Context, bucket, object string
// HealFormat - no-op for fs, Valid only for Erasure. // HealFormat - no-op for fs, Valid only for Erasure.
func (fs *FSObjects) HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error) { func (fs *FSObjects) HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error) {
logger.LogIf(ctx, NotImplemented{})
return madmin.HealResultItem{}, NotImplemented{} return madmin.HealResultItem{}, NotImplemented{}
} }
// HealObject - no-op for fs. Valid only for Erasure. // HealObject - no-op for fs. Valid only for Erasure.
func (fs *FSObjects) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) ( func (fs *FSObjects) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (
res madmin.HealResultItem, err error) { res madmin.HealResultItem, err error) {
logger.LogIf(ctx, NotImplemented{})
return res, NotImplemented{} return res, NotImplemented{}
} }
// HealBucket - no-op for fs, Valid only for Erasure. // HealBucket - no-op for fs, Valid only for Erasure.
func (fs *FSObjects) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, func (fs *FSObjects) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem,
error) { error) {
logger.LogIf(ctx, NotImplemented{})
return madmin.HealResultItem{}, NotImplemented{} return madmin.HealResultItem{}, NotImplemented{}
} }
@@ -1561,12 +1569,6 @@ func (fs *FSObjects) HealObjects(ctx context.Context, bucket, prefix string, opt
return NotImplemented{} return NotImplemented{}
} }
// ListBucketsHeal - list all buckets to be healed. Valid only for Erasure
func (fs *FSObjects) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
logger.LogIf(ctx, NotImplemented{})
return []BucketInfo{}, NotImplemented{}
}
// GetMetrics - no op // GetMetrics - no op
func (fs *FSObjects) GetMetrics(ctx context.Context) (*Metrics, error) { func (fs *FSObjects) GetMetrics(ctx context.Context) (*Metrics, error) {
logger.LogIf(ctx, NotImplemented{}) logger.LogIf(ctx, NotImplemented{})
+2 -2
View File
@@ -179,7 +179,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// Check and load TLS certificates. // Check and load TLS certificates.
var err error var err error
globalPublicCerts, globalTLSCerts, globalIsSSL, err = getTLSConfig() globalPublicCerts, globalTLSCerts, globalIsTLS, err = getTLSConfig()
logger.FatalIf(err, "Invalid TLS certificate file") logger.FatalIf(err, "Invalid TLS certificate file")
// Check and load Root CAs. // Check and load Root CAs.
@@ -211,7 +211,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
if host == "" { if host == "" {
host = sortIPs(localIP4.ToSlice())[0] host = sortIPs(localIP4.ToSlice())[0]
} }
return fmt.Sprintf("%s://%s", getURLScheme(globalIsSSL), net.JoinHostPort(host, globalMinioPort)) return fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(host, globalMinioPort))
}() }()
// Handle gateway specific env // Handle gateway specific env
+1 -1
View File
@@ -44,7 +44,7 @@ func printGatewayStartupMessage(apiEndPoints []string, backendType string) {
// SSL is configured reads certification chain, prints // SSL is configured reads certification chain, prints
// authority and expiry. // authority and expiry.
if color.IsTerminal() && !globalCLIContext.Anonymous { if color.IsTerminal() && !globalCLIContext.Anonymous {
if globalIsSSL { if globalIsTLS {
printCertificateMsg(globalPublicCerts) printCertificateMsg(globalPublicCerts)
} }
} }
+6 -6
View File
@@ -34,6 +34,11 @@ import (
// GatewayUnsupported list of unsupported call stubs for gateway. // GatewayUnsupported list of unsupported call stubs for gateway.
type GatewayUnsupported struct{} type GatewayUnsupported struct{}
// BackendInfo returns the underlying backend information
func (a GatewayUnsupported) BackendInfo() BackendInfo {
return BackendInfo{Type: BackendGateway}
}
// CrawlAndGetDataUsage - crawl is not implemented for gateway // CrawlAndGetDataUsage - crawl is not implemented for gateway
func (a GatewayUnsupported) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter, updates chan<- DataUsageInfo) error { func (a GatewayUnsupported) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter, updates chan<- DataUsageInfo) error {
logger.CriticalIf(ctx, errors.New("not implemented")) logger.CriticalIf(ctx, errors.New("not implemented"))
@@ -166,15 +171,10 @@ func (a GatewayUnsupported) HealFormat(ctx context.Context, dryRun bool) (madmin
} }
// HealBucket - Not implemented stub // HealBucket - Not implemented stub
func (a GatewayUnsupported) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error) { func (a GatewayUnsupported) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
return madmin.HealResultItem{}, NotImplemented{} return madmin.HealResultItem{}, NotImplemented{}
} }
// ListBucketsHeal - Not implemented stub
func (a GatewayUnsupported) ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error) {
return nil, NotImplemented{}
}
// HealObject - Not implemented stub // HealObject - Not implemented stub
func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (h madmin.HealResultItem, e error) { func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object, versionID string, opts madmin.HealOpts) (h madmin.HealResultItem, e error) {
return h, NotImplemented{} return h, NotImplemented{}
+11 -3
View File
@@ -812,7 +812,7 @@ func (a *azureObjects) GetObjectNInfo(ctx context.Context, bucket, object string
pr, pw := io.Pipe() pr, pw := io.Pipe()
go func() { go func() {
err := a.GetObject(ctx, bucket, object, startOffset, length, pw, objInfo.ETag, opts) err := a.GetObject(ctx, bucket, object, startOffset, length, pw, objInfo.InnerETag, opts)
pw.CloseWithError(err) pw.CloseWithError(err)
}() }()
// Setup cleanup function to cause the above go-routine to // Setup cleanup function to cause the above go-routine to
@@ -833,8 +833,13 @@ func (a *azureObjects) GetObject(ctx context.Context, bucket, object string, sta
return azureToObjectError(minio.InvalidRange{}, bucket, object) return azureToObjectError(minio.InvalidRange{}, bucket, object)
} }
accessCond := azblob.BlobAccessConditions{}
if etag != "" {
accessCond.ModifiedAccessConditions.IfMatch = azblob.ETag(etag)
}
blobURL := a.client.NewContainerURL(bucket).NewBlobURL(object) blobURL := a.client.NewContainerURL(bucket).NewBlobURL(object)
blob, err := blobURL.Download(ctx, startOffset, length, azblob.BlobAccessConditions{}, false) blob, err := blobURL.Download(ctx, startOffset, length, accessCond, false)
if err != nil { if err != nil {
return azureToObjectError(err, bucket, object) return azureToObjectError(err, bucket, object)
} }
@@ -855,6 +860,8 @@ func (a *azureObjects) GetObjectInfo(ctx context.Context, bucket, object string,
return objInfo, azureToObjectError(err, bucket, object) return objInfo, azureToObjectError(err, bucket, object)
} }
realETag := string(blob.ETag())
// Populate correct ETag's if possible, this code primarily exists // Populate correct ETag's if possible, this code primarily exists
// because AWS S3 indicates that // because AWS S3 indicates that
// //
@@ -866,7 +873,7 @@ func (a *azureObjects) GetObjectInfo(ctx context.Context, bucket, object string,
// //
// Some applications depend on this behavior refer https://github.com/minio/minio/issues/6550 // Some applications depend on this behavior refer https://github.com/minio/minio/issues/6550
// So we handle it here and make this consistent. // So we handle it here and make this consistent.
etag := minio.ToS3ETag(string(blob.ETag())) etag := minio.ToS3ETag(realETag)
metadata := blob.NewMetadata() metadata := blob.NewMetadata()
contentMD5 := blob.ContentMD5() contentMD5 := blob.ContentMD5()
switch { switch {
@@ -881,6 +888,7 @@ func (a *azureObjects) GetObjectInfo(ctx context.Context, bucket, object string,
Bucket: bucket, Bucket: bucket,
UserDefined: azurePropertiesToS3Meta(metadata, blob.NewHTTPHeaders(), blob.ContentLength()), UserDefined: azurePropertiesToS3Meta(metadata, blob.NewHTTPHeaders(), blob.ContentLength()),
ETag: etag, ETag: etag,
InnerETag: realETag,
ModTime: blob.LastModified(), ModTime: blob.LastModified(),
Name: object, Name: object,
Size: blob.ContentLength(), Size: blob.ContentLength(),
+5
View File
@@ -431,6 +431,11 @@ func (l *s3Objects) GetObject(ctx context.Context, bucket string, key string, st
return minio.ErrorRespToObjectError(err, bucket, key) return minio.ErrorRespToObjectError(err, bucket, key)
} }
} }
if etag != "" {
opts.SetMatchETag(etag)
}
object, _, _, err := l.Client.GetObject(ctx, bucket, key, opts) object, _, _, err := l.Client.GetObject(ctx, bucket, key, opts)
if err != nil { if err != nil {
return minio.ErrorRespToObjectError(err, bucket, key) return minio.ErrorRespToObjectError(err, bucket, key)
+5 -5
View File
@@ -634,7 +634,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
// For browser requests, when federation is setup we need to // For browser requests, when federation is setup we need to
// specifically handle download and upload for browser requests. // specifically handle download and upload for browser requests.
if guessIsBrowserReq(r) && globalDNSConfig != nil && len(globalDomainNames) > 0 { if globalDNSConfig != nil && len(globalDomainNames) > 0 && guessIsBrowserReq(r) {
var bucket, _ string var bucket, _ string
switch r.Method { switch r.Method {
case http.MethodPut: case http.MethodPut:
@@ -665,7 +665,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
} }
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() { if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
r.URL.Scheme = "http" r.URL.Scheme = "http"
if globalIsSSL { if globalIsTLS {
r.URL.Scheme = "https" r.URL.Scheme = "https"
} }
r.URL.Host = getHostFromSrv(sr) r.URL.Host = getHostFromSrv(sr)
@@ -689,7 +689,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
} }
// MakeBucket requests should be handled at current endpoint // MakeBucket requests should be handled at current endpoint
if r.Method == http.MethodPut && bucket != "" && object == "" { if r.Method == http.MethodPut && bucket != "" && object == "" && r.URL.RawQuery == "" {
f.handler.ServeHTTP(w, r) f.handler.ServeHTTP(w, r)
return return
} }
@@ -715,7 +715,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
} }
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() { if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
r.URL.Scheme = "http" r.URL.Scheme = "http"
if globalIsSSL { if globalIsTLS {
r.URL.Scheme = "https" r.URL.Scheme = "https"
} }
r.URL.Host = getHostFromSrv(sr) r.URL.Host = getHostFromSrv(sr)
@@ -798,7 +798,7 @@ type sseTLSHandler struct{ handler http.Handler }
func (h sseTLSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h sseTLSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Deny SSE-C requests if not made over TLS // Deny SSE-C requests if not made over TLS
if !globalIsSSL && (crypto.SSEC.IsRequested(r.Header) || crypto.SSECopy.IsRequested(r.Header)) { if !globalIsTLS && (crypto.SSEC.IsRequested(r.Header) || crypto.SSECopy.IsRequested(r.Header)) {
if r.Method == http.MethodHead { if r.Method == http.MethodHead {
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInsecureSSECustomerRequest)) writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInsecureSSECustomerRequest))
} else { } else {
+11 -10
View File
@@ -24,6 +24,7 @@ import (
"testing" "testing"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
) )
// Tests getRedirectLocation function for all its criteria. // Tests getRedirectLocation function for all its criteria.
@@ -181,15 +182,15 @@ var containsReservedMetadataTests = []struct {
header: http.Header{"X-Minio-Key": []string{"value"}}, header: http.Header{"X-Minio-Key": []string{"value"}},
}, },
{ {
header: http.Header{crypto.SSEIV: []string{"iv"}}, header: http.Header{crypto.MetaIV: []string{"iv"}},
shouldFail: true, shouldFail: true,
}, },
{ {
header: http.Header{crypto.SSESealAlgorithm: []string{crypto.InsecureSealAlgorithm}}, header: http.Header{crypto.MetaAlgorithm: []string{crypto.InsecureSealAlgorithm}},
shouldFail: true, shouldFail: true,
}, },
{ {
header: http.Header{crypto.SSECSealedKey: []string{"mac"}}, header: http.Header{crypto.MetaSealedKeySSEC: []string{"mac"}},
shouldFail: true, shouldFail: true,
}, },
{ {
@@ -217,21 +218,21 @@ var sseTLSHandlerTests = []struct {
Header http.Header Header http.Header
IsTLS, ShouldFail bool IsTLS, ShouldFail bool
}{ }{
{URL: &url.URL{}, Header: http.Header{}, IsTLS: false, ShouldFail: false}, // 0 {URL: &url.URL{}, Header: http.Header{}, IsTLS: false, ShouldFail: false}, // 0
{URL: &url.URL{}, Header: http.Header{crypto.SSECAlgorithm: []string{"AES256"}}, IsTLS: false, ShouldFail: true}, // 1 {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}}, IsTLS: false, ShouldFail: true}, // 1
{URL: &url.URL{}, Header: http.Header{crypto.SSECAlgorithm: []string{"AES256"}}, IsTLS: true, ShouldFail: false}, // 2 {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}}, IsTLS: true, ShouldFail: false}, // 2
{URL: &url.URL{}, Header: http.Header{crypto.SSECKey: []string{""}}, IsTLS: true, ShouldFail: false}, // 3 {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerKey: []string{""}}, IsTLS: true, ShouldFail: false}, // 3
{URL: &url.URL{}, Header: http.Header{crypto.SSECopyAlgorithm: []string{""}}, IsTLS: false, ShouldFail: true}, // 4 {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{""}}, IsTLS: false, ShouldFail: true}, // 4
} }
func TestSSETLSHandler(t *testing.T) { func TestSSETLSHandler(t *testing.T) {
defer func(isSSL bool) { globalIsSSL = isSSL }(globalIsSSL) // reset globalIsSSL after test defer func(isSSL bool) { globalIsTLS = isSSL }(globalIsTLS) // reset globalIsTLS after test
var okHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { var okHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
for i, test := range sseTLSHandlerTests { for i, test := range sseTLSHandlerTests {
globalIsSSL = test.IsTLS globalIsTLS = test.IsTLS
w := httptest.NewRecorder() w := httptest.NewRecorder()
r := new(http.Request) r := new(http.Request)
+35 -18
View File
@@ -96,41 +96,54 @@ func getLocalBackgroundHealStatus() (madmin.BgHealState, bool) {
}, true }, true
} }
// healErasureSet lists and heals all objects in a specific erasure set func mustGetHealSequence(ctx context.Context) *healSequence {
func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, disks []StorageAPI) error {
// Get background heal sequence to send elements to heal // Get background heal sequence to send elements to heal
var bgSeq *healSequence
var ok bool
for { for {
bgSeq, ok = globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID) globalHealStateLK.RLock()
if ok { hstate := globalBackgroundHealState
break globalHealStateLK.RUnlock()
}
select { if hstate == nil {
case <-ctx.Done(): time.Sleep(time.Second)
return nil
case <-time.After(time.Second):
continue continue
} }
bgSeq, ok := hstate.getHealSequenceByToken(bgHealingUUID)
if !ok {
time.Sleep(time.Second)
continue
}
return bgSeq
} }
}
// healErasureSet lists and heals all objects in a specific erasure set
func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, disks []StorageAPI) error {
bgSeq := mustGetHealSequence(ctx)
buckets = append(buckets, BucketInfo{ buckets = append(buckets, BucketInfo{
Name: pathJoin(minioMetaBucket, minioConfigPrefix), Name: pathJoin(minioMetaBucket, minioConfigPrefix),
}, BucketInfo{ })
Name: pathJoin(minioMetaBucket, bucketConfigPrefix),
}) // add metadata .minio.sys/ bucket prefixes to heal
// Try to pro-actively heal backend-encrypted file. // Try to pro-actively heal backend-encrypted file.
bgSeq.sourceCh <- healSource{ if err := bgSeq.queueHealTask(healSource{
bucket: minioMetaBucket, bucket: minioMetaBucket,
object: backendEncryptedFile, object: backendEncryptedFile,
}, madmin.HealItemMetadata); err != nil {
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
} }
// Heal all buckets with all objects // Heal all buckets with all objects
for _, bucket := range buckets { for _, bucket := range buckets {
// Heal current bucket // Heal current bucket
bgSeq.sourceCh <- healSource{ if err := bgSeq.queueHealTask(healSource{
bucket: bucket.Name, bucket: bucket.Name,
}, madmin.HealItemBucket); err != nil {
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
} }
var entryChs []FileInfoVersionsCh var entryChs []FileInfoVersionsCh
@@ -165,10 +178,14 @@ func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, dis
} }
for _, version := range entry.Versions { for _, version := range entry.Versions {
bgSeq.sourceCh <- healSource{ if err := bgSeq.queueHealTask(healSource{
bucket: bucket.Name, bucket: bucket.Name,
object: version.Name, object: version.Name,
versionID: version.VersionID, versionID: version.VersionID,
}, madmin.HealItemObject); err != nil {
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
} }
} }
} }
+7 -3
View File
@@ -20,6 +20,7 @@ import (
"crypto/x509" "crypto/x509"
"net/http" "net/http"
"os" "os"
"sync"
"time" "time"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
@@ -36,7 +37,7 @@ import (
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
etcd "go.etcd.io/etcd/v3/clientv3" etcd "go.etcd.io/etcd/clientv3"
"github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/certs"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
@@ -170,7 +171,7 @@ var (
globalRootCAs *x509.CertPool globalRootCAs *x509.CertPool
// IsSSL indicates if the server is configured with SSL. // IsSSL indicates if the server is configured with SSL.
globalIsSSL bool globalIsTLS bool
globalTLSCerts *certs.Manager globalTLSCerts *certs.Manager
@@ -245,7 +246,8 @@ var (
globalAutoEncryption bool globalAutoEncryption bool
// Is compression enabled? // Is compression enabled?
globalCompressConfig compress.Config globalCompressConfigMu sync.Mutex
globalCompressConfig compress.Config
// Some standard object extensions which we strictly dis-allow for compression. // Some standard object extensions which we strictly dis-allow for compression.
standardExcludeCompressExtensions = []string{".gz", ".bz2", ".rar", ".zip", ".7z", ".xz", ".mp4", ".mkv", ".mov"} standardExcludeCompressExtensions = []string{".gz", ".bz2", ".rar", ".zip", ".7z", ".xz", ".mp4", ".mkv", ".mov"}
@@ -278,6 +280,8 @@ var (
globalInternodeTransport http.RoundTripper globalInternodeTransport http.RoundTripper
globalProxyTransport http.RoundTripper
globalDNSCache *xhttp.DNSCache globalDNSCache *xhttp.DNSCache
// Add new variable global values here. // Add new variable global values here.
) )
+25 -10
View File
@@ -35,6 +35,7 @@ type apiConfig struct {
listQuorum int listQuorum int
extendListLife time.Duration extendListLife time.Duration
corsAllowOrigins []string corsAllowOrigins []string
setDriveCount int
} }
func (t *apiConfig) init(cfg api.Config, setDriveCount int) { func (t *apiConfig) init(cfg api.Config, setDriveCount int) {
@@ -43,6 +44,7 @@ func (t *apiConfig) init(cfg api.Config, setDriveCount int) {
t.clusterDeadline = cfg.ClusterDeadline t.clusterDeadline = cfg.ClusterDeadline
t.corsAllowOrigins = cfg.CorsAllowOrigin t.corsAllowOrigins = cfg.CorsAllowOrigin
t.setDriveCount = setDriveCount
var apiRequestsMaxPerNode int var apiRequestsMaxPerNode int
if cfg.RequestsMax <= 0 { if cfg.RequestsMax <= 0 {
@@ -62,8 +64,14 @@ func (t *apiConfig) init(cfg api.Config, setDriveCount int) {
apiRequestsMaxPerNode /= len(globalEndpoints.Hostnames()) apiRequestsMaxPerNode /= len(globalEndpoints.Hostnames())
} }
} }
if cap(t.requestsPool) < apiRequestsMaxPerNode {
t.requestsPool = make(chan struct{}, apiRequestsMaxPerNode) // Only replace if needed.
// Existing requests will use the previous limit,
// but new requests will use the new limit.
// There will be a short overlap window,
// but this shouldn't last long.
t.requestsPool = make(chan struct{}, apiRequestsMaxPerNode)
}
t.requestsDeadline = cfg.RequestsDeadline t.requestsDeadline = cfg.RequestsDeadline
t.listQuorum = cfg.GetListQuorum() t.listQuorum = cfg.GetListQuorum()
t.extendListLife = cfg.ExtendListLife t.extendListLife = cfg.ExtendListLife
@@ -76,6 +84,13 @@ func (t *apiConfig) getListQuorum() int {
return t.listQuorum return t.listQuorum
} }
func (t *apiConfig) getSetDriveCount() int {
t.mu.RLock()
defer t.mu.RUnlock()
return t.setDriveCount
}
func (t *apiConfig) getExtendListLife() time.Duration { func (t *apiConfig) getExtendListLife() time.Duration {
t.mu.RLock() t.mu.RLock()
defer t.mu.RUnlock() defer t.mu.RUnlock()
@@ -103,34 +118,34 @@ func (t *apiConfig) getClusterDeadline() time.Duration {
return t.clusterDeadline return t.clusterDeadline
} }
func (t *apiConfig) getRequestsPool() (chan struct{}, <-chan time.Time) { func (t *apiConfig) getRequestsPool() (chan struct{}, time.Duration) {
t.mu.RLock() t.mu.RLock()
defer t.mu.RUnlock() defer t.mu.RUnlock()
if t.requestsPool == nil { if t.requestsPool == nil {
return nil, nil return nil, time.Duration(0)
}
if t.requestsDeadline <= 0 {
return t.requestsPool, nil
} }
return t.requestsPool, time.NewTimer(t.requestsDeadline).C return t.requestsPool, t.requestsDeadline
} }
// maxClients throttles the S3 API calls // maxClients throttles the S3 API calls
func maxClients(f http.HandlerFunc) http.HandlerFunc { func maxClients(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
pool, deadlineTimer := globalAPIConfig.getRequestsPool() pool, deadline := globalAPIConfig.getRequestsPool()
if pool == nil { if pool == nil {
f.ServeHTTP(w, r) f.ServeHTTP(w, r)
return return
} }
deadlineTimer := time.NewTimer(deadline)
defer deadlineTimer.Stop()
select { select {
case pool <- struct{}{}: case pool <- struct{}{}:
defer func() { <-pool }() defer func() { <-pool }()
f.ServeHTTP(w, r) f.ServeHTTP(w, r)
case <-deadlineTimer: case <-deadlineTimer.C:
// Send a http timeout message // Send a http timeout message
writeErrorResponse(r.Context(), w, writeErrorResponse(r.Context(), w,
errorCodes.ToAPIErr(ErrOperationMaxedOut), errorCodes.ToAPIErr(ErrOperationMaxedOut),
+2 -8
View File
@@ -29,7 +29,6 @@ import (
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
"time"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@@ -379,12 +378,7 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
f.ServeHTTP(statsWriter, r) f.ServeHTTP(statsWriter, r)
// Time duration in secs since the call started. globalHTTPStats.updateStats(api, r, statsWriter)
// We don't need to do nanosecond precision in this
// simply for the fact that it is not human readable.
durationSecs := time.Since(statsWriter.StartTime).Seconds()
globalHTTPStats.updateStats(api, r, statsWriter, durationSecs)
} }
} }
@@ -522,7 +516,7 @@ func proxyRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, e
}) })
r.URL.Scheme = "http" r.URL.Scheme = "http"
if globalIsSSL { if globalIsTLS {
r.URL.Scheme = "https" r.URL.Scheme = "https"
} }
+3 -5
View File
@@ -161,7 +161,7 @@ func (st *HTTPStats) toServerHTTPStats() ServerHTTPStats {
} }
// Update statistics from http request and response data // Update statistics from http request and response data
func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.ResponseWriter, durationSecs float64) { func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.ResponseWriter) {
// A successful request has a 2xx response code // A successful request has a 2xx response code
successReq := (w.StatusCode >= 200 && w.StatusCode < 300) successReq := (w.StatusCode >= 200 && w.StatusCode < 300)
@@ -172,10 +172,8 @@ func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.Response
} }
} }
if r.Method == http.MethodGet { // Increment the prometheus http request response histogram with appropriate label
// Increment the prometheus http request response histogram with appropriate label httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(w.TimeToFirstByte.Seconds())
httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(durationSecs)
}
} }
// Prepare new HTTPStats structure // Prepare new HTTPStats structure
+14 -18
View File
@@ -97,8 +97,9 @@ type DNSCache struct {
lookupHostFn func(ctx context.Context, host string) ([]string, error) lookupHostFn func(ctx context.Context, host string) ([]string, error)
lookupTimeout time.Duration lookupTimeout time.Duration
cache map[string][]string cache map[string][]string
closer func() doneOnce sync.Once
doneCh chan struct{}
} }
// NewDNSCache initializes DNS cache resolver and starts auto refreshing // NewDNSCache initializes DNS cache resolver and starts auto refreshing
@@ -113,26 +114,24 @@ func NewDNSCache(freq time.Duration, lookupTimeout time.Duration) *DNSCache {
lookupTimeout = defaultLookupTimeout lookupTimeout = defaultLookupTimeout
} }
ticker := time.NewTicker(freq)
ch := make(chan struct{})
closer := func() {
ticker.Stop()
close(ch)
}
r := &DNSCache{ r := &DNSCache{
lookupHostFn: net.DefaultResolver.LookupHost, lookupHostFn: net.DefaultResolver.LookupHost,
lookupTimeout: lookupTimeout, lookupTimeout: lookupTimeout,
cache: make(map[string][]string, cacheSize), cache: make(map[string][]string, cacheSize),
closer: closer, doneCh: make(chan struct{}),
} }
timer := time.NewTimer(freq)
go func() { go func() {
defer timer.Stop()
for { for {
select { select {
case <-ticker.C: case <-timer.C:
timer.Reset(freq)
r.Refresh() r.Refresh()
case <-ch: case <-r.doneCh:
return return
} }
} }
@@ -188,10 +187,7 @@ func (r *DNSCache) Refresh() {
// Stop stops auto refreshing. // Stop stops auto refreshing.
func (r *DNSCache) Stop() { func (r *DNSCache) Stop() {
r.Lock() r.doneOnce.Do(func() {
defer r.Unlock() close(r.doneCh)
if r.closer != nil { })
r.closer()
r.closer = nil
}
} }
+14
View File
@@ -111,6 +111,20 @@ const (
AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length" AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length"
AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5" AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5"
// AWS server-side encryption headers for SSE-S3, SSE-KMS and SSE-C.
AmzServerSideEncryption = "X-Amz-Server-Side-Encryption"
AmzServerSideEncryptionKmsID = AmzServerSideEncryption + "-Aws-Kms-Key-Id"
AmzServerSideEncryptionKmsContext = AmzServerSideEncryption + "-Context"
AmzServerSideEncryptionCustomerAlgorithm = AmzServerSideEncryption + "-Customer-Algorithm"
AmzServerSideEncryptionCustomerKey = AmzServerSideEncryption + "-Customer-Key"
AmzServerSideEncryptionCustomerKeyMD5 = AmzServerSideEncryption + "-Customer-Key-Md5"
AmzServerSideEncryptionCopyCustomerAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
AmzServerSideEncryptionCopyCustomerKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
AmzServerSideEncryptionCopyCustomerKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5"
AmzEncryptionAES = "AES256"
AmzEncryptionKMS = "aws:kms"
// Signature v2 related constants // Signature v2 related constants
AmzSignatureV2 = "Signature" AmzSignatureV2 = "Signature"
AmzAccessKeyID = "AWSAccessKeyId" AmzAccessKeyID = "AWSAccessKeyId"
+2 -2
View File
@@ -33,8 +33,8 @@ import (
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
etcd "go.etcd.io/etcd/v3/clientv3" etcd "go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/v3/mvcc/mvccpb" "go.etcd.io/etcd/mvcc/mvccpb"
) )
var defaultContextTimeout = 30 * time.Second var defaultContextTimeout = 30 * time.Second
+29 -51
View File
@@ -21,6 +21,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"path"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -82,13 +83,12 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context, isSTS b
basePrefix = iamConfigSTSPrefix basePrefix = iamConfigSTSPrefix
} }
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix, true) { for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
if item.Err != nil { if item.Err != nil {
return item.Err return item.Err
} }
user := item.Item user := path.Dir(item.Item)
{ {
// 1. check if there is policy file in old location. // 1. check if there is policy file in old location.
oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile) oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile)
@@ -250,12 +250,12 @@ func (iamOS *IAMObjectStore) loadPolicyDoc(ctx context.Context, policy string, m
} }
func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]iampolicy.Policy) error { func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]iampolicy.Policy) error {
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPoliciesPrefix, true) { for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPoliciesPrefix) {
if item.Err != nil { if item.Err != nil {
return item.Err return item.Err
} }
policyName := item.Item policyName := path.Dir(item.Item)
if err := iamOS.loadPolicyDoc(ctx, policyName, m); err != nil && err != errNoSuchPolicy { if err := iamOS.loadPolicyDoc(ctx, policyName, m); err != nil && err != errNoSuchPolicy {
return err return err
} }
@@ -319,12 +319,12 @@ func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType
basePrefix = iamConfigUsersPrefix basePrefix = iamConfigUsersPrefix
} }
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix, true) { for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
if item.Err != nil { if item.Err != nil {
return item.Err return item.Err
} }
userName := item.Item userName := path.Dir(item.Item)
if err := iamOS.loadUser(ctx, userName, userType, m); err != nil && err != errNoSuchUser { if err := iamOS.loadUser(ctx, userName, userType, m); err != nil && err != errNoSuchUser {
return err return err
} }
@@ -346,12 +346,12 @@ func (iamOS *IAMObjectStore) loadGroup(ctx context.Context, group string, m map[
} }
func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error { func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error {
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigGroupsPrefix, true) { for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigGroupsPrefix) {
if item.Err != nil { if item.Err != nil {
return item.Err return item.Err
} }
group := item.Item group := path.Dir(item.Item)
if err := iamOS.loadGroup(ctx, group, m); err != nil && err != errNoSuchGroup { if err := iamOS.loadGroup(ctx, group, m); err != nil && err != errNoSuchGroup {
return err return err
} }
@@ -388,7 +388,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IA
basePath = iamConfigPolicyDBUsersPrefix basePath = iamConfigPolicyDBUsersPrefix
} }
} }
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePath, false) { for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePath) {
if item.Err != nil { if item.Err != nil {
return item.Err return item.Err
} }
@@ -566,49 +566,29 @@ type itemOrErr struct {
// prefix. If dirs is true, only directories are listed, otherwise // prefix. If dirs is true, only directories are listed, otherwise
// only objects are listed. All returned items have the pathPrefix // only objects are listed. All returned items have the pathPrefix
// removed from their names. // removed from their names.
func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix string, dirs bool) <-chan itemOrErr { func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix string) <-chan itemOrErr {
ch := make(chan itemOrErr) ch := make(chan itemOrErr)
dirList := func(lo ListObjectsInfo) []string {
return lo.Prefixes
}
filesList := func(lo ListObjectsInfo) (r []string) {
for _, o := range lo.Objects {
r = append(r, o.Name)
}
return r
}
go func() { go func() {
defer close(ch) defer close(ch)
marker := "" // Allocate new results channel to receive ObjectInfo.
for { objInfoCh := make(chan ObjectInfo)
lo, err := objAPI.ListObjects(ctx,
minioMetaBucket, pathPrefix, marker, SlashSeparator, maxObjectList)
if err != nil {
select {
case ch <- itemOrErr{Err: err}:
case <-ctx.Done():
}
return
}
marker = lo.NextMarker if err := objAPI.Walk(ctx, minioMetaBucket, pathPrefix, objInfoCh, ObjectOptions{}); err != nil {
lister := dirList(lo) select {
if !dirs { case ch <- itemOrErr{Err: err}:
lister = filesList(lo) case <-ctx.Done():
} }
for _, itemPrefix := range lister { return
item := strings.TrimPrefix(itemPrefix, pathPrefix) }
item = strings.TrimSuffix(item, SlashSeparator)
select { for obj := range objInfoCh {
case ch <- itemOrErr{Item: item}: item := strings.TrimPrefix(obj.Name, pathPrefix)
case <-ctx.Done(): item = strings.TrimSuffix(item, SlashSeparator)
return select {
} case ch <- itemOrErr{Item: item}:
} case <-ctx.Done():
if !lo.IsTruncated {
return return
} }
} }
@@ -620,11 +600,9 @@ func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix stri
func (iamOS *IAMObjectStore) watch(ctx context.Context, sys *IAMSys) { func (iamOS *IAMObjectStore) watch(ctx context.Context, sys *IAMSys) {
// Refresh IAMSys. // Refresh IAMSys.
for { for {
select { time.Sleep(globalRefreshIAMInterval)
case <-ctx.Done(): if err := iamOS.loadAll(ctx, sys); err != nil {
return logger.LogIf(ctx, err)
case <-time.NewTimer(globalRefreshIAMInterval).C:
logger.LogIf(ctx, iamOS.loadAll(ctx, sys))
} }
} }
} }
+21 -9
View File
@@ -466,11 +466,10 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
r := rand.New(rand.NewSource(time.Now().UnixNano())) r := rand.New(rand.NewSource(time.Now().UnixNano()))
var err error
for { for {
// let one of the server acquire the lock, if not let them timeout. // let one of the server acquire the lock, if not let them timeout.
// which shall be retried again by this loop. // which shall be retried again by this loop.
if err = txnLk.GetLock(retryCtx, iamLockTimeout); err != nil { if err := txnLk.GetLock(retryCtx, iamLockTimeout); err != nil {
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. trying to acquire lock") logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. trying to acquire lock")
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second))) time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
continue continue
@@ -480,7 +479,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
// **** WARNING **** // **** WARNING ****
// Migrating to encrypted backend on etcd should happen before initialization of // Migrating to encrypted backend on etcd should happen before initialization of
// IAM sub-system, make sure that we do not move the above codeblock elsewhere. // IAM sub-system, make sure that we do not move the above codeblock elsewhere.
if err = migrateIAMConfigsEtcdToEncrypted(ctx, globalEtcdClient); err != nil { if err := migrateIAMConfigsEtcdToEncrypted(ctx, globalEtcdClient); err != nil {
txnLk.Unlock() txnLk.Unlock()
logger.LogIf(ctx, fmt.Errorf("Unable to decrypt an encrypted ETCD backend for IAM users and policies: %w", err)) logger.LogIf(ctx, fmt.Errorf("Unable to decrypt an encrypted ETCD backend for IAM users and policies: %w", err))
logger.LogIf(ctx, errors.New("IAM sub-system is partially initialized, some users may not be available")) logger.LogIf(ctx, errors.New("IAM sub-system is partially initialized, some users may not be available"))
@@ -494,7 +493,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
} }
// Migrate IAM configuration, if necessary. // Migrate IAM configuration, if necessary.
if err = sys.doIAMConfigMigration(ctx); err != nil { if err := sys.doIAMConfigMigration(ctx); err != nil {
txnLk.Unlock() txnLk.Unlock()
if errors.Is(err, errDiskNotFound) || if errors.Is(err, errDiskNotFound) ||
errors.Is(err, errConfigNotFound) || errors.Is(err, errConfigNotFound) ||
@@ -515,14 +514,27 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
break break
} }
err = sys.store.loadAll(ctx, sys) for {
if err := sys.store.loadAll(ctx, sys); err != nil {
if errors.Is(err, errDiskNotFound) ||
errors.Is(err, errConfigNotFound) ||
errors.Is(err, context.DeadlineExceeded) ||
errors.As(err, &rquorum) ||
errors.As(err, &wquorum) ||
isErrBucketNotFound(err) {
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v)", err)
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
continue
}
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize IAM sub-system, some users may not be available %w", err))
}
}
break
}
// Invalidate the old cred always, even upon error to avoid any leakage. // Invalidate the old cred always, even upon error to avoid any leakage.
globalOldCred = auth.Credentials{} globalOldCred = auth.Credentials{}
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize IAM sub-system, some users may not be available %w", err))
}
go sys.store.watch(ctx, sys) go sys.store.watch(ctx, sys)
} }
+6 -8
View File
@@ -46,13 +46,12 @@ func isWriteLock(lri []lockRequesterInfo) bool {
// localLocker implements Dsync.NetLocker // localLocker implements Dsync.NetLocker
type localLocker struct { type localLocker struct {
mutex sync.Mutex mutex sync.Mutex
endpoint Endpoint lockMap map[string][]lockRequesterInfo
lockMap map[string][]lockRequesterInfo
} }
func (l *localLocker) String() string { func (l *localLocker) String() string {
return l.endpoint.String() return globalEndpoints.Localhost()
} }
func (l *localLocker) canTakeUnlock(resources ...string) bool { func (l *localLocker) canTakeUnlock(resources ...string) bool {
@@ -194,7 +193,7 @@ func (l *localLocker) DupLockMap() map[string][]lockRequesterInfo {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
lockCopy := make(map[string][]lockRequesterInfo) lockCopy := map[string][]lockRequesterInfo{}
for k, v := range l.lockMap { for k, v := range l.lockMap {
lockCopy[k] = append(lockCopy[k], v...) lockCopy[k] = append(lockCopy[k], v...)
} }
@@ -253,9 +252,8 @@ func (l *localLocker) removeEntryIfExists(nlrip nameLockRequesterInfoPair) {
} }
} }
func newLocker(endpoint Endpoint) *localLocker { func newLocker() *localLocker {
return &localLocker{ return &localLocker{
endpoint: endpoint, lockMap: make(map[string][]lockRequesterInfo),
lockMap: make(map[string][]lockRequesterInfo),
} }
} }
+8 -5
View File
@@ -32,7 +32,7 @@ import (
// lockRESTClient is authenticable lock REST client // lockRESTClient is authenticable lock REST client
type lockRESTClient struct { type lockRESTClient struct {
restClient *rest.Client restClient *rest.Client
endpoint Endpoint u *url.URL
} }
func toLockError(err error) error { func toLockError(err error) error {
@@ -51,7 +51,7 @@ func toLockError(err error) error {
// String stringer *dsync.NetLocker* interface compatible method. // String stringer *dsync.NetLocker* interface compatible method.
func (client *lockRESTClient) String() string { func (client *lockRESTClient) String() string {
return client.endpoint.String() return client.u.String()
} }
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected // Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
@@ -137,7 +137,7 @@ func (client *lockRESTClient) Expired(ctx context.Context, args dsync.LockArgs)
func newLockAPI(endpoint Endpoint) dsync.NetLocker { func newLockAPI(endpoint Endpoint) dsync.NetLocker {
if endpoint.IsLocal { if endpoint.IsLocal {
return globalLockServers[endpoint] return globalLockServer
} }
return newlockRESTClient(endpoint) return newlockRESTClient(endpoint)
} }
@@ -147,7 +147,7 @@ func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
serverURL := &url.URL{ serverURL := &url.URL{
Scheme: endpoint.Scheme, Scheme: endpoint.Scheme,
Host: endpoint.Host, Host: endpoint.Host,
Path: pathJoin(lockRESTPrefix, endpoint.Path, lockRESTVersion), Path: pathJoin(lockRESTPrefix, lockRESTVersion),
} }
restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken) restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
@@ -163,5 +163,8 @@ func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
return !isNetworkError(err) return !isNetworkError(err)
} }
return &lockRESTClient{endpoint: endpoint, restClient: restClient} return &lockRESTClient{u: &url.URL{
Scheme: endpoint.Scheme,
Host: endpoint.Host,
}, restClient: restClient}
} }
+50 -71
View File
@@ -22,7 +22,6 @@ import (
"errors" "errors"
"math/rand" "math/rand"
"net/http" "net/http"
"path"
"sort" "sort"
"strconv" "strconv"
"time" "time"
@@ -217,27 +216,23 @@ type nameLockRequesterInfoPair struct {
// getLongLivedLocks returns locks that are older than a certain time and // getLongLivedLocks returns locks that are older than a certain time and
// have not been 'checked' for validity too soon enough // have not been 'checked' for validity too soon enough
func getLongLivedLocks(interval time.Duration) map[Endpoint][]nameLockRequesterInfoPair { func getLongLivedLocks(interval time.Duration) []nameLockRequesterInfoPair {
nlripMap := make(map[Endpoint][]nameLockRequesterInfoPair) nlrip := []nameLockRequesterInfoPair{}
for endpoint, locker := range globalLockServers { globalLockServer.mutex.Lock()
rslt := []nameLockRequesterInfoPair{} for name, lriArray := range globalLockServer.lockMap {
locker.mutex.Lock() for idx := range lriArray {
for name, lriArray := range locker.lockMap { // Check whether enough time has gone by since last check
for idx := range lriArray { if time.Since(lriArray[idx].TimeLastCheck) >= interval {
// Check whether enough time has gone by since last check nlrip = append(nlrip, nameLockRequesterInfoPair{
if time.Since(lriArray[idx].TimeLastCheck) >= interval { name: name,
rslt = append(rslt, nameLockRequesterInfoPair{ lri: lriArray[idx],
name: name, })
lri: lriArray[idx], lriArray[idx].TimeLastCheck = UTCNow()
})
lriArray[idx].TimeLastCheck = UTCNow()
}
} }
} }
nlripMap[endpoint] = rslt
locker.mutex.Unlock()
} }
return nlripMap globalLockServer.mutex.Unlock()
return nlrip
} }
// lockMaintenance loops over locks that have been active for some time and checks back // lockMaintenance loops over locks that have been active for some time and checks back
@@ -277,44 +272,36 @@ func lockMaintenance(ctx context.Context, interval time.Duration) error {
} }
} }
allLockersFn := z.GetAllLockers
// Validate if long lived locks are indeed clean. // Validate if long lived locks are indeed clean.
// Get list of long lived locks to check for staleness. // Get list of long lived locks to check for staleness.
for lendpoint, nlrips := range getLongLivedLocks(interval) { nlrips := getLongLivedLocks(interval)
nlripsMap := make(map[string]nlock, len(nlrips)) nlripsMap := make(map[string]nlock, len(nlrips))
for _, nlrip := range nlrips { for _, nlrip := range nlrips {
for _, c := range allLockersFn() { for _, c := range z.GetAllLockers() {
if !c.IsOnline() || c == nil { ctx, cancel := context.WithTimeout(GlobalContext, 5*time.Second)
continue
}
ctx, cancel := context.WithTimeout(GlobalContext, 5*time.Second) // Call back to original server verify whether the lock is
// still active (based on name & uid)
// Call back to original server verify whether the lock is expired, err := c.Expired(ctx, dsync.LockArgs{
// still active (based on name & uid) Owner: nlrip.lri.Owner,
expired, err := c.Expired(ctx, dsync.LockArgs{ UID: nlrip.lri.UID,
Owner: nlrip.lri.Owner, Resources: []string{nlrip.name},
UID: nlrip.lri.UID, })
Resources: []string{nlrip.name}, cancel()
}) if err != nil {
cancel() updateNlocks(nlripsMap, nlrip.name, nlrip.lri.Writer)
if err != nil { continue
updateNlocks(nlripsMap, nlrip.name, nlrip.lri.Writer)
continue
}
if !expired {
updateNlocks(nlripsMap, nlrip.name, nlrip.lri.Writer)
}
} }
// less than the quorum, we have locks expired. if !expired {
if nlripsMap[nlrip.name].locks < nlrip.lri.Quorum { updateNlocks(nlripsMap, nlrip.name, nlrip.lri.Writer)
// Purge the stale entry if it exists.
globalLockServers[lendpoint].removeEntryIfExists(nlrip)
} }
}
// less than the quorum, we have locks expired.
if nlripsMap[nlrip.name].locks < nlrip.lri.Quorum {
// Purge the stale entry if it exists.
globalLockServer.removeEntryIfExists(nlrip)
} }
} }
@@ -361,28 +348,20 @@ func startLockMaintenance(ctx context.Context) {
} }
// registerLockRESTHandlers - register lock rest router. // registerLockRESTHandlers - register lock rest router.
func registerLockRESTHandlers(router *mux.Router, endpointServerPools EndpointServerPools) { func registerLockRESTHandlers(router *mux.Router) {
for _, ep := range endpointServerPools { lockServer := &lockRESTServer{
for _, endpoint := range ep.Endpoints { ll: newLocker(),
if !endpoint.IsLocal {
continue
}
lockServer := &lockRESTServer{
ll: newLocker(endpoint),
}
subrouter := router.PathPrefix(path.Join(lockRESTPrefix, endpoint.Path)).Subrouter()
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodHealth).HandlerFunc(httpTraceHdrs(lockServer.HealthHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodLock).HandlerFunc(httpTraceHdrs(lockServer.LockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRLock).HandlerFunc(httpTraceHdrs(lockServer.RLockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodUnlock).HandlerFunc(httpTraceHdrs(lockServer.UnlockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRUnlock).HandlerFunc(httpTraceHdrs(lockServer.RUnlockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodExpired).HandlerFunc(httpTraceAll(lockServer.ExpiredHandler))
globalLockServers[endpoint] = lockServer.ll
}
} }
subrouter := router.PathPrefix(lockRESTPrefix).Subrouter()
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodHealth).HandlerFunc(httpTraceHdrs(lockServer.HealthHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodLock).HandlerFunc(httpTraceHdrs(lockServer.LockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRLock).HandlerFunc(httpTraceHdrs(lockServer.RLockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodUnlock).HandlerFunc(httpTraceHdrs(lockServer.UnlockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodRUnlock).HandlerFunc(httpTraceHdrs(lockServer.RUnlockHandler))
subrouter.Methods(http.MethodPost).Path(lockRESTVersionPrefix + lockRESTMethodExpired).HandlerFunc(httpTraceAll(lockServer.ExpiredHandler))
globalLockServer = lockServer.ll
go startLockMaintenance(GlobalContext) go startLockMaintenance(GlobalContext)
} }
+121 -83
View File
@@ -22,6 +22,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"path"
"runtime/debug" "runtime/debug"
"strings" "strings"
"sync" "sync"
@@ -29,6 +30,7 @@ import (
"github.com/klauspost/compress/s2" "github.com/klauspost/compress/s2"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
"github.com/tinylib/msgp/msgp" "github.com/tinylib/msgp/msgp"
) )
@@ -43,6 +45,8 @@ type bucketMetacache struct {
// caches indexed by id. // caches indexed by id.
caches map[string]metacache caches map[string]metacache
// cache ids indexed by root paths
cachesRoot map[string][]string `msg:"-"`
// Internal state // Internal state
mu sync.RWMutex `msg:"-"` mu sync.RWMutex `msg:"-"`
@@ -63,8 +67,15 @@ func newBucketMetacache(bucket string, cleanup bool) *bucketMetacache {
} }
} }
return &bucketMetacache{ return &bucketMetacache{
bucket: bucket, bucket: bucket,
caches: make(map[string]metacache, 10), caches: make(map[string]metacache, 10),
cachesRoot: make(map[string][]string, 10),
}
}
func (b *bucketMetacache) debugf(format string, data ...interface{}) {
if serverDebugLog {
console.Debugf(format+"\n", data...)
} }
} }
@@ -76,7 +87,8 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
case <-time.After(250 * time.Millisecond): default:
time.Sleep(250 * time.Millisecond)
} }
objAPI = newObjectLayerFn() objAPI = newObjectLayerFn()
if objAPI == nil { if objAPI == nil {
@@ -101,6 +113,7 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
// Use global context for this. // Use global context for this.
err := objAPI.GetObject(GlobalContext, minioMetaBucket, pathJoin("buckets", bucket, ".metacache", "index.s2"), 0, -1, w, "", ObjectOptions{}) err := objAPI.GetObject(GlobalContext, minioMetaBucket, pathJoin("buckets", bucket, ".metacache", "index.s2"), 0, -1, w, "", ObjectOptions{})
logger.LogIf(ctx, w.CloseWithError(err)) logger.LogIf(ctx, w.CloseWithError(err))
wg.Wait()
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case ObjectNotFound: case ObjectNotFound:
@@ -113,7 +126,6 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
} }
return newBucketMetacache(bucket, false), err return newBucketMetacache(bucket, false), err
} }
wg.Wait()
if decErr != nil { if decErr != nil {
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {
return newBucketMetacache(bucket, false), err return newBucketMetacache(bucket, false), err
@@ -128,6 +140,11 @@ func loadBucketMetaCache(ctx context.Context, bucket string) (*bucketMetacache,
logger.Info("loadBucketMetaCache: loaded cache name mismatch, want %s, got %s. Discarding.", bucket, meta.bucket) logger.Info("loadBucketMetaCache: loaded cache name mismatch, want %s, got %s. Discarding.", bucket, meta.bucket)
return newBucketMetacache(bucket, true), nil return newBucketMetacache(bucket, true), nil
} }
meta.cachesRoot = make(map[string][]string, len(meta.caches)/10)
// Index roots
for id, cache := range meta.caches {
meta.cachesRoot[cache.root] = append(meta.cachesRoot[cache.root], id)
}
return &meta, nil return &meta, nil
} }
@@ -188,10 +205,7 @@ func (b *bucketMetacache) findCache(o listPathOptions) metacache {
return metacache{} return metacache{}
} }
debugPrint := func(msg string, data ...interface{}) {} extend := globalAPIConfig.getExtendListLife()
if false {
debugPrint = logger.Info
}
// Grab a write lock, since we create one if we cannot find one. // Grab a write lock, since we create one if we cannot find one.
if o.Create { if o.Create {
@@ -204,73 +218,47 @@ func (b *bucketMetacache) findCache(o listPathOptions) metacache {
// Check if exists already. // Check if exists already.
if c, ok := b.caches[o.ID]; ok { if c, ok := b.caches[o.ID]; ok {
debugPrint("returning existing %v", o.ID) b.debugf("returning existing %v", o.ID)
return c return c
} }
// No need to do expensive checks on transients.
if b.transient {
if !o.Create {
return metacache{
id: o.ID,
bucket: o.Bucket,
status: scanStateNone,
}
}
// Create new
best := o.newMetacache()
b.caches[o.ID] = best
b.updated = true
b.debugf("returning new cache %s, bucket: %v", best.id, best.bucket)
return best
}
var best metacache var best metacache
extend := globalAPIConfig.getExtendListLife() rootSplit := strings.Split(o.BaseDir, slashSeparator)
for _, cached := range b.caches { for i := range rootSplit {
// Never return transient caches if there is no id. interesting := b.cachesRoot[path.Join(rootSplit[:i+1]...)]
if b.transient {
break
}
if cached.status == scanStateError || cached.status == scanStateNone || cached.dataVersion != metacacheStreamVersion {
debugPrint("cache %s state or stream version mismatch", cached.id)
continue
}
if cached.startedCycle < o.OldestCycle {
debugPrint("cache %s cycle too old", cached.id)
continue
}
// Root of what we are looking for must at least have
if !strings.HasPrefix(o.BaseDir, cached.root) {
debugPrint("cache %s prefix mismatch, cached:%v, want:%v", cached.id, cached.root, o.BaseDir)
continue
}
if cached.filter != "" && strings.HasPrefix(cached.filter, o.FilterPrefix) {
debugPrint("cache %s cannot be used because of filter %s", cached.id, cached.filter)
continue
}
// If the existing listing wasn't recursive root must match.
if !cached.recursive && o.BaseDir != cached.root {
debugPrint("cache %s non rec prefix mismatch, cached:%v, want:%v", cached.id, cached.root, o.BaseDir)
continue
}
if o.Recursive && !cached.recursive {
debugPrint("cache %s not recursive", cached.id)
// If this is recursive the cached listing must be as well.
continue
}
if o.Separator != slashSeparator && !cached.recursive {
debugPrint("cache %s not slashsep and not recursive", cached.id)
// Non slash separator requires recursive.
continue
}
if !cached.finished() && time.Since(cached.lastUpdate) > metacacheMaxRunningAge {
debugPrint("cache %s not running, time: %v", cached.id, time.Since(cached.lastUpdate))
// Abandoned
continue
}
if cached.finished() && cached.endedCycle <= o.OldestCycle { for _, id := range interesting {
if extend <= 0 { cached, ok := b.caches[id]
// If scan has ended the oldest requested must be less. if !ok {
debugPrint("cache %s ended and cycle (%v) <= oldest allowed (%v)", cached.id, cached.endedCycle, o.OldestCycle)
continue continue
} }
if time.Since(cached.lastUpdate) > metacacheMaxRunningAge+extend { if !cached.matches(&o, extend) {
// Cache ended within bloom cycle, but we can extend the life.
debugPrint("cache %s ended (%v) and beyond extended life (%v)", cached.id, cached.lastUpdate, extend+metacacheMaxRunningAge)
continue continue
} }
if cached.started.Before(best.started) {
b.debugf("cache %s disregarded - we have a better", cached.id)
// If we already have a newer, keep that.
continue
}
best = cached
} }
if cached.started.Before(best.started) {
debugPrint("cache %s disregarded - we have a better", cached.id)
// If we already have a newer, keep that.
continue
}
best = cached
} }
if !best.started.IsZero() { if !best.started.IsZero() {
if o.Create { if o.Create {
@@ -278,7 +266,7 @@ func (b *bucketMetacache) findCache(o listPathOptions) metacache {
b.caches[best.id] = best b.caches[best.id] = best
b.updated = true b.updated = true
} }
debugPrint("returning cached %s, status: %v, ended: %v", best.id, best.status, best.ended) b.debugf("returning cached %s, status: %v, ended: %v", best.id, best.status, best.ended)
return best return best
} }
if !o.Create { if !o.Create {
@@ -292,8 +280,9 @@ func (b *bucketMetacache) findCache(o listPathOptions) metacache {
// Create new and add. // Create new and add.
best = o.newMetacache() best = o.newMetacache()
b.caches[o.ID] = best b.caches[o.ID] = best
b.cachesRoot[best.root] = append(b.cachesRoot[best.root], best.id)
b.updated = true b.updated = true
debugPrint("returning new cache %s, bucket: %v", best.id, best.bucket) b.debugf("returning new cache %s, bucket: %v", best.id, best.bucket)
return best return best
} }
@@ -303,20 +292,18 @@ func (b *bucketMetacache) cleanup() {
remove := make(map[string]struct{}) remove := make(map[string]struct{})
currentCycle := intDataUpdateTracker.current() currentCycle := intDataUpdateTracker.current()
debugPrint := func(msg string, data ...interface{}) {} // Test on a copy
if false { // cleanup is the only one deleting caches.
debugPrint = logger.Info caches, rootIdx := b.cloneCaches()
}
b.mu.RLock() for id, cache := range caches {
for id, cache := range b.caches {
if b.transient && time.Since(cache.lastUpdate) > 15*time.Minute && time.Since(cache.lastHandout) > 15*time.Minute { if b.transient && time.Since(cache.lastUpdate) > 15*time.Minute && time.Since(cache.lastHandout) > 15*time.Minute {
// Keep transient caches only for 1 hour. // Keep transient caches only for 15 minutes.
remove[id] = struct{}{} remove[id] = struct{}{}
continue continue
} }
if !cache.worthKeeping(currentCycle) { if !cache.worthKeeping(currentCycle) {
debugPrint("cache %s not worth keeping", id) b.debugf("cache %s not worth keeping", id)
remove[id] = struct{}{} remove[id] = struct{}{}
continue continue
} }
@@ -334,30 +321,52 @@ func (b *bucketMetacache) cleanup() {
// Check all non-deleted against eachother. // Check all non-deleted against eachother.
// O(n*n), but should still be rather quick. // O(n*n), but should still be rather quick.
for id, cache := range b.caches { for id, cache := range caches {
if b.transient { if b.transient {
break break
} }
if _, ok := remove[id]; ok { if _, ok := remove[id]; ok {
continue continue
} }
for _, cache2 := range b.caches {
interesting := interestingCaches(cache.root, rootIdx)
for _, id2 := range interesting {
if _, ok := remove[id2]; ok || id2 == id {
// Don't check against one we are already removing
continue
}
cache2, ok := caches[id2]
if !ok {
continue
}
if cache.canBeReplacedBy(&cache2) { if cache.canBeReplacedBy(&cache2) {
debugPrint("cache %s can be replaced by %s", id, cache2.id) b.debugf("cache %s can be replaced by %s", id, cache2.id)
remove[id] = struct{}{} remove[id] = struct{}{}
break break
} else { } else {
debugPrint("cache %s can be NOT replaced by %s", id, cache2.id) b.debugf("cache %s can be NOT replaced by %s", id, cache2.id)
} }
} }
} }
b.mu.RUnlock()
for id := range remove { for id := range remove {
b.deleteCache(id) b.deleteCache(id)
} }
} }
// Potentially interesting caches.
// Will only add root if request is for root.
func interestingCaches(root string, cachesRoot map[string][]string) []string {
var interesting []string
rootSplit := strings.Split(root, slashSeparator)
for i := range rootSplit {
want := path.Join(rootSplit[:i+1]...)
interesting = append(interesting, cachesRoot[want]...)
}
return interesting
}
// updateCache will update a cache by id. // updateCache will update a cache by id.
// If the cache cannot be found nil is returned. // If the cache cannot be found nil is returned.
// The bucket cache will be locked until the done . // The bucket cache will be locked until the done .
@@ -391,6 +400,25 @@ func (b *bucketMetacache) updateCacheEntry(update metacache) (metacache, error)
return existing, nil return existing, nil
} }
// cloneCaches will return a clone of all current caches.
func (b *bucketMetacache) cloneCaches() (map[string]metacache, map[string][]string) {
b.mu.RLock()
defer b.mu.RUnlock()
dst := make(map[string]metacache, len(b.caches))
for k, v := range b.caches {
dst[k] = v
}
// Copy indexes
dst2 := make(map[string][]string, len(b.cachesRoot))
for k, v := range b.cachesRoot {
tmp := make([]string, len(v))
copy(tmp, v)
dst2[k] = tmp
}
return dst, dst2
}
// getCache will return a clone of a specific metacache. // getCache will return a clone of a specific metacache.
// Will return nil if the cache doesn't exist. // Will return nil if the cache doesn't exist.
func (b *bucketMetacache) getCache(id string) *metacache { func (b *bucketMetacache) getCache(id string) *metacache {
@@ -406,9 +434,6 @@ func (b *bucketMetacache) getCache(id string) *metacache {
// deleteAll will delete all on disk data for ALL caches. // deleteAll will delete all on disk data for ALL caches.
// Deletes are performed concurrently. // Deletes are performed concurrently.
func (b *bucketMetacache) deleteAll() { func (b *bucketMetacache) deleteAll() {
b.mu.Lock()
defer b.mu.Unlock()
ctx := context.Background() ctx := context.Background()
ez, ok := newObjectLayerFn().(*erasureServerPools) ez, ok := newObjectLayerFn().(*erasureServerPools)
if !ok { if !ok {
@@ -416,11 +441,15 @@ func (b *bucketMetacache) deleteAll() {
return return
} }
b.mu.Lock()
defer b.mu.Unlock()
b.updated = true b.updated = true
if !b.transient { if !b.transient {
// Delete all. // Delete all.
ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(b.bucket, slashSeparator)) ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(b.bucket, slashSeparator))
b.caches = make(map[string]metacache, 10) b.caches = make(map[string]metacache, 10)
b.cachesRoot = make(map[string][]string, 10)
return return
} }
@@ -442,6 +471,15 @@ func (b *bucketMetacache) deleteCache(id string) {
b.mu.Lock() b.mu.Lock()
c, ok := b.caches[id] c, ok := b.caches[id]
if ok { if ok {
// Delete from root map.
list := b.cachesRoot[c.root]
for i, lid := range list {
if id == lid {
list = append(list[:i], list[i+1:]...)
break
}
}
b.cachesRoot[c.root] = list
delete(b.caches, id) delete(b.caches, id)
b.updated = true b.updated = true
} }
+55
View File
@@ -0,0 +1,55 @@
package cmd
import (
"fmt"
"testing"
)
func Benchmark_bucketMetacache_findCache(b *testing.B) {
bm := newBucketMetacache("", false)
const elements = 50000
const paths = 100
if elements%paths != 0 {
b.Fatal("elements must be divisible by the number of paths")
}
var pathNames [paths]string
for i := range pathNames[:] {
pathNames[i] = fmt.Sprintf("prefix/%d", i)
}
for i := 0; i < elements; i++ {
bm.findCache(listPathOptions{
ID: mustGetUUID(),
Bucket: "",
BaseDir: pathNames[i%paths],
Prefix: "",
FilterPrefix: "",
Marker: "",
Limit: 0,
AskDisks: 0,
Recursive: false,
Separator: slashSeparator,
Create: true,
CurrentCycle: uint64(i),
OldestCycle: uint64(i - 1),
})
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bm.findCache(listPathOptions{
ID: mustGetUUID(),
Bucket: "",
BaseDir: pathNames[i%paths],
Prefix: "",
FilterPrefix: "",
Marker: "",
Limit: 0,
AskDisks: 0,
Recursive: false,
Separator: slashSeparator,
Create: true,
CurrentCycle: uint64(i % elements),
OldestCycle: uint64(0),
})
}
}
+30 -19
View File
@@ -73,11 +73,13 @@ func (e *metaCacheEntry) matches(other *metaCacheEntry, bucket string) bool {
if len(e.metadata) != len(other.metadata) || e.name != other.name { if len(e.metadata) != len(other.metadata) || e.name != other.name {
return false return false
} }
eFi, eErr := e.fileInfo(bucket) eFi, eErr := e.fileInfo(bucket)
oFi, oErr := e.fileInfo(bucket) oFi, oErr := other.fileInfo(bucket)
if eErr != nil || oErr != nil { if eErr != nil || oErr != nil {
return eErr == oErr return eErr == oErr
} }
return eFi.ModTime.Equal(oFi.ModTime) && eFi.Size == oFi.Size && eFi.VersionID == oFi.VersionID return eFi.ModTime.Equal(oFi.ModTime) && eFi.Size == oFi.Size && eFi.VersionID == oFi.VersionID
} }
@@ -213,11 +215,12 @@ func (m metaCacheEntries) resolve(r *metadataResolutionParams) (selected *metaCa
} }
// Get new entry metadata // Get new entry metadata
objExists++
fiv, err := entry.fileInfo(r.bucket) fiv, err := entry.fileInfo(r.bucket)
if err != nil { if err != nil {
continue continue
} }
objExists++
if selFIV == nil { if selFIV == nil {
selected = entry selected = entry
selFIV = fiv selFIV = fiv
@@ -227,14 +230,8 @@ func (m metaCacheEntries) resolve(r *metadataResolutionParams) (selected *metaCa
if selected.matches(entry, r.bucket) { if selected.matches(entry, r.bucket) {
continue continue
} }
// Select latest modtime.
if fiv.ModTime.After(selFIV.ModTime) {
selected = entry
selFIV = fiv
continue
}
} }
// If directory, we need quorum. // If directory, we need quorum.
if dirExists > 0 && dirExists < r.dirQuorum { if dirExists > 0 && dirExists < r.dirQuorum {
return nil, false return nil, false
@@ -309,7 +306,7 @@ func (m *metaCacheEntriesSorted) iterate(fn func(entry metaCacheEntry) (cont boo
// fileInfoVersions converts the metadata to FileInfoVersions where possible. // fileInfoVersions converts the metadata to FileInfoVersions where possible.
// Metadata that cannot be decoded is skipped. // Metadata that cannot be decoded is skipped.
func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, afterV string) (versions []ObjectInfo, commonPrefixes []string) { func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, afterV string) (versions []ObjectInfo) {
versions = make([]ObjectInfo, 0, m.len()) versions = make([]ObjectInfo, 0, m.len())
prevPrefix := "" prevPrefix := ""
for _, entry := range m.o { for _, entry := range m.o {
@@ -323,7 +320,11 @@ func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, aft
continue continue
} }
prevPrefix = currPrefix prevPrefix = currPrefix
commonPrefixes = append(commonPrefixes, currPrefix) versions = append(versions, ObjectInfo{
IsDir: true,
Bucket: bucket,
Name: currPrefix,
})
continue continue
} }
} }
@@ -356,17 +357,20 @@ func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, aft
continue continue
} }
prevPrefix = currPrefix prevPrefix = currPrefix
commonPrefixes = append(commonPrefixes, currPrefix) versions = append(versions, ObjectInfo{
continue IsDir: true,
Bucket: bucket,
Name: currPrefix,
})
} }
} }
return versions, commonPrefixes return versions
} }
// fileInfoVersions converts the metadata to FileInfoVersions where possible. // fileInfoVersions converts the metadata to FileInfoVersions where possible.
// Metadata that cannot be decoded is skipped. // Metadata that cannot be decoded is skipped.
func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (objects []ObjectInfo, commonPrefixes []string) { func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (objects []ObjectInfo) {
objects = make([]ObjectInfo, 0, m.len()) objects = make([]ObjectInfo, 0, m.len())
prevPrefix := "" prevPrefix := ""
for _, entry := range m.o { for _, entry := range m.o {
@@ -380,7 +384,11 @@ func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (ob
continue continue
} }
prevPrefix = currPrefix prevPrefix = currPrefix
commonPrefixes = append(commonPrefixes, currPrefix) objects = append(objects, ObjectInfo{
IsDir: true,
Bucket: bucket,
Name: currPrefix,
})
continue continue
} }
} }
@@ -405,12 +413,15 @@ func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (ob
continue continue
} }
prevPrefix = currPrefix prevPrefix = currPrefix
commonPrefixes = append(commonPrefixes, currPrefix) objects = append(objects, ObjectInfo{
continue IsDir: true,
Bucket: bucket,
Name: currPrefix,
})
} }
} }
return objects, commonPrefixes return objects
} }
// forwardTo will truncate m so only entries that are s or after is in the list. // forwardTo will truncate m so only entries that are s or after is in the list.
@@ -84,11 +84,21 @@ func (z *erasureServerPools) listPath(ctx context.Context, o listPathOptions) (e
o.ID = mustGetUUID() o.ID = mustGetUUID()
} }
o.BaseDir = baseDirFromPrefix(o.Prefix) o.BaseDir = baseDirFromPrefix(o.Prefix)
if o.singleObject { if o.discardResult {
// Override for single object. // Override for single object.
o.BaseDir = o.Prefix o.BaseDir = o.Prefix
} }
// For very small recursive listings, don't same cache.
// Attempts to avoid expensive listings to run for a long
// while when clients aren't interested in results.
// If the client DOES resume the listing a full cache
// will be generated due to the marker without ID and this check failing.
if o.Limit < 10 && o.Marker == "" && o.Create && o.Recursive {
o.discardResult = true
o.Transient = true
}
var cache metacache var cache metacache
// If we don't have a list id we must ask the server if it has a cache or create a new. // If we don't have a list id we must ask the server if it has a cache or create a new.
if o.Create { if o.Create {
@@ -137,13 +147,11 @@ func (z *erasureServerPools) listPath(ctx context.Context, o listPathOptions) (e
var wg sync.WaitGroup var wg sync.WaitGroup
var errs []error var errs []error
allAtEOF := true allAtEOF := true
asked := 0
mu.Lock() mu.Lock()
// Ask all sets and merge entries. // Ask all sets and merge entries.
for _, zone := range z.serverPools { for _, zone := range z.serverPools {
for _, set := range zone.sets { for _, set := range zone.sets {
wg.Add(1) wg.Add(1)
asked++
go func(i int, set *erasureObjects) { go func(i int, set *erasureObjects) {
defer wg.Done() defer wg.Done()
e, err := set.listPath(ctx, o) e, err := set.listPath(ctx, o)
@@ -206,7 +214,9 @@ func (z *erasureServerPools) listPath(ctx context.Context, o listPathOptions) (e
} }
truncated := entries.len() > o.Limit || !allAtEOF truncated := entries.len() > o.Limit || !allAtEOF
entries.truncate(o.Limit) entries.truncate(o.Limit)
entries.listID = o.ID if !o.discardResult {
entries.listID = o.ID
}
if !truncated { if !truncated {
return entries, io.EOF return entries, io.EOF
} }
+139 -130
View File
@@ -29,6 +29,7 @@ import (
"sync" "sync"
"time" "time"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/console" "github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
@@ -94,8 +95,9 @@ type listPathOptions struct {
// A transient result will never be returned from the cache so knowing the list id is required. // A transient result will never be returned from the cache so knowing the list id is required.
Transient bool Transient bool
// singleObject will assume that prefix refers to an exact single object. // discardResult will not persist the cache to storage.
singleObject bool // When the initial results are returned listing will be canceled.
discardResult bool
} }
func init() { func init() {
@@ -122,11 +124,22 @@ func (o listPathOptions) newMetacache() metacache {
} }
} }
func (o *listPathOptions) debugf(format string, data ...interface{}) {
if serverDebugLog {
console.Debugf(format, data...)
}
}
func (o *listPathOptions) debugln(data ...interface{}) {
if serverDebugLog {
console.Debugln(data...)
}
}
// gatherResults will collect all results on the input channel and filter results according to the options. // gatherResults will collect all results on the input channel and filter results according to the options.
// Caller should close the channel when done. // Caller should close the channel when done.
// The returned function will return the results once there is enough or input is closed. // The returned function will return the results once there is enough or input is closed.
func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCacheEntriesSorted, error) { func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCacheEntriesSorted, error) {
const debugPrint = false
var resultsDone = make(chan metaCacheEntriesSorted) var resultsDone = make(chan metaCacheEntriesSorted)
// Copy so we can mutate // Copy so we can mutate
resCh := resultsDone resCh := resultsDone
@@ -142,34 +155,22 @@ func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCa
if !o.IncludeDirectories && entry.isDir() { if !o.IncludeDirectories && entry.isDir() {
continue continue
} }
if debugPrint { o.debugln("gather got:", entry.name)
console.Infoln("gather got:", entry.name)
}
if o.Marker != "" && entry.name <= o.Marker { if o.Marker != "" && entry.name <= o.Marker {
if debugPrint { o.debugln("pre marker")
console.Infoln("pre marker")
}
continue continue
} }
if !strings.HasPrefix(entry.name, o.Prefix) { if !strings.HasPrefix(entry.name, o.Prefix) {
if debugPrint { o.debugln("not in prefix")
console.Infoln("not in prefix")
}
continue continue
} }
if !o.Recursive && !entry.isInDir(o.Prefix, o.Separator) { if !o.Recursive && !entry.isInDir(o.Prefix, o.Separator) {
if debugPrint { o.debugln("not in dir", o.Prefix, o.Separator)
console.Infoln("not in dir", o.Prefix, o.Separator)
}
continue continue
} }
if !o.InclDeleted && entry.isObject() { if !o.InclDeleted && entry.isObject() && entry.isLatestDeletemarker() {
if entry.isLatestDeletemarker() { o.debugln("latest is delete marker")
if debugPrint { continue
console.Infoln("latest delete")
}
continue
}
} }
if o.Limit > 0 && results.len() >= o.Limit { if o.Limit > 0 && results.len() >= o.Limit {
// We have enough and we have more. // We have enough and we have more.
@@ -181,9 +182,7 @@ func (o *listPathOptions) gatherResults(in <-chan metaCacheEntry) func() (metaCa
} }
continue continue
} }
if debugPrint { o.debugln("adding...")
console.Infoln("adding...")
}
results.o = append(results.o, entry) results.o = append(results.o, entry)
} }
if resCh != nil { if resCh != nil {
@@ -207,19 +206,15 @@ func (o *listPathOptions) findFirstPart(fi FileInfo) (int, error) {
if search == "" { if search == "" {
return 0, nil return 0, nil
} }
const debugPrint = false o.debugln("searching for ", search)
if debugPrint {
console.Infoln("searching for ", search)
}
var tmp metacacheBlock var tmp metacacheBlock
var json = jsoniter.ConfigCompatibleWithStandardLibrary
i := 0 i := 0
for { for {
partKey := fmt.Sprintf("%s-metacache-part-%d", ReservedMetadataPrefixLower, i) partKey := fmt.Sprintf("%s-metacache-part-%d", ReservedMetadataPrefixLower, i)
v, ok := fi.Metadata[partKey] v, ok := fi.Metadata[partKey]
if !ok { if !ok {
if debugPrint { o.debugln("no match in metadata, waiting")
console.Infoln("no match in metadata, waiting")
}
return -1, io.ErrUnexpectedEOF return -1, io.ErrUnexpectedEOF
} }
err := json.Unmarshal([]byte(v), &tmp) err := json.Unmarshal([]byte(v), &tmp)
@@ -231,27 +226,18 @@ func (o *listPathOptions) findFirstPart(fi FileInfo) (int, error) {
return 0, errFileNotFound return 0, errFileNotFound
} }
if tmp.First >= search { if tmp.First >= search {
if debugPrint { o.debugln("First >= search", v)
console.Infoln("First >= search", v)
}
return i, nil return i, nil
} }
if tmp.Last >= search { if tmp.Last >= search {
if debugPrint { o.debugln("Last >= search", v)
console.Infoln("Last >= search", v)
}
return i, nil return i, nil
} }
if tmp.EOS { if tmp.EOS {
if debugPrint { o.debugln("no match, at EOS", v)
console.Infoln("no match, at EOS", v)
}
return -3, io.EOF return -3, io.EOF
} }
if debugPrint { o.debugln("First ", tmp.First, "<", search, " search", i)
console.Infoln("First ", tmp.First, "<", search, " search", i)
}
i++ i++
} }
} }
@@ -277,8 +263,10 @@ func getMetacacheBlockInfo(fi FileInfo, block int) (*metacacheBlock, error) {
return &tmp, json.Unmarshal([]byte(v), &tmp) return &tmp, json.Unmarshal([]byte(v), &tmp)
} }
const metacachePrefix = ".metacache"
func metacachePrefixForID(bucket, id string) string { func metacachePrefixForID(bucket, id string) string {
return pathJoin("buckets", bucket, ".metacache", id) return pathJoin(bucketMetaPrefix, bucket, metacachePrefix, id)
} }
// objectPath returns the object path of the cache. // objectPath returns the object path of the cache.
@@ -312,7 +300,6 @@ func (o *listPathOptions) SetFilter() {
// Will return io.EOF if there are no more entries with the same filter. // Will return io.EOF if there are no more entries with the same filter.
// The last entry can be used as a marker to resume the listing. // The last entry can be used as a marker to resume the listing.
func (r *metacacheReader) filter(o listPathOptions) (entries metaCacheEntriesSorted, err error) { func (r *metacacheReader) filter(o listPathOptions) (entries metaCacheEntriesSorted, err error) {
const debugPrint = false
// Forward to prefix, if any // Forward to prefix, if any
err = r.forwardTo(o.Prefix) err = r.forwardTo(o.Prefix)
if err != nil { if err != nil {
@@ -334,9 +321,8 @@ func (r *metacacheReader) filter(o listPathOptions) (entries metaCacheEntriesSor
} }
} }
} }
if debugPrint { o.debugln("forwarded to ", o.Prefix, "marker:", o.Marker, "sep:", o.Separator)
console.Infoln("forwarded to ", o.Prefix, "marker:", o.Marker, "sep:", o.Separator)
}
// Filter // Filter
if !o.Recursive { if !o.Recursive {
entries.o = make(metaCacheEntries, 0, o.Limit) entries.o = make(metaCacheEntries, 0, o.Limit)
@@ -446,7 +432,11 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
// We got a stream to start at. // We got a stream to start at.
loadedPart := 0 loadedPart := 0
var buf bytes.Buffer buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@@ -496,7 +486,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
} }
} }
buf.Reset() buf.Reset()
err := er.getObjectWithFileInfo(ctx, minioMetaBucket, o.objectPath(partN), 0, fi.Size, &buf, fi, metaArr, onlineDisks) err := er.getObjectWithFileInfo(ctx, minioMetaBucket, o.objectPath(partN), 0, fi.Size, buf, fi, metaArr, onlineDisks)
if err != nil { if err != nil {
switch toObjectErr(err, minioMetaBucket, o.objectPath(partN)).(type) { switch toObjectErr(err, minioMetaBucket, o.objectPath(partN)).(type) {
case ObjectNotFound: case ObjectNotFound:
@@ -512,7 +502,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
return entries, err return entries, err
} }
} }
tmp, err := newMetacacheReader(&buf) tmp, err := newMetacacheReader(buf)
if err != nil { if err != nil {
return entries, err return entries, err
} }
@@ -549,15 +539,16 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
} }
} }
func (er erasureObjects) SetDriveCount() int {
return er.setDriveCount
}
// Will return io.EOF if continuing would not yield more results. // Will return io.EOF if continuing would not yield more results.
func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entries metaCacheEntriesSorted, err error) { func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entries metaCacheEntriesSorted, err error) {
const debugPrint = false o.debugf("listPath with options: %#v\n", o)
if debugPrint {
console.Printf("listPath with options: %#v\n", o)
}
// See if we have the listing stored. // See if we have the listing stored.
if !o.Create && !o.singleObject { if !o.Create && !o.discardResult {
entries, err := er.streamMetadataParts(ctx, o) entries, err := er.streamMetadataParts(ctx, o)
if IsErr(err, []error{ if IsErr(err, []error{
nil, nil,
@@ -580,9 +571,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
rpc := globalNotificationSys.restClientFromHash(o.Bucket) rpc := globalNotificationSys.restClientFromHash(o.Bucket)
var metaMu sync.Mutex var metaMu sync.Mutex
if debugPrint { o.debugln("listPath: scanning bucket:", o.Bucket, "basedir:", o.BaseDir, "prefix:", o.Prefix, "marker:", o.Marker)
console.Println("listPath: scanning bucket:", o.Bucket, "basedir:", o.BaseDir, "prefix:", o.Prefix, "marker:", o.Marker)
}
// Disconnect from call above, but cancel on exit. // Disconnect from call above, but cancel on exit.
ctx, cancel := context.WithCancel(GlobalContext) ctx, cancel := context.WithCancel(GlobalContext)
@@ -590,9 +579,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
disks := er.getOnlineDisks() disks := er.getOnlineDisks()
defer func() { defer func() {
if debugPrint { o.debugln("listPath returning:", entries.len(), "err:", err)
console.Println("listPath returning:", entries.len(), "err:", err)
}
if err != nil && !errors.Is(err, io.EOF) { if err != nil && !errors.Is(err, io.EOF) {
go func(err string) { go func(err string) {
metaMu.Lock() metaMu.Lock()
@@ -608,15 +595,15 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
}() }()
askDisks := o.AskDisks askDisks := o.AskDisks
if askDisks == -1 { listingQuorum := askDisks - 1
askDisks = getReadQuorum(er.SetDriveCount()) // Special case: ask all disks if the drive count is 4
if askDisks == -1 || er.SetDriveCount() == 4 {
askDisks = len(disks) // with 'strict' quorum list on all online disks.
listingQuorum = getReadQuorum(er.SetDriveCount())
} }
if len(disks) < askDisks { if len(disks) < askDisks {
err = InsufficientReadQuorum{} err = InsufficientReadQuorum{}
if debugPrint {
console.Errorf("listPath: Insufficient disks, %d of %d needed are available", len(disks), askDisks)
}
logger.LogIf(ctx, fmt.Errorf("listPath: Insufficient disks, %d of %d needed are available", len(disks), askDisks)) logger.LogIf(ctx, fmt.Errorf("listPath: Insufficient disks, %d of %d needed are available", len(disks), askDisks))
cancel() cancel()
return return
@@ -628,16 +615,26 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
} }
// Create output for our results. // Create output for our results.
cacheCh := make(chan metaCacheEntry, metacacheBlockSize) var cacheCh chan metaCacheEntry
if !o.discardResult {
cacheCh = make(chan metaCacheEntry, metacacheBlockSize)
}
// Create filter for results. // Create filter for results.
filterCh := make(chan metaCacheEntry, 100) filterCh := make(chan metaCacheEntry, 100)
filteredResults := o.gatherResults(filterCh) filteredResults := o.gatherResults(filterCh)
closeChannels := func() { closeChannels := func() {
close(cacheCh) if !o.discardResult {
close(cacheCh)
}
close(filterCh) close(filterCh)
} }
// Cancel listing on return if non-saved list.
if o.discardResult {
defer cancel()
}
go func() { go func() {
defer cancel() defer cancel()
// Save continuous updates // Save continuous updates
@@ -665,61 +662,67 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
}() }()
const retryDelay = 200 * time.Millisecond const retryDelay = 200 * time.Millisecond
const maxTries = 10 const maxTries = 5
// Write results to disk. var bw *metacacheBlockWriter
bw := newMetacacheBlockWriter(cacheCh, func(b *metacacheBlock) error { // Don't save single object listings.
if o.singleObject { if !o.discardResult {
// Don't save single object listings. // Write results to disk.
return nil bw = newMetacacheBlockWriter(cacheCh, func(b *metacacheBlock) error {
} // if the block is 0 bytes and its a first block skip it.
if debugPrint { // skip only this for Transient caches.
console.Println("listPath: saving block", b.n, "to", o.objectPath(b.n)) if len(b.data) == 0 && b.n == 0 && o.Transient {
} return nil
r, err := hash.NewReader(bytes.NewBuffer(b.data), int64(len(b.data)), "", "", int64(len(b.data)), false)
logger.LogIf(ctx, err)
custom := b.headerKV()
_, err = er.putObject(ctx, minioMetaBucket, o.objectPath(b.n), NewPutObjReader(r, nil, nil), ObjectOptions{UserDefined: custom})
if err != nil {
metaMu.Lock()
if meta.error != "" {
meta.status = scanStateError
meta.error = err.Error()
} }
metaMu.Unlock() o.debugln("listPath: saving block", b.n, "to", o.objectPath(b.n))
cancel() r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)), false)
return err logger.LogIf(ctx, err)
} custom := b.headerKV()
if b.n == 0 { _, err = er.putObject(ctx, minioMetaBucket, o.objectPath(b.n), NewPutObjReader(r, nil, nil), ObjectOptions{
return nil UserDefined: custom,
} NoLock: true, // No need to hold namespace lock, each prefix caches uniquely.
// Update block 0 metadata. })
var retries int if err != nil {
for { metaMu.Lock()
err := er.updateObjectMeta(ctx, minioMetaBucket, o.objectPath(0), b.headerKV(), ObjectOptions{}) if meta.error != "" {
if err == nil { meta.status = scanStateError
break meta.error = err.Error()
} }
switch err.(type) { metaMu.Unlock()
case ObjectNotFound: cancel()
return err
case InsufficientReadQuorum:
default:
logger.LogIf(ctx, err)
}
if retries >= maxTries {
return err return err
} }
retries++ if b.n == 0 {
time.Sleep(retryDelay) return nil
} }
return nil // Update block 0 metadata.
}) var retries int
for {
err := er.updateObjectMeta(ctx, minioMetaBucket, o.objectPath(0), b.headerKV(), ObjectOptions{})
if err == nil {
break
}
switch err.(type) {
case ObjectNotFound:
return err
case InsufficientReadQuorum:
default:
logger.LogIf(ctx, err)
}
if retries >= maxTries {
return err
}
retries++
time.Sleep(retryDelay)
}
return nil
})
}
// How to resolve results. // How to resolve results.
resolver := metadataResolutionParams{ resolver := metadataResolutionParams{
dirQuorum: askDisks - 1, dirQuorum: listingQuorum,
objQuorum: askDisks - 1, objQuorum: listingQuorum,
bucket: o.Bucket, bucket: o.Bucket,
} }
@@ -729,16 +732,20 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
path: o.BaseDir, path: o.BaseDir,
recursive: o.Recursive, recursive: o.Recursive,
filterPrefix: o.FilterPrefix, filterPrefix: o.FilterPrefix,
minDisks: askDisks - 1, minDisks: listingQuorum,
agreed: func(entry metaCacheEntry) { agreed: func(entry metaCacheEntry) {
cacheCh <- entry if !o.discardResult {
cacheCh <- entry
}
filterCh <- entry filterCh <- entry
}, },
partial: func(entries metaCacheEntries, nAgreed int, errs []error) { partial: func(entries metaCacheEntries, nAgreed int, errs []error) {
// Results Disagree :-( // Results Disagree :-(
entry, ok := entries.resolve(&resolver) entry, ok := entries.resolve(&resolver)
if ok { if ok {
cacheCh <- *entry if !o.discardResult {
cacheCh <- *entry
}
filterCh <- *entry filterCh <- *entry
} }
}, },
@@ -759,12 +766,14 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
metaMu.Unlock() metaMu.Unlock()
closeChannels() closeChannels()
if err := bw.Close(); err != nil { if !o.discardResult {
metaMu.Lock() if err := bw.Close(); err != nil {
meta.error = err.Error() metaMu.Lock()
meta.status = scanStateError meta.error = err.Error()
meta, err = o.updateMetacacheListing(meta, rpc) meta.status = scanStateError
metaMu.Unlock() meta, err = o.updateMetacacheListing(meta, rpc)
metaMu.Unlock()
}
} }
}() }()
@@ -814,7 +823,7 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
defer cancel() defer cancel()
askDisks := len(disks) askDisks := len(disks)
var readers = make([]*metacacheReader, askDisks) readers := make([]*metacacheReader, askDisks)
for i := range disks { for i := range disks {
r, w := io.Pipe() r, w := io.Pipe()
d := disks[i] d := disks[i]
@@ -831,7 +840,7 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
ReportNotFound: opts.reportNotFound, ReportNotFound: opts.reportNotFound,
FilterPrefix: opts.filterPrefix}, w) FilterPrefix: opts.filterPrefix}, w)
w.CloseWithError(err) w.CloseWithError(err)
if err != io.EOF { if err != io.EOF && err != nil && err.Error() != errFileNotFound.Error() {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
}() }()
+15 -6
View File
@@ -519,7 +519,7 @@ func (r *metacacheReader) readN(n int, inclDeleted, inclDirs bool, prefix string
if !inclDirs && meta.isDir() { if !inclDirs && meta.isDir() {
continue continue
} }
if meta.isDir() && !inclDeleted && meta.isLatestDeletemarker() { if !inclDeleted && meta.isLatestDeletemarker() {
continue continue
} }
res = append(res, meta) res = append(res, meta)
@@ -745,6 +745,12 @@ type metacacheBlockWriter struct {
blockEntries int blockEntries int
} }
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
// newMetacacheBlockWriter provides a streaming block writer. // newMetacacheBlockWriter provides a streaming block writer.
// Each block is the size of the capacity of the input channel. // Each block is the size of the capacity of the input channel.
// The caller should close to indicate the stream has ended. // The caller should close to indicate the stream has ended.
@@ -755,12 +761,15 @@ func newMetacacheBlockWriter(in <-chan metaCacheEntry, nextBlock func(b *metacac
defer w.wg.Done() defer w.wg.Done()
var current metacacheBlock var current metacacheBlock
var n int var n int
var buf bytes.Buffer buf := bufferPool.Get().(*bytes.Buffer)
block := newMetacacheWriter(&buf, 1<<20) defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
block := newMetacacheWriter(buf, 1<<20)
defer block.Close() defer block.Close()
finishBlock := func() { finishBlock := func() {
err := block.Close() if err := block.Close(); err != nil {
if err != nil {
w.streamErr = err w.streamErr = err
return return
} }
@@ -769,7 +778,7 @@ func newMetacacheBlockWriter(in <-chan metaCacheEntry, nextBlock func(b *metacac
// Prepare for next // Prepare for next
current.n++ current.n++
buf.Reset() buf.Reset()
block.Reset(&buf) block.Reset(buf)
current.First = "" current.First = ""
} }
for o := range in { for o := range in {

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