storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/endpoint.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017-2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "net" 24 "net/http" 25 "net/url" 26 "path" 27 "path/filepath" 28 "reflect" 29 "runtime" 30 "sort" 31 "strconv" 32 "strings" 33 "time" 34 35 "github.com/dustin/go-humanize" 36 "github.com/minio/minio-go/v7/pkg/set" 37 38 "storj.io/minio/cmd/config" 39 xhttp "storj.io/minio/cmd/http" 40 "storj.io/minio/cmd/logger" 41 "storj.io/minio/pkg/env" 42 "storj.io/minio/pkg/mountinfo" 43 xnet "storj.io/minio/pkg/net" 44 ) 45 46 // EndpointType - enum for endpoint type. 47 type EndpointType int 48 49 const ( 50 // PathEndpointType - path style endpoint type enum. 51 PathEndpointType EndpointType = iota + 1 52 53 // URLEndpointType - URL style endpoint type enum. 54 URLEndpointType 55 ) 56 57 // ProxyEndpoint - endpoint used for proxy redirects 58 // See proxyRequest() for details. 59 type ProxyEndpoint struct { 60 Endpoint 61 Transport http.RoundTripper 62 } 63 64 // Endpoint - any type of endpoint. 65 type Endpoint struct { 66 *url.URL 67 IsLocal bool 68 } 69 70 func (endpoint Endpoint) String() string { 71 if endpoint.Host == "" { 72 return endpoint.Path 73 } 74 75 return endpoint.URL.String() 76 } 77 78 // Type - returns type of endpoint. 79 func (endpoint Endpoint) Type() EndpointType { 80 if endpoint.Host == "" { 81 return PathEndpointType 82 } 83 84 return URLEndpointType 85 } 86 87 // HTTPS - returns true if secure for URLEndpointType. 88 func (endpoint Endpoint) HTTPS() bool { 89 return endpoint.Scheme == "https" 90 } 91 92 // UpdateIsLocal - resolves the host and updates if it is local or not. 93 func (endpoint *Endpoint) UpdateIsLocal() (err error) { 94 if !endpoint.IsLocal { 95 endpoint.IsLocal, err = isLocalHost(endpoint.Hostname(), endpoint.Port(), globalMinioPort) 96 if err != nil { 97 return err 98 } 99 } 100 return nil 101 } 102 103 // NewEndpoint - returns new endpoint based on given arguments. 104 func NewEndpoint(arg string) (ep Endpoint, e error) { 105 // isEmptyPath - check whether given path is not empty. 106 isEmptyPath := func(path string) bool { 107 return path == "" || path == SlashSeparator || path == `\` 108 } 109 110 if isEmptyPath(arg) { 111 return ep, fmt.Errorf("empty or root endpoint is not supported") 112 } 113 114 var isLocal bool 115 var host string 116 u, err := url.Parse(arg) 117 if err == nil && u.Host != "" { 118 // URL style of endpoint. 119 // Valid URL style endpoint is 120 // - Scheme field must contain "http" or "https" 121 // - All field should be empty except Host and Path. 122 if !((u.Scheme == "http" || u.Scheme == "https") && 123 u.User == nil && u.Opaque == "" && !u.ForceQuery && u.RawQuery == "" && u.Fragment == "") { 124 return ep, fmt.Errorf("invalid URL endpoint format") 125 } 126 127 var port string 128 host, port, err = net.SplitHostPort(u.Host) 129 if err != nil { 130 if !strings.Contains(err.Error(), "missing port in address") { 131 return ep, fmt.Errorf("invalid URL endpoint format: %w", err) 132 } 133 134 host = u.Host 135 } else { 136 var p int 137 p, err = strconv.Atoi(port) 138 if err != nil { 139 return ep, fmt.Errorf("invalid URL endpoint format: invalid port number") 140 } else if p < 1 || p > 65535 { 141 return ep, fmt.Errorf("invalid URL endpoint format: port number must be between 1 to 65535") 142 } 143 } 144 if i := strings.Index(host, "%"); i > -1 { 145 host = host[:i] 146 } 147 148 if host == "" { 149 return ep, fmt.Errorf("invalid URL endpoint format: empty host name") 150 } 151 152 // As this is path in the URL, we should use path package, not filepath package. 153 // On MS Windows, filepath.Clean() converts into Windows path style ie `/foo` becomes `\foo` 154 u.Path = path.Clean(u.Path) 155 if isEmptyPath(u.Path) { 156 return ep, fmt.Errorf("empty or root path is not supported in URL endpoint") 157 } 158 159 // On windows having a preceding SlashSeparator will cause problems, if the 160 // command line already has C:/<export-folder/ in it. Final resulting 161 // path on windows might become C:/C:/ this will cause problems 162 // of starting minio server properly in distributed mode on windows. 163 // As a special case make sure to trim the separator. 164 165 // NOTE: It is also perfectly fine for windows users to have a path 166 // without C:/ since at that point we treat it as relative path 167 // and obtain the full filesystem path as well. Providing C:/ 168 // style is necessary to provide paths other than C:/, 169 // such as F:/, D:/ etc. 170 // 171 // Another additional benefit here is that this style also 172 // supports providing \\host\share support as well. 173 if runtime.GOOS == globalWindowsOSName { 174 if filepath.VolumeName(u.Path[1:]) != "" { 175 u.Path = u.Path[1:] 176 } 177 } 178 179 } else { 180 // Only check if the arg is an ip address and ask for scheme since its absent. 181 // localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as 182 // /mnt/export1. So we go ahead and start the minio server in FS modes in these cases. 183 if isHostIP(arg) { 184 return ep, fmt.Errorf("invalid URL endpoint format: missing scheme http or https") 185 } 186 absArg, err := filepath.Abs(arg) 187 if err != nil { 188 return Endpoint{}, fmt.Errorf("absolute path failed %s", err) 189 } 190 u = &url.URL{Path: path.Clean(absArg)} 191 isLocal = true 192 } 193 194 return Endpoint{ 195 URL: u, 196 IsLocal: isLocal, 197 }, nil 198 } 199 200 // PoolEndpoints represent endpoints in a given pool 201 // along with its setCount and setDriveCount. 202 type PoolEndpoints struct { 203 SetCount int 204 DrivesPerSet int 205 Endpoints Endpoints 206 } 207 208 // EndpointServerPools - list of list of endpoints 209 type EndpointServerPools []PoolEndpoints 210 211 // GetLocalPoolIdx returns the pool which endpoint belongs to locally. 212 // if ep is remote this code will return -1 poolIndex 213 func (l EndpointServerPools) GetLocalPoolIdx(ep Endpoint) int { 214 for i, zep := range l { 215 for _, cep := range zep.Endpoints { 216 if cep.IsLocal && ep.IsLocal { 217 if reflect.DeepEqual(cep, ep) { 218 return i 219 } 220 } 221 } 222 } 223 return -1 224 } 225 226 // Add add pool endpoints 227 func (l *EndpointServerPools) Add(zeps PoolEndpoints) error { 228 existSet := set.NewStringSet() 229 for _, zep := range *l { 230 for _, ep := range zep.Endpoints { 231 existSet.Add(ep.String()) 232 } 233 } 234 // Validate if there are duplicate endpoints across serverPools 235 for _, ep := range zeps.Endpoints { 236 if existSet.Contains(ep.String()) { 237 return fmt.Errorf("duplicate endpoints found") 238 } 239 } 240 *l = append(*l, zeps) 241 return nil 242 } 243 244 // Localhost - returns the local hostname from list of endpoints 245 func (l EndpointServerPools) Localhost() string { 246 for _, ep := range l { 247 for _, endpoint := range ep.Endpoints { 248 if endpoint.IsLocal { 249 u := &url.URL{ 250 Scheme: endpoint.Scheme, 251 Host: endpoint.Host, 252 } 253 return u.String() 254 } 255 } 256 } 257 return "" 258 } 259 260 // FirstLocal returns true if the first endpoint is local. 261 func (l EndpointServerPools) FirstLocal() bool { 262 return l[0].Endpoints[0].IsLocal 263 } 264 265 // HTTPS - returns true if secure for URLEndpointType. 266 func (l EndpointServerPools) HTTPS() bool { 267 return l[0].Endpoints.HTTPS() 268 } 269 270 // NEndpoints - returns all nodes count 271 func (l EndpointServerPools) NEndpoints() (count int) { 272 for _, ep := range l { 273 count += len(ep.Endpoints) 274 } 275 return count 276 } 277 278 // Hostnames - returns list of unique hostnames 279 func (l EndpointServerPools) Hostnames() []string { 280 foundSet := set.NewStringSet() 281 for _, ep := range l { 282 for _, endpoint := range ep.Endpoints { 283 if foundSet.Contains(endpoint.Hostname()) { 284 continue 285 } 286 foundSet.Add(endpoint.Hostname()) 287 } 288 } 289 return foundSet.ToSlice() 290 } 291 292 // hostsSorted will return all hosts found. 293 // The LOCAL host will be nil, but the indexes of all hosts should 294 // remain consistent across the cluster. 295 func (l EndpointServerPools) hostsSorted() []*xnet.Host { 296 peers, localPeer := l.peers() 297 sort.Strings(peers) 298 hosts := make([]*xnet.Host, len(peers)) 299 for i, hostStr := range peers { 300 if hostStr == localPeer { 301 continue 302 } 303 host, err := xnet.ParseHost(hostStr) 304 if err != nil { 305 logger.LogIf(GlobalContext, err) 306 continue 307 } 308 hosts[i] = host 309 } 310 311 return hosts 312 } 313 314 // peers will return all peers, including local. 315 // The local peer is returned as a separate string. 316 func (l EndpointServerPools) peers() (peers []string, local string) { 317 allSet := set.NewStringSet() 318 for _, ep := range l { 319 for _, endpoint := range ep.Endpoints { 320 if endpoint.Type() != URLEndpointType { 321 continue 322 } 323 324 peer := endpoint.Host 325 if endpoint.IsLocal { 326 if _, port := mustSplitHostPort(peer); port == globalMinioPort { 327 local = peer 328 } 329 } 330 331 allSet.Add(peer) 332 } 333 } 334 335 return allSet.ToSlice(), local 336 } 337 338 // Endpoints - list of same type of endpoint. 339 type Endpoints []Endpoint 340 341 // HTTPS - returns true if secure for URLEndpointType. 342 func (endpoints Endpoints) HTTPS() bool { 343 return endpoints[0].HTTPS() 344 } 345 346 // GetString - returns endpoint string of i-th endpoint (0-based), 347 // and empty string for invalid indexes. 348 func (endpoints Endpoints) GetString(i int) string { 349 if i < 0 || i >= len(endpoints) { 350 return "" 351 } 352 return endpoints[i].String() 353 } 354 355 // GetAllStrings - returns allstring of all endpoints 356 func (endpoints Endpoints) GetAllStrings() (all []string) { 357 for _, e := range endpoints { 358 all = append(all, e.String()) 359 } 360 return 361 } 362 363 func hostResolveToLocalhost(endpoint Endpoint) bool { 364 hostIPs, err := getHostIP(endpoint.Hostname()) 365 if err != nil { 366 // Log the message to console about the host resolving 367 reqInfo := (&logger.ReqInfo{}).AppendTags( 368 "host", 369 endpoint.Hostname(), 370 ) 371 ctx := logger.SetReqInfo(GlobalContext, reqInfo) 372 logger.LogIf(ctx, err, logger.Application) 373 return false 374 } 375 var loopback int 376 for _, hostIP := range hostIPs.ToSlice() { 377 if net.ParseIP(hostIP).IsLoopback() { 378 loopback++ 379 } 380 } 381 return loopback == len(hostIPs) 382 } 383 384 func (endpoints Endpoints) atleastOneEndpointLocal() bool { 385 for _, endpoint := range endpoints { 386 if endpoint.IsLocal { 387 return true 388 } 389 } 390 return false 391 } 392 393 // UpdateIsLocal - resolves the host and discovers the local host. 394 func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error { 395 orchestrated := IsDocker() || IsKubernetes() 396 k8sReplicaSet := IsKubernetesReplicaSet() 397 398 var epsResolved int 399 var foundLocal bool 400 resolvedList := make([]bool, len(endpoints)) 401 // Mark the starting time 402 startTime := time.Now() 403 keepAliveTicker := time.NewTicker(10 * time.Millisecond) 404 defer keepAliveTicker.Stop() 405 for { 406 // Break if the local endpoint is found already Or all the endpoints are resolved. 407 if foundLocal || (epsResolved == len(endpoints)) { 408 break 409 } 410 // Retry infinitely on Kubernetes and Docker swarm. 411 // This is needed as the remote hosts are sometime 412 // not available immediately. 413 select { 414 case <-globalOSSignalCh: 415 return fmt.Errorf("The endpoint resolution got interrupted") 416 default: 417 for i, resolved := range resolvedList { 418 if resolved { 419 // Continue if host is already resolved. 420 continue 421 } 422 423 // Log the message to console about the host resolving 424 reqInfo := (&logger.ReqInfo{}).AppendTags( 425 "host", 426 endpoints[i].Hostname(), 427 ) 428 429 if k8sReplicaSet && hostResolveToLocalhost(endpoints[i]) { 430 err := fmt.Errorf("host %s resolves to 127.*, DNS incorrectly configured retrying", 431 endpoints[i]) 432 // time elapsed 433 timeElapsed := time.Since(startTime) 434 // log error only if more than 1s elapsed 435 if timeElapsed > time.Second { 436 reqInfo.AppendTags("elapsedTime", 437 humanize.RelTime(startTime, 438 startTime.Add(timeElapsed), 439 "elapsed", 440 "")) 441 ctx := logger.SetReqInfo(GlobalContext, reqInfo) 442 logger.LogIf(ctx, err, logger.Application) 443 } 444 continue 445 } 446 447 // return err if not Docker or Kubernetes 448 // We use IsDocker() to check for Docker environment 449 // We use IsKubernetes() to check for Kubernetes environment 450 isLocal, err := isLocalHost(endpoints[i].Hostname(), 451 endpoints[i].Port(), 452 globalMinioPort, 453 ) 454 if err != nil && !orchestrated { 455 return err 456 } 457 if err != nil { 458 // time elapsed 459 timeElapsed := time.Since(startTime) 460 // log error only if more than 1s elapsed 461 if timeElapsed > time.Second { 462 reqInfo.AppendTags("elapsedTime", 463 humanize.RelTime(startTime, 464 startTime.Add(timeElapsed), 465 "elapsed", 466 "", 467 )) 468 ctx := logger.SetReqInfo(GlobalContext, 469 reqInfo) 470 logger.LogIf(ctx, err, logger.Application) 471 } 472 } else { 473 resolvedList[i] = true 474 endpoints[i].IsLocal = isLocal 475 if k8sReplicaSet && !endpoints.atleastOneEndpointLocal() && !foundPrevLocal { 476 // In replicated set in k8s deployment, IPs might 477 // get resolved for older IPs, add this code 478 // to ensure that we wait for this server to 479 // participate atleast one disk and be local. 480 // 481 // In special cases for replica set with expanded 482 // pool setups we need to make sure to provide 483 // value of foundPrevLocal from pool1 if we already 484 // found a local setup. Only if we haven't found 485 // previous local we continue to wait to look for 486 // atleast one local. 487 resolvedList[i] = false 488 // time elapsed 489 err := fmt.Errorf("no endpoint is local to this host: %s", endpoints[i]) 490 timeElapsed := time.Since(startTime) 491 // log error only if more than 1s elapsed 492 if timeElapsed > time.Second { 493 reqInfo.AppendTags("elapsedTime", 494 humanize.RelTime(startTime, 495 startTime.Add(timeElapsed), 496 "elapsed", 497 "", 498 )) 499 ctx := logger.SetReqInfo(GlobalContext, 500 reqInfo) 501 logger.LogIf(ctx, err, logger.Application) 502 } 503 continue 504 } 505 epsResolved++ 506 if !foundLocal { 507 foundLocal = isLocal 508 } 509 } 510 } 511 512 // Wait for the tick, if the there exist a local endpoint in discovery. 513 // Non docker/kubernetes environment we do not need to wait. 514 if !foundLocal && orchestrated { 515 <-keepAliveTicker.C 516 } 517 } 518 } 519 520 // On Kubernetes/Docker setups DNS resolves inappropriately sometimes 521 // where there are situations same endpoints with multiple disks 522 // come online indicating either one of them is local and some 523 // of them are not local. This situation can never happen and 524 // its only a possibility in orchestrated deployments with dynamic 525 // DNS. Following code ensures that we treat if one of the endpoint 526 // says its local for a given host - it is true for all endpoints 527 // for the same host. Following code ensures that this assumption 528 // is true and it works in all scenarios and it is safe to assume 529 // for a given host. 530 endpointLocalMap := make(map[string]bool) 531 for _, ep := range endpoints { 532 if ep.IsLocal { 533 endpointLocalMap[ep.Host] = ep.IsLocal 534 } 535 } 536 for i := range endpoints { 537 endpoints[i].IsLocal = endpointLocalMap[endpoints[i].Host] 538 } 539 return nil 540 } 541 542 // NewEndpoints - returns new endpoint list based on input args. 543 func NewEndpoints(args ...string) (endpoints Endpoints, err error) { 544 var endpointType EndpointType 545 var scheme string 546 547 uniqueArgs := set.NewStringSet() 548 // Loop through args and adds to endpoint list. 549 for i, arg := range args { 550 endpoint, err := NewEndpoint(arg) 551 if err != nil { 552 return nil, fmt.Errorf("'%s': %s", arg, err.Error()) 553 } 554 555 // All endpoints have to be same type and scheme if applicable. 556 if i == 0 { 557 endpointType = endpoint.Type() 558 scheme = endpoint.Scheme 559 } else if endpoint.Type() != endpointType { 560 return nil, fmt.Errorf("mixed style endpoints are not supported") 561 } else if endpoint.Scheme != scheme { 562 return nil, fmt.Errorf("mixed scheme is not supported") 563 } 564 565 arg = endpoint.String() 566 if uniqueArgs.Contains(arg) { 567 return nil, fmt.Errorf("duplicate endpoints found") 568 } 569 uniqueArgs.Add(arg) 570 endpoints = append(endpoints, endpoint) 571 } 572 573 return endpoints, nil 574 } 575 576 // Checks if there are any cross device mounts. 577 func checkCrossDeviceMounts(endpoints Endpoints) (err error) { 578 var absPaths []string 579 for _, endpoint := range endpoints { 580 if endpoint.IsLocal { 581 var absPath string 582 absPath, err = filepath.Abs(endpoint.Path) 583 if err != nil { 584 return err 585 } 586 absPaths = append(absPaths, absPath) 587 } 588 } 589 return mountinfo.CheckCrossDevice(absPaths) 590 } 591 592 // CreateEndpoints - validates and creates new endpoints for given args. 593 func CreateEndpoints(serverAddr string, foundLocal bool, args ...[]string) (Endpoints, SetupType, error) { 594 var endpoints Endpoints 595 var setupType SetupType 596 var err error 597 598 // Check whether serverAddr is valid for this host. 599 if err = CheckLocalServerAddr(serverAddr); err != nil { 600 return endpoints, setupType, err 601 } 602 603 _, serverAddrPort := mustSplitHostPort(serverAddr) 604 605 // For single arg, return FS setup. 606 if len(args) == 1 && len(args[0]) == 1 { 607 var endpoint Endpoint 608 endpoint, err = NewEndpoint(args[0][0]) 609 if err != nil { 610 return endpoints, setupType, err 611 } 612 if err := endpoint.UpdateIsLocal(); err != nil { 613 return endpoints, setupType, err 614 } 615 if endpoint.Type() != PathEndpointType { 616 return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg("use path style endpoint for FS setup") 617 } 618 endpoints = append(endpoints, endpoint) 619 setupType = FSSetupType 620 621 // Check for cross device mounts if any. 622 if err = checkCrossDeviceMounts(endpoints); err != nil { 623 return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg(err.Error()) 624 } 625 626 return endpoints, setupType, nil 627 } 628 629 for _, iargs := range args { 630 // Convert args to endpoints 631 eps, err := NewEndpoints(iargs...) 632 if err != nil { 633 return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error()) 634 } 635 636 // Check for cross device mounts if any. 637 if err = checkCrossDeviceMounts(eps); err != nil { 638 return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error()) 639 } 640 641 endpoints = append(endpoints, eps...) 642 } 643 644 if len(endpoints) == 0 { 645 return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg("invalid number of endpoints") 646 } 647 648 // Return Erasure setup when all endpoints are path style. 649 if endpoints[0].Type() == PathEndpointType { 650 setupType = ErasureSetupType 651 return endpoints, setupType, nil 652 } 653 654 if err = endpoints.UpdateIsLocal(foundLocal); err != nil { 655 return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error()) 656 } 657 658 // Here all endpoints are URL style. 659 endpointPathSet := set.NewStringSet() 660 localEndpointCount := 0 661 localServerHostSet := set.NewStringSet() 662 localPortSet := set.NewStringSet() 663 664 for _, endpoint := range endpoints { 665 endpointPathSet.Add(endpoint.Path) 666 if endpoint.IsLocal { 667 localServerHostSet.Add(endpoint.Hostname()) 668 669 var port string 670 _, port, err = net.SplitHostPort(endpoint.Host) 671 if err != nil { 672 port = serverAddrPort 673 } 674 localPortSet.Add(port) 675 676 localEndpointCount++ 677 } 678 } 679 680 // Check whether same path is not used in endpoints of a host on different port. 681 { 682 pathIPMap := make(map[string]set.StringSet) 683 for _, endpoint := range endpoints { 684 host := endpoint.Hostname() 685 hostIPSet, _ := getHostIP(host) 686 if IPSet, ok := pathIPMap[endpoint.Path]; ok { 687 if !IPSet.Intersection(hostIPSet).IsEmpty() { 688 return endpoints, setupType, 689 config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' can not be served by different port on same address", endpoint.Path)) 690 } 691 pathIPMap[endpoint.Path] = IPSet.Union(hostIPSet) 692 } else { 693 pathIPMap[endpoint.Path] = hostIPSet 694 } 695 } 696 } 697 698 // Check whether same path is used for more than 1 local endpoints. 699 { 700 localPathSet := set.CreateStringSet() 701 for _, endpoint := range endpoints { 702 if !endpoint.IsLocal { 703 continue 704 } 705 if localPathSet.Contains(endpoint.Path) { 706 return endpoints, setupType, 707 config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' cannot be served by different address on same server", endpoint.Path)) 708 } 709 localPathSet.Add(endpoint.Path) 710 } 711 } 712 713 // All endpoints are pointing to local host 714 if len(endpoints) == localEndpointCount { 715 // If all endpoints have same port number, Just treat it as local erasure setup 716 // using URL style endpoints. 717 if len(localPortSet) == 1 { 718 if len(localServerHostSet) > 1 { 719 return endpoints, setupType, 720 config.ErrInvalidErasureEndpoints(nil).Msg("all local endpoints should not have different hostnames/ips") 721 } 722 return endpoints, ErasureSetupType, nil 723 } 724 725 // Even though all endpoints are local, but those endpoints use different ports. 726 // This means it is DistErasure setup. 727 } 728 729 // Add missing port in all endpoints. 730 for i := range endpoints { 731 _, port, err := net.SplitHostPort(endpoints[i].Host) 732 if err != nil { 733 endpoints[i].Host = net.JoinHostPort(endpoints[i].Host, serverAddrPort) 734 } else if endpoints[i].IsLocal && serverAddrPort != port { 735 // If endpoint is local, but port is different than serverAddrPort, then make it as remote. 736 endpoints[i].IsLocal = false 737 } 738 } 739 740 uniqueArgs := set.NewStringSet() 741 for _, endpoint := range endpoints { 742 uniqueArgs.Add(endpoint.Host) 743 } 744 745 // Error out if we have less than 2 unique servers. 746 if len(uniqueArgs.ToSlice()) < 2 && setupType == DistErasureSetupType { 747 err := fmt.Errorf("Unsupported number of endpoints (%s), minimum number of servers cannot be less than 2 in distributed setup", endpoints) 748 return endpoints, setupType, err 749 } 750 751 publicIPs := env.Get(config.EnvPublicIPs, "") 752 if len(publicIPs) == 0 { 753 updateDomainIPs(uniqueArgs) 754 } 755 756 setupType = DistErasureSetupType 757 return endpoints, setupType, nil 758 } 759 760 // GetLocalPeer - returns local peer value, returns globalMinioAddr 761 // for FS and Erasure mode. In case of distributed server return 762 // the first element from the set of peers which indicate that 763 // they are local. There is always one entry that is local 764 // even with repeated server endpoints. 765 func GetLocalPeer(endpointServerPools EndpointServerPools, host, port string) (localPeer string) { 766 peerSet := set.NewStringSet() 767 for _, ep := range endpointServerPools { 768 for _, endpoint := range ep.Endpoints { 769 if endpoint.Type() != URLEndpointType { 770 continue 771 } 772 if endpoint.IsLocal && endpoint.Host != "" { 773 peerSet.Add(endpoint.Host) 774 } 775 } 776 } 777 if peerSet.IsEmpty() { 778 // Local peer can be empty in FS or Erasure coded mode. 779 // If so, return globalMinioHost + globalMinioPort value. 780 if host != "" { 781 return net.JoinHostPort(host, port) 782 } 783 784 return net.JoinHostPort("127.0.0.1", port) 785 } 786 return peerSet.ToSlice()[0] 787 } 788 789 // GetProxyEndpointLocalIndex returns index of the local proxy endpoint 790 func GetProxyEndpointLocalIndex(proxyEps []ProxyEndpoint) int { 791 for i, pep := range proxyEps { 792 if pep.IsLocal { 793 return i 794 } 795 } 796 return -1 797 } 798 799 func httpDo(clnt *http.Client, req *http.Request, f func(*http.Response, error) error) error { 800 ctx, cancel := context.WithTimeout(GlobalContext, 200*time.Millisecond) 801 defer cancel() 802 803 // Run the HTTP request in a goroutine and pass the response to f. 804 c := make(chan error, 1) 805 req = req.WithContext(ctx) 806 go func() { c <- f(clnt.Do(req)) }() 807 select { 808 case <-ctx.Done(): 809 <-c // Wait for f to return. 810 return ctx.Err() 811 case err := <-c: 812 return err 813 } 814 } 815 816 func getOnlineProxyEndpointIdx() int { 817 type reqIndex struct { 818 Request *http.Request 819 Idx int 820 } 821 822 proxyRequests := make(map[*http.Client]reqIndex, len(globalProxyEndpoints)) 823 for i, proxyEp := range globalProxyEndpoints { 824 proxyEp := proxyEp 825 serverURL := &url.URL{ 826 Scheme: proxyEp.Scheme, 827 Host: proxyEp.Host, 828 Path: pathJoin(healthCheckPathPrefix, healthCheckLivenessPath), 829 } 830 831 req, err := http.NewRequest(http.MethodGet, serverURL.String(), nil) 832 if err != nil { 833 continue 834 } 835 836 proxyRequests[&http.Client{ 837 Transport: proxyEp.Transport, 838 }] = reqIndex{ 839 Request: req, 840 Idx: i, 841 } 842 } 843 844 for c, r := range proxyRequests { 845 if err := httpDo(c, r.Request, func(resp *http.Response, err error) error { 846 if err != nil { 847 return err 848 } 849 xhttp.DrainBody(resp.Body) 850 if resp.StatusCode != http.StatusOK { 851 return errors.New(resp.Status) 852 } 853 if v := resp.Header.Get(xhttp.MinIOServerStatus); v == unavailable { 854 return errors.New(v) 855 } 856 return nil 857 }); err != nil { 858 continue 859 } 860 return r.Idx 861 } 862 return -1 863 } 864 865 // GetProxyEndpoints - get all endpoints that can be used to proxy list request. 866 func GetProxyEndpoints(endpointServerPools EndpointServerPools) []ProxyEndpoint { 867 var proxyEps []ProxyEndpoint 868 869 proxyEpSet := set.NewStringSet() 870 871 for _, ep := range endpointServerPools { 872 for _, endpoint := range ep.Endpoints { 873 if endpoint.Type() != URLEndpointType { 874 continue 875 } 876 877 host := endpoint.Host 878 if proxyEpSet.Contains(host) { 879 continue 880 } 881 proxyEpSet.Add(host) 882 883 proxyEps = append(proxyEps, ProxyEndpoint{ 884 Endpoint: endpoint, 885 Transport: globalProxyTransport, 886 }) 887 } 888 } 889 return proxyEps 890 } 891 892 func updateDomainIPs(endPoints set.StringSet) { 893 ipList := set.NewStringSet() 894 for e := range endPoints { 895 host, port, err := net.SplitHostPort(e) 896 if err != nil { 897 if strings.Contains(err.Error(), "missing port in address") { 898 host = e 899 port = globalMinioPort 900 } else { 901 continue 902 } 903 } 904 905 if net.ParseIP(host) == nil { 906 IPs, err := getHostIP(host) 907 if err != nil { 908 continue 909 } 910 911 IPsWithPort := IPs.ApplyFunc(func(ip string) string { 912 return net.JoinHostPort(ip, port) 913 }) 914 915 ipList = ipList.Union(IPsWithPort) 916 } 917 918 ipList.Add(net.JoinHostPort(host, port)) 919 } 920 921 globalDomainIPs = ipList.FuncMatch(func(ip string, matchString string) bool { 922 host, _, err := net.SplitHostPort(ip) 923 if err != nil { 924 host = ip 925 } 926 return !net.ParseIP(host).IsLoopback() && host != "localhost" 927 }, "") 928 }