github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/peer-rest-client.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "encoding/gob" 23 "encoding/hex" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "io" 28 "net/url" 29 "strconv" 30 "sync/atomic" 31 "time" 32 33 "github.com/minio/madmin-go/v3" 34 "github.com/minio/minio/internal/bucket/bandwidth" 35 "github.com/minio/minio/internal/grid" 36 xhttp "github.com/minio/minio/internal/http" 37 "github.com/minio/minio/internal/logger" 38 "github.com/minio/minio/internal/rest" 39 xnet "github.com/minio/pkg/v2/net" 40 ) 41 42 // client to talk to peer Nodes. 43 type peerRESTClient struct { 44 host *xnet.Host 45 restClient *rest.Client 46 gridHost string 47 // Function that returns the grid connection for this peer when initialized. 48 // Will return nil if the grid connection is not initialized yet. 49 gridConn func() *grid.Connection 50 } 51 52 // Returns a peer rest client. 53 func newPeerRESTClient(peer *xnet.Host, gridHost string) *peerRESTClient { 54 scheme := "http" 55 if globalIsTLS { 56 scheme = "https" 57 } 58 59 serverURL := &url.URL{ 60 Scheme: scheme, 61 Host: peer.String(), 62 Path: peerRESTPath, 63 } 64 65 restClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken()) 66 // Use a separate client to avoid recursive calls. 67 healthClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken()) 68 healthClient.NoMetrics = true 69 70 // Construct a new health function. 71 restClient.HealthCheckFn = func() bool { 72 ctx, cancel := context.WithTimeout(context.Background(), restClient.HealthCheckTimeout) 73 defer cancel() 74 respBody, err := healthClient.Call(ctx, peerRESTMethodHealth, nil, nil, -1) 75 xhttp.DrainBody(respBody) 76 return !isNetworkError(err) 77 } 78 var gridConn atomic.Pointer[grid.Connection] 79 80 return &peerRESTClient{ 81 host: peer, restClient: restClient, gridHost: gridHost, 82 gridConn: func() *grid.Connection { 83 // Lazy initialization of grid connection. 84 // When we create this peer client, the grid connection is likely not yet initialized. 85 if gridHost == "" { 86 logger.LogOnceIf(context.Background(), fmt.Errorf("gridHost is empty for peer %s", peer.String()), peer.String()+":gridHost") 87 return nil 88 } 89 gc := gridConn.Load() 90 if gc != nil { 91 return gc 92 } 93 gm := globalGrid.Load() 94 if gm == nil { 95 return nil 96 } 97 gc = gm.Connection(gridHost) 98 if gc == nil { 99 logger.LogOnceIf(context.Background(), fmt.Errorf("gridHost %q not found for peer %s", gridHost, peer.String()), peer.String()+":gridHost") 100 return nil 101 } 102 gridConn.Store(gc) 103 return gc 104 }, 105 } 106 } 107 108 // Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected 109 // permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints() 110 // after verifying format.json 111 func (client *peerRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) { 112 return client.callWithContext(GlobalContext, method, values, body, length) 113 } 114 115 // Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected 116 // permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints() 117 // after verifying format.json 118 func (client *peerRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) { 119 if client == nil || !client.IsOnline() { 120 return nil, errPeerNotReachable 121 } 122 123 if values == nil { 124 values = make(url.Values) 125 } 126 127 respBody, err = client.restClient.Call(ctx, method, values, body, length) 128 if err == nil { 129 return respBody, nil 130 } 131 132 return nil, err 133 } 134 135 // Stringer provides a canonicalized representation of node. 136 func (client *peerRESTClient) String() string { 137 return client.host.String() 138 } 139 140 // IsOnline returns true if the peer client is online. 141 func (client *peerRESTClient) IsOnline() bool { 142 return client.restClient.IsOnline() 143 } 144 145 // Close - marks the client as closed. 146 func (client *peerRESTClient) Close() error { 147 client.restClient.Close() 148 return nil 149 } 150 151 // GetLocks - fetch older locks for a remote node. 152 func (client *peerRESTClient) GetLocks() (lockMap map[string][]lockRequesterInfo, err error) { 153 resp, err := getLocksRPC.Call(context.Background(), client.gridConn(), grid.NewMSS()) 154 if err != nil || resp == nil { 155 return nil, err 156 } 157 return *resp, nil 158 } 159 160 // LocalStorageInfo - fetch server information for a remote node. 161 func (client *peerRESTClient) LocalStorageInfo(metrics bool) (info StorageInfo, err error) { 162 resp, err := localStorageInfoRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 163 peerRESTMetrics: strconv.FormatBool(metrics), 164 })) 165 return resp.ValueOrZero(), err 166 } 167 168 // ServerInfo - fetch server information for a remote node. 169 func (client *peerRESTClient) ServerInfo(metrics bool) (info madmin.ServerProperties, err error) { 170 resp, err := serverInfoRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{peerRESTMetrics: strconv.FormatBool(metrics)})) 171 return resp.ValueOrZero(), err 172 } 173 174 // GetCPUs - fetch CPU information for a remote node. 175 func (client *peerRESTClient) GetCPUs(ctx context.Context) (info madmin.CPUs, err error) { 176 resp, err := getCPUsHandler.Call(ctx, client.gridConn(), grid.NewMSS()) 177 return resp.ValueOrZero(), err 178 } 179 180 // GetNetInfo - fetch network information for a remote node. 181 func (client *peerRESTClient) GetNetInfo(ctx context.Context) (info madmin.NetInfo, err error) { 182 resp, err := getNetInfoRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 183 return resp.ValueOrZero(), err 184 } 185 186 // GetPartitions - fetch disk partition information for a remote node. 187 func (client *peerRESTClient) GetPartitions(ctx context.Context) (info madmin.Partitions, err error) { 188 resp, err := getPartitionsRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 189 return resp.ValueOrZero(), err 190 } 191 192 // GetOSInfo - fetch OS information for a remote node. 193 func (client *peerRESTClient) GetOSInfo(ctx context.Context) (info madmin.OSInfo, err error) { 194 resp, err := getOSInfoRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 195 return resp.ValueOrZero(), err 196 } 197 198 // GetSELinuxInfo - fetch SELinux information for a remote node. 199 func (client *peerRESTClient) GetSELinuxInfo(ctx context.Context) (info madmin.SysServices, err error) { 200 resp, err := getSysServicesRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 201 return resp.ValueOrZero(), err 202 } 203 204 // GetSysConfig - fetch sys config for a remote node. 205 func (client *peerRESTClient) GetSysConfig(ctx context.Context) (info madmin.SysConfig, err error) { 206 sent := time.Now() 207 resp, err := getSysConfigRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 208 info = resp.ValueOrZero() 209 if ti, ok := info.Config["time-info"].(madmin.TimeInfo); ok { 210 rt := int32(time.Since(sent).Milliseconds()) 211 ti.RoundtripDuration = rt 212 info.Config["time-info"] = ti 213 } 214 return info, err 215 } 216 217 // GetSysErrors - fetch sys errors for a remote node. 218 func (client *peerRESTClient) GetSysErrors(ctx context.Context) (info madmin.SysErrors, err error) { 219 resp, err := getSysErrorsRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 220 return resp.ValueOrZero(), err 221 } 222 223 // GetMemInfo - fetch memory information for a remote node. 224 func (client *peerRESTClient) GetMemInfo(ctx context.Context) (info madmin.MemInfo, err error) { 225 resp, err := getMemInfoRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 226 return resp.ValueOrZero(), err 227 } 228 229 // GetMetrics - fetch metrics from a remote node. 230 func (client *peerRESTClient) GetMetrics(ctx context.Context, t madmin.MetricType, opts collectMetricsOpts) (info madmin.RealtimeMetrics, err error) { 231 values := make(url.Values) 232 values.Set(peerRESTMetricsTypes, strconv.FormatUint(uint64(t), 10)) 233 for disk := range opts.disks { 234 values.Add(peerRESTDisk, disk) 235 } 236 for host := range opts.hosts { 237 values.Add(peerRESTHost, host) 238 } 239 values.Set(peerRESTJobID, opts.jobID) 240 values.Set(peerRESTDepID, opts.depID) 241 v, err := getMetricsRPC.Call(ctx, client.gridConn(), grid.NewURLValuesWith(values)) 242 return v.ValueOrZero(), err 243 } 244 245 // GetProcInfo - fetch MinIO process information for a remote node. 246 func (client *peerRESTClient) GetProcInfo(ctx context.Context) (info madmin.ProcInfo, err error) { 247 resp, err := getProcInfoRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 248 return resp.ValueOrZero(), err 249 } 250 251 // StartProfiling - Issues profiling command on the peer node. 252 func (client *peerRESTClient) StartProfiling(profiler string) error { 253 values := make(url.Values) 254 values.Set(peerRESTProfiler, profiler) 255 respBody, err := client.call(peerRESTMethodStartProfiling, values, nil, -1) 256 if err != nil { 257 return err 258 } 259 defer xhttp.DrainBody(respBody) 260 return nil 261 } 262 263 // DownloadProfileData - download profiled data from a remote node. 264 func (client *peerRESTClient) DownloadProfileData() (data map[string][]byte, err error) { 265 respBody, err := client.call(peerRESTMethodDownloadProfilingData, nil, nil, -1) 266 if err != nil { 267 return 268 } 269 defer xhttp.DrainBody(respBody) 270 err = gob.NewDecoder(respBody).Decode(&data) 271 return data, err 272 } 273 274 // GetBucketStats - load bucket statistics 275 func (client *peerRESTClient) GetBucketStats(bucket string) (BucketStats, error) { 276 resp, err := getBucketStatsRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 277 peerRESTBucket: bucket, 278 })) 279 if err != nil || resp == nil { 280 return BucketStats{}, err 281 } 282 return *resp, nil 283 } 284 285 // GetSRMetrics loads site replication metrics, optionally for a specific bucket 286 func (client *peerRESTClient) GetSRMetrics() (SRMetricsSummary, error) { 287 resp, err := getSRMetricsRPC.Call(context.Background(), client.gridConn(), grid.NewMSS()) 288 if err != nil || resp == nil { 289 return SRMetricsSummary{}, err 290 } 291 return *resp, nil 292 } 293 294 // GetAllBucketStats - load replication stats for all buckets 295 func (client *peerRESTClient) GetAllBucketStats() (BucketStatsMap, error) { 296 resp, err := getAllBucketStatsRPC.Call(context.Background(), client.gridConn(), grid.NewMSS()) 297 if err != nil || resp == nil { 298 return BucketStatsMap{}, err 299 } 300 return *resp, nil 301 } 302 303 // LoadBucketMetadata - load bucket metadata 304 func (client *peerRESTClient) LoadBucketMetadata(bucket string) error { 305 _, err := loadBucketMetadataRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 306 peerRESTBucket: bucket, 307 })) 308 return err 309 } 310 311 // DeleteBucketMetadata - Delete bucket metadata 312 func (client *peerRESTClient) DeleteBucketMetadata(bucket string) error { 313 _, err := deleteBucketMetadataRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 314 peerRESTBucket: bucket, 315 })) 316 return err 317 } 318 319 // DeletePolicy - delete a specific canned policy. 320 func (client *peerRESTClient) DeletePolicy(policyName string) (err error) { 321 _, err = deletePolicyRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 322 peerRESTPolicy: policyName, 323 })) 324 return err 325 } 326 327 // LoadPolicy - reload a specific canned policy. 328 func (client *peerRESTClient) LoadPolicy(policyName string) (err error) { 329 _, err = loadPolicyRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 330 peerRESTPolicy: policyName, 331 })) 332 return err 333 } 334 335 // LoadPolicyMapping - reload a specific policy mapping 336 func (client *peerRESTClient) LoadPolicyMapping(userOrGroup string, userType IAMUserType, isGroup bool) error { 337 _, err := loadPolicyMappingRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 338 peerRESTUserOrGroup: userOrGroup, 339 peerRESTUserType: strconv.Itoa(int(userType)), 340 peerRESTIsGroup: strconv.FormatBool(isGroup), 341 })) 342 return err 343 } 344 345 // DeleteUser - delete a specific user. 346 func (client *peerRESTClient) DeleteUser(accessKey string) (err error) { 347 _, err = deleteUserRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 348 peerRESTUser: accessKey, 349 })) 350 return err 351 } 352 353 // DeleteServiceAccount - delete a specific service account. 354 func (client *peerRESTClient) DeleteServiceAccount(accessKey string) (err error) { 355 _, err = deleteSvcActRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 356 peerRESTUser: accessKey, 357 })) 358 return err 359 } 360 361 // LoadUser - reload a specific user. 362 func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error) { 363 _, err = loadUserRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 364 peerRESTUser: accessKey, 365 peerRESTUserTemp: strconv.FormatBool(temp), 366 })) 367 return err 368 } 369 370 // LoadServiceAccount - reload a specific service account. 371 func (client *peerRESTClient) LoadServiceAccount(accessKey string) (err error) { 372 _, err = loadSvcActRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 373 peerRESTUser: accessKey, 374 })) 375 return err 376 } 377 378 // LoadGroup - send load group command to peers. 379 func (client *peerRESTClient) LoadGroup(group string) error { 380 _, err := loadGroupRPC.Call(context.Background(), client.gridConn(), grid.NewMSSWith(map[string]string{ 381 peerRESTGroup: group, 382 })) 383 return err 384 } 385 386 func (client *peerRESTClient) ReloadSiteReplicationConfig(ctx context.Context) error { 387 conn := client.gridConn() 388 if conn == nil { 389 return nil 390 } 391 392 _, err := reloadSiteReplicationConfigRPC.Call(ctx, conn, grid.NewMSS()) 393 return err 394 } 395 396 // VerifyBinary - sends verify binary message to remote peers. 397 func (client *peerRESTClient) VerifyBinary(ctx context.Context, u *url.URL, sha256Sum []byte, releaseInfo string, reader io.Reader) error { 398 values := make(url.Values) 399 values.Set(peerRESTURL, u.String()) 400 values.Set(peerRESTSha256Sum, hex.EncodeToString(sha256Sum)) 401 values.Set(peerRESTReleaseInfo, releaseInfo) 402 403 respBody, err := client.callWithContext(ctx, peerRESTMethodVerifyBinary, values, reader, -1) 404 if err != nil { 405 return err 406 } 407 defer xhttp.DrainBody(respBody) 408 return nil 409 } 410 411 // CommitBinary - sends commit binary message to remote peers. 412 func (client *peerRESTClient) CommitBinary(ctx context.Context) error { 413 respBody, err := client.callWithContext(ctx, peerRESTMethodCommitBinary, nil, nil, -1) 414 if err != nil { 415 return err 416 } 417 defer xhttp.DrainBody(respBody) 418 return nil 419 } 420 421 // SignalService - sends signal to peer nodes. 422 func (client *peerRESTClient) SignalService(sig serviceSignal, subSys string, dryRun bool) error { 423 values := grid.NewMSS() 424 values.Set(peerRESTSignal, strconv.Itoa(int(sig))) 425 values.Set(peerRESTDryRun, strconv.FormatBool(dryRun)) 426 values.Set(peerRESTSubSys, subSys) 427 _, err := signalServiceRPC.Call(context.Background(), client.gridConn(), values) 428 return err 429 } 430 431 func (client *peerRESTClient) BackgroundHealStatus() (madmin.BgHealState, error) { 432 resp, err := getBackgroundHealStatusRPC.Call(context.Background(), client.gridConn(), grid.NewMSS()) 433 return resp.ValueOrZero(), err 434 } 435 436 // GetMetacacheListing - get a new or existing metacache. 437 func (client *peerRESTClient) GetMetacacheListing(ctx context.Context, o listPathOptions) (*metacache, error) { 438 if client == nil { 439 resp := localMetacacheMgr.getBucket(ctx, o.Bucket).findCache(o) 440 return &resp, nil 441 } 442 return getMetacacheListingRPC.Call(ctx, client.gridConn(), &o) 443 } 444 445 // UpdateMetacacheListing - update an existing metacache it will unconditionally be updated to the new state. 446 func (client *peerRESTClient) UpdateMetacacheListing(ctx context.Context, m metacache) (metacache, error) { 447 if client == nil { 448 return localMetacacheMgr.updateCacheEntry(m) 449 } 450 resp, err := updateMetacacheListingRPC.Call(ctx, client.gridConn(), &m) 451 if err != nil || resp == nil { 452 return metacache{}, err 453 } 454 return *resp, nil 455 } 456 457 func (client *peerRESTClient) ReloadPoolMeta(ctx context.Context) error { 458 conn := client.gridConn() 459 if conn == nil { 460 return nil 461 } 462 _, err := reloadPoolMetaRPC.Call(ctx, conn, grid.NewMSSWith(map[string]string{})) 463 return err 464 } 465 466 func (client *peerRESTClient) StopRebalance(ctx context.Context) error { 467 conn := client.gridConn() 468 if conn == nil { 469 return nil 470 } 471 _, err := stopRebalanceRPC.Call(ctx, conn, grid.NewMSSWith(map[string]string{})) 472 return err 473 } 474 475 func (client *peerRESTClient) LoadRebalanceMeta(ctx context.Context, startRebalance bool) error { 476 conn := client.gridConn() 477 if conn == nil { 478 return nil 479 } 480 _, err := loadRebalanceMetaRPC.Call(ctx, conn, grid.NewMSSWith(map[string]string{ 481 peerRESTStartRebalance: strconv.FormatBool(startRebalance), 482 })) 483 return err 484 } 485 486 func (client *peerRESTClient) LoadTransitionTierConfig(ctx context.Context) error { 487 conn := client.gridConn() 488 if conn == nil { 489 return nil 490 } 491 _, err := loadTransitionTierConfigRPC.Call(ctx, conn, grid.NewMSSWith(map[string]string{})) 492 return err 493 } 494 495 func (client *peerRESTClient) doTrace(ctx context.Context, traceCh chan<- []byte, traceOpts madmin.ServiceTraceOpts) { 496 gridConn := client.gridConn() 497 if gridConn == nil { 498 return 499 } 500 501 payload, err := json.Marshal(traceOpts) 502 if err != nil { 503 logger.LogIf(ctx, err) 504 return 505 } 506 507 st, err := gridConn.NewStream(ctx, grid.HandlerTrace, payload) 508 if err != nil { 509 return 510 } 511 st.Results(func(b []byte) error { 512 select { 513 case traceCh <- b: 514 default: 515 // Do not block on slow receivers. 516 // Just recycle the buffer. 517 grid.PutByteBuffer(b) 518 } 519 return nil 520 }) 521 } 522 523 func (client *peerRESTClient) doListen(ctx context.Context, listenCh chan<- []byte, v url.Values) { 524 conn := client.gridConn() 525 if conn == nil { 526 return 527 } 528 st, err := listenRPC.Call(ctx, conn, grid.NewURLValuesWith(v)) 529 if err != nil { 530 return 531 } 532 st.Results(func(b *grid.Bytes) error { 533 select { 534 case listenCh <- *b: 535 default: 536 // Do not block on slow receivers. 537 b.Recycle() 538 } 539 return nil 540 }) 541 } 542 543 // Listen - listen on peers. 544 func (client *peerRESTClient) Listen(ctx context.Context, listenCh chan<- []byte, v url.Values) { 545 go func() { 546 for { 547 client.doListen(ctx, listenCh, v) 548 select { 549 case <-ctx.Done(): 550 return 551 default: 552 // There was error in the REST request, retry after sometime as probably the peer is down. 553 time.Sleep(5 * time.Second) 554 } 555 } 556 }() 557 } 558 559 // Trace - send http trace request to peer nodes 560 func (client *peerRESTClient) Trace(ctx context.Context, traceCh chan<- []byte, traceOpts madmin.ServiceTraceOpts) { 561 go func() { 562 for { 563 // Blocks until context is canceled or an error occurs. 564 client.doTrace(ctx, traceCh, traceOpts) 565 select { 566 case <-ctx.Done(): 567 return 568 default: 569 // There was error in the REST request, retry after sometime as probably the peer is down. 570 time.Sleep(5 * time.Second) 571 } 572 } 573 }() 574 } 575 576 func (client *peerRESTClient) doConsoleLog(ctx context.Context, kind madmin.LogMask, logCh chan<- []byte) { 577 st, err := consoleLogRPC.Call(ctx, client.gridConn(), grid.NewMSSWith(map[string]string{ 578 peerRESTLogMask: strconv.Itoa(int(kind)), 579 })) 580 if err != nil { 581 return 582 } 583 st.Results(func(b *grid.Bytes) error { 584 select { 585 case logCh <- *b: 586 default: 587 consoleLogRPC.PutResponse(b) 588 // Do not block on slow receivers. 589 } 590 return nil 591 }) 592 } 593 594 // ConsoleLog - sends request to peer nodes to get console logs 595 func (client *peerRESTClient) ConsoleLog(ctx context.Context, kind madmin.LogMask, logCh chan<- []byte) { 596 go func() { 597 for { 598 client.doConsoleLog(ctx, kind, logCh) 599 select { 600 case <-ctx.Done(): 601 return 602 default: 603 // There was error in the REST request, retry after sometime as probably the peer is down. 604 time.Sleep(5 * time.Second) 605 } 606 } 607 }() 608 } 609 610 // newPeerRestClients creates new peer clients. 611 // The two slices will point to the same clients, 612 // but 'all' will contain nil entry for local client. 613 // The 'all' slice will be in the same order across the cluster. 614 func newPeerRestClients(endpoints EndpointServerPools) (remote, all []*peerRESTClient) { 615 if !globalIsDistErasure { 616 // Only useful in distributed setups 617 return nil, nil 618 } 619 620 hosts := endpoints.hostsSorted() 621 remote = make([]*peerRESTClient, 0, len(hosts)) 622 all = make([]*peerRESTClient, len(hosts)) 623 for i, host := range hosts { 624 if host == nil { 625 continue 626 } 627 all[i] = newPeerRESTClient(host, endpoints.FindGridHostsFromPeer(host)) 628 remote = append(remote, all[i]) 629 } 630 if len(all) != len(remote)+1 { 631 logger.LogIf(context.Background(), fmt.Errorf("WARNING: Expected number of all hosts (%v) to be remote +1 (%v)", len(all), len(remote))) 632 } 633 return remote, all 634 } 635 636 // MonitorBandwidth - send http trace request to peer nodes 637 func (client *peerRESTClient) MonitorBandwidth(ctx context.Context, buckets []string) (*bandwidth.BucketBandwidthReport, error) { 638 values := grid.NewURLValuesWith(map[string][]string{ 639 peerRESTBuckets: buckets, 640 }) 641 return getBandwidthRPC.Call(ctx, client.gridConn(), values) 642 } 643 644 func (client *peerRESTClient) GetResourceMetrics(ctx context.Context) (<-chan MetricV2, error) { 645 resp, err := getResourceMetricsRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 646 if err != nil { 647 return nil, err 648 } 649 ch := make(chan MetricV2) 650 go func(ch chan<- MetricV2) { 651 defer close(ch) 652 for _, m := range resp.Value() { 653 if m == nil { 654 continue 655 } 656 select { 657 case <-ctx.Done(): 658 return 659 case ch <- *m: 660 } 661 } 662 }(ch) 663 return ch, nil 664 } 665 666 func (client *peerRESTClient) GetPeerMetrics(ctx context.Context) (<-chan MetricV2, error) { 667 resp, err := getPeerMetricsRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 668 if err != nil { 669 return nil, err 670 } 671 ch := make(chan MetricV2) 672 go func() { 673 defer close(ch) 674 for _, m := range resp.Value() { 675 if m == nil { 676 continue 677 } 678 select { 679 case <-ctx.Done(): 680 return 681 case ch <- *m: 682 } 683 } 684 }() 685 return ch, nil 686 } 687 688 func (client *peerRESTClient) GetPeerBucketMetrics(ctx context.Context) (<-chan MetricV2, error) { 689 resp, err := getPeerBucketMetricsRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 690 if err != nil { 691 return nil, err 692 } 693 ch := make(chan MetricV2) 694 go func() { 695 defer close(ch) 696 for _, m := range resp.Value() { 697 if m == nil { 698 continue 699 } 700 select { 701 case <-ctx.Done(): 702 return 703 case ch <- *m: 704 } 705 } 706 }() 707 return ch, nil 708 } 709 710 func (client *peerRESTClient) SpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, error) { 711 values := make(url.Values) 712 values.Set(peerRESTSize, strconv.Itoa(opts.objectSize)) 713 values.Set(peerRESTConcurrent, strconv.Itoa(opts.concurrency)) 714 values.Set(peerRESTDuration, opts.duration.String()) 715 values.Set(peerRESTStorageClass, opts.storageClass) 716 values.Set(peerRESTBucket, opts.bucketName) 717 values.Set(peerRESTEnableSha256, strconv.FormatBool(opts.enableSha256)) 718 719 respBody, err := client.callWithContext(context.Background(), peerRESTMethodSpeedTest, values, nil, -1) 720 if err != nil { 721 return SpeedTestResult{}, err 722 } 723 defer xhttp.DrainBody(respBody) 724 waitReader, err := waitForHTTPResponse(respBody) 725 if err != nil { 726 return SpeedTestResult{}, err 727 } 728 729 var result SpeedTestResult 730 err = gob.NewDecoder(waitReader).Decode(&result) 731 if err != nil { 732 return result, err 733 } 734 if result.Error != "" { 735 return result, errors.New(result.Error) 736 } 737 return result, nil 738 } 739 740 func (client *peerRESTClient) DriveSpeedTest(ctx context.Context, opts madmin.DriveSpeedTestOpts) (madmin.DriveSpeedTestResult, error) { 741 queryVals := make(url.Values) 742 if opts.Serial { 743 queryVals.Set("serial", "true") 744 } 745 queryVals.Set("blocksize", strconv.FormatUint(opts.BlockSize, 10)) 746 queryVals.Set("filesize", strconv.FormatUint(opts.FileSize, 10)) 747 748 respBody, err := client.callWithContext(ctx, peerRESTMethodDriveSpeedTest, queryVals, nil, -1) 749 if err != nil { 750 return madmin.DriveSpeedTestResult{}, err 751 } 752 defer xhttp.DrainBody(respBody) 753 waitReader, err := waitForHTTPResponse(respBody) 754 if err != nil { 755 return madmin.DriveSpeedTestResult{}, err 756 } 757 758 var result madmin.DriveSpeedTestResult 759 err = gob.NewDecoder(waitReader).Decode(&result) 760 if err != nil { 761 return result, err 762 } 763 if result.Error != "" { 764 return result, errors.New(result.Error) 765 } 766 return result, nil 767 } 768 769 func (client *peerRESTClient) GetLastDayTierStats(ctx context.Context) (DailyAllTierStats, error) { 770 resp, err := getLastDayTierStatsRPC.Call(ctx, client.gridConn(), grid.NewMSS()) 771 if err != nil || resp == nil { 772 return DailyAllTierStats{}, err 773 } 774 return *resp, nil 775 } 776 777 // DevNull - Used by netperf to pump data to peer 778 func (client *peerRESTClient) DevNull(ctx context.Context, r io.Reader) error { 779 respBody, err := client.callWithContext(ctx, peerRESTMethodDevNull, nil, r, -1) 780 if err != nil { 781 return err 782 } 783 defer xhttp.DrainBody(respBody) 784 return err 785 } 786 787 // Netperf - To initiate netperf on peer 788 func (client *peerRESTClient) Netperf(ctx context.Context, duration time.Duration) (madmin.NetperfNodeResult, error) { 789 var result madmin.NetperfNodeResult 790 values := make(url.Values) 791 values.Set(peerRESTDuration, duration.String()) 792 respBody, err := client.callWithContext(context.Background(), peerRESTMethodNetperf, values, nil, -1) 793 if err != nil { 794 return result, err 795 } 796 defer xhttp.DrainBody(respBody) 797 err = gob.NewDecoder(respBody).Decode(&result) 798 return result, err 799 } 800 801 // GetReplicationMRF - get replication MRF for bucket 802 func (client *peerRESTClient) GetReplicationMRF(ctx context.Context, bucket string) (chan madmin.ReplicationMRF, error) { 803 values := make(url.Values) 804 values.Set(peerRESTBucket, bucket) 805 806 respBody, err := client.callWithContext(ctx, peerRESTMethodGetReplicationMRF, values, nil, -1) 807 if err != nil { 808 return nil, err 809 } 810 dec := gob.NewDecoder(respBody) 811 ch := make(chan madmin.ReplicationMRF) 812 go func(ch chan madmin.ReplicationMRF) { 813 defer func() { 814 xhttp.DrainBody(respBody) 815 close(ch) 816 }() 817 for { 818 var entry madmin.ReplicationMRF 819 if err := dec.Decode(&entry); err != nil { 820 return 821 } 822 select { 823 case <-ctx.Done(): 824 return 825 case ch <- entry: 826 } 827 } 828 }(ch) 829 return ch, nil 830 }