storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bootstrap-peer-server.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 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  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"net/url"
    26  	"runtime"
    27  	"time"
    28  
    29  	"github.com/gorilla/mux"
    30  	"github.com/minio/minio-go/v7/pkg/set"
    31  
    32  	xhttp "storj.io/minio/cmd/http"
    33  	"storj.io/minio/cmd/logger"
    34  	"storj.io/minio/cmd/rest"
    35  )
    36  
    37  const (
    38  	bootstrapRESTVersion       = "v1"
    39  	bootstrapRESTVersionPrefix = SlashSeparator + bootstrapRESTVersion
    40  	bootstrapRESTPrefix        = minioReservedBucketPath + "/bootstrap"
    41  	bootstrapRESTPath          = bootstrapRESTPrefix + bootstrapRESTVersionPrefix
    42  )
    43  
    44  const (
    45  	bootstrapRESTMethodHealth = "/health"
    46  	bootstrapRESTMethodVerify = "/verify"
    47  )
    48  
    49  // To abstract a node over network.
    50  type bootstrapRESTServer struct{}
    51  
    52  // ServerSystemConfig - captures information about server configuration.
    53  type ServerSystemConfig struct {
    54  	MinioPlatform  string
    55  	MinioRuntime   string
    56  	MinioEndpoints EndpointServerPools
    57  }
    58  
    59  // Diff - returns error on first difference found in two configs.
    60  func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
    61  	if s1.MinioPlatform != s2.MinioPlatform {
    62  		return fmt.Errorf("Expected platform '%s', found to be running '%s'",
    63  			s1.MinioPlatform, s2.MinioPlatform)
    64  	}
    65  	if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
    66  		return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
    67  			s2.MinioEndpoints.NEndpoints())
    68  	}
    69  
    70  	for i, ep := range s1.MinioEndpoints {
    71  		if ep.SetCount != s2.MinioEndpoints[i].SetCount {
    72  			return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
    73  				s2.MinioEndpoints[i].SetCount)
    74  		}
    75  		if ep.DrivesPerSet != s2.MinioEndpoints[i].DrivesPerSet {
    76  			return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
    77  				s2.MinioEndpoints[i].DrivesPerSet)
    78  		}
    79  		for j, endpoint := range ep.Endpoints {
    80  			if endpoint.String() != s2.MinioEndpoints[i].Endpoints[j].String() {
    81  				return fmt.Errorf("Expected endpoint %s, seen %s", endpoint,
    82  					s2.MinioEndpoints[i].Endpoints[j])
    83  			}
    84  		}
    85  
    86  	}
    87  	return nil
    88  }
    89  
    90  func getServerSystemCfg() ServerSystemConfig {
    91  	return ServerSystemConfig{
    92  		MinioPlatform:  fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
    93  		MinioEndpoints: globalEndpoints,
    94  	}
    95  }
    96  
    97  // HealthHandler returns success if request is valid
    98  func (b *bootstrapRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request) {}
    99  
   100  func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Request) {
   101  	ctx := NewContext(r, w, "VerifyHandler")
   102  	cfg := getServerSystemCfg()
   103  	logger.LogIf(ctx, json.NewEncoder(w).Encode(&cfg))
   104  	w.(http.Flusher).Flush()
   105  }
   106  
   107  // registerBootstrapRESTHandlers - register bootstrap rest router.
   108  func registerBootstrapRESTHandlers(router *mux.Router) {
   109  	server := &bootstrapRESTServer{}
   110  	subrouter := router.PathPrefix(bootstrapRESTPrefix).Subrouter()
   111  
   112  	subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth).HandlerFunc(
   113  		HTTPTraceHdrs(server.HealthHandler))
   114  
   115  	subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify).HandlerFunc(
   116  		HTTPTraceHdrs(server.VerifyHandler))
   117  }
   118  
   119  // client to talk to bootstrap NEndpoints.
   120  type bootstrapRESTClient struct {
   121  	endpoint   Endpoint
   122  	restClient *rest.Client
   123  }
   124  
   125  // Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
   126  // permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
   127  // after verifying format.json
   128  func (client *bootstrapRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
   129  	if values == nil {
   130  		values = make(url.Values)
   131  	}
   132  
   133  	respBody, err = client.restClient.Call(ctx, method, values, body, length)
   134  	if err == nil {
   135  		return respBody, nil
   136  	}
   137  
   138  	return nil, err
   139  }
   140  
   141  // Stringer provides a canonicalized representation of node.
   142  func (client *bootstrapRESTClient) String() string {
   143  	return client.endpoint.String()
   144  }
   145  
   146  // Verify - fetches system server config.
   147  func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg ServerSystemConfig) (err error) {
   148  	if newObjectLayerFn() != nil {
   149  		return nil
   150  	}
   151  	respBody, err := client.callWithContext(ctx, bootstrapRESTMethodVerify, nil, nil, -1)
   152  	if err != nil {
   153  		return
   154  	}
   155  	defer xhttp.DrainBody(respBody)
   156  	recvCfg := ServerSystemConfig{}
   157  	if err = json.NewDecoder(respBody).Decode(&recvCfg); err != nil {
   158  		return err
   159  	}
   160  	return srcCfg.Diff(recvCfg)
   161  }
   162  
   163  func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools) error {
   164  	srcCfg := getServerSystemCfg()
   165  	clnts := newBootstrapRESTClients(endpointServerPools)
   166  	var onlineServers int
   167  	var offlineEndpoints []string
   168  	var retries int
   169  	for onlineServers < len(clnts)/2 {
   170  		for _, clnt := range clnts {
   171  			if err := clnt.Verify(ctx, srcCfg); err != nil {
   172  				if isNetworkError(err) {
   173  					offlineEndpoints = append(offlineEndpoints, clnt.String())
   174  					continue
   175  				}
   176  				return fmt.Errorf("%s as has incorrect configuration: %w", clnt.String(), err)
   177  			}
   178  			onlineServers++
   179  		}
   180  		select {
   181  		case <-ctx.Done():
   182  			return ctx.Err()
   183  		default:
   184  			// Sleep for a while - so that we don't go into
   185  			// 100% CPU when half the endpoints are offline.
   186  			time.Sleep(100 * time.Millisecond)
   187  			retries++
   188  			// after 5 retries start logging that servers are not reachable yet
   189  			if retries >= 5 {
   190  				logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers to be online for bootstrap check", len(clnts)/2))
   191  				logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
   192  				retries = 0 // reset to log again after 5 retries.
   193  			}
   194  			offlineEndpoints = nil
   195  		}
   196  	}
   197  	return nil
   198  }
   199  
   200  func newBootstrapRESTClients(endpointServerPools EndpointServerPools) []*bootstrapRESTClient {
   201  	seenHosts := set.NewStringSet()
   202  	var clnts []*bootstrapRESTClient
   203  	for _, ep := range endpointServerPools {
   204  		for _, endpoint := range ep.Endpoints {
   205  			if seenHosts.Contains(endpoint.Host) {
   206  				continue
   207  			}
   208  			seenHosts.Add(endpoint.Host)
   209  
   210  			// Only proceed for remote endpoints.
   211  			if !endpoint.IsLocal {
   212  				clnts = append(clnts, newBootstrapRESTClient(endpoint))
   213  			}
   214  		}
   215  	}
   216  	return clnts
   217  }
   218  
   219  // Returns a new bootstrap client.
   220  func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
   221  	serverURL := &url.URL{
   222  		Scheme: endpoint.Scheme,
   223  		Host:   endpoint.Host,
   224  		Path:   bootstrapRESTPath,
   225  	}
   226  
   227  	restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
   228  	restClient.HealthCheckFn = nil
   229  
   230  	return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
   231  }