Compare commits

...

27 Commits

Author SHA1 Message Date
Harshavardhana b00cda8ad4 Avoid running lock maintenance from all nodes (#8737)
Co-Authored-By: Krishnan Parthasarathi <krisis@users.noreply.github.com>
2020-01-03 23:11:07 +05:30
Anis Elleuch d861edfc00 xl: Print the correct err msg when access to the backend is forbidden (#8735)
minio server /data{1..4} shows an error about inability to bind a port, though
the real problem is /data{1..4} cannot be created because of the lack of
permissions.

This commit fix the behavior.
2020-01-03 21:15:26 +05:30
Harshavardhana dd311623df Update design doc with zone implementation details (#8738)
Fixes #8719
2020-01-02 16:46:16 -08:00
Harshavardhana cb935980a5 Fix version to be release-tag (#8730) 2020-01-02 20:18:32 +05:30
Praveen raj Mani 157721f694 Fix readiness to return 200 for read-only mode (#8728)
- We should declare a cluster ready even if read quorum is achieved (atleast n/2 disks are online).
- Such that, all the zones should have enough read quorum. Thus making the cluster ready for reads.
2020-01-02 05:05:01 -08:00
Nitish Tiwari 97d799b9f0 doc: Update Kubernetes examples to use Readiness Checks (#8727) 2020-01-01 08:25:09 -08:00
Harshavardhana 0b7bd024fb Fix dependencies graph for minio source compilation (#8717)
We had messy cyclical dependency problem with `mc`
due to dependencies in pkg/console, moved the pkg/console
to minio for more control and also to avoid any further
cyclical dependencies of `mc` clobbering up the
dependencies on server.

Fixes #8659
2019-12-31 09:36:13 +05:30
Harshavardhana 3af70b36fd Disallow creating buckets even with different domains (#8716)
If two distinct clusters are started with different domains
along with single common domain, this situation was leading
to conflicting buckets getting created on different clusters

To avoid this do not prematurely error out if the key has no
entries, let the caller decide on which entry matches and
which entry is valid. This allows support for MINIO_DOMAIN
with one common domain, but each cluster may have their own
domains.

Fixes #8705
2019-12-30 17:11:47 -08:00
Minio Trusted 8eba97da74 Update yaml files to latest version RELEASE.2019-12-30T05-45-39Z 2019-12-30 05:56:46 +00:00
Harshavardhana 669c9da85d Disable federated buckets when etcd is namespaced (#8709)
This is to ensure that when we have multiple tenants
deployed all sharing the same etcd for global bucket
should avoid listing each others buckets, this leads
to information leak which should be avoided unless
etcd is not namespaced for IAM assets in which case
it can be assumed that its a federated setup.

Federated setup and namespaced IAM assets on etcd
is not supported since namespacing is only useful
when you wish to separate the tenants as isolated
instances of MinIO.

This PR allows a new type of behavior, primarily
driven by the usecase of m3(mkube) multi-tenant
deployments with global bucket support.
2019-12-29 08:56:45 -08:00
Praveen raj Mani 5d09233115 Fix Readiness check (#8681)
- Remove goroutine-check in Readiness check
- Bring in quorum check for readiness

Fixes #8385

Co-authored-by: Harshavardhana <harsha@minio.io>
2019-12-28 22:24:43 +05:30
Haines Chan b2044dd22f Fix typo in comments (#8704) 2019-12-27 15:51:32 +05:30
Anis Elleuch c31e67dcce Better error when the server is unable to write in the backend (#8697) 2019-12-25 22:05:54 -08:00
kannappanr cd59a945d8 Fix: Policy action name for Get/Put BucketLifeCycle (#8698)
S3:GetBucketLifeCycle should be changed to S3:GetLifeCycleConfiguration
S3:PutBucketLifeCycle should be changed to S3:PutLifeCycleConfiguration
2019-12-25 22:04:46 -08:00
Harshavardhana d4a390028a node 6.x is EOL'ed upgrade to latest stable (#8702) 2019-12-26 08:27:35 +05:30
Bruce Wang c476b27a65 Comment typo "index max" to "index map" (#8700) 2019-12-24 21:57:43 -08:00
kannappanr eaa838a63f mint: Change aws cli version (#8699) 2019-12-24 19:58:19 -08:00
Minio Trusted 4364102363 Update yaml files to latest version RELEASE.2019-12-24T23-04-45Z 2019-12-24 23:15:44 +00:00
Harshavardhana 99ad445260 Avoid double for loops in notification init (#8691) 2019-12-24 13:49:48 -08:00
Harshavardhana 54431b3953 Change replica set detection for localhost on single endpoint (#8692) 2019-12-24 11:31:32 -08:00
Harshavardhana f68a7005c0 Improve disk formatting stage for large disk sets (#8690) 2019-12-23 16:31:03 -08:00
Harshavardhana 725172e13b fix: Do not need safe-mode for unreachable targets upon restart (#8686) 2019-12-21 22:35:50 -08:00
Harshavardhana 0f092c2be8 Add env values and description for config sub-systems (#8684)
The output is directly borrowed from `mc` command output
2019-12-21 22:52:16 +05:30
Harshavardhana a3c8ef79a4 fix: remove extra newline from GetConfig() output (#8678) 2019-12-20 14:47:14 -08:00
Aditya Manthramurthy 01468d5a75 Fix user and policy deletion IAM commands (#8683) 2019-12-20 14:42:08 -08:00
Harshavardhana 8f1243986e fix: listenBucket should filter events based on bucket (#8677)
Currently all bucket events are sent to all watchers
with matching prefix and event names, this becomes
problematic and prone to performance issues, fix this
situation by filtering based on buckets as well.
2019-12-20 11:45:03 -08:00
Minio Trusted dc1eb57f4e Update yaml files to latest version RELEASE.2019-12-19T22-52-26Z 2019-12-19 22:58:19 +00:00
72 changed files with 4902 additions and 3995 deletions
+3200 -2740
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -46,9 +46,7 @@ function main()
gw_pid="$(start_minio_gateway_s3)"
SERVER_ENDPOINT=127.0.0.1:24240 ENABLE_HTTPS=0 ACCESS_KEY=minio \
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh aws-sdk-go \
aws-sdk-java aws-sdk-php aws-sdk-ruby awscli healthcheck minio-dotnet \
minio-go minio-java minio-js minio-py
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh
rv=$?
kill "$sr_pid"
+8 -1
View File
@@ -427,6 +427,10 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
var s strings.Builder
hkvs := config.HelpSubSysMap[""]
var count int
for _, hkv := range hkvs {
count += len(cfg[hkv.Key])
}
for _, hkv := range hkvs {
v := cfg[hkv.Key]
for target, kv := range v {
@@ -460,7 +464,10 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
}
s.WriteString(config.KvSpaceSeparator)
s.WriteString(kv.String())
s.WriteString(config.KvNewline)
count--
if count > 0 {
s.WriteString(config.KvNewline)
}
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"os"
"strings"
"github.com/minio/minio-go/pkg/set"
"github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/pkg/cpu"
"github.com/minio/minio/pkg/disk"
"github.com/minio/minio/pkg/madmin"
+1 -1
View File
@@ -29,7 +29,7 @@ import (
"time"
"github.com/gorilla/mux"
"github.com/minio/minio-go/pkg/set"
"github.com/minio/minio-go/v6/pkg/set"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/rest"
+1 -1
View File
@@ -278,7 +278,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
// If etcd, dns federation configured list buckets from etcd.
var bucketsInfo []BucketInfo
if globalDNSConfig != nil {
if globalDNSConfig != nil && globalBucketFederation {
dnsBuckets, err := globalDNSConfig.List()
if err != nil && err != dns.ErrNoEntriesFound {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+5 -1
View File
@@ -228,6 +228,7 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
bucketName := vars["bucket"]
values := r.URL.Query()
values.Set(peerRESTListenBucket, bucketName)
var prefix string
if len(values[peerRESTListenPrefix]) > 1 {
@@ -295,6 +296,9 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
if !ok {
return false
}
if ev.S3.Bucket.Name != values.Get(peerRESTListenBucket) {
return false
}
objectName, uerr := url.QueryUnescape(ev.S3.Object.Key)
if uerr != nil {
objectName = ev.S3.Object.Key
@@ -306,7 +310,7 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
if peer == nil {
continue
}
peer.Listen(listenCh, doneCh, r.URL.Query())
peer.Listen(listenCh, doneCh, values)
}
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
+12
View File
@@ -315,6 +315,13 @@ func lookupConfigs(s config.Config) {
}
}
// Bucket federation is 'true' only when IAM assets are not namespaced
// per tenant and all tenants interested in globally available users
// if namespace was requested such as specifying etcdPathPrefix then
// we assume that users are interested in global bucket support
// but not federation.
globalBucketFederation = etcdCfg.PathPrefix == "" && etcdCfg.Enabled
if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
globalDNSConfig, err = dns.NewCoreDNS(etcdCfg.Config,
dns.DomainNames(globalDomainNames),
@@ -415,6 +422,11 @@ func lookupConfigs(s config.Config) {
logger.AddAuditTarget(http.New(l.Endpoint, loggerUserAgent, string(logger.All), NewCustomHTTPTransport()))
}
}
globalConfigTargetList, err = notify.GetNotificationTargets(s, GlobalServiceDoneCh, NewCustomHTTPTransport())
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
}
}
// Help - return sub-system level help
+39 -20
View File
@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"os"
"strings"
"unicode/utf8"
@@ -39,7 +40,6 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
// If its server mode or nas gateway, migrate the backend.
doneCh := make(chan struct{})
defer close(doneCh)
var encrypted bool
var err error
@@ -48,17 +48,27 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
if encrypted, err = checkBackendEncrypted(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) {
logger.Info("Waiting for config backend to be encrypted..")
continue
retryTimerCh := newRetryTimerSimple(doneCh)
var stop bool
for !stop {
select {
case <-retryTimerCh:
if encrypted, err = checkBackendEncrypted(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) {
logger.Info("Waiting for config backend to be encrypted..")
continue
}
close(doneCh)
return err
}
return err
stop = true
case <-globalOSSignalCh:
close(doneCh)
return fmt.Errorf("Config encryption process stopped gracefully")
}
break
}
close(doneCh)
if encrypted {
// backend is encrypted, but credentials are not specified
@@ -83,24 +93,33 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
return err
}
doneCh = make(chan struct{})
defer close(doneCh)
retryTimerCh = newRetryTimerSimple(doneCh)
// Migrating Config backend needs a retry mechanism for
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
// Migrate IAM configuration
if err = migrateConfigPrefixToEncrypted(objAPI, activeCredOld, encrypted); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for config backend to be encrypted..")
continue
for {
select {
case <-retryTimerCh:
// Migrate IAM configuration
if err = migrateConfigPrefixToEncrypted(objAPI, activeCredOld, encrypted); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for config backend to be encrypted..")
continue
}
return err
}
return err
return nil
case <-globalOSSignalCh:
return fmt.Errorf("Config encryption process stopped gracefully")
}
break
}
return nil
}
const (
+1 -1
View File
@@ -24,7 +24,7 @@ import (
"regexp"
"strings"
"github.com/minio/minio-go/pkg/set"
"github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/madmin"
+34 -12
View File
@@ -20,7 +20,7 @@ import (
"errors"
"fmt"
"io"
"os"
"net"
"syscall"
"github.com/minio/minio/pkg/color"
@@ -36,22 +36,39 @@ type Err struct {
hint string
}
// Clone returns a new Err struct with the same information
func (u Err) Clone() Err {
return Err{
msg: u.msg,
detail: u.detail,
action: u.action,
hint: u.hint,
}
}
// Return the error message
func (u Err) Error() string {
if u.detail == "" {
return u.msg
if u.msg != "" {
return u.msg
}
return "<nil>"
}
return u.detail
}
// Msg - Replace the current error's message
func (u Err) Msg(m string, args ...interface{}) Err {
return Err{
msg: fmt.Sprintf(m, args...),
detail: u.detail,
action: u.action,
hint: u.hint,
}
e := u.Clone()
e.msg = fmt.Sprintf(m, args...)
return e
}
// Hint - Replace the current error's message
func (u Err) Hint(m string, args ...interface{}) Err {
e := u.Clone()
e.hint = fmt.Sprintf(m, args...)
return e
}
// ErrFn function wrapper
@@ -77,6 +94,10 @@ func newErrFn(msg, action, hint string) ErrFn {
// ErrorToErr inspects the passed error and transforms it
// to the appropriate UI error.
func ErrorToErr(err error) Err {
if err == nil {
return Err{}
}
// If this is already a Err, do nothing
if e, ok := err.(Err); ok {
return e
@@ -85,9 +106,11 @@ func ErrorToErr(err error) Err {
// Show a generic message for known golang errors
if errors.Is(err, syscall.EADDRINUSE) {
return ErrPortAlreadyInUse(err).Msg("Specified port is already in use")
} else if errors.Is(err, syscall.EACCES) {
return ErrPortAccess(err).Msg("Insufficient permissions to use specified port")
} else if os.IsPermission(err) {
} else if errors.Is(err, syscall.EACCES) || errors.Is(err, syscall.EPERM) {
switch err.(type) {
case *net.OpError:
return ErrPortAccess(err).Msg("Insufficient permissions to use specified port")
}
return ErrNoPermissionsToAccessDirFiles(err).Msg("Insufficient permissions to access path")
} else if errors.Is(err, io.ErrUnexpectedEOF) {
return ErrUnexpectedDataContent(err)
@@ -95,7 +118,6 @@ func ErrorToErr(err error) Err {
// Failed to identify what type of error this, return a simple UI error
return Err{msg: err.Error()}
}
}
// FmtError converts a fatal error message to a more clear error
-6
View File
@@ -117,9 +117,6 @@ func (c *CoreDNS) list(key string) ([]SrvRecord, error) {
if err != nil {
return nil, err
}
if r.Count == 0 {
return nil, ErrNoEntriesFound
}
}
var srvRecords []SrvRecord
@@ -143,9 +140,6 @@ func (c *CoreDNS) list(key string) ([]SrvRecord, error) {
srvRecords = append(srvRecords, srvRecord)
}
if len(srvRecords) == 0 {
return nil, ErrNoEntriesFound
}
sort.Slice(srvRecords, func(i int, j int) bool {
return srvRecords[i].Key < srvRecords[j].Key
})
+35 -18
View File
@@ -18,7 +18,6 @@ package cmd
import (
"context"
"errors"
"fmt"
"net"
"net/url"
@@ -230,23 +229,25 @@ func (endpoints Endpoints) GetString(i int) string {
return endpoints[i].String()
}
func (endpoints Endpoints) doAnyHostsResolveToLocalhost() bool {
for _, endpoint := range endpoints {
hostIPs, err := getHostIP(endpoint.Hostname())
if err != nil {
continue
}
var loopback int
for _, hostIP := range hostIPs.ToSlice() {
if net.ParseIP(hostIP).IsLoopback() {
loopback++
}
}
if loopback == len(hostIPs) {
return true
func hostResolveToLocalhost(endpoint Endpoint) bool {
hostIPs, err := getHostIP(endpoint.Hostname())
if err != nil {
// Log the message to console about the host resolving
reqInfo := (&logger.ReqInfo{}).AppendTags(
"host",
endpoint.Hostname(),
)
ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err, logger.Application)
return false
}
var loopback int
for _, hostIP := range hostIPs.ToSlice() {
if net.ParseIP(hostIP).IsLoopback() {
loopback++
}
}
return false
return loopback == len(hostIPs)
}
func (endpoints Endpoints) atleastOneEndpointLocal() bool {
@@ -294,8 +295,9 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
endpoints[i].Hostname(),
)
if k8sReplicaSet && endpoints.doAnyHostsResolveToLocalhost() {
err := errors.New("host found resolves to 127.*, DNS incorrectly configured retrying")
if k8sReplicaSet && hostResolveToLocalhost(endpoints[i]) {
err := fmt.Errorf("host %s resolves to 127.*, DNS incorrectly configured retrying",
endpoints[i])
// time elapsed
timeElapsed := time.Since(startTime)
// log error only if more than 1s elapsed
@@ -352,6 +354,21 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
// previous local we continue to wait to look for
// atleast one local.
resolvedList[i] = false
// time elapsed
err := fmt.Errorf("no endpoint is local to this host: %s", endpoints[i])
timeElapsed := time.Since(startTime)
// log error only if more than 1s elapsed
if timeElapsed > time.Second {
reqInfo.AppendTags("elapsedTime",
humanize.RelTime(startTime,
startTime.Add(timeElapsed),
"elapsed",
"",
))
ctx := logger.SetReqInfo(context.Background(),
reqInfo)
logger.LogIf(ctx, err, logger.Application)
}
continue
}
epsResolved++
+17 -10
View File
@@ -343,17 +343,24 @@ func formatFSFixDeploymentID(fsFormatPath string) error {
defer close(doneCh)
var wlk *lock.LockedFile
for range newRetryTimerSimple(doneCh) {
wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0)
if err == lock.ErrAlreadyLocked {
// Lock already present, sleep and attempt again
logger.Info("Another minio process(es) might be holding a lock to the file %s. Please kill that minio process(es) (elapsed %s)\n", fsFormatPath, getElapsedTime())
continue
retryCh := newRetryTimerSimple(doneCh)
var stop bool
for !stop {
select {
case <-retryCh:
wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0)
if err == lock.ErrAlreadyLocked {
// Lock already present, sleep and attempt again
logger.Info("Another minio process(es) might be holding a lock to the file %s. Please kill that minio process(es) (elapsed %s)\n", fsFormatPath, getElapsedTime())
continue
}
if err != nil {
return err
}
stop = true
case <-globalOSSignalCh:
return fmt.Errorf("Initializing FS format stopped gracefully")
}
break
}
if err != nil {
return err
}
defer wlk.Close()
+70 -102
View File
@@ -155,24 +155,9 @@ func newFormatXLV3(numSets int, setLen int) *formatXLV3 {
return format
}
// Returns formatXL.XL.Version information, this code is specifically
// used to read XL `format.json` and capture any version information
// that it may have.
func formatXLGetVersion(formatPath string) (string, error) {
format := &formatXLVersionDetect{}
b, err := ioutil.ReadFile(formatPath)
if err != nil {
return "", err
}
if err = json.Unmarshal(b, format); err != nil {
return "", err
}
return format.XL.Version, nil
}
// Returns format meta format version from `format.json`. This code
// is specifically used to detect meta format.
func formatMetaGetFormatBackendXL(formatPath string) (string, error) {
// Returns format XL version after reading `format.json`, returns
// successfully the version only if the backend is XL.
func formatGetBackendXLVersion(formatPath string) (string, error) {
meta := &formatMetaV1{}
b, err := ioutil.ReadFile(formatPath)
if err != nil {
@@ -184,7 +169,15 @@ func formatMetaGetFormatBackendXL(formatPath string) (string, error) {
if meta.Version != formatMetaVersionV1 {
return "", fmt.Errorf(`format.Version expected: %s, got: %s`, formatMetaVersionV1, meta.Version)
}
return meta.Format, nil
if meta.Format != formatBackendXL {
return "", fmt.Errorf(`found backend %s, expected %s`, meta.Format, formatBackendXL)
}
// XL backend found, proceed to detect version.
format := &formatXLVersionDetect{}
if err = json.Unmarshal(b, format); err != nil {
return "", err
}
return format.XL.Version, nil
}
// Migrates all previous versions to latest version of `format.json`,
@@ -192,30 +185,27 @@ func formatMetaGetFormatBackendXL(formatPath string) (string, error) {
// first before it V2 migrates to V3.
func formatXLMigrate(export string) error {
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
backend, err := formatMetaGetFormatBackendXL(formatPath)
if err != nil {
return err
}
if backend != formatBackendXL {
return fmt.Errorf(`Disk %s: found backend %s, expected %s`, export, backend, formatBackendXL)
}
version, err := formatXLGetVersion(formatPath)
version, err := formatGetBackendXLVersion(formatPath)
if err != nil {
return err
}
switch version {
case formatXLVersionV1:
if err = formatXLMigrateV1ToV2(export); err != nil {
if err = formatXLMigrateV1ToV2(export, version); err != nil {
return err
}
// Migrate successful v1 => v2, proceed to v2 => v3
version = formatXLVersionV2
fallthrough
case formatXLVersionV2:
if err = formatXLMigrateV2ToV3(export); err != nil {
if err = formatXLMigrateV2ToV3(export, version); err != nil {
return err
}
// Migrate successful v2 => v3, v3 is latest
version = formatXLVersionV3
fallthrough
case formatXLVersionV3:
// format-V3 is the latest verion.
// v3 is the latest version, return.
return nil
}
return fmt.Errorf(`%s: unknown format version %s`, export, version)
@@ -223,16 +213,13 @@ func formatXLMigrate(export string) error {
// Migrates version V1 of format.json to version V2 of format.json,
// migration fails upon any error.
func formatXLMigrateV1ToV2(export string) error {
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
version, err := formatXLGetVersion(formatPath)
if err != nil {
return err
}
func formatXLMigrateV1ToV2(export, version string) error {
if version != formatXLVersionV1 {
return fmt.Errorf(`Disk %s: format version expected %s, found %s`, export, formatXLVersionV1, version)
}
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
formatV1 := &formatXLV1{}
b, err := ioutil.ReadFile(formatPath)
if err != nil {
@@ -260,15 +247,12 @@ func formatXLMigrateV1ToV2(export string) error {
}
// Migrates V2 for format.json to V3 (Flat hierarchy for multipart)
func formatXLMigrateV2ToV3(export string) error {
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
version, err := formatXLGetVersion(formatPath)
if err != nil {
return err
}
func formatXLMigrateV2ToV3(export, version string) error {
if version != formatXLVersionV2 {
return fmt.Errorf(`Disk %s: format version expected %s, found %s`, export, formatXLVersionV2, version)
}
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
formatV2 := &formatXLV2{}
b, err := ioutil.ReadFile(formatPath)
if err != nil {
@@ -360,18 +344,18 @@ func saveFormatXL(disk StorageAPI, format interface{}) error {
return err
}
tmpFormatJSON := mustGetUUID() + ".json"
tmpFormat := mustGetUUID()
// Purge any existing temporary file, okay to ignore errors here.
defer disk.DeleteFile(minioMetaBucket, tmpFormatJSON)
defer disk.DeleteFile(minioMetaBucket, tmpFormat)
// Append file `format.json.tmp`.
if err = disk.WriteAll(minioMetaBucket, tmpFormatJSON, bytes.NewReader(formatBytes)); err != nil {
// write to unique file.
if err = disk.WriteAll(minioMetaBucket, tmpFormat, bytes.NewReader(formatBytes)); err != nil {
return err
}
// Rename file `uuid.json` --> `format.json`.
return disk.RenameFile(minioMetaBucket, tmpFormatJSON, minioMetaBucket, formatConfigFile)
return disk.RenameFile(minioMetaBucket, tmpFormat, minioMetaBucket, formatConfigFile)
}
var ignoredHiddenDirectories = []string{
@@ -635,6 +619,39 @@ func formatXLV3Check(reference *formatXLV3, format *formatXLV3) error {
return fmt.Errorf("Disk ID %s not found in any disk sets %s", this, format.XL.Sets)
}
// Initializes meta volume on all input storage disks.
func initFormatXLMetaVolume(storageDisks []StorageAPI, formats []*formatXLV3) error {
// This happens for the first time, but keep this here since this
// is the only place where it can be made expensive optimizing all
// other calls. Create minio meta volume, if it doesn't exist yet.
// Initialize errs to collect errors inside go-routine.
g := errgroup.WithNErrs(len(storageDisks))
// Initialize all disks in parallel.
for index := range storageDisks {
index := index
g.Go(func() error {
if formats[index] == nil || storageDisks[index] == nil {
// Ignore create meta volume on disks which are not found.
return nil
}
return makeFormatXLMetaVolumes(storageDisks[index])
}, index)
}
// Return upon first error.
for _, err := range g.Wait() {
if err == nil {
continue
}
return toObjectErr(err, minioMetaBucket)
}
// Return success here.
return nil
}
// saveFormatXLAll - populates `format.json` on disks in its order.
func saveFormatXLAll(ctx context.Context, storageDisks []StorageAPI, formats []*formatXLV3) error {
g := errgroup.WithNErrs(len(storageDisks))
@@ -646,6 +663,9 @@ func saveFormatXLAll(ctx context.Context, storageDisks []StorageAPI, formats []*
if formats[index] == nil || storageDisks[index] == nil {
return errDiskNotFound
}
if err := makeFormatXLMetaVolumes(storageDisks[index]); err != nil {
return err
}
return saveFormatXL(storageDisks[index], formats[index])
}, index)
}
@@ -745,11 +765,6 @@ func initFormatXL(ctx context.Context, storageDisks []StorageAPI, setCount, driv
}
}
// Initialize meta volume, if volume already exists ignores it.
if err := initFormatXLMetaVolume(storageDisks, formats); err != nil {
return format, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %w", err)
}
// Save formats `format.json` across all disks.
if err := saveFormatXLAll(ctx, storageDisks, formats); err != nil {
return nil, err
@@ -760,23 +775,9 @@ func initFormatXL(ctx context.Context, storageDisks []StorageAPI, setCount, driv
// Make XL backend meta volumes.
func makeFormatXLMetaVolumes(disk StorageAPI) error {
// Attempt to create `.minio.sys`.
if err := disk.MakeVol(minioMetaBucket); err != nil {
if !IsErrIgnored(err, initMetaVolIgnoredErrs...) {
return err
}
}
if err := disk.MakeVol(minioMetaTmpBucket); err != nil {
if !IsErrIgnored(err, initMetaVolIgnoredErrs...) {
return err
}
}
if err := disk.MakeVol(minioMetaBackgroundOpsBucket); err != nil {
if !IsErrIgnored(err, initMetaVolIgnoredErrs...) {
return err
}
}
if err := disk.MakeVol(minioMetaMultipartBucket); err != nil {
// Attempt to create MinIO internal buckets.
err := disk.MakeVolBulk(minioMetaBucket, minioMetaTmpBucket, minioMetaMultipartBucket, minioMetaBackgroundOpsBucket)
if err != nil {
if !IsErrIgnored(err, initMetaVolIgnoredErrs...) {
return err
}
@@ -786,39 +787,6 @@ func makeFormatXLMetaVolumes(disk StorageAPI) error {
var initMetaVolIgnoredErrs = append(baseIgnoredErrs, errVolumeExists)
// Initializes meta volume on all input storage disks.
func initFormatXLMetaVolume(storageDisks []StorageAPI, formats []*formatXLV3) error {
// This happens for the first time, but keep this here since this
// is the only place where it can be made expensive optimizing all
// other calls. Create minio meta volume, if it doesn't exist yet.
// Initialize errs to collect errors inside go-routine.
g := errgroup.WithNErrs(len(storageDisks))
// Initialize all disks in parallel.
for index := range storageDisks {
index := index
g.Go(func() error {
if formats[index] == nil || storageDisks[index] == nil {
// Ignore create meta volume on disks which are not found.
return nil
}
return makeFormatXLMetaVolumes(storageDisks[index])
}, index)
}
// Return upon first error.
for _, err := range g.Wait() {
if err == nil {
continue
}
return toObjectErr(err, minioMetaBucket)
}
// Return success here.
return nil
}
// Get all UUIDs which are present in reference format should
// be present in the list of formats provided, those are considered
// as online UUIDs.
+2 -90
View File
@@ -149,95 +149,6 @@ func TestFormatXLEmpty(t *testing.T) {
}
}
// Tests format xl get version.
func TestFormatXLGetVersion(t *testing.T) {
// Get test root.
rootPath, err := getTestRoot()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootPath)
v := &formatXLVersionDetect{}
v.XL.Version = "1"
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
if err = ioutil.WriteFile(pathJoin(rootPath, formatConfigFile), b, os.FileMode(0644)); err != nil {
t.Fatal(err)
}
_, err = formatXLGetVersion("not-found")
if err == nil {
t.Fatal("Expected to fail but found success")
}
vstr, err := formatXLGetVersion(pathJoin(rootPath, formatConfigFile))
if err != nil {
t.Fatal(err)
}
if vstr != "1" {
t.Fatalf("Expected version '1', got '%s'", vstr)
}
}
// Tests format get backend format.
func TestFormatMetaGetFormatBackendXL(t *testing.T) {
// Get test root.
rootPath, err := getTestRoot()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootPath)
m := &formatMetaV1{
Format: "fs",
Version: formatMetaVersionV1,
}
b, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
if err = ioutil.WriteFile(pathJoin(rootPath, formatConfigFile), b, os.FileMode(0644)); err != nil {
t.Fatal(err)
}
_, err = formatMetaGetFormatBackendXL("not-found")
if err == nil {
t.Fatal("Expected to fail but found success")
}
format, err := formatMetaGetFormatBackendXL(pathJoin(rootPath, formatConfigFile))
if err != nil {
t.Fatal(err)
}
if format != m.Format {
t.Fatalf("Expected format value %s, got %s", m.Format, format)
}
m = &formatMetaV1{
Format: "xl",
Version: "2",
}
b, err = json.Marshal(m)
if err != nil {
t.Fatal(err)
}
if err = ioutil.WriteFile(pathJoin(rootPath, formatConfigFile), b, os.FileMode(0644)); err != nil {
t.Fatal(err)
}
_, err = formatMetaGetFormatBackendXL(pathJoin(rootPath, formatConfigFile))
if err == nil {
t.Fatal("Expected to fail with incompatible meta version")
}
}
// Tests xl format migration.
func TestFormatXLMigrate(t *testing.T) {
// Get test root.
@@ -271,10 +182,11 @@ func TestFormatXLMigrate(t *testing.T) {
t.Fatal(err)
}
migratedVersion, err := formatXLGetVersion(pathJoin(rootPath, minioMetaBucket, formatConfigFile))
migratedVersion, err := formatGetBackendXLVersion(pathJoin(rootPath, minioMetaBucket, formatConfigFile))
if err != nil {
t.Fatal(err)
}
if migratedVersion != formatXLVersionV3 {
t.Fatalf("expected version: %s, got: %s", formatXLVersionV3, migratedVersion)
}
+17 -1
View File
@@ -20,10 +20,12 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/user"
"path"
"path/filepath"
"sort"
@@ -129,7 +131,15 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) {
if err == errMinDiskSize {
return nil, err
}
return nil, config.ErrUnableToWriteInBackend(err)
// Show a descriptive error with a hint about how to fix it.
var username string
if u, err := user.Current(); err == nil {
username = u.Username
} else {
username = "<your-username>"
}
hint := fmt.Sprintf("Use 'sudo chown %s %s && sudo chmod u+rxw %s' to provide sufficient permissions.", username, fsPath, fsPath)
return nil, config.ErrUnableToWriteInBackend(err).Hint(hint)
}
// Assign a new UUID for FS minio mode. Each server instance
@@ -1346,3 +1356,9 @@ func (fs *FSObjects) IsEncryptionSupported() bool {
func (fs *FSObjects) IsCompressionSupported() bool {
return true
}
// IsReady - Check if the backend disk is ready to accept traffic.
func (fs *FSObjects) IsReady(_ context.Context) bool {
_, err := os.Stat(fs.fsPath)
return err == nil
}
+5
View File
@@ -192,3 +192,8 @@ func (a GatewayUnsupported) IsEncryptionSupported() bool {
func (a GatewayUnsupported) IsCompressionSupported() bool {
return false
}
// IsReady - No Op.
func (a GatewayUnsupported) IsReady(_ context.Context) bool {
return false
}
+5
View File
@@ -1296,3 +1296,8 @@ func (a *azureObjects) DeleteBucketPolicy(ctx context.Context, bucket string) er
func (a *azureObjects) IsCompressionSupported() bool {
return false
}
// IsReady returns whether the layer is ready to take requests.
func (a *azureObjects) IsReady(ctx context.Context) bool {
return minio.IsBackendOnline(ctx, a.httpClient, a.endpoint)
}
+5
View File
@@ -862,3 +862,8 @@ func (l *b2Objects) DeleteBucketPolicy(ctx context.Context, bucket string) error
func (l *b2Objects) IsCompressionSupported() bool {
return false
}
// IsReady returns whether the layer is ready to take requests.
func (l *b2Objects) IsReady(ctx context.Context) bool {
return minio.IsBackendOnline(ctx, l.httpClient, "https://api.backblazeb2.com/b2api/v1")
}
+5
View File
@@ -1467,3 +1467,8 @@ func (l *gcsGateway) DeleteBucketPolicy(ctx context.Context, bucket string) erro
func (l *gcsGateway) IsCompressionSupported() bool {
return false
}
// IsReady returns whether the layer is ready to take requests.
func (l *gcsGateway) IsReady(ctx context.Context) bool {
return minio.IsBackendOnline(ctx, l.httpClient, "https://storage.googleapis.com")
}
+5
View File
@@ -715,3 +715,8 @@ func (n *hdfsObjects) AbortMultipartUpload(ctx context.Context, bucket, object,
}
return hdfsToObjectErr(ctx, n.clnt.Remove(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, uploadID)), bucket, object, uploadID)
}
// IsReady returns whether the layer is ready to take requests.
func (n *hdfsObjects) IsReady(_ context.Context) bool {
return true
}
+6
View File
@@ -117,3 +117,9 @@ func (n *nasObjects) StorageInfo(ctx context.Context) minio.StorageInfo {
type nasObjects struct {
minio.ObjectLayer
}
// IsReady returns whether the layer is ready to take requests.
func (n *nasObjects) IsReady(ctx context.Context) bool {
sinfo := n.ObjectLayer.StorageInfo(ctx)
return sinfo.Backend.Type == minio.BackendFS
}
+5
View File
@@ -1098,3 +1098,8 @@ func (l *ossObjects) DeleteBucketPolicy(ctx context.Context, bucket string) erro
func (l *ossObjects) IsCompressionSupported() bool {
return false
}
// IsReady returns whether the layer is ready to take requests.
func (l *ossObjects) IsReady(ctx context.Context) bool {
return minio.IsBackendOnline(ctx, l.Client.HTTPClient, l.Client.Config.Endpoint)
}
+5
View File
@@ -670,3 +670,8 @@ func (l *s3Objects) IsCompressionSupported() bool {
func (l *s3Objects) IsEncryptionSupported() bool {
return minio.GlobalKMS != nil || len(minio.GlobalGatewaySSE) > 0
}
// IsReady returns whether the layer is ready to take requests.
func (l *s3Objects) IsReady(ctx context.Context) bool {
return minio.IsBackendOnline(ctx, l.HTTPClient, l.Client.EndpointURL().String())
}
+2 -1
View File
@@ -619,7 +619,8 @@ type bucketForwardingHandler struct {
func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if globalDNSConfig == nil || len(globalDomainNames) == 0 ||
guessIsHealthCheckReq(r) || guessIsMetricsReq(r) ||
guessIsRPCReq(r) || guessIsLoginSTSReq(r) || isAdminReq(r) {
guessIsRPCReq(r) || guessIsLoginSTSReq(r) || isAdminReq(r) ||
!globalBucketFederation {
f.handler.ServeHTTP(w, r)
return
}
+9 -3
View File
@@ -36,6 +36,7 @@ import (
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/certs"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/pubsub"
)
@@ -137,9 +138,10 @@ var (
// globalConfigSys server config system.
globalConfigSys *ConfigSys
globalNotificationSys *NotificationSys
globalPolicySys *PolicySys
globalIAMSys *IAMSys
globalNotificationSys *NotificationSys
globalConfigTargetList *event.TargetList
globalPolicySys *PolicySys
globalIAMSys *IAMSys
globalLifecycleSys *LifecycleSys
@@ -210,6 +212,10 @@ var (
// Allocated etcd endpoint for config and bucket DNS.
globalEtcdClient *etcd.Client
// Is set to true when Bucket federation is requested
// and is 'true' when etcdConfig.PathPrefix is empty
globalBucketFederation bool
// Allocated DNS config wrapper over etcd client.
globalDNSConfig *dns.CoreDNS
+9 -24
View File
@@ -17,31 +17,26 @@
package cmd
import (
"fmt"
"net/http"
"os"
"runtime"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
)
const (
minioHealthGoroutineThreshold = 10000
)
// ReadinessCheckHandler -- checks if there are more than threshold
// number of goroutines running, returns service unavailable.
//
// Readiness probes are used to detect situations where application
// is under heavy load and temporarily unable to serve. In a orchestrated
// setup like Kubernetes, containers reporting that they are not ready do
// not receive traffic through Kubernetes Services.
// ReadinessCheckHandler -- Checks if the quorum number of disks are available.
// For FS - Checks if the backend disk is available
// For Zones - Checks if all the zones have enough read quorum
func ReadinessCheckHandler(w http.ResponseWriter, r *http.Request) {
if err := goroutineCountCheck(minioHealthGoroutineThreshold); err != nil {
ctx := newContext(r, w, "ReadinessCheckHandler")
objLayer := newObjectLayerFn()
// Service not initialized yet
if objLayer == nil || !objLayer.IsReady(ctx) {
writeResponse(w, http.StatusServiceUnavailable, nil, mimeNone)
return
}
writeResponse(w, http.StatusOK, nil, mimeNone)
}
@@ -102,13 +97,3 @@ func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
}
writeResponse(w, http.StatusOK, nil, mimeNone)
}
// checks threshold against total number of go-routines in the system and
// throws error if more than threshold go-routines are running.
func goroutineCountCheck(threshold int) error {
count := runtime.NumGoroutine()
if count > threshold {
return fmt.Errorf("too many goroutines (%d > %d)", count, threshold)
}
return nil
}
-44
View File
@@ -1,44 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"testing"
"time"
)
func TestGoroutineCountCheck(t *testing.T) {
tests := []struct {
threshold int
wantErr bool
}{
{5000, false},
{5, true},
{6, true},
}
for _, tt := range tests {
// Make goroutines -- to make sure number of go-routines is higher than threshold
if tt.threshold == 5 || tt.threshold == 6 {
for i := 0; i < 6; i++ {
go time.Sleep(5 * time.Nanosecond)
}
}
if err := goroutineCountCheck(tt.threshold); (err != nil) != tt.wantErr {
t.Errorf("goroutineCountCheck() error = %v, wantErr %v", err, tt.wantErr)
}
}
}
+90 -35
View File
@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"sync"
@@ -365,46 +366,41 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
doneCh := make(chan struct{})
defer close(doneCh)
// Migrating IAM needs a retry mechanism for
// Migrating IAM amd Loading IAM needs a retry mechanism for
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
// Migrate IAM configuration
if err := sys.doIAMConfigMigration(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for IAM subsystem to be initialized..")
continue
retryCh := newRetryTimerSimple(doneCh)
for {
select {
case <-retryCh:
// Migrate IAM configuration
if err := sys.doIAMConfigMigration(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for IAM subsystem to be initialized..")
continue
}
return err
}
return err
}
break
}
sys.store.watch(sys)
sys.store.watch(sys)
// Initializing IAM needs a retry mechanism for
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
// Load IAMSys once during boot. Need to pass in
// objAPI as server has not yet initialized.
if err := sys.store.loadAll(sys, objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for IAM subsystem to be initialized..")
continue
if err := sys.store.loadAll(sys, objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for IAM subsystem to be initialized..")
continue
}
return err
}
return err
return nil
case <-globalOSSignalCh:
return fmt.Errorf("Initializing IAM sub-system gracefully stopped")
}
break
}
return nil
}
// DeletePolicy - deletes a canned policy from backend or etcd.
@@ -433,6 +429,38 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
}
delete(sys.iamPolicyDocsMap, policyName)
// Delete user-policy mappings that will no longer apply
var usersToDel []string
var isUserSTS []bool
for u, mp := range sys.iamUserPolicyMap {
if mp.Policy == policyName {
usersToDel = append(usersToDel, u)
cr, ok := sys.iamUsersMap[u]
if !ok {
// This case cannot happen
return errNoSuchUser
}
// User is from STS if the creds are temporary
isSTS := cr.IsTemp()
isUserSTS = append(isUserSTS, isSTS)
}
}
for i, u := range usersToDel {
sys.policyDBSet(u, "", isUserSTS[i], false)
}
// Delete group-policy mappings that will no longer apply
var groupsToDel []string
for g, mp := range sys.iamGroupPolicyMap {
if mp.Policy == policyName {
groupsToDel = append(groupsToDel, g)
}
}
for _, g := range groupsToDel {
sys.policyDBSet(g, "", false, true)
}
return err
}
@@ -509,6 +537,19 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
return errServerNotInitialized
}
// First we remove the user from their groups.
userInfo, getErr := sys.GetUserInfo(accessKey)
if getErr != nil {
return getErr
}
for _, group := range userInfo.MemberOf {
removeErr := sys.RemoveUsersFromGroup(group, []string{accessKey})
if removeErr != nil {
return removeErr
}
}
// Next we can remove the user from memory and IAM store
sys.Lock()
defer sys.Unlock()
@@ -1069,17 +1110,17 @@ func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error {
return sys.policyDBSet(name, policy, false, isGroup)
}
// policyDBSet - sets a policy for user in the policy db. Assumes that
// caller has sys.Lock().
// policyDBSet - sets a policy for user in the policy db. Assumes that caller
// has sys.Lock(). If policy == "", then policy mapping is removed.
func (sys *IAMSys) policyDBSet(name, policy string, isSTS, isGroup bool) error {
if sys.store == nil {
return errServerNotInitialized
}
if name == "" || policy == "" {
if name == "" {
return errInvalidArgument
}
if _, ok := sys.iamPolicyDocsMap[policy]; !ok {
if _, ok := sys.iamPolicyDocsMap[policy]; !ok && policy != "" {
return errNoSuchPolicy
}
@@ -1095,6 +1136,20 @@ func (sys *IAMSys) policyDBSet(name, policy string, isSTS, isGroup bool) error {
}
}
// Handle policy mapping removal
if policy == "" {
if err := sys.store.deleteMappedPolicy(name, isSTS, isGroup); err != nil {
return err
}
if !isGroup {
delete(sys.iamUserPolicyMap, name)
} else {
delete(sys.iamGroupPolicyMap, name)
}
return nil
}
// Handle policy mapping set/update
mp := newMappedPolicy(policy)
if err := sys.store.saveMappedPolicy(name, isSTS, isGroup, mp); err != nil {
return err
+35 -5
View File
@@ -17,6 +17,7 @@
package cmd
import (
"context"
"errors"
"math/rand"
"net/http"
@@ -29,7 +30,7 @@ import (
const (
// Lock maintenance interval.
lockMaintenanceInterval = 30 * time.Second
lockMaintenanceInterval = 1 * time.Minute
// Lock validity check interval.
lockValidityCheckInterval = 2 * time.Minute
@@ -179,6 +180,8 @@ func getLongLivedLocks(interval time.Duration) map[Endpoint][]nameLockRequesterI
return nlripMap
}
var lockMaintenanceTimeout = newDynamicTimeout(60*time.Second, time.Second)
// lockMaintenance loops over locks that have been active for some time and checks back
// with the original server whether it is still alive or not
//
@@ -187,7 +190,14 @@ func getLongLivedLocks(interval time.Duration) map[Endpoint][]nameLockRequesterI
// - some network error (and server is up normally)
//
// We will ignore the error, and we will retry later to get a resolve on this lock
func lockMaintenance(interval time.Duration) {
func lockMaintenance(ctx context.Context, interval time.Duration, objAPI ObjectLayer) error {
// Lock to avoid concurrent lock maintenance loops
maintenanceLock := objAPI.NewNSLock(ctx, "system", "lock-maintenance-ops")
if err := maintenanceLock.GetLock(lockMaintenanceTimeout); err != nil {
return err
}
defer maintenanceLock.Unlock()
// Validate if long lived locks are indeed clean.
// Get list of long lived locks to check for staleness.
for lendpoint, nlrips := range getLongLivedLocks(interval) {
@@ -203,7 +213,8 @@ func lockMaintenance(interval time.Duration) {
continue
}
// Call back to original server verify whether the lock is still active (based on name & uid)
// Call back to original server verify whether the lock is
// still active (based on name & uid)
expired, err := c.Expired(dsync.LockArgs{
UID: nlrip.lri.UID,
Resource: nlrip.name,
@@ -230,15 +241,31 @@ func lockMaintenance(interval time.Duration) {
}
}
}
return nil
}
// Start lock maintenance from all lock servers.
func startLockMaintenance() {
var objAPI ObjectLayer
var ctx = context.Background()
// Wait until the object API is ready
for {
objAPI = newObjectLayerWithoutSafeModeFn()
if objAPI == nil {
time.Sleep(time.Second)
continue
}
break
}
// Initialize a new ticker with a minute between each ticks.
ticker := time.NewTicker(lockMaintenanceInterval)
// Stop the timer upon service closure and cleanup the go-routine.
defer ticker.Stop()
r := rand.New(rand.NewSource(UTCNow().UnixNano()))
for {
// Verifies every minute for locks held more than 2 minutes.
select {
@@ -247,10 +274,13 @@ func startLockMaintenance() {
case <-ticker.C:
// Start with random sleep time, so as to avoid
// "synchronous checks" between servers
r := rand.New(rand.NewSource(UTCNow().UnixNano()))
duration := time.Duration(r.Float64() * float64(lockMaintenanceInterval))
time.Sleep(duration)
lockMaintenance(lockValidityCheckInterval)
if err := lockMaintenance(ctx, lockValidityCheckInterval, objAPI); err != nil {
// Sleep right after an error.
duration := time.Duration(r.Float64() * float64(lockMaintenanceInterval))
time.Sleep(duration)
}
}
}
}
+1 -1
View File
@@ -23,9 +23,9 @@ import (
"strings"
"time"
c "github.com/minio/mc/pkg/console"
"github.com/minio/minio/cmd/logger/message/log"
"github.com/minio/minio/pkg/color"
c "github.com/minio/minio/pkg/console"
)
// Logger interface describes the methods that need to be implemented to satisfy the interface requirements.
+5 -6
View File
@@ -1,5 +1,5 @@
/*
* MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc.
* MinIO Cloud Storage, (C) 2015-2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import (
"sort"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/trie"
"github.com/minio/minio/pkg/words"
)
@@ -74,8 +74,8 @@ FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
VERSION:
` + Version +
`{{ "\n"}}`
{{.Version}}
`
func newApp(name string) *cli.App {
// Collection of minio commands currently supported are.
@@ -126,11 +126,10 @@ func newApp(name string) *cli.App {
app := cli.NewApp()
app.Name = name
app.Author = "MinIO, Inc."
app.Version = Version
app.Version = ReleaseTag
app.Usage = "High Performance Object Storage"
app.Description = `Build high performance data infrastructure for machine learning, analytics and application data workloads with MinIO`
app.Flags = GlobalFlags
app.HideVersion = true // Hide `--version` flag, we already have `minio version`.
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
app.Commands = commands
app.CustomAppHelpTemplate = minioHelpTemplate
+7
View File
@@ -91,6 +91,13 @@ func (d *naughtyDisk) DiskInfo() (info DiskInfo, err error) {
return d.disk.DiskInfo()
}
func (d *naughtyDisk) MakeVolBulk(volumes ...string) (err error) {
if err := d.calcError(); err != nil {
return err
}
return d.disk.MakeVolBulk(volumes...)
}
func (d *naughtyDisk) MakeVol(volume string) (err error) {
if err := d.calcError(); err != nil {
return err
+11 -29
View File
@@ -30,8 +30,6 @@ import (
"time"
"github.com/klauspost/compress/zip"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/config/notify"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/event"
@@ -589,20 +587,6 @@ func (sys *NotificationSys) PutBucketNotification(ctx context.Context, bucketNam
}()
}
// AddNotificationTargetsFromConfig - adds notification targets from server config.
func (sys *NotificationSys) AddNotificationTargetsFromConfig(cfg config.Config) error {
targetList, err := notify.GetNotificationTargets(cfg, GlobalServiceDoneCh, NewCustomHTTPTransport())
if err != nil {
return err
}
for _, target := range targetList.Targets() {
if err = sys.targetList.Add(target); err != nil {
return err
}
}
return nil
}
// AddRemoteTarget - adds event rules map, HTTP/PeerRPC client target to bucket name.
func (sys *NotificationSys) AddRemoteTarget(bucketName string, target event.Target, rulesMap event.RulesMap) error {
if err := sys.targetList.Add(target); err != nil {
@@ -718,6 +702,14 @@ func (sys *NotificationSys) Init(buckets []BucketInfo, objAPI ObjectLayer) error
return nil
}
if globalConfigTargetList != nil {
for _, target := range globalConfigTargetList.Targets() {
if err := sys.targetList.Add(target); err != nil {
return err
}
}
}
doneCh := make(chan struct{})
defer close(doneCh)
@@ -726,8 +718,7 @@ func (sys *NotificationSys) Init(buckets []BucketInfo, objAPI ObjectLayer) error
// - Read quorum is lost just after the initialization
// of the object layer.
retryTimerCh := newRetryTimerSimple(doneCh)
stop := false
for !stop {
for {
select {
case <-retryTimerCh:
if err := sys.load(buckets, objAPI); err != nil {
@@ -739,17 +730,8 @@ func (sys *NotificationSys) Init(buckets []BucketInfo, objAPI ObjectLayer) error
}
return err
}
stop = true
case <-globalOSSignalCh:
return fmt.Errorf("Initializing Notification sub-system gracefully stopped")
}
}
// Initializing bucket retention config needs a retry mechanism if
// read quorum is lost just after the initialization of the object layer.
for {
select {
case <-retryTimerCh:
// Initializing bucket retention config needs a retry mechanism if
// read quorum is lost just after the initialization of the object layer.
if err := sys.initBucketObjectLockConfig(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
+3
View File
@@ -122,4 +122,7 @@ type ObjectLayer interface {
// Backend related metrics
GetMetrics(ctx context.Context) (*Metrics, error)
// Check Readiness
IsReady(ctx context.Context) bool
}
+6 -2
View File
@@ -640,8 +640,11 @@ func isRemoteCallRequired(ctx context.Context, bucket string, objAPI ObjectLayer
if globalDNSConfig == nil {
return false
}
_, err := objAPI.GetBucketInfo(ctx, bucket)
return err == toObjectErr(errVolumeNotFound, bucket)
if globalBucketFederation {
_, err := objAPI.GetBucketInfo(ctx, bucket)
return err == toObjectErr(errVolumeNotFound, bucket)
}
return false
}
// CopyObjectHandler - Copy Object
@@ -2421,6 +2424,7 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(err), r.URL, guessIsBrowserReq(r))
return
}
if globalDNSConfig != nil {
_, err := globalDNSConfig.Get(bucket)
if err != nil {
+1
View File
@@ -82,6 +82,7 @@ const (
peerRESTTraceAll = "all"
peerRESTTraceErr = "err"
peerRESTListenBucket = "bucket"
peerRESTListenPrefix = "prefix"
peerRESTListenSuffix = "suffix"
peerRESTListenEvents = "events"
+3
View File
@@ -978,6 +978,9 @@ func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
if !ok {
return false
}
if ev.S3.Bucket.Name != values.Get(peerRESTListenBucket) {
return false
}
objectName, uerr := url.QueryUnescape(ev.S3.Object.Key)
if uerr != nil {
objectName = ev.S3.Object.Key
+7
View File
@@ -74,6 +74,13 @@ func (p *posixDiskIDCheck) DiskInfo() (info DiskInfo, err error) {
return p.storage.DiskInfo()
}
func (p *posixDiskIDCheck) MakeVolBulk(volumes ...string) (err error) {
if p.isDiskStale() {
return errDiskNotFound
}
return p.storage.MakeVolBulk(volumes...)
}
func (p *posixDiskIDCheck) MakeVol(volume string) (err error) {
if p.isDiskStale() {
return errDiskNotFound
+9
View File
@@ -551,6 +551,15 @@ func (s *posix) SetDiskID(id string) {
// storage rest server for remote disks.
}
func (s *posix) MakeVolBulk(volumes ...string) (err error) {
for _, volume := range volumes {
if err = s.MakeVol(volume); err != nil {
return err
}
}
return nil
}
// Make a volume entry.
func (s *posix) MakeVol(volume string) (err error) {
defer func() {
+8 -6
View File
@@ -223,12 +223,13 @@ func connectLoadInitFormats(retryCount int, firstDisk bool, endpoints Endpoints,
// Assign globalDeploymentID on first run for the
// minio server managing the first disk
globalDeploymentID = format.ID
} else {
// The first will always recreate some directories inside .minio.sys
// such as, tmp, multipart and background-ops
if firstDisk {
initFormatXLMetaVolume(storageDisks, formatConfigs)
}
return format, nil
}
// The first will always recreate some directories inside .minio.sys
// such as, tmp, multipart and background-ops
if firstDisk {
initFormatXLMetaVolume(storageDisks, formatConfigs)
}
// Return error when quorum unformatted disks - indicating we are
@@ -288,6 +289,7 @@ func connectLoadInitFormats(retryCount int, firstDisk bool, endpoints Endpoints,
if err = formatXLFixLocalDeploymentID(endpoints, storageDisks, format); err != nil {
return nil, err
}
return format, nil
}
-4
View File
@@ -227,10 +227,6 @@ func initAllSubsystems(buckets []BucketInfo, newObject ObjectLayer) (err error)
return fmt.Errorf("Unable to initialize config system: %w", err)
}
if err = globalNotificationSys.AddNotificationTargetsFromConfig(globalServerConfig); err != nil {
return fmt.Errorf("Unable to initialize notification target(s) from config: %w", err)
}
if globalEtcdClient != nil {
// **** WARNING ****
// Migrating to encrypted backend on etcd should happen before initialization of
+1
View File
@@ -36,6 +36,7 @@ type StorageAPI interface {
// Volume operations.
MakeVol(volume string) (err error)
MakeVolBulk(volumes ...string) (err error)
ListVols() (vols []VolInfo, err error)
StatVol(volume string) (vol VolInfo, err error)
DeleteVol(volume string) (err error)
+10
View File
@@ -28,6 +28,7 @@ import (
"net/url"
"path"
"strconv"
"strings"
"sync/atomic"
"github.com/minio/minio/cmd/http"
@@ -189,6 +190,15 @@ func (client *storageRESTClient) DiskInfo() (info DiskInfo, err error) {
return info, err
}
// MakeVolBulk - create multiple volumes in a bulk operation.
func (client *storageRESTClient) MakeVolBulk(volumes ...string) (err error) {
values := make(url.Values)
values.Set(storageRESTVolumes, strings.Join(volumes, ","))
respBody, err := client.call(storageRESTMethodMakeVolBulk, values, nil, -1)
defer http.DrainBody(respBody)
return err
}
// MakeVol - create a volume on a remote disk.
func (client *storageRESTClient) MakeVol(volume string) (err error) {
values := make(url.Values)
+3 -1
View File
@@ -17,7 +17,7 @@
package cmd
const (
storageRESTVersion = "v11"
storageRESTVersion = "v12"
storageRESTVersionPrefix = SlashSeparator + storageRESTVersion
storageRESTPrefix = minioReservedBucketPath + "/storage"
)
@@ -26,6 +26,7 @@ const (
storageRESTMethodDiskInfo = "/diskinfo"
storageRESTMethodCrawlAndGetDataUsage = "/crawlandgetdatausage"
storageRESTMethodMakeVol = "/makevol"
storageRESTMethodMakeVolBulk = "/makevolbulk"
storageRESTMethodStatVol = "/statvol"
storageRESTMethodDeleteVol = "/deletevol"
storageRESTMethodListVols = "/listvols"
@@ -47,6 +48,7 @@ const (
const (
storageRESTVolume = "volume"
storageRESTVolumes = "volumes"
storageRESTDirPath = "dir-path"
storageRESTFilePath = "file-path"
storageRESTSrcVolume = "source-volume"
+26 -1
View File
@@ -24,8 +24,10 @@ import (
"fmt"
"io"
"net/http"
"os/user"
"path"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
@@ -150,6 +152,19 @@ func (s *storageRESTServer) MakeVolHandler(w http.ResponseWriter, r *http.Reques
}
}
// MakeVolBulkHandler - create multiple volumes as a bulk operation.
func (s *storageRESTServer) MakeVolBulkHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
return
}
vars := mux.Vars(r)
volumes := strings.Split(vars[storageRESTVolumes], ",")
err := s.storage.MakeVolBulk(volumes...)
if err != nil {
s.writeErrorResponse(w, err)
}
}
// ListVolsHandler - list volumes.
func (s *storageRESTServer) ListVolsHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
@@ -594,7 +609,16 @@ func registerStorageRESTHandlers(router *mux.Router, endpointZones EndpointZones
}
storage, err := newPosix(endpoint.Path)
if err != nil {
logger.Fatal(config.ErrUnableToWriteInBackend(err),
// Show a descriptive error with a hint about how to fix it.
var username string
if u, err := user.Current(); err == nil {
username = u.Username
} else {
username = "<your-username>"
}
hint := fmt.Sprintf("Run the following command to add the convenient permissions: `sudo chown %s %s && sudo chmod u+rxw %s`",
username, endpoint.Path, endpoint.Path)
logger.Fatal(config.ErrUnableToWriteInBackend(err).Hint(hint),
"Unable to initialize posix backend")
}
@@ -605,6 +629,7 @@ func registerStorageRESTHandlers(router *mux.Router, endpointZones EndpointZones
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodDiskInfo).HandlerFunc(httpTraceHdrs(server.DiskInfoHandler))
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodCrawlAndGetDataUsage).HandlerFunc(httpTraceHdrs(server.CrawlAndGetDataUsageHandler))
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodMakeVol).HandlerFunc(httpTraceHdrs(server.MakeVolHandler)).Queries(restQueries(storageRESTVolume)...)
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodMakeVolBulk).HandlerFunc(httpTraceHdrs(server.MakeVolBulkHandler)).Queries(restQueries(storageRESTVolumes)...)
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodStatVol).HandlerFunc(httpTraceHdrs(server.StatVolHandler)).Queries(restQueries(storageRESTVolume)...)
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodDeleteVol).HandlerFunc(httpTraceHdrs(server.DeleteVolHandler)).Queries(restQueries(storageRESTVolume)...)
subrouter.Methods(http.MethodPost).Path(storageRESTVersionPrefix + storageRESTMethodListVols).HandlerFunc(httpTraceHdrs(server.ListVolsHandler))
+2 -4
View File
@@ -258,10 +258,6 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
return toJSONError(ctx, err, args.BucketName)
}
globalNotificationSys.RemoveNotification(args.BucketName)
globalPolicySys.Remove(args.BucketName)
globalNotificationSys.DeleteBucket(ctx, args.BucketName)
if globalDNSConfig != nil {
if err := globalDNSConfig.Delete(args.BucketName); err != nil {
// Deleting DNS entry failed, attempt to create the bucket again.
@@ -270,6 +266,8 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
}
}
globalNotificationSys.DeleteBucket(ctx, args.BucketName)
return nil
}
+27 -6
View File
@@ -1491,12 +1491,6 @@ func (s *xlSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.HealRe
}
}
// Initialize meta volume, if volume already exists ignores it, all disks which
// are not found are ignored as well.
if err = initFormatXLMetaVolume(storageDisks, tmpNewFormats); err != nil {
return madmin.HealResultItem{}, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %w", err)
}
// Save formats `format.json` across all disks.
if err = saveFormatXLAll(ctx, storageDisks, tmpNewFormats); err != nil {
return madmin.HealResultItem{}, err
@@ -1662,3 +1656,30 @@ func (s *xlSets) GetMetrics(ctx context.Context) (*Metrics, error) {
logger.LogIf(ctx, NotImplemented{})
return &Metrics{}, NotImplemented{}
}
// IsReady - Returns true if atleast n/2 disks (read quorum) are online
func (s *xlSets) IsReady(_ context.Context) bool {
s.xlDisksMu.RLock()
defer s.xlDisksMu.RUnlock()
var activeDisks int
for i := 0; i < s.setCount; i++ {
for j := 0; j < s.drivesPerSet; j++ {
if s.xlDisks[i][j] == nil {
continue
}
if !s.xlLockers[i][j].IsOnline() {
continue
}
if s.xlDisks[i][j].IsOnline() {
activeDisks++
}
// Return true if read quorum is available.
if activeDisks >= len(s.endpoints)/2 {
return true
}
}
}
// Disks are not ready
return false
}
+6
View File
@@ -245,3 +245,9 @@ func (xl xlObjects) crawlAndGetDataUsage(ctx context.Context, endCh <-chan struc
return dataUsageInfo
}
// IsReady - No Op.
func (xl xlObjects) IsReady(ctx context.Context) bool {
logger.CriticalIf(ctx, NotImplemented{})
return true
}
+13
View File
@@ -1359,3 +1359,16 @@ func (z *xlZones) GetMetrics(ctx context.Context) (*Metrics, error) {
logger.LogIf(ctx, NotImplemented{})
return &Metrics{}, NotImplemented{}
}
// IsReady - Returns True if all the zones have enough quorum to accept requests.
func (z *xlZones) IsReady(ctx context.Context) bool {
if z.SingleZone() {
return z.zones[0].IsReady(ctx)
}
for _, xlsets := range z.zones {
if !xlsets.IsReady(ctx) {
return false
}
}
return true
}
+352 -65
View File
@@ -38,6 +38,8 @@ notify_elasticsearch publish bucket notifications to Elasticsearch endpoints
notify_redis publish bucket notifications to Redis datastores
```
> NOTE: '*' at the end of arg means its mandatory, '*' at the end of the values, means its the default value for the arg.
<a name="AMQP"></a>
## Publish MinIO events via AMQP
@@ -48,22 +50,47 @@ Install RabbitMQ from [here](https://www.rabbitmq.com/).
The AMQP configuration is located under the sub-system `notify_amqp` top-level key. Create a configuration key-value pair here for your AMQP instance. The key is a name for your AMQP endpoint, and the value is a collection of key-value parameters described in the table below.
| Parameter | Description |
| :------------- | :------------------------------------------------------------------------------- |
| `state` | (Required) Is this server endpoint configuration active/enabled? |
| `url` | (Required) AMQP server endpoint, e.g. `amqp://myuser:mypassword@localhost:5672` |
| `exchange` | Name of the exchange. |
| `routing_key` | Routing key for publishing. |
| `exchange_type` | Kind of exchange. |
| `delivery_mode` | Delivery mode for publishing. 0 or 1 - transient; 2 - persistent. |
| `mandatory` | Publishing related bool. |
| `immediate` | Publishing related bool. |
| `durable` | Exchange declaration related bool. |
| `internal` | Exchange declaration related bool. |
| `no_wait` | Exchange declaration related bool. |
| `auto_deleted` | Exchange declaration related bool. |
| `queue_dir` | Persistent store for events when AMQP broker is offline |
| `queue_limit` | Set the maximum event limit for the persistent store. The default limit is 10000 |
```
KEY:
notify_amqp[:name] publish bucket notifications to AMQP endpoints
ARGS:
url* (url) AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`
exchange (string) name of the AMQP exchange
exchange_type (string) AMQP exchange type
routing_key (string) routing key for publishing
mandatory (on|off) quietly ignore undelivered messages when set to 'off', default is 'on'
durable (on|off) persist queue across broker restarts when set to 'on', default is 'off'
no_wait (on|off) non-blocking message delivery when set to 'on', default is 'off'
internal (on|off) set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges
auto_deleted (on|off) auto delete queue when set to 'on', when there are no consumers
delivery_mode (number) set to '1' for non-persistent or '2' for persistent queue
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
Or environment variables
```
KEY:
notify_amqp[:name] publish bucket notifications to AMQP endpoints
ARGS:
MINIO_NOTIFY_AMQP_URL* (url) AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`
MINIO_NOTIFY_AMQP_EXCHANGE (string) name of the AMQP exchange
MINIO_NOTIFY_AMQP_EXCHANGE_TYPE (string) AMQP exchange type
MINIO_NOTIFY_AMQP_ROUTING_KEY (string) routing key for publishing
MINIO_NOTIFY_AMQP_MANDATORY (on|off) quietly ignore undelivered messages when set to 'off', default is 'on'
MINIO_NOTIFY_AMQP_DURABLE (on|off) persist queue across broker restarts when set to 'on', default is 'off'
MINIO_NOTIFY_AMQP_NO_WAIT (on|off) non-blocking message delivery when set to 'on', default is 'off'
MINIO_NOTIFY_AMQP_INTERNAL (on|off) set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges
MINIO_NOTIFY_AMQP_AUTO_DELETED (on|off) auto delete queue when set to 'on', when there are no consumers
MINIO_NOTIFY_AMQP_DELIVERY_MODE (number) set to '1' for non-persistent or '2' for persistent queue
MINIO_NOTIFY_AMQP_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_AMQP_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_AMQP_COMMENT (sentence) optionally add a comment to this setting
```
MinIO supports persistent event store. The persistent store will backup events when the AMQP broker goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queue_dir` field and the maximum limit of events in the queue_dir in `queue_limit` field. For eg, the `queue_dir` can be `/home/events` and `queue_limit` can be `1000`. By default, the `queue_limit` is set to 10000.
@@ -159,17 +186,41 @@ Install an MQTT Broker from [here](https://mosquitto.org/).
The MQTT configuration is located as `notify_mqtt` key. Create a configuration key-value pair here for your MQTT instance. The key is a name for your MQTT endpoint, and the value is a collection of key-value parameters described in the table below.
| Parameter | Description |
| :----------- | :------------------------------------------------------------------------------- |
| `state` | (Required) Is this server endpoint configuration active/enabled? |
| `broker` | (Required) MQTT server endpoint, e.g. `tcp://localhost:1883` |
| `topic` | (Required) Name of the MQTT topic to publish on, e.g. `minio` |
| `qos` | Set the Quality of Service Level |
| `username` | Username to connect to the MQTT server (if required) |
| `password` | Password to connect to the MQTT server (if required) |
| `queue_dir` | Persistent store for events when MQTT broker is offline |
| `queue_limit` | Set the maximum event limit for the persistent store. The default limit is 10000 |
```
KEY:
notify_mqtt[:name] publish bucket notifications to MQTT endpoints
ARGS:
broker* (uri) MQTT server endpoint e.g. `tcp://localhost:1883`
topic* (string) name of the MQTT topic to publish
username (string) MQTT username
password (string) MQTT password
qos (number) set the quality of service priority, defaults to '0'
keep_alive_interval (duration) keep-alive interval for MQTT connections in s,m,h,d
reconnect_interval (duration) reconnect interval for MQTT connections in s,m,h,d
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_mqtt[:name] publish bucket notifications to MQTT endpoints
ARGS:
MINIO_NOTIFY_MQTT_BROKER* (uri) MQTT server endpoint e.g. `tcp://localhost:1883`
MINIO_NOTIFY_MQTT_TOPIC* (string) name of the MQTT topic to publish
MINIO_NOTIFY_MQTT_USERNAME (string) MQTT username
MINIO_NOTIFY_MQTT_PASSWORD (string) MQTT password
MINIO_NOTIFY_MQTT_QOS (number) set the quality of service priority, defaults to '0'
MINIO_NOTIFY_MQTT_KEEP_ALIVE_INTERVAL (duration) keep-alive interval for MQTT connections in s,m,h,d
MINIO_NOTIFY_MQTT_RECONNECT_INTERVAL (duration) reconnect interval for MQTT connections in s,m,h,d
MINIO_NOTIFY_MQTT_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_MQTT_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_MQTT_COMMENT (sentence) optionally add a comment to this setting
```
MinIO supports persistent event store. The persistent store will backup events when the MQTT broker goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queue_dir` field and the maximum limit of events in the queue_dir in `queue_limit` field. For eg, the `queue_dir` can be `/home/events` and `queue_limit` can be `1000`. By default, the `queue_limit` is set to 10000.
@@ -271,14 +322,33 @@ MinIO requires a 5.x series version of Elasticsearch. This is the latest major r
The Elasticsearch configuration is located in the `notify_elasticsearch` key. Create a configuration key-value pair here for your Elasticsearch instance. The key is a name for your Elasticsearch endpoint, and the value is a collection of key-value parameters described in the table below.
| Parameter | Description |
| :----------- | :------------------------------------------------------------------------------- |
| `state` | (Required) Is this server endpoint configuration active/enabled? |
| `format` | (Required) Either `namespace` or `access`. |
| `url` | (Required) The Elasticsearch server's address, with optional authentication info. |
| `index` | (Required) The name of an Elasticsearch index in which MinIO will store documents. |
| `queue_dir` | Persistent store for events when Elasticsearch broker is offline |
| `queue_limit` | Set the maximum event limit for the persistent store. The default limit is 10000 |
```
KEY:
notify_elasticsearch[:name] publish bucket notifications to Elasticsearch endpoints
ARGS:
url* (url) Elasticsearch server's address, with optional authentication info
index* (string) Elasticsearch index to store/update events, index is auto-created
format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_elasticsearch[:name] publish bucket notifications to Elasticsearch endpoints
ARGS:
MINIO_NOTIFY_ELASTICSEARCH_URL* (url) Elasticsearch server's address, with optional authentication info
MINIO_NOTIFY_ELASTICSEARCH_INDEX* (string) Elasticsearch index to store/update events, index is auto-created
MINIO_NOTIFY_ELASTICSEARCH_FORMAT* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
MINIO_NOTIFY_ELASTICSEARCH_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_ELASTICSEARCH_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_ELASTICSEARCH_COMMENT (sentence) optionally add a comment to this setting
```
For example: `http://localhost:9200` or with authentication info `http://elastic:MagicWord@127.0.0.1:9200`.
@@ -416,13 +486,35 @@ The steps below show how to use this notification target in `namespace` and `acc
The MinIO server configuration file is stored on the backend in json format.The Redis configuration is located in the `redis` key under the `notify` top-level key. Create a configuration key-value pair here for your Redis instance. The key is a name for your Redis endpoint, and the value is a collection of key-value parameters described in the table below.
| Parameter | Description |
| :--------- | :------------- |
| `state` | (Required) Is this server endpoint configuration active/enabled? |
| `format` | (Required) Either `namespace` or `access` |
| `address` | (Required) The Redis server's address. For example: `localhost:6379` |
| `password` | (Optional) The Redis server's password |
| `key` | (Required) The name of the redis key under which events are stored. A hash is used in case of `namespace` format and a list in case of `access` format. |
```
KEY:
notify_redis[:name] publish bucket notifications to Redis datastores
ARGS:
address* (address) Redis server's address. For example: `localhost:6379`
key* (string) Redis key to store/update events, key is auto-created
format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
password (string) Redis server password
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_redis[:name] publish bucket notifications to Redis datastores
ARGS:
MINIO_NOTIFY_REDIS_ADDRESS* (address) Redis server's address. For example: `localhost:6379`
MINIO_NOTIFY_REDIS_KEY* (string) Redis key to store/update events, key is auto-created
MINIO_NOTIFY_REDIS_FORMAT* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
MINIO_NOTIFY_REDIS_PASSWORD (string) Redis server password
MINIO_NOTIFY_REDIS_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_REDIS_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_REDIS_COMMENT (sentence) optionally add a comment to this setting
```
MinIO supports persistent event store. The persistent store will backup events when the Redis broker goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queue_dir` field and the maximum limit of events in the queue_dir in `queue_limit` field. For eg, the `queue_dir` can be `/home/events` and `queue_limit` can be `1000`. By default, the `queue_limit` is set to 10000.
@@ -495,6 +587,57 @@ Install NATS from [here](http://nats.io/).
MinIO supports persistent event store. The persistent store will backup events when the NATS broker goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queue_dir` field and the maximum limit of events in the queue_dir in `queue_limit` field. For eg, the `queue_dir` can be `/home/events` and `queue_limit` can be `1000`. By default, the `queue_limit` is set to 10000.
```
KEY:
notify_nats[:name] publish bucket notifications to NATS endpoints
ARGS:
address* (address) NATS server address e.g. '0.0.0.0:4222'
subject* (string) NATS subscription subject
username (string) NATS username
password (string) NATS password
token (string) NATS token
tls (on|off) set to 'on' to enable TLS
tls_skip_verify (on|off) trust server TLS without verification, defaults to "on" (verify)
ping_interval (duration) client ping commands interval in s,m,h,d. Disabled by default
streaming (on|off) set to 'on', to use streaming NATS server
streaming_async (on|off) set to 'on', to enable asynchronous publish
streaming_max_pub_acks_in_flight (number) number of messages to publish without waiting for ACKs
streaming_cluster_id (string) unique ID for NATS streaming cluster
cert_authority (string) path to certificate chain of the target NATS server
client_cert (string) client cert for NATS mTLS auth
client_key (string) client cert key for NATS mTLS auth
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_nats[:name] publish bucket notifications to NATS endpoints
ARGS:
MINIO_NOTIFY_NATS_ADDRESS* (address) NATS server address e.g. '0.0.0.0:4222'
MINIO_NOTIFY_NATS_SUBJECT* (string) NATS subscription subject
MINIO_NOTIFY_NATS_USERNAME (string) NATS username
MINIO_NOTIFY_NATS_PASSWORD (string) NATS password
MINIO_NOTIFY_NATS_TOKEN (string) NATS token
MINIO_NOTIFY_NATS_TLS (on|off) set to 'on' to enable TLS
MINIO_NOTIFY_NATS_TLS_SKIP_VERIFY (on|off) trust server TLS without verification, defaults to "on" (verify)
MINIO_NOTIFY_NATS_PING_INTERVAL (duration) client ping commands interval in s,m,h,d. Disabled by default
MINIO_NOTIFY_NATS_STREAMING (on|off) set to 'on', to use streaming NATS server
MINIO_NOTIFY_NATS_STREAMING_ASYNC (on|off) set to 'on', to enable asynchronous publish
MINIO_NOTIFY_NATS_STREAMING_MAX_PUB_ACKS_IN_FLIGHT (number) number of messages to publish without waiting for ACKs
MINIO_NOTIFY_NATS_STREAMING_CLUSTER_ID (string) unique ID for NATS streaming cluster
MINIO_NOTIFY_NATS_CERT_AUTHORITY (string) path to certificate chain of the target NATS server
MINIO_NOTIFY_NATS_CLIENT_CERT (string) client cert for NATS mTLS auth
MINIO_NOTIFY_NATS_CLIENT_KEY (string) client cert key for NATS mTLS auth
MINIO_NOTIFY_NATS_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_NATS_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_NATS_COMMENT (sentence) optionally add a comment to this setting
```
To update the configuration, use `mc admin config get` command to get the current configuration file for the minio deployment.
```sh
@@ -675,18 +818,42 @@ MinIO requires PostgreSQL version 9.5 or above. MinIO uses the [`INSERT ON CONFL
The PostgreSQL configuration is located in the `notify_postgresql` key. Create a configuration key-value pair here for your PostgreSQL instance. The key is a name for your PostgreSQL endpoint, and the value is a collection of key-value parameters described in the table below.
| Parameter | Description |
| :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | (Required) Is this server endpoint configuration active/enabled? |
| `format` | (Required) Either `namespace` or `access`. |
| `connection_string` | (Optional) [Connection string parameters](https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters) for the PostgreSQL server. Can be used to set `sslmode` for example. |
| `table` | (Required) Table name in which events will be stored/updated. If the table does not exist, the MinIO server creates it at start-up. |
| `host` | (Optional) Host name of the PostgreSQL server. Defaults to `localhost`. IPv6 host should be enclosed with `[` and `]` |
| `port` | (Optional) Port on which to connect to PostgreSQL server. Defaults to `5432`. |
| `user` | (Optional) Database user name. Defaults to user running the server process. |
| `password` | (Optional) Database password. |
| `database` | (Optional) Database name. |
| | |
```
KEY:
notify_postgres[:name] publish bucket notifications to Postgres databases
ARGS:
connection_string* (string) Postgres server connection-string
table* (string) DB table name to store/update events, table is auto-created
format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
host (hostname) Postgres server hostname (used only if `connection_string` is empty)
port (port) Postgres server port, defaults to `5432` (used only if `connection_string` is empty)
username (string) database username (used only if `connection_string` is empty)
password (string) database password (used only if `connection_string` is empty)
database (string) database name (used only if `connection_string` is empty)
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_postgres[:name] publish bucket notifications to Postgres databases
ARGS:
MINIO_NOTIFY_POSTGRES_CONNECTION_STRING* (string) Postgres server connection-string
MINIO_NOTIFY_POSTGRES_TABLE* (string) DB table name to store/update events, table is auto-created
MINIO_NOTIFY_POSTGRES_FORMAT* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
MINIO_NOTIFY_POSTGRES_HOST (hostname) Postgres server hostname (used only if `connection_string` is empty)
MINIO_NOTIFY_POSTGRES_PORT (port) Postgres server port, defaults to `5432` (used only if `connection_string` is empty)
MINIO_NOTIFY_POSTGRES_USERNAME (string) database username (used only if `connection_string` is empty)
MINIO_NOTIFY_POSTGRES_PASSWORD (string) database password (used only if `connection_string` is empty)
MINIO_NOTIFY_POSTGRES_DATABASE (string) database name (used only if `connection_string` is empty)
MINIO_NOTIFY_POSTGRES_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_POSTGRES_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_POSTGRES_COMMENT (sentence) optionally add a comment to this setting
```
MinIO supports persistent event store. The persistent store will backup events when the PostgreSQL connection goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queue_dir` field and the maximum limit of events in the queue_dir in `queue_limit` field. For eg, the `queue_dir` can be `/home/events` and `queue_limit` can be `1000`. By default, the `queue_limit` is set to 10000.
@@ -767,16 +934,42 @@ MinIO requires MySQL version 5.7.8 or above. MinIO uses the [JSON](https://dev.m
The MySQL configuration is located in the `notify_mysql` key. Create a configuration key-value pair here for your MySQL instance. The key is a name for your MySQL endpoint, and the value is a collection of key-value parameters described in the table below.
| Parameter | Description |
| :---------- | :------------- |
| `format` | (Required) Either `namespace` or `access`. |
| `dsn_string` | (Optional) [Data-Source-Name connection string](https://github.com/go-sql-driver/mysql#dsn-data-source-name) for the MySQL server. |
| `table` | (Required) Table name in which events will be stored/updated. If the table does not exist, the MinIO server creates it at start-up. |
| `host` | Host name of the MySQL server (used only if `dsnString` is empty). |
| `port` | Port on which to connect to the MySQL server (used only if `dsn_string` is empty). |
| `user` | Database user-name (used only if `dsnString` is empty). |
| `password` | Database password (used only if `dsnString` is empty). |
| `database` | Database name (used only if `dsnString` is empty). |
```
KEY:
notify_mysql[:name] publish bucket notifications to MySQL databases
ARGS:
dsn_string* (string) MySQL data-source-name connection string
table* (string) DB table name to store/update events, table is auto-created
format* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
host (hostname) MySQL server hostname (used only if `dsn_string` is empty)
port (port) MySQL server port (used only if `dsn_string` is empty)
username (string) database username (used only if `dsn_string` is empty)
password (string) database password (used only if `dsn_string` is empty)
database (string) database name (used only if `dsn_string` is empty)
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_mysql[:name] publish bucket notifications to MySQL databases
ARGS:
MINIO_NOTIFY_MYSQL_DSN_STRING* (string) MySQL data-source-name connection string
MINIO_NOTIFY_MYSQL_TABLE* (string) DB table name to store/update events, table is auto-created
MINIO_NOTIFY_MYSQL_FORMAT* (namespace*|access) 'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'
MINIO_NOTIFY_MYSQL_HOST (hostname) MySQL server hostname (used only if `dsn_string` is empty)
MINIO_NOTIFY_MYSQL_PORT (port) MySQL server port (used only if `dsn_string` is empty)
MINIO_NOTIFY_MYSQL_USERNAME (string) database username (used only if `dsn_string` is empty)
MINIO_NOTIFY_MYSQL_PASSWORD (string) database password (used only if `dsn_string` is empty)
MINIO_NOTIFY_MYSQL_DATABASE (string) database name (used only if `dsn_string` is empty)
MINIO_NOTIFY_MYSQL_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_MYSQL_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_MYSQL_COMMENT (sentence) optionally add a comment to this setting
```
`dns_string` is optional, if not specified, the connection information specified by the `host`, `port`, `user`, `password` and `database` parameters are used.
@@ -851,6 +1044,47 @@ MinIO requires Kafka version 0.10 or 0.9. Internally MinIO uses the [Shopify/sar
MinIO supports persistent event store. The persistent store will backup events when the kafka broker goes offline and replays it when the broker comes back online. The event store can be configured by setting the directory path in `queue_dir` field and the maximum limit of events in the queue_dir in `queue_limit` field. For eg, the `queue_dir` can be `/home/events` and `queue_limit` can be `1000`. By default, the `queue_limit` is set to 10000.
```
KEY:
notify_kafka[:name] publish bucket notifications to Kafka endpoints
ARGS:
brokers* (csv) comma separated list of Kafka broker addresses
topic (string) Kafka topic used for bucket notifications
sasl_username (string) username for SASL/PLAIN or SASL/SCRAM authentication
sasl_password (string) password for SASL/PLAIN or SASL/SCRAM authentication
tls_client_auth (string) clientAuth determines the Kafka server's policy for TLS client auth
sasl (on|off) set to 'on' to enable SASL authentication
tls (on|off) set to 'on' to enable TLS
tls_skip_verify (on|off) trust server TLS without verification, defaults to "on" (verify)
client_tls_cert (path) path to client certificate for mTLS auth
client_tls_key (path) path to client key for mTLS auth
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_kafka[:name] publish bucket notifications to Kafka endpoints
ARGS:
MINIO_NOTIFY_KAFKA_BROKERS* (csv) comma separated list of Kafka broker addresses
MINIO_NOTIFY_KAFKA_TOPIC (string) Kafka topic used for bucket notifications
MINIO_NOTIFY_KAFKA_SASL_USERNAME (string) username for SASL/PLAIN or SASL/SCRAM authentication
MINIO_NOTIFY_KAFKA_SASL_PASSWORD (string) password for SASL/PLAIN or SASL/SCRAM authentication
MINIO_NOTIFY_KAFKA_TLS_CLIENT_AUTH (string) clientAuth determines the Kafka server's policy for TLS client auth
MINIO_NOTIFY_KAFKA_SASL (on|off) set to 'on' to enable SASL authentication
MINIO_NOTIFY_KAFKA_TLS (on|off) set to 'on' to enable TLS
MINIO_NOTIFY_KAFKA_TLS_SKIP_VERIFY (on|off) trust server TLS without verification, defaults to "on" (verify)
MINIO_NOTIFY_KAFKA_CLIENT_TLS_CERT (path) path to client certificate for mTLS auth
MINIO_NOTIFY_KAFKA_CLIENT_TLS_KEY (path) path to client key for mTLS auth
MINIO_NOTIFY_KAFKA_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_KAFKA_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_KAFKA_COMMENT (sentence) optionally add a comment to this setting
```
To update the configuration, use `mc admin config get` command to get the current configuration.
```sh
@@ -960,9 +1194,33 @@ MinIO supports persistent event store. The persistent store will backup events w
To update the configuration, use `mc admin config get` command to get the current configuration.
```
KEY:
notify_webhook[:name] publish bucket notifications to webhook endpoints
ARGS:
endpoint* (url) webhook server endpoint e.g. http://localhost:8080/minio/events
auth_token (string) opaque string or JWT authorization token
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_webhook[:name] publish bucket notifications to webhook endpoints
ARGS:
MINIO_NOTIFY_WEBHOOK_ENDPOINT* (url) webhook server endpoint e.g. http://localhost:8080/minio/events
MINIO_NOTIFY_WEBHOOK_AUTH_TOKEN (string) opaque string or JWT authorization token
MINIO_NOTIFY_WEBHOOK_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_WEBHOOK_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_WEBHOOK_COMMENT (sentence) optionally add a comment to this setting
```
```sh
$ mc admin config get myminio/ notify_webhook
notify_webhook:1 queue_limit="0" endpoint="" queue_dir=""
$ mc admin config get myminio/ notify_webhook notify_webhook:1 queue_limit="0" endpoint="" queue_dir=""
```
Use `mc admin config set` command to update the configuration for the deployment. Here the endpoint is the server listening for webhook notifications. Save the settings and restart the MinIO server for changes to take effect. Note that the endpoint needs to be live and reachable when you restart your MinIO server.
@@ -1039,6 +1297,35 @@ MinIO supports persistent event store. The persistent store will backup events w
To update the configuration, use `mc admin config get` command to get the current configuration for `notify_nsq`.
```
KEY:
notify_nsq[:name] publish bucket notifications to NSQ endpoints
ARGS:
nsqd_address* (address) NSQ server address e.g. '127.0.0.1:4150'
topic* (string) NSQ topic
tls (on|off) set to 'on' to enable TLS
tls_skip_verify (on|off) trust server TLS without verification, defaults to "on" (verify)
queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '10000'
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
notify_nsq[:name] publish bucket notifications to NSQ endpoints
ARGS:
MINIO_NOTIFY_NSQ_NSQD_ADDRESS* (address) NSQ server address e.g. '127.0.0.1:4150'
MINIO_NOTIFY_NSQ_TOPIC* (string) NSQ topic
MINIO_NOTIFY_NSQ_TLS (on|off) set to 'on' to enable TLS
MINIO_NOTIFY_NSQ_TLS_SKIP_VERIFY (on|off) trust server TLS without verification, defaults to "on" (verify)
MINIO_NOTIFY_NSQ_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_NSQ_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '10000'
MINIO_NOTIFY_NSQ_COMMENT (sentence) optionally add a comment to this setting
```
```sh
$ mc admin config get myminio/ notify_nsq
notify_nsq:1 nsqd_address="" queue_dir="" queue_limit="0" tls_enable="off" tls_skip_verify="off" topic=""
+111 -29
View File
@@ -59,9 +59,24 @@ Once the migration is complete, server will automatically unset the `MINIO_ACCES
> **NOTE: Make sure to remove `MINIO_ACCESS_KEY_OLD` and `MINIO_SECRET_KEY_OLD` in scripts or service files before next service restarts of the server to avoid double encryption of your existing contents.**
#### Region
| Field | Type | Description |
|:--------------------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ``region name=my_region`` | _string_ | `region` describes the physical location of the server. By default it is blank. You may override this field with `MINIO_REGION_NAME` environment variable. If you are unsure leave it unset. |
```
KEY:
region label the location of the server
ARGS:
name (string) name of the location of the server e.g. "us-west-rack2"
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
region label the location of the server
ARGS:
MINIO_REGION_NAME (string) name of the location of the server e.g. "us-west-rack2"
MINIO_REGION_COMMENT (sentence) optionally add a comment to this setting
```
Example:
@@ -71,37 +86,104 @@ minio server /data
```
### Storage Class
| Field | Type | Description |
|:-------------------------------|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ``storage_class`` | | Set storage class for configurable data and parity, as per object basis. |
| ``storage_class standard=EC:4`` | _string_ | Value for standard storage class. It should be in the format `EC:Parity`, for example to set 4 disk parity for standard storage class objects, set this field to `EC:4`. |
| ``storage_class rrs=EC:2`` | _string_ | Value for reduced redundancy storage class. It should be in the format `EC:Parity`, for example to set 3 disk parity for reduced redundancy storage class objects, set this field to `EC:3`. |
By default, parity for objects with standard storage class is set to `N/2`, and parity for objects with reduced redundancy storage class objects is set to `2`. Read more about storage class support in MinIO server [here](https://github.com/minio/minio/blob/master/docs/erasure/storage-class/README.md).
```
KEY:
storage_class define object level redundancy
ARGS:
standard (string) set the parity count for default standard storage class e.g. "EC:4"
rrs (string) set the parity count for reduced redundancy storage class e.g. "EC:2"
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
storage_class define object level redundancy
ARGS:
MINIO_STORAGE_CLASS_STANDARD (string) set the parity count for default standard storage class e.g. "EC:4"
MINIO_STORAGE_CLASS_RRS (string) set the parity count for reduced redundancy storage class e.g. "EC:2"
MINIO_STORAGE_CLASS_COMMENT (sentence) optionally add a comment to this setting
```
### Cache
| Field | Type | Description |
|:-------------------------------------------------------------|:-----------|:------------------------------------------------------------------------------------------------------------|
| ``cache drives="/mnt/drive1,/mnt/drive2,/mnt/cache{1...3}"`` | _[]string_ | List of mounted file system drives with [`atime`](http://kerolasa.github.io/filetimes.html) support enabled |
| ``cache exclude="*.pdf,mybucket/*"`` | _[]string_ | List of wildcard patterns for prefixes to exclude from cache |
| ``cache expiry=90`` | _int_ | Days to cache expiry |
| ``cache quota=70`` | _int_ | Percentage of disk available to cache |
| | | |
MinIO provides caching storage tier for primarily gateway deployments, allowing you to cache content for faster reads, cost savings on repeated downloads from the cloud.
#### Notify
```
KEY:
cache add caching storage tier
| Field | Type | Description |
|:-------------------------|:-----|:--------------------------------------------------------------------------------------------------------------------------------------|
| ``notify_amqp`` | | [Configure to publish MinIO events via AMQP target.](https://docs.min.io/docs/minio-bucket-notification-guide#AMQP) |
| ``notify_nats`` | | [Configure to publish MinIO events via NATS target.](https://docs.min.io/docs/minio-bucket-notification-guide#NATS) |
| ``notify_elasticsearch`` | | [Configure to publish MinIO events via Elasticsearch target.](https://docs.min.io/docs/minio-bucket-notification-guide#Elasticsearch) |
| ``notify_redis`` | | [Configure to publish MinIO events via Redis target.](https://docs.min.io/docs/minio-bucket-notification-guide#Redis) |
| ``notify_postgresql`` | | [Configure to publish MinIO events via PostgreSQL target.](https://docs.min.io/docs/minio-bucket-notification-guide#PostgreSQL) |
| ``notify_kafka`` | | [Configure to publish MinIO events via Apache Kafka target.](https://docs.min.io/docs/minio-bucket-notification-guide#apache-kafka) |
| ``notify_webhook`` | | [Configure to publish MinIO events via Webhooks target.](https://docs.min.io/docs/minio-bucket-notification-guide#webhooks) |
| ``notify_mysql`` | | [Configure to publish MinIO events via MySql target.](https://docs.min.io/docs/minio-bucket-notification-guide#MySQL) |
| ``notify_mqtt`` | | [Configure to publish MinIO events via MQTT target.](https://docs.min.io/docs/minio-bucket-notification-guide#MQTT) |
ARGS:
drives* (csv) comma separated mountpoints e.g. "/optane1,/optane2"
expiry (number) cache expiry duration in days e.g. "90"
quota (number) limit cache drive usage in percentage e.g. "90"
exclude (csv) comma separated wildcard exclusion patterns e.g. "bucket/*.tmp,*.exe"
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
cache add caching storage tier
ARGS:
MINIO_CACHE_DRIVES* (csv) comma separated mountpoints e.g. "/optane1,/optane2"
MINIO_CACHE_EXPIRY (number) cache expiry duration in days e.g. "90"
MINIO_CACHE_QUOTA (number) limit cache drive usage in percentage e.g. "90"
MINIO_CACHE_EXCLUDE (csv) comma separated wildcard exclusion patterns e.g. "bucket/*.tmp,*.exe"
MINIO_CACHE_COMMENT (sentence) optionally add a comment to this setting
```
#### Etcd
MinIO supports storing encrypted IAM assets and bucket DNS records on etcd.
> NOTE: if *path_prefix* is set then MinIO will not federate your buckets, namespaced IAM assets are assumed as isolated tenants, only buckets are considered globally unique but performing a lookup with a *bucket* which belongs to a different tenant will fail unlike federated setups where MinIO would port-forward and route the request to relevant cluster accordingly. This is a special feature, federated deployments should not need to set *path_prefix*.
```
KEY:
etcd federate multiple clusters for IAM and Bucket DNS
ARGS:
endpoints* (csv) comma separated list of etcd endpoints e.g. "http://localhost:2379"
path_prefix (path) namespace prefix to isolate tenants e.g. "customer1/"
coredns_path (path) shared bucket DNS records, default is "/skydns"
client_cert (path) client cert for mTLS authentication
client_cert_key (path) client cert key for mTLS authentication
comment (sentence) optionally add a comment to this setting
```
or environment variables
```
KEY:
etcd federate multiple clusters for IAM and Bucket DNS
ARGS:
MINIO_ETCD_ENDPOINTS* (csv) comma separated list of etcd endpoints e.g. "http://localhost:2379"
MINIO_ETCD_PATH_PREFIX (path) namespace prefix to isolate tenants e.g. "customer1/"
MINIO_ETCD_COREDNS_PATH (path) shared bucket DNS records, default is "/skydns"
MINIO_ETCD_CLIENT_CERT (path) client cert for mTLS authentication
MINIO_ETCD_CLIENT_CERT_KEY (path) client cert key for mTLS authentication
MINIO_ETCD_COMMENT (sentence) optionally add a comment to this setting
```
#### Notifications
Notification targets supported by MinIO are in the following list. To configure individual targets please refer to more detailed documentation [here](https://docs.min.io/docs/minio-bucket-notification-guide.html)
```
notify_webhook publish bucket notifications to webhook endpoints
notify_amqp publish bucket notifications to AMQP endpoints
notify_kafka publish bucket notifications to Kafka endpoints
notify_mqtt publish bucket notifications to MQTT endpoints
notify_nats publish bucket notifications to NATS endpoints
notify_nsq publish bucket notifications to NSQ endpoints
notify_mysql publish bucket notifications to MySQL databases
notify_postgres publish bucket notifications to Postgres databases
notify_elasticsearch publish bucket notifications to Elasticsearch endpoints
notify_redis publish bucket notifications to Redis datastores
```
### Accessing configuration file
All configuration changes can be made using [`mc admin config` get/set commands](https://github.com/minio/mc/blob/master/docs/minio-admin-complete-guide.md). Following sections provide brief explanation of fields and how to customize them. A complete example of `config.json` is available [here](https://raw.githubusercontent.com/minio/minio/master/docs/config/config.sample.json)
+40 -99
View File
@@ -1,14 +1,15 @@
# Distributed Server Design Guide [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)
This document explains the design approach and advanced use cases of the MinIO distributed server.
This document explains the design, architecture and advanced use cases of the MinIO distributed server.
## Command-line
```
NAME:
minio server - start object storage server.
minio server - start object storage server
USAGE:
minio server [FLAGS] DIR1 [DIR2..]
minio server [FLAGS] DIR{1...64}
minio server [FLAGS] DIR{1...64} DIR{65...128}
DIR:
DIR points to a directory on a filesystem. When you want to combine
@@ -48,46 +49,30 @@ Expansion of ellipses and choice of erasure sets based on this expansion is an a
- In this algorithm, we also make sure that we spread the disks out evenly. MinIO server expands ellipses passed as arguments. Here is a sample expansion to demonstrate the process.
```
minio server http://host{1...4}/export{1...8}
minio server http://host{1...2}/export{1...8}
```
Expected expansion
```
> http://host1/export1
> http://host2/export1
> http://host3/export1
> http://host4/export1
> http://host1/export2
> http://host2/export2
> http://host3/export2
> http://host4/export2
> http://host1/export3
> http://host2/export3
> http://host3/export3
> http://host4/export3
> http://host1/export4
> http://host2/export4
> http://host3/export4
> http://host4/export4
> http://host1/export5
> http://host2/export5
> http://host3/export5
> http://host4/export5
> http://host1/export6
> http://host2/export6
> http://host3/export6
> http://host4/export6
> http://host1/export7
> http://host2/export7
> http://host3/export7
> http://host4/export7
> http://host1/export8
> http://host2/export8
> http://host3/export8
> http://host4/export8
```
A noticeable trait of this expansion is that it chooses unique hosts such that the erasure code is efficient across drives and hosts.
*A noticeable trait of this expansion is that it chooses unique hosts such the setup provides maximum protection and availability.*
- Choosing an erasure set for the object is decided during `PutObject()`, object names are used to find the right erasure set using the following pseudo code.
```go
@@ -103,6 +88,40 @@ Input for the key is the object name specified in `PutObject()`, returns a uniqu
- MinIO does erasure coding at the object level not at the volume level, unlike other object storage vendors. This allows applications to choose different storage class by setting `x-amz-storage-class=STANDARD/REDUCED_REDUNDANCY` for each object uploads so effectively utilizing the capacity of the cluster. Additionally these can also be enforced using IAM policies to make sure the client uploads with correct HTTP headers.
- MinIO also supports expansion of existing clusters in zones. Each zone is a self contained entity with same SLA's (read/write quorum) for each object as original cluster. By using the existing namespace for lookup validation MinIO ensures conflicting objects are not created. When no such object exists then MinIO simply uses the least used zone.
*There are no limits on how many zones can be combined*
```
minio server http://host{1...32}/export{1...32} http://host{5...6}/export{1...8}
```
In above example there are two zones
- 32 * 32 = 1024 drives zone1
- 2 * 8 = 16 drives zone2
> Notice the requirement of common SLA here original cluster had 1024 drives with 16 drives per erasure set, second zone is expected to have a minimum of 16 drives to match the original cluster SLA or it should be in multiples of 16.
Following pseudo code returns the correct least used zone index to upload an object.
```go
func getAvailableZoneIdx(ctx context.Context) int {
zones := z.getZonesAvailableSpace(ctx)
total := zones.TotalAvailable()
// choose when we reach this many
choose := rand.Uint64() % total
atTotal := uint64(0)
for _, zone := range zones {
atTotal += zone.Available
if atTotal > choose && zone.Available > 0 {
return zone.Index
}
}
// Should not happen, but print values just in case.
panic(fmt.Errorf("reached end of zones (total: %v, atTotal: %v, choose: %v)", total, atTotal, choose))
}
```
## Other usages
### Advanced use cases with multiple ellipses
@@ -114,7 +133,7 @@ minio server /mnt/controller{1...4}/data{1...16}
Standalone erasure coded configuration with 16 sets, 16 disks per set, across mounts and controllers.
```
minio server /mnt{1..4}/controller{1...4}/data{1...16}
minio server /mnt{1...4}/controller{1...4}/data{1...16}
```
Distributed erasure coded configuration with 2 sets, 16 disks per set across hosts.
@@ -126,81 +145,3 @@ Distributed erasure coded configuration with rack level redundancy 32 sets in to
```
minio server http://rack{1...4}-host{1...8}.example.net/export{1...16}
```
## Backend `format.json` changes
`format.json` has new fields
- `disk` is changed to `this`
- `jbod` is changed to `sets` , along with this change sets is also a two dimensional list representing total sets and disks per set.
A sample `format.json` looks like below
```json
{
"version": "1",
"format": "xl",
"xl": {
"version": "2",
"this": "4ec63786-3dbd-4a9e-96f5-535f6e850fb1",
"sets": [
[
"4ec63786-3dbd-4a9e-96f5-535f6e850fb1",
"1f3cf889-bc90-44ca-be2a-732b53be6c9d",
"4b23eede-1846-482c-b96f-bfb647f058d3",
"e1f17302-a850-419d-8cdb-a9f884a63c92"
], [
"2ca4c5c1-dccb-4198-a840-309fea3b5449",
"6d1e666e-a22c-4db4-a038-2545c2ccb6d5",
"d4fa35ab-710f-4423-a7c2-e1ca33124df0",
"88c65e8b-00cb-4037-a801-2549119c9a33"
]
],
"distributionAlgo": "CRCMOD"
}
}
```
New `format-xl.go` behavior is format structure is used as a opaque type, `Format` field signifies the format of the backend. Once the format has been identified it is now the job of the identified backend to further interpret the next structures and validate them.
```go
type formatType string
const (
formatFS formatType = "fs"
formatXL = "xl"
)
type format struct {
Version string
Format BackendFormat
}
```
### Current format
```go
type formatXLV1 struct{
format
XL struct{
Version string
Disk string
JBOD []string
}
}
```
### New format
```go
type formatXLV2 struct {
Version string `json:"version"`
Format string `json:"format"`
XL struct {
Version string `json:"version"`
This string `json:"this"`
Sets [][]string `json:"sets"`
DistributionAlgo string `json:"distributionAlgo"`
} `json:"xl"`
}
```
@@ -5,7 +5,7 @@ version: '3.7'
# 9001 through 9004.
services:
minio1:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
volumes:
- data1-1:/data1
- data1-2:/data2
@@ -22,7 +22,7 @@ services:
retries: 3
minio2:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
volumes:
- data2-1:/data1
- data2-2:/data2
@@ -39,7 +39,7 @@ services:
retries: 3
minio3:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
volumes:
- data3-1:/data1
- data3-2:/data2
@@ -56,7 +56,7 @@ services:
retries: 3
minio4:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
volumes:
- data4-1:/data1
- data4-2:/data2
@@ -2,7 +2,7 @@ version: '3.7'
services:
minio1:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio1
volumes:
- minio1-data:/export
@@ -29,7 +29,7 @@ services:
retries: 3
minio2:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio2
volumes:
- minio2-data:/export
@@ -56,7 +56,7 @@ services:
retries: 3
minio3:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio3
volumes:
- minio3-data:/export
@@ -83,7 +83,7 @@ services:
retries: 3
minio4:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio4
volumes:
- minio4-data:/export
@@ -2,7 +2,7 @@ version: '3.7'
services:
minio1:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio1
volumes:
- minio1-data:/export
@@ -33,7 +33,7 @@ services:
retries: 3
minio2:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio2
volumes:
- minio2-data:/export
@@ -64,7 +64,7 @@ services:
retries: 3
minio3:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio3
volumes:
- minio3-data:/export
@@ -95,7 +95,7 @@ services:
retries: 3
minio4:
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
hostname: minio4
volumes:
- minio4-data:/export
@@ -30,7 +30,7 @@ spec:
value: "minio"
- name: MINIO_SECRET_KEY
value: "minio123"
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
# Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future.
args:
- server
@@ -5,6 +5,7 @@ metadata:
labels:
app: minio
spec:
publishNotReadyAddresses: true
clusterIP: None
ports:
- port: 9000
@@ -5,6 +5,7 @@ metadata:
name: minio
spec:
serviceName: minio
podManagementPolicy: Parallel
replicas: 4
selector:
matchLabels:
@@ -21,7 +22,7 @@ spec:
value: "minio"
- name: MINIO_SECRET_KEY
value: "minio123"
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
args:
- server
- http://minio-{0...3}.minio.default.svc.cluster.local/data
@@ -41,6 +42,15 @@ spec:
port: 9000
initialDelaySeconds: 120
periodSeconds: 20
# Readiness probe detects situations where MinIO server instance
# is not ready to accept connections. Kubernetes automatically
# stops all the traffic to the pods if readiness checks fail.
readinessProbe:
httpGet:
path: /minio/health/ready
port: 9000
initialDelaySeconds: 120
periodSeconds: 20
# These are converted to volume claims by the controller
# and mounted at the paths mentioned above.
volumeClaimTemplates:
@@ -24,7 +24,7 @@ spec:
containers:
- name: minio
# Pulls the default Minio image from Docker Hub
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
args:
- gateway
- gcs
@@ -32,7 +32,7 @@ spec:
- name: data
mountPath: "/data"
# Pulls the lastest Minio image from Docker Hub
image: minio/minio:RELEASE.2019-12-17T23-16-33Z
image: minio/minio:RELEASE.2019-12-30T05-45-39Z
args:
- server
- /data
+50 -17
View File
@@ -4,55 +4,82 @@ go 1.13
require (
cloud.google.com/go v0.39.0
contrib.go.opencensus.io/exporter/ocagent v0.5.0 // indirect
github.com/Azure/azure-pipeline-go v0.2.1
github.com/Azure/azure-storage-blob-go v0.8.0
github.com/Azure/go-autorest v11.7.1+incompatible // indirect
github.com/Shopify/sarama v1.24.1
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/alecthomas/participle v0.2.1
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5
github.com/aws/aws-sdk-go v1.20.21
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2
github.com/beevik/ntp v0.2.0
github.com/cheggaaa/pb v1.0.28
github.com/coredns/coredns v1.4.0
github.com/coreos/bbolt v1.3.3 // indirect
github.com/coreos/etcd v3.3.12+incompatible
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/djherbis/atime v1.0.0
github.com/dustin/go-humanize v1.0.0
github.com/eapache/go-resiliency v1.2.0 // indirect
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/fatih/color v1.7.0
github.com/fatih/structs v1.1.0
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-sql-driver/mysql v1.4.1
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
github.com/gomodule/redigo v2.0.0+incompatible
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f // indirect
github.com/gorilla/handlers v1.4.0
github.com/gorilla/mux v1.7.0
github.com/gorilla/rpc v1.2.0+incompatible
github.com/gorilla/websocket v1.4.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect
github.com/hashicorp/go-hclog v0.9.2 // indirect
github.com/hashicorp/raft v1.1.1-0.20190703171940-f639636d18e0 // indirect
github.com/hashicorp/vault/api v1.0.4
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/json-iterator/go v1.1.7
github.com/klauspost/compress v1.8.3
github.com/klauspost/compress v1.9.4
github.com/klauspost/cpuid v1.2.1 // indirect
github.com/klauspost/pgzip v1.2.1
github.com/klauspost/readahead v1.3.1
github.com/klauspost/reedsolomon v1.9.3
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3
github.com/lib/pq v1.1.1
github.com/mattn/go-colorable v0.1.1
github.com/mattn/go-ieproxy v0.0.0-20190805055040-f9202b1cfdeb // indirect
github.com/mattn/go-isatty v0.0.7
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/miekg/dns v1.1.8
github.com/minio/cli v1.22.0
github.com/minio/gokrb5/v7 v7.2.5
github.com/minio/hdfs/v3 v3.0.1
github.com/minio/highwayhash v1.0.0
github.com/minio/lsync v1.0.1
github.com/minio/mc v0.0.0-20191012041914-735aa139b19c
github.com/minio/minio-go v0.0.0-20190327203652-5325257a208f
github.com/minio/minio-go/v6 v6.0.39
github.com/minio/parquet-go v0.0.0-20190318185229-9d767baf1679
github.com/minio/minio-go/v6 v6.0.44
github.com/minio/parquet-go v0.0.0-20191231003236-20b3c07bcd2c
github.com/minio/sha256-simd v0.1.1
github.com/minio/sio v0.2.0
github.com/mitchellh/go-homedir v1.1.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/nats-io/gnatsd v1.4.1 // indirect
github.com/nats-io/go-nats v1.7.2 // indirect
github.com/nats-io/go-nats-streaming v0.4.4 // indirect
github.com/nats-io/nats-server v1.4.1 // indirect
github.com/nats-io/nats-server/v2 v2.1.2
github.com/nats-io/nats-streaming-server v0.14.2 // indirect
github.com/nats-io/nats.go v1.9.1
github.com/nats-io/stan.go v0.4.5
github.com/ncw/directio v1.0.5
@@ -60,18 +87,36 @@ require (
github.com/pkg/errors v0.8.1
github.com/pkg/profile v1.3.0
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/rcrowley/go-metrics v0.0.0-20190704165056-9c2d0518ed81 // indirect
github.com/rjeczalik/notify v0.9.2
github.com/rs/cors v1.6.0
github.com/satori/go.uuid v1.2.0 // indirect
github.com/secure-io/sio-go v0.3.0
github.com/shirou/gopsutil v2.18.12+incompatible
github.com/sirupsen/logrus v1.4.2
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
github.com/ugorji/go v1.1.5-pre // indirect
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.etcd.io/bbolt v1.3.3 // indirect
go.uber.org/atomic v1.3.2
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.10.0 // indirect
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69
golang.org/x/text v0.3.2 // indirect
google.golang.org/api v0.5.0
google.golang.org/appengine v1.6.0 // indirect
google.golang.org/genproto v0.0.0-20190513181449-d00d292a067c // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/ini.v1 v1.48.0 // indirect
gopkg.in/ldap.v3 v3.0.3
gopkg.in/olivere/elastic.v5 v5.0.80
gopkg.in/yaml.v2 v2.2.4
@@ -79,15 +124,3 @@ require (
// Added for go1.13 migration https://github.com/golang/go/issues/32805
replace github.com/gorilla/rpc v1.2.0+incompatible => github.com/gorilla/rpc v1.2.0
// Allow this for offline builds
replace github.com/eapache/go-xerial-snappy => github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21
replace github.com/eapache/queue => github.com/eapache/queue v1.1.0
replace github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.4
replace github.com/mitchellh/mapstructure => github.com/mitchellh/mapstructure v1.1.2
// Version 1.2.0 adds support for go modules
replace github.com/hashicorp/vault => github.com/hashicorp/vault v1.2.0-beta2
+10 -582
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -15,6 +15,6 @@
# limitations under the License.
#
AWS_CLI_VERSION="1.11.177"
AWS_CLI_VERSION="1.16.309"
python -m pip install awscli==$AWS_CLI_VERSION
+2 -2
View File
@@ -19,7 +19,7 @@ export APT="apt --quiet --yes"
export WGET="wget --quiet --no-check-certificate"
# install nodejs source list
if ! $WGET --output-document=- https://deb.nodesource.com/setup_6.x | bash -; then
if ! $WGET --output-document=- https://deb.nodesource.com/setup_13.x | bash -; then
echo "unable to set nodejs repository"
exit 1
fi
@@ -32,7 +32,7 @@ rm -f packages-microsoft-prod.deb
$APT update
# download and install golang
GO_VERSION="1.13"
GO_VERSION="1.13.5"
GO_INSTALL_PATH="/usr/local"
download_url="https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz"
if ! $WGET --output-document=- "$download_url" | tar -C "${GO_INSTALL_PATH}" -zxf -; then
+435
View File
@@ -0,0 +1,435 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package console implements console printing helpers
package console
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"github.com/fatih/color"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
var (
// DebugPrint enables/disables console debug printing.
DebugPrint = false
// Used by the caller to print multiple lines atomically. Exposed by Lock/Unlock methods.
publicMutex = &sync.Mutex{}
// Used internally by console.
privateMutex = &sync.Mutex{}
stderrColoredOutput = colorable.NewColorableStderr()
// Print prints a message.
Print = func(data ...interface{}) {
consolePrint("Print", Theme["Print"], data...)
}
// PrintC prints a message with color.
PrintC = func(data ...interface{}) {
consolePrint("PrintC", Theme["PrintC"], data...)
}
// Printf prints a formatted message.
Printf = func(format string, data ...interface{}) {
consolePrintf("Print", Theme["Print"], format, data...)
}
// Println prints a message with a newline.
Println = func(data ...interface{}) {
consolePrintln("Print", Theme["Print"], data...)
}
// Fatal print a error message and exit.
Fatal = func(data ...interface{}) {
consolePrint("Fatal", Theme["Fatal"], data...)
os.Exit(1)
}
// Fatalf print a error message with a format specified and exit.
Fatalf = func(format string, data ...interface{}) {
consolePrintf("Fatal", Theme["Fatal"], format, data...)
os.Exit(1)
}
// Fatalln print a error message with a new line and exit.
Fatalln = func(data ...interface{}) {
consolePrintln("Fatal", Theme["Fatal"], data...)
os.Exit(1)
}
// Error prints a error message.
Error = func(data ...interface{}) {
consolePrint("Error", Theme["Error"], data...)
}
// Errorf print a error message with a format specified.
Errorf = func(format string, data ...interface{}) {
consolePrintf("Error", Theme["Error"], format, data...)
}
// Errorln prints a error message with a new line.
Errorln = func(data ...interface{}) {
consolePrintln("Error", Theme["Error"], data...)
}
// Info prints a informational message.
Info = func(data ...interface{}) {
consolePrint("Info", Theme["Info"], data...)
}
// Infof prints a informational message in custom format.
Infof = func(format string, data ...interface{}) {
consolePrintf("Info", Theme["Info"], format, data...)
}
// Infoln prints a informational message with a new line.
Infoln = func(data ...interface{}) {
consolePrintln("Info", Theme["Info"], data...)
}
// Debug prints a debug message without a new line
// Debug prints a debug message.
Debug = func(data ...interface{}) {
if DebugPrint {
consolePrint("Debug", Theme["Debug"], data...)
}
}
// Debugf prints a debug message with a new line.
Debugf = func(format string, data ...interface{}) {
if DebugPrint {
consolePrintf("Debug", Theme["Debug"], format, data...)
}
}
// Debugln prints a debug message with a new line.
Debugln = func(data ...interface{}) {
if DebugPrint {
consolePrintln("Debug", Theme["Debug"], data...)
}
}
// Colorize prints message in a colorized form, dictated by the corresponding tag argument.
Colorize = func(tag string, data interface{}) string {
if isatty.IsTerminal(os.Stdout.Fd()) {
colorized, ok := Theme[tag]
if ok {
return colorized.SprintFunc()(data)
} // else: No theme found. Return as string.
}
return fmt.Sprint(data)
}
// Eraseline Print in new line and adjust to top so that we don't print over the ongoing progress bar.
Eraseline = func() {
consolePrintf("Print", Theme["Print"], "%c[2K\n", 27)
consolePrintf("Print", Theme["Print"], "%c[A", 27)
}
)
// wrap around standard fmt functions.
// consolePrint prints a message prefixed with message type and program name.
func consolePrint(tag string, c *color.Color, a ...interface{}) {
privateMutex.Lock()
defer privateMutex.Unlock()
switch tag {
case "Debug":
// if no arguments are given do not invoke debug printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": <DEBUG> ")
c.Print(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": <DEBUG> ")
fmt.Fprint(color.Output, a...)
}
color.Output = output
case "Fatal":
fallthrough
case "Error":
// if no arguments are given do not invoke fatal and error printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": <ERROR> ")
c.Print(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": <ERROR> ")
fmt.Fprint(color.Output, a...)
}
color.Output = output
case "Info":
// if no arguments are given do not invoke info printer.
if len(a) == 0 {
return
}
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(ProgramName() + ": ")
c.Print(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprint(color.Output, a...)
}
default:
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(a...)
} else {
fmt.Fprint(color.Output, a...)
}
}
}
// consolePrintf - same as print with a new line.
func consolePrintf(tag string, c *color.Color, format string, a ...interface{}) {
privateMutex.Lock()
defer privateMutex.Unlock()
switch tag {
case "Debug":
// if no arguments are given do not invoke debug printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": <DEBUG> ")
c.Printf(format, a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": <DEBUG> ")
fmt.Fprintf(color.Output, format, a...)
}
color.Output = output
case "Fatal":
fallthrough
case "Error":
// if no arguments are given do not invoke fatal and error printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": <ERROR> ")
c.Printf(format, a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": <ERROR> ")
fmt.Fprintf(color.Output, format, a...)
}
color.Output = output
case "Info":
// if no arguments are given do not invoke info printer.
if len(a) == 0 {
return
}
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(ProgramName() + ": ")
c.Printf(format, a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintf(color.Output, format, a...)
}
default:
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Printf(format, a...)
} else {
fmt.Fprintf(color.Output, format, a...)
}
}
}
// consolePrintln - same as print with a new line.
func consolePrintln(tag string, c *color.Color, a ...interface{}) {
privateMutex.Lock()
defer privateMutex.Unlock()
switch tag {
case "Debug":
// if no arguments are given do not invoke debug printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": <DEBUG> ")
c.Println(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": <DEBUG> ")
fmt.Fprintln(color.Output, a...)
}
color.Output = output
case "Fatal":
fallthrough
case "Error":
// if no arguments are given do not invoke fatal and error printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": <ERROR> ")
c.Println(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": <ERROR> ")
fmt.Fprintln(color.Output, a...)
}
color.Output = output
case "Info":
// if no arguments are given do not invoke info printer.
if len(a) == 0 {
return
}
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(ProgramName() + ": ")
c.Println(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintln(color.Output, a...)
}
default:
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Println(a...)
} else {
fmt.Fprintln(color.Output, a...)
}
}
}
// Lock console.
func Lock() {
publicMutex.Lock()
}
// Unlock locked console.
func Unlock() {
publicMutex.Unlock()
}
// ProgramName - return the name of the executable program.
func ProgramName() string {
_, progName := filepath.Split(os.Args[0])
return progName
}
// Table - data to print in table format with fixed row widths.
type Table struct {
// per-row colors
RowColors []*color.Color
// per-column align-right flag (aligns left by default)
AlignRight []bool
// Left margin width for table
TableIndentWidth int
}
// NewTable - create a new Table instance. Takes per-row colors and
// per-column right-align flags and table indentation width (i.e. left
// margin width)
func NewTable(rowColors []*color.Color, alignRight []bool, indentWidth int) *Table {
return &Table{rowColors, alignRight, indentWidth}
}
// DisplayTable - prints the table
func (t *Table) DisplayTable(rows [][]string) error {
numRows := len(rows)
numCols := len(rows[0])
if numRows != len(t.RowColors) {
return fmt.Errorf("row count and row-colors mismatch")
}
// Compute max. column widths
maxColWidths := make([]int, numCols)
for _, row := range rows {
if len(row) != len(t.AlignRight) {
return fmt.Errorf("col count and align-right mismatch")
}
for i, v := range row {
if len([]rune(v)) > maxColWidths[i] {
maxColWidths[i] = len([]rune(v))
}
}
}
// Compute per-cell text with padding and alignment applied.
paddedText := make([][]string, numRows)
for r, row := range rows {
paddedText[r] = make([]string, numCols)
for c, cell := range row {
if t.AlignRight[c] {
fmtStr := fmt.Sprintf("%%%ds", maxColWidths[c])
paddedText[r][c] = fmt.Sprintf(fmtStr, cell)
} else {
extraWidth := maxColWidths[c] - len([]rune(cell))
fmtStr := fmt.Sprintf("%%s%%%ds", extraWidth)
paddedText[r][c] = fmt.Sprintf(fmtStr, cell, "")
}
}
}
// Draw table top border
segments := make([]string, numCols)
for i, c := range maxColWidths {
segments[i] = strings.Repeat("─", c+2)
}
indentText := strings.Repeat(" ", t.TableIndentWidth)
border := fmt.Sprintf("%s┌%s┐", indentText, strings.Join(segments, "┬"))
fmt.Println(border)
// Print the table with colors
for r, row := range paddedText {
fmt.Print(indentText + "│ ")
for c, text := range row {
t.RowColors[r].Print(text)
if c != numCols-1 {
fmt.Print(" │ ")
}
}
fmt.Println(" │")
}
// Draw table bottom border
border = fmt.Sprintf("%s└%s┘", indentText, strings.Join(segments, "┴"))
fmt.Println(border)
return nil
}
// RewindLines - uses terminal escape symbols to clear and rewind
// upwards on the console for `n` lines.
func RewindLines(n int) {
for i := 0; i < n; i++ {
fmt.Printf("\033[1A\033[K")
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package console
import (
"testing"
"github.com/fatih/color"
)
func TestSetColor(t *testing.T) {
SetColor("unknown", color.New(color.FgWhite))
_, ok := Theme["unknown"]
if !ok {
t.Fatal("missing theme")
}
}
func TestColorLock(t *testing.T) {
Lock()
Print("") // Test for deadlocks.
Unlock()
}
+54
View File
@@ -0,0 +1,54 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package console
import "github.com/fatih/color"
var (
// Theme contains default color mapping.
Theme = map[string]*color.Color{
"Debug": color.New(color.FgWhite, color.Faint, color.Italic),
"Fatal": color.New(color.FgRed, color.Italic, color.Bold),
"Error": color.New(color.FgYellow, color.Italic),
"Info": color.New(color.FgGreen, color.Bold),
"Print": color.New(),
"PrintB": color.New(color.FgBlue, color.Bold),
"PrintC": color.New(color.FgGreen, color.Bold),
}
)
// SetColorOff disables coloring for the entire session.
func SetColorOff() {
privateMutex.Lock()
defer privateMutex.Unlock()
color.NoColor = true
}
// SetColorOn enables coloring for the entire session.
func SetColorOn() {
privateMutex.Lock()
defer privateMutex.Unlock()
color.NoColor = false
}
// SetColor sets a color for a particular tag.
func SetColor(tag string, cl *color.Color) {
privateMutex.Lock()
defer privateMutex.Unlock()
// add new theme
Theme[tag] = cl
}
+1 -1
View File
@@ -30,7 +30,7 @@ import (
const defaultAppendBufferSize = humanize.MiByte
// WriteOnCloser implements io.WriteCloser and always
// exectues at least one write operation if it is closed.
// executes at least one write operation if it is closed.
//
// This can be useful within the context of HTTP. At least
// one write operation must happen to send the HTTP headers
+2 -2
View File
@@ -85,10 +85,10 @@ const (
PutObjectAction = "s3:PutObject"
// PutBucketLifecycleAction - PutBucketLifecycle Rest API action.
PutBucketLifecycleAction = "s3:PutBucketLifecycle"
PutBucketLifecycleAction = "s3:PutLifecycleConfiguration"
// GetBucketLifecycleAction - GetBucketLifecycle Rest API action.
GetBucketLifecycleAction = "s3:GetBucketLifecycle"
GetBucketLifecycleAction = "s3:GetLifecycleConfiguration"
// BypassGovernanceModeAction - bypass governance mode for DeleteObject Rest API action.
BypassGovernanceModeAction = "s3:BypassGovernanceMode"
+1 -1
View File
@@ -84,7 +84,7 @@ func (r *Reader) Read(dst sql.Record) (sql.Record, error) {
}
}
// If no index max, add that.
// If no index map, add that.
if r.nameIndexMap == nil {
r.nameIndexMap = make(map[string]int64)
for i := range r.columnNames {