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 }