github.com/minio/madmin-go/v3@v3.0.51/cluster-commands.go (about) 1 // 2 // Copyright (c) 2015-2022 MinIO, Inc. 3 // 4 // This file is part of MinIO Object Storage stack 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU Affero General Public License as 8 // published by the Free Software Foundation, either version 3 of the 9 // License, or (at your option) any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU Affero General Public License for more details. 15 // 16 // You should have received a copy of the GNU Affero General Public License 17 // along with this program. If not, see <http://www.gnu.org/licenses/>. 18 // 19 20 package madmin 21 22 import ( 23 "context" 24 "encoding/json" 25 "io" 26 "net/http" 27 "net/url" 28 "strconv" 29 "time" 30 31 "github.com/minio/minio-go/v7/pkg/replication" 32 ) 33 34 // SiteReplAPIVersion holds the supported version of the server Replication API 35 const SiteReplAPIVersion = "1" 36 37 // PeerSite - represents a cluster/site to be added to the set of replicated 38 // sites. 39 type PeerSite struct { 40 Name string `json:"name"` 41 Endpoint string `json:"endpoints"` 42 AccessKey string `json:"accessKey"` 43 SecretKey string `json:"secretKey"` 44 } 45 46 // Meaningful values for ReplicateAddStatus.Status 47 const ( 48 ReplicateAddStatusSuccess = "Requested sites were configured for replication successfully." 49 ReplicateAddStatusPartial = "Some sites could not be configured for replication." 50 ) 51 52 // ReplicateAddStatus - returns status of add request. 53 type ReplicateAddStatus struct { 54 Success bool `json:"success"` 55 Status string `json:"status"` 56 ErrDetail string `json:"errorDetail,omitempty"` 57 InitialSyncErrorMessage string `json:"initialSyncErrorMessage,omitempty"` 58 } 59 60 // SRAddOptions holds SR Add options 61 type SRAddOptions struct { 62 ReplicateILMExpiry bool 63 } 64 65 func (o *SRAddOptions) getURLValues() url.Values { 66 urlValues := make(url.Values) 67 urlValues.Set("replicateILMExpiry", strconv.FormatBool(o.ReplicateILMExpiry)) 68 return urlValues 69 } 70 71 // SiteReplicationAdd - sends the SR add API call. 72 func (adm *AdminClient) SiteReplicationAdd(ctx context.Context, sites []PeerSite, opts SRAddOptions) (ReplicateAddStatus, error) { 73 sitesBytes, err := json.Marshal(sites) 74 if err != nil { 75 return ReplicateAddStatus{}, nil 76 } 77 encBytes, err := EncryptData(adm.getSecretKey(), sitesBytes) 78 if err != nil { 79 return ReplicateAddStatus{}, err 80 } 81 82 q := opts.getURLValues() 83 q.Set("api-version", SiteReplAPIVersion) 84 85 reqData := requestData{ 86 relPath: adminAPIPrefix + "/site-replication/add", 87 content: encBytes, 88 queryValues: q, 89 } 90 91 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 92 defer closeResponse(resp) 93 if err != nil { 94 return ReplicateAddStatus{}, err 95 } 96 97 if resp.StatusCode != http.StatusOK { 98 return ReplicateAddStatus{}, httpRespToErrorResponse(resp) 99 } 100 101 b, err := io.ReadAll(resp.Body) 102 if err != nil { 103 return ReplicateAddStatus{}, err 104 } 105 106 var res ReplicateAddStatus 107 if err = json.Unmarshal(b, &res); err != nil { 108 return ReplicateAddStatus{}, err 109 } 110 111 return res, nil 112 } 113 114 // SiteReplicationInfo - contains cluster replication information. 115 type SiteReplicationInfo struct { 116 Enabled bool `json:"enabled"` 117 Name string `json:"name,omitempty"` 118 Sites []PeerInfo `json:"sites,omitempty"` 119 ServiceAccountAccessKey string `json:"serviceAccountAccessKey,omitempty"` 120 } 121 122 // SiteReplicationInfo - returns cluster replication information. 123 func (adm *AdminClient) SiteReplicationInfo(ctx context.Context) (info SiteReplicationInfo, err error) { 124 q := make(url.Values) 125 q.Set("api-version", SiteReplAPIVersion) 126 127 reqData := requestData{ 128 relPath: adminAPIPrefix + "/site-replication/info", 129 queryValues: q, 130 } 131 132 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 133 defer closeResponse(resp) 134 if err != nil { 135 return info, err 136 } 137 138 if resp.StatusCode != http.StatusOK { 139 return info, httpRespToErrorResponse(resp) 140 } 141 142 b, err := io.ReadAll(resp.Body) 143 if err != nil { 144 return info, err 145 } 146 147 err = json.Unmarshal(b, &info) 148 return info, err 149 } 150 151 // SRPeerJoinReq - arg body for SRPeerJoin 152 type SRPeerJoinReq struct { 153 SvcAcctAccessKey string `json:"svcAcctAccessKey"` 154 SvcAcctSecretKey string `json:"svcAcctSecretKey"` 155 SvcAcctParent string `json:"svcAcctParent"` 156 Peers map[string]PeerInfo `json:"peers"` 157 UpdatedAt time.Time `json:"updatedAt"` 158 } 159 160 // PeerInfo - contains some properties of a cluster peer. 161 type PeerInfo struct { 162 Endpoint string `json:"endpoint"` 163 Name string `json:"name"` 164 // Deployment ID is useful as it is immutable - though endpoint may 165 // change. 166 DeploymentID string `json:"deploymentID"` 167 SyncState SyncStatus `json:"sync"` // whether to enable| disable synchronous replication 168 DefaultBandwidth BucketBandwidth `json:"defaultbandwidth"` // bandwidth limit per bucket in bytes/sec 169 ReplicateILMExpiry bool `json:"replicate-ilm-expiry"` 170 } 171 172 // BucketBandwidth has default bandwidth limit per bucket in bytes/sec 173 type BucketBandwidth struct { 174 Limit uint64 `json:"bandwidthLimitPerBucket"` 175 IsSet bool `json:"set"` 176 UpdatedAt time.Time `json:"updatedAt,omitempty"` 177 } 178 179 type SyncStatus string // change in sync state 180 const ( 181 SyncEnabled SyncStatus = "enable" 182 SyncDisabled SyncStatus = "disable" 183 ) 184 185 func (s SyncStatus) Empty() bool { 186 return s != SyncDisabled && s != SyncEnabled 187 } 188 189 // SRPeerJoin - used only by minio server to send SR join requests to peer 190 // servers. 191 func (adm *AdminClient) SRPeerJoin(ctx context.Context, r SRPeerJoinReq) error { 192 b, err := json.Marshal(r) 193 if err != nil { 194 return err 195 } 196 encBuf, err := EncryptData(adm.getSecretKey(), b) 197 if err != nil { 198 return err 199 } 200 201 q := make(url.Values) 202 q.Set("api-version", SiteReplAPIVersion) 203 204 reqData := requestData{ 205 relPath: adminAPIPrefix + "/site-replication/peer/join", 206 content: encBuf, 207 queryValues: q, 208 } 209 210 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 211 defer closeResponse(resp) 212 if err != nil { 213 return err 214 } 215 216 if resp.StatusCode != http.StatusOK { 217 return httpRespToErrorResponse(resp) 218 } 219 220 return nil 221 } 222 223 // BktOp represents the bucket operation being requested. 224 type BktOp string 225 226 // BktOp value constants. 227 const ( 228 // make bucket and enable versioning 229 MakeWithVersioningBktOp BktOp = "make-with-versioning" 230 // add replication configuration 231 ConfigureReplBktOp BktOp = "configure-replication" 232 // delete bucket (forceDelete = off) 233 DeleteBucketBktOp BktOp = "delete-bucket" 234 // delete bucket (forceDelete = on) 235 ForceDeleteBucketBktOp BktOp = "force-delete-bucket" 236 // purge bucket 237 PurgeDeletedBucketOp BktOp = "purge-deleted-bucket" 238 ) 239 240 // SRPeerBucketOps - tells peers to create bucket and setup replication. 241 func (adm *AdminClient) SRPeerBucketOps(ctx context.Context, bucket string, op BktOp, opts map[string]string) error { 242 v := url.Values{} 243 v.Add("bucket", bucket) 244 v.Add("operation", string(op)) 245 246 // For make-bucket, bucket options may be sent via `opts` 247 if op == MakeWithVersioningBktOp || op == DeleteBucketBktOp { 248 for k, val := range opts { 249 v.Add(k, val) 250 } 251 } 252 253 v.Set("api-version", SiteReplAPIVersion) 254 255 reqData := requestData{ 256 queryValues: v, 257 relPath: adminAPIPrefix + "/site-replication/peer/bucket-ops", 258 } 259 260 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 261 defer closeResponse(resp) 262 if err != nil { 263 return err 264 } 265 266 if resp.StatusCode != http.StatusOK { 267 return httpRespToErrorResponse(resp) 268 } 269 270 return nil 271 } 272 273 // SRIAMItem.Type constants. 274 const ( 275 SRIAMItemPolicy = "policy" 276 SRIAMItemPolicyMapping = "policy-mapping" 277 SRIAMItemGroupInfo = "group-info" 278 SRIAMItemCredential = "credential" 279 SRIAMItemSvcAcc = "service-account" 280 SRIAMItemSTSAcc = "sts-account" 281 SRIAMItemIAMUser = "iam-user" 282 ) 283 284 // SRSvcAccCreate - create operation 285 type SRSvcAccCreate struct { 286 Parent string `json:"parent"` 287 AccessKey string `json:"accessKey"` 288 SecretKey string `json:"secretKey"` 289 Groups []string `json:"groups"` 290 Claims map[string]interface{} `json:"claims"` 291 SessionPolicy json.RawMessage `json:"sessionPolicy"` 292 Status string `json:"status"` 293 Name string `json:"name"` 294 Description string `json:"description"` 295 Expiration *time.Time `json:"expiration,omitempty"` 296 } 297 298 // SRSvcAccUpdate - update operation 299 type SRSvcAccUpdate struct { 300 AccessKey string `json:"accessKey"` 301 SecretKey string `json:"secretKey"` 302 Status string `json:"status"` 303 Name string `json:"name"` 304 Description string `json:"description"` 305 SessionPolicy json.RawMessage `json:"sessionPolicy"` 306 Expiration *time.Time `json:"expiration,omitempty"` 307 } 308 309 // SRSvcAccDelete - delete operation 310 type SRSvcAccDelete struct { 311 AccessKey string `json:"accessKey"` 312 } 313 314 // SRSvcAccChange - sum-type to represent an svc account change. 315 type SRSvcAccChange struct { 316 Create *SRSvcAccCreate `json:"crSvcAccCreate"` 317 Update *SRSvcAccUpdate `json:"crSvcAccUpdate"` 318 Delete *SRSvcAccDelete `json:"crSvcAccDelete"` 319 } 320 321 // SRPolicyMapping - represents mapping of a policy to a user or group. 322 type SRPolicyMapping struct { 323 UserOrGroup string `json:"userOrGroup"` 324 UserType int `json:"userType"` 325 IsGroup bool `json:"isGroup"` 326 Policy string `json:"policy"` 327 CreatedAt time.Time `json:"createdAt,omitempty"` 328 UpdatedAt time.Time `json:"updatedAt,omitempty"` 329 } 330 331 // SRSTSCredential - represents an STS credential to be replicated. 332 type SRSTSCredential struct { 333 AccessKey string `json:"accessKey"` 334 SecretKey string `json:"secretKey"` 335 SessionToken string `json:"sessionToken"` 336 ParentUser string `json:"parentUser"` 337 ParentPolicyMapping string `json:"parentPolicyMapping,omitempty"` 338 } 339 340 // SRIAMUser - represents a regular (IAM) user to be replicated. A nil UserReq 341 // implies that a user delete operation should be replicated on the peer cluster. 342 type SRIAMUser struct { 343 AccessKey string `json:"accessKey"` 344 IsDeleteReq bool `json:"isDeleteReq"` 345 UserReq *AddOrUpdateUserReq `json:"userReq"` 346 } 347 348 // SRGroupInfo - represents a regular (IAM) user to be replicated. 349 type SRGroupInfo struct { 350 UpdateReq GroupAddRemove `json:"updateReq"` 351 } 352 353 // SRCredInfo - represents a credential change (create/update/delete) to be 354 // replicated. This replaces `SvcAccChange`, `STSCredential` and `IAMUser` and 355 // will DEPRECATE them. 356 type SRCredInfo struct { 357 AccessKey string `json:"accessKey"` 358 359 // This type corresponds to github.com/minio/minio/cmd.IAMUserType 360 IAMUserType int `json:"iamUserType"` 361 362 IsDeleteReq bool `json:"isDeleteReq,omitempty"` 363 364 // This is the JSON encoded value of github.com/minio/minio/cmd.UserIdentity 365 UserIdentityJSON json.RawMessage `json:"userIdentityJSON"` 366 } 367 368 // SRIAMItem - represents an IAM object that will be copied to a peer. 369 type SRIAMItem struct { 370 Type string `json:"type"` 371 372 // Name and Policy below are used when Type == SRIAMItemPolicy 373 Name string `json:"name"` 374 Policy json.RawMessage `json:"policy"` 375 376 // Used when Type == SRIAMItemPolicyMapping 377 PolicyMapping *SRPolicyMapping `json:"policyMapping"` 378 379 // Used when Type = SRIAMItemGroupInfo 380 GroupInfo *SRGroupInfo `json:"groupInfo"` 381 382 // Used when Type = SRIAMItemCredential 383 CredentialInfo *SRCredInfo `json:"credentialChange"` 384 385 // Used when Type == SRIAMItemSvcAcc 386 SvcAccChange *SRSvcAccChange `json:"serviceAccountChange"` 387 388 // Used when Type = SRIAMItemSTSAcc 389 STSCredential *SRSTSCredential `json:"stsCredential"` 390 391 // Used when Type = SRIAMItemIAMUser 392 IAMUser *SRIAMUser `json:"iamUser"` 393 394 // UpdatedAt - timestamp of last update 395 UpdatedAt time.Time `json:"updatedAt,omitempty"` 396 } 397 398 // SRPeerReplicateIAMItem - copies an IAM object to a peer cluster. 399 func (adm *AdminClient) SRPeerReplicateIAMItem(ctx context.Context, item SRIAMItem) error { 400 b, err := json.Marshal(item) 401 if err != nil { 402 return err 403 } 404 405 q := make(url.Values) 406 q.Add("api-version", SiteReplAPIVersion) 407 408 reqData := requestData{ 409 relPath: adminAPIPrefix + "/site-replication/peer/iam-item", 410 content: b, 411 queryValues: q, 412 } 413 414 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 415 defer closeResponse(resp) 416 if err != nil { 417 return err 418 } 419 420 if resp.StatusCode != http.StatusOK { 421 return httpRespToErrorResponse(resp) 422 } 423 424 return nil 425 } 426 427 // SRBucketMeta.Type constants 428 const ( 429 SRBucketMetaTypePolicy = "policy" 430 SRBucketMetaTypeTags = "tags" 431 SRBucketMetaTypeVersionConfig = "version-config" 432 SRBucketMetaTypeObjectLockConfig = "object-lock-config" 433 SRBucketMetaTypeSSEConfig = "sse-config" 434 SRBucketMetaTypeQuotaConfig = "quota-config" 435 SRBucketMetaLCConfig = "lc-config" 436 ) 437 438 // SRBucketMeta - represents a bucket metadata change that will be copied to a peer. 439 type SRBucketMeta struct { 440 Type string `json:"type"` 441 Bucket string `json:"bucket"` 442 Policy json.RawMessage `json:"policy,omitempty"` 443 444 // Since Versioning config does not have a json representation, we use 445 // xml byte presentation directly. 446 Versioning *string `json:"versioningConfig,omitempty"` 447 448 // Since tags does not have a json representation, we use its xml byte 449 // representation directly. 450 Tags *string `json:"tags,omitempty"` 451 452 // Since object lock does not have a json representation, we use its xml 453 // byte representation. 454 ObjectLockConfig *string `json:"objectLockConfig,omitempty"` 455 456 // Since SSE config does not have a json representation, we use its xml 457 // byte respresentation. 458 SSEConfig *string `json:"sseConfig,omitempty"` 459 460 // Quota has a json representation use it as is. 461 Quota json.RawMessage `json:"quota,omitempty"` 462 463 // Since Expiry Lifecycle config does not have a json representation, we use its xml 464 // byte respresentation. 465 ExpiryLCConfig *string `json:"expLCConfig,omitempty"` 466 467 // UpdatedAt - timestamp of last update 468 UpdatedAt time.Time `json:"updatedAt,omitempty"` 469 470 // ExpiryUpdatedAt - timestamp of last update of expiry rule 471 ExpiryUpdatedAt time.Time `json:"expiryUpdatedAt,omitempty"` 472 } 473 474 // SRPeerReplicateBucketMeta - copies a bucket metadata change to a peer cluster. 475 func (adm *AdminClient) SRPeerReplicateBucketMeta(ctx context.Context, item SRBucketMeta) error { 476 b, err := json.Marshal(item) 477 if err != nil { 478 return err 479 } 480 481 q := make(url.Values) 482 q.Set("api-version", SiteReplAPIVersion) 483 484 reqData := requestData{ 485 relPath: adminAPIPrefix + "/site-replication/peer/bucket-meta", 486 content: b, 487 queryValues: q, 488 } 489 490 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 491 defer closeResponse(resp) 492 if err != nil { 493 return err 494 } 495 496 if resp.StatusCode != http.StatusOK { 497 return httpRespToErrorResponse(resp) 498 } 499 500 return nil 501 } 502 503 // SRBucketInfo - returns all the bucket metadata available for bucket 504 type SRBucketInfo struct { 505 Bucket string `json:"bucket"` 506 Policy json.RawMessage `json:"policy,omitempty"` 507 508 // Since Versioning config does not have a json representation, we use 509 // xml byte presentation directly. 510 Versioning *string `json:"versioningConfig,omitempty"` 511 512 // Since tags does not have a json representation, we use its xml byte 513 // representation directly. 514 Tags *string `json:"tags,omitempty"` 515 516 // Since object lock does not have a json representation, we use its xml 517 // byte representation. 518 ObjectLockConfig *string `json:"objectLockConfig,omitempty"` 519 520 // Since SSE config does not have a json representation, we use its xml 521 // byte respresentation. 522 SSEConfig *string `json:"sseConfig,omitempty"` 523 // replication config in json representation 524 ReplicationConfig *string `json:"replicationConfig,omitempty"` 525 // quota config in json representation 526 QuotaConfig *string `json:"quotaConfig,omitempty"` 527 528 // Since Expiry Licfecycle config does not have a json representation, we use its xml 529 // byte representation 530 ExpiryLCConfig *string `json:"expLCConfig,omitempty"` 531 532 // time stamps of bucket metadata updates 533 PolicyUpdatedAt time.Time `json:"policyTimestamp,omitempty"` 534 TagConfigUpdatedAt time.Time `json:"tagTimestamp,omitempty"` 535 ObjectLockConfigUpdatedAt time.Time `json:"olockTimestamp,omitempty"` 536 SSEConfigUpdatedAt time.Time `json:"sseTimestamp,omitempty"` 537 VersioningConfigUpdatedAt time.Time `json:"versioningTimestamp,omitempty"` 538 ReplicationConfigUpdatedAt time.Time `json:"replicationConfigTimestamp,omitempty"` 539 QuotaConfigUpdatedAt time.Time `json:"quotaTimestamp,omitempty"` 540 ExpiryLCConfigUpdatedAt time.Time `json:"expLCTimestamp,omitempty"` 541 CreatedAt time.Time `json:"bucketTimestamp,omitempty"` 542 DeletedAt time.Time `json:"bucketDeletedTimestamp,omitempty"` 543 Location string `json:"location,omitempty"` 544 } 545 546 // OpenIDProviderSettings contains info on a particular OIDC based provider. 547 type OpenIDProviderSettings struct { 548 ClaimName string 549 ClaimUserinfoEnabled bool 550 RolePolicy string 551 ClientID string 552 HashedClientSecret string 553 } 554 555 // OpenIDSettings contains OpenID configuration info of a cluster. 556 type OpenIDSettings struct { 557 // Enabled is true iff there is at least one OpenID provider configured. 558 Enabled bool 559 Region string 560 // Map of role ARN to provider info 561 Roles map[string]OpenIDProviderSettings 562 // Info on the claim based provider (all fields are empty if not 563 // present) 564 ClaimProvider OpenIDProviderSettings 565 } 566 567 // IDPSettings contains key IDentity Provider settings to validate that all 568 // peers have the same configuration. 569 type IDPSettings struct { 570 LDAP LDAPSettings 571 OpenID OpenIDSettings 572 } 573 574 // LDAPSettings contains LDAP configuration info of a cluster. 575 type LDAPSettings struct { 576 IsLDAPEnabled bool 577 LDAPUserDNSearchBase string 578 LDAPUserDNSearchFilter string 579 LDAPGroupSearchBase string 580 LDAPGroupSearchFilter string 581 } 582 583 // SRPeerGetIDPSettings - fetches IDP settings from the server. 584 func (adm *AdminClient) SRPeerGetIDPSettings(ctx context.Context) (info IDPSettings, err error) { 585 q := make(url.Values) 586 q.Set("api-version", SiteReplAPIVersion) 587 588 reqData := requestData{ 589 relPath: adminAPIPrefix + "/site-replication/peer/idp-settings", 590 queryValues: q, 591 } 592 593 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 594 defer closeResponse(resp) 595 if err != nil { 596 return info, err 597 } 598 599 if resp.StatusCode != http.StatusOK { 600 return info, httpRespToErrorResponse(resp) 601 } 602 603 b, err := io.ReadAll(resp.Body) 604 if err != nil { 605 return info, err 606 } 607 608 err = json.Unmarshal(b, &info) 609 if err != nil { 610 // If the server is older version, the IDPSettings was = 611 // LDAPSettings, so we try that. 612 err2 := json.Unmarshal(b, &info.LDAP) 613 if err2 == nil { 614 err = nil 615 } 616 } 617 return info, err 618 } 619 620 // SRIAMPolicy - represents an IAM policy. 621 type SRIAMPolicy struct { 622 Policy json.RawMessage `json:"policy"` 623 UpdatedAt time.Time `json:"updatedAt,omitempty"` 624 } 625 626 // ILMExpiryRule - represents an ILM expiry rule 627 type ILMExpiryRule struct { 628 ILMRule string `json:"ilm-rule"` 629 Bucket string `json:"bucket"` 630 UpdatedAt time.Time `json:"updatedAt,omitempty"` 631 } 632 633 // SRInfo gets replication metadata for a site 634 type SRInfo struct { 635 Enabled bool 636 Name string 637 DeploymentID string 638 Buckets map[string]SRBucketInfo // map of bucket metadata info 639 Policies map[string]SRIAMPolicy // map of IAM policy name to content 640 UserPolicies map[string]SRPolicyMapping // map of username -> user policy mapping 641 UserInfoMap map[string]UserInfo // map of user name to UserInfo 642 GroupDescMap map[string]GroupDesc // map of group name to GroupDesc 643 GroupPolicies map[string]SRPolicyMapping // map of groupname -> group policy mapping 644 ReplicationCfg map[string]replication.Config // map of bucket -> replication config 645 ILMExpiryRules map[string]ILMExpiryRule // map of ILM Expiry rule to content 646 State SRStateInfo // peer state 647 } 648 649 // SRMetaInfo - returns replication metadata info for a site. 650 func (adm *AdminClient) SRMetaInfo(ctx context.Context, opts SRStatusOptions) (info SRInfo, err error) { 651 q := opts.getURLValues() 652 q.Set("api-version", SiteReplAPIVersion) 653 654 reqData := requestData{ 655 relPath: adminAPIPrefix + "/site-replication/metainfo", 656 queryValues: q, 657 } 658 659 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 660 defer closeResponse(resp) 661 if err != nil { 662 return info, err 663 } 664 665 if resp.StatusCode != http.StatusOK { 666 return info, httpRespToErrorResponse(resp) 667 } 668 669 err = json.NewDecoder(resp.Body).Decode(&info) 670 return info, err 671 } 672 673 // SRStatusInfo returns detailed status on site replication status 674 type SRStatusInfo struct { 675 Enabled bool 676 MaxBuckets int // maximum buckets seen across sites 677 MaxUsers int // maximum users seen across sites 678 MaxGroups int // maximum groups seen across sites 679 MaxPolicies int // maximum policies across sites 680 MaxILMExpiryRules int // maxmimum ILM Expiry rules across sites 681 Sites map[string]PeerInfo // deployment->sitename 682 StatsSummary map[string]SRSiteSummary // map of deployment id -> site stat 683 // BucketStats map of bucket to slice of deployment IDs with stats. This is populated only if there are 684 // mismatches or if a specific bucket's stats are requested 685 BucketStats map[string]map[string]SRBucketStatsSummary 686 // PolicyStats map of policy to slice of deployment IDs with stats. This is populated only if there are 687 // mismatches or if a specific bucket's stats are requested 688 PolicyStats map[string]map[string]SRPolicyStatsSummary 689 // UserStats map of user to slice of deployment IDs with stats. This is populated only if there are 690 // mismatches or if a specific bucket's stats are requested 691 UserStats map[string]map[string]SRUserStatsSummary 692 // GroupStats map of group to slice of deployment IDs with stats. This is populated only if there are 693 // mismatches or if a specific bucket's stats are requested 694 GroupStats map[string]map[string]SRGroupStatsSummary 695 // Metrics summary of SRMetrics 696 Metrics SRMetricsSummary // metrics summary. This is populated if buckets/bucket entity requested 697 // ILMExpiryStats map of ILM Expiry rules to slice of deployment IDs with stats. This is populated if there 698 // are mismatches or if a specific ILM expiry rule's stats are requested 699 ILMExpiryStats map[string]map[string]SRILMExpiryStatsSummary 700 } 701 702 // SRPolicyStatsSummary has status of policy replication misses 703 type SRPolicyStatsSummary struct { 704 DeploymentID string 705 PolicyMismatch bool 706 HasPolicy bool 707 } 708 709 // SRUserStatsSummary has status of user replication misses 710 type SRUserStatsSummary struct { 711 DeploymentID string 712 PolicyMismatch bool 713 UserInfoMismatch bool 714 HasUser bool 715 HasPolicyMapping bool 716 } 717 718 // SRGroupStatsSummary has status of group replication misses 719 type SRGroupStatsSummary struct { 720 DeploymentID string 721 PolicyMismatch bool 722 HasGroup bool 723 GroupDescMismatch bool 724 HasPolicyMapping bool 725 } 726 727 // SRBucketStatsSummary has status of bucket metadata replication misses 728 type SRBucketStatsSummary struct { 729 DeploymentID string 730 HasBucket bool 731 BucketMarkedDeleted bool 732 TagMismatch bool 733 VersioningConfigMismatch bool 734 OLockConfigMismatch bool 735 PolicyMismatch bool 736 SSEConfigMismatch bool 737 ReplicationCfgMismatch bool 738 QuotaCfgMismatch bool 739 HasTagsSet bool 740 HasOLockConfigSet bool 741 HasPolicySet bool 742 HasSSECfgSet bool 743 HasReplicationCfg bool 744 HasQuotaCfgSet bool 745 } 746 747 // SRILMExpiryStatsSummary has status of ILM Expiry rules metadata replication misses 748 type SRILMExpiryStatsSummary struct { 749 DeploymentID string 750 ILMExpiryRuleMismatch bool 751 HasILMExpiryRules bool 752 } 753 754 // SRSiteSummary holds the count of replicated items in site replication 755 type SRSiteSummary struct { 756 ReplicatedBuckets int // count of buckets replicated across sites 757 ReplicatedTags int // count of buckets with tags replicated across sites 758 ReplicatedBucketPolicies int // count of policies replicated across sites 759 ReplicatedIAMPolicies int // count of IAM policies replicated across sites 760 ReplicatedUsers int // count of users replicated across sites 761 ReplicatedGroups int // count of groups replicated across sites 762 ReplicatedLockConfig int // count of object lock config replicated across sites 763 ReplicatedSSEConfig int // count of SSE config replicated across sites 764 ReplicatedVersioningConfig int // count of versioning config replicated across sites 765 ReplicatedQuotaConfig int // count of bucket with quota config replicated across sites 766 ReplicatedUserPolicyMappings int // count of user policy mappings replicated across sites 767 ReplicatedGroupPolicyMappings int // count of group policy mappings replicated across sites 768 ReplicatedILMExpiryRules int // count of ILM expiry rules replicated across sites 769 770 TotalBucketsCount int // total buckets on this site 771 TotalTagsCount int // total count of buckets with tags on this site 772 TotalBucketPoliciesCount int // total count of buckets with bucket policies for this site 773 TotalIAMPoliciesCount int // total count of IAM policies for this site 774 TotalLockConfigCount int // total count of buckets with object lock config for this site 775 TotalSSEConfigCount int // total count of buckets with SSE config 776 TotalVersioningConfigCount int // total count of bucekts with versioning config 777 TotalQuotaConfigCount int // total count of buckets with quota config 778 TotalUsersCount int // total number of users seen on this site 779 TotalGroupsCount int // total number of groups seen on this site 780 TotalUserPolicyMappingCount int // total number of user policy mappings seen on this site 781 TotalGroupPolicyMappingCount int // total number of group policy mappings seen on this site 782 TotalILMExpiryRulesCount int // total number of ILM expiry rules seen on the site 783 } 784 785 // SREntityType specifies type of entity 786 type SREntityType int 787 788 const ( 789 // Unspecified entity 790 Unspecified SREntityType = iota 791 792 // SRBucketEntity Bucket entity type 793 SRBucketEntity 794 795 // SRPolicyEntity Policy entity type 796 SRPolicyEntity 797 798 // SRUserEntity User entity type 799 SRUserEntity 800 801 // SRGroupEntity Group entity type 802 SRGroupEntity 803 804 // SRILMExpiryRuleEntity ILM expiry rule entity type 805 SRILMExpiryRuleEntity 806 ) 807 808 // SRStatusOptions holds SR status options 809 type SRStatusOptions struct { 810 Buckets bool 811 Policies bool 812 Users bool 813 Groups bool 814 Metrics bool 815 ILMExpiryRules bool 816 PeerState bool 817 Entity SREntityType 818 EntityValue string 819 ShowDeleted bool 820 } 821 822 // IsEntitySet returns true if entity option is set 823 func (o *SRStatusOptions) IsEntitySet() bool { 824 switch o.Entity { 825 case SRBucketEntity, SRPolicyEntity, SRUserEntity, SRGroupEntity, SRILMExpiryRuleEntity: 826 return true 827 default: 828 return false 829 } 830 } 831 832 // GetSREntityType returns the SREntityType for a key 833 func GetSREntityType(name string) SREntityType { 834 switch name { 835 case "bucket": 836 return SRBucketEntity 837 case "user": 838 return SRUserEntity 839 case "group": 840 return SRGroupEntity 841 case "policy": 842 return SRPolicyEntity 843 case "ilm-expiry-rule": 844 return SRILMExpiryRuleEntity 845 default: 846 return Unspecified 847 } 848 } 849 850 func (o *SRStatusOptions) getURLValues() url.Values { 851 urlValues := make(url.Values) 852 urlValues.Set("buckets", strconv.FormatBool(o.Buckets)) 853 urlValues.Set("policies", strconv.FormatBool(o.Policies)) 854 urlValues.Set("users", strconv.FormatBool(o.Users)) 855 urlValues.Set("groups", strconv.FormatBool(o.Groups)) 856 urlValues.Set("showDeleted", strconv.FormatBool(o.ShowDeleted)) 857 urlValues.Set("metrics", strconv.FormatBool(o.Metrics)) 858 urlValues.Set("ilm-expiry-rules", strconv.FormatBool(o.ILMExpiryRules)) 859 urlValues.Set("peer-state", strconv.FormatBool(o.PeerState)) 860 861 if o.IsEntitySet() { 862 urlValues.Set("entityvalue", o.EntityValue) 863 switch o.Entity { 864 case SRBucketEntity: 865 urlValues.Set("entity", "bucket") 866 case SRPolicyEntity: 867 urlValues.Set("entity", "policy") 868 case SRUserEntity: 869 urlValues.Set("entity", "user") 870 case SRGroupEntity: 871 urlValues.Set("entity", "group") 872 case SRILMExpiryRuleEntity: 873 urlValues.Set("entity", "ilm-expiry-rule") 874 } 875 } 876 return urlValues 877 } 878 879 // SRStatusInfo - returns site replication status 880 func (adm *AdminClient) SRStatusInfo(ctx context.Context, opts SRStatusOptions) (info SRStatusInfo, err error) { 881 q := opts.getURLValues() 882 q.Set("api-version", SiteReplAPIVersion) 883 884 reqData := requestData{ 885 relPath: adminAPIPrefix + "/site-replication/status", 886 queryValues: q, 887 } 888 889 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 890 defer closeResponse(resp) 891 if err != nil { 892 return info, err 893 } 894 895 if resp.StatusCode != http.StatusOK { 896 return info, httpRespToErrorResponse(resp) 897 } 898 899 err = json.NewDecoder(resp.Body).Decode(&info) 900 return info, err 901 } 902 903 // ReplicateEditStatus - returns status of edit request. 904 type ReplicateEditStatus struct { 905 Success bool `json:"success"` 906 Status string `json:"status"` 907 ErrDetail string `json:"errorDetail,omitempty"` 908 } 909 910 // SREditOptions holds SR Edit options 911 type SREditOptions struct { 912 DisableILMExpiryReplication bool 913 EnableILMExpiryReplication bool 914 } 915 916 func (o *SREditOptions) getURLValues() url.Values { 917 urlValues := make(url.Values) 918 urlValues.Set("disableILMExpiryReplication", strconv.FormatBool(o.DisableILMExpiryReplication)) 919 urlValues.Set("enableILMExpiryReplication", strconv.FormatBool(o.EnableILMExpiryReplication)) 920 return urlValues 921 } 922 923 // SiteReplicationEdit - sends the SR edit API call. 924 func (adm *AdminClient) SiteReplicationEdit(ctx context.Context, site PeerInfo, opts SREditOptions) (ReplicateEditStatus, error) { 925 sitesBytes, err := json.Marshal(site) 926 if err != nil { 927 return ReplicateEditStatus{}, nil 928 } 929 encBytes, err := EncryptData(adm.getSecretKey(), sitesBytes) 930 if err != nil { 931 return ReplicateEditStatus{}, err 932 } 933 934 q := opts.getURLValues() 935 q.Set("api-version", SiteReplAPIVersion) 936 937 reqData := requestData{ 938 relPath: adminAPIPrefix + "/site-replication/edit", 939 content: encBytes, 940 queryValues: q, 941 } 942 943 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 944 defer closeResponse(resp) 945 if err != nil { 946 return ReplicateEditStatus{}, err 947 } 948 949 if resp.StatusCode != http.StatusOK { 950 return ReplicateEditStatus{}, httpRespToErrorResponse(resp) 951 } 952 953 var res ReplicateEditStatus 954 err = json.NewDecoder(resp.Body).Decode(&res) 955 return res, err 956 } 957 958 // SRPeerEdit - used only by minio server to update peer endpoint 959 // for a server already in the site replication setup 960 func (adm *AdminClient) SRPeerEdit(ctx context.Context, pi PeerInfo) error { 961 b, err := json.Marshal(pi) 962 if err != nil { 963 return err 964 } 965 966 q := make(url.Values) 967 q.Set("api-version", SiteReplAPIVersion) 968 969 reqData := requestData{ 970 relPath: adminAPIPrefix + "/site-replication/peer/edit", 971 content: b, 972 queryValues: q, 973 } 974 975 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 976 defer closeResponse(resp) 977 if err != nil { 978 return err 979 } 980 981 if resp.StatusCode != http.StatusOK { 982 return httpRespToErrorResponse(resp) 983 } 984 985 return nil 986 } 987 988 // SRStateEdit - used only by minio server to update peer state 989 // for a server already in the site replication setup 990 func (adm *AdminClient) SRStateEdit(ctx context.Context, state SRStateEditReq) error { 991 b, err := json.Marshal(state) 992 if err != nil { 993 return err 994 } 995 996 q := make(url.Values) 997 q.Set("api-version", SiteReplAPIVersion) 998 999 reqData := requestData{ 1000 relPath: adminAPIPrefix + "/site-replication/state/edit", 1001 content: b, 1002 queryValues: q, 1003 } 1004 1005 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 1006 defer closeResponse(resp) 1007 if err != nil { 1008 return err 1009 } 1010 1011 if resp.StatusCode != http.StatusOK { 1012 return httpRespToErrorResponse(resp) 1013 } 1014 1015 return nil 1016 } 1017 1018 // SiteReplicationRemove - unlinks a site from site replication 1019 func (adm *AdminClient) SiteReplicationRemove(ctx context.Context, removeReq SRRemoveReq) (st ReplicateRemoveStatus, err error) { 1020 rmvBytes, err := json.Marshal(removeReq) 1021 if err != nil { 1022 return st, nil 1023 } 1024 q := make(url.Values) 1025 q.Set("api-version", SiteReplAPIVersion) 1026 1027 reqData := requestData{ 1028 relPath: adminAPIPrefix + "/site-replication/remove", 1029 content: rmvBytes, 1030 queryValues: q, 1031 } 1032 1033 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 1034 defer closeResponse(resp) 1035 if err != nil { 1036 return st, err 1037 } 1038 1039 if resp.StatusCode != http.StatusOK { 1040 return st, httpRespToErrorResponse(resp) 1041 } 1042 var res ReplicateRemoveStatus 1043 err = json.NewDecoder(resp.Body).Decode(&res) 1044 return res, err 1045 } 1046 1047 // SRPeerRemove - used only by minio server to unlink cluster replication 1048 // for a server already in the site replication setup 1049 func (adm *AdminClient) SRPeerRemove(ctx context.Context, removeReq SRRemoveReq) (st ReplicateRemoveStatus, err error) { 1050 reqBytes, err := json.Marshal(removeReq) 1051 if err != nil { 1052 return st, err 1053 } 1054 q := make(url.Values) 1055 q.Set("api-version", SiteReplAPIVersion) 1056 1057 reqData := requestData{ 1058 relPath: adminAPIPrefix + "/site-replication/peer/remove", 1059 content: reqBytes, 1060 queryValues: q, 1061 } 1062 1063 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 1064 defer closeResponse(resp) 1065 if err != nil { 1066 return st, err 1067 } 1068 1069 if resp.StatusCode != http.StatusOK { 1070 return st, httpRespToErrorResponse(resp) 1071 } 1072 return ReplicateRemoveStatus{}, nil 1073 } 1074 1075 // ReplicateRemoveStatus - returns status of unlink request. 1076 type ReplicateRemoveStatus struct { 1077 Status string `json:"status"` 1078 ErrDetail string `json:"errorDetail,omitempty"` 1079 } 1080 1081 // SRRemoveReq - arg body for SRRemoveReq 1082 type SRRemoveReq struct { 1083 RequestingDepID string `json:"requestingDepID"` 1084 SiteNames []string `json:"sites"` 1085 RemoveAll bool `json:"all"` // true if all sites are to be removed. 1086 } 1087 1088 // SRStateEditReq - arg body for SRStateEditReq 1089 type SRStateEditReq struct { 1090 Peers map[string]PeerInfo `json:"peers"` 1091 UpdatedAt time.Time `json:"updatedAt"` 1092 } 1093 1094 // SRStateInfo - site replication state information 1095 type SRStateInfo struct { 1096 Name string `json:"name"` 1097 Peers map[string]PeerInfo `json:"peers"` 1098 UpdatedAt time.Time `json:"updatedAt"` 1099 } 1100 1101 const ( 1102 ReplicateRemoveStatusSuccess = "Requested site(s) were removed from cluster replication successfully." 1103 ReplicateRemoveStatusPartial = "Some site(s) could not be removed from cluster replication configuration." 1104 ) 1105 1106 type ResyncBucketStatus struct { 1107 Bucket string `json:"bucket"` 1108 Status string `json:"status"` 1109 ErrDetail string `json:"errorDetail,omitempty"` 1110 } 1111 1112 // SRResyncOpStatus - returns status of resync start request. 1113 type SRResyncOpStatus struct { 1114 OpType string `json:"op"` // one of "start" or "cancel" 1115 ResyncID string `json:"id"` 1116 Status string `json:"status"` 1117 Buckets []ResyncBucketStatus `json:"buckets"` 1118 ErrDetail string `json:"errorDetail,omitempty"` 1119 } 1120 1121 // SiteResyncOp type of resync operation 1122 type SiteResyncOp string 1123 1124 const ( 1125 // SiteResyncStart starts a site resync operation 1126 SiteResyncStart SiteResyncOp = "start" 1127 // SiteResyncCancel cancels ongoing site resync 1128 SiteResyncCancel SiteResyncOp = "cancel" 1129 ) 1130 1131 // SiteReplicationResyncOp - perform a site replication resync operation 1132 func (adm *AdminClient) SiteReplicationResyncOp(ctx context.Context, site PeerInfo, op SiteResyncOp) (SRResyncOpStatus, error) { 1133 reqBytes, err := json.Marshal(site) 1134 if err != nil { 1135 return SRResyncOpStatus{}, nil 1136 } 1137 1138 v := url.Values{} 1139 v.Set("operation", string(op)) 1140 v.Set("api-version", SiteReplAPIVersion) 1141 1142 reqData := requestData{ 1143 relPath: adminAPIPrefix + "/site-replication/resync/op", 1144 content: reqBytes, 1145 queryValues: v, 1146 } 1147 1148 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 1149 defer closeResponse(resp) 1150 if err != nil { 1151 return SRResyncOpStatus{}, err 1152 } 1153 1154 if resp.StatusCode != http.StatusOK { 1155 return SRResyncOpStatus{}, httpRespToErrorResponse(resp) 1156 } 1157 1158 var res SRResyncOpStatus 1159 err = json.NewDecoder(resp.Body).Decode(&res) 1160 return res, err 1161 } 1162 1163 // SRMetric - captures replication metrics for a site replication peer 1164 type SRMetric struct { 1165 DeploymentID string `json:"deploymentID"` 1166 Endpoint string `json:"endpoint"` 1167 TotalDowntime time.Duration `json:"totalDowntime"` 1168 LastOnline time.Time `json:"lastOnline"` 1169 Online bool `json:"isOnline"` 1170 Latency LatencyStat `json:"latency"` 1171 1172 // replication metrics across buckets roll up 1173 ReplicatedSize int64 `json:"replicatedSize"` 1174 // Total number of completed operations 1175 ReplicatedCount int64 `json:"replicatedCount"` 1176 // ReplicationErrorStats captures replication errors 1177 Failed TimedErrStats `json:"failed,omitempty"` 1178 // XferStats captures transfer stats 1179 XferStats map[replication.MetricName]replication.XferStats `json:"transferSummary"` 1180 // MRFStats captures current backlog entries in the last 5 minutes 1181 MRFStats replication.ReplMRFStats `json:"mrfStats"` 1182 } 1183 1184 // WorkerStat captures number of replication workers 1185 type WorkerStat struct { 1186 Curr int `json:"curr"` 1187 Avg float32 `json:"avg"` 1188 Max int `json:"max"` 1189 } 1190 1191 // InQueueMetric holds stats for objects in replication queue 1192 type InQueueMetric struct { 1193 Curr QStat `json:"curr" msg:"cq"` 1194 Avg QStat `json:"avg" msg:"aq"` 1195 Max QStat `json:"max" msg:"pq"` 1196 } 1197 1198 // QStat represents number of objects and bytes in queue 1199 type QStat struct { 1200 Count float64 `json:"count"` 1201 Bytes float64 `json:"bytes"` 1202 } 1203 1204 // Add two QStat 1205 func (q *QStat) Add(o QStat) QStat { 1206 return QStat{Bytes: q.Bytes + o.Bytes, Count: q.Count + o.Count} 1207 } 1208 1209 // SRMetricsSummary captures summary of replication counts across buckets on site 1210 // along with op metrics rollup. 1211 type SRMetricsSummary struct { 1212 // op metrics roll up 1213 ActiveWorkers WorkerStat `json:"activeWorkers"` 1214 // Total Replica size in bytes 1215 ReplicaSize int64 `json:"replicaSize"` 1216 // Total count of replica received 1217 ReplicaCount int64 `json:"replicaCount"` 1218 // queue metrics 1219 Queued InQueueMetric `json:"queued"` 1220 // proxied metrics 1221 Proxied ReplProxyMetric `json:"proxied"` 1222 // replication metrics summary for each site replication peer 1223 Metrics map[string]SRMetric `json:"replMetrics"` 1224 // uptime of node being queried for site replication metrics 1225 Uptime int64 `json:"uptime"` 1226 } 1227 1228 // ReplProxyMetric holds stats for replication proxying 1229 type ReplProxyMetric struct { 1230 PutTagTotal uint64 `json:"putTaggingProxyTotal" msg:"ptc"` 1231 GetTagTotal uint64 `json:"getTaggingProxyTotal" msg:"gtc"` 1232 RmvTagTotal uint64 `json:"removeTaggingProxyTotal" msg:"rtc"` 1233 GetTotal uint64 `json:"getProxyTotal" msg:"gc"` 1234 HeadTotal uint64 `json:"headProxyTotal" msg:"hc"` 1235 PutTagFailedTotal uint64 `json:"putTaggingProxyFailed" msg:"ptc"` 1236 GetTagFailedTotal uint64 `json:"getTaggingProxyFailed" msg:"gtc"` 1237 RmvTagFailedTotal uint64 `json:"removeTaggingProxyFailed" msg:"rtc"` 1238 GetFailedTotal uint64 `json:"getProxyFailed" msg:"gc"` 1239 HeadFailedTotal uint64 `json:"headProxyFailed" msg:"hc"` 1240 } 1241 1242 // Add updates proxy metrics 1243 func (p *ReplProxyMetric) Add(p2 ReplProxyMetric) { 1244 p.GetTagTotal += p2.GetTagTotal 1245 p.PutTagTotal += p2.PutTagTotal 1246 p.RmvTagTotal += p2.RmvTagTotal 1247 p.GetTotal += p2.GetTotal 1248 p.HeadTotal += p2.HeadTotal 1249 p.PutTagFailedTotal += p2.PutTagFailedTotal 1250 p.GetTagFailedTotal += p2.GetTagFailedTotal 1251 p.RmvTagFailedTotal += p2.RmvTagFailedTotal 1252 p.GetFailedTotal += p2.GetFailedTotal 1253 p.HeadFailedTotal += p2.HeadFailedTotal 1254 }