Compare commits

..

16 Commits

Author SHA1 Message Date
Praveen raj Mani c27d0583d4 Send kafka notification messages in batches when queue_dir is enabled (#18164)
Fixes #18124
2023-10-07 08:07:38 -07:00
Klaus Post 0de2b9a1b2 Fix panic on double unfreezeServices (#18177)
Calling unfreezeServices twice results in panic:

```
panic: "POST /minio/peer/v32/signalservice?signal=4&sub-sys=": close of nil channel
goroutine 14703 [running]:
runtime/debug.Stack()
	runtime/debug/stack.go:24 +0x65
github.com/minio/minio/cmd.setCriticalErrorHandler.func1.1()
	github.com/minio/minio/cmd/generic-handlers.go:549 +0x8e
panic({0x27c3020, 0x4c9b370})
	runtime/panic.go:884 +0x212
github.com/minio/minio/cmd.unfreezeServices()
	github.com/minio/minio/cmd/service.go:112 +0xc7
github.com/minio/minio/cmd.(*peerRESTServer).SignalServiceHandler(0x0?, {0x4cb6af0, 0xc010b96420}, 0xc01affab00)
	github.com/minio/minio/cmd/peer-rest-server.go:837 +0x13a
net/http.HandlerFunc.ServeHTTP(...)
```

If the function was called a second time `val` would not be nil, but the returned channel `ch` would be, causing the panic.

Check the channel isn't nil and also use Swap for an atomic swap instead of 2 separate operations (though we are in a mutex).
2023-10-06 07:51:50 -06:00
Poorna 9dc29d7687 Avoid ILM expiry on deleted versions that are yet to replicate (#18175)
Fixes #18167
2023-10-06 06:55:15 -06:00
Poorna 72871dbb9a delete replication: avoid overwriting replication decision (#18174)
from ObjectInfo unless version purge status is present. Otherwise
there is potential to make incorrect replication decision if Stat
returned an error
2023-10-05 21:09:45 -06:00
Aditya Manthramurthy 4bda4e4e2b fix: check for disk-level O_DIRECT support (#18173)
Disk level O_DIRECT support checking at xl storage initialization was
conditional on a config setting being enabled. (This never took effect
because config initialization happens after ObjectLayer is ready.) This
is not necessary as the config setting is dynamic - O_DIRECT should be
enabled via runtime config. So we need to do the disk level support
check regardless of the config setting.
2023-10-05 20:54:49 -06:00
Harshavardhana 1971c54a50 update buffer channels for both trace and listen events (#18171)
- Trace needs higher buffered channels than 4000 to ensure
  when we run `mc admin trace -a` it captures all information
  sufficiently.

- Listen event notification needs the event channel to be
  `apiRequestsMaxPerNode` * number of nodes
2023-10-05 18:16:04 -06:00
Cesar N bb77b89da0 Update MinIO Console version (#18168)
Co-authored-by: cesnietor <>
2023-10-04 16:25:59 -07:00
Anis Eleuch b336e9a79f fix: loading usage cache to not fail early when reading the backup fails (#18158)
Currently, the retry is not fully used when there is no backup copy of
the data usage; use 5 retry attempts when we don't have any valid data, 
new or backup, unless we have seen an un-recognized error.
2023-10-02 19:22:35 -07:00
Harshavardhana a2ab21e91c add max-keys=2 optimization for spark workloads (#18154)
comment in the code provides more detailed explanation
on what this PR entails and its assumptions.

this PR reduces the amount of listing() by an order
of magnitude, however there are other such calls that
still needs further optimization that shall be done
in subsequent PRs.
2023-10-02 07:52:59 -06:00
Sveinn 603437e70f Fix startup formatting (#18156)
Percentages in root user names are used for formatting.

Before:
```
S3-API: http://192.168.50.21:9000  http://172.31.96.1:9000  http://127.0.0.1:9000
RootUser: "U4B6Zi!b75DXSPm%!!(MISSING)a(MISSING)vZb"
RootPass: "Q4#Q6y8G%!P(MISSING)x#npP4dudUobU#NBcGB7RMKV4ajYb"

Console: http://192.168.50.21:51915 http://172.31.96.1:51915 http://127.0.0.1:51915
RootUser: "U4B6Zi!b75DXSPm%!!(MISSING)a(MISSING)vZb"
RootPass: "Q4#Q6y8G%!P(MISSING)x#npP4dudUobU#NBcGB7RMKV4ajYb"

Command-line: https://min.io/docs/minio/linux/reference/minio-mc.html#quickstart
FORMAT: %117s MESSAGE: $ mc alias set myminio http://192.168.50.21:9000 "U4B6Zi!b75DXSPm%avZb" "Q4#Q6y8G%%Px#npP4dudUobU#NBcGB7RMKV4ajYb"
   $ mc alias set myminio http://192.168.50.21:9000 "U4B6Zi!b75DXSPm%!a(MISSING)vZb" "Q4#Q6y8G%Px#npP4dudUobU#NBcGB7RMKV4ajYb"
```

After:

```
Status:         1 Online, 0 Offline.
S3-API: http://192.168.50.21:9000  http://172.31.96.1:9000  http://127.0.0.1:9000
RootUser: "U4B6Zi!b75DXSPm%avZb"
RootPass: "Q4#Q6y8G%%Px#npP4dudUobU#NBcGB7RMKV4ajYb"

Console: http://192.168.50.21:52421 http://172.31.96.1:52421 http://127.0.0.1:52421
RootUser: "U4B6Zi!b75DXSPm%avZb"
RootPass: "Q4#Q6y8G%%Px#npP4dudUobU#NBcGB7RMKV4ajYb"

Command-line: https://min.io/docs/minio/linux/reference/minio-mc.html#quickstart
   $ mc alias set myminio http://192.168.50.21:9000 "U4B6Zi!b75DXSPm%avZb" "Q4#Q6y8G%%Px#npP4dudUobU#NBcGB7RMKV4ajYb"
```

No need for special Windows case. `mc` works just fine.
2023-10-02 07:39:47 -06:00
Harshavardhana db3a9a5990 update missing mc command on multipart-tests 2023-09-30 20:29:45 -07:00
Harshavardhana 24c7e73b4e update helm chart images and release v5.0.14
Signed-off-by: Harshavardhana <harsha@minio.io>
2023-09-30 13:46:10 -07:00
Alik c053e57068 Add paramaters in Helm chart to load OIDC clientSecret from Secret Resource (#17784) 2023-09-30 13:44:38 -07:00
Shireesh Anjal 6d20ec3bea Add support for resource metrics (#18057)
Add a new endpoint for "resource" metrics `/v2/metrics/resource`

This should return system metrics related to drives, network, CPU and
memory. Except for drives, other metrics should have corresponding "avg"
and "max" values also.

Reuse the real-time feature to capture the required data,
introducing CPU and memory metrics in it.

Collect the data every minute and keep updating the average and max values
accordingly, returning the latest values when the API is called.
2023-09-30 13:40:20 -07:00
Harshavardhana c50627ee3e Add tests for multipart upload overwrites on versioned buckets (#18142) 2023-09-30 03:13:56 -07:00
Minio Trusted b3cd893f93 Update yaml files to latest version RELEASE.2023-09-30T07-02-29Z 2023-09-30 07:51:58 +00:00
65 changed files with 1880 additions and 422 deletions
+13 -2
View File
@@ -37,7 +37,11 @@ jobs:
- name: build-minio
run: |
TAG="minio/minio:${{ steps.vars.outputs.sha_short }}" make docker
TAG="quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}" make docker
- name: multipart uploads test
run: |
${GITHUB_WORKSPACE}/.github/workflows/multipart/migrate.sh "${{ steps.vars.outputs.sha_short }}"
- name: compress and encrypt
run: |
@@ -59,7 +63,14 @@ jobs:
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml down || true
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml rm || true
done
docker rmi -f minio/minio:${{ steps.vars.outputs.sha_short }}
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site1.yaml rm -s -f || true
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site2.yaml rm -s -f || true
for volume in $(docker volume ls -q | grep minio); do
docker volume rm ${volume} || true
done
docker rmi -f quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}
docker system prune -f || true
docker volume prune -f || true
docker volume rm $(docker volume ls -q -f dangling=true) || true
@@ -2,7 +2,7 @@ version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: minio/minio:${JOB_NAME}
image: quay.io/minio/minio:${JOB_NAME}
command: server --console-address ":9001" http://minio{1...4}/cdata{1...2}
expose:
- "9000"
+1 -1
View File
@@ -2,7 +2,7 @@ version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: minio/minio:${JOB_NAME}
image: quay.io/minio/minio:${JOB_NAME}
command: server --console-address ":9001" edata{1...4}
expose:
- "9000"
+1 -1
View File
@@ -2,7 +2,7 @@ version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: minio/minio:${JOB_NAME}
image: quay.io/minio/minio:${JOB_NAME}
command: server --console-address ":9001" http://minio{1...4}/pdata{1...2} http://minio{5...8}/pdata{1...2}
expose:
- "9000"
@@ -0,0 +1,66 @@
version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: quay.io/minio/minio:${RELEASE}
command: server http://site1-minio{1...4}/data{1...2}
environment:
- MINIO_PROMETHEUS_AUTH_TYPE=public
- CI=true
# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
services:
site1-minio1:
<<: *minio-common
hostname: site1-minio1
volumes:
- site1-data1-1:/data1
- site1-data1-2:/data2
site1-minio2:
<<: *minio-common
hostname: site1-minio2
volumes:
- site1-data2-1:/data1
- site1-data2-2:/data2
site1-minio3:
<<: *minio-common
hostname: site1-minio3
volumes:
- site1-data3-1:/data1
- site1-data3-2:/data2
site1-minio4:
<<: *minio-common
hostname: site1-minio4
volumes:
- site1-data4-1:/data1
- site1-data4-2:/data2
site1-nginx:
image: nginx:1.19.2-alpine
hostname: site1-nginx
volumes:
- ./nginx-site1.conf:/etc/nginx/nginx.conf:ro
ports:
- "9001:9001"
depends_on:
- site1-minio1
- site1-minio2
- site1-minio3
- site1-minio4
## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
volumes:
site1-data1-1:
site1-data1-2:
site1-data2-1:
site1-data2-2:
site1-data3-1:
site1-data3-2:
site1-data4-1:
site1-data4-2:
@@ -0,0 +1,66 @@
version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: quay.io/minio/minio:${RELEASE}
command: server http://site2-minio{1...4}/data{1...2}
environment:
- MINIO_PROMETHEUS_AUTH_TYPE=public
- CI=true
# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
services:
site2-minio1:
<<: *minio-common
hostname: site2-minio1
volumes:
- site2-data1-1:/data1
- site2-data1-2:/data2
site2-minio2:
<<: *minio-common
hostname: site2-minio2
volumes:
- site2-data2-1:/data1
- site2-data2-2:/data2
site2-minio3:
<<: *minio-common
hostname: site2-minio3
volumes:
- site2-data3-1:/data1
- site2-data3-2:/data2
site2-minio4:
<<: *minio-common
hostname: site2-minio4
volumes:
- site2-data4-1:/data1
- site2-data4-2:/data2
site2-nginx:
image: nginx:1.19.2-alpine
hostname: site2-nginx
volumes:
- ./nginx-site2.conf:/etc/nginx/nginx.conf:ro
ports:
- "9002:9002"
depends_on:
- site2-minio1
- site2-minio2
- site2-minio3
- site2-minio4
## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
volumes:
site2-data1-1:
site2-data1-2:
site2-data2-1:
site2-data2-2:
site2-data3-1:
site2-data3-2:
site2-data4-1:
site2-data4-2:
+115
View File
@@ -0,0 +1,115 @@
#!/bin/bash
set -x
## change working directory
cd .github/workflows/multipart/
docker-compose -f docker-compose-site1.yaml rm -s -f
docker-compose -f docker-compose-site2.yaml rm -s -f
for volume in $(docker volume ls -q | grep minio); do
docker volume rm ${volume}
done
if [ ! -f ./mc ]; then
wget --quiet -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
chmod +x mc
fi
(
cd /tmp
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
)
export RELEASE=RELEASE.2023-08-29T23-07-35Z
docker-compose -f docker-compose-site1.yaml up -d
docker-compose -f docker-compose-site2.yaml up -d
sleep 30s
./mc alias set site1 http://site1-nginx:9001 minioadmin minioadmin --api s3v4
./mc alias set site2 http://site2-nginx:9002 minioadmin minioadmin --api s3v4
./mc ready site1/
./mc ready site2/
./mc admin replicate add site1 site2
./mc mb site1/testbucket/
./mc cp -r --quiet /usr/bin site1/testbucket/
sleep 5
s3-check-md5 -h
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
if [ $failed_count_site1 -ne 0 ]; then
echo "failed with multipart on site1 uploads"
exit 1
fi
if [ $failed_count_site2 -ne 0 ]; then
echo "failed with multipart on site2 uploads"
exit 1
fi
./mc cp -r --quiet /usr/bin site1/testbucket/
sleep 5
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
## we do not need to fail here, since we are going to test
## upgrading to master, healing and being able to recover
## the last version.
if [ $failed_count_site1 -ne 0 ]; then
echo "failed with multipart on site1 uploads ${failed_count_site1}"
fi
if [ $failed_count_site2 -ne 0 ]; then
echo "failed with multipart on site2 uploads ${failed_count_site2}"
fi
export RELEASE=${1}
docker-compose -f docker-compose-site1.yaml up -d
docker-compose -f docker-compose-site2.yaml up -d
./mc ready site1/
./mc ready site2/
for i in $(seq 1 10); do
# mc admin heal -r --remove when used against a LB endpoint
# behaves flaky, let this run 10 times before giving up
./mc admin heal -r --remove --json site1/ 2>&1 >/dev/null
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
done
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
if [ $failed_count_site1 -ne 0 ]; then
echo "failed with multipart on site1 uploads"
exit 1
fi
if [ $failed_count_site2 -ne 0 ]; then
echo "failed with multipart on site2 uploads"
exit 1
fi
docker-compose -f docker-compose-site1.yaml rm -s -f
docker-compose -f docker-compose-site2.yaml rm -s -f
for volume in $(docker volume ls -q | grep minio); do
docker volume rm ${volume}
done
docker system prune -f || true
docker volume prune -f || true
docker volume rm $(docker volume ls -q -f dangling=true) || true
## change working directory
cd ../../../
@@ -0,0 +1,61 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# include /etc/nginx/conf.d/*.conf;
upstream minio {
server site1-minio1:9000;
server site1-minio2:9000;
server site1-minio3:9000;
server site1-minio4:9000;
}
server {
listen 9001;
listen [::]:9001;
server_name localhost;
# To allow special characters in headers
ignore_invalid_headers off;
# Allow any size file to be uploaded.
# Set to a value such as 1000m; to restrict file size to a specific value
client_max_body_size 0;
# To disable buffering
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300;
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://minio;
}
}
}
@@ -0,0 +1,61 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# include /etc/nginx/conf.d/*.conf;
upstream minio {
server site2-minio1:9000;
server site2-minio2:9000;
server site2-minio3:9000;
server site2-minio4:9000;
}
server {
listen 9002;
listen [::]:9002;
server_name localhost;
# To allow special characters in headers
ignore_invalid_headers off;
# Allow any size file to be uploaded.
# Set to a value such as 1000m; to restrict file size to a specific value
client_max_body_size 0;
# To disable buffering
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300;
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://minio;
}
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ docker volume rm $(docker volume ls -f dangling=true) || true
cd .github/workflows/mint
docker-compose -f minio-${MODE}.yaml up -d
sleep 5m
sleep 30s
docker system prune -f || true
docker volume prune -f || true
+1 -1
View File
@@ -6,7 +6,7 @@ GOARCH := $(shell go env GOARCH)
GOOS := $(shell go env GOOS)
VERSION ?= $(shell git describe --tags)
TAG ?= "minio/minio:$(VERSION)"
TAG ?= "quay.io/minio/minio:$(VERSION)"
GOLANGCI_VERSION = v1.51.2
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
+4 -2
View File
@@ -1581,7 +1581,9 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
// Use buffered channel to take care of burst sends or slow w.Write()
traceCh := make(chan madmin.TraceInfo, 4000)
// Keep 100k buffered channel, should be sufficient to ensure we do not lose any events.
traceCh := make(chan madmin.TraceInfo, 100000)
peers, _ := newPeerRestClients(globalEndpoints)
@@ -1605,7 +1607,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
peer.Trace(traceCh, ctx.Done(), traceOpts)
}
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
keepAliveTicker := time.NewTicker(time.Second)
defer keepAliveTicker.Stop()
enc := json.NewEncoder(w)
+4 -2
View File
@@ -376,8 +376,10 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
} else {
// can be the case that other cluster is down and duplicate `mc rm --vid`
// is issued - this still needs to be replicated back to the other target
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
if !oi.VersionPurgeStatus.Empty() {
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
}
continue
}
}
+6 -2
View File
@@ -929,7 +929,8 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
versionID := oi.VersionID
rCfg, _ := globalBucketObjectLockSys.Get(i.bucket)
lcEvt := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, oi)
replcfg, _ := getReplicationConfig(ctx, i.bucket)
lcEvt := evalActionFromLifecycle(ctx, *i.lifeCycle, rCfg, replcfg, oi)
if i.debug {
if versionID != "" {
console.Debugf(applyActionsLogPrefix+" lifecycle: %q (version-id=%s), Initial scan: %v\n", i.objectPath(), versionID, lcEvt.Action)
@@ -1129,7 +1130,7 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
return size
}
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, obj ObjectInfo) lifecycle.Event {
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr lock.Retention, rcfg *replication.Config, obj ObjectInfo) lifecycle.Event {
event := lc.Eval(obj.ToLifecycleOpts())
if serverDebugLog {
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action)
@@ -1161,6 +1162,9 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
}
return lifecycle.Event{Action: lifecycle.NoneAction}
}
if rcfg != nil && !obj.VersionPurgeStatus.Empty() && rcfg.HasActiveRules(obj.Name, true) {
return lifecycle.Event{Action: lifecycle.NoneAction}
}
}
return event
+9 -3
View File
@@ -924,7 +924,7 @@ type objectIO interface {
// load the cache content with name from minioMetaBackgroundOpsBucket.
// Only backend errors are returned as errors.
// The loader is optimistic and has no locking, but tries 5 times before giving up.
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
// If the object is not found, a nil error with empty data usage cache is returned.
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
// By defaut, empty data usage cache
*d = dataUsageCache{}
@@ -960,13 +960,19 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
if !retry {
break
}
retry, _ = load(name+".bkp", 30*time.Second)
if !retry {
retry, err = load(name+".bkp", 30*time.Second)
if err == nil && !retry {
// Only return when we have valid data from the backup
break
}
retries++
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
}
if retries == 5 {
logger.LogOnceIf(ctx, fmt.Errorf("maximum retry reached to load the data usage cache `%s`", name), "retry-loading-data-usage-cache")
}
return nil
}
+3 -1
View File
@@ -1628,10 +1628,12 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
var lc *lifecycle.Lifecycle
var rcfg lock.Retention
var replcfg *replication.Config
if opts.Expiration.Expire {
// Check if the current bucket has a configured lifecycle policy
lc, _ = globalLifecycleSys.Get(bucket)
rcfg, _ = globalBucketObjectLockSys.Get(bucket)
replcfg, _ = getReplicationConfig(ctx, bucket)
}
// expiration attempted on a bucket with no lifecycle
@@ -1684,7 +1686,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
if opts.Expiration.Expire {
if gerr == nil {
evt := evalActionFromLifecycle(ctx, *lc, rcfg, goi)
evt := evalActionFromLifecycle(ctx, *lc, rcfg, replcfg, goi)
var isErr bool
switch evt.Action {
case lifecycle.NoneAction:
+4 -2
View File
@@ -734,6 +734,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
// Check if bucket is object locked.
lr, _ := globalBucketObjectLockSys.Get(bi.Name)
rcfg, _ := getReplicationConfig(ctx, bi.Name)
for setIdx, set := range pool.sets {
set := set
@@ -745,7 +746,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
versioned := vc != nil && vc.Versioned(object)
objInfo := fi.ToObjectInfo(bucket, object, versioned)
evt := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
evt := evalActionFromLifecycle(ctx, *lc, lr, rcfg, objInfo)
switch {
case evt.Action.DeleteRestored(): // if restored copy has expired,delete it synchronously
applyExpiryOnTransitionedObject(ctx, z, objInfo, evt, lcEventSrc_Decom)
@@ -1048,6 +1049,7 @@ func (z *erasureServerPools) checkAfterDecom(ctx context.Context, idx int) error
// Check if bucket is object locked.
lr, _ := globalBucketObjectLockSys.Get(bi.Name)
rcfg, _ := getReplicationConfig(ctx, bi.Name)
filterLifecycle := func(bucket, object string, fi FileInfo) bool {
if lc == nil {
@@ -1056,7 +1058,7 @@ func (z *erasureServerPools) checkAfterDecom(ctx context.Context, idx int) error
versioned := vc != nil && vc.Versioned(object)
objInfo := fi.ToObjectInfo(bucket, object, versioned)
evt := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
evt := evalActionFromLifecycle(ctx, *lc, lr, rcfg, objInfo)
switch {
case evt.Action.DeleteRestored(): // if restored copy has expired,delete it synchronously
applyExpiryOnTransitionedObject(ctx, z, objInfo, evt, lcEventSrc_Decom)
+2 -1
View File
@@ -440,6 +440,7 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
lc, _ := globalLifecycleSys.Get(bucket)
// Check if bucket is object locked.
lr, _ := globalBucketObjectLockSys.Get(bucket)
rcfg, _ := getReplicationConfig(ctx, bucket)
pool := z.serverPools[poolIdx]
const envRebalanceWorkers = "_MINIO_REBALANCE_WORKERS"
@@ -467,7 +468,7 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
versioned := vc != nil && vc.Versioned(object)
objInfo := fi.ToObjectInfo(bucket, object, versioned)
evt := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
evt := evalActionFromLifecycle(ctx, *lc, lr, rcfg, objInfo)
if evt.Action.Delete() {
globalExpiryState.enqueueByDays(objInfo, evt, lcEventSrc_Rebal)
return true
+81 -1
View File
@@ -25,6 +25,7 @@ import (
"io"
"math/rand"
"net/http"
"path"
"sort"
"strconv"
"strings"
@@ -1323,6 +1324,85 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
}
opts.setBucketMeta(ctx)
ri := logger.GetReqInfo(ctx)
hadoop := ri != nil && strings.Contains(ri.UserAgent, `Hadoop `) && strings.Contains(ri.UserAgent, "scala/")
matches := func() bool {
if prefix == "" {
return false
}
// List of standard files supported by s3a
// that involves a List() on a directory
// where directory is actually an object on
// namespace.
for _, k := range []string{
"_SUCCESS/",
".parquet/",
".csv/",
".json/",
".avro/",
".orc/",
".txt/",
// Add any other files in future
} {
if strings.HasSuffix(prefix, k) {
return true
}
}
return false
}
if hadoop && matches() && delimiter == SlashSeparator && maxKeys == 2 && marker == "" {
// Optimization for Spark/Hadoop workload where spark sends a garbage
// request of this kind
//
// GET /testbucket/?list-type=2&delimiter=%2F&max-keys=2&prefix=parquet%2F_SUCCESS%2F&fetch-owner=false
//
// Here spark is expecting that the List() return empty instead, so from MinIO's point
// of view if we simply do a GetObjectInfo() on this prefix by treating it as an object
// We save a lot of calls over the network.
//
// This happens repeatedly for all objects that are created concurrently() avoiding this
// as a List() call is an important performance improvement.
//
// Spark based s3a committers are a big enough use-case to have this optimization.
//
// A sample code to see the improvements is as follows, this sample code is
// simply a read on JSON from MinIO and write it back as "parquet".
//
// import org.apache.spark.sql.SparkSession
// import org.apache.spark.sql.types.{IntegerType, StringType, StructField, StructType}
// object SparkJSONRead {
// def main(args: Array[String]): Unit = {
// val spark:SparkSession = SparkSession.builder()
// .appName("SparkByExample")
// .master("local[1]").getOrCreate()
//
// spark.sparkContext.setLogLevel("ERROR")
// spark.sparkContext.hadoopConfiguration.set("fs.s3a.endpoint", "http://minio-lb:9000")
// spark.sparkContext.hadoopConfiguration.set("fs.s3a.path.style.access", "true")
// spark.sparkContext.hadoopConfiguration.set("fs.s3a.access.key", "minioadmin")
// spark.sparkContext.hadoopConfiguration.set("fs.s3a.secret.key", "minioadmin")
//
// val df = spark.read.json("s3a://testbucket/s3.json")
//
// df.write.parquet("s3a://testbucket/parquet/")
// }
// }
objInfo, err := z.GetObjectInfo(ctx, bucket, path.Dir(prefix), ObjectOptions{NoLock: true})
if err == nil {
if opts.Lifecycle != nil {
evt := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, opts.Replication.Config, objInfo)
if evt.Action.Delete() {
globalExpiryState.enqueueByDays(objInfo, evt, lcEventSrc_s3ListObjects)
if !evt.Action.DeleteRestored() {
// Skip entry if ILM action was DeleteVersionAction or DeleteAction
return loi, nil
}
}
}
return loi, nil
}
}
if len(prefix) > 0 && maxKeys == 1 && marker == "" {
// Optimization for certain applications like
// - Cohesity
@@ -1334,7 +1414,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
objInfo, err := z.GetObjectInfo(ctx, bucket, prefix, ObjectOptions{NoLock: true})
if err == nil {
if opts.Lifecycle != nil {
evt := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo)
evt := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, opts.Replication.Config, objInfo)
if evt.Action.Delete() {
globalExpiryState.enqueueByDays(objInfo, evt, lcEventSrc_s3ListObjects)
if !evt.Action.DeleteRestored() {
+2 -1
View File
@@ -231,7 +231,8 @@ func guessIsMetricsReq(req *http.Request) bool {
req.URL.Path == minioReservedBucketPath+prometheusMetricsPathLegacy ||
req.URL.Path == minioReservedBucketPath+prometheusMetricsV2ClusterPath ||
req.URL.Path == minioReservedBucketPath+prometheusMetricsV2NodePath ||
req.URL.Path == minioReservedBucketPath+prometheusMetricsV2BucketPath
req.URL.Path == minioReservedBucketPath+prometheusMetricsV2BucketPath ||
req.URL.Path == minioReservedBucketPath+prometheusMetricsV2ResourcePath
}
// guessIsRPCReq - returns true if the request is for an RPC endpoint.
+1 -1
View File
@@ -116,7 +116,7 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
// Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers.
// Use buffered channel to take care of burst sends or slow w.Write()
listenCh := make(chan event.Event, 4000)
listenCh := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity()*len(globalEndpoints.Hostnames()))
peers, _ := newPeerRestClients(globalEndpoints)
+2 -2
View File
@@ -370,7 +370,7 @@ func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCa
objInfo := fi.ToObjectInfo(o.Bucket, obj.name, versioned)
if o.Lifecycle != nil {
act := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, objInfo).Action
act := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, o.Replication.Config, objInfo).Action
skip = act.Delete()
if act.DeleteRestored() {
// do not skip DeleteRestored* actions
@@ -398,7 +398,7 @@ func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCa
objInfo := version.ToObjectInfo(o.Bucket, obj.name, versioned)
if o.Lifecycle != nil {
evt := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, objInfo)
evt := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, o.Replication.Config, objInfo)
if evt.Action.Delete() {
globalExpiryState.enqueueByDays(objInfo, evt, lcEventSrc_s3ListObjects)
if !evt.Action.DeleteRestored() {
+31
View File
@@ -19,12 +19,15 @@ package cmd
import (
"context"
"fmt"
"net/http"
"time"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/disk"
"github.com/minio/minio/internal/net"
c "github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/load"
)
type collectMetricsOpts struct {
@@ -89,6 +92,34 @@ func collectLocalMetrics(types madmin.MetricType, opts collectMetricsOpts) (m ma
m.Aggregated.Net.NetStats = netStats
}
}
if types.Contains(madmin.MetricsMem) {
m.Aggregated.Mem = &madmin.MemMetrics{
CollectedAt: UTCNow(),
}
m.Aggregated.Mem.Info = madmin.GetMemInfo(GlobalContext, globalMinioAddr)
}
if types.Contains(madmin.MetricsCPU) {
m.Aggregated.CPU = &madmin.CPUMetrics{
CollectedAt: UTCNow(),
}
cm, err := c.Times(false)
if err != nil {
m.Errors = append(m.Errors, err.Error())
} else {
// not collecting per-cpu stats, so there will be only one element
if len(cm) == 1 {
m.Aggregated.CPU.TimesStat = &cm[0]
} else {
m.Errors = append(m.Errors, fmt.Sprintf("Expected one CPU stat, got %d", len(cm)))
}
}
loadStat, err := load.Avg()
if err != nil {
m.Errors = append(m.Errors, err.Error())
} else {
m.Aggregated.CPU.LoadStat = loadStat
}
}
// Add types...
// ByHost is a shallow reference, so careful about sharing.
+453
View File
@@ -0,0 +1,453 @@
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"context"
"fmt"
"math"
"net/http"
"sync"
"time"
"github.com/minio/madmin-go/v3"
"github.com/prometheus/client_golang/prometheus"
"github.com/shirou/gopsutil/v3/host"
)
const (
resourceMetricsCollectionInterval = time.Minute
resourceMetricsCacheInterval = time.Minute
// drive stats
totalInodes MetricName = "total_inodes"
readsPerSec MetricName = "reads_per_sec"
writesPerSec MetricName = "writes_per_sec"
readsKBPerSec MetricName = "reads_kb_per_sec"
writesKBPerSec MetricName = "writes_kb_per_sec"
readsAwait MetricName = "reads_await"
writesAwait MetricName = "writes_await"
percUtil MetricName = "perc_util"
usedInodes MetricName = "used_inodes"
// network stats
interfaceRxBytes MetricName = "rx_bytes"
interfaceRxErrors MetricName = "rx_errors"
interfaceTxBytes MetricName = "tx_bytes"
interfaceTxErrors MetricName = "tx_errors"
// memory stats
memUsed MetricName = "used"
memFree MetricName = "free"
memShared MetricName = "shared"
memBuffers MetricName = "buffers"
memCache MetricName = "cache"
memAvailable MetricName = "available"
// cpu stats
cpuUser MetricName = "user"
cpuSystem MetricName = "system"
cpuIOWait MetricName = "iowait"
cpuIdle MetricName = "idle"
cpuNice MetricName = "nice"
cpuSteal MetricName = "steal"
cpuLoad1 MetricName = "load1"
cpuLoad5 MetricName = "load5"
cpuLoad15 MetricName = "load15"
)
var (
resourceCollector *minioResourceCollector
// resourceMetricsMap is a map of subsystem to its metrics
resourceMetricsMap map[MetricSubsystem]ResourceMetrics
// resourceMetricsHelpMap maps metric name to its help string
resourceMetricsHelpMap map[MetricName]string
resourceMetricsGroups []*MetricsGroup
)
// PeerResourceMetrics represents the resource metrics
// retrieved from a peer, along with errors if any
type PeerResourceMetrics struct {
Metrics map[MetricSubsystem]ResourceMetrics
Errors []string
}
// ResourceMetrics is a map of unique key identifying
// a resource metric (e.g. reads_per_sec_{node}_{drive})
// to its data
type ResourceMetrics map[string]ResourceMetric
// ResourceMetric represents a single resource metric
// The metrics are collected from all servers periodically
// and stored in the resource metrics map.
// It also maintains the count of number of times this metric
// was collected since the server started, and the sum,
// average and max values across the same.
type ResourceMetric struct {
Name MetricName
Labels map[string]string
// value captured in current cycle
Current float64
// Used when system provides cumulative (since uptime) values
// helps in calculating the current value by comparing the new
// cumulative value with previous one
Cumulative float64
Max float64
Avg float64
Sum float64
Count uint64
}
func init() {
interval := fmt.Sprintf("%ds", int(resourceMetricsCollectionInterval.Seconds()))
resourceMetricsHelpMap = map[MetricName]string{
interfaceRxBytes: "Bytes received on the interface in " + interval,
interfaceRxErrors: "Receive errors in " + interval,
interfaceTxBytes: "Bytes transmitted in " + interval,
interfaceTxErrors: "Transmit errors in " + interval,
total: "Total memory on the node",
memUsed: "Used memory on the node",
memFree: "Free memory on the node",
memShared: "Shared memory on the node",
memBuffers: "Buffers memory on the node",
memCache: "Cache memory on the node",
memAvailable: "Available memory on the node",
readsPerSec: "Reads per second on a drive",
writesPerSec: "Writes per second on a drive",
readsKBPerSec: "Kilobytes read per second on a drive",
writesKBPerSec: "Kilobytes written per second on a drive",
readsAwait: "Average time for read requests to be served on a drive",
writesAwait: "Average time for write requests to be served on a drive",
percUtil: "Percentage of time the disk was busy since uptime",
usedBytes: "Used bytes on a drive",
totalBytes: "Total bytes on a drive",
usedInodes: "Total inodes used on a drive",
totalInodes: "Total inodes on a drive",
cpuUser: "CPU user time",
cpuSystem: "CPU system time",
cpuIdle: "CPU idle time",
cpuIOWait: "CPU ioWait time",
cpuSteal: "CPU steal time",
cpuNice: "CPU nice time",
cpuLoad1: "CPU load average 1min",
cpuLoad5: "CPU load average 5min",
cpuLoad15: "CPU load average 15min",
}
resourceMetricsGroups = []*MetricsGroup{
getResourceMetrics(),
}
resourceCollector = newMinioResourceCollector(resourceMetricsGroups)
}
func updateResourceMetrics(subSys MetricSubsystem, name MetricName, val float64, labels map[string]string, isCumulative bool) {
subsysMetrics, found := resourceMetricsMap[subSys]
if !found {
subsysMetrics = ResourceMetrics{}
}
// labels are used to uniquely identify a metric
// e.g. reads_per_sec_{drive} inside the map
sfx := ""
for _, v := range labels {
if len(sfx) > 0 {
sfx += "_"
}
sfx += v
}
key := string(name) + "_" + sfx
metric, found := subsysMetrics[key]
if !found {
metric = ResourceMetric{
Name: name,
Labels: labels,
}
}
if isCumulative {
metric.Current = val - metric.Cumulative
metric.Cumulative = val
} else {
metric.Current = val
}
if metric.Current > metric.Max {
metric.Max = val
}
metric.Sum += metric.Current
metric.Count++
metric.Avg = metric.Sum / float64(metric.Count)
subsysMetrics[key] = metric
resourceMetricsMap[subSys] = subsysMetrics
}
func collectDriveMetrics(m madmin.RealtimeMetrics) {
upt, _ := host.Uptime()
kib := 1 << 10
sectorSize := uint64(512)
for d, dm := range m.ByDisk {
stats := dm.IOStats
labels := map[string]string{"drive": d}
updateResourceMetrics(driveSubsystem, readsPerSec, float64(stats.ReadIOs)/float64(upt), labels, false)
readBytes := stats.ReadSectors * sectorSize
readKib := float64(readBytes) / float64(kib)
readKibPerSec := readKib / float64(upt)
updateResourceMetrics(driveSubsystem, readsKBPerSec, readKibPerSec, labels, false)
updateResourceMetrics(driveSubsystem, writesPerSec, float64(stats.WriteIOs)/float64(upt), labels, false)
writeBytes := stats.WriteSectors * sectorSize
writeKib := float64(writeBytes) / float64(kib)
writeKibPerSec := writeKib / float64(upt)
updateResourceMetrics(driveSubsystem, writesKBPerSec, writeKibPerSec, labels, false)
rdAwait := 0.0
if stats.ReadIOs > 0 {
rdAwait = float64(stats.ReadTicks) / float64(stats.ReadIOs)
}
updateResourceMetrics(driveSubsystem, readsAwait, rdAwait, labels, false)
wrAwait := 0.0
if stats.WriteIOs > 0 {
wrAwait = float64(stats.WriteTicks) / float64(stats.WriteIOs)
}
updateResourceMetrics(driveSubsystem, writesAwait, wrAwait, labels, false)
updateResourceMetrics(driveSubsystem, percUtil, float64(stats.TotalTicks)/float64(upt*10), labels, false)
}
globalLocalDrivesMu.RLock()
gld := globalLocalDrives
globalLocalDrivesMu.RUnlock()
for _, d := range gld {
labels := map[string]string{"drive": d.Endpoint().RawPath}
di, err := d.DiskInfo(GlobalContext, false)
if err == nil {
updateResourceMetrics(driveSubsystem, usedBytes, float64(di.Used), labels, false)
updateResourceMetrics(driveSubsystem, totalBytes, float64(di.Total), labels, false)
updateResourceMetrics(driveSubsystem, usedInodes, float64(di.UsedInodes), labels, false)
updateResourceMetrics(driveSubsystem, totalInodes, float64(di.FreeInodes+di.UsedInodes), labels, false)
}
}
}
func collectLocalResourceMetrics() {
var types madmin.MetricType = madmin.MetricsDisk | madmin.MetricNet | madmin.MetricsMem | madmin.MetricsCPU
m := collectLocalMetrics(types, collectMetricsOpts{
hosts: map[string]struct{}{
globalLocalNodeName: {},
},
})
for host, hm := range m.ByHost {
if len(host) > 0 {
if hm.Net != nil && len(hm.Net.NetStats.Name) > 0 {
stats := hm.Net.NetStats
labels := map[string]string{"interface": stats.Name}
updateResourceMetrics(interfaceSubsystem, interfaceRxBytes, float64(stats.RxBytes), labels, true)
updateResourceMetrics(interfaceSubsystem, interfaceRxErrors, float64(stats.RxErrors), labels, true)
updateResourceMetrics(interfaceSubsystem, interfaceTxBytes, float64(stats.TxBytes), labels, true)
updateResourceMetrics(interfaceSubsystem, interfaceTxErrors, float64(stats.TxErrors), labels, true)
}
if hm.Mem != nil && len(hm.Mem.Info.Addr) > 0 {
labels := map[string]string{}
stats := hm.Mem.Info
updateResourceMetrics(memSubsystem, total, float64(stats.Total), labels, false)
updateResourceMetrics(memSubsystem, memUsed, float64(stats.Used), labels, false)
updateResourceMetrics(memSubsystem, memFree, float64(stats.Free), labels, false)
updateResourceMetrics(memSubsystem, memShared, float64(stats.Shared), labels, false)
updateResourceMetrics(memSubsystem, memBuffers, float64(stats.Buffers), labels, false)
updateResourceMetrics(memSubsystem, memAvailable, float64(stats.Available), labels, false)
updateResourceMetrics(memSubsystem, memCache, float64(stats.Cache), labels, false)
}
if hm.CPU != nil {
labels := map[string]string{}
ts := hm.CPU.TimesStat
if ts != nil {
tot := ts.User + ts.System + ts.Idle + ts.Iowait + ts.Nice + ts.Steal
cpuUserVal := math.Round(ts.User/tot*100*100) / 100
updateResourceMetrics(cpuSubsystem, cpuUser, cpuUserVal, labels, false)
cpuSystemVal := math.Round(ts.System/tot*100*100) / 100
updateResourceMetrics(cpuSubsystem, cpuSystem, cpuSystemVal, labels, false)
cpuIdleVal := math.Round(ts.Idle/tot*100*100) / 100
updateResourceMetrics(cpuSubsystem, cpuIdle, cpuIdleVal, labels, false)
cpuIOWaitVal := math.Round(ts.Iowait/tot*100*100) / 100
updateResourceMetrics(cpuSubsystem, cpuIOWait, cpuIOWaitVal, labels, false)
cpuNiceVal := math.Round(ts.Nice/tot*100*100) / 100
updateResourceMetrics(cpuSubsystem, cpuNice, cpuNiceVal, labels, false)
cpuStealVal := math.Round(ts.Steal/tot*100*100) / 100
updateResourceMetrics(cpuSubsystem, cpuSteal, cpuStealVal, labels, false)
}
ls := hm.CPU.LoadStat
if ls != nil {
updateResourceMetrics(cpuSubsystem, cpuLoad1, ls.Load1, labels, false)
updateResourceMetrics(cpuSubsystem, cpuLoad5, ls.Load5, labels, false)
updateResourceMetrics(cpuSubsystem, cpuLoad15, ls.Load15, labels, false)
}
}
break // only one host expected
}
}
collectDriveMetrics(m)
}
// startResourceMetricsCollection - starts the job for collecting resource metrics
func startResourceMetricsCollection() {
resourceMetricsMap = map[MetricSubsystem]ResourceMetrics{}
metricsTimer := time.NewTimer(resourceMetricsCollectionInterval)
defer metricsTimer.Stop()
collectLocalResourceMetrics()
for {
select {
case <-GlobalContext.Done():
return
case <-metricsTimer.C:
collectLocalResourceMetrics()
// Reset the timer for next cycle.
metricsTimer.Reset(resourceMetricsCollectionInterval)
}
}
}
// minioResourceCollector is the Collector for resource metrics
type minioResourceCollector struct {
metricsGroups []*MetricsGroup
desc *prometheus.Desc
}
// Describe sends the super-set of all possible descriptors of metrics
func (c *minioResourceCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.desc
}
// Collect is called by the Prometheus registry when collecting metrics.
func (c *minioResourceCollector) Collect(out chan<- prometheus.Metric) {
var wg sync.WaitGroup
publish := func(in <-chan Metric) {
defer wg.Done()
for metric := range in {
labels, values := getOrderedLabelValueArrays(metric.VariableLabels)
collectMetric(metric, labels, values, "resource", out)
}
}
// Call peer api to fetch metrics
wg.Add(2)
go publish(ReportMetrics(GlobalContext, c.metricsGroups))
go publish(globalNotificationSys.GetResourceMetrics(GlobalContext))
wg.Wait()
}
// newMinioResourceCollector describes the collector
// and returns reference of minio resource Collector
// It creates the Prometheus Description which is used
// to define Metric and help string
func newMinioResourceCollector(metricsGroups []*MetricsGroup) *minioResourceCollector {
return &minioResourceCollector{
metricsGroups: metricsGroups,
desc: prometheus.NewDesc("minio_resource_stats", "Resource statistics exposed by MinIO server", nil, nil),
}
}
func prepareResourceMetrics(rm ResourceMetric, subSys MetricSubsystem, requireAvgMax bool) []Metric {
help := resourceMetricsHelpMap[rm.Name]
name := rm.Name
metrics := []Metric{}
metrics = append(metrics, Metric{
Description: getResourceMetricDescription(subSys, name, help),
Value: rm.Current,
VariableLabels: rm.Labels,
})
if requireAvgMax {
avgName := MetricName(fmt.Sprintf("%s_avg", name))
avgHelp := fmt.Sprintf("%s (avg)", help)
metrics = append(metrics, Metric{
Description: getResourceMetricDescription(subSys, avgName, avgHelp),
Value: math.Round(rm.Avg*100) / 100,
VariableLabels: rm.Labels,
})
maxName := MetricName(fmt.Sprintf("%s_max", name))
maxHelp := fmt.Sprintf("%s (max)", help)
metrics = append(metrics, Metric{
Description: getResourceMetricDescription(subSys, maxName, maxHelp),
Value: rm.Max,
VariableLabels: rm.Labels,
})
}
return metrics
}
func getResourceMetricDescription(subSys MetricSubsystem, name MetricName, help string) MetricDescription {
return MetricDescription{
Namespace: nodeMetricNamespace,
Subsystem: subSys,
Name: name,
Help: help,
Type: gaugeMetric,
}
}
func getResourceMetrics() *MetricsGroup {
mg := &MetricsGroup{
cacheInterval: resourceMetricsCacheInterval,
}
mg.RegisterRead(func(ctx context.Context) []Metric {
metrics := []Metric{}
subSystems := []MetricSubsystem{interfaceSubsystem, memSubsystem, driveSubsystem, cpuSubsystem}
for _, subSys := range subSystems {
stats, found := resourceMetricsMap[subSys]
if found {
requireAvgMax := true
if subSys == driveSubsystem {
requireAvgMax = false
}
for _, m := range stats {
metrics = append(metrics, prepareResourceMetrics(m, subSys, requireAvgMax)...)
}
}
}
return metrics
})
return mg
}
// metricsResourceHandler is the prometheus handler for resource metrics
func metricsResourceHandler() http.Handler {
return metricsHTTPHandler(resourceCollector, "handler.MetricsResource")
}
+6 -4
View File
@@ -25,10 +25,11 @@ import (
)
const (
prometheusMetricsPathLegacy = "/prometheus/metrics"
prometheusMetricsV2ClusterPath = "/v2/metrics/cluster"
prometheusMetricsV2BucketPath = "/v2/metrics/bucket"
prometheusMetricsV2NodePath = "/v2/metrics/node"
prometheusMetricsPathLegacy = "/prometheus/metrics"
prometheusMetricsV2ClusterPath = "/v2/metrics/cluster"
prometheusMetricsV2BucketPath = "/v2/metrics/bucket"
prometheusMetricsV2NodePath = "/v2/metrics/node"
prometheusMetricsV2ResourcePath = "/v2/metrics/resource"
)
// Standard env prometheus auth type
@@ -57,4 +58,5 @@ func registerMetricsRouter(router *mux.Router) {
metricsRouter.Handle(prometheusMetricsV2ClusterPath, auth(metricsServerHandler()))
metricsRouter.Handle(prometheusMetricsV2BucketPath, auth(metricsBucketHandler()))
metricsRouter.Handle(prometheusMetricsV2NodePath, auth(metricsNodeHandler()))
metricsRouter.Handle(prometheusMetricsV2ResourcePath, auth(metricsResourceHandler()))
}
+73 -110
View File
@@ -132,6 +132,9 @@ const (
capacityRawSubsystem MetricSubsystem = "capacity_raw"
capacityUsableSubsystem MetricSubsystem = "capacity_usable"
driveSubsystem MetricSubsystem = "drive"
interfaceSubsystem MetricSubsystem = "if"
memSubsystem MetricSubsystem = "mem"
cpuSubsystem MetricSubsystem = "cpu_avg"
storageClassSubsystem MetricSubsystem = "storage_class"
fileDescriptorSubsystem MetricSubsystem = "file_descriptor"
goRoutines MetricSubsystem = "go_routine"
@@ -539,12 +542,12 @@ func getNodeRRSParityMD() MetricDescription {
}
}
func getNodeDrivesFreeInodes() MetricDescription {
func getNodeDrivesFreeInodesMD() MetricDescription {
return MetricDescription{
Namespace: nodeMetricNamespace,
Subsystem: driveSubsystem,
Name: freeInodes,
Help: "Total free inodes",
Help: "Free inodes on a drive",
Type: gaugeMetric,
}
}
@@ -3207,7 +3210,7 @@ func getLocalStorageMetrics() *MetricsGroup {
})
metrics = append(metrics, Metric{
Description: getNodeDrivesFreeInodes(),
Description: getNodeDrivesFreeInodesMD(),
Value: float64(disk.FreeInodes),
VariableLabels: map[string]string{"drive": disk.DrivePath},
})
@@ -3546,6 +3549,61 @@ func getKMSMetrics() *MetricsGroup {
return mg
}
func collectMetric(metric Metric, labels []string, values []string, metricName string, out chan<- prometheus.Metric) {
if metric.Description.Type == histogramMetric {
if metric.Histogram == nil {
return
}
for k, v := range metric.Histogram {
pmetric, err := prometheus.NewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(string(metric.Description.Namespace),
string(metric.Description.Subsystem),
string(metric.Description.Name)),
metric.Description.Help,
append(labels, metric.HistogramBucketLabel),
metric.StaticLabels,
),
prometheus.GaugeValue,
float64(v),
append(values, k)...)
if err != nil {
// Enable for debugging
if serverDebugLog {
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to validate prometheus metric (%w) %v+%v", err, values, metric.Histogram), metricName+"-metrics-histogram")
}
} else {
out <- pmetric
}
}
return
}
metricType := prometheus.GaugeValue
if metric.Description.Type == counterMetric {
metricType = prometheus.CounterValue
}
pmetric, err := prometheus.NewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(string(metric.Description.Namespace),
string(metric.Description.Subsystem),
string(metric.Description.Name)),
metric.Description.Help,
labels,
metric.StaticLabels,
),
metricType,
metric.Value,
values...)
if err != nil {
// Enable for debugging
if serverDebugLog {
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to validate prometheus metric (%w) %v", err, values), metricName+"-metrics")
}
} else {
out <- pmetric
}
}
type minioBucketCollector struct {
metricsGroups []*MetricsGroup
desc *prometheus.Desc
@@ -3570,52 +3628,7 @@ func (c *minioBucketCollector) Collect(out chan<- prometheus.Metric) {
defer wg.Done()
for metric := range in {
labels, values := getOrderedLabelValueArrays(metric.VariableLabels)
if metric.Description.Type == histogramMetric {
if metric.Histogram == nil {
continue
}
for k, v := range metric.Histogram {
pmetric, err := prometheus.NewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(string(metric.Description.Namespace),
string(metric.Description.Subsystem),
string(metric.Description.Name)),
metric.Description.Help,
append(labels, metric.HistogramBucketLabel),
metric.StaticLabels,
),
prometheus.GaugeValue,
float64(v),
append(values, k)...)
if err != nil {
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to validate prometheus metric (%w) %v+%v", err, values, metric.Histogram), "bucket-metrics-histogram")
} else {
out <- pmetric
}
}
continue
}
metricType := prometheus.GaugeValue
if metric.Description.Type == counterMetric {
metricType = prometheus.CounterValue
}
pmetric, err := prometheus.NewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(string(metric.Description.Namespace),
string(metric.Description.Subsystem),
string(metric.Description.Name)),
metric.Description.Help,
labels,
metric.StaticLabels,
),
metricType,
metric.Value,
values...)
if err != nil {
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to validate prometheus metric (%w) %v", err, values), "bucket-metrics")
} else {
out <- pmetric
}
collectMetric(metric, labels, values, "bucket", out)
}
}
@@ -3650,59 +3663,7 @@ func (c *minioClusterCollector) Collect(out chan<- prometheus.Metric) {
defer wg.Done()
for metric := range in {
labels, values := getOrderedLabelValueArrays(metric.VariableLabels)
if metric.Description.Type == histogramMetric {
if metric.Histogram == nil {
continue
}
for k, v := range metric.Histogram {
pmetric, err := prometheus.NewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(string(metric.Description.Namespace),
string(metric.Description.Subsystem),
string(metric.Description.Name)),
metric.Description.Help,
append(labels, metric.HistogramBucketLabel),
metric.StaticLabels,
),
prometheus.GaugeValue,
float64(v),
append(values, k)...)
if err != nil {
// Enable for debugging
if serverDebugLog {
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to validate prometheus metric (%w) %v:%v", err, values, metric.Histogram), "cluster-metrics-histogram")
}
} else {
out <- pmetric
}
}
continue
}
metricType := prometheus.GaugeValue
if metric.Description.Type == counterMetric {
metricType = prometheus.CounterValue
}
pmetric, err := prometheus.NewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(string(metric.Description.Namespace),
string(metric.Description.Subsystem),
string(metric.Description.Name)),
metric.Description.Help,
labels,
metric.StaticLabels,
),
metricType,
metric.Value,
values...)
if err != nil {
// Enable for debugging
if serverDebugLog {
logger.LogOnceIf(GlobalContext, fmt.Errorf("unable to validate prometheus metric (%w) %v", err, values), "cluster-metrics")
}
} else {
out <- pmetric
}
collectMetric(metric, labels, values, "cluster", out)
}
}
@@ -3835,11 +3796,11 @@ func newMinioCollectorNode(metricsGroups []*MetricsGroup) *minioNodeCollector {
}
}
func metricsBucketHandler() http.Handler {
func metricsHTTPHandler(c prometheus.Collector, funcName string) http.Handler {
registry := prometheus.NewRegistry()
// Report all other metrics
logger.CriticalIf(GlobalContext, registry.Register(bucketCollector))
logger.CriticalIf(GlobalContext, registry.Register(c))
// DefaultGatherers include golang metrics and process metrics.
gatherers := prometheus.Gatherers{
@@ -3849,16 +3810,14 @@ func metricsBucketHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
if ok {
tc.FuncName = "handler.MetricsBucket"
tc.FuncName = funcName
tc.ResponseRecorder.LogErrBody = true
}
mfs, err := gatherers.Gather()
if err != nil {
if len(mfs) == 0 {
writeErrorResponseJSON(r.Context(), w, toAdminAPIErr(r.Context(), err), r.URL)
return
}
if err != nil && len(mfs) == 0 {
writeErrorResponseJSON(r.Context(), w, toAdminAPIErr(r.Context(), err), r.URL)
return
}
contentType := expfmt.Negotiate(r.Header)
@@ -3878,6 +3837,10 @@ func metricsBucketHandler() http.Handler {
})
}
func metricsBucketHandler() http.Handler {
return metricsHTTPHandler(bucketCollector, "handler.MetricsBucket")
}
func metricsServerHandler() http.Handler {
registry := prometheus.NewRegistry()
+44 -58
View File
@@ -783,6 +783,27 @@ func (sys *NotificationSys) GetMetrics(ctx context.Context, t madmin.MetricType,
return reply
}
// GetResourceMetrics - gets the resource metrics from all nodes excluding self.
func (sys *NotificationSys) GetResourceMetrics(ctx context.Context) <-chan Metric {
if sys == nil {
return nil
}
g := errgroup.WithNErrs(len(sys.peerClients))
peerChannels := make([]<-chan Metric, len(sys.peerClients))
for index := range sys.peerClients {
index := index
g.Go(func() error {
if sys.peerClients[index] == nil {
return errPeerNotReachable
}
var err error
peerChannels[index], err = sys.peerClients[index].GetResourceMetrics(ctx)
return err
}, index)
}
return sys.collectPeerMetrics(ctx, peerChannels, g)
}
// GetSysConfig - Get information about system config
// (only the config that are of concern to minio)
func (sys *NotificationSys) GetSysConfig(ctx context.Context) []madmin.SysConfig {
@@ -1122,25 +1143,7 @@ func (sys *NotificationSys) GetBandwidthReports(ctx context.Context, buckets ...
return consolidatedReport
}
// GetBucketMetrics - gets the cluster level bucket metrics from all nodes excluding self.
func (sys *NotificationSys) GetBucketMetrics(ctx context.Context) <-chan Metric {
if sys == nil {
return nil
}
g := errgroup.WithNErrs(len(sys.peerClients))
peerChannels := make([]<-chan Metric, len(sys.peerClients))
for index := range sys.peerClients {
index := index
g.Go(func() error {
if sys.peerClients[index] == nil {
return errPeerNotReachable
}
var err error
peerChannels[index], err = sys.peerClients[index].GetPeerBucketMetrics(ctx)
return err
}, index)
}
func (sys *NotificationSys) collectPeerMetrics(ctx context.Context, peerChannels []<-chan Metric, g *errgroup.Group) <-chan Metric {
ch := make(chan Metric)
var wg sync.WaitGroup
for index, err := range g.Wait() {
@@ -1181,6 +1184,27 @@ func (sys *NotificationSys) GetBucketMetrics(ctx context.Context) <-chan Metric
return ch
}
// GetBucketMetrics - gets the cluster level bucket metrics from all nodes excluding self.
func (sys *NotificationSys) GetBucketMetrics(ctx context.Context) <-chan Metric {
if sys == nil {
return nil
}
g := errgroup.WithNErrs(len(sys.peerClients))
peerChannels := make([]<-chan Metric, len(sys.peerClients))
for index := range sys.peerClients {
index := index
g.Go(func() error {
if sys.peerClients[index] == nil {
return errPeerNotReachable
}
var err error
peerChannels[index], err = sys.peerClients[index].GetPeerBucketMetrics(ctx)
return err
}, index)
}
return sys.collectPeerMetrics(ctx, peerChannels, g)
}
// GetClusterMetrics - gets the cluster metrics from all nodes excluding self.
func (sys *NotificationSys) GetClusterMetrics(ctx context.Context) <-chan Metric {
if sys == nil {
@@ -1199,45 +1223,7 @@ func (sys *NotificationSys) GetClusterMetrics(ctx context.Context) <-chan Metric
return err
}, index)
}
ch := make(chan Metric)
var wg sync.WaitGroup
for index, err := range g.Wait() {
if err != nil {
if sys.peerClients[index] != nil {
reqInfo := (&logger.ReqInfo{}).AppendTags("peerAddress",
sys.peerClients[index].host.String())
logger.LogOnceIf(logger.SetReqInfo(ctx, reqInfo), err, sys.peerClients[index].host.String())
} else {
logger.LogOnceIf(ctx, err, "peer-offline")
}
continue
}
wg.Add(1)
go func(ctx context.Context, peerChannel <-chan Metric, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case m, ok := <-peerChannel:
if !ok {
return
}
select {
case ch <- m:
case <-ctx.Done():
return
}
case <-ctx.Done():
return
}
}
}(ctx, peerChannels[index], &wg)
}
go func(wg *sync.WaitGroup, ch chan Metric) {
wg.Wait()
close(ch)
}(&wg, ch)
return ch
return sys.collectPeerMetrics(ctx, peerChannels, g)
}
// ServiceFreeze freezes all S3 API calls when 'freeze' is true,
+4 -2
View File
@@ -474,7 +474,8 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
// Automatically remove the object/version if an expiry lifecycle rule can be applied
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
event := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo)
replcfg, _ := getReplicationConfig(ctx, bucket)
event := evalActionFromLifecycle(ctx, *lc, rcfg, replcfg, objInfo)
if event.Action.Delete() {
// apply whatever the expiry rule is.
applyExpiryRule(event, lcEventSrc_s3GetObject, objInfo)
@@ -732,7 +733,8 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
// Automatically remove the object/version if an expiry lifecycle rule can be applied
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
event := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo)
replcfg, _ := getReplicationConfig(ctx, bucket)
event := evalActionFromLifecycle(ctx, *lc, rcfg, replcfg, objInfo)
if event.Action.Delete() {
// apply whatever the expiry rule is.
applyExpiryRule(event, lcEventSrc_s3HeadObject, objInfo)
+27
View File
@@ -229,6 +229,33 @@ func (client *peerRESTClient) GetMetrics(ctx context.Context, t madmin.MetricTyp
return info, err
}
func (client *peerRESTClient) GetResourceMetrics(ctx context.Context) (<-chan Metric, error) {
respBody, err := client.callWithContext(ctx, peerRESTMethodResourceMetrics, nil, nil, -1)
if err != nil {
return nil, err
}
dec := gob.NewDecoder(respBody)
ch := make(chan Metric)
go func(ch chan<- Metric) {
defer func() {
xhttp.DrainBody(respBody)
close(ch)
}()
for {
var metric Metric
if err := dec.Decode(&metric); err != nil {
return
}
select {
case <-ctx.Done():
return
case ch <- metric:
}
}
}(ch)
return ch, nil
}
// GetProcInfo - fetch MinIO process information for a remote node.
func (client *peerRESTClient) GetProcInfo(ctx context.Context) (info madmin.ProcInfo, err error) {
respBody, err := client.callWithContext(ctx, peerRESTMethodProcInfo, nil, nil, -1)
+1
View File
@@ -77,6 +77,7 @@ const (
peerRESTMethodDevNull = "/devnull"
peerRESTMethodNetperf = "/netperf"
peerRESTMethodMetrics = "/metrics"
peerRESTMethodResourceMetrics = "/resourcemetrics"
peerRESTMethodGetReplicationMRF = "/getreplicationmrf"
peerRESTMethodGetSRMetrics = "/getsrmetrics"
)
+21 -4
View File
@@ -474,6 +474,22 @@ func (s *peerRESTServer) GetMetricsHandler(w http.ResponseWriter, r *http.Reques
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
}
func (s *peerRESTServer) GetResourceMetrics(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("invalid request"))
return
}
enc := gob.NewEncoder(w)
for m := range ReportMetrics(r.Context(), resourceMetricsGroups) {
if err := enc.Encode(m); err != nil {
s.writeErrorResponse(w, errors.New("Encoding metric failed: "+err.Error()))
return
}
}
}
// GetSysConfigHandler - returns system config information.
// (only the config that are of concern to minio)
func (s *peerRESTServer) GetSysConfigHandler(w http.ResponseWriter, r *http.Request) {
@@ -939,7 +955,7 @@ func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
// Listen Publisher uses nonblocking publish and hence does not wait for slow subscribers.
// Use buffered channel to take care of burst sends or slow w.Write()
ch := make(chan event.Event, 2000)
ch := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity())
err := globalHTTPListen.Subscribe(mask, ch, doneCh, func(ev event.Event) bool {
if ev.S3.Bucket.Name != "" && values.Get(peerRESTListenBucket) != "" {
@@ -994,7 +1010,7 @@ func (s *peerRESTServer) TraceHandler(w http.ResponseWriter, r *http.Request) {
// Trace Publisher uses nonblocking publish and hence does not wait for slow subscribers.
// Use buffered channel to take care of burst sends or slow w.Write()
ch := make(chan madmin.TraceInfo, 2000)
ch := make(chan madmin.TraceInfo, 100000)
err = globalTrace.Subscribe(traceOpts.TraceTypes(), ch, r.Context().Done(), func(entry madmin.TraceInfo) bool {
return shouldTrace(entry, traceOpts)
})
@@ -1143,13 +1159,13 @@ func (s *peerRESTServer) ConsoleLogHandler(w http.ResponseWriter, r *http.Reques
doneCh := make(chan struct{})
defer close(doneCh)
ch := make(chan log.Info, 2000)
ch := make(chan log.Info, 100000)
err := globalConsoleSys.Subscribe(ch, doneCh, "", 0, madmin.LogMaskAll, nil)
if err != nil {
s.writeErrorResponse(w, err)
return
}
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
keepAliveTicker := time.NewTicker(time.Second)
defer keepAliveTicker.Stop()
enc := gob.NewEncoder(w)
@@ -1438,6 +1454,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodProcInfo).HandlerFunc(h(server.GetProcInfoHandler))
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodMemInfo).HandlerFunc(h(server.GetMemInfoHandler))
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodMetrics).HandlerFunc(h(server.GetMetricsHandler)).Queries(restQueries(peerRESTMetricsTypes)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodResourceMetrics).HandlerFunc(h(server.GetResourceMetrics))
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysErrors).HandlerFunc(h(server.GetSysErrorsHandler))
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysServices).HandlerFunc(h(server.GetSysServicesHandler))
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSysConfig).HandlerFunc(h(server.GetSysConfigHandler))
+4
View File
@@ -928,6 +928,10 @@ func serverMain(ctx *cli.Context) {
logger.FatalIf(err, "Unable to initialize MinIO client")
})
go bootstrapTrace("startResourceMetricsCollection", func() {
startResourceMetricsCollection()
})
// Add User-Agent to differentiate the requests.
globalMinioClient.SetAppInfo("minio-perf-test", ReleaseTag)
+10 -17
View File
@@ -21,12 +21,11 @@ import (
"fmt"
"net"
"net/url"
"runtime"
"strings"
humanize "github.com/dustin/go-humanize"
"github.com/dustin/go-humanize"
"github.com/minio/madmin-go/v3"
color "github.com/minio/minio/internal/color"
"github.com/minio/minio/internal/color"
"github.com/minio/minio/internal/logger"
xnet "github.com/minio/pkg/v2/net"
)
@@ -129,10 +128,10 @@ func printServerCommonMsg(apiEndpoints []string) {
// Colorize the message and print.
logger.Info(color.Blue("S3-API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
if color.IsTerminal() && (!globalCLIContext.Anonymous && !globalCLIContext.JSON) {
logger.Info(color.Blue("RootUser: ") + color.Bold(fmt.Sprintf("%s ", cred.AccessKey)))
logger.Info(color.Blue("RootPass: ") + color.Bold(fmt.Sprintf("%s \n", cred.SecretKey)))
logger.Info(color.Blue("RootUser: ") + color.Bold("%s ", cred.AccessKey))
logger.Info(color.Blue("RootPass: ") + color.Bold("%s \n", cred.SecretKey))
if region != "" {
logger.Info(color.Blue("Region: ") + color.Bold(fmt.Sprintf(getFormatStr(len(region), 2), region)))
logger.Info(color.Blue("Region: ") + color.Bold("%s", fmt.Sprintf(getFormatStr(len(region), 2), region)))
}
}
@@ -140,8 +139,8 @@ func printServerCommonMsg(apiEndpoints []string) {
consoleEndpointStr := strings.Join(stripStandardPorts(getConsoleEndpoints(), globalMinioConsoleHost), " ")
logger.Info(color.Blue("Console: ") + color.Bold(fmt.Sprintf("%s ", consoleEndpointStr)))
if color.IsTerminal() && (!globalCLIContext.Anonymous && !globalCLIContext.JSON) {
logger.Info(color.Blue("RootUser: ") + color.Bold(fmt.Sprintf("%s ", cred.AccessKey)))
logger.Info(color.Blue("RootPass: ") + color.Bold(fmt.Sprintf("%s ", cred.SecretKey)))
logger.Info(color.Blue("RootUser: ") + color.Bold("%s ", cred.AccessKey))
logger.Info(color.Blue("RootPass: ") + color.Bold("%s ", cred.SecretKey))
}
}
@@ -196,15 +195,9 @@ func printCLIAccessMsg(endPoint string, alias string) {
// Configure 'mc', following block prints platform specific information for minio client.
if color.IsTerminal() && !globalCLIContext.Anonymous {
logger.Info(color.Blue("\nCommand-line: ") + mcQuickStartGuide)
if runtime.GOOS == globalWindowsOSName {
mcMessage := fmt.Sprintf("$ mc.exe alias set %s %s %s %s", alias,
endPoint, cred.AccessKey, cred.SecretKey)
logger.Info(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
} else {
mcMessage := fmt.Sprintf("$ mc alias set %s %s %s %s", alias,
endPoint, cred.AccessKey, cred.SecretKey)
logger.Info(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
}
mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias,
endPoint, cred.AccessKey, cred.SecretKey)
logger.Info(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
}
}
+5 -5
View File
@@ -104,11 +104,11 @@ func unfreezeServices() {
// Close when we reach 0
globalServiceFreezeCnt--
if globalServiceFreezeCnt <= 0 {
// Ensure we only close once.
if val := globalServiceFreeze.Load(); val != nil {
var _ch chan struct{}
if ch, ok := val.(chan struct{}); ok {
globalServiceFreeze.Store(_ch)
// Set to a nil channel.
var _ch chan struct{}
if val := globalServiceFreeze.Swap(_ch); val != nil {
if ch, ok := val.(chan struct{}); ok && ch != nil {
// Close previous non-nil channel.
close(ch)
}
}
+9 -11
View File
@@ -290,18 +290,16 @@ func newXLStorage(ep Endpoint, cleanUp bool) (s *xlStorage, err error) {
s.formatLegacy = format.Erasure.DistributionAlgo == formatErasureVersionV2DistributionAlgoV1
}
if globalAPIConfig.odirectEnabled() {
// Return an error if ODirect is not supported
// unless it is a single erasure disk mode
if err := s.checkODirectDiskSupport(); err == nil {
s.oDirect = true
// Return an error if ODirect is not supported unless it is a single erasure
// disk mode
if err := s.checkODirectDiskSupport(); err == nil {
s.oDirect = true
} else {
// Allow if unsupported platform or single disk.
if errors.Is(err, errUnsupportedDisk) && globalIsErasureSD || !disk.ODirectPlatform {
s.oDirect = false
} else {
// Allow if unsupported platform or single disk.
if errors.Is(err, errUnsupportedDisk) && globalIsErasureSD || !disk.ODirectPlatform {
s.oDirect = false
} else {
return s, err
}
return s, err
}
}
+106
View File
@@ -303,3 +303,109 @@ For deployments with [Site Replication](https://min.io/docs/minio/linux/operatio
| `minio_bucket_requests_total` | Total number of S3 requests on a bucket. |
| `minio_bucket_requests_canceled_total` | Total number S3 requests canceled by the client. |
| `minio_bucket_requests_ttfb_seconds_distribution` | Distribution of time to first byte across API calls per bucket. |
# Resource Metrics
MinIO collects the following resource metrics at the node level.
Each metric includes the `server` label to identify the corresponding node.
Metrics may include one or more additional labels, such as the drive path, interface name, etc.
These metrics can be obtained from any MinIO server once per collection by using the following URL:
```shell
https://HOSTNAME:PORT/minio/metrics/v2/resource
```
Replace `HOSTNAME:PORT` with the hostname of your MinIO deployment.
For deployments behind a load balancer, use the load balancer hostname instead of a single node hostname.
## Drive Resource Metrics
| Name | Description |
| :----------------------------------- | :------------------------------------------------------- |
| `minio_node_drive_total_bytes` | Total bytes on a drive. |
| `minio_node_drive_used_bytes` | Used bytes on a drive. |
| `minio_node_drive_total_inodes` | Total inodes on a drive. |
| `minio_node_drive_used_inodes` | Total inodes used on a drive. |
| `minio_node_drive_reads_per_sec` | Reads per second on a drive. |
| `minio_node_drive_reads_kb_per_sec` | Kilobytes read per second on a drive. |
| `minio_node_drive_reads_await` | Average time for read requests to be served on a drive. |
| `minio_node_drive_writes_per_sec` | Writes per second on a drive. |
| `minio_node_drive_writes_kb_per_sec` | Kilobytes written per second on a drive. |
| `minio_node_drive_writes_await` | Average time for write requests to be served on a drive. |
| `minio_node_drive_perc_util` | Percentage of time the disk was busy since uptime. |
## Network Interface Metrics
| Name | Description |
| :---------------------------- | :-------------------------------------------- |
| `minio_node_if_rx_bytes` | Bytes received on the interface in 60s. |
| `minio_node_if_rx_bytes_avg` | Bytes received on the interface in 60s (avg). |
| `minio_node_if_rx_bytes_max` | Bytes received on the interface in 60s (max). |
| `minio_node_if_rx_errors` | Receive errors in 60s. |
| `minio_node_if_rx_errors_avg` | Receive errors in 60s (avg). |
| `minio_node_if_rx_errors_max` | Receive errors in 60s (max). |
| `minio_node_if_tx_bytes` | Bytes transmitted in 60s. |
| `minio_node_if_tx_bytes_avg` | Bytes transmitted in 60s (avg). |
| `minio_node_if_tx_bytes_max` | Bytes transmitted in 60s (max). |
| `minio_node_if_tx_errors` | Transmit errors in 60s. |
| `minio_node_if_tx_errors_avg` | Transmit errors in 60s (avg). |
| `minio_node_if_tx_errors_max` | Transmit errors in 60s (max). |
## CPU Metrics
| Name | Description |
| :------------------------------ | :---------------------------- |
| `minio_node_cpu_avg_user` | CPU user time. |
| `minio_node_cpu_avg_user_avg` | CPU user time (avg). |
| `minio_node_cpu_avg_user_max` | CPU user time (max). |
| `minio_node_cpu_avg_system` | CPU system time. |
| `minio_node_cpu_avg_system_avg` | CPU system time (avg). |
| `minio_node_cpu_avg_system_max` | CPU system time (max). |
| `minio_node_cpu_avg_idle` | CPU idle time. |
| `minio_node_cpu_avg_idle_avg` | CPU idle time (avg). |
| `minio_node_cpu_avg_idle_max` | CPU idle time (max). |
| `minio_node_cpu_avg_iowait` | CPU ioWait time. |
| `minio_node_cpu_avg_iowait_avg` | CPU ioWait time (avg). |
| `minio_node_cpu_avg_iowait_max` | CPU ioWait time (max). |
| `minio_node_cpu_avg_nice` | CPU nice time. |
| `minio_node_cpu_avg_nice_avg` | CPU nice time (avg). |
| `minio_node_cpu_avg_nice_max` | CPU nice time (max). |
| `minio_node_cpu_avg_steal` | CPU steam time. |
| `minio_node_cpu_avg_steal_avg` | CPU steam time (avg). |
| `minio_node_cpu_avg_steal_max` | CPU steam time (max). |
| `minio_node_cpu_avg_load1` | CPU load average 1min. |
| `minio_node_cpu_avg_load1_avg` | CPU load average 1min (avg). |
| `minio_node_cpu_avg_load1_max` | CPU load average 1min (max). |
| `minio_node_cpu_avg_load5` | CPU load average 5min. |
| `minio_node_cpu_avg_load5_avg` | CPU load average 5min (avg). |
| `minio_node_cpu_avg_load5_max` | CPU load average 5min (max). |
| `minio_node_cpu_avg_load15` | CPU load average 15min. |
| `minio_node_cpu_avg_load15_avg` | CPU load average 15min (avg). |
| `minio_node_cpu_avg_load15_max` | CPU load average 15min (max). |
## Memory Metrics
| Name | Description |
| :----------------------------- | :---------------------------------- |
| `minio_node_mem_available` | Available memory on the node. |
| `minio_node_mem_available_avg` | Available memory on the node (avg). |
| `minio_node_mem_available_max` | Available memory on the node (max). |
| `minio_node_mem_buffers` | Buffers memory on the node. |
| `minio_node_mem_buffers_avg` | Buffers memory on the node (avg). |
| `minio_node_mem_buffers_max` | Buffers memory on the node (max). |
| `minio_node_mem_cache` | Cache memory on the node. |
| `minio_node_mem_cache_avg` | Cache memory on the node (avg). |
| `minio_node_mem_cache_max` | Cache memory on the node (max). |
| `minio_node_mem_free` | Free memory on the node. |
| `minio_node_mem_free_avg` | Free memory on the node (avg). |
| `minio_node_mem_free_max` | Free memory on the node (max). |
| `minio_node_mem_shared` | Shared memory on the node. |
| `minio_node_mem_shared_avg` | Shared memory on the node (avg). |
| `minio_node_mem_shared_max` | Shared memory on the node (max). |
| `minio_node_mem_total` | Total memory on the node. |
| `minio_node_mem_total_avg` | Total memory on the node (avg). |
| `minio_node_mem_total_max` | Total memory on the node (max). |
| `minio_node_mem_used` | Used memory on the node. |
| `minio_node_mem_used_avg` | Used memory on the node (avg). |
| `minio_node_mem_used_max` | Used memory on the node (max). |
@@ -2,7 +2,7 @@ version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: quay.io/minio/minio:RELEASE.2023-09-27T15-22-50Z
image: quay.io/minio/minio:RELEASE.2023-09-30T07-02-29Z
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
expose:
- "9000"
+2 -2
View File
@@ -43,14 +43,14 @@ require (
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/miekg/dns v1.1.56
github.com/minio/cli v1.24.2
github.com/minio/console v0.39.0
github.com/minio/console v0.40.0
github.com/minio/csvparser v1.0.0
github.com/minio/dnscache v0.1.1
github.com/minio/dperf v0.5.0
github.com/minio/highwayhash v1.0.2
github.com/minio/kes-go v0.2.0
github.com/minio/madmin-go/v3 v3.0.21
github.com/minio/minio-go/v7 v7.0.63
github.com/minio/minio-go/v7 v7.0.64-0.20230920204636-e783c9ba11b3
github.com/minio/mux v1.9.0
github.com/minio/pkg/v2 v2.0.2
github.com/minio/selfupdate v0.6.0
+4 -4
View File
@@ -460,8 +460,8 @@ github.com/minio/cli v1.24.2 h1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=
github.com/minio/cli v1.24.2/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
github.com/minio/colorjson v1.0.6 h1:m7TUvpvt0u7FBmVIEQNIa0T4NBQlxrcMBp4wJKsg2Ik=
github.com/minio/colorjson v1.0.6/go.mod h1:LUXwS5ZGNb6Eh9f+t+3uJiowD3XsIWtsvTriUBeqgYs=
github.com/minio/console v0.39.0 h1:Ta3C5Magg9bLp1C2LwFX98QZWV282Z+X0ICJ+xH7J4k=
github.com/minio/console v0.39.0/go.mod h1:lg8HbAvha71oKDxWI5la0v22kX8zNpitvw80etkhOpk=
github.com/minio/console v0.40.0 h1:zldPHumWA5SN6uAUyAsctUFQ80LvLDv3n0VlONmBJMc=
github.com/minio/console v0.40.0/go.mod h1:ra5pDF0mFQ9DI8K5iyk6Q5gV6KQuZq3MKPGYU66a+vc=
github.com/minio/csvparser v1.0.0 h1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=
github.com/minio/csvparser v1.0.0/go.mod h1:lKXskSLzPgC5WQyzP7maKH7Sl1cqvANXo9YCto8zbtM=
github.com/minio/dnscache v0.1.1 h1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=
@@ -481,8 +481,8 @@ github.com/minio/mc v0.0.0-20230922050746-ae05d451739b/go.mod h1:+4OzbgOsVsKvdmL
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ=
github.com/minio/minio-go/v7 v7.0.63/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4=
github.com/minio/minio-go/v7 v7.0.64-0.20230920204636-e783c9ba11b3 h1:0DtfDxg67S/IRcGnIBKgzyjZ0GXyk3jN1Fy5/+8CQlM=
github.com/minio/minio-go/v7 v7.0.64-0.20230920204636-e783c9ba11b3/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4=
github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA=
github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
github.com/minio/pkg v1.7.5 h1:UOUJjewE5zoaDPlCMJtNx/swc1jT1ZR+IajT7hrLd44=
Binary file not shown.
+2 -2
View File
@@ -1,8 +1,8 @@
apiVersion: v1
description: Multi-Cloud Object Storage
name: minio
version: 5.0.13
appVersion: RELEASE.2023-07-07T07-13-57Z
version: 5.0.14
appVersion: RELEASE.2023-09-30T07-02-29Z
keywords:
- minio
- storage
+7
View File
@@ -120,7 +120,14 @@ spec:
- name: MINIO_IDENTITY_OPENID_CLIENT_ID
value: {{ .Values.oidc.clientId }}
- name: MINIO_IDENTITY_OPENID_CLIENT_SECRET
{{- if and .Values.oidc.existingClientSecretName .Values.oidc.existingClientSecretKey }}
valueFrom:
secretKeyRef:
name: {{ .Values.oidc.existingClientSecretName }}
key: {{ .Values.oidc.existingClientSecretKey }}
{{- else }}
value: {{ .Values.oidc.clientSecret }}
{{- end }}
- name: MINIO_IDENTITY_OPENID_CLAIM_NAME
value: {{ .Values.oidc.claimName }}
- name: MINIO_IDENTITY_OPENID_CLAIM_PREFIX
+7
View File
@@ -158,7 +158,14 @@ spec:
- name: MINIO_IDENTITY_OPENID_CLIENT_ID
value: {{ .Values.oidc.clientId }}
- name: MINIO_IDENTITY_OPENID_CLIENT_SECRET
{{- if and .Values.oidc.existingClientSecretName .Values.oidc.existingClientSecretKey }}
valueFrom:
secretKeyRef:
name: {{ .Values.oidc.existingClientSecretName }}
key: {{ .Values.oidc.existingClientSecretKey }}
{{- else }}
value: {{ .Values.oidc.clientSecret }}
{{- end }}
- name: MINIO_IDENTITY_OPENID_CLAIM_NAME
value: {{ .Values.oidc.claimName }}
- name: MINIO_IDENTITY_OPENID_CLAIM_PREFIX
+5 -2
View File
@@ -14,7 +14,7 @@ clusterDomain: cluster.local
##
image:
repository: quay.io/minio/minio
tag: RELEASE.2023-07-07T07-13-57Z
tag: RELEASE.2023-09-30T07-02-29Z
pullPolicy: IfNotPresent
imagePullSecrets: []
@@ -25,7 +25,7 @@ imagePullSecrets: []
##
mcImage:
repository: quay.io/minio/mc
tag: RELEASE.2023-06-28T21-54-17Z
tag: RELEASE.2023-09-29T16-41-22Z
pullPolicy: IfNotPresent
## minio mode, i.e. standalone or distributed
@@ -486,6 +486,9 @@ oidc:
configUrl: "https://identity-provider-url/.well-known/openid-configuration"
clientId: "minio"
clientSecret: ""
# Provide existing client secret from the Kubernetes Secret resource, existing secret will have priority over `clientSecret`
existingClientSecretName: ""
existingClientSecretKey: ""
claimName: "policy"
scopes: "openid,profile,email"
redirectUri: "https://console-endpoint-url/oauth_callback"
+107 -85
View File
@@ -1,9 +1,31 @@
apiVersion: v1
entries:
minio:
- apiVersion: v1
appVersion: RELEASE.2023-09-30T07-02-29Z
created: "2023-09-30T13:45:44.318107697-07:00"
description: Multi-Cloud Object Storage
digest: 6c3656924fbad2cb17f810cd78f352f9b60626aaec64b837c96829095b215ad3
home: https://min.io
icon: https://min.io/resources/img/logo/MINIO_wordmark.png
keywords:
- minio
- storage
- object-storage
- s3
- cluster
maintainers:
- email: dev@minio.io
name: MinIO, Inc
name: minio
sources:
- https://github.com/minio/minio
urls:
- https://charts.min.io/helm-releases/minio-5.0.14.tgz
version: 5.0.14
- apiVersion: v1
appVersion: RELEASE.2023-07-07T07-13-57Z
created: "2023-07-09T00:12:52.083613719-07:00"
created: "2023-09-30T13:45:44.317015357-07:00"
description: Multi-Cloud Object Storage
digest: 3c18f7381efe6d86497f952e6d5f59003ee5a009c54778ddea1ee8d3c7bed9c8
home: https://min.io
@@ -25,7 +47,7 @@ entries:
version: 5.0.13
- apiVersion: v1
appVersion: RELEASE.2023-07-07T07-13-57Z
created: "2023-07-09T00:12:52.082626304-07:00"
created: "2023-09-30T13:45:44.315355964-07:00"
description: Multi-Cloud Object Storage
digest: 5318bc56c73a8f4539c3dd178f4d55c7f41bee4a25d7dc02ac6a5843eeee7976
home: https://min.io
@@ -47,7 +69,7 @@ entries:
version: 5.0.12
- apiVersion: v1
appVersion: RELEASE.2023-06-19T19-52-50Z
created: "2023-07-09T00:12:52.081596401-07:00"
created: "2023-09-30T13:45:44.314320466-07:00"
description: Multi-Cloud Object Storage
digest: cba44c8cddcda1fb5c082dce82004a39f53cc20677ab9698a6998f01efefd8db
home: https://min.io
@@ -69,7 +91,7 @@ entries:
version: 5.0.11
- apiVersion: v1
appVersion: RELEASE.2023-05-18T00-05-36Z
created: "2023-07-09T00:12:52.07978903-07:00"
created: "2023-09-30T13:45:44.313340376-07:00"
description: Multi-Cloud Object Storage
digest: a3d55b12f38a2049ddf3efe35b38b6dc4e59777452b72d18d5a82f3378deb9cd
home: https://min.io
@@ -91,7 +113,7 @@ entries:
version: 5.0.10
- apiVersion: v1
appVersion: RELEASE.2023-04-28T18-11-17Z
created: "2023-07-09T00:12:52.092296696-07:00"
created: "2023-09-30T13:45:44.327034407-07:00"
description: Multi-Cloud Object Storage
digest: cf98985e32675e4ce327304ea9ac61046a788b3d5190d6b501330f7803d41a11
home: https://min.io
@@ -113,7 +135,7 @@ entries:
version: 5.0.9
- apiVersion: v1
appVersion: RELEASE.2023-04-13T03-08-07Z
created: "2023-07-09T00:12:52.091349939-07:00"
created: "2023-09-30T13:45:44.326090811-07:00"
description: Multi-Cloud Object Storage
digest: 034d68f85799f6693836975797f85a91842cf2d003a6c4ff401bd4ea4c946af6
home: https://min.io
@@ -135,7 +157,7 @@ entries:
version: 5.0.8
- apiVersion: v1
appVersion: RELEASE.2023-02-10T18-48-39Z
created: "2023-07-09T00:12:52.090389234-07:00"
created: "2023-09-30T13:45:44.32510969-07:00"
description: Multi-Cloud Object Storage
digest: 3f935a310e1b5b873052629b66005c160356ca7b2bd394cb07b34dbaf9905e3f
home: https://min.io
@@ -157,7 +179,7 @@ entries:
version: 5.0.7
- apiVersion: v1
appVersion: RELEASE.2023-02-10T18-48-39Z
created: "2023-07-09T00:12:52.089429881-07:00"
created: "2023-09-30T13:45:44.32366994-07:00"
description: Multi-Cloud Object Storage
digest: 82ef858ce483c2d736444792986cb36bd0fb4fc90a80b97fe30d7b2f2034d24a
home: https://min.io
@@ -179,7 +201,7 @@ entries:
version: 5.0.6
- apiVersion: v1
appVersion: RELEASE.2023-01-31T02-24-19Z
created: "2023-07-09T00:12:52.087989387-07:00"
created: "2023-09-30T13:45:44.322210657-07:00"
description: Multi-Cloud Object Storage
digest: fefeea10e4e525e45f82fb80a03900d34605ec432dd92f56d94eaf4fb1b98c41
home: https://min.io
@@ -201,7 +223,7 @@ entries:
version: 5.0.5
- apiVersion: v1
appVersion: RELEASE.2022-12-12T19-27-27Z
created: "2023-07-09T00:12:52.0865042-07:00"
created: "2023-09-30T13:45:44.321254746-07:00"
description: Multi-Cloud Object Storage
digest: 6b305783c98b0b97ffab079ff4430094fd0ca6e98e82bb8153cb93033a1bf40f
home: https://min.io
@@ -223,7 +245,7 @@ entries:
version: 5.0.4
- apiVersion: v1
appVersion: RELEASE.2022-12-12T19-27-27Z
created: "2023-07-09T00:12:52.08555416-07:00"
created: "2023-09-30T13:45:44.320169821-07:00"
description: Multi-Cloud Object Storage
digest: bac89157c53b324aece263c294aa49f5c9b64f426b4b06c9bca3d72e77e244f2
home: https://min.io
@@ -245,7 +267,7 @@ entries:
version: 5.0.3
- apiVersion: v1
appVersion: RELEASE.2022-12-12T19-27-27Z
created: "2023-07-09T00:12:52.084560152-07:00"
created: "2023-09-30T13:45:44.319086339-07:00"
description: Multi-Cloud Object Storage
digest: 935ce4f09366231b11d414d626f887fa6fa6024dd30a42e81e810ca1438d5904
home: https://min.io
@@ -267,7 +289,7 @@ entries:
version: 5.0.2
- apiVersion: v1
appVersion: RELEASE.2022-11-11T03-44-20Z
created: "2023-07-09T00:12:52.078824549-07:00"
created: "2023-09-30T13:45:44.312066393-07:00"
description: Multi-Cloud Object Storage
digest: 3e952c5d737980b8ccdfb819021eafb4b4e8da226f764a1dc3de1ba63ceb1ffa
home: https://min.io
@@ -289,7 +311,7 @@ entries:
version: 5.0.1
- apiVersion: v1
appVersion: RELEASE.2022-10-24T18-35-07Z
created: "2023-07-09T00:12:52.077895753-07:00"
created: "2023-09-30T13:45:44.311113045-07:00"
description: Multi-Cloud Object Storage
digest: 6215c800d84fd4c40e4fb4142645fc1c6a039c251776a3cc8c11a24b9e3b59c7
home: https://min.io
@@ -311,7 +333,7 @@ entries:
version: 5.0.0
- apiVersion: v1
appVersion: RELEASE.2022-10-24T18-35-07Z
created: "2023-07-09T00:12:52.076911932-07:00"
created: "2023-09-30T13:45:44.310137758-07:00"
description: Multi-Cloud Object Storage
digest: 2d3d884490ea1127742f938bc9382844bae713caae08b3308f766f3c9000659a
home: https://min.io
@@ -333,7 +355,7 @@ entries:
version: 4.1.0
- apiVersion: v1
appVersion: RELEASE.2022-09-17T00-09-45Z
created: "2023-07-09T00:12:52.068200433-07:00"
created: "2023-09-30T13:45:44.299926747-07:00"
description: Multi-Cloud Object Storage
digest: 6f16f2dbfed91ab81a7fae60b6ea32f554365bd27bf5fda55b64a0fa264f4252
home: https://min.io
@@ -355,7 +377,7 @@ entries:
version: 4.0.15
- apiVersion: v1
appVersion: RELEASE.2022-09-01T23-53-36Z
created: "2023-07-09T00:12:52.06625577-07:00"
created: "2023-09-30T13:45:44.298411641-07:00"
description: Multi-Cloud Object Storage
digest: 35d89d8f49d53ea929466fb88ee26123431326033f1387e6b2d536a629c0a398
home: https://min.io
@@ -377,7 +399,7 @@ entries:
version: 4.0.14
- apiVersion: v1
appVersion: RELEASE.2022-08-22T23-53-06Z
created: "2023-07-09T00:12:52.065251383-07:00"
created: "2023-09-30T13:45:44.29745713-07:00"
description: Multi-Cloud Object Storage
digest: 5b86937ca88d9f6046141fdc2b1cc54760435ed92d289cd0a115fa7148781d4e
home: https://min.io
@@ -399,7 +421,7 @@ entries:
version: 4.0.13
- apiVersion: v1
appVersion: RELEASE.2022-08-13T21-54-44Z
created: "2023-07-09T00:12:52.064278973-07:00"
created: "2023-09-30T13:45:44.296233683-07:00"
description: Multi-Cloud Object Storage
digest: 2d9c227c0f46ea8bdef4d760c212156fd4c6623ddc5406779c569fe925527787
home: https://min.io
@@ -421,7 +443,7 @@ entries:
version: 4.0.12
- apiVersion: v1
appVersion: RELEASE.2022-08-05T23-27-09Z
created: "2023-07-09T00:12:52.063368673-07:00"
created: "2023-09-30T13:45:44.295016135-07:00"
description: Multi-Cloud Object Storage
digest: 6caaffcb636e040cd7e8bc4883a1674a673757f4781c32d53b5ec0f41fea3944
home: https://min.io
@@ -443,7 +465,7 @@ entries:
version: 4.0.11
- apiVersion: v1
appVersion: RELEASE.2022-08-02T23-59-16Z
created: "2023-07-09T00:12:52.062416509-07:00"
created: "2023-09-30T13:45:44.29308946-07:00"
description: Multi-Cloud Object Storage
digest: 841d87788fb094d6a7d8a91e91821fe1e847bc952e054c781fc93742d112e18a
home: https://min.io
@@ -465,7 +487,7 @@ entries:
version: 4.0.10
- apiVersion: v1
appVersion: RELEASE.2022-08-02T23-59-16Z
created: "2023-07-09T00:12:52.075955122-07:00"
created: "2023-09-30T13:45:44.308504002-07:00"
description: Multi-Cloud Object Storage
digest: 6f1a78382df3215deac07495a5e7de7009a1153b4cf6cb565630652a69aec4cf
home: https://min.io
@@ -487,7 +509,7 @@ entries:
version: 4.0.9
- apiVersion: v1
appVersion: RELEASE.2022-07-29T19-40-48Z
created: "2023-07-09T00:12:52.075062029-07:00"
created: "2023-09-30T13:45:44.307480879-07:00"
description: Multi-Cloud Object Storage
digest: d11db37963636922cb778b6bc0ad2ca4724cb391ea7b785995ada52467d7dd83
home: https://min.io
@@ -509,7 +531,7 @@ entries:
version: 4.0.8
- apiVersion: v1
appVersion: RELEASE.2022-07-26T00-53-03Z
created: "2023-07-09T00:12:52.073779859-07:00"
created: "2023-09-30T13:45:44.306527164-07:00"
description: Multi-Cloud Object Storage
digest: ca775e08c84331bb5029d4d29867d30c16e2c62e897788eb432212a756e91e4e
home: https://min.io
@@ -531,7 +553,7 @@ entries:
version: 4.0.7
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-07-09T00:12:52.07269585-07:00"
created: "2023-09-30T13:45:44.305405238-07:00"
description: Multi-Cloud Object Storage
digest: 06542b8f3d149d5908b15de9a8d6f8cf304af0213830be56dc315785d14f9ccd
home: https://min.io
@@ -553,7 +575,7 @@ entries:
version: 4.0.6
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-07-09T00:12:52.071826014-07:00"
created: "2023-09-30T13:45:44.304317131-07:00"
description: Multi-Cloud Object Storage
digest: dd2676362f067454a496cdd293609d0c904b08f521625af49f95402a024ba1f5
home: https://min.io
@@ -575,7 +597,7 @@ entries:
version: 4.0.5
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-07-09T00:12:52.070968807-07:00"
created: "2023-09-30T13:45:44.303451898-07:00"
description: Multi-Cloud Object Storage
digest: bab9ef192d4eda4c572ad0ce0cf551736c847f582d1837d6833ee10543c23167
home: https://min.io
@@ -597,7 +619,7 @@ entries:
version: 4.0.4
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-07-09T00:12:52.070118552-07:00"
created: "2023-09-30T13:45:44.302546931-07:00"
description: Multi-Cloud Object Storage
digest: c770bb9841c76576e4e8573f78b0ec33e0d729504c9667e67ad62d48df5ed64c
home: https://min.io
@@ -619,7 +641,7 @@ entries:
version: 4.0.3
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-07-09T00:12:52.069249607-07:00"
created: "2023-09-30T13:45:44.301269526-07:00"
description: Multi-Cloud Object Storage
digest: 95835f4199d963e2a23a2493610b348e6f2ff8b71c1a648c4a3b84af9b7a83eb
home: https://min.io
@@ -641,7 +663,7 @@ entries:
version: 4.0.2
- apiVersion: v1
appVersion: RELEASE.2022-04-30T22-23-53Z
created: "2023-07-09T00:12:52.061513072-07:00"
created: "2023-09-30T13:45:44.29213066-07:00"
description: Multi-Cloud Object Storage
digest: 55a088c403b056e1f055a97426aa11759c3d6cbad38face170fe6cbbec7d568f
home: https://min.io
@@ -663,7 +685,7 @@ entries:
version: 4.0.1
- apiVersion: v1
appVersion: RELEASE.2022-04-26T01-20-24Z
created: "2023-07-09T00:12:52.060505181-07:00"
created: "2023-09-30T13:45:44.291284045-07:00"
description: Multi-Cloud Object Storage
digest: f541237e24336ec3f7f45ae0d523fef694e3a2f9ef648c5b11c15734db6ba2b2
home: https://min.io
@@ -685,7 +707,7 @@ entries:
version: 4.0.0
- apiVersion: v1
appVersion: RELEASE.2022-04-16T04-26-02Z
created: "2023-07-09T00:12:52.058907841-07:00"
created: "2023-09-30T13:45:44.290426627-07:00"
description: Multi-Cloud Object Storage
digest: edc0c3dd6d5246a06b74ba16bb4aff80a6d7225dc9aecf064fd89a8af371b9c1
home: https://min.io
@@ -707,7 +729,7 @@ entries:
version: 3.6.6
- apiVersion: v1
appVersion: RELEASE.2022-04-12T06-55-35Z
created: "2023-07-09T00:12:52.058000086-07:00"
created: "2023-09-30T13:45:44.289556731-07:00"
description: Multi-Cloud Object Storage
digest: 211e89f6b9eb0b9a3583abaa127be60e1f9717a098e6b2858cb9dc1cc50c1650
home: https://min.io
@@ -729,7 +751,7 @@ entries:
version: 3.6.5
- apiVersion: v1
appVersion: RELEASE.2022-04-09T15-09-52Z
created: "2023-07-09T00:12:52.056972348-07:00"
created: "2023-09-30T13:45:44.28851016-07:00"
description: Multi-Cloud Object Storage
digest: 534a879d73b370a18b554b93d0930e1c115419619c4ce4ec7dbaae632acacf06
home: https://min.io
@@ -751,7 +773,7 @@ entries:
version: 3.6.4
- apiVersion: v1
appVersion: RELEASE.2022-03-24T00-43-44Z
created: "2023-07-09T00:12:52.056123789-07:00"
created: "2023-09-30T13:45:44.287632827-07:00"
description: Multi-Cloud Object Storage
digest: 99508b20eb0083a567dcccaf9a6c237e09575ed1d70cd2e8333f89c472d13d75
home: https://min.io
@@ -773,7 +795,7 @@ entries:
version: 3.6.3
- apiVersion: v1
appVersion: RELEASE.2022-03-17T06-34-49Z
created: "2023-07-09T00:12:52.055271276-07:00"
created: "2023-09-30T13:45:44.28672558-07:00"
description: Multi-Cloud Object Storage
digest: b4cd25611ca322b1d23d23112fdfa6b068fd91eefe0b0663b88ff87ea4282495
home: https://min.io
@@ -795,7 +817,7 @@ entries:
version: 3.6.2
- apiVersion: v1
appVersion: RELEASE.2022-03-14T18-25-24Z
created: "2023-07-09T00:12:52.054400832-07:00"
created: "2023-09-30T13:45:44.285161842-07:00"
description: Multi-Cloud Object Storage
digest: d75b88162bfe54740a233bcecf87328bba2ae23d170bec3a35c828bc6fdc224c
home: https://min.io
@@ -817,7 +839,7 @@ entries:
version: 3.6.1
- apiVersion: v1
appVersion: RELEASE.2022-03-11T23-57-45Z
created: "2023-07-09T00:12:52.053508093-07:00"
created: "2023-09-30T13:45:44.284217509-07:00"
description: Multi-Cloud Object Storage
digest: 22e53a1184a21a679bc7d8b94e955777f3506340fc29da5ab0cb6d729bdbde8d
home: https://min.io
@@ -839,7 +861,7 @@ entries:
version: 3.6.0
- apiVersion: v1
appVersion: RELEASE.2022-03-03T21-21-16Z
created: "2023-07-09T00:12:52.051823002-07:00"
created: "2023-09-30T13:45:44.28321379-07:00"
description: Multi-Cloud Object Storage
digest: 6fda968d3fdfd60470c0055a4e1a3bd8e5aee9ad0af5ba2fb7b7b926fdc9e4a0
home: https://min.io
@@ -861,7 +883,7 @@ entries:
version: 3.5.9
- apiVersion: v1
appVersion: RELEASE.2022-02-26T02-54-46Z
created: "2023-07-09T00:12:52.050984544-07:00"
created: "2023-09-30T13:45:44.282331055-07:00"
description: Multi-Cloud Object Storage
digest: 8e015369048a3a82bbd53ad36696786f18561c6b25d14eee9e2c93a7336cef46
home: https://min.io
@@ -883,7 +905,7 @@ entries:
version: 3.5.8
- apiVersion: v1
appVersion: RELEASE.2022-02-18T01-50-10Z
created: "2023-07-09T00:12:52.050137914-07:00"
created: "2023-09-30T13:45:44.281421196-07:00"
description: Multi-Cloud Object Storage
digest: cb3543fe748e5f0d59b3ccf4ab9af8e10b731405ae445d1f5715e30013632373
home: https://min.io
@@ -905,7 +927,7 @@ entries:
version: 3.5.7
- apiVersion: v1
appVersion: RELEASE.2022-02-18T01-50-10Z
created: "2023-07-09T00:12:52.049298332-07:00"
created: "2023-09-30T13:45:44.280388028-07:00"
description: Multi-Cloud Object Storage
digest: f2e359fa5eefffc59abb3d14a8fa94b11ddeaa99f6cd8dd5f40f4e04121000d6
home: https://min.io
@@ -927,7 +949,7 @@ entries:
version: 3.5.6
- apiVersion: v1
appVersion: RELEASE.2022-02-16T00-35-27Z
created: "2023-07-09T00:12:52.048393471-07:00"
created: "2023-09-30T13:45:44.279117054-07:00"
description: Multi-Cloud Object Storage
digest: 529d56cca9d83a3d0e5672e63b6e87b5bcbe10a6b45f7a55ba998cceb32f9c81
home: https://min.io
@@ -949,7 +971,7 @@ entries:
version: 3.5.5
- apiVersion: v1
appVersion: RELEASE.2022-02-12T00-51-25Z
created: "2023-07-09T00:12:52.047593644-07:00"
created: "2023-09-30T13:45:44.277558085-07:00"
description: Multi-Cloud Object Storage
digest: 3d530598f8ece67bec5b7f990d206584893987c713502f9228e4ee24b5535414
home: https://min.io
@@ -971,7 +993,7 @@ entries:
version: 3.5.4
- apiVersion: v1
appVersion: RELEASE.2022-02-12T00-51-25Z
created: "2023-07-09T00:12:52.046792295-07:00"
created: "2023-09-30T13:45:44.276727539-07:00"
description: Multi-Cloud Object Storage
digest: 53937031348b29615f07fc4869b2d668391d8ba9084630a497abd7a7dea9dfb0
home: https://min.io
@@ -993,7 +1015,7 @@ entries:
version: 3.5.3
- apiVersion: v1
appVersion: RELEASE.2022-02-07T08-17-33Z
created: "2023-07-09T00:12:52.046012432-07:00"
created: "2023-09-30T13:45:44.275957276-07:00"
description: Multi-Cloud Object Storage
digest: 68d643414ff0d565716c5715034fcbf1af262e041915a5c02eb51ec1a65c1ea0
home: https://min.io
@@ -1015,7 +1037,7 @@ entries:
version: 3.5.2
- apiVersion: v1
appVersion: RELEASE.2022-02-01T18-00-14Z
created: "2023-07-09T00:12:52.044649337-07:00"
created: "2023-09-30T13:45:44.275214578-07:00"
description: Multi-Cloud Object Storage
digest: a3e855ed0f31233b989fffd775a29d6fbfa0590089010ff16783fd7f142ef6e7
home: https://min.io
@@ -1037,7 +1059,7 @@ entries:
version: 3.5.1
- apiVersion: v1
appVersion: RELEASE.2022-02-01T18-00-14Z
created: "2023-07-09T00:12:52.043927219-07:00"
created: "2023-09-30T13:45:44.274471492-07:00"
description: Multi-Cloud Object Storage
digest: b1b0ae3c54b4260a698753e11d7781bb8ddc67b7e3fbf0af82796e4cd4ef92a3
home: https://min.io
@@ -1059,7 +1081,7 @@ entries:
version: 3.5.0
- apiVersion: v1
appVersion: RELEASE.2022-01-28T02-28-16Z
created: "2023-07-09T00:12:52.043197969-07:00"
created: "2023-09-30T13:45:44.273733788-07:00"
description: Multi-Cloud Object Storage
digest: fecf25d2d3fb208c6f894fed642a60780a570b7f6d0adddde846af7236dc80aa
home: https://min.io
@@ -1081,7 +1103,7 @@ entries:
version: 3.4.8
- apiVersion: v1
appVersion: RELEASE.2022-01-25T19-56-04Z
created: "2023-07-09T00:12:52.042459778-07:00"
created: "2023-09-30T13:45:44.272981708-07:00"
description: Multi-Cloud Object Storage
digest: c78008caa5ce98f64c887630f59d0cbd481cb3f19a7d4e9d3e81bf4e1e45cadc
home: https://min.io
@@ -1103,7 +1125,7 @@ entries:
version: 3.4.7
- apiVersion: v1
appVersion: RELEASE.2022-01-08T03-11-54Z
created: "2023-07-09T00:12:52.041744623-07:00"
created: "2023-09-30T13:45:44.272032593-07:00"
description: Multi-Cloud Object Storage
digest: 8f2e2691bf897f74ff094dd370ec56ba9d417e5e8926710c14c2ba346330238d
home: https://min.io
@@ -1125,7 +1147,7 @@ entries:
version: 3.4.6
- apiVersion: v1
appVersion: RELEASE.2022-01-04T07-41-07Z
created: "2023-07-09T00:12:52.040983842-07:00"
created: "2023-09-30T13:45:44.270594751-07:00"
description: Multi-Cloud Object Storage
digest: bacd140f0016fab35f516bde787da6449b3a960c071fad9e4b6563118033ac84
home: https://min.io
@@ -1147,7 +1169,7 @@ entries:
version: 3.4.5
- apiVersion: v1
appVersion: RELEASE.2021-12-29T06-49-06Z
created: "2023-07-09T00:12:52.040253734-07:00"
created: "2023-09-30T13:45:44.269865392-07:00"
description: Multi-Cloud Object Storage
digest: 48a453ea5ffeef25933904caefd9470bfb26224dfc2d1096bd0031467ba53007
home: https://min.io
@@ -1169,7 +1191,7 @@ entries:
version: 3.4.4
- apiVersion: v1
appVersion: RELEASE.2021-12-20T22-07-16Z
created: "2023-07-09T00:12:52.039441814-07:00"
created: "2023-09-30T13:45:44.269127567-07:00"
description: Multi-Cloud Object Storage
digest: 47ef4a930713b98f9438ceca913c6e700f85bb25dba5624b056486254b5f0c60
home: https://min.io
@@ -1191,7 +1213,7 @@ entries:
version: 3.4.3
- apiVersion: v1
appVersion: RELEASE.2021-12-20T22-07-16Z
created: "2023-07-09T00:12:52.037915028-07:00"
created: "2023-09-30T13:45:44.268163723-07:00"
description: Multi-Cloud Object Storage
digest: d6763f7e2ea66810bd55eb225579a9c3b968f9ae1256f45fd469362e55d846ff
home: https://min.io
@@ -1213,7 +1235,7 @@ entries:
version: 3.4.2
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-07-09T00:12:52.037190929-07:00"
created: "2023-09-30T13:45:44.267246354-07:00"
description: Multi-Cloud Object Storage
digest: 2fb822c87216ba3fc2ae51a54a0a3e239aa560d86542991504a841cc2a2b9a37
home: https://min.io
@@ -1235,7 +1257,7 @@ entries:
version: 3.4.1
- apiVersion: v1
appVersion: RELEASE.2021-12-18T04-42-33Z
created: "2023-07-09T00:12:52.036385019-07:00"
created: "2023-09-30T13:45:44.266343771-07:00"
description: Multi-Cloud Object Storage
digest: fa8ba1aeb1a15316c6be8403416a5e6b5e6139b7166592087e7bddc9e6db5453
home: https://min.io
@@ -1257,7 +1279,7 @@ entries:
version: 3.4.0
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-07-09T00:12:52.035682883-07:00"
created: "2023-09-30T13:45:44.265456326-07:00"
description: Multi-Cloud Object Storage
digest: b9b0af9ca50b8d00868e1f1b989dca275829d9110af6de91bb9b3a398341e894
home: https://min.io
@@ -1279,7 +1301,7 @@ entries:
version: 3.3.4
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-07-09T00:12:52.034982834-07:00"
created: "2023-09-30T13:45:44.264304002-07:00"
description: Multi-Cloud Object Storage
digest: f8b22a5b8fe95a7ddf61b825e17d11c9345fb10e4c126b0d78381608aa300a08
home: https://min.io
@@ -1301,7 +1323,7 @@ entries:
version: 3.3.3
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-07-09T00:12:52.034279304-07:00"
created: "2023-09-30T13:45:44.259435328-07:00"
description: Multi-Cloud Object Storage
digest: c48d474f269427abe5ab446f00687d0625b3d1adfc5c73bdb4b21ca9e42853fb
home: https://min.io
@@ -1323,7 +1345,7 @@ entries:
version: 3.3.2
- apiVersion: v1
appVersion: RELEASE.2021-11-24T23-19-33Z
created: "2023-07-09T00:12:52.033504928-07:00"
created: "2023-09-30T13:45:44.258448201-07:00"
description: Multi-Cloud Object Storage
digest: 7c3da39d9b0090cbf5efedf0cc163a1e2df05becc5152c3add8e837384690bc4
home: https://min.io
@@ -1345,7 +1367,7 @@ entries:
version: 3.3.1
- apiVersion: v1
appVersion: RELEASE.2021-11-24T23-19-33Z
created: "2023-07-09T00:12:52.032082552-07:00"
created: "2023-09-30T13:45:44.257734262-07:00"
description: Multi-Cloud Object Storage
digest: 50d6590b4cc779c40f81cc13b1586fbe508aa7f3230036c760bfc5f4154fbce4
home: https://min.io
@@ -1367,7 +1389,7 @@ entries:
version: 3.3.0
- apiVersion: v1
appVersion: RELEASE.2021-10-13T00-23-17Z
created: "2023-07-09T00:12:52.031187915-07:00"
created: "2023-09-30T13:45:44.257013636-07:00"
description: Multi-Cloud Object Storage
digest: 5b797b7208cd904c11a76cd72938c8652160cb5fcd7f09fa41e4e703e6d64054
home: https://min.io
@@ -1389,7 +1411,7 @@ entries:
version: 3.2.0
- apiVersion: v1
appVersion: RELEASE.2021-10-10T16-53-30Z
created: "2023-07-09T00:12:52.030483162-07:00"
created: "2023-09-30T13:45:44.25605328-07:00"
description: Multi-Cloud Object Storage
digest: e084ac4bb095f071e59f8f08bd092e4ab2404c1ddadacfdce7dbe248f1bafff8
home: https://min.io
@@ -1411,7 +1433,7 @@ entries:
version: 3.1.9
- apiVersion: v1
appVersion: RELEASE.2021-10-06T23-36-31Z
created: "2023-07-09T00:12:52.02979236-07:00"
created: "2023-09-30T13:45:44.255324762-07:00"
description: Multi-Cloud Object Storage
digest: 2890430a8d9487d1fa5508c26776e4881d0086b2c052aa6bdc65c0e4423b9159
home: https://min.io
@@ -1433,7 +1455,7 @@ entries:
version: 3.1.8
- apiVersion: v1
appVersion: RELEASE.2021-10-02T16-31-05Z
created: "2023-07-09T00:12:52.029062485-07:00"
created: "2023-09-30T13:45:44.254617009-07:00"
description: Multi-Cloud Object Storage
digest: 01a92196af6c47e3a01e1c68d7cf693a8bc487cba810c2cecff155071e4d6a11
home: https://min.io
@@ -1455,7 +1477,7 @@ entries:
version: 3.1.7
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2023-07-09T00:12:52.028178186-07:00"
created: "2023-09-30T13:45:44.253895027-07:00"
description: Multi-Cloud Object Storage
digest: e779d73f80b75f33b9c9d995ab10fa455c9c57ee575ebc54e06725a64cd04310
home: https://min.io
@@ -1477,7 +1499,7 @@ entries:
version: 3.1.6
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2023-07-09T00:12:52.027297471-07:00"
created: "2023-09-30T13:45:44.252741769-07:00"
description: Multi-Cloud Object Storage
digest: 19de4bbc8a400f0c2a94c5e85fc25c9bfc666e773fb3e368dd621d5a57dd1c2a
home: https://min.io
@@ -1499,7 +1521,7 @@ entries:
version: 3.1.5
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2023-07-09T00:12:52.02646493-07:00"
created: "2023-09-30T13:45:44.251426132-07:00"
description: Multi-Cloud Object Storage
digest: f789d93a171296dd01af0105a5ce067c663597afbb2432faeda293b752b355c0
home: https://min.io
@@ -1521,7 +1543,7 @@ entries:
version: 3.1.4
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-07-09T00:12:52.025609511-07:00"
created: "2023-09-30T13:45:44.2504309-07:00"
description: Multi-Cloud Object Storage
digest: e2eb34d31560b012ef6581f0ff6004ea4376c968cbe0daed2d8f3a614a892afb
home: https://min.io
@@ -1543,7 +1565,7 @@ entries:
version: 3.1.3
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-07-09T00:12:52.02463492-07:00"
created: "2023-09-30T13:45:44.249671479-07:00"
description: Multi-Cloud Object Storage
digest: 8d7e0cc46b3583abd71b97dc0c071f98321101f90eca17348f1e9e0831be64cd
home: https://min.io
@@ -1565,7 +1587,7 @@ entries:
version: 3.1.2
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-07-09T00:12:52.016273999-07:00"
created: "2023-09-30T13:45:44.248904262-07:00"
description: Multi-Cloud Object Storage
digest: 50dcbf366b1b21f4a6fc429d0b884c0c7ff481d0fb95c5e9b3ae157c348dd124
home: https://min.io
@@ -1587,7 +1609,7 @@ entries:
version: 3.1.1
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-07-09T00:12:52.015535262-07:00"
created: "2023-09-30T13:45:44.248011769-07:00"
description: Multi-Cloud Object Storage
digest: 6c01af55d2e2e5f716eabf6fef3a92a8464d0674529e9bacab292e5478a73b7a
home: https://min.io
@@ -1609,7 +1631,7 @@ entries:
version: 3.1.0
- apiVersion: v1
appVersion: RELEASE.2021-09-03T03-56-13Z
created: "2023-07-09T00:12:52.014848017-07:00"
created: "2023-09-30T13:45:44.246954326-07:00"
description: Multi-Cloud Object Storage
digest: 18e10be4d0458bc590ca9abf753227e0c70f60511495387b8d4fb15a4daf932e
home: https://min.io
@@ -1631,7 +1653,7 @@ entries:
version: 3.0.2
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2023-07-09T00:12:52.01416606-07:00"
created: "2023-09-30T13:45:44.246078126-07:00"
description: Multi-Cloud Object Storage
digest: f5b6e7f6272a9e71aef3b75555f6f756a39eef65cb78873f26451dba79b19906
home: https://min.io
@@ -1653,7 +1675,7 @@ entries:
version: 3.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2023-07-09T00:12:52.013496497-07:00"
created: "2023-09-30T13:45:44.24452265-07:00"
description: Multi-Cloud Object Storage
digest: 6d2ee1336c412affaaf209fdb80215be2a6ebb23ab2443adbaffef9e7df13fab
home: https://min.io
@@ -1675,7 +1697,7 @@ entries:
version: 3.0.0
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2023-07-09T00:12:52.012743494-07:00"
created: "2023-09-30T13:45:44.243841906-07:00"
description: Multi-Cloud Object Storage
digest: 0a004aaf5bb61deed6a5c88256d1695ebe2f9ff1553874a93e4acfd75e8d339b
home: https://min.io
@@ -1695,7 +1717,7 @@ entries:
version: 2.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2023-07-09T00:12:52.011973175-07:00"
created: "2023-09-30T13:45:44.242951969-07:00"
description: Multi-Cloud Object Storage
digest: fcd944e837ee481307de6aa3d387ea18c234f995a84c15abb211aab4a4054afc
home: https://min.io
@@ -1715,7 +1737,7 @@ entries:
version: 2.0.0
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2023-07-09T00:12:52.011186882-07:00"
created: "2023-09-30T13:45:44.242280535-07:00"
description: Multi-Cloud Object Storage
digest: 7b6c033d43a856479eb493ab8ca05b230f77c3e42e209e8f298fac6af1a9796f
home: https://min.io
@@ -1735,7 +1757,7 @@ entries:
version: 1.0.5
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2023-07-09T00:12:52.010322776-07:00"
created: "2023-09-30T13:45:44.241614992-07:00"
description: Multi-Cloud Object Storage
digest: abd221245ace16c8e0c6c851cf262d1474a5219dcbf25c4b2e7b77142f9c59ed
home: https://min.io
@@ -1755,7 +1777,7 @@ entries:
version: 1.0.4
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2023-07-09T00:12:52.00894929-07:00"
created: "2023-09-30T13:45:44.24052836-07:00"
description: Multi-Cloud Object Storage
digest: 922a333f5413d1042f7aa81929f43767f6ffca9b260c46713f04ce1dda86d57d
home: https://min.io
@@ -1775,7 +1797,7 @@ entries:
version: 1.0.3
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2023-07-09T00:12:52.008126414-07:00"
created: "2023-09-30T13:45:44.23955753-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: 10e22773506bbfb1c66442937956534cf4057b94f06a977db78b8cd223588388
home: https://min.io
@@ -1795,7 +1817,7 @@ entries:
version: 1.0.2
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2023-07-09T00:12:52.007422376-07:00"
created: "2023-09-30T13:45:44.234084367-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: ef86ab6df23d6942705da9ef70991b649638c51bc310587d37a425268ba4a06c
home: https://min.io
@@ -1815,7 +1837,7 @@ entries:
version: 1.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-17T20-53-08Z
created: "2023-07-09T00:12:52.006698484-07:00"
created: "2023-09-30T13:45:44.233235408-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: 1add7608692cbf39aaf9b1252530e566f7b2f306a14e390b0f49b97a20f2b188
home: https://min.io
@@ -1833,4 +1855,4 @@ entries:
urls:
- https://charts.min.io/helm-releases/minio-1.0.0.tgz
version: 1.0.0
generated: "2023-07-09T00:12:52.00579794-07:00"
generated: "2023-09-30T13:45:44.232215184-07:00"
+14
View File
@@ -362,6 +362,10 @@ var (
Key: target.KafkaVersion,
Value: "",
},
config.KV{
Key: target.KafkaBatchSize,
Value: "0",
},
}
)
@@ -434,6 +438,15 @@ func GetNotifyKafka(kafkaKVS map[string]config.KVS) (map[string]target.KafkaArgs
versionEnv = versionEnv + config.Default + k
}
batchSizeEnv := target.EnvKafkaBatchSize
if k != config.Default {
batchSizeEnv = batchSizeEnv + config.Default + k
}
batchSize, err := strconv.ParseUint(env.Get(batchSizeEnv, kv.Get(target.KafkaBatchSize)), 10, 32)
if err != nil {
return nil, err
}
kafkaArgs := target.KafkaArgs{
Enable: enabled,
Brokers: brokers,
@@ -441,6 +454,7 @@ func GetNotifyKafka(kafkaKVS map[string]config.KVS) (map[string]target.KafkaArgs
QueueDir: env.Get(queueDirEnv, kv.Get(target.KafkaQueueDir)),
QueueLimit: queueLimit,
Version: env.Get(versionEnv, kv.Get(target.KafkaVersion)),
BatchSize: uint32(batchSize),
}
tlsEnableEnv := target.EnvKafkaTLS
+3 -3
View File
@@ -291,7 +291,7 @@ func (target *AMQPTarget) Save(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to AMQP091.
func (target *AMQPTarget) SendFromStore(eventKey string) error {
func (target *AMQPTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -302,7 +302,7 @@ func (target *AMQPTarget) SendFromStore(eventKey string) error {
}
defer ch.Close()
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -317,7 +317,7 @@ func (target *AMQPTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - does nothing and available for interface compatibility.
+3 -3
View File
@@ -264,7 +264,7 @@ func (target *ElasticsearchTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to Elasticsearch.
func (target *ElasticsearchTarget) SendFromStore(eventKey string) error {
func (target *ElasticsearchTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -277,7 +277,7 @@ func (target *ElasticsearchTarget) SendFromStore(eventKey string) error {
return err
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -295,7 +295,7 @@ func (target *ElasticsearchTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - does nothing and available for interface compatibility.
+105 -41
View File
@@ -57,6 +57,7 @@ const (
KafkaClientTLSCert = "client_tls_cert"
KafkaClientTLSKey = "client_tls_key"
KafkaVersion = "version"
KafkaBatchSize = "batch_size"
EnvKafkaEnable = "MINIO_NOTIFY_KAFKA_ENABLE"
EnvKafkaBrokers = "MINIO_NOTIFY_KAFKA_BROKERS"
@@ -73,6 +74,9 @@ const (
EnvKafkaClientTLSCert = "MINIO_NOTIFY_KAFKA_CLIENT_TLS_CERT"
EnvKafkaClientTLSKey = "MINIO_NOTIFY_KAFKA_CLIENT_TLS_KEY"
EnvKafkaVersion = "MINIO_NOTIFY_KAFKA_VERSION"
EnvKafkaBatchSize = "MINIO_NOTIFY_KAFKA_BATCH_SIZE"
maxBatchLimit = 100
)
// KafkaArgs - Kafka target arguments.
@@ -83,6 +87,7 @@ type KafkaArgs struct {
QueueDir string `json:"queueDir"`
QueueLimit uint64 `json:"queueLimit"`
Version string `json:"version"`
BatchSize uint32 `json:"batchSize"`
TLS struct {
Enable bool `json:"enable"`
RootCAs *x509.CertPool `json:"-"`
@@ -122,6 +127,14 @@ func (k KafkaArgs) Validate() error {
return err
}
}
if k.BatchSize > 1 {
if k.QueueDir == "" {
return errors.New("batch should be enabled only if queue dir is enabled")
}
if k.BatchSize > maxBatchLimit {
return fmt.Errorf("batch limit should not exceed %d", maxBatchLimit)
}
}
return nil
}
@@ -134,6 +147,7 @@ type KafkaTarget struct {
producer sarama.SyncProducer
config *sarama.Config
store store.Store[event.Event]
batch *store.Batch[string, *sarama.ProducerMessage]
loggerOnce logger.LogOnce
quitCh chan struct{}
}
@@ -188,55 +202,33 @@ func (target *KafkaTarget) send(eventData event.Event) error {
if target.producer == nil {
return store.ErrNotConnected
}
objectName, err := url.QueryUnescape(eventData.S3.Object.Key)
msg, err := target.toProducerMessage(eventData)
if err != nil {
return err
}
key := eventData.S3.Bucket.Name + "/" + objectName
data, err := json.Marshal(event.Log{EventName: eventData.EventName, Key: key, Records: []event.Event{eventData}})
if err != nil {
return err
}
msg := sarama.ProducerMessage{
Topic: target.args.Topic,
Key: sarama.StringEncoder(key),
Value: sarama.ByteEncoder(data),
}
_, _, err = target.producer.SendMessage(&msg)
_, _, err = target.producer.SendMessage(msg)
return err
}
// SendFromStore - reads an event from store and sends it to Kafka.
func (target *KafkaTarget) SendFromStore(eventKey string) error {
func (target *KafkaTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
// If batch is enabled, the event will be batched in memory
// and will be committed once the batch is full.
if target.batch != nil {
return target.addToBatch(key)
}
var err error
_, err = target.isActive()
if err != nil {
return err
}
if target.producer == nil {
brokers := []string{}
for _, broker := range target.args.Brokers {
brokers = append(brokers, broker.String())
}
target.producer, err = sarama.NewSyncProducer(brokers, target.config)
if err != nil {
if err != sarama.ErrOutOfBrokers {
return err
}
return store.ErrNotConnected
}
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -248,15 +240,79 @@ func (target *KafkaTarget) SendFromStore(eventKey string) error {
err = target.send(eventData)
if err != nil {
// Sarama opens the ciruit breaker after 3 consecutive connection failures.
if err == sarama.ErrLeaderNotAvailable || err.Error() == "circuit breaker is open" {
if isKafkaConnErr(err) {
return store.ErrNotConnected
}
return err
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
func (target *KafkaTarget) addToBatch(key store.Key) error {
if target.batch.IsFull() {
if err := target.commitBatch(); err != nil {
return err
}
}
if _, ok := target.batch.GetByKey(key.Name); !ok {
eventData, err := target.store.Get(key.Name)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
msg, err := target.toProducerMessage(eventData)
if err != nil {
return err
}
if err = target.batch.Add(key.Name, msg); err != nil {
return err
}
}
// commit the batch if the key is the last one present in the store.
if key.IsLast || target.batch.IsFull() {
return target.commitBatch()
}
return nil
}
func (target *KafkaTarget) commitBatch() error {
if _, err := target.isActive(); err != nil {
return err
}
keys, msgs, err := target.batch.GetAll()
if err != nil {
return err
}
if err = target.producer.SendMessages(msgs); err != nil {
if isKafkaConnErr(err) {
return store.ErrNotConnected
}
return err
}
return target.store.DelList(keys)
}
func (target *KafkaTarget) toProducerMessage(eventData event.Event) (*sarama.ProducerMessage, error) {
objectName, err := url.QueryUnescape(eventData.S3.Object.Key)
if err != nil {
return nil, err
}
key := eventData.S3.Bucket.Name + "/" + objectName
data, err := json.Marshal(event.Log{EventName: eventData.EventName, Key: key, Records: []event.Event{eventData}})
if err != nil {
return nil, err
}
return &sarama.ProducerMessage{
Topic: target.args.Topic,
Key: sarama.StringEncoder(key),
Value: sarama.ByteEncoder(data),
}, nil
}
// Close - closes underneath kafka connection.
@@ -335,16 +391,16 @@ func (target *KafkaTarget) initKafka() error {
// These settings are needed to ensure that kafka client doesn't hang on brokers
// refer https://github.com/IBM/sarama/issues/765#issuecomment-254333355
config.Producer.Retry.Max = 2
config.Producer.Retry.Backoff = (10 * time.Second)
config.Producer.Retry.Backoff = (1 * time.Second)
config.Producer.Return.Successes = true
config.Producer.Return.Errors = true
config.Producer.RequiredAcks = 1
config.Producer.Timeout = (10 * time.Second)
config.Net.ReadTimeout = (10 * time.Second)
config.Net.DialTimeout = (10 * time.Second)
config.Net.WriteTimeout = (10 * time.Second)
config.Producer.Timeout = (5 * time.Second)
config.Net.ReadTimeout = (5 * time.Second)
config.Net.DialTimeout = (5 * time.Second)
config.Net.WriteTimeout = (5 * time.Second)
config.Metadata.Retry.Max = 1
config.Metadata.Retry.Backoff = (10 * time.Second)
config.Metadata.Retry.Backoff = (1 * time.Second)
config.Metadata.RefreshFrequency = (15 * time.Minute)
target.config = config
@@ -394,8 +450,16 @@ func NewKafkaTarget(id string, args KafkaArgs, loggerOnce logger.LogOnce) (*Kafk
}
if target.store != nil {
if args.BatchSize > 1 {
target.batch = store.NewBatch[string, *sarama.ProducerMessage](args.BatchSize)
}
store.StreamItems(target.store, target, target.quitCh, target.loggerOnce)
}
return target, nil
}
func isKafkaConnErr(err error) bool {
// Sarama opens the ciruit breaker after 3 consecutive connection failures.
return err == sarama.ErrLeaderNotAvailable || err.Error() == "circuit breaker is open"
}
+3 -3
View File
@@ -169,7 +169,7 @@ func (target *MQTTTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to MQTT.
func (target *MQTTTarget) SendFromStore(eventKey string) error {
func (target *MQTTTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -180,7 +180,7 @@ func (target *MQTTTarget) SendFromStore(eventKey string) error {
return err
}
eventData, err := target.store.Get(eventKey)
eventData, err := target.store.Get(key.Name)
if err != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -195,7 +195,7 @@ func (target *MQTTTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Save - saves the events to the store if queuestore is configured, which will
+3 -3
View File
@@ -254,7 +254,7 @@ func (target *MySQLTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to MySQL.
func (target *MySQLTarget) SendFromStore(eventKey string) error {
func (target *MySQLTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -273,7 +273,7 @@ func (target *MySQLTarget) SendFromStore(eventKey string) error {
}
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -291,7 +291,7 @@ func (target *MySQLTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - closes underneath connections to MySQL database.
+3 -3
View File
@@ -335,7 +335,7 @@ func (target *NATSTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to Nats.
func (target *NATSTarget) SendFromStore(eventKey string) error {
func (target *NATSTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -345,7 +345,7 @@ func (target *NATSTarget) SendFromStore(eventKey string) error {
return err
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -359,7 +359,7 @@ func (target *NATSTarget) SendFromStore(eventKey string) error {
return err
}
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - closes underneath connections to NATS server.
+3 -3
View File
@@ -178,7 +178,7 @@ func (target *NSQTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to NSQ.
func (target *NSQTarget) SendFromStore(eventKey string) error {
func (target *NSQTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -188,7 +188,7 @@ func (target *NSQTarget) SendFromStore(eventKey string) error {
return err
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -203,7 +203,7 @@ func (target *NSQTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - closes underneath connections to NSQD server.
+3 -3
View File
@@ -251,7 +251,7 @@ func (target *PostgreSQLTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to PostgreSQL.
func (target *PostgreSQLTarget) SendFromStore(eventKey string) error {
func (target *PostgreSQLTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -269,7 +269,7 @@ func (target *PostgreSQLTarget) SendFromStore(eventKey string) error {
}
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and wouldve been already been sent successfully.
@@ -287,7 +287,7 @@ func (target *PostgreSQLTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - closes underneath connections to PostgreSQL database.
+3 -3
View File
@@ -223,7 +223,7 @@ func (target *RedisTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to redis.
func (target *RedisTarget) SendFromStore(eventKey string) error {
func (target *RedisTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
@@ -249,7 +249,7 @@ func (target *RedisTarget) SendFromStore(eventKey string) error {
target.firstPing = true
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and would've been already been sent successfully.
@@ -267,7 +267,7 @@ func (target *RedisTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - releases the resources used by the pool.
+3 -3
View File
@@ -207,12 +207,12 @@ func (target *WebhookTarget) send(eventData event.Event) error {
}
// SendFromStore - reads an event from store and sends it to webhook.
func (target *WebhookTarget) SendFromStore(eventKey string) error {
func (target *WebhookTarget) SendFromStore(key store.Key) error {
if err := target.init(); err != nil {
return err
}
eventData, eErr := target.store.Get(eventKey)
eventData, eErr := target.store.Get(key.Name)
if eErr != nil {
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
// Such events will not exist and would've been already been sent successfully.
@@ -230,7 +230,7 @@ func (target *WebhookTarget) SendFromStore(eventKey string) error {
}
// Delete the event from store.
return target.store.Del(eventKey)
return target.store.Del(key.Name)
}
// Close - does nothing and available for interface compatibility.
+3 -1
View File
@@ -22,6 +22,8 @@ import (
"strings"
"sync"
"sync/atomic"
"github.com/minio/minio/internal/store"
)
const (
@@ -34,7 +36,7 @@ type Target interface {
ID() TargetID
IsActive() (bool, error)
Save(Event) error
SendFromStore(string) error
SendFromStore(store.Key) error
Close() error
Store() TargetStore
}
+3 -1
View File
@@ -23,6 +23,8 @@ import (
"reflect"
"testing"
"time"
"github.com/minio/minio/internal/store"
)
type ExampleTarget struct {
@@ -61,7 +63,7 @@ func (target ExampleTarget) send(eventData Event) error {
}
// SendFromStore - interface compatible method does no-op.
func (target *ExampleTarget) SendFromStore(eventKey string) error {
func (target *ExampleTarget) SendFromStore(_ store.Key) error {
return nil
}
+8
View File
@@ -49,8 +49,16 @@ func consoleLog(console Logger, msg string, args ...interface{}) {
msg = ansiRE.ReplaceAllLiteralString(msg, "")
console.json(msg, args...)
case quietFlag:
if len(msg) != 0 && len(args) == 0 {
args = append(args, msg)
msg = "%s"
}
console.quiet(msg+"\n", args...)
default:
if len(msg) != 0 && len(args) == 0 {
args = append(args, msg)
msg = "%s"
}
console.pretty(msg+"\n", args...)
}
}
+3 -3
View File
@@ -349,9 +349,9 @@ func New(config Config) *Target {
}
// SendFromStore - reads the log from store and sends it to webhook.
func (h *Target) SendFromStore(key string) (err error) {
func (h *Target) SendFromStore(key store.Key) (err error) {
var eventData interface{}
eventData, err = h.store.Get(key)
eventData, err = h.store.Get(key.Name)
if err != nil {
if os.IsNotExist(err) {
return nil
@@ -372,7 +372,7 @@ func (h *Target) SendFromStore(key string) (err error) {
return err
}
// Delete the event from store.
return h.store.Del(key)
return h.store.Del(key.Name)
}
// Send the log message 'entry' to the http target.
+3 -3
View File
@@ -361,8 +361,8 @@ func (h *Target) Send(ctx context.Context, entry interface{}) error {
}
// SendFromStore - reads the log from store and sends it to kafka.
func (h *Target) SendFromStore(key string) (err error) {
auditEntry, err := h.store.Get(key)
func (h *Target) SendFromStore(key store.Key) (err error) {
auditEntry, err := h.store.Get(key.Name)
if err != nil {
if os.IsNotExist(err) {
return nil
@@ -376,7 +376,7 @@ func (h *Target) SendFromStore(key string) (err error) {
return
}
// Delete the event from store.
return h.store.Del(key)
return h.store.Del(key.Name)
}
// Cancel - cancels the target
+117
View File
@@ -0,0 +1,117 @@
// Copyright (c) 2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package store
import (
"errors"
"fmt"
"sync"
)
// ErrBatchFull indicates that the batch is full
var ErrBatchFull = errors.New("batch is full")
type key interface {
string | int | int64
}
// Batch represents an ordered batch
type Batch[K key, T any] struct {
keys []K
items map[K]T
limit uint32
sync.Mutex
}
// Add adds the item to the batch
func (b *Batch[K, T]) Add(key K, item T) error {
b.Lock()
defer b.Unlock()
if b.isFull() {
return ErrBatchFull
}
if _, ok := b.items[key]; !ok {
b.keys = append(b.keys, key)
}
b.items[key] = item
return nil
}
// GetAll fetches the items and resets the batch
// Returned items are not referenced by the batch
func (b *Batch[K, T]) GetAll() (orderedKeys []K, orderedItems []T, err error) {
b.Lock()
defer b.Unlock()
orderedKeys = append([]K(nil), b.keys...)
for _, key := range orderedKeys {
item, ok := b.items[key]
if !ok {
err = fmt.Errorf("item not found for the key: %v; should not happen;", key)
return
}
orderedItems = append(orderedItems, item)
delete(b.items, key)
}
b.keys = b.keys[:0]
return
}
// GetByKey will get the batch item by the provided key
func (b *Batch[K, T]) GetByKey(key K) (T, bool) {
b.Lock()
defer b.Unlock()
item, ok := b.items[key]
return item, ok
}
// Len returns the no of items in the batch
func (b *Batch[K, T]) Len() int {
b.Lock()
defer b.Unlock()
return len(b.keys)
}
// IsFull checks if the batch is full or not
func (b *Batch[K, T]) IsFull() bool {
b.Lock()
defer b.Unlock()
return b.isFull()
}
func (b *Batch[K, T]) isFull() bool {
return len(b.items) >= int(b.limit)
}
// NewBatch creates a new batch
func NewBatch[K key, T any](limit uint32) *Batch[K, T] {
return &Batch[K, T]{
keys: make([]K, 0, limit),
items: make(map[K]T, limit),
limit: limit,
}
}
+126
View File
@@ -0,0 +1,126 @@
// Copyright (c) 2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package store
import (
"errors"
"sync"
"testing"
)
func TestBatch(t *testing.T) {
var limit uint32 = 100
batch := NewBatch[int, int](limit)
for i := 0; i < int(limit); i++ {
if err := batch.Add(i, i); err != nil {
t.Fatalf("failed to add %v; %v", i, err)
}
if _, ok := batch.GetByKey(i); !ok {
t.Fatalf("failed to get the item by key %v after adding", i)
}
}
err := batch.Add(101, 101)
if err == nil || !errors.Is(err, ErrBatchFull) {
t.Fatalf("Expected err %v but got %v", ErrBatchFull, err)
}
if !batch.IsFull() {
t.Fatal("Expected batch.IsFull to be true but got false")
}
batchLen := batch.Len()
if batchLen != int(limit) {
t.Fatalf("expected batch length to be %v but got %v", limit, batchLen)
}
keys, items, err := batch.GetAll()
if err != nil {
t.Fatalf("unable to get the items from the batch; %v", err)
}
if len(items) != int(limit) {
t.Fatalf("Expected length of the batch items to be %v but got %v", limit, len(items))
}
if len(keys) != int(limit) {
t.Fatalf("Expected length of the batch keys to be %v but got %v", limit, len(items))
}
batchLen = batch.Len()
if batchLen != 0 {
t.Fatalf("expected batch to be empty but still left with %d items", batchLen)
}
// Add duplicate entries
for i := 0; i < 10; i++ {
if err := batch.Add(99, 99); err != nil {
t.Fatalf("failed to add duplicate item %v to batch after Get; %v", i, err)
}
}
if _, ok := batch.GetByKey(99); !ok {
t.Fatal("failed to get the duplicxate item by key '99' after adding")
}
keys, items, err = batch.GetAll()
if err != nil {
t.Fatalf("unable to get the items from the batch; %v", err)
}
if len(items) != 1 {
t.Fatalf("Expected length of the batch items to be 1 but got %v", len(items))
}
if len(keys) != 1 {
t.Fatalf("Expected length of the batch keys to be 1 but got %v", len(items))
}
// try adding again after Get.
for i := 0; i < int(limit); i++ {
if err := batch.Add(i, i); err != nil {
t.Fatalf("failed to add item %v to batch after Get; %v", i, err)
}
if _, ok := batch.GetByKey(i); !ok {
t.Fatalf("failed to get the item by key %v after adding", i)
}
}
}
func TestBatchWithConcurrency(t *testing.T) {
var limit uint32 = 100
batch := NewBatch[int, int](limit)
var wg sync.WaitGroup
for i := 0; i < int(limit); i++ {
wg.Add(1)
go func(item int) {
defer wg.Done()
if err := batch.Add(item, item); err != nil {
t.Errorf("failed to add item %v; %v", item, err)
return
}
if _, ok := batch.GetByKey(item); !ok {
t.Errorf("failed to get the item by key %v after adding", item)
}
}(i)
}
wg.Wait()
keys, items, err := batch.GetAll()
if err != nil {
t.Fatalf("unable to get the items from the batch; %v", err)
}
if len(items) != int(limit) {
t.Fatalf("expected batch length %v but got %v", limit, len(items))
}
if len(keys) != int(limit) {
t.Fatalf("Expected length of the batch keys to be %v but got %v", limit, len(items))
}
batchLen := batch.Len()
if batchLen != 0 {
t.Fatalf("expected batch to be empty but still left with %d items", batchLen)
}
}
+15
View File
@@ -167,6 +167,21 @@ func (store *QueueStore[_]) Del(key string) error {
return store.del(key)
}
// DelList - Deletes a list of entries from the store.
// Returns an error even if one key fails to be deleted.
func (store *QueueStore[_]) DelList(keys []string) error {
store.Lock()
defer store.Unlock()
for _, key := range keys {
if err := store.del(key); err != nil {
return err
}
}
return nil
}
// Len returns the entry count.
func (store *QueueStore[_]) Len() int {
store.RLock()
+22 -14
View File
@@ -39,7 +39,7 @@ var ErrNotConnected = errors.New("not connected to target server/service")
// Target - store target interface
type Target interface {
Name() string
SendFromStore(key string) error
SendFromStore(key Key) error
}
// Store - Used to persist items.
@@ -49,16 +49,23 @@ type Store[I any] interface {
Len() int
List() ([]string, error)
Del(key string) error
DelList(key []string) error
Open() error
Extension() string
}
// Key denotes the key present in the store.
type Key struct {
Name string
IsLast bool
}
// replayItems - Reads the items from the store and replays.
func replayItems[I any](store Store[I], doneCh <-chan struct{}, log logger, id string) <-chan string {
itemKeyCh := make(chan string)
func replayItems[I any](store Store[I], doneCh <-chan struct{}, log logger, id string) <-chan Key {
keyCh := make(chan Key)
go func() {
defer close(itemKeyCh)
defer close(keyCh)
retryTicker := time.NewTicker(retryInterval)
defer retryTicker.Stop()
@@ -68,9 +75,10 @@ func replayItems[I any](store Store[I], doneCh <-chan struct{}, log logger, id s
if err != nil {
log(context.Background(), fmt.Errorf("store.List() failed with: %w", err), id)
} else {
for _, name := range names {
keyCount := len(names)
for i, name := range names {
select {
case itemKeyCh <- strings.TrimSuffix(name, store.Extension()):
case keyCh <- Key{strings.TrimSuffix(name, store.Extension()), keyCount == i+1}:
// Get next key.
case <-doneCh:
return
@@ -86,17 +94,17 @@ func replayItems[I any](store Store[I], doneCh <-chan struct{}, log logger, id s
}
}()
return itemKeyCh
return keyCh
}
// sendItems - Reads items from the store and re-plays.
func sendItems(target Target, itemKeyCh <-chan string, doneCh <-chan struct{}, logger logger) {
func sendItems(target Target, keyCh <-chan Key, doneCh <-chan struct{}, logger logger) {
retryTicker := time.NewTicker(retryInterval)
defer retryTicker.Stop()
send := func(itemKey string) bool {
send := func(key Key) bool {
for {
err := target.SendFromStore(itemKey)
err := target.SendFromStore(key)
if err == nil {
break
}
@@ -120,13 +128,13 @@ func sendItems(target Target, itemKeyCh <-chan string, doneCh <-chan struct{}, l
for {
select {
case itemKey, ok := <-itemKeyCh:
case key, ok := <-keyCh:
if !ok {
// closed channel.
return
}
if !send(itemKey) {
if !send(key) {
return
}
case <-doneCh:
@@ -139,8 +147,8 @@ func sendItems(target Target, itemKeyCh <-chan string, doneCh <-chan struct{}, l
func StreamItems[I any](store Store[I], target Target, doneCh <-chan struct{}, logger logger) {
go func() {
// Replays the items from the store.
itemKeyCh := replayItems(store, doneCh, logger, target.Name())
keyCh := replayItems(store, doneCh, logger, target.Name())
// Send items from the store.
sendItems(target, itemKeyCh, doneCh, logger)
sendItems(target, keyCh, doneCh, logger)
}()
}