2022-11-16 10:59:10 -05:00
// Copyright (c) 2015-2022 MinIO, Inc.
2021-04-18 15:41:13 -04:00
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2019-11-22 15:45:13 -05:00
package cmd
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
2023-06-08 11:39:47 -04:00
"os"
2021-08-30 11:27:39 -04:00
"reflect"
2019-11-22 15:45:13 -05:00
"time"
2020-07-14 12:38:05 -04:00
"github.com/minio/minio-go/v7/pkg/set"
2021-06-01 17:59:40 -04:00
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/rest"
2023-01-23 06:12:47 -05:00
"github.com/minio/mux"
2023-09-04 15:57:37 -04:00
"github.com/minio/pkg/v2/env"
2019-11-22 15:45:13 -05:00
)
const (
bootstrapRESTVersion = "v1"
bootstrapRESTVersionPrefix = SlashSeparator + bootstrapRESTVersion
bootstrapRESTPrefix = minioReservedBucketPath + "/bootstrap"
bootstrapRESTPath = bootstrapRESTPrefix + bootstrapRESTVersionPrefix
)
const (
2020-07-10 12:26:21 -04:00
bootstrapRESTMethodHealth = "/health"
2019-11-22 15:45:13 -05:00
bootstrapRESTMethodVerify = "/verify"
)
// To abstract a node over network.
type bootstrapRESTServer struct { }
// ServerSystemConfig - captures information about server configuration.
type ServerSystemConfig struct {
2020-12-01 16:50:33 -05:00
MinioEndpoints EndpointServerPools
2021-08-30 11:27:39 -04:00
MinioEnv map [ string ] string
2019-11-22 15:45:13 -05:00
}
// Diff - returns error on first difference found in two configs.
func ( s1 ServerSystemConfig ) Diff ( s2 ServerSystemConfig ) error {
2020-03-24 15:43:40 -04:00
if s1 . MinioEndpoints . NEndpoints ( ) != s2 . MinioEndpoints . NEndpoints ( ) {
return fmt . Errorf ( "Expected number of endpoints %d, seen %d" , s1 . MinioEndpoints . NEndpoints ( ) ,
s2 . MinioEndpoints . NEndpoints ( ) )
2019-11-22 15:45:13 -05:00
}
for i , ep := range s1 . MinioEndpoints {
2023-01-23 05:54:50 -05:00
if ep . CmdLine != s2 . MinioEndpoints [ i ] . CmdLine {
return fmt . Errorf ( "Expected command line argument %s, seen %s" , ep . CmdLine ,
s2 . MinioEndpoints [ i ] . CmdLine )
}
2019-11-22 15:45:13 -05:00
if ep . SetCount != s2 . MinioEndpoints [ i ] . SetCount {
return fmt . Errorf ( "Expected set count %d, seen %d" , ep . SetCount ,
s2 . MinioEndpoints [ i ] . SetCount )
}
if ep . DrivesPerSet != s2 . MinioEndpoints [ i ] . DrivesPerSet {
return fmt . Errorf ( "Expected drives pet set %d, seen %d" , ep . DrivesPerSet ,
s2 . MinioEndpoints [ i ] . DrivesPerSet )
}
2023-01-23 05:54:50 -05:00
if ep . Platform != s2 . MinioEndpoints [ i ] . Platform {
return fmt . Errorf ( "Expected platform '%s', found to be on '%s'" ,
ep . Platform , s2 . MinioEndpoints [ i ] . Platform )
2019-11-22 15:45:13 -05:00
}
2021-08-30 11:27:39 -04:00
}
if ! reflect . DeepEqual ( s1 . MinioEnv , s2 . MinioEnv ) {
2021-12-22 14:43:01 -05:00
var missing [ ] string
var mismatching [ ] string
for k , v := range s1 . MinioEnv {
ev , ok := s2 . MinioEnv [ k ]
if ! ok {
missing = append ( missing , k )
} else if v != ev {
mismatching = append ( mismatching , k )
}
}
if len ( mismatching ) > 0 {
return fmt . Errorf ( ` Expected same MINIO_ environment variables and values across all servers: Missing environment values: %s / Mismatch environment values: %s ` , missing , mismatching )
}
return fmt . Errorf ( ` Expected same MINIO_ environment variables and values across all servers: Missing environment values: %s ` , missing )
2019-11-22 15:45:13 -05:00
}
return nil
}
2021-09-01 14:37:58 -04:00
var skipEnvs = map [ string ] struct { } {
2023-03-20 04:40:24 -04:00
"MINIO_OPTS" : { } ,
"MINIO_CERT_PASSWD" : { } ,
"MINIO_SERVER_DEBUG" : { } ,
"MINIO_DSYNC_TRACE" : { } ,
"MINIO_ROOT_USER" : { } ,
"MINIO_ROOT_PASSWORD" : { } ,
"MINIO_ACCESS_KEY" : { } ,
"MINIO_SECRET_KEY" : { } ,
2021-09-01 14:37:58 -04:00
}
2019-11-22 15:45:13 -05:00
func getServerSystemCfg ( ) ServerSystemConfig {
2021-08-30 11:27:39 -04:00
envs := env . List ( "MINIO_" )
envValues := make ( map [ string ] string , len ( envs ) )
for _ , envK := range envs {
2021-09-01 14:37:58 -04:00
// skip certain environment variables as part
// of the whitelist and could be configured
// differently on each nodes, update skipEnvs()
// map if there are such environment values
if _ , ok := skipEnvs [ envK ] ; ok {
continue
}
2023-03-20 04:40:24 -04:00
envValues [ envK ] = logger . HashString ( env . Get ( envK , "" ) )
2021-08-30 11:27:39 -04:00
}
2019-11-22 15:45:13 -05:00
return ServerSystemConfig {
MinioEndpoints : globalEndpoints ,
2021-08-30 11:27:39 -04:00
MinioEnv : envValues ,
2019-11-22 15:45:13 -05:00
}
}
2023-03-20 04:40:24 -04:00
func ( b * bootstrapRESTServer ) writeErrorResponse ( w http . ResponseWriter , err error ) {
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( err . Error ( ) ) )
}
2020-07-10 12:26:21 -04:00
// HealthHandler returns success if request is valid
func ( b * bootstrapRESTServer ) HealthHandler ( w http . ResponseWriter , r * http . Request ) { }
2019-11-22 15:45:13 -05:00
func ( b * bootstrapRESTServer ) VerifyHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "VerifyHandler" )
2023-03-20 04:40:24 -04:00
if err := storageServerRequestValidate ( r ) ; err != nil {
b . writeErrorResponse ( w , err )
return
}
2019-11-22 15:45:13 -05:00
cfg := getServerSystemCfg ( )
logger . LogIf ( ctx , json . NewEncoder ( w ) . Encode ( & cfg ) )
}
// registerBootstrapRESTHandlers - register bootstrap rest router.
func registerBootstrapRESTHandlers ( router * mux . Router ) {
2023-07-08 10:35:11 -04:00
h := func ( f http . HandlerFunc ) http . HandlerFunc {
return collectInternodeStats ( httpTraceHdrs ( f ) )
}
2019-11-22 15:45:13 -05:00
server := & bootstrapRESTServer { }
subrouter := router . PathPrefix ( bootstrapRESTPrefix ) . Subrouter ( )
2020-07-10 12:26:21 -04:00
subrouter . Methods ( http . MethodPost ) . Path ( bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth ) . HandlerFunc (
2023-07-08 10:35:11 -04:00
h ( server . HealthHandler ) )
2020-07-10 12:26:21 -04:00
2019-11-22 15:45:13 -05:00
subrouter . Methods ( http . MethodPost ) . Path ( bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify ) . HandlerFunc (
2023-07-08 10:35:11 -04:00
h ( server . VerifyHandler ) )
2019-11-22 15:45:13 -05:00
}
2020-03-24 15:43:40 -04:00
// client to talk to bootstrap NEndpoints.
2019-11-22 15:45:13 -05:00
type bootstrapRESTClient struct {
endpoint Endpoint
restClient * rest . Client
}
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func ( client * bootstrapRESTClient ) callWithContext ( ctx context . Context , method string , values url . Values , body io . Reader , length int64 ) ( respBody io . ReadCloser , err error ) {
if values == nil {
values = make ( url . Values )
}
2020-09-04 12:45:06 -04:00
respBody , err = client . restClient . Call ( ctx , method , values , body , length )
2019-11-22 15:45:13 -05:00
if err == nil {
return respBody , nil
}
return nil , err
}
// Stringer provides a canonicalized representation of node.
func ( client * bootstrapRESTClient ) String ( ) string {
return client . endpoint . String ( )
}
// Verify - fetches system server config.
2020-07-10 12:26:21 -04:00
func ( client * bootstrapRESTClient ) Verify ( ctx context . Context , srcCfg ServerSystemConfig ) ( err error ) {
2019-11-22 15:45:13 -05:00
if newObjectLayerFn ( ) != nil {
return nil
}
2020-07-10 12:26:21 -04:00
respBody , err := client . callWithContext ( ctx , bootstrapRESTMethodVerify , nil , nil , - 1 )
2019-11-22 15:45:13 -05:00
if err != nil {
return
}
defer xhttp . DrainBody ( respBody )
recvCfg := ServerSystemConfig { }
if err = json . NewDecoder ( respBody ) . Decode ( & recvCfg ) ; err != nil {
return err
}
return srcCfg . Diff ( recvCfg )
}
2020-12-01 16:50:33 -05:00
func verifyServerSystemConfig ( ctx context . Context , endpointServerPools EndpointServerPools ) error {
2019-11-22 15:45:13 -05:00
srcCfg := getServerSystemCfg ( )
2020-12-01 16:50:33 -05:00
clnts := newBootstrapRESTClients ( endpointServerPools )
2019-11-22 15:45:13 -05:00
var onlineServers int
2022-11-16 10:59:10 -05:00
var offlineEndpoints [ ] error
var incorrectConfigs [ ] error
2020-07-10 12:26:21 -04:00
var retries int
2019-11-22 15:45:13 -05:00
for onlineServers < len ( clnts ) / 2 {
for _ , clnt := range clnts {
2020-07-10 12:26:21 -04:00
if err := clnt . Verify ( ctx , srcCfg ) ; err != nil {
2023-08-23 06:07:06 -04:00
bootstrapTraceMsg ( fmt . Sprintf ( "clnt.Verify: %v, endpoint: %v" , err , clnt . endpoint ) )
2022-02-07 13:39:57 -05:00
if ! isNetworkError ( err ) {
2022-11-16 10:59:10 -05:00
logger . LogOnceIf ( ctx , fmt . Errorf ( "%s has incorrect configuration: %w" , clnt . String ( ) , err ) , clnt . String ( ) )
incorrectConfigs = append ( incorrectConfigs , fmt . Errorf ( "%s has incorrect configuration: %w" , clnt . String ( ) , err ) )
} else {
offlineEndpoints = append ( offlineEndpoints , fmt . Errorf ( "%s is unreachable: %w" , clnt . String ( ) , err ) )
2019-11-22 15:45:13 -05:00
}
2022-02-07 13:39:57 -05:00
continue
2019-11-22 15:45:13 -05:00
}
onlineServers ++
}
2020-09-08 12:10:55 -04:00
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
default :
// Sleep for a while - so that we don't go into
// 100% CPU when half the endpoints are offline.
time . Sleep ( 100 * time . Millisecond )
retries ++
2022-11-16 10:59:10 -05:00
// after 20 retries start logging that servers are not reachable yet
if retries >= 20 {
logger . Info ( fmt . Sprintf ( "Waiting for atleast %d remote servers with valid configuration to be online" , len ( clnts ) / 2 ) )
2021-08-18 21:05:05 -04:00
if len ( offlineEndpoints ) > 0 {
logger . Info ( fmt . Sprintf ( "Following servers are currently offline or unreachable %s" , offlineEndpoints ) )
}
2022-11-16 10:59:10 -05:00
if len ( incorrectConfigs ) > 0 {
2023-01-23 05:54:50 -05:00
logger . Info ( fmt . Sprintf ( "Following servers have mismatching configuration %s" , incorrectConfigs ) )
2022-11-16 10:59:10 -05:00
}
2020-09-08 12:10:55 -04:00
retries = 0 // reset to log again after 5 retries.
}
offlineEndpoints = nil
2022-11-16 10:59:10 -05:00
incorrectConfigs = nil
2020-07-10 12:26:21 -04:00
}
2019-11-22 15:45:13 -05:00
}
return nil
}
2020-12-01 16:50:33 -05:00
func newBootstrapRESTClients ( endpointServerPools EndpointServerPools ) [ ] * bootstrapRESTClient {
2019-11-22 15:45:13 -05:00
seenHosts := set . NewStringSet ( )
var clnts [ ] * bootstrapRESTClient
2020-12-01 16:50:33 -05:00
for _ , ep := range endpointServerPools {
2019-11-22 15:45:13 -05:00
for _ , endpoint := range ep . Endpoints {
if seenHosts . Contains ( endpoint . Host ) {
continue
}
seenHosts . Add ( endpoint . Host )
// Only proceed for remote endpoints.
if ! endpoint . IsLocal {
2023-06-08 11:39:47 -04:00
cl := newBootstrapRESTClient ( endpoint )
if serverDebugLog {
cl . restClient . TraceOutput = os . Stdout
}
clnts = append ( clnts , cl )
2019-11-22 15:45:13 -05:00
}
}
}
return clnts
}
// Returns a new bootstrap client.
2020-07-12 01:19:38 -04:00
func newBootstrapRESTClient ( endpoint Endpoint ) * bootstrapRESTClient {
2019-11-22 15:45:13 -05:00
serverURL := & url . URL {
Scheme : endpoint . Scheme ,
Host : endpoint . Host ,
Path : bootstrapRESTPath ,
}
2021-11-23 12:51:53 -05:00
restClient := rest . NewClient ( serverURL , globalInternodeTransport , newCachedAuthToken ( ) )
2020-10-26 13:29:29 -04:00
restClient . HealthCheckFn = nil
2020-07-10 12:26:21 -04:00
2020-07-12 01:19:38 -04:00
return & bootstrapRESTClient { endpoint : endpoint , restClient : restClient }
2019-11-22 15:45:13 -05:00
}