2022-11-14 10:16:40 -05:00
// Copyright (c) 2015-2022 MinIO, Inc.
2021-10-06 19:36:31 -04: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"
2022-11-14 10:16:40 -05:00
"encoding/binary"
2021-10-06 19:36:31 -04:00
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"net/url"
2022-01-06 18:52:43 -05:00
"reflect"
2023-02-13 11:09:52 -05:00
"runtime"
2021-10-06 19:36:31 -04:00
"sort"
"strings"
"sync"
"time"
2023-06-19 20:53:08 -04:00
"github.com/minio/madmin-go/v3"
2023-09-15 21:01:47 -04:00
"github.com/minio/minio-go/v7"
2021-10-06 19:36:31 -04:00
minioClient "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/replication"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/auth"
sreplication "github.com/minio/minio/internal/bucket/replication"
"github.com/minio/minio/internal/logger"
2023-09-04 15:57:37 -04:00
"github.com/minio/pkg/v2/policy"
2021-10-06 19:36:31 -04:00
)
const (
srStatePrefix = minioConfigPrefix + "/site-replication"
2021-12-08 14:50:15 -05:00
srStateFile = "state.json"
2021-10-06 19:36:31 -04:00
)
const (
srStateFormatVersion1 = 1
)
var (
2021-12-14 17:09:57 -05:00
errSRCannotJoin = SRError {
Cause : errors . New ( "this site is already configured for site-replication" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
errSRDuplicateSites = SRError {
Cause : errors . New ( "duplicate sites provided for site-replication" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
errSRSelfNotFound = SRError {
Cause : errors . New ( "none of the given sites correspond to the current one" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
errSRPeerNotFound = SRError {
Cause : errors . New ( "peer not found" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
2022-12-22 04:31:20 -05:00
errSRRequestorNotFound = SRError {
Cause : errors . New ( "requesting site not found in site replication config" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
2021-12-14 17:09:57 -05:00
errSRNotEnabled = SRError {
Cause : errors . New ( "site replication is not enabled" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
2022-11-14 10:16:40 -05:00
errSRResyncStarted = SRError {
Cause : errors . New ( "site replication resync is already in progress" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
errSRResyncCanceled = SRError {
Cause : errors . New ( "site replication resync is already canceled" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
errSRNoResync = SRError {
Cause : errors . New ( "no resync in progress" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
errSRResyncToSelf = SRError {
Cause : errors . New ( "invalid peer specified - cannot resync to self" ) ,
Code : ErrSiteReplicationInvalidRequest ,
}
2021-10-06 19:36:31 -04:00
)
func errSRInvalidRequest ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationInvalidRequest ,
}
}
func errSRPeerResp ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationPeerResp ,
}
}
func errSRBackendIssue ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationBackendIssue ,
}
}
func errSRServiceAccount ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationServiceAccountError ,
}
}
func errSRBucketConfigError ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationBucketConfigError ,
}
}
func errSRBucketMetaError ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationBucketMetaError ,
}
}
func errSRIAMError ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationIAMError ,
}
}
2022-05-06 15:40:34 -04:00
func errSRConfigMissingError ( err error ) SRError {
return SRError {
Cause : err ,
Code : ErrSiteReplicationConfigMissing ,
}
}
2022-01-02 12:15:06 -05:00
var errSRObjectLayerNotReady = SRError {
Cause : fmt . Errorf ( "object layer not ready" ) ,
Code : ErrServerNotInitialized ,
}
2021-10-06 19:36:31 -04:00
func getSRStateFilePath ( ) string {
return srStatePrefix + SlashSeparator + srStateFile
}
// SRError - wrapped error for site replication.
type SRError struct {
Cause error
Code APIErrorCode
}
func ( c SRError ) Error ( ) string {
2022-06-20 13:48:11 -04:00
if c . Cause != nil {
return c . Cause . Error ( )
}
return "<nil>"
}
func ( c SRError ) Unwrap ( ) error {
return c . Cause
2021-10-06 19:36:31 -04:00
}
func wrapSRErr ( err error ) SRError {
return SRError { Cause : err , Code : ErrInternalError }
}
// SiteReplicationSys - manages cluster-level replication.
type SiteReplicationSys struct {
sync . RWMutex
enabled bool
// In-memory and persisted multi-site replication state.
state srState
2023-02-09 00:16:53 -05:00
iamMetaCache srIAMCache
2021-10-06 19:36:31 -04:00
}
type srState srStateV1
// srStateV1 represents version 1 of the site replication state persistence
// format.
type srStateV1 struct {
Name string ` json:"name" `
// Peers maps peers by their deploymentID
Peers map [ string ] madmin . PeerInfo ` json:"peers" `
ServiceAccountAccessKey string ` json:"serviceAccountAccessKey" `
}
// srStateData represents the format of the current `srStateFile`.
type srStateData struct {
Version int ` json:"version" `
SRState srStateV1 ` json:"srState" `
}
// Init - initialize the site replication manager.
func ( c * SiteReplicationSys ) Init ( ctx context . Context , objAPI ObjectLayer ) error {
2022-04-28 05:39:00 -04:00
go c . startHealRoutine ( ctx , objAPI )
2021-10-06 19:36:31 -04:00
err := c . loadFromDisk ( ctx , objAPI )
if err == errConfigNotFound {
return nil
}
c . RLock ( )
defer c . RUnlock ( )
if c . enabled {
2022-03-03 16:21:16 -05:00
logger . Info ( "Cluster replication initialized" )
2021-10-06 19:36:31 -04:00
}
return err
}
func ( c * SiteReplicationSys ) loadFromDisk ( ctx context . Context , objAPI ObjectLayer ) error {
buf , err := readConfig ( ctx , objAPI , getSRStateFilePath ( ) )
if err != nil {
2023-06-01 13:19:56 -04:00
if errors . Is ( err , errConfigNotFound ) {
c . Lock ( )
defer c . Unlock ( )
c . state = srState { }
c . enabled = false
}
2021-10-06 19:36:31 -04:00
return err
}
// attempt to read just the version key in the state file to ensure we
// are reading a compatible version.
var ver struct {
Version int ` json:"version" `
}
err = json . Unmarshal ( buf , & ver )
if err != nil {
return err
}
if ver . Version != srStateFormatVersion1 {
return fmt . Errorf ( "Unexpected ClusterRepl state version: %d" , ver . Version )
}
var sdata srStateData
err = json . Unmarshal ( buf , & sdata )
if err != nil {
return err
}
c . Lock ( )
defer c . Unlock ( )
c . state = srState ( sdata . SRState )
2022-09-20 17:32:23 -04:00
c . enabled = len ( c . state . Peers ) != 0
2021-10-06 19:36:31 -04:00
return nil
}
func ( c * SiteReplicationSys ) saveToDisk ( ctx context . Context , state srState ) error {
sdata := srStateData {
Version : srStateFormatVersion1 ,
SRState : srStateV1 ( state ) ,
}
buf , err := json . Marshal ( sdata )
if err != nil {
return err
}
objAPI := newObjectLayerFn ( )
if objAPI == nil {
return errServerNotInitialized
}
2022-06-20 13:48:11 -04:00
if err = saveConfig ( ctx , objAPI , getSRStateFilePath ( ) , buf ) ; err != nil {
2021-10-06 19:36:31 -04:00
return err
}
2022-06-20 13:48:11 -04:00
for _ , err := range globalNotificationSys . ReloadSiteReplicationConfig ( ctx ) {
logger . LogIf ( ctx , err )
2021-10-06 19:36:31 -04:00
}
c . Lock ( )
defer c . Unlock ( )
c . state = state
2022-02-01 20:26:09 -05:00
c . enabled = len ( c . state . Peers ) != 0
2021-10-06 19:36:31 -04:00
return nil
}
2022-09-20 17:32:23 -04:00
func ( c * SiteReplicationSys ) removeFromDisk ( ctx context . Context ) error {
objAPI := newObjectLayerFn ( )
if objAPI == nil {
return errServerNotInitialized
}
if err := deleteConfig ( ctx , objAPI , getSRStateFilePath ( ) ) ; err != nil {
return err
}
for _ , err := range globalNotificationSys . ReloadSiteReplicationConfig ( ctx ) {
logger . LogIf ( ctx , err )
}
c . Lock ( )
defer c . Unlock ( )
c . state = srState { }
c . enabled = false
return nil
}
2021-10-06 19:36:31 -04:00
const (
// Access key of service account used for perform cluster-replication
// operations.
siteReplicatorSvcAcc = "site-replicator-0"
)
2021-11-30 16:16:37 -05:00
// PeerSiteInfo is a wrapper struct around madmin.PeerSite with extra info on site status
type PeerSiteInfo struct {
madmin . PeerSite
self bool
DeploymentID string
Replicated bool // true if already participating in site replication
Empty bool // true if cluster has no buckets
}
2021-10-06 19:36:31 -04:00
2021-11-30 16:16:37 -05:00
// getSiteStatuses gathers more info on the sites being added
2022-01-19 23:02:24 -05:00
func ( c * SiteReplicationSys ) getSiteStatuses ( ctx context . Context , sites ... madmin . PeerSite ) ( psi [ ] PeerSiteInfo , err error ) {
psi = make ( [ ] PeerSiteInfo , 0 , len ( sites ) )
2021-11-30 16:16:37 -05:00
for _ , v := range sites {
2021-10-06 19:36:31 -04:00
admClient , err := getAdminClient ( v . Endpoint , v . AccessKey , v . SecretKey )
if err != nil {
2021-11-30 16:16:37 -05:00
return psi , errSRPeerResp ( fmt . Errorf ( "unable to create admin client for %s: %w" , v . Name , err ) )
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
2021-10-06 19:36:31 -04:00
info , err := admClient . ServerInfo ( ctx )
if err != nil {
2021-11-30 16:16:37 -05:00
return psi , errSRPeerResp ( fmt . Errorf ( "unable to fetch server info for %s: %w" , v . Name , err ) )
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
s3Client , err := getS3Client ( v )
if err != nil {
return psi , errSRPeerResp ( fmt . Errorf ( "unable to create s3 client for %s: %w" , v . Name , err ) )
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
buckets , err := s3Client . ListBuckets ( ctx )
if err != nil {
return psi , errSRPeerResp ( fmt . Errorf ( "unable to list buckets for %s: %v" , v . Name , err ) )
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
psi = append ( psi , PeerSiteInfo {
PeerSite : v ,
DeploymentID : info . DeploymentID ,
Empty : len ( buckets ) == 0 ,
2023-10-18 11:06:57 -04:00
self : info . DeploymentID == globalDeploymentID ( ) ,
2022-01-19 23:02:24 -05:00
} )
2021-11-30 16:16:37 -05:00
}
return
}
2021-10-06 19:36:31 -04:00
2021-11-30 16:16:37 -05:00
// AddPeerClusters - add cluster sites for replication configuration.
2021-12-14 17:09:57 -05:00
func ( c * SiteReplicationSys ) AddPeerClusters ( ctx context . Context , psites [ ] madmin . PeerSite ) ( madmin . ReplicateAddStatus , error ) {
2022-01-19 23:02:24 -05:00
sites , serr := c . getSiteStatuses ( ctx , psites ... )
if serr != nil {
2021-11-30 16:16:37 -05:00
return madmin . ReplicateAddStatus { } , serr
}
var (
currSites madmin . SiteReplicationInfo
currDeploymentIDsSet = set . NewStringSet ( )
err error
)
2022-04-28 05:39:00 -04:00
currSites , err = c . GetClusterInfo ( ctx )
if err != nil {
return madmin . ReplicateAddStatus { } , errSRBackendIssue ( err )
}
for _ , v := range currSites . Sites {
currDeploymentIDsSet . Add ( v . DeploymentID )
2021-11-30 16:16:37 -05:00
}
deploymentIDsSet := set . NewStringSet ( )
localHasBuckets := false
nonLocalPeerWithBuckets := ""
2022-01-02 12:15:06 -05:00
selfIdx := - 1
2021-11-30 16:16:37 -05:00
for i , v := range sites {
// deploymentIDs must be unique
if deploymentIDsSet . Contains ( v . DeploymentID ) {
2021-12-14 17:09:57 -05:00
return madmin . ReplicateAddStatus { } , errSRDuplicateSites
2021-11-30 16:16:37 -05:00
}
deploymentIDsSet . Add ( v . DeploymentID )
2021-10-06 19:36:31 -04:00
2021-11-30 16:16:37 -05:00
if v . self {
selfIdx = i
localHasBuckets = ! v . Empty
continue
}
if ! v . Empty && ! currDeploymentIDsSet . Contains ( v . DeploymentID ) {
2021-10-06 19:36:31 -04:00
nonLocalPeerWithBuckets = v . Name
}
}
2022-08-25 14:30:52 -04:00
if selfIdx == - 1 {
2023-10-18 11:06:57 -04:00
return madmin . ReplicateAddStatus { } , errSRBackendIssue ( fmt . Errorf ( "global deployment ID %s mismatch, expected one of %s" , globalDeploymentID ( ) , deploymentIDsSet ) )
2022-08-25 14:30:52 -04:00
}
2022-04-28 05:39:00 -04:00
if ! currDeploymentIDsSet . IsEmpty ( ) {
2021-11-30 16:16:37 -05:00
// If current cluster is already SR enabled and no new site being added ,fail.
if currDeploymentIDsSet . Equals ( deploymentIDsSet ) {
2021-12-14 17:09:57 -05:00
return madmin . ReplicateAddStatus { } , errSRCannotJoin
2021-11-30 16:16:37 -05:00
}
if len ( currDeploymentIDsSet . Intersection ( deploymentIDsSet ) ) != len ( currDeploymentIDsSet ) {
diffSlc := getMissingSiteNames ( currDeploymentIDsSet , deploymentIDsSet , currSites . Sites )
2021-12-14 17:09:57 -05:00
return madmin . ReplicateAddStatus { } , errSRInvalidRequest ( fmt . Errorf ( "all existing replicated sites must be specified - missing %s" , strings . Join ( diffSlc , " " ) ) )
2021-11-30 16:16:37 -05:00
}
}
2022-01-19 23:02:24 -05:00
// validate that all clusters are using the same IDP settings.
pass , err := c . validateIDPSettings ( ctx , sites )
if err != nil {
return madmin . ReplicateAddStatus { } , err
}
if ! pass {
return madmin . ReplicateAddStatus { } , errSRInvalidRequest ( errors . New ( "all cluster sites must have the same IAM/IDP settings" ) )
}
2021-10-06 19:36:31 -04:00
// For this `add` API, either all clusters must be empty or the local
// cluster must be the only one having some buckets.
if localHasBuckets && nonLocalPeerWithBuckets != "" {
2021-12-14 17:09:57 -05:00
return madmin . ReplicateAddStatus { } , errSRInvalidRequest ( errors . New ( "only one cluster may have data when configuring site replication" ) )
2021-10-06 19:36:31 -04:00
}
if ! localHasBuckets && nonLocalPeerWithBuckets != "" {
2021-12-14 17:09:57 -05:00
return madmin . ReplicateAddStatus { } , errSRInvalidRequest ( fmt . Errorf ( "please send your request to the cluster containing data/buckets: %s" , nonLocalPeerWithBuckets ) )
2021-10-06 19:36:31 -04:00
}
// FIXME: Ideally, we also need to check if there are any global IAM
// policies and any (LDAP user created) service accounts on the other
// peer clusters, and if so, reject the cluster replicate add request.
// This is not yet implemented.
// VALIDATIONS COMPLETE.
// Create a common service account for all clusters, with root
// permissions.
// Create a local service account.
2021-11-30 16:16:37 -05:00
// Generate a secret key for the service account if not created already.
2021-10-06 19:36:31 -04:00
var secretKey string
2022-07-01 16:19:13 -04:00
var svcCred auth . Credentials
sa , _ , err := globalIAMSys . getServiceAccount ( ctx , siteReplicatorSvcAcc )
2021-11-30 16:16:37 -05:00
switch {
case err == errNoSuchServiceAccount :
_ , secretKey , err = auth . GenerateCredentials ( )
if err != nil {
return madmin . ReplicateAddStatus { } , errSRServiceAccount ( fmt . Errorf ( "unable to create local service account: %w" , err ) )
2021-10-06 19:36:31 -04:00
}
2022-07-01 16:19:13 -04:00
svcCred , _ , err = globalIAMSys . NewServiceAccount ( ctx , sites [ selfIdx ] . AccessKey , nil , newServiceAccountOpts {
2023-04-28 15:24:14 -04:00
accessKey : siteReplicatorSvcAcc ,
secretKey : secretKey ,
allowSiteReplicatorAccount : true ,
2021-11-30 16:16:37 -05:00
} )
if err != nil {
return madmin . ReplicateAddStatus { } , errSRServiceAccount ( fmt . Errorf ( "unable to create local service account: %w" , err ) )
}
case err == nil :
2022-07-01 16:19:13 -04:00
svcCred = sa . Credentials
2021-11-30 16:16:37 -05:00
secretKey = svcCred . SecretKey
default :
return madmin . ReplicateAddStatus { } , errSRBackendIssue ( err )
2021-10-06 19:36:31 -04:00
}
2021-12-27 02:10:34 -05:00
joinReq := madmin . SRPeerJoinReq {
2021-10-06 19:36:31 -04:00
SvcAcctAccessKey : svcCred . AccessKey ,
2021-11-30 16:16:37 -05:00
SvcAcctSecretKey : secretKey ,
2021-10-06 19:36:31 -04:00
Peers : make ( map [ string ] madmin . PeerInfo ) ,
}
2021-11-30 16:16:37 -05:00
for _ , v := range sites {
joinReq . Peers [ v . DeploymentID ] = madmin . PeerInfo {
2021-10-06 19:36:31 -04:00
Endpoint : v . Endpoint ,
Name : v . Name ,
2021-11-30 16:16:37 -05:00
DeploymentID : v . DeploymentID ,
2021-10-06 19:36:31 -04:00
}
}
addedCount := 0
2021-11-30 16:16:37 -05:00
var (
2021-12-14 17:09:57 -05:00
peerAddErr error
2021-11-30 16:16:37 -05:00
admClient * madmin . AdminClient
)
for _ , v := range sites {
if v . self {
2021-10-06 19:36:31 -04:00
continue
}
2021-11-30 16:16:37 -05:00
switch {
case currDeploymentIDsSet . Contains ( v . DeploymentID ) :
admClient , err = c . getAdminClient ( ctx , v . DeploymentID )
default :
admClient , err = getAdminClient ( v . Endpoint , v . AccessKey , v . SecretKey )
}
2021-10-06 19:36:31 -04:00
if err != nil {
peerAddErr = errSRPeerResp ( fmt . Errorf ( "unable to create admin client for %s: %w" , v . Name , err ) )
break
}
joinReq . SvcAcctParent = v . AccessKey
2021-12-27 02:10:34 -05:00
err = admClient . SRPeerJoin ( ctx , joinReq )
2021-10-06 19:36:31 -04:00
if err != nil {
peerAddErr = errSRPeerResp ( fmt . Errorf ( "unable to link with peer %s: %w" , v . Name , err ) )
break
}
addedCount ++
}
2021-12-14 17:09:57 -05:00
if peerAddErr != nil {
2021-10-06 19:36:31 -04:00
if addedCount == 0 {
return madmin . ReplicateAddStatus { } , peerAddErr
}
// In this case, it means at least one cluster was added
// successfully, we need to send a response to the client with
// some details - FIXME: the disks on this cluster would need to
// be cleaned to recover.
partial := madmin . ReplicateAddStatus {
Status : madmin . ReplicateAddStatusPartial ,
ErrDetail : peerAddErr . Error ( ) ,
}
2021-11-30 16:16:37 -05:00
2021-12-14 17:09:57 -05:00
return partial , nil
2021-10-06 19:36:31 -04:00
}
2021-12-14 17:09:57 -05:00
2021-10-06 19:36:31 -04:00
// Other than handling existing buckets, we can now save the cluster
// replication configuration state.
state := srState {
Name : sites [ selfIdx ] . Name ,
Peers : joinReq . Peers ,
ServiceAccountAccessKey : svcCred . AccessKey ,
}
2021-12-14 17:09:57 -05:00
if err = c . saveToDisk ( ctx , state ) ; err != nil {
2021-10-06 19:36:31 -04:00
return madmin . ReplicateAddStatus {
Status : madmin . ReplicateAddStatusPartial ,
ErrDetail : fmt . Sprintf ( "unable to save cluster-replication state on local: %v" , err ) ,
2021-12-14 17:09:57 -05:00
} , nil
2021-10-06 19:36:31 -04:00
}
result := madmin . ReplicateAddStatus {
Success : true ,
Status : madmin . ReplicateAddStatusSuccess ,
}
2021-12-14 17:09:57 -05:00
2022-01-19 23:02:24 -05:00
if err := c . syncToAllPeers ( ctx ) ; err != nil {
2021-12-14 17:09:57 -05:00
result . InitialSyncErrorMessage = err . Error ( )
2021-10-06 19:36:31 -04:00
}
2021-12-14 17:09:57 -05:00
return result , nil
2021-10-06 19:36:31 -04:00
}
2023-04-28 15:24:14 -04:00
// PeerJoinReq - internal API handler to respond to a peer cluster's request to join.
2022-01-05 05:44:08 -05:00
func ( c * SiteReplicationSys ) PeerJoinReq ( ctx context . Context , arg madmin . SRPeerJoinReq ) error {
2021-10-06 19:36:31 -04:00
var ourName string
for d , p := range arg . Peers {
2023-10-18 11:06:57 -04:00
if d == globalDeploymentID ( ) {
2021-10-06 19:36:31 -04:00
ourName = p . Name
break
}
}
if ourName == "" {
2021-12-14 17:09:57 -05:00
return errSRSelfNotFound
2021-10-06 19:36:31 -04:00
}
2021-11-30 16:16:37 -05:00
_ , _ , err := globalIAMSys . GetServiceAccount ( ctx , arg . SvcAcctAccessKey )
if err == errNoSuchServiceAccount {
2022-07-01 16:19:13 -04:00
_ , _ , err = globalIAMSys . NewServiceAccount ( ctx , arg . SvcAcctParent , nil , newServiceAccountOpts {
2023-04-28 15:24:14 -04:00
accessKey : arg . SvcAcctAccessKey ,
secretKey : arg . SvcAcctSecretKey ,
allowSiteReplicatorAccount : arg . SvcAcctAccessKey == siteReplicatorSvcAcc ,
2021-11-30 16:16:37 -05:00
} )
}
2021-10-06 19:36:31 -04:00
if err != nil {
return errSRServiceAccount ( fmt . Errorf ( "unable to create service account on %s: %v" , ourName , err ) )
}
state := srState {
Name : ourName ,
Peers : arg . Peers ,
ServiceAccountAccessKey : arg . SvcAcctAccessKey ,
}
2021-12-14 17:09:57 -05:00
if err = c . saveToDisk ( ctx , state ) ; err != nil {
2022-08-04 19:10:08 -04:00
return errSRBackendIssue ( fmt . Errorf ( "unable to save cluster-replication state to drive on %s: %v" , ourName , err ) )
2021-10-06 19:36:31 -04:00
}
2021-12-14 17:09:57 -05:00
return nil
2021-10-06 19:36:31 -04:00
}
// GetIDPSettings returns info about the configured identity provider. It is
// used to validate that all peers have the same IDP.
func ( c * SiteReplicationSys ) GetIDPSettings ( ctx context . Context ) madmin . IDPSettings {
2022-01-06 18:52:43 -05:00
s := madmin . IDPSettings { }
s . LDAP = madmin . LDAPSettings {
2023-02-24 21:37:22 -05:00
IsLDAPEnabled : globalIAMSys . LDAPConfig . Enabled ( ) ,
LDAPUserDNSearchBase : globalIAMSys . LDAPConfig . LDAP . UserDNSearchBaseDistName ,
LDAPUserDNSearchFilter : globalIAMSys . LDAPConfig . LDAP . UserDNSearchFilter ,
LDAPGroupSearchBase : globalIAMSys . LDAPConfig . LDAP . GroupSearchBaseDistName ,
LDAPGroupSearchFilter : globalIAMSys . LDAPConfig . LDAP . GroupSearchFilter ,
2021-10-06 19:36:31 -04:00
}
2023-02-26 00:01:37 -05:00
s . OpenID = globalIAMSys . OpenIDConfig . GetSettings ( )
2022-01-06 18:52:43 -05:00
if s . OpenID . Enabled {
s . OpenID . Region = globalSite . Region
}
return s
2021-10-06 19:36:31 -04:00
}
2021-12-14 17:09:57 -05:00
func ( c * SiteReplicationSys ) validateIDPSettings ( ctx context . Context , peers [ ] PeerSiteInfo ) ( bool , error ) {
2021-10-06 19:36:31 -04:00
s := make ( [ ] madmin . IDPSettings , 0 , len ( peers ) )
2021-11-30 16:16:37 -05:00
for _ , v := range peers {
if v . self {
2021-10-06 19:36:31 -04:00
s = append ( s , c . GetIDPSettings ( ctx ) )
continue
}
admClient , err := getAdminClient ( v . Endpoint , v . AccessKey , v . SecretKey )
if err != nil {
return false , errSRPeerResp ( fmt . Errorf ( "unable to create admin client for %s: %w" , v . Name , err ) )
}
2021-12-27 02:10:34 -05:00
is , err := admClient . SRPeerGetIDPSettings ( ctx )
2021-10-06 19:36:31 -04:00
if err != nil {
return false , errSRPeerResp ( fmt . Errorf ( "unable to fetch IDP settings from %s: %v" , v . Name , err ) )
}
s = append ( s , is )
}
for i := 1 ; i < len ( s ) ; i ++ {
2022-01-06 18:52:43 -05:00
if ! reflect . DeepEqual ( s [ i ] , s [ 0 ] ) {
2021-12-14 17:09:57 -05:00
return false , nil
2021-10-06 19:36:31 -04:00
}
}
2021-12-14 17:09:57 -05:00
return true , nil
2021-10-06 19:36:31 -04:00
}
2023-07-06 01:28:26 -04:00
// Netperf for site-replication net perf
func ( c * SiteReplicationSys ) Netperf ( ctx context . Context , duration time . Duration ) ( results madmin . SiteNetPerfResult , err error ) {
infos , err := globalSiteReplicationSys . GetClusterInfo ( ctx )
if err != nil {
return results , err
}
var wg sync . WaitGroup
var resultsMu sync . RWMutex
for _ , info := range infos . Sites {
info := info
// will call siteNetperf, means call others's adminAPISiteReplicationDevNull
2023-10-18 11:06:57 -04:00
if globalDeploymentID ( ) == info . DeploymentID {
2023-07-06 01:28:26 -04:00
wg . Add ( 1 )
2023-08-24 13:20:37 -04:00
go func ( ) {
2023-07-06 01:28:26 -04:00
defer wg . Done ( )
2023-08-24 13:20:37 -04:00
result := madmin . SiteNetPerfNodeResult { }
2023-07-06 01:28:26 -04:00
cli , err := globalSiteReplicationSys . getAdminClient ( ctx , info . DeploymentID )
if err != nil {
result . Error = err . Error ( )
2023-08-24 13:20:37 -04:00
} else {
result = siteNetperf ( ctx , duration )
result . Endpoint = cli . GetEndpointURL ( ) . String ( )
2023-07-06 01:28:26 -04:00
}
resultsMu . Lock ( )
results . NodeResults = append ( results . NodeResults , result )
resultsMu . Unlock ( )
2023-08-24 13:20:37 -04:00
return
2023-07-06 01:28:26 -04:00
} ( )
2023-08-24 13:20:37 -04:00
continue
}
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
ctx , cancel := context . WithTimeout ( ctx , duration + 10 * time . Second )
defer cancel ( )
result := perfNetRequest (
ctx ,
info . DeploymentID ,
adminPathPrefix + adminAPIVersionPrefix + adminAPISiteReplicationNetPerf ,
nil ,
)
resultsMu . Lock ( )
results . NodeResults = append ( results . NodeResults , result )
resultsMu . Unlock ( )
return
2023-07-06 01:28:26 -04:00
} ( )
}
wg . Wait ( )
return
}
2021-10-06 19:36:31 -04:00
// GetClusterInfo - returns site replication information.
func ( c * SiteReplicationSys ) GetClusterInfo ( ctx context . Context ) ( info madmin . SiteReplicationInfo , err error ) {
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return info , nil
}
info . Enabled = true
info . Name = c . state . Name
info . Sites = make ( [ ] madmin . PeerInfo , 0 , len ( c . state . Peers ) )
for _ , peer := range c . state . Peers {
info . Sites = append ( info . Sites , peer )
}
2023-06-12 16:22:07 -04:00
sort . Slice ( info . Sites , func ( i , j int ) bool {
2021-10-06 19:36:31 -04:00
return info . Sites [ i ] . Name < info . Sites [ j ] . Name
} )
info . ServiceAccountAccessKey = c . state . ServiceAccountAccessKey
return info , nil
}
2022-06-20 13:48:11 -04:00
const (
makeBucketWithVersion = "MakeBucketWithVersioning"
configureReplication = "ConfigureReplication"
deleteBucket = "DeleteBucket"
replicateIAMItem = "SRPeerReplicateIAMItem"
replicateBucketMetadata = "SRPeerReplicateBucketMeta"
)
2021-10-06 19:36:31 -04:00
// MakeBucketHook - called during a regular make bucket call when cluster
// replication is enabled. It is responsible for the creation of the same bucket
// on remote clusters, and creating replication rules on local and peer
// clusters.
2022-07-25 20:51:32 -04:00
func ( c * SiteReplicationSys ) MakeBucketHook ( ctx context . Context , bucket string , opts MakeBucketOptions ) error {
2021-10-06 19:36:31 -04:00
// At this point, the local bucket is created.
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
optsMap := make ( map [ string ] string )
if opts . LockEnabled {
2021-12-14 17:09:57 -05:00
optsMap [ "lockEnabled" ] = "true"
optsMap [ "versioningEnabled" ] = "true"
}
if opts . VersioningEnabled {
optsMap [ "versioningEnabled" ] = "true"
2021-10-06 19:36:31 -04:00
}
2022-04-27 07:44:07 -04:00
if opts . ForceCreate {
optsMap [ "forceCreate" ] = "true"
}
2022-07-25 20:51:32 -04:00
createdAt , _ := globalBucketMetadataSys . CreatedAt ( bucket )
2022-10-27 12:46:52 -04:00
optsMap [ "createdAt" ] = createdAt . UTC ( ) . Format ( time . RFC3339Nano )
2022-07-25 20:51:32 -04:00
opts . CreatedAt = createdAt
2021-10-06 19:36:31 -04:00
// Create bucket and enable versioning on all peers.
makeBucketConcErr := c . concDo (
func ( ) error {
2022-06-20 13:48:11 -04:00
return c . annotateErr ( makeBucketWithVersion , c . PeerBucketMakeWithVersioningHandler ( ctx , bucket , opts ) )
2021-10-06 19:36:31 -04:00
} ,
func ( deploymentID string , p madmin . PeerInfo ) error {
admClient , err := c . getAdminClient ( ctx , deploymentID )
if err != nil {
return err
}
2022-06-20 13:48:11 -04:00
return c . annotatePeerErr ( p . Name , makeBucketWithVersion , admClient . SRPeerBucketOps ( ctx , bucket , madmin . MakeWithVersioningBktOp , optsMap ) )
2021-10-06 19:36:31 -04:00
} ,
2022-06-20 13:48:11 -04:00
makeBucketWithVersion ,
2021-10-06 19:36:31 -04:00
)
2022-06-20 13:48:11 -04:00
// Create bucket remotes and add replication rules for the bucket on self and peers.
2021-10-06 19:36:31 -04:00
makeRemotesConcErr := c . concDo (
func ( ) error {
2022-06-20 13:48:11 -04:00
return c . annotateErr ( configureReplication , c . PeerBucketConfigureReplHandler ( ctx , bucket ) )
2021-10-06 19:36:31 -04:00
} ,
func ( deploymentID string , p madmin . PeerInfo ) error {
admClient , err := c . getAdminClient ( ctx , deploymentID )
if err != nil {
return err
}
2022-06-20 13:48:11 -04:00
return c . annotatePeerErr ( p . Name , configureReplication , admClient . SRPeerBucketOps ( ctx , bucket , madmin . ConfigureReplBktOp , nil ) )
2021-10-06 19:36:31 -04:00
} ,
2022-06-20 13:48:11 -04:00
configureReplication ,
2021-10-06 19:36:31 -04:00
)
2022-06-20 13:48:11 -04:00
if err := errors . Unwrap ( makeBucketConcErr ) ; err != nil {
return err
}
if err := errors . Unwrap ( makeRemotesConcErr ) ; err != nil {
2021-10-06 19:36:31 -04:00
return err
}
return nil
}
// DeleteBucketHook - called during a regular delete bucket call when cluster
// replication is enabled. It is responsible for the deletion of the same bucket
// on remote clusters.
func ( c * SiteReplicationSys ) DeleteBucketHook ( ctx context . Context , bucket string , forceDelete bool ) error {
// At this point, the local bucket is deleted.
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
op := madmin . DeleteBucketBktOp
if forceDelete {
op = madmin . ForceDeleteBucketBktOp
}
// Send bucket delete to other clusters.
2022-06-20 13:48:11 -04:00
cerr := c . concDo ( nil , func ( deploymentID string , p madmin . PeerInfo ) error {
2021-10-06 19:36:31 -04:00
admClient , err := c . getAdminClient ( ctx , deploymentID )
if err != nil {
return wrapSRErr ( err )
}
2022-06-20 13:48:11 -04:00
return c . annotatePeerErr ( p . Name , deleteBucket , admClient . SRPeerBucketOps ( ctx , bucket , op , nil ) )
} ,
deleteBucket ,
)
return errors . Unwrap ( cerr )
2021-10-06 19:36:31 -04:00
}
// PeerBucketMakeWithVersioningHandler - creates bucket and enables versioning.
2022-07-25 20:51:32 -04:00
func ( c * SiteReplicationSys ) PeerBucketMakeWithVersioningHandler ( ctx context . Context , bucket string , opts MakeBucketOptions ) error {
2021-10-06 19:36:31 -04:00
objAPI := newObjectLayerFn ( )
if objAPI == nil {
return errServerNotInitialized
}
2021-12-14 17:09:57 -05:00
2022-12-23 10:46:00 -05:00
err := objAPI . MakeBucket ( ctx , bucket , opts )
2021-10-06 19:36:31 -04:00
if err != nil {
// Check if this is a bucket exists error.
_ , ok1 := err . ( BucketExists )
_ , ok2 := err . ( BucketAlreadyExists )
if ! ok1 && ! ok2 {
2022-06-20 13:48:11 -04:00
return wrapSRErr ( c . annotateErr ( makeBucketWithVersion , err ) )
2021-10-06 19:36:31 -04:00
}
} else {
// Load updated bucket metadata into memory as new
// bucket was created.
globalNotificationSys . LoadBucketMetadata ( GlobalContext , bucket )
}
2021-12-14 17:09:57 -05:00
meta , err := globalBucketMetadataSys . Get ( bucket )
2021-10-06 19:36:31 -04:00
if err != nil {
2022-06-20 13:48:11 -04:00
return wrapSRErr ( c . annotateErr ( makeBucketWithVersion , err ) )
2021-10-06 19:36:31 -04:00
}
2021-12-14 17:09:57 -05:00
2022-07-25 20:51:32 -04:00
meta . SetCreatedAt ( opts . CreatedAt )
2021-12-14 17:09:57 -05:00
meta . VersioningConfigXML = enabledBucketVersioningConfig
if opts . LockEnabled {
meta . ObjectLockConfigXML = enabledBucketObjectLockConfig
2021-10-06 19:36:31 -04:00
}
2021-12-14 17:09:57 -05:00
if err := meta . Save ( context . Background ( ) , objAPI ) ; err != nil {
return wrapSRErr ( err )
}
globalBucketMetadataSys . Set ( bucket , meta )
// Load updated bucket metadata into memory as new metadata updated.
globalNotificationSys . LoadBucketMetadata ( GlobalContext , bucket )
2021-10-06 19:36:31 -04:00
return nil
}
// PeerBucketConfigureReplHandler - configures replication remote and
// replication rules to all other peers for the local bucket.
func ( c * SiteReplicationSys ) PeerBucketConfigureReplHandler ( ctx context . Context , bucket string ) error {
creds , err := c . getPeerCreds ( )
if err != nil {
return wrapSRErr ( err )
}
// The following function, creates a bucket remote and sets up a bucket
// replication rule for the given peer.
configurePeerFn := func ( d string , peer madmin . PeerInfo ) error {
2023-02-20 05:36:13 -05:00
// Create bucket replication rule to this peer.
// To add the bucket replication rule, we fetch the current
// server configuration, and convert it to minio-go's
// replication configuration type (by converting to xml and
// parsing it back), use minio-go's add rule function, and
// finally convert it back to the server type (again via xml).
// This is needed as there is no add-rule function in the server
// yet.
// Though we do not check if the rule already exists, this is
// not a problem as we are always using the same replication
// rule ID - if the rule already exists, it is just replaced.
replicationConfigS , _ , err := globalBucketMetadataSys . GetReplicationConfig ( ctx , bucket )
if err != nil {
_ , ok := err . ( BucketReplicationConfigNotFound )
if ! ok {
return err
}
}
var replicationConfig replication . Config
if replicationConfigS != nil {
replCfgSBytes , err := xml . Marshal ( replicationConfigS )
if err != nil {
return err
}
err = xml . Unmarshal ( replCfgSBytes , & replicationConfig )
if err != nil {
return err
}
}
var (
ruleID = fmt . Sprintf ( "site-repl-%s" , d )
hasRule bool
)
var ruleARN string
for _ , r := range replicationConfig . Rules {
if r . ID == ruleID {
hasRule = true
ruleARN = r . Destination . Bucket
}
}
2021-10-06 19:36:31 -04:00
ep , _ := url . Parse ( peer . Endpoint )
2023-02-20 05:36:13 -05:00
var targets [ ] madmin . BucketTarget
if targetsPtr , _ := globalBucketTargetSys . ListBucketTargets ( ctx , bucket ) ; targetsPtr != nil {
targets = targetsPtr . Targets
}
2021-10-06 19:36:31 -04:00
targetARN := ""
2023-09-25 18:50:52 -04:00
var updateTgt , updateBW bool
2023-02-20 05:36:13 -05:00
var targetToUpdate madmin . BucketTarget
2021-10-06 19:36:31 -04:00
for _ , target := range targets {
2023-02-20 05:36:13 -05:00
if target . Arn == ruleARN {
targetARN = ruleARN
2023-09-25 18:50:52 -04:00
updateBW = peer . DefaultBandwidth . Limit != 0 && target . BandwidthLimit == 0
if ( target . URL ( ) . String ( ) != peer . Endpoint ) || updateBW {
2023-02-20 05:36:13 -05:00
updateTgt = true
targetToUpdate = target
}
2021-10-06 19:36:31 -04:00
break
}
}
2023-02-20 05:36:13 -05:00
// replication config had a stale target ARN - update the endpoint
if updateTgt {
targetToUpdate . Endpoint = ep . Host
targetToUpdate . Secure = ep . Scheme == "https"
targetToUpdate . Credentials = & madmin . Credentials {
AccessKey : creds . AccessKey ,
SecretKey : creds . SecretKey ,
}
2023-03-24 17:41:23 -04:00
if ! peer . SyncState . Empty ( ) {
targetToUpdate . ReplicationSync = ( peer . SyncState == madmin . SyncEnabled )
}
2023-09-25 18:50:52 -04:00
if updateBW {
targetToUpdate . BandwidthLimit = int64 ( peer . DefaultBandwidth . Limit )
}
2023-02-20 05:36:13 -05:00
err := globalBucketTargetSys . SetTarget ( ctx , bucket , & targetToUpdate , true )
if err != nil {
return c . annotatePeerErr ( peer . Name , "Bucket target update error" , err )
}
targets , err := globalBucketTargetSys . ListBucketTargets ( ctx , bucket )
if err != nil {
return wrapSRErr ( err )
}
tgtBytes , err := json . Marshal ( & targets )
if err != nil {
return wrapSRErr ( err )
}
if _ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketTargetsFile , tgtBytes ) ; err != nil {
return wrapSRErr ( err )
}
}
// no replication rule for this peer or target ARN missing in bucket targets
2021-10-06 19:36:31 -04:00
if targetARN == "" {
bucketTarget := madmin . BucketTarget {
SourceBucket : bucket ,
Endpoint : ep . Host ,
Credentials : & madmin . Credentials {
AccessKey : creds . AccessKey ,
SecretKey : creds . SecretKey ,
} ,
TargetBucket : bucket ,
Secure : ep . Scheme == "https" ,
API : "s3v4" ,
Type : madmin . ReplicationService ,
Region : "" ,
2023-03-24 17:41:23 -04:00
ReplicationSync : peer . SyncState == madmin . SyncEnabled ,
2023-08-30 04:00:59 -04:00
DeploymentID : d ,
2023-09-25 18:50:52 -04:00
BandwidthLimit : int64 ( peer . DefaultBandwidth . Limit ) ,
2021-10-06 19:36:31 -04:00
}
2022-12-14 06:24:06 -05:00
var exists bool // true if ARN already exists
2023-02-20 05:36:13 -05:00
bucketTarget . Arn , exists = globalBucketTargetSys . getRemoteARN ( bucket , & bucketTarget , peer . DeploymentID )
2022-12-14 06:24:06 -05:00
if ! exists { // persist newly generated ARN to targets and metadata on disk
err := globalBucketTargetSys . SetTarget ( ctx , bucket , & bucketTarget , false )
if err != nil {
return c . annotatePeerErr ( peer . Name , "Bucket target creation error" , err )
}
targets , err := globalBucketTargetSys . ListBucketTargets ( ctx , bucket )
if err != nil {
return err
}
tgtBytes , err := json . Marshal ( & targets )
if err != nil {
return err
}
if _ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketTargetsFile , tgtBytes ) ; err != nil {
return err
}
2021-10-06 19:36:31 -04:00
}
targetARN = bucketTarget . Arn
}
2023-02-20 05:36:13 -05:00
opts := replication . Options {
// Set the ID so we can identify the rule as being
// created for site-replication and include the
// destination cluster's deployment ID.
ID : ruleID ,
2021-10-06 19:36:31 -04:00
2023-02-20 05:36:13 -05:00
// Use a helper to generate unique priority numbers.
Priority : fmt . Sprintf ( "%d" , getPriorityHelper ( replicationConfig ) ) ,
2021-10-06 19:36:31 -04:00
2023-02-20 05:36:13 -05:00
Op : replication . AddOption ,
RuleStatus : "enable" ,
DestBucket : targetARN ,
// Replicate everything!
ReplicateDeletes : "enable" ,
ReplicateDeleteMarkers : "enable" ,
ReplicaSync : "enable" ,
ExistingObjectReplicate : "enable" ,
2021-10-06 19:36:31 -04:00
}
2021-11-30 16:16:37 -05:00
switch {
case hasRule :
2022-05-26 20:57:23 -04:00
if ruleARN != opts . DestBucket {
// remove stale replication rule and replace rule with correct target ARN
if len ( replicationConfig . Rules ) > 1 {
err = replicationConfig . RemoveRule ( opts )
} else {
replicationConfig = replication . Config { }
}
if err == nil {
err = replicationConfig . AddRule ( opts )
}
} else {
err = replicationConfig . EditRule ( opts )
}
2021-11-30 16:16:37 -05:00
default :
err = replicationConfig . AddRule ( opts )
}
2021-10-06 19:36:31 -04:00
if err != nil {
2022-06-20 13:48:11 -04:00
return c . annotatePeerErr ( peer . Name , "Error adding bucket replication rule" , err )
2021-10-06 19:36:31 -04:00
}
2022-06-20 13:48:11 -04:00
2021-10-06 19:36:31 -04:00
// Now convert the configuration back to server's type so we can
// do some validation.
newReplCfgBytes , err := xml . Marshal ( replicationConfig )
if err != nil {
return err
}
newReplicationConfig , err := sreplication . ParseConfig ( bytes . NewReader ( newReplCfgBytes ) )
if err != nil {
return err
}
2022-05-26 20:57:23 -04:00
sameTarget , apiErr := validateReplicationDestination ( ctx , bucket , newReplicationConfig , true )
2021-10-06 19:36:31 -04:00
if apiErr != noError {
return fmt . Errorf ( "bucket replication config validation error: %#v" , apiErr )
}
err = newReplicationConfig . Validate ( bucket , sameTarget )
if err != nil {
return err
}
// Config looks good, so we save it.
replCfgData , err := xml . Marshal ( newReplicationConfig )
if err != nil {
return err
}
2022-06-20 13:48:11 -04:00
2022-06-28 21:09:20 -04:00
_ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketReplicationConfig , replCfgData )
2022-06-20 13:48:11 -04:00
return c . annotatePeerErr ( peer . Name , "Error updating replication configuration" , err )
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
c . RLock ( )
defer c . RUnlock ( )
2021-10-06 19:36:31 -04:00
errMap := make ( map [ string ] error , len ( c . state . Peers ) )
for d , peer := range c . state . Peers {
2023-10-18 11:06:57 -04:00
if d == globalDeploymentID ( ) {
2021-10-06 19:36:31 -04:00
continue
}
2022-06-20 13:48:11 -04:00
errMap [ d ] = configurePeerFn ( d , peer )
2021-10-06 19:36:31 -04:00
}
2022-06-20 13:48:11 -04:00
return c . toErrorFromErrMap ( errMap , configureReplication )
2021-10-06 19:36:31 -04:00
}
// PeerBucketDeleteHandler - deletes bucket on local in response to a delete
// bucket request from a peer.
2022-07-25 20:51:32 -04:00
func ( c * SiteReplicationSys ) PeerBucketDeleteHandler ( ctx context . Context , bucket string , opts DeleteBucketOptions ) error {
2021-10-06 19:36:31 -04:00
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return errSRNotEnabled
}
objAPI := newObjectLayerFn ( )
if objAPI == nil {
return errServerNotInitialized
}
2021-12-14 17:09:57 -05:00
if globalDNSConfig != nil {
if err := globalDNSConfig . Delete ( bucket ) ; err != nil {
return err
}
}
2022-07-25 20:51:32 -04:00
err := objAPI . DeleteBucket ( ctx , bucket , opts )
2021-10-06 19:36:31 -04:00
if err != nil {
2021-12-14 17:09:57 -05:00
if globalDNSConfig != nil {
if err2 := globalDNSConfig . Put ( bucket ) ; err2 != nil {
logger . LogIf ( ctx , fmt . Errorf ( "Unable to restore bucket DNS entry %w, please fix it manually" , err2 ) )
}
}
2021-10-06 19:36:31 -04:00
return err
}
globalNotificationSys . DeleteBucketMetadata ( ctx , bucket )
return nil
}
// IAMChangeHook - called when IAM items need to be replicated to peer clusters.
// This includes named policy creation, policy mapping changes and service
// account changes.
//
// All policies are replicated.
//
// Policy mappings are only replicated when they are for LDAP users or groups
// (as an external IDP is always assumed when SR is used). In the case of
// OpenID, such mappings are provided from the IDP directly and so are not
// applicable here.
//
2022-01-06 18:52:43 -05:00
// Service accounts are replicated as long as they are not meant for the root
// user.
2021-10-06 19:36:31 -04:00
//
// STS accounts are replicated, but only if the session token is verifiable
// using the local cluster's root credential.
func ( c * SiteReplicationSys ) IAMChangeHook ( ctx context . Context , item madmin . SRIAMItem ) error {
// The IAM item has already been applied to the local cluster at this
// point, and only needs to be updated on all remote peer clusters.
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
2022-06-20 13:48:11 -04:00
cerr := c . concDo ( nil , func ( d string , p madmin . PeerInfo ) error {
2021-10-06 19:36:31 -04:00
admClient , err := c . getAdminClient ( ctx , d )
if err != nil {
return wrapSRErr ( err )
}
2022-06-20 13:48:11 -04:00
return c . annotatePeerErr ( p . Name , replicateIAMItem , admClient . SRPeerReplicateIAMItem ( ctx , item ) )
} ,
replicateIAMItem ,
)
return errors . Unwrap ( cerr )
2021-10-06 19:36:31 -04:00
}
// PeerAddPolicyHandler - copies IAM policy to local. A nil policy argument,
// causes the named policy to be deleted.
2023-09-04 15:57:37 -04:00
func ( c * SiteReplicationSys ) PeerAddPolicyHandler ( ctx context . Context , policyName string , p * policy . Policy , updatedAt time . Time ) error {
2021-10-06 19:36:31 -04:00
var err error
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) {
if p , err := globalIAMSys . store . GetPolicyDoc ( policyName ) ; err == nil && p . UpdateDate . After ( updatedAt ) {
return nil
}
}
2021-10-06 19:36:31 -04:00
if p == nil {
2021-11-29 17:38:57 -05:00
err = globalIAMSys . DeletePolicy ( ctx , policyName , true )
2021-10-06 19:36:31 -04:00
} else {
2022-07-01 16:19:13 -04:00
_ , err = globalIAMSys . SetPolicy ( ctx , policyName , * p )
2021-10-06 19:36:31 -04:00
}
if err != nil {
return wrapSRErr ( err )
}
return nil
}
2022-01-06 18:52:43 -05:00
// PeerIAMUserChangeHandler - copies IAM user to local.
2022-07-01 16:19:13 -04:00
func ( c * SiteReplicationSys ) PeerIAMUserChangeHandler ( ctx context . Context , change * madmin . SRIAMUser , updatedAt time . Time ) error {
2022-01-06 18:52:43 -05:00
if change == nil {
return errSRInvalidRequest ( errInvalidArgument )
}
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) {
if ui , err := globalIAMSys . GetUserInfo ( ctx , change . AccessKey ) ; err == nil && ui . UpdatedAt . After ( updatedAt ) {
return nil
}
}
2022-01-06 18:52:43 -05:00
var err error
if change . IsDeleteReq {
err = globalIAMSys . DeleteUser ( ctx , change . AccessKey , true )
} else {
if change . UserReq == nil {
return errSRInvalidRequest ( errInvalidArgument )
}
2022-01-19 23:02:24 -05:00
userReq := * change . UserReq
if userReq . Status != "" && userReq . SecretKey == "" {
// Status is set without secretKey updates means we are
// only changing the account status.
2022-07-01 16:19:13 -04:00
_ , err = globalIAMSys . SetUserStatus ( ctx , change . AccessKey , userReq . Status )
2022-01-19 23:02:24 -05:00
} else {
2022-07-01 16:19:13 -04:00
_ , err = globalIAMSys . CreateUser ( ctx , change . AccessKey , userReq )
2022-01-19 23:02:24 -05:00
}
2022-01-06 18:52:43 -05:00
}
if err != nil {
return wrapSRErr ( err )
}
return nil
}
// PeerGroupInfoChangeHandler - copies group changes to local.
2022-07-01 16:19:13 -04:00
func ( c * SiteReplicationSys ) PeerGroupInfoChangeHandler ( ctx context . Context , change * madmin . SRGroupInfo , updatedAt time . Time ) error {
2022-01-06 18:52:43 -05:00
if change == nil {
return errSRInvalidRequest ( errInvalidArgument )
}
updReq := change . UpdateReq
var err error
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) {
if gd , err := globalIAMSys . GetGroupDescription ( updReq . Group ) ; err == nil && gd . UpdatedAt . After ( updatedAt ) {
return nil
}
}
2022-01-06 18:52:43 -05:00
if updReq . IsRemove {
2022-07-01 16:19:13 -04:00
_ , err = globalIAMSys . RemoveUsersFromGroup ( ctx , updReq . Group , updReq . Members )
2022-01-06 18:52:43 -05:00
} else {
2022-01-19 23:02:24 -05:00
if updReq . Status != "" && len ( updReq . Members ) == 0 {
2022-07-01 16:19:13 -04:00
_ , err = globalIAMSys . SetGroupStatus ( ctx , updReq . Group , updReq . Status == madmin . GroupEnabled )
2022-01-19 23:02:24 -05:00
} else {
2022-07-01 16:19:13 -04:00
_ , err = globalIAMSys . AddUsersToGroup ( ctx , updReq . Group , updReq . Members )
2022-08-09 18:17:43 -04:00
if err == nil && updReq . Status != madmin . GroupEnabled {
_ , err = globalIAMSys . SetGroupStatus ( ctx , updReq . Group , updReq . Status == madmin . GroupEnabled )
}
2022-01-19 23:02:24 -05:00
}
2022-01-06 18:52:43 -05:00
}
if err != nil {
return wrapSRErr ( err )
}
return nil
}
2021-10-06 19:36:31 -04:00
// PeerSvcAccChangeHandler - copies service-account change to local.
2022-07-01 16:19:13 -04:00
func ( c * SiteReplicationSys ) PeerSvcAccChangeHandler ( ctx context . Context , change * madmin . SRSvcAccChange , updatedAt time . Time ) error {
2021-12-08 14:50:15 -05:00
if change == nil {
2021-12-14 17:09:57 -05:00
return errSRInvalidRequest ( errInvalidArgument )
2021-12-08 14:50:15 -05:00
}
2021-10-06 19:36:31 -04:00
switch {
case change . Create != nil :
2023-09-04 15:57:37 -04:00
var sp * policy . Policy
2021-10-06 19:36:31 -04:00
var err error
if len ( change . Create . SessionPolicy ) > 0 {
2023-09-04 15:57:37 -04:00
sp , err = policy . ParseConfig ( bytes . NewReader ( change . Create . SessionPolicy ) )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
}
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) && change . Create . AccessKey != "" {
if sa , _ , err := globalIAMSys . getServiceAccount ( ctx , change . Create . AccessKey ) ; err == nil && sa . UpdatedAt . After ( updatedAt ) {
return nil
}
}
2021-10-06 19:36:31 -04:00
opts := newServiceAccountOpts {
accessKey : change . Create . AccessKey ,
secretKey : change . Create . SecretKey ,
sessionPolicy : sp ,
claims : change . Create . Claims ,
2023-05-17 20:05:36 -04:00
name : change . Create . Name ,
description : change . Create . Description ,
2023-02-27 13:10:22 -05:00
expiration : change . Create . Expiration ,
2021-10-06 19:36:31 -04:00
}
2022-07-01 16:19:13 -04:00
_ , _ , err = globalIAMSys . NewServiceAccount ( ctx , change . Create . Parent , change . Create . Groups , opts )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
case change . Update != nil :
2023-09-04 15:57:37 -04:00
var sp * policy . Policy
2021-10-06 19:36:31 -04:00
var err error
if len ( change . Update . SessionPolicy ) > 0 {
2023-09-04 15:57:37 -04:00
sp , err = policy . ParseConfig ( bytes . NewReader ( change . Update . SessionPolicy ) )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
}
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) {
if sa , _ , err := globalIAMSys . getServiceAccount ( ctx , change . Update . AccessKey ) ; err == nil && sa . UpdatedAt . After ( updatedAt ) {
return nil
}
}
2021-10-06 19:36:31 -04:00
opts := updateServiceAccountOpts {
secretKey : change . Update . SecretKey ,
status : change . Update . Status ,
2023-05-17 20:05:36 -04:00
name : change . Update . Name ,
description : change . Update . Description ,
2021-10-06 19:36:31 -04:00
sessionPolicy : sp ,
2023-02-27 13:10:22 -05:00
expiration : change . Update . Expiration ,
2021-10-06 19:36:31 -04:00
}
2022-07-01 16:19:13 -04:00
_ , err = globalIAMSys . UpdateServiceAccount ( ctx , change . Update . AccessKey , opts )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
case change . Delete != nil :
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) {
if sa , _ , err := globalIAMSys . getServiceAccount ( ctx , change . Delete . AccessKey ) ; err == nil && sa . UpdatedAt . After ( updatedAt ) {
return nil
}
}
if err := globalIAMSys . DeleteServiceAccount ( ctx , change . Delete . AccessKey , true ) ; err != nil {
2021-10-06 19:36:31 -04:00
return wrapSRErr ( err )
}
}
return nil
}
// PeerPolicyMappingHandler - copies policy mapping to local.
2022-07-01 16:19:13 -04:00
func ( c * SiteReplicationSys ) PeerPolicyMappingHandler ( ctx context . Context , mapping * madmin . SRPolicyMapping , updatedAt time . Time ) error {
2021-12-08 14:50:15 -05:00
if mapping == nil {
2021-12-14 17:09:57 -05:00
return errSRInvalidRequest ( errInvalidArgument )
2021-12-08 14:50:15 -05:00
}
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) {
mp , ok := globalIAMSys . store . GetMappedPolicy ( mapping . Policy , mapping . IsGroup )
if ok && mp . UpdatedAt . After ( updatedAt ) {
return nil
}
}
2022-08-23 14:11:45 -04:00
_ , err := globalIAMSys . PolicyDBSet ( ctx , mapping . UserOrGroup , mapping . Policy , IAMUserType ( mapping . UserType ) , mapping . IsGroup )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
// PeerSTSAccHandler - replicates STS credential locally.
2022-07-01 16:19:13 -04:00
func ( c * SiteReplicationSys ) PeerSTSAccHandler ( ctx context . Context , stsCred * madmin . SRSTSCredential , updatedAt time . Time ) error {
2021-12-08 14:50:15 -05:00
if stsCred == nil {
2021-12-14 17:09:57 -05:00
return errSRInvalidRequest ( errInvalidArgument )
2021-12-08 14:50:15 -05:00
}
2022-07-01 16:19:13 -04:00
// skip overwrite of local update if peer sent stale info
if ! updatedAt . IsZero ( ) {
2022-11-22 10:26:33 -05:00
if u , _ , err := globalIAMSys . getTempAccount ( ctx , stsCred . AccessKey ) ; err == nil {
if u . UpdatedAt . After ( updatedAt ) {
2022-07-01 16:19:13 -04:00
return nil
}
}
}
2021-12-08 14:50:15 -05:00
2021-10-06 19:36:31 -04:00
// Verify the session token of the stsCred
claims , err := auth . ExtractClaims ( stsCred . SessionToken , globalActiveCred . SecretKey )
if err != nil {
2022-06-20 13:48:11 -04:00
return fmt . Errorf ( "STS credential could not be verified: %w" , err )
2021-10-06 19:36:31 -04:00
}
mapClaims := claims . Map ( )
expiry , err := auth . ExpToInt64 ( mapClaims [ "exp" ] )
if err != nil {
2022-06-20 13:48:11 -04:00
return fmt . Errorf ( "Expiry claim was not found: %v: %w" , mapClaims , err )
2021-10-06 19:36:31 -04:00
}
cred := auth . Credentials {
AccessKey : stsCred . AccessKey ,
SecretKey : stsCred . SecretKey ,
Expiration : time . Unix ( expiry , 0 ) . UTC ( ) ,
SessionToken : stsCred . SessionToken ,
2022-01-06 18:52:43 -05:00
ParentUser : stsCred . ParentUser ,
2021-10-06 19:36:31 -04:00
Status : auth . AccountOn ,
2022-01-06 18:52:43 -05:00
}
// Extract the username and lookup DN and groups in LDAP.
ldapUser , isLDAPSTS := claims . Lookup ( ldapUserN )
2023-03-06 11:56:10 -05:00
if isLDAPSTS {
2022-01-06 18:52:43 -05:00
// Need to lookup the groups from LDAP.
2023-02-24 21:37:22 -05:00
_ , ldapGroups , err := globalIAMSys . LDAPConfig . LookupUserDN ( ldapUser )
2022-01-06 18:52:43 -05:00
if err != nil {
2022-06-20 13:48:11 -04:00
return fmt . Errorf ( "unable to query LDAP server for %s: %w" , ldapUser , err )
2022-01-06 18:52:43 -05:00
}
cred . Groups = ldapGroups
2021-10-06 19:36:31 -04:00
}
// Set these credentials to IAM.
2022-07-01 16:19:13 -04:00
if _ , err := globalIAMSys . SetTempUser ( ctx , cred . AccessKey , cred , stsCred . ParentPolicyMapping ) ; err != nil {
2022-06-20 13:48:11 -04:00
return fmt . Errorf ( "unable to save STS credential and/or parent policy mapping: %w" , err )
2021-10-06 19:36:31 -04:00
}
return nil
}
// BucketMetaHook - called when bucket meta changes happen and need to be
// replicated to peer clusters.
func ( c * SiteReplicationSys ) BucketMetaHook ( ctx context . Context , item madmin . SRBucketMeta ) error {
// The change has already been applied to the local cluster at this
// point, and only needs to be updated on all remote peer clusters.
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
2022-06-20 13:48:11 -04:00
cerr := c . concDo ( nil , func ( d string , p madmin . PeerInfo ) error {
2021-10-06 19:36:31 -04:00
admClient , err := c . getAdminClient ( ctx , d )
if err != nil {
return wrapSRErr ( err )
}
2022-06-20 13:48:11 -04:00
return c . annotatePeerErr ( p . Name , replicateBucketMetadata , admClient . SRPeerReplicateBucketMeta ( ctx , item ) )
} ,
replicateBucketMetadata ,
)
return errors . Unwrap ( cerr )
2021-10-06 19:36:31 -04:00
}
2022-05-07 21:39:40 -04:00
// PeerBucketVersioningHandler - updates versioning config to local cluster.
2022-06-28 21:09:20 -04:00
func ( c * SiteReplicationSys ) PeerBucketVersioningHandler ( ctx context . Context , bucket string , versioning * string , updatedAt time . Time ) error {
2022-05-07 21:39:40 -04:00
if versioning != nil {
2022-06-28 21:09:20 -04:00
// skip overwrite if local update is newer than peer update.
if ! updatedAt . IsZero ( ) {
if _ , updateTm , err := globalBucketMetadataSys . GetVersioningConfig ( bucket ) ; err == nil && updateTm . After ( updatedAt ) {
return nil
}
}
2022-05-07 21:39:40 -04:00
configData , err := base64 . StdEncoding . DecodeString ( * versioning )
if err != nil {
return wrapSRErr ( err )
}
2022-06-28 21:09:20 -04:00
_ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketVersioningConfig , configData )
2022-05-07 21:39:40 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
return nil
}
2023-08-25 10:59:16 -04:00
// PeerBucketMetadataUpdateHandler - merges the bucket metadata, save and ping other nodes
func ( c * SiteReplicationSys ) PeerBucketMetadataUpdateHandler ( ctx context . Context , item madmin . SRBucketMeta ) error {
objectAPI := newObjectLayerFn ( )
if objectAPI == nil {
return errSRObjectLayerNotReady
}
if item . Bucket == "" || item . UpdatedAt . IsZero ( ) {
return wrapSRErr ( errInvalidArgument )
}
meta , err := readBucketMetadata ( ctx , objectAPI , item . Bucket )
if err != nil {
return wrapSRErr ( err )
}
if meta . Created . After ( item . UpdatedAt ) {
return nil
}
if item . Policy != nil {
meta . PolicyConfigJSON = item . Policy
meta . PolicyConfigUpdatedAt = item . UpdatedAt
}
if item . Versioning != nil {
configData , err := base64 . StdEncoding . DecodeString ( * item . Versioning )
if err != nil {
return wrapSRErr ( err )
}
meta . VersioningConfigXML = configData
meta . VersioningConfigUpdatedAt = item . UpdatedAt
}
if item . Tags != nil {
configData , err := base64 . StdEncoding . DecodeString ( * item . Tags )
if err != nil {
return wrapSRErr ( err )
}
meta . TaggingConfigXML = configData
meta . TaggingConfigUpdatedAt = item . UpdatedAt
}
if item . ObjectLockConfig != nil {
configData , err := base64 . StdEncoding . DecodeString ( * item . ObjectLockConfig )
if err != nil {
return wrapSRErr ( err )
}
meta . ObjectLockConfigXML = configData
meta . ObjectLockConfigUpdatedAt = item . UpdatedAt
}
if item . SSEConfig != nil {
configData , err := base64 . StdEncoding . DecodeString ( * item . SSEConfig )
if err != nil {
return wrapSRErr ( err )
}
meta . EncryptionConfigXML = configData
meta . EncryptionConfigUpdatedAt = item . UpdatedAt
}
if item . Quota != nil {
meta . QuotaConfigJSON = item . Quota
meta . QuotaConfigUpdatedAt = item . UpdatedAt
}
return globalBucketMetadataSys . save ( ctx , meta )
}
2021-10-06 19:36:31 -04:00
// PeerBucketPolicyHandler - copies/deletes policy to local cluster.
2023-09-04 15:57:37 -04:00
func ( c * SiteReplicationSys ) PeerBucketPolicyHandler ( ctx context . Context , bucket string , policy * policy . BucketPolicy , updatedAt time . Time ) error {
2022-06-28 21:09:20 -04:00
// skip overwrite if local update is newer than peer update.
if ! updatedAt . IsZero ( ) {
if _ , updateTm , err := globalBucketMetadataSys . GetPolicyConfig ( bucket ) ; err == nil && updateTm . After ( updatedAt ) {
return nil
}
}
2021-10-06 19:36:31 -04:00
if policy != nil {
configData , err := json . Marshal ( policy )
if err != nil {
return wrapSRErr ( err )
}
2022-06-28 21:09:20 -04:00
_ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketPolicyConfig , configData )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
// Delete the bucket policy
2022-10-19 20:55:09 -04:00
_ , err := globalBucketMetadataSys . Delete ( ctx , bucket , bucketPolicyConfig )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
// PeerBucketTaggingHandler - copies/deletes tags to local cluster.
2022-06-28 21:09:20 -04:00
func ( c * SiteReplicationSys ) PeerBucketTaggingHandler ( ctx context . Context , bucket string , tags * string , updatedAt time . Time ) error {
// skip overwrite if local update is newer than peer update.
if ! updatedAt . IsZero ( ) {
if _ , updateTm , err := globalBucketMetadataSys . GetTaggingConfig ( bucket ) ; err == nil && updateTm . After ( updatedAt ) {
return nil
}
}
2021-10-06 19:36:31 -04:00
if tags != nil {
configData , err := base64 . StdEncoding . DecodeString ( * tags )
if err != nil {
return wrapSRErr ( err )
}
2022-06-28 21:09:20 -04:00
_ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketTaggingConfig , configData )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
// Delete the tags
2022-10-19 20:55:09 -04:00
_ , err := globalBucketMetadataSys . Delete ( ctx , bucket , bucketTaggingConfig )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
// PeerBucketObjectLockConfigHandler - sets object lock on local bucket.
2022-06-28 21:09:20 -04:00
func ( c * SiteReplicationSys ) PeerBucketObjectLockConfigHandler ( ctx context . Context , bucket string , objectLockData * string , updatedAt time . Time ) error {
2021-10-06 19:36:31 -04:00
if objectLockData != nil {
2022-06-28 21:09:20 -04:00
// skip overwrite if local update is newer than peer update.
if ! updatedAt . IsZero ( ) {
if _ , updateTm , err := globalBucketMetadataSys . GetObjectLockConfig ( bucket ) ; err == nil && updateTm . After ( updatedAt ) {
return nil
}
}
2021-10-06 19:36:31 -04:00
configData , err := base64 . StdEncoding . DecodeString ( * objectLockData )
if err != nil {
return wrapSRErr ( err )
}
2022-06-28 21:09:20 -04:00
_ , err = globalBucketMetadataSys . Update ( ctx , bucket , objectLockConfig , configData )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
return nil
}
// PeerBucketSSEConfigHandler - copies/deletes SSE config to local cluster.
2022-06-28 21:09:20 -04:00
func ( c * SiteReplicationSys ) PeerBucketSSEConfigHandler ( ctx context . Context , bucket string , sseConfig * string , updatedAt time . Time ) error {
// skip overwrite if local update is newer than peer update.
if ! updatedAt . IsZero ( ) {
if _ , updateTm , err := globalBucketMetadataSys . GetSSEConfig ( bucket ) ; err == nil && updateTm . After ( updatedAt ) {
return nil
}
}
2021-10-06 19:36:31 -04:00
if sseConfig != nil {
configData , err := base64 . StdEncoding . DecodeString ( * sseConfig )
if err != nil {
return wrapSRErr ( err )
}
2022-06-28 21:09:20 -04:00
_ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketSSEConfig , configData )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
// Delete sse config
2022-10-19 20:55:09 -04:00
_ , err := globalBucketMetadataSys . Delete ( ctx , bucket , bucketSSEConfig )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
2022-01-19 23:02:24 -05:00
// PeerBucketQuotaConfigHandler - copies/deletes policy to local cluster.
2022-06-28 21:09:20 -04:00
func ( c * SiteReplicationSys ) PeerBucketQuotaConfigHandler ( ctx context . Context , bucket string , quota * madmin . BucketQuota , updatedAt time . Time ) error {
// skip overwrite if local update is newer than peer update.
if ! updatedAt . IsZero ( ) {
if _ , updateTm , err := globalBucketMetadataSys . GetQuotaConfig ( ctx , bucket ) ; err == nil && updateTm . After ( updatedAt ) {
return nil
}
}
2022-01-19 23:02:24 -05:00
if quota != nil {
quotaData , err := json . Marshal ( quota )
if err != nil {
return wrapSRErr ( err )
}
2022-06-28 21:09:20 -04:00
if _ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketQuotaConfigFile , quotaData ) ; err != nil {
2022-01-19 23:02:24 -05:00
return wrapSRErr ( err )
}
return nil
}
// Delete the bucket policy
2022-10-19 20:55:09 -04:00
_ , err := globalBucketMetadataSys . Delete ( ctx , bucket , bucketQuotaConfigFile )
2022-01-19 23:02:24 -05:00
if err != nil {
return wrapSRErr ( err )
}
return nil
}
2021-10-06 19:36:31 -04:00
// getAdminClient - NOTE: ensure to take at least a read lock on SiteReplicationSys
// before calling this.
func ( c * SiteReplicationSys ) getAdminClient ( ctx context . Context , deploymentID string ) ( * madmin . AdminClient , error ) {
creds , err := c . getPeerCreds ( )
if err != nil {
return nil , err
}
peer , ok := c . state . Peers [ deploymentID ]
if ! ok {
return nil , errSRPeerNotFound
}
return getAdminClient ( peer . Endpoint , creds . AccessKey , creds . SecretKey )
}
2022-01-21 11:48:21 -05:00
// getAdminClientWithEndpoint - NOTE: ensure to take at least a read lock on SiteReplicationSys
// before calling this.
func ( c * SiteReplicationSys ) getAdminClientWithEndpoint ( ctx context . Context , deploymentID , endpoint string ) ( * madmin . AdminClient , error ) {
creds , err := c . getPeerCreds ( )
if err != nil {
return nil , err
}
if _ , ok := c . state . Peers [ deploymentID ] ; ! ok {
return nil , errSRPeerNotFound
}
return getAdminClient ( endpoint , creds . AccessKey , creds . SecretKey )
}
2021-10-06 19:36:31 -04:00
func ( c * SiteReplicationSys ) getPeerCreds ( ) ( * auth . Credentials , error ) {
2022-07-01 16:19:13 -04:00
u , ok := globalIAMSys . store . GetUser ( c . state . ServiceAccountAccessKey )
2021-10-06 19:36:31 -04:00
if ! ok {
2021-12-14 17:09:57 -05:00
return nil , errors . New ( "site replication service account not found" )
2021-10-06 19:36:31 -04:00
}
2022-07-01 16:19:13 -04:00
return & u . Credentials , nil
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
// listBuckets returns a consistent common view of latest unique buckets across
// sites, this is used for replication.
func ( c * SiteReplicationSys ) listBuckets ( ctx context . Context ) ( [ ] BucketInfo , error ) {
2021-10-06 19:36:31 -04:00
// If local has buckets, enable versioning on them, create them on peers
// and setup replication rules.
objAPI := newObjectLayerFn ( )
if objAPI == nil {
2022-01-19 23:02:24 -05:00
return nil , errSRObjectLayerNotReady
2021-10-06 19:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
return objAPI . ListBuckets ( ctx , BucketOptions { Deleted : true } )
2022-01-19 23:02:24 -05:00
}
// syncToAllPeers is used for syncing local data to all remote peers, it is
// called once during initial "AddPeerClusters" request.
func ( c * SiteReplicationSys ) syncToAllPeers ( ctx context . Context ) error {
2023-02-08 00:44:42 -05:00
objAPI := newObjectLayerFn ( )
if objAPI == nil {
return errSRObjectLayerNotReady
}
buckets , err := objAPI . ListBuckets ( ctx , BucketOptions { } )
2021-10-06 19:36:31 -04:00
if err != nil {
2022-01-19 23:02:24 -05:00
return err
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
2021-10-06 19:36:31 -04:00
for _ , bucketInfo := range buckets {
bucket := bucketInfo . Name
2023-02-08 00:44:42 -05:00
meta , err := globalBucketMetadataSys . GetConfigFromDisk ( ctx , bucket )
if err != nil && ! errors . Is ( err , errConfigNotFound ) {
return errSRBackendIssue ( err )
2021-10-06 19:36:31 -04:00
}
2023-05-22 15:05:14 -04:00
opts := MakeBucketOptions {
LockEnabled : meta . ObjectLocking ( ) ,
CreatedAt : bucketInfo . Created . UTC ( ) ,
2021-10-06 19:36:31 -04:00
}
// Now call the MakeBucketHook on existing bucket - this will
// create buckets and replication rules on peer clusters.
2023-02-08 00:44:42 -05:00
if err = c . MakeBucketHook ( ctx , bucket , opts ) ; err != nil {
2021-10-06 19:36:31 -04:00
return errSRBucketConfigError ( err )
}
// Replicate bucket policy if present.
2023-02-08 00:44:42 -05:00
policyJSON , tm := meta . PolicyConfigJSON , meta . PolicyConfigUpdatedAt
if len ( policyJSON ) > 0 {
2021-10-06 19:36:31 -04:00
err = c . BucketMetaHook ( ctx , madmin . SRBucketMeta {
2022-06-28 21:09:20 -04:00
Type : madmin . SRBucketMetaTypePolicy ,
Bucket : bucket ,
Policy : policyJSON ,
UpdatedAt : tm ,
2021-10-06 19:36:31 -04:00
} )
if err != nil {
return errSRBucketMetaError ( err )
}
}
// Replicate bucket tags if present.
2023-02-08 00:44:42 -05:00
tagCfg , tm := meta . TaggingConfigXML , meta . TaggingConfigUpdatedAt
if len ( tagCfg ) > 0 {
2021-10-06 19:36:31 -04:00
tagCfgStr := base64 . StdEncoding . EncodeToString ( tagCfg )
err = c . BucketMetaHook ( ctx , madmin . SRBucketMeta {
2022-06-28 21:09:20 -04:00
Type : madmin . SRBucketMetaTypeTags ,
Bucket : bucket ,
Tags : & tagCfgStr ,
UpdatedAt : tm ,
2021-10-06 19:36:31 -04:00
} )
if err != nil {
return errSRBucketMetaError ( err )
}
}
// Replicate object-lock config if present.
2023-02-08 00:44:42 -05:00
objLockCfgData , tm := meta . ObjectLockConfigXML , meta . ObjectLockConfigUpdatedAt
if len ( objLockCfgData ) > 0 {
2021-10-06 19:36:31 -04:00
objLockStr := base64 . StdEncoding . EncodeToString ( objLockCfgData )
err = c . BucketMetaHook ( ctx , madmin . SRBucketMeta {
2022-06-28 21:09:20 -04:00
Type : madmin . SRBucketMetaTypeObjectLockConfig ,
Bucket : bucket ,
Tags : & objLockStr ,
UpdatedAt : tm ,
2021-10-06 19:36:31 -04:00
} )
if err != nil {
return errSRBucketMetaError ( err )
}
}
// Replicate existing bucket bucket encryption settings
2023-02-08 00:44:42 -05:00
sseConfigData , tm := meta . EncryptionConfigXML , meta . EncryptionConfigUpdatedAt
if len ( sseConfigData ) > 0 {
2021-10-06 19:36:31 -04:00
sseConfigStr := base64 . StdEncoding . EncodeToString ( sseConfigData )
err = c . BucketMetaHook ( ctx , madmin . SRBucketMeta {
Type : madmin . SRBucketMetaTypeSSEConfig ,
Bucket : bucket ,
SSEConfig : & sseConfigStr ,
2022-06-28 21:09:20 -04:00
UpdatedAt : tm ,
2021-10-06 19:36:31 -04:00
} )
if err != nil {
return errSRBucketMetaError ( err )
}
}
2022-01-19 23:02:24 -05:00
2023-02-08 00:44:42 -05:00
quotaConfigJSON , tm := meta . QuotaConfigJSON , meta . QuotaConfigUpdatedAt
if len ( quotaConfigJSON ) > 0 {
2022-01-19 23:02:24 -05:00
err = c . BucketMetaHook ( ctx , madmin . SRBucketMeta {
2022-06-28 21:09:20 -04:00
Type : madmin . SRBucketMetaTypeQuotaConfig ,
Bucket : bucket ,
Quota : quotaConfigJSON ,
UpdatedAt : tm ,
2022-01-19 23:02:24 -05:00
} )
if err != nil {
return errSRBucketMetaError ( err )
}
}
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
// Order matters from now on how the information is
// synced to remote sites.
// Policies should be synced first.
2021-10-06 19:36:31 -04:00
{
// Replicate IAM policies on local to all peers.
2022-07-01 16:19:13 -04:00
allPolicyDocs , err := globalIAMSys . ListPolicyDocs ( ctx , "" )
2021-10-06 19:36:31 -04:00
if err != nil {
return errSRBackendIssue ( err )
}
2022-07-01 16:19:13 -04:00
for pname , pdoc := range allPolicyDocs {
policyJSON , err := json . Marshal ( pdoc . Policy )
2021-10-06 19:36:31 -04:00
if err != nil {
return wrapSRErr ( err )
}
err = c . IAMChangeHook ( ctx , madmin . SRIAMItem {
2022-07-01 16:19:13 -04:00
Type : madmin . SRIAMItemPolicy ,
Name : pname ,
Policy : policyJSON ,
UpdatedAt : pdoc . UpdateDate ,
2021-10-06 19:36:31 -04:00
} )
if err != nil {
return errSRIAMError ( err )
}
}
}
2022-01-19 23:02:24 -05:00
// Next should be userAccounts those are local users, OIDC and LDAP will not
// may not have any local users.
2021-10-06 19:36:31 -04:00
{
2022-07-01 16:19:13 -04:00
userAccounts := make ( map [ string ] UserIdentity )
2022-01-19 23:02:24 -05:00
err := globalIAMSys . store . loadUsers ( ctx , regUser , userAccounts )
if err != nil {
return errSRBackendIssue ( err )
2021-10-06 19:36:31 -04:00
}
2022-01-19 23:02:24 -05:00
for _ , acc := range userAccounts {
if err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemIAMUser ,
IAMUser : & madmin . SRIAMUser {
2022-07-01 16:19:13 -04:00
AccessKey : acc . Credentials . AccessKey ,
2022-01-19 23:02:24 -05:00
IsDeleteReq : false ,
UserReq : & madmin . AddOrUpdateUserReq {
2022-07-01 16:19:13 -04:00
SecretKey : acc . Credentials . SecretKey ,
Status : madmin . AccountStatus ( acc . Credentials . Status ) ,
2022-01-19 23:02:24 -05:00
} ,
2021-10-06 19:36:31 -04:00
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : acc . UpdatedAt ,
2022-01-19 23:02:24 -05:00
} ) ; err != nil {
2021-10-06 19:36:31 -04:00
return errSRIAMError ( err )
}
}
2022-01-19 23:02:24 -05:00
}
2021-10-06 19:36:31 -04:00
2022-01-19 23:02:24 -05:00
// Next should be Groups for some of these users, LDAP might have some Group
// DNs here
{
groups := make ( map [ string ] GroupInfo )
err := globalIAMSys . store . loadGroups ( ctx , groups )
if err != nil {
return errSRBackendIssue ( err )
}
for gname , group := range groups {
if err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemGroupInfo ,
GroupInfo : & madmin . SRGroupInfo {
UpdateReq : madmin . GroupAddRemove {
Group : gname ,
Members : group . Members ,
Status : madmin . GroupStatus ( group . Status ) ,
IsRemove : false ,
} ,
2021-10-06 19:36:31 -04:00
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : group . UpdatedAt ,
2022-01-19 23:02:24 -05:00
} ) ; err != nil {
2021-10-06 19:36:31 -04:00
return errSRIAMError ( err )
}
}
}
2022-08-23 14:11:45 -04:00
// Followed by group policy mapping
{
// Replicate policy mappings on local to all peers.
groupPolicyMap := make ( map [ string ] MappedPolicy )
errG := globalIAMSys . store . loadMappedPolicies ( ctx , unknownIAMUserType , true , groupPolicyMap )
if errG != nil {
return errSRBackendIssue ( errG )
}
for group , mp := range groupPolicyMap {
err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemPolicyMapping ,
PolicyMapping : & madmin . SRPolicyMapping {
UserOrGroup : group ,
UserType : - 1 ,
IsGroup : true ,
Policy : mp . Policies ,
} ,
UpdatedAt : mp . UpdatedAt ,
} )
if err != nil {
return errSRIAMError ( err )
}
}
}
2022-01-19 23:02:24 -05:00
// Service accounts are the static accounts that should be synced with
// valid claims.
2021-10-06 19:36:31 -04:00
{
2022-07-01 16:19:13 -04:00
serviceAccounts := make ( map [ string ] UserIdentity )
2021-10-06 19:36:31 -04:00
err := globalIAMSys . store . loadUsers ( ctx , svcUser , serviceAccounts )
if err != nil {
return errSRBackendIssue ( err )
}
2022-01-19 23:02:24 -05:00
2021-10-06 19:36:31 -04:00
for user , acc := range serviceAccounts {
2022-01-06 18:52:43 -05:00
if user == siteReplicatorSvcAcc {
// skip the site replicate svc account as it is
// already replicated.
continue
}
2022-01-19 23:02:24 -05:00
2022-07-01 16:19:13 -04:00
claims , err := globalIAMSys . GetClaimsForSvcAcc ( ctx , acc . Credentials . AccessKey )
2021-10-06 19:36:31 -04:00
if err != nil {
return errSRBackendIssue ( err )
}
2022-01-19 23:02:24 -05:00
2022-07-01 16:19:13 -04:00
_ , policy , err := globalIAMSys . GetServiceAccount ( ctx , acc . Credentials . AccessKey )
2021-10-06 19:36:31 -04:00
if err != nil {
return errSRBackendIssue ( err )
}
2022-01-19 23:02:24 -05:00
2021-10-06 19:36:31 -04:00
var policyJSON [ ] byte
if policy != nil {
policyJSON , err = json . Marshal ( policy )
if err != nil {
return wrapSRErr ( err )
}
}
2022-01-19 23:02:24 -05:00
2021-10-06 19:36:31 -04:00
err = c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemSvcAcc ,
SvcAccChange : & madmin . SRSvcAccChange {
Create : & madmin . SRSvcAccCreate {
2022-07-01 16:19:13 -04:00
Parent : acc . Credentials . ParentUser ,
2021-10-06 19:36:31 -04:00
AccessKey : user ,
2022-07-01 16:19:13 -04:00
SecretKey : acc . Credentials . SecretKey ,
Groups : acc . Credentials . Groups ,
2021-10-06 19:36:31 -04:00
Claims : claims ,
SessionPolicy : json . RawMessage ( policyJSON ) ,
2022-07-01 16:19:13 -04:00
Status : acc . Credentials . Status ,
2023-05-17 20:05:36 -04:00
Name : acc . Credentials . Name ,
Description : acc . Credentials . Description ,
2023-02-27 13:10:22 -05:00
Expiration : & acc . Credentials . Expiration ,
2021-10-06 19:36:31 -04:00
} ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : acc . UpdatedAt ,
2021-10-06 19:36:31 -04:00
} )
if err != nil {
return errSRIAMError ( err )
}
}
}
2022-01-19 23:02:24 -05:00
// Followed by policy mapping for the userAccounts we previously synced.
{
// Replicate policy mappings on local to all peers.
userPolicyMap := make ( map [ string ] MappedPolicy )
errU := globalIAMSys . store . loadMappedPolicies ( ctx , regUser , false , userPolicyMap )
if errU != nil {
return errSRBackendIssue ( errU )
}
for user , mp := range userPolicyMap {
err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemPolicyMapping ,
PolicyMapping : & madmin . SRPolicyMapping {
UserOrGroup : user ,
2022-08-23 14:11:45 -04:00
UserType : int ( regUser ) ,
2022-01-19 23:02:24 -05:00
IsGroup : false ,
Policy : mp . Policies ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : mp . UpdatedAt ,
2022-01-19 23:02:24 -05:00
} )
if err != nil {
return errSRIAMError ( err )
}
}
}
// and finally followed by policy mappings for for STS users.
{
// Replicate policy mappings on local to all peers.
2022-08-23 14:11:45 -04:00
stsPolicyMap := make ( map [ string ] MappedPolicy )
errU := globalIAMSys . store . loadMappedPolicies ( ctx , stsUser , false , stsPolicyMap )
2022-01-19 23:02:24 -05:00
if errU != nil {
return errSRBackendIssue ( errU )
}
2022-08-23 14:11:45 -04:00
for user , mp := range stsPolicyMap {
2022-01-19 23:02:24 -05:00
err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemPolicyMapping ,
PolicyMapping : & madmin . SRPolicyMapping {
UserOrGroup : user ,
2022-08-23 14:11:45 -04:00
UserType : int ( stsUser ) ,
2022-01-19 23:02:24 -05:00
IsGroup : false ,
Policy : mp . Policies ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : mp . UpdatedAt ,
2022-01-19 23:02:24 -05:00
} )
if err != nil {
return errSRIAMError ( err )
}
}
}
2021-12-14 17:09:57 -05:00
return nil
2021-10-06 19:36:31 -04:00
}
// Concurrency helpers
type concErr struct {
errMap map [ string ] error
summaryErr error
}
func ( c concErr ) Error ( ) string {
2022-06-20 13:48:11 -04:00
if c . summaryErr != nil {
return c . summaryErr . Error ( )
}
return "<nil>"
2021-10-06 19:36:31 -04:00
}
2022-06-20 13:48:11 -04:00
func ( c concErr ) Unwrap ( ) error {
return c . summaryErr
2021-10-06 19:36:31 -04:00
}
2022-06-20 13:48:11 -04:00
func ( c * SiteReplicationSys ) toErrorFromErrMap ( errMap map [ string ] error , actionName string ) error {
2021-10-06 19:36:31 -04:00
if len ( errMap ) == 0 {
return nil
}
2022-06-20 13:48:11 -04:00
var success int
2021-10-06 19:36:31 -04:00
msgs := [ ] string { }
for d , err := range errMap {
name := c . state . Peers [ d ] . Name
2022-06-20 13:48:11 -04:00
if err == nil {
msgs = append ( msgs , fmt . Sprintf ( "'%s' on site %s (%s): succeeded" , actionName , name , d ) )
success ++
} else {
msgs = append ( msgs , fmt . Sprintf ( "'%s' on site %s (%s): failed(%v)" , actionName , name , d , err ) )
}
2021-10-06 19:36:31 -04:00
}
2022-06-20 13:48:11 -04:00
if success == len ( errMap ) {
return nil
}
return fmt . Errorf ( "Site replication error(s): \n%s" , strings . Join ( msgs , "\n" ) )
2021-10-06 19:36:31 -04:00
}
2022-06-20 13:48:11 -04:00
func ( c * SiteReplicationSys ) newConcErr ( errMap map [ string ] error , actionName string ) error {
2021-10-06 19:36:31 -04:00
return concErr {
errMap : errMap ,
2022-06-20 13:48:11 -04:00
summaryErr : c . toErrorFromErrMap ( errMap , actionName ) ,
2021-10-06 19:36:31 -04:00
}
}
// concDo calls actions concurrently. selfActionFn is run for the current
// cluster and peerActionFn is run for each peer replication cluster.
2022-06-20 13:48:11 -04:00
func ( c * SiteReplicationSys ) concDo ( selfActionFn func ( ) error , peerActionFn func ( deploymentID string , p madmin . PeerInfo ) error , actionName string ) error {
2021-10-06 19:36:31 -04:00
depIDs := make ( [ ] string , 0 , len ( c . state . Peers ) )
for d := range c . state . Peers {
depIDs = append ( depIDs , d )
}
errs := make ( [ ] error , len ( c . state . Peers ) )
var wg sync . WaitGroup
2022-06-20 13:48:11 -04:00
wg . Add ( len ( depIDs ) )
2021-10-06 19:36:31 -04:00
for i := range depIDs {
go func ( i int ) {
2022-06-20 13:48:11 -04:00
defer wg . Done ( )
2023-10-18 11:06:57 -04:00
if depIDs [ i ] == globalDeploymentID ( ) {
2021-10-06 19:36:31 -04:00
if selfActionFn != nil {
errs [ i ] = selfActionFn ( )
}
} else {
errs [ i ] = peerActionFn ( depIDs [ i ] , c . state . Peers [ depIDs [ i ] ] )
}
} ( i )
}
wg . Wait ( )
errMap := make ( map [ string ] error , len ( c . state . Peers ) )
for i , depID := range depIDs {
2022-06-20 13:48:11 -04:00
errMap [ depID ] = errs [ i ]
2023-09-15 21:01:47 -04:00
if errs [ i ] != nil && minio . IsNetworkOrHostDown ( errs [ i ] , true ) {
ep := c . state . Peers [ depID ] . Endpoint
epURL , _ := url . Parse ( ep )
if ! globalBucketTargetSys . isOffline ( epURL ) {
globalBucketTargetSys . markOffline ( epURL )
}
}
2021-10-06 19:36:31 -04:00
}
2022-06-20 13:48:11 -04:00
return c . newConcErr ( errMap , actionName )
2021-10-06 19:36:31 -04:00
}
func ( c * SiteReplicationSys ) annotateErr ( annotation string , err error ) error {
if err == nil {
return nil
}
2022-06-20 13:48:11 -04:00
return fmt . Errorf ( "%s: %s: %w" , c . state . Name , annotation , err )
2021-10-06 19:36:31 -04:00
}
func ( c * SiteReplicationSys ) annotatePeerErr ( dstPeer string , annotation string , err error ) error {
if err == nil {
return nil
}
2022-06-20 13:48:11 -04:00
return fmt . Errorf ( "%s->%s: %s: %w" , c . state . Name , dstPeer , annotation , err )
2021-10-06 19:36:31 -04:00
}
2021-12-15 13:37:08 -05:00
// isEnabled returns true if site replication is enabled
func ( c * SiteReplicationSys ) isEnabled ( ) bool {
c . RLock ( )
defer c . RUnlock ( )
return c . enabled
}
2022-08-18 14:10:49 -04:00
var errMissingSRConfig = fmt . Errorf ( "unable to find site replication configuration" )
2022-05-06 15:40:34 -04:00
2022-02-01 20:26:09 -05:00
// RemovePeerCluster - removes one or more clusters from site replication configuration.
func ( c * SiteReplicationSys ) RemovePeerCluster ( ctx context . Context , objectAPI ObjectLayer , rreq madmin . SRRemoveReq ) ( st madmin . ReplicateRemoveStatus , err error ) {
if ! c . isEnabled ( ) {
return st , errSRNotEnabled
}
info , err := c . GetClusterInfo ( ctx )
if err != nil {
return st , errSRBackendIssue ( err )
}
peerMap := make ( map [ string ] madmin . PeerInfo )
var rmvEndpoints [ ] string
siteNames := rreq . SiteNames
updatedPeers := make ( map [ string ] madmin . PeerInfo )
for _ , pi := range info . Sites {
updatedPeers [ pi . DeploymentID ] = pi
peerMap [ pi . Name ] = pi
if rreq . RemoveAll {
siteNames = append ( siteNames , pi . Name )
}
}
for _ , s := range siteNames {
2022-12-22 04:31:20 -05:00
pinfo , ok := peerMap [ s ]
2022-02-01 20:26:09 -05:00
if ! ok {
2022-05-06 15:40:34 -04:00
return st , errSRConfigMissingError ( errMissingSRConfig )
2022-02-01 20:26:09 -05:00
}
2022-12-22 04:31:20 -05:00
rmvEndpoints = append ( rmvEndpoints , pinfo . Endpoint )
delete ( updatedPeers , pinfo . DeploymentID )
2022-02-01 20:26:09 -05:00
}
var wg sync . WaitGroup
errs := make ( map [ string ] error , len ( c . state . Peers ) )
for _ , v := range info . Sites {
wg . Add ( 1 )
2023-10-18 11:06:57 -04:00
if v . DeploymentID == globalDeploymentID ( ) {
2022-02-01 20:26:09 -05:00
go func ( ) {
defer wg . Done ( )
err := c . RemoveRemoteTargetsForEndpoint ( ctx , objectAPI , rmvEndpoints , false )
2023-10-18 11:06:57 -04:00
errs [ globalDeploymentID ( ) ] = err
2022-02-01 20:26:09 -05:00
} ( )
continue
}
go func ( pi madmin . PeerInfo ) {
defer wg . Done ( )
admClient , err := c . getAdminClient ( ctx , pi . DeploymentID )
if err != nil {
errs [ pi . DeploymentID ] = errSRPeerResp ( fmt . Errorf ( "unable to create admin client for %s: %w" , pi . Name , err ) )
return
}
2022-12-22 04:31:20 -05:00
// set the requesting site's deploymentID for verification of peer request
2023-10-18 11:06:57 -04:00
rreq . RequestingDepID = globalDeploymentID ( )
2022-02-01 20:26:09 -05:00
if _ , err = admClient . SRPeerRemove ( ctx , rreq ) ; err != nil {
2022-08-18 14:10:49 -04:00
if errors . Is ( err , errMissingSRConfig ) {
// ignore if peer is already removed.
2022-05-06 15:40:34 -04:00
return
}
2022-02-01 20:26:09 -05:00
errs [ pi . DeploymentID ] = errSRPeerResp ( fmt . Errorf ( "unable to update peer %s: %w" , pi . Name , err ) )
return
}
} ( v )
}
wg . Wait ( )
2022-09-20 17:32:23 -04:00
errdID := ""
2023-10-18 11:06:57 -04:00
selfTgtsDeleted := errs [ globalDeploymentID ( ) ] == nil // true if all remote targets and replication config cleared successfully on local cluster
2022-12-22 04:31:20 -05:00
2022-02-01 20:26:09 -05:00
for dID , err := range errs {
if err != nil {
2022-12-22 04:31:20 -05:00
if ! rreq . RemoveAll && ! selfTgtsDeleted {
2022-09-20 17:32:23 -04:00
return madmin . ReplicateRemoveStatus {
ErrDetail : err . Error ( ) ,
Status : madmin . ReplicateRemoveStatusPartial ,
} , errSRPeerResp ( fmt . Errorf ( "unable to update peer %s: %w" , c . state . Peers [ dID ] . Name , err ) )
}
errdID = dID
}
}
// force local config to be cleared even if peers failed since the remote targets are deleted
// by now from the replication config and user intended to forcibly clear all site replication
if rreq . RemoveAll {
if err = c . removeFromDisk ( ctx ) ; err != nil {
2022-02-01 20:26:09 -05:00
return madmin . ReplicateRemoveStatus {
Status : madmin . ReplicateRemoveStatusPartial ,
2022-09-20 17:32:23 -04:00
ErrDetail : fmt . Sprintf ( "unable to remove cluster-replication state on local: %v" , err ) ,
} , nil
2022-02-01 20:26:09 -05:00
}
2022-09-20 17:32:23 -04:00
if errdID != "" {
err := errs [ errdID ]
return madmin . ReplicateRemoveStatus {
Status : madmin . ReplicateRemoveStatusPartial ,
ErrDetail : err . Error ( ) ,
} , nil
}
return madmin . ReplicateRemoveStatus {
Status : madmin . ReplicateRemoveStatusSuccess ,
} , nil
2022-02-01 20:26:09 -05:00
}
2022-09-20 17:32:23 -04:00
2022-02-01 20:26:09 -05:00
// Update cluster state
var state srState
if len ( updatedPeers ) > 1 {
state = srState {
Name : info . Name ,
Peers : updatedPeers ,
ServiceAccountAccessKey : info . ServiceAccountAccessKey ,
}
}
if err = c . saveToDisk ( ctx , state ) ; err != nil {
return madmin . ReplicateRemoveStatus {
Status : madmin . ReplicateRemoveStatusPartial ,
ErrDetail : fmt . Sprintf ( "unable to save cluster-replication state on local: %v" , err ) ,
2022-12-22 04:31:20 -05:00
} , err
2022-02-01 20:26:09 -05:00
}
2022-12-22 04:31:20 -05:00
st = madmin . ReplicateRemoveStatus {
2022-02-01 20:26:09 -05:00
Status : madmin . ReplicateRemoveStatusSuccess ,
2022-12-22 04:31:20 -05:00
}
if errs [ errdID ] != nil {
st . Status = madmin . ReplicateRemoveStatusPartial
st . ErrDetail = errs [ errdID ] . Error ( )
}
return st , nil
2022-02-01 20:26:09 -05:00
}
// InternalRemoveReq - sends an unlink request to peer cluster to remove one or more sites
// from the site replication configuration.
func ( c * SiteReplicationSys ) InternalRemoveReq ( ctx context . Context , objectAPI ObjectLayer , rreq madmin . SRRemoveReq ) error {
2022-05-06 15:40:34 -04:00
if ! c . isEnabled ( ) {
return errSRNotEnabled
}
2022-12-22 04:31:20 -05:00
if rreq . RequestingDepID != "" {
// validate if requesting site is still part of site replication
var foundRequestor bool
for _ , p := range c . state . Peers {
if p . DeploymentID == rreq . RequestingDepID {
foundRequestor = true
break
}
}
if ! foundRequestor {
return errSRRequestorNotFound
}
}
2022-05-06 15:40:34 -04:00
2022-02-01 20:26:09 -05:00
ourName := ""
peerMap := make ( map [ string ] madmin . PeerInfo )
updatedPeers := make ( map [ string ] madmin . PeerInfo )
siteNames := rreq . SiteNames
for _ , p := range c . state . Peers {
peerMap [ p . Name ] = p
2023-10-18 11:06:57 -04:00
if p . DeploymentID == globalDeploymentID ( ) {
2022-02-01 20:26:09 -05:00
ourName = p . Name
}
updatedPeers [ p . DeploymentID ] = p
if rreq . RemoveAll {
siteNames = append ( siteNames , p . Name )
}
}
var rmvEndpoints [ ] string
var unlinkSelf bool
for _ , s := range siteNames {
info , ok := peerMap [ s ]
if ! ok {
2022-05-06 15:40:34 -04:00
return errMissingSRConfig
2022-02-01 20:26:09 -05:00
}
2023-10-18 11:06:57 -04:00
if info . DeploymentID == globalDeploymentID ( ) {
2022-02-01 20:26:09 -05:00
unlinkSelf = true
continue
}
delete ( updatedPeers , info . DeploymentID )
rmvEndpoints = append ( rmvEndpoints , info . Endpoint )
}
if err := c . RemoveRemoteTargetsForEndpoint ( ctx , objectAPI , rmvEndpoints , unlinkSelf ) ; err != nil {
return err
}
var state srState
if ! unlinkSelf {
state = srState {
Name : c . state . Name ,
Peers : updatedPeers ,
ServiceAccountAccessKey : c . state . ServiceAccountAccessKey ,
}
}
if err := c . saveToDisk ( ctx , state ) ; err != nil {
2022-08-04 19:10:08 -04:00
return errSRBackendIssue ( fmt . Errorf ( "unable to save cluster-replication state to drive on %s: %v" , ourName , err ) )
2022-02-01 20:26:09 -05:00
}
return nil
}
// RemoveRemoteTargetsForEndpoint removes replication targets corresponding to endpoint
func ( c * SiteReplicationSys ) RemoveRemoteTargetsForEndpoint ( ctx context . Context , objectAPI ObjectLayer , endpoints [ ] string , unlinkSelf bool ) ( err error ) {
targets := globalBucketTargetSys . ListTargets ( ctx , "" , string ( madmin . ReplicationService ) )
m := make ( map [ string ] madmin . BucketTarget )
for _ , t := range targets {
for _ , endpoint := range endpoints {
ep , _ := url . Parse ( endpoint )
if t . Endpoint == ep . Host &&
t . Secure == ( ep . Scheme == "https" ) &&
t . Type == madmin . ReplicationService {
m [ t . Arn ] = t
}
}
// all remote targets from self are to be delinked
if unlinkSelf {
m [ t . Arn ] = t
}
}
2022-07-25 20:51:32 -04:00
buckets , err := objectAPI . ListBuckets ( ctx , BucketOptions { } )
2023-02-08 00:44:42 -05:00
if err != nil {
return errSRBackendIssue ( err )
}
2022-02-01 20:26:09 -05:00
for _ , b := range buckets {
2022-04-24 05:36:31 -04:00
config , _ , err := globalBucketMetadataSys . GetReplicationConfig ( ctx , b . Name )
2022-02-01 20:26:09 -05:00
if err != nil {
2022-07-11 17:11:46 -04:00
if errors . Is ( err , BucketReplicationConfigNotFound { Bucket : b . Name } ) {
continue
}
2022-02-01 20:26:09 -05:00
return err
}
var nRules [ ] sreplication . Rule
for _ , r := range config . Rules {
if _ , ok := m [ r . Destination . Bucket ] ; ! ok {
nRules = append ( nRules , r )
}
}
if len ( nRules ) > 0 {
config . Rules = nRules
configData , err := xml . Marshal ( config )
if err != nil {
return err
}
2022-06-28 21:09:20 -04:00
if _ , err = globalBucketMetadataSys . Update ( ctx , b . Name , bucketReplicationConfig , configData ) ; err != nil {
2022-02-01 20:26:09 -05:00
return err
}
} else {
2022-10-19 20:55:09 -04:00
if _ , err := globalBucketMetadataSys . Delete ( ctx , b . Name , bucketReplicationConfig ) ; err != nil {
2022-02-01 20:26:09 -05:00
return err
}
}
}
for arn , t := range m {
if err := globalBucketTargetSys . RemoveTarget ( ctx , t . SourceBucket , arn ) ; err != nil {
2022-07-11 17:11:46 -04:00
if errors . Is ( err , BucketRemoteTargetNotFound { Bucket : t . SourceBucket } ) {
continue
}
2022-02-01 20:26:09 -05:00
return err
}
2023-06-01 13:19:56 -04:00
targets , terr := globalBucketTargetSys . ListBucketTargets ( ctx , t . SourceBucket )
if terr != nil {
return err
}
tgtBytes , terr := json . Marshal ( & targets )
if terr != nil {
return err
}
if _ , err = globalBucketMetadataSys . Update ( ctx , t . SourceBucket , bucketTargetsFile , tgtBytes ) ; err != nil {
return err
}
2022-02-01 20:26:09 -05:00
}
return
}
2021-10-06 19:36:31 -04:00
// Other helpers
func getAdminClient ( endpoint , accessKey , secretKey string ) ( * madmin . AdminClient , error ) {
2022-08-08 14:12:05 -04:00
epURL , err := url . Parse ( endpoint )
if err != nil {
return nil , err
}
2022-08-16 20:46:22 -04:00
if globalBucketTargetSys . isOffline ( epURL ) {
return nil , RemoteTargetConnectionErr { Endpoint : epURL . String ( ) , Err : fmt . Errorf ( "remote target is offline for endpoint %s" , epURL . String ( ) ) }
}
2021-10-06 19:36:31 -04:00
client , err := madmin . New ( epURL . Host , accessKey , secretKey , epURL . Scheme == "https" )
if err != nil {
return nil , err
}
2022-06-22 19:28:25 -04:00
client . SetCustomTransport ( globalRemoteTargetTransport )
2021-10-06 19:36:31 -04:00
return client , nil
}
func getS3Client ( pc madmin . PeerSite ) ( * minioClient . Client , error ) {
2022-01-19 23:02:24 -05:00
ep , err := url . Parse ( pc . Endpoint )
if err != nil {
return nil , err
}
2022-08-16 20:46:22 -04:00
if globalBucketTargetSys . isOffline ( ep ) {
return nil , RemoteTargetConnectionErr { Endpoint : ep . String ( ) , Err : fmt . Errorf ( "remote target is offline for endpoint %s" , ep . String ( ) ) }
}
2021-10-06 19:36:31 -04:00
return minioClient . New ( ep . Host , & minioClient . Options {
Creds : credentials . NewStaticV4 ( pc . AccessKey , pc . SecretKey , "" ) ,
Secure : ep . Scheme == "https" ,
2022-06-22 19:28:25 -04:00
Transport : globalRemoteTargetTransport ,
2021-10-06 19:36:31 -04:00
} )
}
func getPriorityHelper ( replicationConfig replication . Config ) int {
maxPrio := 0
for _ , rule := range replicationConfig . Rules {
if rule . Priority > maxPrio {
maxPrio = rule . Priority
}
}
// leave some gaps in priority numbers for flexibility
return maxPrio + 10
}
2021-11-30 16:16:37 -05:00
// returns a slice with site names participating in site replciation but unspecified while adding
// a new site.
func getMissingSiteNames ( oldDeps , newDeps set . StringSet , currSites [ ] madmin . PeerInfo ) [ ] string {
diff := oldDeps . Difference ( newDeps )
var diffSlc [ ] string
for _ , v := range currSites {
if diff . Contains ( v . DeploymentID ) {
diffSlc = append ( diffSlc , v . Name )
}
}
return diffSlc
}
2022-01-05 05:44:08 -05:00
type srBucketMetaInfo struct {
madmin . SRBucketInfo
DeploymentID string
}
type srPolicy struct {
2022-04-24 05:36:31 -04:00
madmin . SRIAMPolicy
2022-01-05 05:44:08 -05:00
DeploymentID string
}
2022-04-24 05:36:31 -04:00
type srPolicyMapping struct {
2022-01-05 05:44:08 -05:00
madmin . SRPolicyMapping
DeploymentID string
}
2022-01-28 18:37:55 -05:00
type srUserInfo struct {
madmin . UserInfo
DeploymentID string
}
type srGroupDesc struct {
madmin . GroupDesc
DeploymentID string
}
2022-01-05 05:44:08 -05:00
// SiteReplicationStatus returns the site replication status across clusters participating in site replication.
2022-01-28 18:37:55 -05:00
func ( c * SiteReplicationSys ) SiteReplicationStatus ( ctx context . Context , objAPI ObjectLayer , opts madmin . SRStatusOptions ) ( info madmin . SRStatusInfo , err error ) {
2022-04-24 05:36:31 -04:00
sinfo , err := c . siteReplicationStatus ( ctx , objAPI , opts )
if err != nil {
return info , err
}
info = madmin . SRStatusInfo {
Enabled : sinfo . Enabled ,
MaxBuckets : sinfo . MaxBuckets ,
MaxUsers : sinfo . MaxUsers ,
MaxGroups : sinfo . MaxGroups ,
MaxPolicies : sinfo . MaxPolicies ,
Sites : sinfo . Sites ,
StatsSummary : sinfo . StatsSummary ,
2023-08-30 04:00:59 -04:00
Metrics : sinfo . Metrics ,
2022-04-24 05:36:31 -04:00
}
info . BucketStats = make ( map [ string ] map [ string ] madmin . SRBucketStatsSummary , len ( sinfo . Sites ) )
info . PolicyStats = make ( map [ string ] map [ string ] madmin . SRPolicyStatsSummary )
info . UserStats = make ( map [ string ] map [ string ] madmin . SRUserStatsSummary )
info . GroupStats = make ( map [ string ] map [ string ] madmin . SRGroupStatsSummary )
numSites := len ( info . Sites )
for b , stat := range sinfo . BucketStats {
for dID , st := range stat {
if st . TagMismatch ||
2022-05-07 21:39:40 -04:00
st . VersioningConfigMismatch ||
2022-04-24 05:36:31 -04:00
st . OLockConfigMismatch ||
st . SSEConfigMismatch ||
st . PolicyMismatch ||
st . ReplicationCfgMismatch ||
st . QuotaCfgMismatch ||
opts . Entity == madmin . SRBucketEntity {
if _ , ok := info . BucketStats [ b ] ; ! ok {
info . BucketStats [ b ] = make ( map [ string ] madmin . SRBucketStatsSummary , numSites )
}
info . BucketStats [ b ] [ dID ] = st . SRBucketStatsSummary
}
}
}
for u , stat := range sinfo . UserStats {
for dID , st := range stat {
if st . PolicyMismatch || st . UserInfoMismatch || opts . Entity == madmin . SRUserEntity {
if _ , ok := info . UserStats [ u ] ; ! ok {
info . UserStats [ u ] = make ( map [ string ] madmin . SRUserStatsSummary , numSites )
}
info . UserStats [ u ] [ dID ] = st . SRUserStatsSummary
}
}
}
for g , stat := range sinfo . GroupStats {
for dID , st := range stat {
if st . PolicyMismatch || st . GroupDescMismatch || opts . Entity == madmin . SRGroupEntity {
if _ , ok := info . GroupStats [ g ] ; ! ok {
info . GroupStats [ g ] = make ( map [ string ] madmin . SRGroupStatsSummary , numSites )
}
info . GroupStats [ g ] [ dID ] = st . SRGroupStatsSummary
}
}
}
for p , stat := range sinfo . PolicyStats {
for dID , st := range stat {
if st . PolicyMismatch || opts . Entity == madmin . SRPolicyEntity {
if _ , ok := info . PolicyStats [ p ] ; ! ok {
info . PolicyStats [ p ] = make ( map [ string ] madmin . SRPolicyStatsSummary , numSites )
}
info . PolicyStats [ p ] [ dID ] = st . SRPolicyStatsSummary
}
}
}
return
}
2022-06-20 13:48:11 -04:00
const (
replicationStatus = "ReplicationStatus"
)
2022-04-24 05:36:31 -04:00
// siteReplicationStatus returns the site replication status across clusters participating in site replication.
func ( c * SiteReplicationSys ) siteReplicationStatus ( ctx context . Context , objAPI ObjectLayer , opts madmin . SRStatusOptions ) ( info srStatusInfo , err error ) {
2022-01-05 05:44:08 -05:00
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return info , err
}
sris := make ( [ ] madmin . SRInfo , len ( c . state . Peers ) )
2022-04-24 05:36:31 -04:00
depIdx := make ( map [ string ] int , len ( c . state . Peers ) )
2022-04-20 19:20:07 -04:00
i := 0
2022-01-05 05:44:08 -05:00
for d := range c . state . Peers {
2022-04-24 05:36:31 -04:00
depIdx [ d ] = i
2022-04-20 19:20:07 -04:00
i ++
2022-01-05 05:44:08 -05:00
}
2022-04-20 19:20:07 -04:00
metaInfoConcErr := c . concDo (
func ( ) error {
srInfo , err := c . SiteReplicationMetaInfo ( ctx , objAPI , opts )
2022-04-28 05:39:00 -04:00
if err != nil {
return err
}
2023-10-18 11:06:57 -04:00
sris [ depIdx [ globalDeploymentID ( ) ] ] = srInfo
2022-04-28 05:39:00 -04:00
return nil
2022-04-20 19:20:07 -04:00
} ,
func ( deploymentID string , p madmin . PeerInfo ) error {
admClient , err := c . getAdminClient ( ctx , deploymentID )
2022-01-05 05:44:08 -05:00
if err != nil {
2023-08-15 00:31:41 -04:00
switch err . ( type ) {
case RemoteTargetConnectionErr :
sris [ depIdx [ deploymentID ] ] = madmin . SRInfo { }
return nil
default :
return err
}
2022-01-05 05:44:08 -05:00
}
2022-04-20 19:20:07 -04:00
srInfo , err := admClient . SRMetaInfo ( ctx , opts )
2022-04-28 05:39:00 -04:00
if err != nil {
return err
}
sris [ depIdx [ deploymentID ] ] = srInfo
return nil
2022-04-20 19:20:07 -04:00
} ,
2022-06-20 13:48:11 -04:00
replicationStatus ,
2022-04-20 19:20:07 -04:00
)
2022-06-20 13:48:11 -04:00
if err := errors . Unwrap ( metaInfoConcErr ) ; err != nil {
return info , errSRBackendIssue ( err )
2022-01-05 05:44:08 -05:00
}
2022-04-20 19:20:07 -04:00
2022-01-05 05:44:08 -05:00
info . Enabled = true
info . Sites = make ( map [ string ] madmin . PeerInfo , len ( c . state . Peers ) )
for d , peer := range c . state . Peers {
info . Sites [ d ] = peer
}
var maxBuckets int
2022-04-24 05:36:31 -04:00
for _ , sri := range sris {
2022-01-05 05:44:08 -05:00
if len ( sri . Buckets ) > maxBuckets {
maxBuckets = len ( sri . Buckets )
}
}
// mapping b/w entity and entity config across sites
bucketStats := make ( map [ string ] [ ] srBucketMetaInfo )
policyStats := make ( map [ string ] [ ] srPolicy )
2022-04-24 05:36:31 -04:00
userPolicyStats := make ( map [ string ] [ ] srPolicyMapping )
groupPolicyStats := make ( map [ string ] [ ] srPolicyMapping )
2022-01-28 18:37:55 -05:00
userInfoStats := make ( map [ string ] [ ] srUserInfo )
groupDescStats := make ( map [ string ] [ ] srGroupDesc )
2022-01-05 05:44:08 -05:00
numSites := len ( sris )
2022-04-24 05:36:31 -04:00
allBuckets := set . NewStringSet ( ) // across sites
allUsers := set . NewStringSet ( )
allUserWPolicies := set . NewStringSet ( )
allGroups := set . NewStringSet ( )
allGroupWPolicies := set . NewStringSet ( )
allPolicies := set . NewStringSet ( )
2022-01-05 05:44:08 -05:00
for _ , sri := range sris {
2022-04-24 05:36:31 -04:00
for b := range sri . Buckets {
allBuckets . Add ( b )
}
for u := range sri . UserInfoMap {
allUsers . Add ( u )
}
for g := range sri . GroupDescMap {
allGroups . Add ( g )
}
for p := range sri . Policies {
allPolicies . Add ( p )
}
for u := range sri . UserPolicies {
allUserWPolicies . Add ( u )
}
for g := range sri . GroupPolicies {
allGroupWPolicies . Add ( g )
}
}
for i , sri := range sris {
for b := range allBuckets {
if _ , ok := bucketStats [ b ] ; ! ok {
bucketStats [ b ] = make ( [ ] srBucketMetaInfo , numSites )
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
si , ok := sri . Buckets [ b ]
if ! ok {
si = madmin . SRBucketInfo { Bucket : b }
}
bucketStats [ b ] [ i ] = srBucketMetaInfo { SRBucketInfo : si , DeploymentID : sri . DeploymentID }
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
for pname := range allPolicies {
2022-01-05 05:44:08 -05:00
if _ , ok := policyStats [ pname ] ; ! ok {
2022-04-24 05:36:31 -04:00
policyStats [ pname ] = make ( [ ] srPolicy , numSites )
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
// if pname is not present in the map, the zero value
// will be returned.
pi := sri . Policies [ pname ]
policyStats [ pname ] [ i ] = srPolicy { SRIAMPolicy : pi , DeploymentID : sri . DeploymentID }
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
for user := range allUserWPolicies {
2022-01-05 05:44:08 -05:00
if _ , ok := userPolicyStats [ user ] ; ! ok {
2022-04-24 05:36:31 -04:00
userPolicyStats [ user ] = make ( [ ] srPolicyMapping , numSites )
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
up := sri . UserPolicies [ user ]
userPolicyStats [ user ] [ i ] = srPolicyMapping { SRPolicyMapping : up , DeploymentID : sri . DeploymentID }
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
for group := range allGroupWPolicies {
2022-01-28 18:37:55 -05:00
if _ , ok := groupPolicyStats [ group ] ; ! ok {
2022-04-24 05:36:31 -04:00
groupPolicyStats [ group ] = make ( [ ] srPolicyMapping , numSites )
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
up := sri . GroupPolicies [ group ]
groupPolicyStats [ group ] [ i ] = srPolicyMapping { SRPolicyMapping : up , DeploymentID : sri . DeploymentID }
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
for u := range allUsers {
2022-01-28 18:37:55 -05:00
if _ , ok := userInfoStats [ u ] ; ! ok {
2022-04-24 05:36:31 -04:00
userInfoStats [ u ] = make ( [ ] srUserInfo , numSites )
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
ui := sri . UserInfoMap [ u ]
userInfoStats [ u ] [ i ] = srUserInfo { UserInfo : ui , DeploymentID : sri . DeploymentID }
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
for g := range allGroups {
2022-01-28 18:37:55 -05:00
if _ , ok := groupDescStats [ g ] ; ! ok {
2022-04-24 05:36:31 -04:00
groupDescStats [ g ] = make ( [ ] srGroupDesc , numSites )
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
gd := sri . GroupDescMap [ g ]
groupDescStats [ g ] [ i ] = srGroupDesc { GroupDesc : gd , DeploymentID : sri . DeploymentID }
2022-01-05 05:44:08 -05:00
}
}
2022-01-28 18:37:55 -05:00
info . StatsSummary = make ( map [ string ] madmin . SRSiteSummary , len ( c . state . Peers ) )
2022-04-24 05:36:31 -04:00
info . BucketStats = make ( map [ string ] map [ string ] srBucketStatsSummary )
info . PolicyStats = make ( map [ string ] map [ string ] srPolicyStatsSummary )
info . UserStats = make ( map [ string ] map [ string ] srUserStatsSummary )
info . GroupStats = make ( map [ string ] map [ string ] srGroupStatsSummary )
2022-01-05 05:44:08 -05:00
// collect user policy mapping replication status across sites
2022-01-28 18:37:55 -05:00
if opts . Users || opts . Entity == madmin . SRUserEntity {
for u , pslc := range userPolicyStats {
2022-04-24 05:36:31 -04:00
if len ( info . UserStats [ u ] ) == 0 {
info . UserStats [ u ] = make ( map [ string ] srUserStatsSummary )
}
var policyMappings [ ] madmin . SRPolicyMapping
2022-01-28 18:37:55 -05:00
uPolicyCount := 0
2022-01-05 05:44:08 -05:00
for _ , ps := range pslc {
2022-04-24 05:36:31 -04:00
policyMappings = append ( policyMappings , ps . SRPolicyMapping )
2022-01-28 18:37:55 -05:00
uPolicyCount ++
sum := info . StatsSummary [ ps . DeploymentID ]
sum . TotalUserPolicyMappingCount ++
info . StatsSummary [ ps . DeploymentID ] = sum
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
userPolicyMismatch := ! isPolicyMappingReplicated ( uPolicyCount , numSites , policyMappings )
for _ , ps := range pslc {
dID := depIdx [ ps . DeploymentID ]
_ , hasUser := sris [ dID ] . UserPolicies [ u ]
info . UserStats [ u ] [ ps . DeploymentID ] = srUserStatsSummary {
SRUserStatsSummary : madmin . SRUserStatsSummary {
2022-01-28 18:37:55 -05:00
PolicyMismatch : userPolicyMismatch ,
HasUser : hasUser ,
HasPolicyMapping : ps . Policy != "" ,
2022-04-24 05:36:31 -04:00
} ,
userPolicy : ps ,
2022-01-28 18:37:55 -05:00
}
2022-04-24 05:36:31 -04:00
if ! userPolicyMismatch || opts . Entity != madmin . SRUserEntity {
sum := info . StatsSummary [ ps . DeploymentID ]
if ! ps . IsGroup {
2022-01-28 18:37:55 -05:00
sum . ReplicatedUserPolicyMappings ++
}
2022-04-24 05:36:31 -04:00
info . StatsSummary [ ps . DeploymentID ] = sum
2022-01-28 18:37:55 -05:00
}
2022-01-05 05:44:08 -05:00
}
}
2022-04-24 05:36:31 -04:00
2022-01-28 18:37:55 -05:00
// collect user info replication status across sites
for u , pslc := range userInfoStats {
2022-04-24 05:36:31 -04:00
var uiSlc [ ] madmin . UserInfo
2022-01-28 18:37:55 -05:00
userCount := 0
2022-01-05 05:44:08 -05:00
for _ , ps := range pslc {
2022-04-24 05:36:31 -04:00
uiSlc = append ( uiSlc , ps . UserInfo )
2022-01-28 18:37:55 -05:00
userCount ++
sum := info . StatsSummary [ ps . DeploymentID ]
sum . TotalUsersCount ++
info . StatsSummary [ ps . DeploymentID ] = sum
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
userInfoMismatch := ! isUserInfoReplicated ( userCount , numSites , uiSlc )
for _ , ps := range pslc {
dID := depIdx [ ps . DeploymentID ]
_ , hasUser := sris [ dID ] . UserInfoMap [ u ]
if len ( info . UserStats [ u ] ) == 0 {
info . UserStats [ u ] = make ( map [ string ] srUserStatsSummary )
}
umis , ok := info . UserStats [ u ] [ ps . DeploymentID ]
if ! ok {
umis = srUserStatsSummary {
SRUserStatsSummary : madmin . SRUserStatsSummary {
2022-01-28 18:37:55 -05:00
HasUser : hasUser ,
2022-04-24 05:36:31 -04:00
} ,
2022-01-28 18:37:55 -05:00
}
}
2022-04-24 05:36:31 -04:00
umis . UserInfoMismatch = userInfoMismatch
umis . userInfo = ps
info . UserStats [ u ] [ ps . DeploymentID ] = umis
if ! userInfoMismatch || opts . Entity != madmin . SRUserEntity {
sum := info . StatsSummary [ ps . DeploymentID ]
2022-01-28 18:37:55 -05:00
sum . ReplicatedUsers ++
2022-04-24 05:36:31 -04:00
info . StatsSummary [ ps . DeploymentID ] = sum
2022-01-05 05:44:08 -05:00
}
}
}
}
2022-01-28 18:37:55 -05:00
if opts . Groups || opts . Entity == madmin . SRGroupEntity {
// collect group policy mapping replication status across sites
for g , pslc := range groupPolicyStats {
2022-04-24 05:36:31 -04:00
var policyMappings [ ] madmin . SRPolicyMapping
2022-01-28 18:37:55 -05:00
gPolicyCount := 0
for _ , ps := range pslc {
2022-04-24 05:36:31 -04:00
policyMappings = append ( policyMappings , ps . SRPolicyMapping )
2022-01-28 18:37:55 -05:00
gPolicyCount ++
sum := info . StatsSummary [ ps . DeploymentID ]
sum . TotalGroupPolicyMappingCount ++
info . StatsSummary [ ps . DeploymentID ] = sum
}
2022-04-24 05:36:31 -04:00
groupPolicyMismatch := ! isPolicyMappingReplicated ( gPolicyCount , numSites , policyMappings )
if len ( info . GroupStats [ g ] ) == 0 {
info . GroupStats [ g ] = make ( map [ string ] srGroupStatsSummary )
}
for _ , ps := range pslc {
dID := depIdx [ ps . DeploymentID ]
_ , hasGroup := sris [ dID ] . GroupPolicies [ g ]
info . GroupStats [ g ] [ ps . DeploymentID ] = srGroupStatsSummary {
SRGroupStatsSummary : madmin . SRGroupStatsSummary {
2022-01-28 18:37:55 -05:00
PolicyMismatch : groupPolicyMismatch ,
HasGroup : hasGroup ,
HasPolicyMapping : ps . Policy != "" ,
2022-04-24 05:36:31 -04:00
DeploymentID : ps . DeploymentID ,
} ,
groupPolicy : ps ,
2022-01-28 18:37:55 -05:00
}
2022-04-24 05:36:31 -04:00
if ! groupPolicyMismatch && opts . Entity != madmin . SRGroupEntity {
sum := info . StatsSummary [ ps . DeploymentID ]
2022-01-28 18:37:55 -05:00
sum . ReplicatedGroupPolicyMappings ++
2022-04-24 05:36:31 -04:00
info . StatsSummary [ ps . DeploymentID ] = sum
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
2022-01-05 05:44:08 -05:00
}
2022-01-28 18:37:55 -05:00
}
// collect group desc replication status across sites
for g , pslc := range groupDescStats {
2022-04-24 05:36:31 -04:00
var gds [ ] madmin . GroupDesc
2022-01-28 18:37:55 -05:00
groupCount := 0
for _ , ps := range pslc {
groupCount ++
sum := info . StatsSummary [ ps . DeploymentID ]
sum . TotalGroupsCount ++
info . StatsSummary [ ps . DeploymentID ] = sum
2022-04-24 05:36:31 -04:00
gds = append ( gds , ps . GroupDesc )
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
gdMismatch := ! isGroupDescReplicated ( groupCount , numSites , gds )
for _ , ps := range pslc {
dID := depIdx [ ps . DeploymentID ]
_ , hasGroup := sris [ dID ] . GroupDescMap [ g ]
if len ( info . GroupStats [ g ] ) == 0 {
info . GroupStats [ g ] = make ( map [ string ] srGroupStatsSummary )
}
gmis , ok := info . GroupStats [ g ] [ ps . DeploymentID ]
if ! ok {
gmis = srGroupStatsSummary {
SRGroupStatsSummary : madmin . SRGroupStatsSummary {
2022-01-28 18:37:55 -05:00
HasGroup : hasGroup ,
2022-04-24 05:36:31 -04:00
} ,
2022-01-28 18:37:55 -05:00
}
}
2022-04-24 05:36:31 -04:00
gmis . GroupDescMismatch = gdMismatch
gmis . groupDesc = ps
info . GroupStats [ g ] [ ps . DeploymentID ] = gmis
if ! gdMismatch && opts . Entity != madmin . SRGroupEntity {
sum := info . StatsSummary [ ps . DeploymentID ]
2022-01-28 18:37:55 -05:00
sum . ReplicatedGroups ++
2022-04-24 05:36:31 -04:00
info . StatsSummary [ ps . DeploymentID ] = sum
2022-01-28 18:37:55 -05:00
}
}
}
}
if opts . Policies || opts . Entity == madmin . SRPolicyEntity {
// collect IAM policy replication status across sites
for p , pslc := range policyStats {
2023-09-04 15:57:37 -04:00
var policies [ ] * policy . Policy
2022-01-28 18:37:55 -05:00
uPolicyCount := 0
for _ , ps := range pslc {
2023-09-04 15:57:37 -04:00
plcy , err := policy . ParseConfig ( bytes . NewReader ( [ ] byte ( ps . SRIAMPolicy . Policy ) ) )
2022-01-05 05:44:08 -05:00
if err != nil {
continue
}
policies = append ( policies , plcy )
2022-01-28 18:37:55 -05:00
uPolicyCount ++
sum := info . StatsSummary [ ps . DeploymentID ]
sum . TotalIAMPoliciesCount ++
info . StatsSummary [ ps . DeploymentID ] = sum
}
2022-04-24 05:36:31 -04:00
if len ( info . PolicyStats [ p ] ) == 0 {
info . PolicyStats [ p ] = make ( map [ string ] srPolicyStatsSummary )
}
2022-01-28 18:37:55 -05:00
policyMismatch := ! isIAMPolicyReplicated ( uPolicyCount , numSites , policies )
2022-04-24 05:36:31 -04:00
for _ , ps := range pslc {
dID := depIdx [ ps . DeploymentID ]
_ , hasPolicy := sris [ dID ] . Policies [ p ]
info . PolicyStats [ p ] [ ps . DeploymentID ] = srPolicyStatsSummary {
SRPolicyStatsSummary : madmin . SRPolicyStatsSummary {
2022-01-28 18:37:55 -05:00
PolicyMismatch : policyMismatch ,
HasPolicy : hasPolicy ,
2022-04-24 05:36:31 -04:00
} ,
policy : ps ,
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
switch {
case policyMismatch , opts . Entity == madmin . SRPolicyEntity :
default :
sum := info . StatsSummary [ ps . DeploymentID ]
2022-01-28 18:37:55 -05:00
if ! policyMismatch {
sum . ReplicatedIAMPolicies ++
}
2022-04-24 05:36:31 -04:00
info . StatsSummary [ ps . DeploymentID ] = sum
2022-01-05 05:44:08 -05:00
}
}
}
2022-01-28 18:37:55 -05:00
}
if opts . Buckets || opts . Entity == madmin . SRBucketEntity {
// collect bucket metadata replication stats across sites
for b , slc := range bucketStats {
tagSet := set . NewStringSet ( )
olockConfigSet := set . NewStringSet ( )
2023-09-04 15:57:37 -04:00
policies := make ( [ ] * policy . BucketPolicy , numSites )
2022-04-24 05:36:31 -04:00
replCfgs := make ( [ ] * sreplication . Config , numSites )
quotaCfgs := make ( [ ] * madmin . BucketQuota , numSites )
2022-01-28 18:37:55 -05:00
sseCfgSet := set . NewStringSet ( )
2022-05-07 21:39:40 -04:00
versionCfgSet := set . NewStringSet ( )
var tagCount , olockCfgCount , sseCfgCount , versionCfgCount int
2022-04-24 05:36:31 -04:00
for i , s := range slc {
2022-01-28 18:37:55 -05:00
if s . ReplicationConfig != nil {
cfgBytes , err := base64 . StdEncoding . DecodeString ( * s . ReplicationConfig )
if err != nil {
continue
}
cfg , err := sreplication . ParseConfig ( bytes . NewReader ( cfgBytes ) )
if err != nil {
continue
}
2022-04-24 05:36:31 -04:00
replCfgs [ i ] = cfg
2022-01-05 05:44:08 -05:00
}
2022-05-07 21:39:40 -04:00
if s . Versioning != nil {
configData , err := base64 . StdEncoding . DecodeString ( * s . Versioning )
if err != nil {
continue
}
versionCfgCount ++
if ! versionCfgSet . Contains ( string ( configData ) ) {
versionCfgSet . Add ( string ( configData ) )
}
}
2022-01-28 18:37:55 -05:00
if s . QuotaConfig != nil {
cfgBytes , err := base64 . StdEncoding . DecodeString ( * s . QuotaConfig )
if err != nil {
continue
}
cfg , err := parseBucketQuota ( b , cfgBytes )
if err != nil {
continue
}
2022-04-24 05:36:31 -04:00
quotaCfgs [ i ] = cfg
2022-01-28 18:37:55 -05:00
}
if s . Tags != nil {
tagBytes , err := base64 . StdEncoding . DecodeString ( * s . Tags )
if err != nil {
continue
}
tagCount ++
if ! tagSet . Contains ( string ( tagBytes ) ) {
tagSet . Add ( string ( tagBytes ) )
}
2022-01-05 05:44:08 -05:00
}
2022-01-28 18:37:55 -05:00
if len ( s . Policy ) > 0 {
2023-09-04 15:57:37 -04:00
plcy , err := policy . ParseBucketPolicyConfig ( bytes . NewReader ( s . Policy ) , b )
2022-01-28 18:37:55 -05:00
if err != nil {
continue
}
2022-04-24 05:36:31 -04:00
policies [ i ] = plcy
2022-01-05 05:44:08 -05:00
}
2022-01-28 18:37:55 -05:00
if s . ObjectLockConfig != nil {
2022-04-24 05:36:31 -04:00
configData , err := base64 . StdEncoding . DecodeString ( * s . ObjectLockConfig )
if err != nil {
continue
}
2022-05-07 21:39:40 -04:00
olockCfgCount ++
2022-04-24 05:36:31 -04:00
if ! olockConfigSet . Contains ( string ( configData ) ) {
olockConfigSet . Add ( string ( configData ) )
2022-01-28 18:37:55 -05:00
}
2022-01-05 05:44:08 -05:00
}
2022-01-28 18:37:55 -05:00
if s . SSEConfig != nil {
2022-04-24 05:36:31 -04:00
configData , err := base64 . StdEncoding . DecodeString ( * s . SSEConfig )
if err != nil {
continue
}
2022-05-07 21:39:40 -04:00
sseCfgCount ++
2022-04-24 05:36:31 -04:00
if ! sseCfgSet . Contains ( string ( configData ) ) {
sseCfgSet . Add ( string ( configData ) )
2022-01-28 18:37:55 -05:00
}
}
ss , ok := info . StatsSummary [ s . DeploymentID ]
if ! ok {
ss = madmin . SRSiteSummary { }
}
// increment total number of replicated buckets
if len ( slc ) == numSites {
ss . ReplicatedBuckets ++
}
ss . TotalBucketsCount ++
if tagCount > 0 {
ss . TotalTagsCount ++
}
if olockCfgCount > 0 {
ss . TotalLockConfigCount ++
}
if sseCfgCount > 0 {
ss . TotalSSEConfigCount ++
}
2022-05-07 21:39:40 -04:00
if versionCfgCount > 0 {
ss . TotalVersioningConfigCount ++
}
2022-01-28 18:37:55 -05:00
if len ( policies ) > 0 {
ss . TotalBucketPoliciesCount ++
}
info . StatsSummary [ s . DeploymentID ] = ss
}
tagMismatch := ! isReplicated ( tagCount , numSites , tagSet )
olockCfgMismatch := ! isReplicated ( olockCfgCount , numSites , olockConfigSet )
sseCfgMismatch := ! isReplicated ( sseCfgCount , numSites , sseCfgSet )
2022-05-07 21:39:40 -04:00
versionCfgMismatch := ! isReplicated ( versionCfgCount , numSites , versionCfgSet )
2022-01-28 18:37:55 -05:00
policyMismatch := ! isBktPolicyReplicated ( numSites , policies )
replCfgMismatch := ! isBktReplCfgReplicated ( numSites , replCfgs )
quotaCfgMismatch := ! isBktQuotaCfgReplicated ( numSites , quotaCfgs )
2022-04-24 05:36:31 -04:00
info . BucketStats [ b ] = make ( map [ string ] srBucketStatsSummary , numSites )
for i , s := range slc {
dIdx := depIdx [ s . DeploymentID ]
2022-07-25 20:51:32 -04:00
var hasBucket , isBucketMarkedDeleted bool
bi , ok := sris [ dIdx ] . Buckets [ s . Bucket ]
if ok {
isBucketMarkedDeleted = ! bi . DeletedAt . IsZero ( ) && ( bi . CreatedAt . IsZero ( ) || bi . DeletedAt . After ( bi . CreatedAt ) )
hasBucket = ! bi . CreatedAt . IsZero ( )
2022-01-28 18:37:55 -05:00
}
2022-07-14 17:27:47 -04:00
quotaCfgSet := hasBucket && quotaCfgs [ i ] != nil && * quotaCfgs [ i ] != madmin . BucketQuota { }
2022-04-24 05:36:31 -04:00
ss := madmin . SRBucketStatsSummary {
2022-05-07 21:39:40 -04:00
DeploymentID : s . DeploymentID ,
HasBucket : hasBucket ,
2022-07-25 20:51:32 -04:00
BucketMarkedDeleted : isBucketMarkedDeleted ,
2022-05-07 21:39:40 -04:00
TagMismatch : tagMismatch ,
OLockConfigMismatch : olockCfgMismatch ,
SSEConfigMismatch : sseCfgMismatch ,
VersioningConfigMismatch : versionCfgMismatch ,
PolicyMismatch : policyMismatch ,
ReplicationCfgMismatch : replCfgMismatch ,
QuotaCfgMismatch : quotaCfgMismatch ,
HasReplicationCfg : s . ReplicationConfig != nil ,
HasTagsSet : s . Tags != nil ,
HasOLockConfigSet : s . ObjectLockConfig != nil ,
HasPolicySet : s . Policy != nil ,
HasQuotaCfgSet : quotaCfgSet ,
HasSSECfgSet : s . SSEConfig != nil ,
2022-04-24 05:36:31 -04:00
}
var m srBucketMetaInfo
if len ( bucketStats [ s . Bucket ] ) > dIdx {
m = bucketStats [ s . Bucket ] [ dIdx ]
}
info . BucketStats [ b ] [ s . DeploymentID ] = srBucketStatsSummary {
SRBucketStatsSummary : ss ,
meta : m ,
}
}
// no mismatch
for _ , s := range slc {
sum := info . StatsSummary [ s . DeploymentID ]
if ! olockCfgMismatch && olockCfgCount == numSites {
sum . ReplicatedLockConfig ++
}
2022-05-07 21:39:40 -04:00
if ! versionCfgMismatch && versionCfgCount == numSites {
sum . ReplicatedVersioningConfig ++
}
2022-04-24 05:36:31 -04:00
if ! sseCfgMismatch && sseCfgCount == numSites {
sum . ReplicatedSSEConfig ++
}
if ! policyMismatch && len ( policies ) == numSites {
sum . ReplicatedBucketPolicies ++
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
if ! tagMismatch && tagCount == numSites {
sum . ReplicatedTags ++
}
info . StatsSummary [ s . DeploymentID ] = sum
2022-01-05 05:44:08 -05:00
}
}
}
2022-04-24 05:36:31 -04:00
2023-08-30 04:00:59 -04:00
if opts . Metrics {
m , err := globalSiteReplicationSys . getSiteMetrics ( ctx )
if err != nil {
return info , err
}
info . Metrics = m
}
2022-01-05 05:44:08 -05:00
// maximum buckets users etc seen across sites
info . MaxBuckets = len ( bucketStats )
2022-01-28 18:37:55 -05:00
info . MaxUsers = len ( userInfoStats )
info . MaxGroups = len ( groupDescStats )
2022-01-05 05:44:08 -05:00
info . MaxPolicies = len ( policyStats )
return
}
// isReplicated returns true if count of replicated matches the number of
// sites and there is atmost one unique entry in the set.
func isReplicated ( cntReplicated , total int , valSet set . StringSet ) bool {
if cntReplicated > 0 && cntReplicated < total {
return false
}
if len ( valSet ) > 1 {
// mismatch - one or more sites has differing tags/policy
return false
}
return true
}
// isIAMPolicyReplicated returns true if count of replicated IAM policies matches total
// number of sites and IAM policies are identical.
2023-09-04 15:57:37 -04:00
func isIAMPolicyReplicated ( cntReplicated , total int , policies [ ] * policy . Policy ) bool {
2022-01-05 05:44:08 -05:00
if cntReplicated > 0 && cntReplicated != total {
return false
}
// check if policies match between sites
2023-09-04 15:57:37 -04:00
var prev * policy . Policy
2022-01-05 05:44:08 -05:00
for i , p := range policies {
if i == 0 {
prev = p
continue
}
if ! prev . Equals ( * p ) {
return false
}
}
return true
}
2022-04-24 05:36:31 -04:00
// isPolicyMappingReplicated returns true if count of replicated IAM policy mappings matches total
// number of sites and IAM policy mappings are identical.
func isPolicyMappingReplicated ( cntReplicated , total int , policies [ ] madmin . SRPolicyMapping ) bool {
if cntReplicated > 0 && cntReplicated != total {
return false
}
// check if policies match between sites
var prev madmin . SRPolicyMapping
for i , p := range policies {
if i == 0 {
prev = p
continue
}
if prev . IsGroup != p . IsGroup ||
prev . Policy != p . Policy ||
prev . UserOrGroup != p . UserOrGroup {
return false
}
}
return true
}
func isUserInfoReplicated ( cntReplicated , total int , uis [ ] madmin . UserInfo ) bool {
if cntReplicated > 0 && cntReplicated != total {
return false
}
// check if policies match between sites
var prev madmin . UserInfo
for i , ui := range uis {
if i == 0 {
prev = ui
continue
}
if ! isUserInfoEqual ( prev , ui ) {
return false
}
}
return true
}
func isGroupDescReplicated ( cntReplicated , total int , gds [ ] madmin . GroupDesc ) bool {
if cntReplicated > 0 && cntReplicated != total {
return false
}
// check if policies match between sites
var prev madmin . GroupDesc
for i , gd := range gds {
if i == 0 {
prev = gd
continue
}
if ! isGroupDescEqual ( prev , gd ) {
return false
}
}
return true
}
2022-01-28 18:37:55 -05:00
func isBktQuotaCfgReplicated ( total int , quotaCfgs [ ] * madmin . BucketQuota ) bool {
2022-04-24 05:36:31 -04:00
numquotaCfgs := 0
for _ , q := range quotaCfgs {
if q == nil {
continue
}
numquotaCfgs ++
}
2023-02-15 21:38:35 -05:00
if numquotaCfgs == 0 {
return true
}
2022-04-24 05:36:31 -04:00
if numquotaCfgs > 0 && numquotaCfgs != total {
2022-01-28 18:37:55 -05:00
return false
}
var prev * madmin . BucketQuota
for i , q := range quotaCfgs {
if q == nil {
return false
}
if i == 0 {
prev = q
continue
}
if prev . Quota != q . Quota || prev . Type != q . Type {
return false
}
}
return true
}
2022-01-05 05:44:08 -05:00
// isBktPolicyReplicated returns true if count of replicated bucket policies matches total
// number of sites and bucket policies are identical.
2023-09-04 15:57:37 -04:00
func isBktPolicyReplicated ( total int , policies [ ] * policy . BucketPolicy ) bool {
2022-04-24 05:36:31 -04:00
numPolicies := 0
for _ , p := range policies {
if p == nil {
continue
}
numPolicies ++
}
if numPolicies > 0 && numPolicies != total {
2022-01-05 05:44:08 -05:00
return false
}
// check if policies match between sites
2023-09-04 15:57:37 -04:00
var prev * policy . BucketPolicy
2022-01-05 05:44:08 -05:00
for i , p := range policies {
2022-04-24 05:36:31 -04:00
if p == nil {
continue
}
2022-01-05 05:44:08 -05:00
if i == 0 {
prev = p
continue
}
if ! prev . Equals ( * p ) {
return false
}
}
return true
}
// isBktReplCfgReplicated returns true if all the sites have same number
// of replication rules with all replication features enabled.
func isBktReplCfgReplicated ( total int , cfgs [ ] * sreplication . Config ) bool {
2022-04-24 05:36:31 -04:00
cntReplicated := 0
for _ , c := range cfgs {
if c == nil {
continue
}
cntReplicated ++
}
if cntReplicated > 0 && cntReplicated != total {
2022-01-05 05:44:08 -05:00
return false
}
// check if policies match between sites
var prev * sreplication . Config
for i , c := range cfgs {
2022-04-24 05:36:31 -04:00
if c == nil {
continue
}
2022-01-05 05:44:08 -05:00
if i == 0 {
prev = c
continue
}
if len ( prev . Rules ) != len ( c . Rules ) {
return false
}
if len ( c . Rules ) != total - 1 {
return false
}
for _ , r := range c . Rules {
if ! strings . HasPrefix ( r . ID , "site-repl-" ) {
return false
}
if r . DeleteMarkerReplication . Status == sreplication . Disabled ||
r . DeleteReplication . Status == sreplication . Disabled ||
r . ExistingObjectReplication . Status == sreplication . Disabled ||
r . SourceSelectionCriteria . ReplicaModifications . Status == sreplication . Disabled {
return false
}
}
}
return true
}
2023-02-09 00:16:53 -05:00
// cache of IAM info fetched in last SiteReplicationMetaInfo call
type srIAMCache struct {
sync . RWMutex
lastUpdate time . Time
srIAMInfo madmin . SRInfo // caches IAM info
}
func ( c * SiteReplicationSys ) getSRCachedIAMInfo ( ) ( info madmin . SRInfo , ok bool ) {
c . iamMetaCache . RLock ( )
defer c . iamMetaCache . RUnlock ( )
if c . iamMetaCache . lastUpdate . IsZero ( ) {
return info , false
}
if time . Since ( c . iamMetaCache . lastUpdate ) < siteHealTimeInterval {
return c . iamMetaCache . srIAMInfo , true
}
return info , false
}
func ( c * SiteReplicationSys ) srCacheIAMInfo ( info madmin . SRInfo ) {
c . iamMetaCache . Lock ( )
defer c . iamMetaCache . Unlock ( )
c . iamMetaCache . srIAMInfo = info
c . iamMetaCache . lastUpdate = time . Now ( )
}
2022-01-05 05:44:08 -05:00
// SiteReplicationMetaInfo returns the metadata info on buckets, policies etc for the replicated site
2022-01-28 18:37:55 -05:00
func ( c * SiteReplicationSys ) SiteReplicationMetaInfo ( ctx context . Context , objAPI ObjectLayer , opts madmin . SRStatusOptions ) ( info madmin . SRInfo , err error ) {
2022-01-05 05:44:08 -05:00
if objAPI == nil {
return info , errSRObjectLayerNotReady
}
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return info , nil
}
2023-10-18 11:06:57 -04:00
info . DeploymentID = globalDeploymentID ( )
2022-01-28 18:37:55 -05:00
if opts . Buckets || opts . Entity == madmin . SRBucketEntity {
var (
buckets [ ] BucketInfo
err error
)
if opts . Entity == madmin . SRBucketEntity {
2022-07-25 20:51:32 -04:00
bi , err := objAPI . GetBucketInfo ( ctx , opts . EntityValue , BucketOptions { Deleted : opts . ShowDeleted } )
2022-01-05 05:44:08 -05:00
if err != nil {
2022-07-25 20:51:32 -04:00
if isErrBucketNotFound ( err ) {
return info , nil
}
2022-04-28 05:39:00 -04:00
return info , errSRBackendIssue ( err )
2022-01-05 05:44:08 -05:00
}
2022-01-28 18:37:55 -05:00
buckets = append ( buckets , bi )
} else {
2022-07-25 20:51:32 -04:00
buckets , err = objAPI . ListBuckets ( ctx , BucketOptions { Deleted : opts . ShowDeleted } )
2022-01-05 05:44:08 -05:00
if err != nil {
2022-01-28 18:37:55 -05:00
return info , errSRBackendIssue ( err )
}
}
info . Buckets = make ( map [ string ] madmin . SRBucketInfo , len ( buckets ) )
for _ , bucketInfo := range buckets {
bucket := bucketInfo . Name
2022-07-25 20:51:32 -04:00
bucketExists := bucketInfo . Deleted . IsZero ( ) || ( ! bucketInfo . Created . IsZero ( ) && bucketInfo . Created . After ( bucketInfo . Deleted ) )
2022-04-24 05:36:31 -04:00
bms := madmin . SRBucketInfo {
Bucket : bucket ,
2022-07-25 20:51:32 -04:00
CreatedAt : bucketInfo . Created . UTC ( ) ,
DeletedAt : bucketInfo . Deleted . UTC ( ) ,
2022-04-24 05:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
if ! bucketExists {
info . Buckets [ bucket ] = bms
continue
}
2022-01-05 05:44:08 -05:00
2023-02-08 00:44:42 -05:00
meta , err := globalBucketMetadataSys . GetConfigFromDisk ( ctx , bucket )
if err != nil && ! errors . Is ( err , errConfigNotFound ) {
2022-01-28 18:37:55 -05:00
return info , errSRBackendIssue ( err )
}
2023-02-08 00:44:42 -05:00
bms . Policy = meta . PolicyConfigJSON
bms . PolicyUpdatedAt = meta . PolicyConfigUpdatedAt
if len ( meta . TaggingConfigXML ) > 0 {
tagCfgStr := base64 . StdEncoding . EncodeToString ( meta . TaggingConfigXML )
2022-01-28 18:37:55 -05:00
bms . Tags = & tagCfgStr
2023-02-08 00:44:42 -05:00
bms . TagConfigUpdatedAt = meta . TaggingConfigUpdatedAt
2022-01-05 05:44:08 -05:00
}
2023-02-08 00:44:42 -05:00
if len ( meta . VersioningConfigXML ) > 0 {
versioningCfgStr := base64 . StdEncoding . EncodeToString ( meta . VersioningConfigXML )
2022-05-31 05:57:57 -04:00
bms . Versioning = & versioningCfgStr
2023-02-08 00:44:42 -05:00
bms . VersioningConfigUpdatedAt = meta . VersioningConfigUpdatedAt
2022-05-31 05:57:57 -04:00
}
2023-02-08 00:44:42 -05:00
if len ( meta . ObjectLockConfigXML ) > 0 {
objLockStr := base64 . StdEncoding . EncodeToString ( meta . ObjectLockConfigXML )
2022-01-28 18:37:55 -05:00
bms . ObjectLockConfig = & objLockStr
2023-02-08 00:44:42 -05:00
bms . ObjectLockConfigUpdatedAt = meta . ObjectLockConfigUpdatedAt
2022-01-28 18:37:55 -05:00
}
2023-02-08 00:44:42 -05:00
if len ( meta . QuotaConfigJSON ) > 0 {
quotaConfigStr := base64 . StdEncoding . EncodeToString ( meta . QuotaConfigJSON )
2022-01-28 18:37:55 -05:00
bms . QuotaConfig = & quotaConfigStr
2023-02-08 00:44:42 -05:00
bms . QuotaConfigUpdatedAt = meta . QuotaConfigUpdatedAt
2022-01-05 05:44:08 -05:00
}
2022-01-28 18:37:55 -05:00
2023-02-08 00:44:42 -05:00
if len ( meta . EncryptionConfigXML ) > 0 {
sseConfigStr := base64 . StdEncoding . EncodeToString ( meta . EncryptionConfigXML )
2022-01-28 18:37:55 -05:00
bms . SSEConfig = & sseConfigStr
2023-02-08 00:44:42 -05:00
bms . SSEConfigUpdatedAt = meta . EncryptionConfigUpdatedAt
2022-01-28 18:37:55 -05:00
}
2023-02-08 00:44:42 -05:00
if len ( meta . ReplicationConfigXML ) > 0 {
rcfgXMLStr := base64 . StdEncoding . EncodeToString ( meta . ReplicationConfigXML )
2022-01-28 18:37:55 -05:00
bms . ReplicationConfig = & rcfgXMLStr
2023-02-08 00:44:42 -05:00
bms . ReplicationConfigUpdatedAt = meta . ReplicationConfigUpdatedAt
2022-01-28 18:37:55 -05:00
}
2023-02-08 00:44:42 -05:00
2022-01-28 18:37:55 -05:00
info . Buckets [ bucket ] = bms
2022-01-05 05:44:08 -05:00
}
}
2023-02-09 00:16:53 -05:00
if opts . Users && opts . Groups && opts . Policies && ! opts . Buckets {
// serialize SiteReplicationMetaInfo calls - if data in cache is within
// healing interval, avoid fetching IAM data again from disk.
if metaInfo , ok := c . getSRCachedIAMInfo ( ) ; ok {
return metaInfo , nil
}
}
2022-01-28 18:37:55 -05:00
if opts . Policies || opts . Entity == madmin . SRPolicyEntity {
2022-04-24 05:36:31 -04:00
var allPolicies map [ string ] PolicyDoc
2022-01-28 18:37:55 -05:00
if opts . Entity == madmin . SRPolicyEntity {
2022-04-24 05:36:31 -04:00
if p , err := globalIAMSys . store . GetPolicyDoc ( opts . EntityValue ) ; err == nil {
allPolicies = map [ string ] PolicyDoc { opts . EntityValue : p }
2022-01-28 18:37:55 -05:00
}
} else {
// Replicate IAM policies on local to all peers.
2023-02-09 00:16:53 -05:00
allPolicies , err = globalIAMSys . store . listPolicyDocs ( ctx , "" )
2022-01-28 18:37:55 -05:00
if err != nil {
return info , errSRBackendIssue ( err )
}
2022-01-05 05:44:08 -05:00
}
2022-04-24 05:36:31 -04:00
info . Policies = make ( map [ string ] madmin . SRIAMPolicy , len ( allPolicies ) )
for pname , policyDoc := range allPolicies {
policyJSON , err := json . Marshal ( policyDoc . Policy )
2022-01-05 05:44:08 -05:00
if err != nil {
return info , wrapSRErr ( err )
}
2022-04-24 05:36:31 -04:00
info . Policies [ pname ] = madmin . SRIAMPolicy { Policy : json . RawMessage ( policyJSON ) , UpdatedAt : policyDoc . UpdateDate }
2022-01-05 05:44:08 -05:00
}
}
2022-01-28 18:37:55 -05:00
if opts . Users || opts . Entity == madmin . SRUserEntity {
2022-01-05 05:44:08 -05:00
// Replicate policy mappings on local to all peers.
userPolicyMap := make ( map [ string ] MappedPolicy )
2022-01-28 18:37:55 -05:00
if opts . Entity == madmin . SRUserEntity {
if mp , ok := globalIAMSys . store . GetMappedPolicy ( opts . EntityValue , false ) ; ok {
userPolicyMap [ opts . EntityValue ] = mp
}
} else {
2022-05-27 13:26:38 -04:00
stsErr := globalIAMSys . store . loadMappedPolicies ( ctx , stsUser , false , userPolicyMap )
if stsErr != nil {
return info , errSRBackendIssue ( stsErr )
2022-01-28 18:37:55 -05:00
}
2022-05-27 13:26:38 -04:00
usrErr := globalIAMSys . store . loadMappedPolicies ( ctx , regUser , false , userPolicyMap )
if usrErr != nil {
return info , errSRBackendIssue ( usrErr )
}
2022-10-04 13:41:47 -04:00
svcErr := globalIAMSys . store . loadMappedPolicies ( ctx , svcUser , false , userPolicyMap )
if svcErr != nil {
return info , errSRBackendIssue ( svcErr )
}
2022-01-05 05:44:08 -05:00
}
info . UserPolicies = make ( map [ string ] madmin . SRPolicyMapping , len ( userPolicyMap ) )
for user , mp := range userPolicyMap {
info . UserPolicies [ user ] = madmin . SRPolicyMapping {
IsGroup : false ,
UserOrGroup : user ,
Policy : mp . Policies ,
2022-04-24 05:36:31 -04:00
UpdatedAt : mp . UpdatedAt ,
2022-01-05 05:44:08 -05:00
}
}
2022-01-28 18:37:55 -05:00
info . UserInfoMap = make ( map [ string ] madmin . UserInfo )
if opts . Entity == madmin . SRUserEntity {
if ui , err := globalIAMSys . GetUserInfo ( ctx , opts . EntityValue ) ; err == nil {
info . UserInfoMap [ opts . EntityValue ] = ui
}
} else {
2022-10-04 13:41:47 -04:00
userAccounts := make ( map [ string ] UserIdentity )
uerr := globalIAMSys . store . loadUsers ( ctx , regUser , userAccounts )
if uerr != nil {
return info , errSRBackendIssue ( uerr )
2022-01-28 18:37:55 -05:00
}
2022-10-04 13:41:47 -04:00
serr := globalIAMSys . store . loadUsers ( ctx , svcUser , userAccounts )
if serr != nil {
return info , errSRBackendIssue ( serr )
}
terr := globalIAMSys . store . loadUsers ( ctx , stsUser , userAccounts )
if terr != nil {
return info , errSRBackendIssue ( terr )
}
for k , v := range userAccounts {
if k == siteReplicatorSvcAcc {
// skip the site replicate svc account as it is
// already replicated.
continue
2022-04-28 05:39:00 -04:00
}
2022-10-04 13:41:47 -04:00
if v . Credentials . ParentUser != "" && v . Credentials . ParentUser == globalActiveCred . AccessKey {
// skip all root user service accounts.
continue
2022-04-28 05:39:00 -04:00
}
2022-10-04 13:41:47 -04:00
info . UserInfoMap [ k ] = madmin . UserInfo {
Status : madmin . AccountStatus ( v . Credentials . Status ) ,
2022-04-28 05:39:00 -04:00
}
2022-01-28 18:37:55 -05:00
}
}
}
2022-04-28 05:39:00 -04:00
2022-01-28 18:37:55 -05:00
if opts . Groups || opts . Entity == madmin . SRGroupEntity {
// Replicate policy mappings on local to all peers.
groupPolicyMap := make ( map [ string ] MappedPolicy )
2022-10-04 13:19:17 -04:00
if opts . Entity == madmin . SRGroupEntity {
2022-01-28 18:37:55 -05:00
if mp , ok := globalIAMSys . store . GetMappedPolicy ( opts . EntityValue , true ) ; ok {
groupPolicyMap [ opts . EntityValue ] = mp
}
} else {
2022-05-27 13:26:38 -04:00
stsErr := globalIAMSys . store . loadMappedPolicies ( ctx , stsUser , true , groupPolicyMap )
if stsErr != nil {
return info , errSRBackendIssue ( stsErr )
2022-01-28 18:37:55 -05:00
}
2022-05-27 13:26:38 -04:00
userErr := globalIAMSys . store . loadMappedPolicies ( ctx , regUser , true , groupPolicyMap )
if userErr != nil {
return info , errSRBackendIssue ( userErr )
}
2022-01-28 18:37:55 -05:00
}
2022-01-05 05:44:08 -05:00
2022-01-28 18:37:55 -05:00
info . GroupPolicies = make ( map [ string ] madmin . SRPolicyMapping , len ( c . state . Peers ) )
2022-01-05 05:44:08 -05:00
for group , mp := range groupPolicyMap {
2022-01-28 18:37:55 -05:00
info . GroupPolicies [ group ] = madmin . SRPolicyMapping {
2022-01-05 05:44:08 -05:00
IsGroup : true ,
UserOrGroup : group ,
Policy : mp . Policies ,
2022-04-24 05:36:31 -04:00
UpdatedAt : mp . UpdatedAt ,
2022-01-05 05:44:08 -05:00
}
}
2022-01-28 18:37:55 -05:00
info . GroupDescMap = make ( map [ string ] madmin . GroupDesc )
if opts . Entity == madmin . SRGroupEntity {
if gd , err := globalIAMSys . GetGroupDescription ( opts . EntityValue ) ; err == nil {
info . GroupDescMap [ opts . EntityValue ] = gd
}
} else {
// get users/group info on local.
2023-02-09 00:16:53 -05:00
groups , errG := globalIAMSys . store . listGroups ( ctx )
2022-01-28 18:37:55 -05:00
if errG != nil {
return info , errSRBackendIssue ( errG )
}
groupDescMap := make ( map [ string ] madmin . GroupDesc , len ( groups ) )
for _ , g := range groups {
groupDescMap [ g ] , errG = globalIAMSys . GetGroupDescription ( g )
if errG != nil {
return info , errSRBackendIssue ( errG )
}
}
for group , d := range groupDescMap {
info . GroupDescMap [ group ] = d
}
}
2022-01-05 05:44:08 -05:00
}
2023-02-09 00:16:53 -05:00
// cache SR metadata info for IAM
if opts . Users && opts . Groups && opts . Policies && ! opts . Buckets {
c . srCacheIAMInfo ( info )
}
2022-01-05 05:44:08 -05:00
return info , nil
}
2022-01-21 11:48:21 -05:00
// EditPeerCluster - edits replication configuration and updates peer endpoint.
func ( c * SiteReplicationSys ) EditPeerCluster ( ctx context . Context , peer madmin . PeerInfo ) ( madmin . ReplicateEditStatus , error ) {
sites , err := c . GetClusterInfo ( ctx )
if err != nil {
return madmin . ReplicateEditStatus { } , errSRBackendIssue ( err )
}
if ! sites . Enabled {
return madmin . ReplicateEditStatus { } , errSRNotEnabled
}
var (
found bool
admClient * madmin . AdminClient
)
2023-10-18 11:06:57 -04:00
if globalDeploymentID ( ) == peer . DeploymentID && ! peer . SyncState . Empty ( ) && ! peer . DefaultBandwidth . IsSet {
2023-09-25 18:50:52 -04:00
return madmin . ReplicateEditStatus { } , errSRInvalidRequest ( fmt . Errorf ( "a peer cluster, rather than the local cluster (endpoint=%s, deployment-id=%s) needs to be specified while setting a 'sync' replication mode" , peer . Endpoint , peer . DeploymentID ) )
2023-06-15 15:44:22 -04:00
}
2022-01-21 11:48:21 -05:00
for _ , v := range sites . Sites {
if peer . DeploymentID == v . DeploymentID {
found = true
2023-09-25 18:50:52 -04:00
if ( ! peer . SyncState . Empty ( ) || peer . DefaultBandwidth . IsSet ) && peer . Endpoint == "" { // peer.Endpoint may be "" if only sync state/bandwidth is being updated
2023-06-15 15:44:22 -04:00
break
2023-03-24 17:41:23 -04:00
}
2023-09-25 18:50:52 -04:00
if peer . Endpoint == v . Endpoint && peer . SyncState . Empty ( ) && ! peer . DefaultBandwidth . IsSet {
2022-01-21 11:48:21 -05:00
return madmin . ReplicateEditStatus { } , errSRInvalidRequest ( fmt . Errorf ( "Endpoint %s entered for deployment id %s already configured in site replication" , v . Endpoint , v . DeploymentID ) )
}
admClient , err = c . getAdminClientWithEndpoint ( ctx , v . DeploymentID , peer . Endpoint )
if err != nil {
return madmin . ReplicateEditStatus { } , errSRPeerResp ( fmt . Errorf ( "unable to create admin client for %s: %w" , v . Name , err ) )
}
// check if endpoint is reachable
2022-11-03 19:23:45 -04:00
info , err := admClient . ServerInfo ( ctx )
if err != nil {
return madmin . ReplicateEditStatus { } , errSRInvalidRequest ( fmt . Errorf ( "Endpoint %s not reachable: %w" , peer . Endpoint , err ) )
}
if info . DeploymentID != v . DeploymentID {
return madmin . ReplicateEditStatus { } , errSRInvalidRequest ( fmt . Errorf ( "Endpoint %s does not belong to deployment expected: %s (found %s) " , v . Endpoint , v . DeploymentID , info . DeploymentID ) )
2022-01-21 11:48:21 -05:00
}
}
}
if ! found {
return madmin . ReplicateEditStatus { } , errSRInvalidRequest ( fmt . Errorf ( "%s not found in existing replicated sites" , peer . DeploymentID ) )
}
2023-09-25 18:50:52 -04:00
var state srState
c . RLock ( )
2022-01-21 11:48:21 -05:00
pi := c . state . Peers [ peer . DeploymentID ]
2023-09-25 18:50:52 -04:00
state = c . state
c . RUnlock ( )
2023-02-20 05:36:13 -05:00
prevPeerInfo := pi
2023-03-24 17:41:23 -04:00
if ! peer . SyncState . Empty ( ) { // update replication to peer to be sync/async
pi . SyncState = peer . SyncState
}
if peer . Endpoint != "" { // `admin replicate update` requested an endpoint change
pi . Endpoint = peer . Endpoint
}
2022-01-21 11:48:21 -05:00
2023-09-25 18:50:52 -04:00
if peer . DefaultBandwidth . IsSet {
pi . DefaultBandwidth = peer . DefaultBandwidth
pi . DefaultBandwidth . UpdatedAt = UTCNow ( )
}
state . Peers [ peer . DeploymentID ] = pi
2023-03-24 17:41:23 -04:00
2023-09-25 18:50:52 -04:00
errs := make ( map [ string ] error , len ( state . Peers ) )
var wg sync . WaitGroup
2023-03-24 17:41:23 -04:00
2023-09-25 18:50:52 -04:00
for dID , v := range state . Peers {
2023-10-18 11:06:57 -04:00
if v . DeploymentID == globalDeploymentID ( ) {
2023-09-25 18:50:52 -04:00
continue
}
wg . Add ( 1 )
go func ( pi madmin . PeerInfo , dID string ) {
defer wg . Done ( )
admClient , err := c . getAdminClient ( ctx , dID )
if dID == peer . DeploymentID {
admClient , err = c . getAdminClientWithEndpoint ( ctx , dID , pi . Endpoint )
}
2022-01-21 11:48:21 -05:00
if err != nil {
2023-09-25 18:50:52 -04:00
errs [ dID ] = errSRPeerResp ( fmt . Errorf ( "unable to create admin client for %s: %w" , pi . Name , err ) )
return
2022-01-21 11:48:21 -05:00
}
2023-09-25 18:50:52 -04:00
if err = admClient . SRPeerEdit ( ctx , pi ) ; err != nil {
errs [ dID ] = errSRPeerResp ( fmt . Errorf ( "unable to update peer %s: %w" , pi . Name , err ) )
return
}
} ( pi , dID )
}
wg . Wait ( )
for dID , err := range errs {
if err != nil {
return madmin . ReplicateEditStatus { } , errSRPeerResp ( fmt . Errorf ( "unable to update peer %s: %w" , state . Peers [ dID ] . Name , err ) )
2022-01-21 11:48:21 -05:00
}
}
2023-03-24 17:41:23 -04:00
2022-01-21 11:48:21 -05:00
// we can now save the cluster replication configuration state.
2023-09-25 18:50:52 -04:00
if err = c . saveToDisk ( ctx , state ) ; err != nil {
2022-01-21 11:48:21 -05:00
return madmin . ReplicateEditStatus {
Status : madmin . ReplicateAddStatusPartial ,
ErrDetail : fmt . Sprintf ( "unable to save cluster-replication state on local: %v" , err ) ,
} , nil
}
2023-09-25 18:50:52 -04:00
2023-03-24 17:41:23 -04:00
if err = c . updateTargetEndpoints ( ctx , prevPeerInfo , pi ) ; err != nil {
2023-02-20 05:36:13 -05:00
return madmin . ReplicateEditStatus {
Status : madmin . ReplicateAddStatusPartial ,
ErrDetail : fmt . Sprintf ( "unable to update peer targets on local: %v" , err ) ,
} , nil
}
2022-01-21 11:48:21 -05:00
result := madmin . ReplicateEditStatus {
Success : true ,
Status : fmt . Sprintf ( "Cluster replication configuration updated with endpoint %s for peer %s successfully" , peer . Endpoint , peer . Name ) ,
}
return result , nil
}
2023-02-20 05:36:13 -05:00
func ( c * SiteReplicationSys ) updateTargetEndpoints ( ctx context . Context , prevInfo , peer madmin . PeerInfo ) error {
objAPI := newObjectLayerFn ( )
if objAPI == nil {
return errSRObjectLayerNotReady
}
buckets , err := objAPI . ListBuckets ( ctx , BucketOptions { } )
if err != nil {
return err
}
for _ , bucketInfo := range buckets {
bucket := bucketInfo . Name
ep , _ := url . Parse ( peer . Endpoint )
prevEp , _ := url . Parse ( prevInfo . Endpoint )
targets , err := globalBucketTargetSys . ListBucketTargets ( ctx , bucket )
if err != nil {
continue // site healing will take care of configuring new targets
}
for _ , target := range targets . Targets {
if target . SourceBucket == bucket &&
target . TargetBucket == bucket &&
target . Endpoint == prevEp . Host &&
target . Secure == ( prevEp . Scheme == "https" ) &&
target . Type == madmin . ReplicationService {
bucketTarget := target
bucketTarget . Secure = ep . Scheme == "https"
bucketTarget . Endpoint = ep . Host
2023-09-25 18:50:52 -04:00
if peer . DefaultBandwidth . IsSet && target . BandwidthLimit == 0 {
bucketTarget . BandwidthLimit = int64 ( peer . DefaultBandwidth . Limit )
}
2023-03-24 17:41:23 -04:00
if ! peer . SyncState . Empty ( ) {
bucketTarget . ReplicationSync = ( peer . SyncState == madmin . SyncEnabled )
}
2023-02-20 05:36:13 -05:00
err := globalBucketTargetSys . SetTarget ( ctx , bucket , & bucketTarget , true )
if err != nil {
logger . LogIf ( ctx , c . annotatePeerErr ( peer . Name , "Bucket target creation error" , err ) )
continue
}
targets , err := globalBucketTargetSys . ListBucketTargets ( ctx , bucket )
if err != nil {
logger . LogIf ( ctx , err )
continue
}
tgtBytes , err := json . Marshal ( & targets )
if err != nil {
logger . LogIf ( ctx , err )
continue
}
if _ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketTargetsFile , tgtBytes ) ; err != nil {
logger . LogIf ( ctx , err )
continue
}
}
}
}
return nil
}
2022-01-21 11:48:21 -05:00
// PeerEditReq - internal API handler to respond to a peer cluster's request
// to edit endpoint.
func ( c * SiteReplicationSys ) PeerEditReq ( ctx context . Context , arg madmin . PeerInfo ) error {
ourName := ""
for i := range c . state . Peers {
p := c . state . Peers [ i ]
if p . DeploymentID == arg . DeploymentID {
p . Endpoint = arg . Endpoint
2023-09-25 18:50:52 -04:00
if arg . DefaultBandwidth . IsSet {
if arg . DefaultBandwidth . UpdatedAt . After ( p . DefaultBandwidth . UpdatedAt ) {
p . DefaultBandwidth = arg . DefaultBandwidth
}
}
if ! arg . SyncState . Empty ( ) {
p . SyncState = arg . SyncState
}
2022-01-21 11:48:21 -05:00
c . state . Peers [ arg . DeploymentID ] = p
}
2023-10-18 11:06:57 -04:00
if p . DeploymentID == globalDeploymentID ( ) {
2022-01-21 11:48:21 -05:00
ourName = p . Name
}
}
if err := c . saveToDisk ( ctx , c . state ) ; err != nil {
2022-08-04 19:10:08 -04:00
return errSRBackendIssue ( fmt . Errorf ( "unable to save cluster-replication state to drive on %s: %v" , ourName , err ) )
2022-01-21 11:48:21 -05:00
}
return nil
}
2022-04-24 05:36:31 -04:00
2023-02-13 11:09:52 -05:00
const siteHealTimeInterval = 30 * time . Second
2022-04-24 05:36:31 -04:00
func ( c * SiteReplicationSys ) startHealRoutine ( ctx context . Context , objAPI ObjectLayer ) {
2022-11-08 11:55:55 -05:00
ctx , cancel := globalLeaderLock . GetLock ( ctx )
defer cancel ( )
2022-08-24 16:46:09 -04:00
2022-04-24 05:36:31 -04:00
healTimer := time . NewTimer ( siteHealTimeInterval )
defer healTimer . Stop ( )
2023-02-13 11:09:52 -05:00
var maxRefreshDurationSecondsForLog float64 = 10 // 10 seconds..
2022-04-24 05:36:31 -04:00
for {
select {
case <- healTimer . C :
c . RLock ( )
enabled := c . enabled
c . RUnlock ( )
2022-05-18 18:37:58 -04:00
if enabled {
2023-02-13 11:09:52 -05:00
refreshStart := time . Now ( )
2022-05-18 18:37:58 -04:00
c . healIAMSystem ( ctx , objAPI ) // heal IAM system first
c . healBuckets ( ctx , objAPI ) // heal buckets subsequently
2023-02-13 11:09:52 -05:00
took := time . Since ( refreshStart ) . Seconds ( )
if took > maxRefreshDurationSecondsForLog {
// Log if we took a lot of time.
logger . Info ( "Site replication healing refresh took %.2fs" , took )
}
// wait for 200 millisecond, if we are experience lot of I/O
waitForLowIO ( runtime . GOMAXPROCS ( 0 ) , 200 * time . Millisecond , currentHTTPIO )
2022-04-24 05:36:31 -04:00
}
2022-05-18 18:37:58 -04:00
healTimer . Reset ( siteHealTimeInterval )
2022-04-24 05:36:31 -04:00
case <- ctx . Done ( ) :
return
}
}
}
type srBucketStatsSummary struct {
madmin . SRBucketStatsSummary
meta srBucketMetaInfo
}
type srPolicyStatsSummary struct {
madmin . SRPolicyStatsSummary
policy srPolicy
}
type srUserStatsSummary struct {
madmin . SRUserStatsSummary
userInfo srUserInfo
userPolicy srPolicyMapping
}
type srGroupStatsSummary struct {
madmin . SRGroupStatsSummary
groupDesc srGroupDesc
groupPolicy srPolicyMapping
}
type srStatusInfo struct {
// SRStatusInfo returns detailed status on site replication status
Enabled bool
MaxBuckets int // maximum buckets seen across sites
MaxUsers int // maximum users seen across sites
MaxGroups int // maximum groups seen across sites
MaxPolicies int // maximum policies across sites
Sites map [ string ] madmin . PeerInfo // deployment->sitename
StatsSummary map [ string ] madmin . SRSiteSummary // map of deployment id -> site stat
// BucketStats map of bucket to slice of deployment IDs with stats. This is populated only if there are
// mismatches or if a specific bucket's stats are requested
BucketStats map [ string ] map [ string ] srBucketStatsSummary
// PolicyStats map of policy to slice of deployment IDs with stats. This is populated only if there are
// mismatches or if a specific bucket's stats are requested
PolicyStats map [ string ] map [ string ] srPolicyStatsSummary
// UserStats map of user to slice of deployment IDs with stats. This is populated only if there are
// mismatches or if a specific bucket's stats are requested
UserStats map [ string ] map [ string ] srUserStatsSummary
// GroupStats map of group to slice of deployment IDs with stats. This is populated only if there are
// mismatches or if a specific bucket's stats are requested
GroupStats map [ string ] map [ string ] srGroupStatsSummary
2023-08-30 04:00:59 -04:00
Metrics madmin . SRMetricsSummary
2022-04-24 05:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
// SRBucketDeleteOp - type of delete op
type SRBucketDeleteOp string
const (
// MarkDelete creates .minio.sys/buckets/.deleted/<bucket> vol entry to hold onto deleted bucket's state
// until peers are synced in site replication setup.
MarkDelete SRBucketDeleteOp = "MarkDelete"
// Purge deletes the .minio.sys/buckets/.deleted/<bucket> vol entry
Purge SRBucketDeleteOp = "Purge"
// NoOp no action needed
NoOp SRBucketDeleteOp = "NoOp"
)
// Empty returns true if this Op is not set
func ( s SRBucketDeleteOp ) Empty ( ) bool {
return string ( s ) == "" || string ( s ) == string ( NoOp )
}
func getSRBucketDeleteOp ( isSiteReplicated bool ) SRBucketDeleteOp {
if ! isSiteReplicated {
return NoOp
}
return MarkDelete
}
2022-04-24 05:36:31 -04:00
func ( c * SiteReplicationSys ) healBuckets ( ctx context . Context , objAPI ObjectLayer ) error {
2022-07-25 20:51:32 -04:00
buckets , err := c . listBuckets ( ctx )
2022-04-24 05:36:31 -04:00
if err != nil {
return err
}
2022-07-25 20:51:32 -04:00
for _ , bi := range buckets {
bucket := bi . Name
info , err := c . siteReplicationStatus ( ctx , objAPI , madmin . SRStatusOptions {
Entity : madmin . SRBucketEntity ,
EntityValue : bucket ,
ShowDeleted : true ,
} )
if err != nil {
logger . LogIf ( ctx , err )
continue
}
c . healBucket ( ctx , objAPI , bucket , info )
if bi . Deleted . IsZero ( ) || ( ! bi . Created . IsZero ( ) && bi . Deleted . Before ( bi . Created ) ) {
c . healVersioningMetadata ( ctx , objAPI , bucket , info )
c . healOLockConfigMetadata ( ctx , objAPI , bucket , info )
c . healSSEMetadata ( ctx , objAPI , bucket , info )
c . healBucketReplicationConfig ( ctx , objAPI , bucket , info )
c . healBucketPolicies ( ctx , objAPI , bucket , info )
c . healTagMetadata ( ctx , objAPI , bucket , info )
c . healBucketQuotaConfig ( ctx , objAPI , bucket , info )
}
2022-04-24 05:36:31 -04:00
// Notification and ILM are site specific settings.
}
return nil
}
func ( c * SiteReplicationSys ) healTagMetadata ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
bs := info . BucketStats [ bucket ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestTaggingConfig * string
)
for dID , ss := range bs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . meta . TagConfigUpdatedAt
latestID = dID
latestTaggingConfig = ss . meta . Tags
}
// avoid considering just created buckets as latest. Perhaps this site
// just joined cluster replication and yet to be sync'd
if ss . meta . CreatedAt . Equal ( ss . meta . TagConfigUpdatedAt ) {
continue
}
if ss . meta . TagConfigUpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . meta . TagConfigUpdatedAt
latestID = dID
latestTaggingConfig = ss . meta . Tags
}
}
latestPeerName = info . Sites [ latestID ] . Name
var latestTaggingConfigBytes [ ] byte
var err error
if latestTaggingConfig != nil {
latestTaggingConfigBytes , err = base64 . StdEncoding . DecodeString ( * latestTaggingConfig )
if err != nil {
return err
}
}
for dID , bStatus := range bs {
if ! bStatus . TagMismatch {
continue
}
if isBucketMetadataEqual ( latestTaggingConfig , bStatus . meta . Tags ) {
continue
}
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-06-28 21:09:20 -04:00
if _ , err := globalBucketMetadataSys . Update ( ctx , bucket , bucketTaggingConfig , latestTaggingConfigBytes ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal tagging metadata from peer site %s : %w" , latestPeerName , err ) )
2022-04-24 05:36:31 -04:00
}
continue
}
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return wrapSRErr ( err )
}
peerName := info . Sites [ dID ] . Name
err = admClient . SRPeerReplicateBucketMeta ( ctx , madmin . SRBucketMeta {
Type : madmin . SRBucketMetaTypeTags ,
Bucket : bucket ,
Tags : latestTaggingConfig ,
} )
if err != nil {
2022-06-20 13:48:11 -04:00
logger . LogIf ( ctx , c . annotatePeerErr ( peerName , replicateBucketMetadata ,
2023-01-26 14:11:54 -05:00
fmt . Errorf ( "Unable to heal tagging metadata for peer %s from peer %s : %w" , peerName , latestPeerName , err ) ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
func ( c * SiteReplicationSys ) healBucketPolicies ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
bs := info . BucketStats [ bucket ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestIAMPolicy json . RawMessage
)
for dID , ss := range bs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . meta . PolicyUpdatedAt
latestID = dID
latestIAMPolicy = ss . meta . Policy
}
// avoid considering just created buckets as latest. Perhaps this site
// just joined cluster replication and yet to be sync'd
if ss . meta . CreatedAt . Equal ( ss . meta . PolicyUpdatedAt ) {
continue
}
if ss . meta . PolicyUpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . meta . PolicyUpdatedAt
latestID = dID
latestIAMPolicy = ss . meta . Policy
}
}
latestPeerName = info . Sites [ latestID ] . Name
for dID , bStatus := range bs {
if ! bStatus . PolicyMismatch {
continue
}
if strings . EqualFold ( string ( latestIAMPolicy ) , string ( bStatus . meta . Policy ) ) {
continue
}
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-06-28 21:09:20 -04:00
if _ , err := globalBucketMetadataSys . Update ( ctx , bucket , bucketPolicyConfig , latestIAMPolicy ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal bucket policy metadata from peer site %s : %w" , latestPeerName , err ) )
2022-04-24 05:36:31 -04:00
}
continue
}
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return wrapSRErr ( err )
}
peerName := info . Sites [ dID ] . Name
if err = admClient . SRPeerReplicateBucketMeta ( ctx , madmin . SRBucketMeta {
2022-06-28 21:09:20 -04:00
Type : madmin . SRBucketMetaTypePolicy ,
Bucket : bucket ,
Policy : latestIAMPolicy ,
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} ) ; err != nil {
2022-06-20 13:48:11 -04:00
logger . LogIf ( ctx , c . annotatePeerErr ( peerName , replicateBucketMetadata ,
2023-01-26 14:11:54 -05:00
fmt . Errorf ( "Unable to heal bucket policy metadata for peer %s from peer %s : %w" ,
2022-06-20 13:48:11 -04:00
peerName , latestPeerName , err ) ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
func ( c * SiteReplicationSys ) healBucketQuotaConfig ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
bs := info . BucketStats [ bucket ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestQuotaConfig * string
latestQuotaConfigBytes [ ] byte
)
for dID , ss := range bs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . meta . QuotaConfigUpdatedAt
latestID = dID
latestQuotaConfig = ss . meta . QuotaConfig
}
// avoid considering just created buckets as latest. Perhaps this site
// just joined cluster replication and yet to be sync'd
if ss . meta . CreatedAt . Equal ( ss . meta . QuotaConfigUpdatedAt ) {
continue
}
if ss . meta . QuotaConfigUpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . meta . QuotaConfigUpdatedAt
latestID = dID
latestQuotaConfig = ss . meta . QuotaConfig
}
}
var err error
if latestQuotaConfig != nil {
latestQuotaConfigBytes , err = base64 . StdEncoding . DecodeString ( * latestQuotaConfig )
if err != nil {
return err
}
}
latestPeerName = info . Sites [ latestID ] . Name
for dID , bStatus := range bs {
if ! bStatus . QuotaCfgMismatch {
continue
}
if isBucketMetadataEqual ( latestQuotaConfig , bStatus . meta . QuotaConfig ) {
continue
}
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-06-28 21:09:20 -04:00
if _ , err := globalBucketMetadataSys . Update ( ctx , bucket , bucketQuotaConfigFile , latestQuotaConfigBytes ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal quota metadata from peer site %s : %w" , latestPeerName , err ) )
2022-04-24 05:36:31 -04:00
}
continue
}
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return wrapSRErr ( err )
}
peerName := info . Sites [ dID ] . Name
if err = admClient . SRPeerReplicateBucketMeta ( ctx , madmin . SRBucketMeta {
2022-06-28 21:09:20 -04:00
Type : madmin . SRBucketMetaTypeQuotaConfig ,
Bucket : bucket ,
Quota : latestQuotaConfigBytes ,
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} ) ; err != nil {
2022-06-20 13:48:11 -04:00
logger . LogIf ( ctx , c . annotatePeerErr ( peerName , replicateBucketMetadata ,
2023-01-26 14:11:54 -05:00
fmt . Errorf ( "Unable to heal quota config metadata for peer %s from peer %s : %w" ,
2022-06-20 13:48:11 -04:00
peerName , latestPeerName , err ) ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
2022-05-07 21:39:40 -04:00
func ( c * SiteReplicationSys ) healVersioningMetadata ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestVersioningConfig * string
)
bs := info . BucketStats [ bucket ]
for dID , ss := range bs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . meta . VersioningConfigUpdatedAt
latestID = dID
latestVersioningConfig = ss . meta . Versioning
}
// avoid considering just created buckets as latest. Perhaps this site
// just joined cluster replication and yet to be sync'd
if ss . meta . CreatedAt . Equal ( ss . meta . VersioningConfigUpdatedAt ) {
continue
}
if ss . meta . VersioningConfigUpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . meta . VersioningConfigUpdatedAt
latestID = dID
latestVersioningConfig = ss . meta . Versioning
}
}
latestPeerName = info . Sites [ latestID ] . Name
var latestVersioningConfigBytes [ ] byte
var err error
if latestVersioningConfig != nil {
latestVersioningConfigBytes , err = base64 . StdEncoding . DecodeString ( * latestVersioningConfig )
if err != nil {
return err
}
}
for dID , bStatus := range bs {
if ! bStatus . VersioningConfigMismatch {
continue
}
if isBucketMetadataEqual ( latestVersioningConfig , bStatus . meta . Versioning ) {
continue
}
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-06-28 21:09:20 -04:00
if _ , err := globalBucketMetadataSys . Update ( ctx , bucket , bucketVersioningConfig , latestVersioningConfigBytes ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal versioning metadata from peer site %s : %w" , latestPeerName , err ) )
2022-05-07 21:39:40 -04:00
}
continue
}
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return wrapSRErr ( err )
}
peerName := info . Sites [ dID ] . Name
err = admClient . SRPeerReplicateBucketMeta ( ctx , madmin . SRBucketMeta {
Type : madmin . SRBucketMetaTypeVersionConfig ,
Bucket : bucket ,
Versioning : latestVersioningConfig ,
2022-06-28 21:09:20 -04:00
UpdatedAt : lastUpdate ,
2022-05-07 21:39:40 -04:00
} )
if err != nil {
2022-06-20 13:48:11 -04:00
logger . LogIf ( ctx , c . annotatePeerErr ( peerName , replicateBucketMetadata ,
2023-01-26 14:11:54 -05:00
fmt . Errorf ( "Unable to heal versioning config metadata for peer %s from peer %s : %w" ,
2022-06-20 13:48:11 -04:00
peerName , latestPeerName , err ) ) )
2022-05-07 21:39:40 -04:00
}
}
return nil
}
2022-04-24 05:36:31 -04:00
func ( c * SiteReplicationSys ) healSSEMetadata ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestSSEConfig * string
)
bs := info . BucketStats [ bucket ]
for dID , ss := range bs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . meta . SSEConfigUpdatedAt
latestID = dID
latestSSEConfig = ss . meta . SSEConfig
}
// avoid considering just created buckets as latest. Perhaps this site
// just joined cluster replication and yet to be sync'd
if ss . meta . CreatedAt . Equal ( ss . meta . SSEConfigUpdatedAt ) {
continue
}
if ss . meta . SSEConfigUpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . meta . SSEConfigUpdatedAt
latestID = dID
latestSSEConfig = ss . meta . SSEConfig
}
}
latestPeerName = info . Sites [ latestID ] . Name
var latestSSEConfigBytes [ ] byte
var err error
if latestSSEConfig != nil {
latestSSEConfigBytes , err = base64 . StdEncoding . DecodeString ( * latestSSEConfig )
if err != nil {
return err
}
}
for dID , bStatus := range bs {
if ! bStatus . SSEConfigMismatch {
continue
}
if isBucketMetadataEqual ( latestSSEConfig , bStatus . meta . SSEConfig ) {
continue
}
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-06-28 21:09:20 -04:00
if _ , err := globalBucketMetadataSys . Update ( ctx , bucket , bucketSSEConfig , latestSSEConfigBytes ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal sse metadata from peer site %s : %w" , latestPeerName , err ) )
2022-04-24 05:36:31 -04:00
}
continue
}
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return wrapSRErr ( err )
}
peerName := info . Sites [ dID ] . Name
err = admClient . SRPeerReplicateBucketMeta ( ctx , madmin . SRBucketMeta {
Type : madmin . SRBucketMetaTypeSSEConfig ,
Bucket : bucket ,
SSEConfig : latestSSEConfig ,
2022-06-28 21:09:20 -04:00
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} )
if err != nil {
2022-06-20 13:48:11 -04:00
logger . LogIf ( ctx , c . annotatePeerErr ( peerName , replicateBucketMetadata ,
2023-01-26 14:11:54 -05:00
fmt . Errorf ( "Unable to heal SSE config metadata for peer %s from peer %s : %w" ,
2022-06-20 13:48:11 -04:00
peerName , latestPeerName , err ) ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
func ( c * SiteReplicationSys ) healOLockConfigMetadata ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
bs := info . BucketStats [ bucket ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestObjLockConfig * string
)
for dID , ss := range bs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . meta . ObjectLockConfigUpdatedAt
latestID = dID
latestObjLockConfig = ss . meta . ObjectLockConfig
}
// avoid considering just created buckets as latest. Perhaps this site
// just joined cluster replication and yet to be sync'd
if ss . meta . CreatedAt . Equal ( ss . meta . ObjectLockConfigUpdatedAt ) {
continue
}
if ss . meta . ObjectLockConfig != nil && ss . meta . ObjectLockConfigUpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . meta . ObjectLockConfigUpdatedAt
latestID = dID
latestObjLockConfig = ss . meta . ObjectLockConfig
}
}
latestPeerName = info . Sites [ latestID ] . Name
var latestObjLockConfigBytes [ ] byte
var err error
if latestObjLockConfig != nil {
latestObjLockConfigBytes , err = base64 . StdEncoding . DecodeString ( * latestObjLockConfig )
if err != nil {
return err
}
}
for dID , bStatus := range bs {
if ! bStatus . OLockConfigMismatch {
continue
}
if isBucketMetadataEqual ( latestObjLockConfig , bStatus . meta . ObjectLockConfig ) {
continue
}
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-06-28 21:09:20 -04:00
if _ , err := globalBucketMetadataSys . Update ( ctx , bucket , objectLockConfig , latestObjLockConfigBytes ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal objectlock config metadata from peer site %s : %w" , latestPeerName , err ) )
2022-04-24 05:36:31 -04:00
}
continue
}
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return wrapSRErr ( err )
}
peerName := info . Sites [ dID ] . Name
err = admClient . SRPeerReplicateBucketMeta ( ctx , madmin . SRBucketMeta {
2022-06-28 21:09:20 -04:00
Type : madmin . SRBucketMetaTypeObjectLockConfig ,
Bucket : bucket ,
Tags : latestObjLockConfig ,
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} )
if err != nil {
2022-06-20 13:48:11 -04:00
logger . LogIf ( ctx , c . annotatePeerErr ( peerName , replicateBucketMetadata ,
2023-01-26 14:11:54 -05:00
fmt . Errorf ( "Unable to heal object lock config metadata for peer %s from peer %s : %w" ,
2022-06-20 13:48:11 -04:00
peerName , latestPeerName , err ) ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
2022-07-25 20:51:32 -04:00
func ( c * SiteReplicationSys ) purgeDeletedBucket ( ctx context . Context , objAPI ObjectLayer , bucket string ) {
z , ok := objAPI . ( * erasureServerPools )
if ! ok {
return
}
2023-01-17 09:07:47 -05:00
z . s3Peer . DeleteBucket ( context . Background ( ) , pathJoin ( minioMetaBucket , bucketMetaPrefix , deletedBucketsPrefix , bucket ) , DeleteBucketOptions { } )
2022-07-25 20:51:32 -04:00
}
2022-04-24 05:36:31 -04:00
2022-07-25 20:51:32 -04:00
// healBucket creates/deletes the bucket according to latest state across clusters participating in site replication.
func ( c * SiteReplicationSys ) healBucket ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
bs := info . BucketStats [ bucket ]
2022-04-24 05:36:31 -04:00
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
2022-07-25 20:51:32 -04:00
numSites := len ( c . state . Peers )
mostRecent := func ( d1 , d2 time . Time ) time . Time {
if d1 . IsZero ( ) {
return d2
}
if d2 . IsZero ( ) {
return d1
}
if d1 . After ( d2 ) {
return d1
}
return d2
}
2022-04-24 05:36:31 -04:00
var (
latestID string
lastUpdate time . Time
2022-07-25 20:51:32 -04:00
withB [ ] string
missingB [ ] string
deletedCnt int
2022-04-24 05:36:31 -04:00
)
for dID , ss := range bs {
if lastUpdate . IsZero ( ) {
2022-07-25 20:51:32 -04:00
lastUpdate = mostRecent ( ss . meta . CreatedAt , ss . meta . DeletedAt )
2022-04-24 05:36:31 -04:00
latestID = dID
}
2022-07-25 20:51:32 -04:00
recentUpdt := mostRecent ( ss . meta . CreatedAt , ss . meta . DeletedAt )
if recentUpdt . After ( lastUpdate ) {
lastUpdate = recentUpdt
2022-04-24 05:36:31 -04:00
latestID = dID
}
2022-07-25 20:51:32 -04:00
if ss . BucketMarkedDeleted {
deletedCnt ++
}
if ss . HasBucket {
withB = append ( withB , dID )
} else {
missingB = append ( missingB , dID )
}
2022-04-24 05:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
2022-04-24 05:36:31 -04:00
latestPeerName := info . Sites [ latestID ] . Name
bStatus := info . BucketStats [ bucket ] [ latestID ] . meta
2022-07-25 20:51:32 -04:00
isMakeBucket := len ( missingB ) > 0
deleteOp := NoOp
2023-10-18 11:06:57 -04:00
if latestID != globalDeploymentID ( ) {
2022-07-25 20:51:32 -04:00
return nil
2022-04-24 05:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
if lastUpdate . Equal ( bStatus . DeletedAt ) {
isMakeBucket = false
switch {
case len ( withB ) == numSites && deletedCnt == numSites :
deleteOp = NoOp
case len ( withB ) == 0 && len ( missingB ) == numSites :
deleteOp = Purge
default :
deleteOp = MarkDelete
2022-04-24 05:36:31 -04:00
}
}
2022-07-25 20:51:32 -04:00
if isMakeBucket {
var opts MakeBucketOptions
optsMap := make ( map [ string ] string )
optsMap [ "versioningEnabled" ] = "true"
opts . VersioningEnabled = true
opts . CreatedAt = bStatus . CreatedAt
2022-10-27 12:46:52 -04:00
optsMap [ "createdAt" ] = bStatus . CreatedAt . UTC ( ) . Format ( time . RFC3339Nano )
2022-07-25 20:51:32 -04:00
if bStatus . ObjectLockConfig != nil {
config , err := base64 . StdEncoding . DecodeString ( * bStatus . ObjectLockConfig )
2022-04-24 05:36:31 -04:00
if err != nil {
2022-07-25 20:51:32 -04:00
return err
2022-04-24 05:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
if bytes . Equal ( [ ] byte ( string ( config ) ) , enabledBucketObjectLockConfig ) {
optsMap [ "lockEnabled" ] = "true"
opts . LockEnabled = true
}
}
for _ , dID := range missingB {
peerName := info . Sites [ dID ] . Name
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-07-25 20:51:32 -04:00
err := c . PeerBucketMakeWithVersioningHandler ( ctx , bucket , opts )
if err != nil {
return c . annotateErr ( makeBucketWithVersion , fmt . Errorf ( "error healing bucket for site replication %w from %s -> %s" ,
err , latestPeerName , peerName ) )
}
} else {
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return c . annotateErr ( configureReplication , fmt . Errorf ( "unable to use admin client for %s: %w" , dID , err ) )
}
if err = admClient . SRPeerBucketOps ( ctx , bucket , madmin . MakeWithVersioningBktOp , optsMap ) ; err != nil {
return c . annotatePeerErr ( peerName , makeBucketWithVersion , err )
}
if err = admClient . SRPeerBucketOps ( ctx , bucket , madmin . ConfigureReplBktOp , nil ) ; err != nil {
return c . annotatePeerErr ( peerName , configureReplication , err )
}
2022-04-24 05:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
}
if len ( missingB ) > 0 {
// configure replication from current cluster to other clusters
err := c . PeerBucketConfigureReplHandler ( ctx , bucket )
if err != nil {
return c . annotateErr ( configureReplication , err )
2022-04-24 05:36:31 -04:00
}
2022-07-25 20:51:32 -04:00
}
return nil
}
// all buckets are marked deleted across sites at this point. It should be safe to purge the .minio.sys/buckets/.deleted/<bucket> entry
// from disk
if deleteOp == Purge {
for _ , dID := range missingB {
peerName := info . Sites [ dID ] . Name
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-07-25 20:51:32 -04:00
c . purgeDeletedBucket ( ctx , objAPI , bucket )
} else {
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return c . annotateErr ( configureReplication , fmt . Errorf ( "unable to use admin client for %s: %w" , dID , err ) )
}
if err = admClient . SRPeerBucketOps ( ctx , bucket , madmin . PurgeDeletedBucketOp , nil ) ; err != nil {
return c . annotatePeerErr ( peerName , deleteBucket , err )
}
2022-04-24 05:36:31 -04:00
}
}
}
2022-07-25 20:51:32 -04:00
// Mark buckets deleted on remaining peers
if deleteOp == MarkDelete {
for _ , dID := range withB {
peerName := info . Sites [ dID ] . Name
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-07-25 20:51:32 -04:00
err := c . PeerBucketDeleteHandler ( ctx , bucket , DeleteBucketOptions {
Force : true ,
} )
if err != nil {
return c . annotateErr ( deleteBucket , fmt . Errorf ( "error healing bucket for site replication %w from %s -> %s" ,
err , latestPeerName , peerName ) )
}
} else {
admClient , err := c . getAdminClient ( ctx , dID )
if err != nil {
return c . annotateErr ( configureReplication , fmt . Errorf ( "unable to use admin client for %s: %w" , dID , err ) )
}
if err = admClient . SRPeerBucketOps ( ctx , bucket , madmin . ForceDeleteBucketBktOp , nil ) ; err != nil {
return c . annotatePeerErr ( peerName , deleteBucket , err )
}
}
2022-04-24 05:36:31 -04:00
}
}
return nil
}
func ( c * SiteReplicationSys ) healBucketReplicationConfig ( ctx context . Context , objAPI ObjectLayer , bucket string , info srStatusInfo ) error {
bs := info . BucketStats [ bucket ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var replMismatch bool
for _ , ss := range bs {
if ss . ReplicationCfgMismatch {
replMismatch = true
break
}
}
2022-05-26 20:57:23 -04:00
rcfg , _ , err := globalBucketMetadataSys . GetReplicationConfig ( ctx , bucket )
if err != nil {
2022-06-02 15:34:03 -04:00
_ , ok := err . ( BucketReplicationConfigNotFound )
if ! ok {
return err
}
2022-05-26 20:57:23 -04:00
replMismatch = true
}
2023-02-20 05:36:13 -05:00
var (
epDeplIDMap = make ( map [ string ] string )
arnTgtMap = make ( map [ string ] madmin . BucketTarget )
)
if targetsPtr , _ := globalBucketTargetSys . ListBucketTargets ( ctx , bucket ) ; targetsPtr != nil {
for _ , t := range targetsPtr . Targets {
arnTgtMap [ t . Arn ] = t
}
}
for _ , p := range c . state . Peers {
epDeplIDMap [ p . Endpoint ] = p . DeploymentID
}
// fix stale ARN's in replication config and endpoint mismatch between site config and
// targets associated to this config.
2022-06-02 15:34:03 -04:00
if rcfg != nil {
2023-02-20 05:36:13 -05:00
for _ , rule := range rcfg . Rules {
if rule . Status != sreplication . Status ( replication . Disabled ) {
tgt , isValidARN := arnTgtMap [ rule . Destination . ARN ] // detect stale ARN in replication config
_ , epFound := epDeplIDMap [ tgt . URL ( ) . String ( ) ] // detect end point change at site level
if ! isValidARN || ! epFound {
replMismatch = true
break
}
}
}
}
if rcfg != nil && ! replMismatch {
2022-06-02 15:34:03 -04:00
// validate remote targets on current cluster for this bucket
_ , apiErr := validateReplicationDestination ( ctx , bucket , rcfg , false )
if apiErr != noError {
replMismatch = true
}
2022-05-26 20:57:23 -04:00
}
2022-06-02 15:34:03 -04:00
2022-04-24 05:36:31 -04:00
if replMismatch {
2022-06-20 13:48:11 -04:00
logger . LogIf ( ctx , c . annotateErr ( configureReplication , c . PeerBucketConfigureReplHandler ( ctx , bucket ) ) )
2022-04-24 05:36:31 -04:00
}
return nil
}
func isBucketMetadataEqual ( one , two * string ) bool {
switch {
case one == nil && two == nil :
return true
case one == nil || two == nil :
return false
default :
return strings . EqualFold ( * one , * two )
}
}
func ( c * SiteReplicationSys ) healIAMSystem ( ctx context . Context , objAPI ObjectLayer ) error {
info , err := c . siteReplicationStatus ( ctx , objAPI , madmin . SRStatusOptions {
Users : true ,
Policies : true ,
Groups : true ,
} )
if err != nil {
return err
}
for policy := range info . PolicyStats {
c . healPolicies ( ctx , objAPI , policy , info )
}
for user := range info . UserStats {
c . healUsers ( ctx , objAPI , user , info )
}
for group := range info . GroupStats {
c . healGroups ( ctx , objAPI , group , info )
}
for user := range info . UserStats {
2022-08-23 14:11:45 -04:00
c . healUserPolicies ( ctx , objAPI , user , info )
2022-04-24 05:36:31 -04:00
}
for group := range info . GroupStats {
2022-08-23 14:11:45 -04:00
c . healGroupPolicies ( ctx , objAPI , group , info )
2022-04-24 05:36:31 -04:00
}
return nil
}
// heal iam policies present on this site to peers, provided current cluster has the most recent update.
func ( c * SiteReplicationSys ) healPolicies ( ctx context . Context , objAPI ObjectLayer , policy string , info srStatusInfo ) error {
// create IAM policy on peer cluster if missing
ps := info . PolicyStats [ policy ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestPolicyStat srPolicyStatsSummary
)
for dID , ss := range ps {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . policy . UpdatedAt
latestID = dID
latestPolicyStat = ss
}
if ! ss . policy . UpdatedAt . IsZero ( ) && ss . policy . UpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . policy . UpdatedAt
latestID = dID
latestPolicyStat = ss
}
}
2023-10-18 11:06:57 -04:00
if latestID != globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
// heal only from the site with latest info.
return nil
}
latestPeerName = info . Sites [ latestID ] . Name
// heal policy of peers if peer does not have it.
for dID , pStatus := range ps {
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
continue
}
if ! pStatus . PolicyMismatch && pStatus . HasPolicy {
continue
}
peerName := info . Sites [ dID ] . Name
err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
2022-07-01 16:19:13 -04:00
Type : madmin . SRIAMItemPolicy ,
Name : policy ,
Policy : latestPolicyStat . policy . Policy ,
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} )
if err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal IAM policy %s from peer site %s -> site %s : %w" , policy , latestPeerName , peerName , err ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
// heal user policy mappings present on this site to peers, provided current cluster has the most recent update.
2022-08-23 14:11:45 -04:00
func ( c * SiteReplicationSys ) healUserPolicies ( ctx context . Context , objAPI ObjectLayer , user string , info srStatusInfo ) error {
2022-04-24 05:36:31 -04:00
// create user policy mapping on peer cluster if missing
us := info . UserStats [ user ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestUserStat srUserStatsSummary
)
for dID , ss := range us {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . userPolicy . UpdatedAt
latestID = dID
latestUserStat = ss
}
if ! ss . userPolicy . UpdatedAt . IsZero ( ) && ss . userPolicy . UpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . userPolicy . UpdatedAt
latestID = dID
latestUserStat = ss
}
}
2023-10-18 11:06:57 -04:00
if latestID != globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
// heal only from the site with latest info.
return nil
}
latestPeerName = info . Sites [ latestID ] . Name
// heal policy of peers if peer does not have it.
for dID , pStatus := range us {
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
continue
}
if ! pStatus . PolicyMismatch && pStatus . HasPolicyMapping {
continue
}
if isPolicyMappingEqual ( pStatus . userPolicy , latestUserStat . userPolicy ) {
continue
}
peerName := info . Sites [ dID ] . Name
err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemPolicyMapping ,
PolicyMapping : & madmin . SRPolicyMapping {
UserOrGroup : user ,
IsGroup : false ,
Policy : latestUserStat . userPolicy . Policy ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} )
if err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal IAM user policy mapping for %s from peer site %s -> site %s : %w" , user , latestPeerName , peerName , err ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
// heal group policy mappings present on this site to peers, provided current cluster has the most recent update.
2022-08-23 14:11:45 -04:00
func ( c * SiteReplicationSys ) healGroupPolicies ( ctx context . Context , objAPI ObjectLayer , group string , info srStatusInfo ) error {
2022-04-24 05:36:31 -04:00
// create group policy mapping on peer cluster if missing
gs := info . GroupStats [ group ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestGroupStat srGroupStatsSummary
)
for dID , ss := range gs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . groupPolicy . UpdatedAt
latestID = dID
latestGroupStat = ss
}
if ! ss . groupPolicy . UpdatedAt . IsZero ( ) && ss . groupPolicy . UpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . groupPolicy . UpdatedAt
latestID = dID
latestGroupStat = ss
}
}
2023-10-18 11:06:57 -04:00
if latestID != globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
// heal only from the site with latest info.
return nil
}
latestPeerName = info . Sites [ latestID ] . Name
// heal policy of peers if peer does not have it.
for dID , pStatus := range gs {
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
continue
}
if ! pStatus . PolicyMismatch && pStatus . HasPolicyMapping {
continue
}
if isPolicyMappingEqual ( pStatus . groupPolicy , latestGroupStat . groupPolicy ) {
continue
}
peerName := info . Sites [ dID ] . Name
err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemPolicyMapping ,
PolicyMapping : & madmin . SRPolicyMapping {
UserOrGroup : group ,
IsGroup : true ,
Policy : latestGroupStat . groupPolicy . Policy ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} )
if err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal IAM group policy mapping for %s from peer site %s -> site %s : %w" , group , latestPeerName , peerName , err ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
2022-10-04 13:41:47 -04:00
// heal all users and their service accounts that are present on this site,
// provided current cluster has the most recent update.
2022-04-24 05:36:31 -04:00
func ( c * SiteReplicationSys ) healUsers ( ctx context . Context , objAPI ObjectLayer , user string , info srStatusInfo ) error {
// create user if missing; fix user policy mapping if missing
us := info . UserStats [ user ]
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestUserStat srUserStatsSummary
)
for dID , ss := range us {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . userInfo . UserInfo . UpdatedAt
latestID = dID
latestUserStat = ss
}
if ! ss . userInfo . UserInfo . UpdatedAt . IsZero ( ) && ss . userInfo . UserInfo . UpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . userInfo . UserInfo . UpdatedAt
latestID = dID
latestUserStat = ss
}
}
2023-10-18 11:06:57 -04:00
if latestID != globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
// heal only from the site with latest info.
return nil
}
latestPeerName = info . Sites [ latestID ] . Name
for dID , uStatus := range us {
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
continue
}
if ! uStatus . UserInfoMismatch {
continue
}
if isUserInfoEqual ( latestUserStat . userInfo . UserInfo , uStatus . userInfo . UserInfo ) {
continue
}
2022-04-28 05:39:00 -04:00
peerName := info . Sites [ dID ] . Name
2022-07-01 16:19:13 -04:00
u , ok := globalIAMSys . GetUser ( ctx , user )
2022-04-24 05:36:31 -04:00
if ! ok {
continue
}
2022-07-01 16:19:13 -04:00
creds := u . Credentials
2022-04-28 05:39:00 -04:00
if creds . IsServiceAccount ( ) {
claims , err := globalIAMSys . GetClaimsForSvcAcc ( ctx , creds . AccessKey )
if err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal service account %s from peer site %s -> %s : %w" , user , latestPeerName , peerName , err ) )
2022-04-28 05:39:00 -04:00
continue
}
_ , policy , err := globalIAMSys . GetServiceAccount ( ctx , creds . AccessKey )
if err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal service account %s from peer site %s -> %s : %w" , user , latestPeerName , peerName , err ) )
2022-04-28 05:39:00 -04:00
continue
}
var policyJSON [ ] byte
if policy != nil {
policyJSON , err = json . Marshal ( policy )
if err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal service account %s from peer site %s -> %s : %w" , user , latestPeerName , peerName , err ) )
2022-04-28 05:39:00 -04:00
continue
}
}
if err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemSvcAcc ,
SvcAccChange : & madmin . SRSvcAccChange {
Create : & madmin . SRSvcAccCreate {
Parent : creds . ParentUser ,
AccessKey : creds . AccessKey ,
SecretKey : creds . SecretKey ,
Groups : creds . Groups ,
Claims : claims ,
SessionPolicy : json . RawMessage ( policyJSON ) ,
Status : creds . Status ,
2023-05-17 20:05:36 -04:00
Name : creds . Name ,
Description : creds . Description ,
2023-02-27 13:10:22 -05:00
Expiration : & creds . Expiration ,
2022-04-28 05:39:00 -04:00
} ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : lastUpdate ,
2022-04-28 05:39:00 -04:00
} ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal service account %s from peer site %s -> %s : %w" , user , latestPeerName , peerName , err ) )
2022-04-28 05:39:00 -04:00
}
continue
}
if creds . IsTemp ( ) && ! creds . IsExpired ( ) {
2023-03-10 19:21:51 -05:00
var parentPolicy string
2022-04-28 05:39:00 -04:00
u , err := globalIAMSys . GetUserInfo ( ctx , creds . ParentUser )
if err != nil {
2023-03-10 19:21:51 -05:00
// Parent may be "virtual" (for ldap, oidc, client tls auth,
// custom auth plugin), so in such cases we apply no parent
// policy. The session token will contain info about policy to
// be applied.
if ! errors . Is ( err , errNoSuchUser ) {
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal temporary credentials %s from peer site %s -> %s : %w" , user , latestPeerName , peerName , err ) )
continue
}
} else {
parentPolicy = u . PolicyName
2022-04-28 05:39:00 -04:00
}
// Call hook for site replication.
if err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemSTSAcc ,
STSCredential : & madmin . SRSTSCredential {
AccessKey : creds . AccessKey ,
SecretKey : creds . SecretKey ,
SessionToken : creds . SessionToken ,
ParentUser : creds . ParentUser ,
2023-03-10 19:21:51 -05:00
ParentPolicyMapping : parentPolicy ,
2022-04-28 05:39:00 -04:00
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : lastUpdate ,
2022-04-28 05:39:00 -04:00
} ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal temporary credentials %s from peer site %s -> %s : %w" , user , latestPeerName , peerName , err ) )
2022-04-28 05:39:00 -04:00
}
2022-04-24 05:36:31 -04:00
continue
}
if err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemIAMUser ,
IAMUser : & madmin . SRIAMUser {
AccessKey : user ,
IsDeleteReq : false ,
UserReq : & madmin . AddOrUpdateUserReq {
SecretKey : creds . SecretKey ,
Status : latestUserStat . userInfo . Status ,
} ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal user %s from peer site %s -> %s : %w" , user , latestPeerName , peerName , err ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
func ( c * SiteReplicationSys ) healGroups ( ctx context . Context , objAPI ObjectLayer , group string , info srStatusInfo ) error {
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return nil
}
var (
latestID , latestPeerName string
lastUpdate time . Time
latestGroupStat srGroupStatsSummary
)
// create group if missing; fix group policy mapping if missing
gs , ok := info . GroupStats [ group ]
if ! ok {
return nil
}
for dID , ss := range gs {
if lastUpdate . IsZero ( ) {
lastUpdate = ss . groupDesc . UpdatedAt
latestID = dID
latestGroupStat = ss
}
if ! ss . groupDesc . UpdatedAt . IsZero ( ) && ss . groupDesc . UpdatedAt . After ( lastUpdate ) {
lastUpdate = ss . groupDesc . UpdatedAt
latestID = dID
latestGroupStat = ss
}
}
2023-10-18 11:06:57 -04:00
if latestID != globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
// heal only from the site with latest info.
return nil
}
latestPeerName = info . Sites [ latestID ] . Name
for dID , gStatus := range gs {
2023-10-18 11:06:57 -04:00
if dID == globalDeploymentID ( ) {
2022-04-24 05:36:31 -04:00
continue
}
if ! gStatus . GroupDescMismatch {
continue
}
if isGroupDescEqual ( latestGroupStat . groupDesc . GroupDesc , gStatus . groupDesc . GroupDesc ) {
continue
}
peerName := info . Sites [ dID ] . Name
if err := c . IAMChangeHook ( ctx , madmin . SRIAMItem {
Type : madmin . SRIAMItemGroupInfo ,
GroupInfo : & madmin . SRGroupInfo {
UpdateReq : madmin . GroupAddRemove {
Group : group ,
Status : madmin . GroupStatus ( latestGroupStat . groupDesc . Status ) ,
Members : latestGroupStat . groupDesc . Members ,
IsRemove : false ,
} ,
} ,
2022-07-01 16:19:13 -04:00
UpdatedAt : lastUpdate ,
2022-04-24 05:36:31 -04:00
} ) ; err != nil {
2023-01-26 14:11:54 -05:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to heal group %s from peer site %s -> site %s : %w" , group , latestPeerName , peerName , err ) )
2022-04-24 05:36:31 -04:00
}
}
return nil
}
func isGroupDescEqual ( g1 , g2 madmin . GroupDesc ) bool {
if g1 . Name != g2 . Name ||
g1 . Status != g2 . Status ||
g1 . Policy != g2 . Policy {
return false
}
if len ( g1 . Members ) != len ( g2 . Members ) {
return false
}
for _ , v1 := range g1 . Members {
var found bool
for _ , v2 := range g2 . Members {
if v1 == v2 {
found = true
break
}
}
if ! found {
return false
}
}
return true
}
func isUserInfoEqual ( u1 , u2 madmin . UserInfo ) bool {
if u1 . PolicyName != u2 . PolicyName ||
u1 . Status != u2 . Status ||
u1 . SecretKey != u2 . SecretKey {
return false
}
for len ( u1 . MemberOf ) != len ( u2 . MemberOf ) {
return false
}
for _ , v1 := range u1 . MemberOf {
var found bool
for _ , v2 := range u2 . MemberOf {
if v1 == v2 {
found = true
break
}
}
if ! found {
return false
}
}
return true
}
func isPolicyMappingEqual ( p1 , p2 srPolicyMapping ) bool {
return p1 . Policy == p2 . Policy && p1 . IsGroup == p2 . IsGroup && p1 . UserOrGroup == p2 . UserOrGroup
}
2022-10-25 13:52:29 -04:00
type srPeerInfo struct {
madmin . PeerInfo
EndpointURL * url . URL
}
// getPeerForUpload returns the site replication peer handling this upload. Defaults to local cluster otherwise
func ( c * SiteReplicationSys ) getPeerForUpload ( deplID string ) ( pi srPeerInfo , local bool ) {
ci , _ := c . GetClusterInfo ( GlobalContext )
if ! ci . Enabled {
return pi , true
}
for _ , site := range ci . Sites {
if deplID == site . DeploymentID {
ep , _ := url . Parse ( site . Endpoint )
pi = srPeerInfo {
PeerInfo : site ,
EndpointURL : ep ,
}
2023-10-18 11:06:57 -04:00
return pi , site . DeploymentID == globalDeploymentID ( )
2022-10-25 13:52:29 -04:00
}
}
return pi , true
}
2022-11-14 10:16:40 -05:00
// startResync initiates resync of data to peerSite specified. The overall site resync status
// is maintained in .minio.sys/buckets/site-replication/resync/<deployment-id.meta>, while collecting
// individual bucket resync status in .minio.sys/buckets/<bucket-name>/replication/resync.bin
func ( c * SiteReplicationSys ) startResync ( ctx context . Context , objAPI ObjectLayer , peer madmin . PeerInfo ) ( res madmin . SRResyncOpStatus , err error ) {
if ! c . isEnabled ( ) {
return res , errSRNotEnabled
}
if objAPI == nil {
return res , errSRObjectLayerNotReady
}
2023-10-18 11:06:57 -04:00
if peer . DeploymentID == globalDeploymentID ( ) {
2022-11-14 10:16:40 -05:00
return res , errSRResyncToSelf
}
if _ , ok := c . state . Peers [ peer . DeploymentID ] ; ! ok {
return res , errSRPeerNotFound
}
rs , err := globalSiteResyncMetrics . siteStatus ( ctx , objAPI , peer . DeploymentID )
if err != nil {
return res , err
}
if rs . Status == ResyncStarted {
return res , errSRResyncStarted
}
var buckets [ ] BucketInfo
buckets , err = objAPI . ListBuckets ( ctx , BucketOptions { } )
if err != nil {
return res , err
}
rs = newSiteResyncStatus ( peer . DeploymentID , buckets )
defer func ( ) {
if err != nil {
rs . Status = ResyncFailed
saveSiteResyncMetadata ( ctx , rs , objAPI )
globalSiteResyncMetrics . updateState ( rs )
}
} ( )
2023-10-14 01:17:22 -04:00
if err := globalSiteResyncMetrics . updateState ( rs ) ; err != nil {
2022-11-14 10:16:40 -05:00
return res , err
}
for _ , bi := range buckets {
bucket := bi . Name
if _ , err := getReplicationConfig ( ctx , bucket ) ; err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
Status : ResyncFailed . String ( ) ,
} )
continue
}
// mark remote target for this deployment with the new reset id
tgtArn := globalBucketTargetSys . getRemoteARNForPeer ( bucket , peer )
if tgtArn == "" {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : fmt . Sprintf ( "no valid remote target found for this peer %s (%s)" , peer . Name , peer . DeploymentID ) ,
Bucket : bucket ,
} )
continue
}
target := globalBucketTargetSys . GetRemoteBucketTargetByArn ( ctx , bucket , tgtArn )
target . ResetBeforeDate = UTCNow ( )
target . ResetID = rs . ResyncID
if err = globalBucketTargetSys . SetTarget ( ctx , bucket , & target , true ) ; err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
targets , err := globalBucketTargetSys . ListBucketTargets ( ctx , bucket )
if err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
tgtBytes , err := json . Marshal ( & targets )
if err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
if _ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketTargetsFile , tgtBytes ) ; err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
if err := globalReplicationPool . resyncer . start ( ctx , objAPI , resyncOpts {
bucket : bucket ,
arn : tgtArn ,
resyncID : rs . ResyncID ,
} ) ; err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
}
res = madmin . SRResyncOpStatus {
Status : ResyncStarted . String ( ) ,
OpType : "start" ,
ResyncID : rs . ResyncID ,
}
if len ( res . Buckets ) > 0 {
res . ErrDetail = "partial failure in starting site resync"
}
return res , nil
}
// cancelResync stops an ongoing site level resync for the peer specified.
func ( c * SiteReplicationSys ) cancelResync ( ctx context . Context , objAPI ObjectLayer , peer madmin . PeerInfo ) ( res madmin . SRResyncOpStatus , err error ) {
if ! c . isEnabled ( ) {
return res , errSRNotEnabled
}
if objAPI == nil {
return res , errSRObjectLayerNotReady
}
2023-10-18 11:06:57 -04:00
if peer . DeploymentID == globalDeploymentID ( ) {
2022-11-14 10:16:40 -05:00
return res , errSRResyncToSelf
}
if _ , ok := c . state . Peers [ peer . DeploymentID ] ; ! ok {
return res , errSRPeerNotFound
}
rs , err := globalSiteResyncMetrics . siteStatus ( ctx , objAPI , peer . DeploymentID )
if err != nil {
return res , err
}
switch rs . Status {
case ResyncCanceled :
return res , errSRResyncCanceled
case ResyncCompleted , NoResync :
return res , errSRNoResync
}
res = madmin . SRResyncOpStatus {
Status : rs . Status . String ( ) ,
OpType : "cancel" ,
ResyncID : rs . ResyncID ,
}
switch rs . Status {
case ResyncCanceled :
return res , errSRResyncCanceled
case ResyncCompleted , NoResync :
return res , errSRNoResync
}
targets := globalBucketTargetSys . ListTargets ( ctx , "" , string ( madmin . ReplicationService ) )
// clear the remote target resetID set while initiating resync to stop replication
for _ , t := range targets {
if t . ResetID == rs . ResyncID {
// get tgt with credentials
tgt := globalBucketTargetSys . GetRemoteBucketTargetByArn ( ctx , t . SourceBucket , t . Arn )
tgt . ResetID = ""
bucket := t . SourceBucket
if err = globalBucketTargetSys . SetTarget ( ctx , bucket , & tgt , true ) ; err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
targets , err := globalBucketTargetSys . ListBucketTargets ( ctx , bucket )
if err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
tgtBytes , err := json . Marshal ( & targets )
if err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
if _ , err = globalBucketMetadataSys . Update ( ctx , bucket , bucketTargetsFile , tgtBytes ) ; err != nil {
res . Buckets = append ( res . Buckets , madmin . ResyncBucketStatus {
ErrDetail : err . Error ( ) ,
Bucket : bucket ,
} )
continue
}
// update resync state for the bucket
globalReplicationPool . resyncer . Lock ( )
m , ok := globalReplicationPool . resyncer . statusMap [ bucket ]
if ! ok {
m = newBucketResyncStatus ( bucket )
}
if st , ok := m . TargetsMap [ t . Arn ] ; ok {
st . LastUpdate = UTCNow ( )
st . ResyncStatus = ResyncCanceled
m . TargetsMap [ t . Arn ] = st
m . LastUpdate = UTCNow ( )
}
globalReplicationPool . resyncer . statusMap [ bucket ] = m
globalReplicationPool . resyncer . Unlock ( )
}
}
rs . Status = ResyncCanceled
rs . LastUpdate = UTCNow ( )
if err := saveSiteResyncMetadata ( ctx , rs , objAPI ) ; err != nil {
return res , err
}
2023-06-15 11:05:08 -04:00
select {
case globalReplicationPool . resyncer . resyncCancelCh <- struct { } { } :
case <- ctx . Done ( ) :
}
2022-11-14 10:16:40 -05:00
globalSiteResyncMetrics . updateState ( rs )
res . Status = rs . Status . String ( )
return res , nil
}
const (
siteResyncMetaFormat = 1
siteResyncMetaVersionV1 = 1
siteResyncMetaVersion = siteResyncMetaVersionV1
siteResyncSaveInterval = 10 * time . Second
)
func newSiteResyncStatus ( dID string , buckets [ ] BucketInfo ) SiteResyncStatus {
now := UTCNow ( )
s := SiteResyncStatus {
Version : siteResyncMetaVersion ,
Status : ResyncStarted ,
DeplID : dID ,
TotBuckets : len ( buckets ) ,
BucketStatuses : make ( map [ string ] ResyncStatusType ) ,
}
for _ , bi := range buckets {
s . BucketStatuses [ bi . Name ] = ResyncPending
}
s . ResyncID = mustGetUUID ( )
s . StartTime = now
s . LastUpdate = now
return s
}
// load site resync metadata from disk
func loadSiteResyncMetadata ( ctx context . Context , objAPI ObjectLayer , dID string ) ( rs SiteResyncStatus , e error ) {
data , err := readConfig ( GlobalContext , objAPI , getSRResyncFilePath ( dID ) )
if err != nil {
return rs , err
}
if len ( data ) == 0 {
// Seems to be empty.
return rs , nil
}
if len ( data ) <= 4 {
return rs , fmt . Errorf ( "site resync: no data" )
}
// Read resync meta header
switch binary . LittleEndian . Uint16 ( data [ 0 : 2 ] ) {
case siteResyncMetaFormat :
default :
return rs , fmt . Errorf ( "resyncMeta: unknown format: %d" , binary . LittleEndian . Uint16 ( data [ 0 : 2 ] ) )
}
switch binary . LittleEndian . Uint16 ( data [ 2 : 4 ] ) {
case siteResyncMetaVersion :
default :
return rs , fmt . Errorf ( "resyncMeta: unknown version: %d" , binary . LittleEndian . Uint16 ( data [ 2 : 4 ] ) )
}
// OK, parse data.
if _ , err = rs . UnmarshalMsg ( data [ 4 : ] ) ; err != nil {
return rs , err
}
switch rs . Version {
case siteResyncMetaVersionV1 :
default :
return rs , fmt . Errorf ( "unexpected resync meta version: %d" , rs . Version )
}
return rs , nil
}
// save resync status of peer to resync/depl-id.meta
func saveSiteResyncMetadata ( ctx context . Context , ss SiteResyncStatus , objectAPI ObjectLayer ) error {
2023-10-14 01:17:22 -04:00
if objectAPI == nil {
return errSRObjectLayerNotReady
}
2022-11-14 10:16:40 -05:00
data := make ( [ ] byte , 4 , ss . Msgsize ( ) + 4 )
// Initialize the resync meta header.
binary . LittleEndian . PutUint16 ( data [ 0 : 2 ] , siteResyncMetaFormat )
binary . LittleEndian . PutUint16 ( data [ 2 : 4 ] , siteResyncMetaVersion )
buf , err := ss . MarshalMsg ( data )
if err != nil {
return err
}
return saveConfig ( ctx , objectAPI , getSRResyncFilePath ( ss . DeplID ) , buf )
}
func getSRResyncFilePath ( dID string ) string {
return pathJoin ( siteResyncPrefix , dID + ".meta" )
}
2023-08-30 04:00:59 -04:00
func ( c * SiteReplicationSys ) getDeplIDForEndpoint ( ep string ) ( dID string , err error ) {
if ep == "" {
return dID , fmt . Errorf ( "no deployment id found for endpoint %s" , ep )
}
c . RLock ( )
defer c . RUnlock ( )
if ! c . enabled {
return dID , errSRNotEnabled
}
for _ , peer := range c . state . Peers {
if ep == peer . Endpoint {
return peer . DeploymentID , nil
}
}
return dID , fmt . Errorf ( "no deployment id found for endpoint %s" , ep )
}
func ( c * SiteReplicationSys ) getSiteMetrics ( ctx context . Context ) ( madmin . SRMetricsSummary , error ) {
if ! c . isEnabled ( ) {
return madmin . SRMetricsSummary { } , errSRNotEnabled
}
peerSMetricsList := globalNotificationSys . GetClusterSiteMetrics ( ctx )
var sm madmin . SRMetricsSummary
sm . Metrics = make ( map [ string ] madmin . SRMetric )
for _ , peer := range peerSMetricsList {
sm . ActiveWorkers . Avg += peer . ActiveWorkers . Avg
sm . ActiveWorkers . Curr += peer . ActiveWorkers . Curr
if peer . ActiveWorkers . Max > sm . ActiveWorkers . Max {
sm . ActiveWorkers . Max += peer . ActiveWorkers . Max
}
sm . Queued . Avg . Bytes += peer . Queued . Avg . Bytes
sm . Queued . Avg . Count += peer . Queued . Avg . Count
sm . Queued . Curr . Bytes += peer . Queued . Curr . Bytes
sm . Queued . Curr . Count += peer . Queued . Curr . Count
if peer . Queued . Max . Count > sm . Queued . Max . Count {
sm . Queued . Max . Bytes = peer . Queued . Max . Bytes
sm . Queued . Max . Count = peer . Queued . Max . Count
}
sm . ReplicaCount += peer . ReplicaCount
sm . ReplicaSize += peer . ReplicaSize
for dID , v := range peer . Metrics {
v2 , ok := sm . Metrics [ dID ]
if ! ok {
v2 = madmin . SRMetric { }
v2 . Failed . ErrCounts = make ( map [ string ] int )
}
// use target endpoint metrics from node which has been up the longest
if v2 . LastOnline . After ( v . LastOnline ) || v2 . LastOnline . IsZero ( ) {
v2 . Endpoint = v . Endpoint
v2 . LastOnline = v . LastOnline
v2 . Latency = v . Latency
v2 . Online = v . Online
v2 . TotalDowntime = v . TotalDowntime
v2 . DeploymentID = v . DeploymentID
}
v2 . ReplicatedCount += v . ReplicatedCount
v2 . ReplicatedSize += v . ReplicatedSize
v2 . Failed = v2 . Failed . Add ( v . Failed )
for k , v := range v . Failed . ErrCounts {
v2 . Failed . ErrCounts [ k ] += v
}
if v2 . XferStats == nil {
v2 . XferStats = make ( map [ replication . MetricName ] replication . XferStats )
}
for rm , x := range v . XferStats {
x2 , ok := v2 . XferStats [ replication . MetricName ( rm ) ]
if ! ok {
x2 = replication . XferStats { }
}
x2 . AvgRate += x . Avg
x2 . CurrRate += x . Curr
if x . Peak > x2 . PeakRate {
x2 . PeakRate = x . Peak
}
v2 . XferStats [ replication . MetricName ( rm ) ] = x2
}
sm . Metrics [ dID ] = v2
}
}
sm . Uptime = UTCNow ( ) . Unix ( ) - globalBootTime . Unix ( )
return sm , nil
}