| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | // Copyright (c) 2015-2024 MinIO, Inc
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/base64" | 
					
						
							|  |  |  | 	"encoding/binary" | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2022-12-09 03:18:07 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	"path" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2023-12-21 12:13:40 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 08:53:08 +08:00
										 |  |  | 	"github.com/minio/madmin-go/v3" | 
					
						
							| 
									
										
										
										
											2021-06-02 05:59:40 +08:00
										 |  |  | 	"github.com/minio/minio/internal/crypto" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/hash" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/kms" | 
					
						
							| 
									
										
										
										
											2023-12-21 12:13:40 +08:00
										 |  |  | 	"github.com/prometheus/client_golang/prometheus" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //go:generate msgp -file $GOFILE
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							| 
									
										
										
										
											2022-12-09 03:18:07 +08:00
										 |  |  | 	errTierMissingCredentials = AdminError{ | 
					
						
							|  |  |  | 		Code:       "XMinioAdminTierMissingCredentials", | 
					
						
							|  |  |  | 		Message:    "Specified remote credentials are empty", | 
					
						
							|  |  |  | 		StatusCode: http.StatusForbidden, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	errTierBackendInUse = AdminError{ | 
					
						
							|  |  |  | 		Code:       "XMinioAdminTierBackendInUse", | 
					
						
							|  |  |  | 		Message:    "Specified remote tier is already in use", | 
					
						
							|  |  |  | 		StatusCode: http.StatusConflict, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	errTierTypeUnsupported = AdminError{ | 
					
						
							|  |  |  | 		Code:       "XMinioAdminTierTypeUnsupported", | 
					
						
							|  |  |  | 		Message:    "Specified tier type is unsupported", | 
					
						
							|  |  |  | 		StatusCode: http.StatusBadRequest, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	errTierBackendNotEmpty = AdminError{ | 
					
						
							|  |  |  | 		Code:       "XMinioAdminTierBackendNotEmpty", | 
					
						
							|  |  |  | 		Message:    "Specified remote backend is not empty", | 
					
						
							|  |  |  | 		StatusCode: http.StatusBadRequest, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	tierConfigFile    = "tier-config.bin" | 
					
						
							|  |  |  | 	tierConfigFormat  = 1 | 
					
						
							| 
									
										
										
										
											2022-04-12 04:24:40 +08:00
										 |  |  | 	tierConfigV1      = 1 | 
					
						
							|  |  |  | 	tierConfigVersion = 2 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // tierConfigPath refers to remote tier config object name
 | 
					
						
							| 
									
										
										
										
											2021-05-26 05:17:33 +08:00
										 |  |  | var tierConfigPath = path.Join(minioConfigPrefix, tierConfigFile) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | const tierCfgRefreshAtHdr = "X-MinIO-TierCfg-RefreshedAt" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // TierConfigMgr holds the collection of remote tiers configured in this deployment.
 | 
					
						
							|  |  |  | type TierConfigMgr struct { | 
					
						
							|  |  |  | 	sync.RWMutex `msg:"-"` | 
					
						
							|  |  |  | 	drivercache  map[string]WarmBackend `msg:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 	Tiers           map[string]madmin.TierConfig `json:"tiers"` | 
					
						
							|  |  |  | 	lastRefreshedAt time.Time                    `msg:"-"` | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 12:13:40 +08:00
										 |  |  | type tierMetrics struct { | 
					
						
							|  |  |  | 	sync.RWMutex  // protects requestsCount only
 | 
					
						
							|  |  |  | 	requestsCount map[string]struct { | 
					
						
							|  |  |  | 		success int64 | 
					
						
							|  |  |  | 		failure int64 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	histogram *prometheus.HistogramVec | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var globalTierMetrics = tierMetrics{ | 
					
						
							|  |  |  | 	requestsCount: make(map[string]struct { | 
					
						
							|  |  |  | 		success int64 | 
					
						
							|  |  |  | 		failure int64 | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 	histogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{ | 
					
						
							|  |  |  | 		Name:    "tier_ttlb_seconds", | 
					
						
							|  |  |  | 		Help:    "Time taken by requests served by warm tier", | 
					
						
							|  |  |  | 		Buckets: []float64{0.01, 0.1, 1, 2, 5, 10, 60, 5 * 60, 15 * 60, 30 * 60}, | 
					
						
							|  |  |  | 	}, []string{"tier"}), | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *tierMetrics) Observe(tier string, dur time.Duration) { | 
					
						
							|  |  |  | 	t.histogram.With(prometheus.Labels{"tier": tier}).Observe(dur.Seconds()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *tierMetrics) logSuccess(tier string) { | 
					
						
							|  |  |  | 	t.Lock() | 
					
						
							|  |  |  | 	defer t.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	stat := t.requestsCount[tier] | 
					
						
							|  |  |  | 	stat.success++ | 
					
						
							|  |  |  | 	t.requestsCount[tier] = stat | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *tierMetrics) logFailure(tier string) { | 
					
						
							|  |  |  | 	t.Lock() | 
					
						
							|  |  |  | 	defer t.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	stat := t.requestsCount[tier] | 
					
						
							|  |  |  | 	stat.failure++ | 
					
						
							|  |  |  | 	t.requestsCount[tier] = stat | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	// {minio_node}_{tier}_{ttlb_seconds_distribution}
 | 
					
						
							|  |  |  | 	tierTTLBMD = MetricDescription{ | 
					
						
							|  |  |  | 		Namespace: nodeMetricNamespace, | 
					
						
							|  |  |  | 		Subsystem: tierSubsystem, | 
					
						
							|  |  |  | 		Name:      ttlbDistribution, | 
					
						
							|  |  |  | 		Help:      "Distribution of time to last byte for objects downloaded from warm tier", | 
					
						
							|  |  |  | 		Type:      gaugeMetric, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// {minio_node}_{tier}_{requests_success}
 | 
					
						
							|  |  |  | 	tierRequestsSuccessMD = MetricDescription{ | 
					
						
							|  |  |  | 		Namespace: nodeMetricNamespace, | 
					
						
							|  |  |  | 		Subsystem: tierSubsystem, | 
					
						
							|  |  |  | 		Name:      tierRequestsSuccess, | 
					
						
							|  |  |  | 		Help:      "Number of requests to download object from warm tier that were successful", | 
					
						
							|  |  |  | 		Type:      counterMetric, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// {minio_node}_{tier}_{requests_failure}
 | 
					
						
							|  |  |  | 	tierRequestsFailureMD = MetricDescription{ | 
					
						
							|  |  |  | 		Namespace: nodeMetricNamespace, | 
					
						
							|  |  |  | 		Subsystem: tierSubsystem, | 
					
						
							|  |  |  | 		Name:      tierRequestsFailure, | 
					
						
							|  |  |  | 		Help:      "Number of requests to download object from warm tier that failed", | 
					
						
							|  |  |  | 		Type:      counterMetric, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-10 17:15:15 +08:00
										 |  |  | func (t *tierMetrics) Report() []MetricV2 { | 
					
						
							| 
									
										
										
										
											2024-03-05 02:05:56 +08:00
										 |  |  | 	metrics := getHistogramMetrics(t.histogram, tierTTLBMD, true) | 
					
						
							| 
									
										
										
										
											2023-12-21 12:13:40 +08:00
										 |  |  | 	t.RLock() | 
					
						
							|  |  |  | 	defer t.RUnlock() | 
					
						
							|  |  |  | 	for tier, stat := range t.requestsCount { | 
					
						
							| 
									
										
										
										
											2024-03-10 17:15:15 +08:00
										 |  |  | 		metrics = append(metrics, MetricV2{ | 
					
						
							| 
									
										
										
										
											2023-12-21 12:13:40 +08:00
										 |  |  | 			Description:    tierRequestsSuccessMD, | 
					
						
							|  |  |  | 			Value:          float64(stat.success), | 
					
						
							|  |  |  | 			VariableLabels: map[string]string{"tier": tier}, | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2024-03-10 17:15:15 +08:00
										 |  |  | 		metrics = append(metrics, MetricV2{ | 
					
						
							| 
									
										
										
										
											2023-12-21 12:13:40 +08:00
										 |  |  | 			Description:    tierRequestsFailureMD, | 
					
						
							|  |  |  | 			Value:          float64(stat.failure), | 
					
						
							|  |  |  | 			VariableLabels: map[string]string{"tier": tier}, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return metrics | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | func (config *TierConfigMgr) refreshedAt() time.Time { | 
					
						
							|  |  |  | 	config.RLock() | 
					
						
							|  |  |  | 	defer config.RUnlock() | 
					
						
							|  |  |  | 	return config.lastRefreshedAt | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // IsTierValid returns true if there exists a remote tier by name tierName,
 | 
					
						
							|  |  |  | // otherwise returns false.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) IsTierValid(tierName string) bool { | 
					
						
							|  |  |  | 	config.RLock() | 
					
						
							|  |  |  | 	defer config.RUnlock() | 
					
						
							|  |  |  | 	_, valid := config.isTierNameInUse(tierName) | 
					
						
							|  |  |  | 	return valid | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // isTierNameInUse returns tier type and true if there exists a remote tier by
 | 
					
						
							|  |  |  | // name tierName, otherwise returns madmin.Unsupported and false. N B this
 | 
					
						
							|  |  |  | // function is meant for internal use, where the caller is expected to take
 | 
					
						
							|  |  |  | // appropriate locks.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) isTierNameInUse(tierName string) (madmin.TierType, bool) { | 
					
						
							|  |  |  | 	if t, ok := config.Tiers[tierName]; ok { | 
					
						
							|  |  |  | 		return t.Type, true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return madmin.Unsupported, false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Add adds tier to config if it passes all validations.
 | 
					
						
							| 
									
										
										
										
											2023-02-28 01:26:26 +08:00
										 |  |  | func (config *TierConfigMgr) Add(ctx context.Context, tier madmin.TierConfig, ignoreInUse bool) error { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	config.Lock() | 
					
						
							|  |  |  | 	defer config.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check if tier name is in all caps
 | 
					
						
							|  |  |  | 	tierName := tier.Name | 
					
						
							|  |  |  | 	if tierName != strings.ToUpper(tierName) { | 
					
						
							|  |  |  | 		return errTierNameNotUppercase | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check if tier name already in use
 | 
					
						
							|  |  |  | 	if _, exists := config.isTierNameInUse(tierName); exists { | 
					
						
							|  |  |  | 		return errTierAlreadyExists | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 	d, err := newWarmBackend(ctx, tier, true) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-28 01:26:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if !ignoreInUse { | 
					
						
							|  |  |  | 		// Check if warmbackend is in use by other MinIO tenants
 | 
					
						
							|  |  |  | 		inUse, err := d.InUse(ctx) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if inUse { | 
					
						
							|  |  |  | 			return errTierBackendInUse | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	config.Tiers[tierName] = tier | 
					
						
							|  |  |  | 	config.drivercache[tierName] = d | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 05:34:25 +08:00
										 |  |  | // Remove removes tier if it is empty.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Remove(ctx context.Context, tier string) error { | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 	d, err := config.getDriver(ctx, tier) | 
					
						
							| 
									
										
										
										
											2022-02-24 05:34:25 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 		if errors.Is(err, errTierNotFound) { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-02-24 05:34:25 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if inuse, err := d.InUse(ctx); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} else if inuse { | 
					
						
							|  |  |  | 		return errTierBackendNotEmpty | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-12-07 10:17:03 +08:00
										 |  |  | 	config.Lock() | 
					
						
							|  |  |  | 	delete(config.Tiers, tier) | 
					
						
							|  |  |  | 	delete(config.drivercache, tier) | 
					
						
							|  |  |  | 	config.Unlock() | 
					
						
							| 
									
										
										
										
											2022-02-24 05:34:25 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Verify verifies if tier's config is valid by performing all supported
 | 
					
						
							|  |  |  | // operations on the corresponding warmbackend.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Verify(ctx context.Context, tier string) error { | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 	d, err := config.getDriver(ctx, tier) | 
					
						
							| 
									
										
										
										
											2022-02-24 05:34:25 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return checkWarmBackend(ctx, d) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-17 22:50:00 +08:00
										 |  |  | // Empty returns if tier targets are empty
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Empty() bool { | 
					
						
							| 
									
										
										
										
											2022-03-30 01:10:06 +08:00
										 |  |  | 	if config == nil { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-17 22:50:00 +08:00
										 |  |  | 	return len(config.ListTiers()) == 0 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-31 00:59:51 +08:00
										 |  |  | // TierType returns the type of tier
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) TierType(name string) string { | 
					
						
							|  |  |  | 	config.RLock() | 
					
						
							|  |  |  | 	defer config.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cfg, ok := config.Tiers[name] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return "internal" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return cfg.Type.String() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // ListTiers lists remote tiers configured in this deployment.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) ListTiers() []madmin.TierConfig { | 
					
						
							| 
									
										
										
										
											2023-10-31 00:59:51 +08:00
										 |  |  | 	if config == nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	config.RLock() | 
					
						
							|  |  |  | 	defer config.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var tierCfgs []madmin.TierConfig | 
					
						
							|  |  |  | 	for _, tier := range config.Tiers { | 
					
						
							|  |  |  | 		// This makes a local copy of tier config before
 | 
					
						
							|  |  |  | 		// passing a reference to it.
 | 
					
						
							|  |  |  | 		tier := tier.Clone() | 
					
						
							|  |  |  | 		tierCfgs = append(tierCfgs, tier) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return tierCfgs | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Edit replaces the credentials of the remote tier specified by tierName with creds.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Edit(ctx context.Context, tierName string, creds madmin.TierCreds) error { | 
					
						
							|  |  |  | 	config.Lock() | 
					
						
							|  |  |  | 	defer config.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check if tier by this name exists
 | 
					
						
							|  |  |  | 	tierType, exists := config.isTierNameInUse(tierName) | 
					
						
							|  |  |  | 	if !exists { | 
					
						
							|  |  |  | 		return errTierNotFound | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-23 17:34:31 +08:00
										 |  |  | 	cfg := config.Tiers[tierName] | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	switch tierType { | 
					
						
							|  |  |  | 	case madmin.S3: | 
					
						
							| 
									
										
										
										
											2023-12-22 08:58:10 +08:00
										 |  |  | 		if creds.AWSRole { | 
					
						
							| 
									
										
										
										
											2021-09-23 17:34:31 +08:00
										 |  |  | 			cfg.S3.AWSRole = true | 
					
						
							| 
									
										
										
										
											2023-12-22 08:58:10 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if creds.AWSRoleWebIdentityTokenFile != "" && creds.AWSRoleARN != "" { | 
					
						
							|  |  |  | 			cfg.S3.AWSRoleARN = creds.AWSRoleARN | 
					
						
							|  |  |  | 			cfg.S3.AWSRoleWebIdentityTokenFile = creds.AWSRoleWebIdentityTokenFile | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if creds.AccessKey != "" && creds.SecretKey != "" { | 
					
						
							| 
									
										
										
										
											2021-09-23 17:34:31 +08:00
										 |  |  | 			cfg.S3.AccessKey = creds.AccessKey | 
					
						
							|  |  |  | 			cfg.S3.SecretKey = creds.SecretKey | 
					
						
							| 
									
										
										
										
											2021-06-05 03:47:00 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	case madmin.Azure: | 
					
						
							| 
									
										
										
										
											2023-12-22 08:58:10 +08:00
										 |  |  | 		if creds.SecretKey != "" { | 
					
						
							|  |  |  | 			cfg.Azure.AccountKey = creds.SecretKey | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if creds.AzSP.TenantID != "" { | 
					
						
							|  |  |  | 			cfg.Azure.SPAuth.TenantID = creds.AzSP.TenantID | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if creds.AzSP.ClientID != "" { | 
					
						
							|  |  |  | 			cfg.Azure.SPAuth.ClientID = creds.AzSP.ClientID | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if creds.AzSP.ClientSecret != "" { | 
					
						
							|  |  |  | 			cfg.Azure.SPAuth.ClientSecret = creds.AzSP.ClientSecret | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	case madmin.GCS: | 
					
						
							|  |  |  | 		if creds.CredsJSON == nil { | 
					
						
							| 
									
										
										
										
											2022-12-09 03:18:07 +08:00
										 |  |  | 			return errTierMissingCredentials | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-09-23 17:34:31 +08:00
										 |  |  | 		cfg.GCS.Creds = base64.URLEncoding.EncodeToString(creds.CredsJSON) | 
					
						
							| 
									
										
										
										
											2022-04-12 04:24:40 +08:00
										 |  |  | 	case madmin.MinIO: | 
					
						
							|  |  |  | 		if creds.AccessKey == "" || creds.SecretKey == "" { | 
					
						
							| 
									
										
										
										
											2022-12-09 03:18:07 +08:00
										 |  |  | 			return errTierMissingCredentials | 
					
						
							| 
									
										
										
										
											2022-04-12 04:24:40 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		cfg.MinIO.AccessKey = creds.AccessKey | 
					
						
							|  |  |  | 		cfg.MinIO.SecretKey = creds.SecretKey | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 	d, err := newWarmBackend(ctx, cfg, true) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-23 17:34:31 +08:00
										 |  |  | 	config.Tiers[tierName] = cfg | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	config.drivercache[tierName] = d | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Bytes returns msgpack encoded config with format and version headers.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Bytes() ([]byte, error) { | 
					
						
							|  |  |  | 	config.RLock() | 
					
						
							|  |  |  | 	defer config.RUnlock() | 
					
						
							|  |  |  | 	data := make([]byte, 4, config.Msgsize()+4) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Initialize the header.
 | 
					
						
							|  |  |  | 	binary.LittleEndian.PutUint16(data[0:2], tierConfigFormat) | 
					
						
							|  |  |  | 	binary.LittleEndian.PutUint16(data[2:4], tierConfigVersion) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Marshal the tier config
 | 
					
						
							|  |  |  | 	return config.MarshalMsg(data) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // getDriver returns a warmBackend interface object initialized with remote tier config matching tierName
 | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | func (config *TierConfigMgr) getDriver(ctx context.Context, tierName string) (d WarmBackend, err error) { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	config.Lock() | 
					
						
							|  |  |  | 	defer config.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var ok bool | 
					
						
							|  |  |  | 	// Lookup in-memory drivercache
 | 
					
						
							|  |  |  | 	d, ok = config.drivercache[tierName] | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		return d, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Initialize driver from tier config matching tierName
 | 
					
						
							|  |  |  | 	t, ok := config.Tiers[tierName] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil, errTierNotFound | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 	d, err = newWarmBackend(ctx, t, false) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	config.drivercache[tierName] = d | 
					
						
							|  |  |  | 	return d, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // configReader returns a PutObjReader and ObjectOptions needed to save config
 | 
					
						
							|  |  |  | // using a PutObject API. PutObjReader encrypts json encoded tier configurations
 | 
					
						
							|  |  |  | // if KMS is enabled, otherwise simply yields the json encoded bytes as is.
 | 
					
						
							|  |  |  | // Similarly, ObjectOptions value depends on KMS' status.
 | 
					
						
							| 
									
										
										
										
											2023-09-19 01:00:54 +08:00
										 |  |  | func (config *TierConfigMgr) configReader(ctx context.Context) (*PutObjReader, *ObjectOptions, error) { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	b, err := config.Bytes() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	payloadSize := int64(len(b)) | 
					
						
							|  |  |  | 	br := bytes.NewReader(b) | 
					
						
							| 
									
										
										
										
											2023-09-19 01:00:54 +08:00
										 |  |  | 	hr, err := hash.NewReader(ctx, br, payloadSize, "", "", payloadSize) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if GlobalKMS == nil { | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 		return NewPutObjReader(hr), &ObjectOptions{MaxParity: true}, nil | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Note: Local variables with names ek, oek, etc are named inline with
 | 
					
						
							|  |  |  | 	// acronyms defined here -
 | 
					
						
							|  |  |  | 	// https://github.com/minio/minio/blob/master/docs/security/README.md#acronyms
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Encrypt json encoded tier configurations
 | 
					
						
							|  |  |  | 	metadata := make(map[string]string) | 
					
						
							| 
									
										
										
										
											2022-07-19 09:54:27 +08:00
										 |  |  | 	encBr, oek, err := newEncryptReader(context.Background(), hr, crypto.S3, "", nil, minioMetaBucket, tierConfigPath, metadata, kms.Context{}) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	info := ObjectInfo{ | 
					
						
							|  |  |  | 		Size: payloadSize, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	encSize := info.EncryptedSize() | 
					
						
							| 
									
										
										
										
											2023-09-19 01:00:54 +08:00
										 |  |  | 	encHr, err := hash.NewReader(ctx, encBr, encSize, "", "", encSize) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pReader, err := NewPutObjReader(hr).WithEncryption(encHr, &oek) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	opts := &ObjectOptions{ | 
					
						
							|  |  |  | 		UserDefined: metadata, | 
					
						
							|  |  |  | 		MTime:       UTCNow(), | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 		MaxParity:   true, | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return pReader, opts, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Reload updates config by reloading remote tier config from config store.
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Reload(ctx context.Context, objAPI ObjectLayer) error { | 
					
						
							|  |  |  | 	newConfig, err := loadTierConfig(ctx, objAPI) | 
					
						
							| 
									
										
										
										
											2024-04-17 13:09:58 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	config.Lock() | 
					
						
							|  |  |  | 	defer config.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	switch err { | 
					
						
							|  |  |  | 	case nil: | 
					
						
							|  |  |  | 		break | 
					
						
							|  |  |  | 	case errConfigNotFound: // nothing to reload
 | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 		// To maintain the invariance that lastRefreshedAt records the
 | 
					
						
							|  |  |  | 		// timestamp of last successful refresh
 | 
					
						
							|  |  |  | 		config.lastRefreshedAt = UTCNow() | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reset drivercache built using current config
 | 
					
						
							|  |  |  | 	for k := range config.drivercache { | 
					
						
							|  |  |  | 		delete(config.drivercache, k) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Remove existing tier configs
 | 
					
						
							|  |  |  | 	for k := range config.Tiers { | 
					
						
							|  |  |  | 		delete(config.Tiers, k) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Copy over the new tier configs
 | 
					
						
							|  |  |  | 	for tier, cfg := range newConfig.Tiers { | 
					
						
							|  |  |  | 		config.Tiers[tier] = cfg | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 	config.lastRefreshedAt = UTCNow() | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Save saves tier configuration onto objAPI
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Save(ctx context.Context, objAPI ObjectLayer) error { | 
					
						
							|  |  |  | 	if objAPI == nil { | 
					
						
							|  |  |  | 		return errServerNotInitialized | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-19 01:00:54 +08:00
										 |  |  | 	pr, opts, err := globalTierConfigMgr.configReader(ctx) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = objAPI.PutObject(ctx, minioMetaBucket, tierConfigPath, pr, *opts) | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewTierConfigMgr - creates new tier configuration manager,
 | 
					
						
							|  |  |  | func NewTierConfigMgr() *TierConfigMgr { | 
					
						
							|  |  |  | 	return &TierConfigMgr{ | 
					
						
							|  |  |  | 		drivercache: make(map[string]WarmBackend), | 
					
						
							|  |  |  | 		Tiers:       make(map[string]madmin.TierConfig), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | func (config *TierConfigMgr) refreshTierConfig(ctx context.Context, objAPI ObjectLayer) { | 
					
						
							|  |  |  | 	const tierCfgRefresh = 15 * time.Minute | 
					
						
							|  |  |  | 	r := rand.New(rand.NewSource(time.Now().UnixNano())) | 
					
						
							|  |  |  | 	randInterval := func() time.Duration { | 
					
						
							|  |  |  | 		return time.Duration(r.Float64() * 5 * float64(time.Second)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// To avoid all MinIO nodes reading the tier config object at the same
 | 
					
						
							|  |  |  | 	// time.
 | 
					
						
							|  |  |  | 	t := time.NewTimer(tierCfgRefresh + randInterval()) | 
					
						
							|  |  |  | 	defer t.Stop() | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		case <-t.C: | 
					
						
							|  |  |  | 			err := config.Reload(ctx, objAPI) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2024-04-04 20:04:40 +08:00
										 |  |  | 				tierLogIf(ctx, err) | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.Reset(tierCfgRefresh + randInterval()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // loadTierConfig loads remote tier configuration from objAPI.
 | 
					
						
							|  |  |  | func loadTierConfig(ctx context.Context, objAPI ObjectLayer) (*TierConfigMgr, error) { | 
					
						
							|  |  |  | 	if objAPI == nil { | 
					
						
							|  |  |  | 		return nil, errServerNotInitialized | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data, err := readConfig(ctx, objAPI, tierConfigPath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(data) <= 4 { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("tierConfigInit: no data") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Read header
 | 
					
						
							| 
									
										
										
										
											2022-04-12 04:24:40 +08:00
										 |  |  | 	switch format := binary.LittleEndian.Uint16(data[0:2]); format { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	case tierConfigFormat: | 
					
						
							|  |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2022-04-12 04:24:40 +08:00
										 |  |  | 		return nil, fmt.Errorf("tierConfigInit: unknown format: %d", format) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cfg := NewTierConfigMgr() | 
					
						
							| 
									
										
										
										
											2022-04-12 04:24:40 +08:00
										 |  |  | 	switch version := binary.LittleEndian.Uint16(data[2:4]); version { | 
					
						
							|  |  |  | 	case tierConfigV1, tierConfigVersion: | 
					
						
							|  |  |  | 		if _, decErr := cfg.UnmarshalMsg(data[4:]); decErr != nil { | 
					
						
							|  |  |  | 			return nil, decErr | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("tierConfigInit: unknown version: %d", version) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-04-12 04:24:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return cfg, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Init initializes tier configuration reading from objAPI
 | 
					
						
							|  |  |  | func (config *TierConfigMgr) Init(ctx context.Context, objAPI ObjectLayer) error { | 
					
						
							| 
									
										
										
										
											2024-02-16 03:52:44 +08:00
										 |  |  | 	err := config.Reload(ctx, objAPI) | 
					
						
							|  |  |  | 	if globalIsDistErasure { | 
					
						
							|  |  |  | 		go config.refreshTierConfig(ctx, objAPI) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | } |