github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/grid/manager.go (about)

     1  // Copyright (c) 2015-2023 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 grid
    19  
    20  import (
    21  	"context"
    22  	"crypto/tls"
    23  	"fmt"
    24  	"net/http"
    25  	"runtime/debug"
    26  	"strings"
    27  
    28  	"github.com/gobwas/ws"
    29  	"github.com/gobwas/ws/wsutil"
    30  	"github.com/google/uuid"
    31  	"github.com/minio/madmin-go/v3"
    32  	"github.com/minio/minio/internal/logger"
    33  	"github.com/minio/minio/internal/pubsub"
    34  	"github.com/minio/mux"
    35  )
    36  
    37  const (
    38  	// apiVersion is a major version of the entire api.
    39  	// Bumping this should only be done when overall,
    40  	// incompatible changes are made, not when adding a new handler
    41  	// or changing an existing handler.
    42  	apiVersion = "v1"
    43  
    44  	// RoutePath is the remote path to connect to.
    45  	RoutePath = "/minio/grid/" + apiVersion
    46  )
    47  
    48  // Manager will contain all the connections to the grid.
    49  // It also handles incoming requests and routes them to the appropriate connection.
    50  type Manager struct {
    51  	// ID is an instance ID, that will change whenever the server restarts.
    52  	// This allows remotes to keep track of whether state is preserved.
    53  	ID uuid.UUID
    54  
    55  	// Immutable after creation, so no locks.
    56  	targets map[string]*Connection
    57  
    58  	// serverside handlers.
    59  	handlers handlers
    60  
    61  	// local host name.
    62  	local string
    63  
    64  	// Validate incoming requests.
    65  	authRequest func(r *http.Request) error
    66  }
    67  
    68  // ManagerOptions are options for creating a new grid manager.
    69  type ManagerOptions struct {
    70  	Dialer       ContextDialer               // Outgoing dialer.
    71  	Local        string                      // Local host name.
    72  	Hosts        []string                    // All hosts, including local in the grid.
    73  	AddAuth      AuthFn                      // Add authentication to the given audience.
    74  	AuthRequest  func(r *http.Request) error // Validate incoming requests.
    75  	TLSConfig    *tls.Config                 // TLS to apply to the connections.
    76  	Incoming     func(n int64)               // Record incoming bytes.
    77  	Outgoing     func(n int64)               // Record outgoing bytes.
    78  	BlockConnect chan struct{}               // If set, incoming and outgoing connections will be blocked until closed.
    79  	TraceTo      *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]
    80  }
    81  
    82  // NewManager creates a new grid manager
    83  func NewManager(ctx context.Context, o ManagerOptions) (*Manager, error) {
    84  	found := false
    85  	if o.AuthRequest == nil {
    86  		return nil, fmt.Errorf("grid: AuthRequest must be set")
    87  	}
    88  	m := &Manager{
    89  		ID:          uuid.New(),
    90  		targets:     make(map[string]*Connection, len(o.Hosts)),
    91  		local:       o.Local,
    92  		authRequest: o.AuthRequest,
    93  	}
    94  	m.handlers.init()
    95  	if ctx == nil {
    96  		ctx = context.Background()
    97  	}
    98  	for _, host := range o.Hosts {
    99  		if host == o.Local {
   100  			if found {
   101  				return nil, fmt.Errorf("grid: local host found multiple times")
   102  			}
   103  			found = true
   104  			// No connection to local.
   105  			continue
   106  		}
   107  		m.targets[host] = newConnection(connectionParams{
   108  			ctx:           ctx,
   109  			id:            m.ID,
   110  			local:         o.Local,
   111  			remote:        host,
   112  			dial:          o.Dialer,
   113  			handlers:      &m.handlers,
   114  			auth:          o.AddAuth,
   115  			blockConnect:  o.BlockConnect,
   116  			tlsConfig:     o.TLSConfig,
   117  			publisher:     o.TraceTo,
   118  			incomingBytes: o.Incoming,
   119  			outgoingBytes: o.Outgoing,
   120  		})
   121  	}
   122  	if !found {
   123  		return nil, fmt.Errorf("grid: local host not found")
   124  	}
   125  
   126  	return m, nil
   127  }
   128  
   129  // AddToMux will add the grid manager to the given mux.
   130  func (m *Manager) AddToMux(router *mux.Router) {
   131  	router.Handle(RoutePath, m.Handler())
   132  }
   133  
   134  // Handler returns a handler that can be used to serve grid requests.
   135  // This should be connected on RoutePath to the main server.
   136  func (m *Manager) Handler() http.HandlerFunc {
   137  	return func(w http.ResponseWriter, req *http.Request) {
   138  		defer func() {
   139  			if debugPrint {
   140  				fmt.Printf("grid: Handler returning from: %v %v\n", req.Method, req.URL)
   141  			}
   142  			if r := recover(); r != nil {
   143  				debug.PrintStack()
   144  				err := fmt.Errorf("grid: panic: %v\n", r)
   145  				logger.LogIf(context.Background(), err, err.Error())
   146  				w.WriteHeader(http.StatusInternalServerError)
   147  			}
   148  		}()
   149  		if debugPrint {
   150  			fmt.Printf("grid: Got a %s request for: %v\n", req.Method, req.URL)
   151  		}
   152  		ctx := req.Context()
   153  		if err := m.authRequest(req); err != nil {
   154  			logger.LogOnceIf(ctx, fmt.Errorf("auth %s: %w", req.RemoteAddr, err), req.RemoteAddr+err.Error())
   155  			w.WriteHeader(http.StatusForbidden)
   156  			return
   157  		}
   158  		conn, _, _, err := ws.UpgradeHTTP(req, w)
   159  		if err != nil {
   160  			if debugPrint {
   161  				fmt.Printf("grid: Unable to upgrade: %v. http.ResponseWriter is type %T\n", err, w)
   162  			}
   163  			w.WriteHeader(http.StatusUpgradeRequired)
   164  			return
   165  		}
   166  		// will write an OpConnectResponse message to the remote and log it once locally.
   167  		writeErr := func(err error) {
   168  			if err == nil {
   169  				return
   170  			}
   171  			logger.LogOnceIf(ctx, err, err.Error())
   172  			resp := connectResp{
   173  				ID:             m.ID,
   174  				Accepted:       false,
   175  				RejectedReason: err.Error(),
   176  			}
   177  			if b, err := resp.MarshalMsg(nil); err == nil {
   178  				msg := message{
   179  					Op:      OpConnectResponse,
   180  					Payload: b,
   181  				}
   182  				if b, err := msg.MarshalMsg(nil); err == nil {
   183  					wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpBinary, b)
   184  				}
   185  			}
   186  		}
   187  		defer conn.Close()
   188  		if debugPrint {
   189  			fmt.Printf("grid: Upgraded request: %v\n", req.URL)
   190  		}
   191  
   192  		msg, _, err := wsutil.ReadClientData(conn)
   193  		if err != nil {
   194  			writeErr(fmt.Errorf("reading connect: %w", err))
   195  			w.WriteHeader(http.StatusForbidden)
   196  			return
   197  		}
   198  		if debugPrint {
   199  			fmt.Printf("%s handler: Got message, length %v\n", m.local, len(msg))
   200  		}
   201  
   202  		var message message
   203  		_, _, err = message.parse(msg)
   204  		if err != nil {
   205  			writeErr(fmt.Errorf("error parsing grid connect: %w", err))
   206  			return
   207  		}
   208  		if message.Op != OpConnect {
   209  			writeErr(fmt.Errorf("unexpected connect op: %v", message.Op))
   210  			return
   211  		}
   212  		var cReq connectReq
   213  		_, err = cReq.UnmarshalMsg(message.Payload)
   214  		if err != nil {
   215  			writeErr(fmt.Errorf("error parsing connectReq: %w", err))
   216  			return
   217  		}
   218  		remote := m.targets[cReq.Host]
   219  		if remote == nil {
   220  			writeErr(fmt.Errorf("unknown incoming host: %v", cReq.Host))
   221  			return
   222  		}
   223  		if debugPrint {
   224  			fmt.Printf("handler: Got Connect Req %+v\n", cReq)
   225  		}
   226  		writeErr(remote.handleIncoming(ctx, conn, cReq))
   227  	}
   228  }
   229  
   230  // AuthFn should provide an authentication string for the given aud.
   231  type AuthFn func(aud string) string
   232  
   233  // Connection will return the connection for the specified host.
   234  // If the host does not exist nil will be returned.
   235  func (m *Manager) Connection(host string) *Connection {
   236  	return m.targets[host]
   237  }
   238  
   239  // RegisterSingleHandler will register a stateless handler that serves
   240  // []byte -> ([]byte, error) requests.
   241  // subroutes are joined with "/" to a single subroute.
   242  func (m *Manager) RegisterSingleHandler(id HandlerID, h SingleHandlerFn, subroute ...string) error {
   243  	if !id.valid() {
   244  		return ErrUnknownHandler
   245  	}
   246  	s := strings.Join(subroute, "/")
   247  	if debugPrint {
   248  		fmt.Println("RegisterSingleHandler: ", id.String(), "subroute:", s)
   249  	}
   250  
   251  	if len(subroute) == 0 {
   252  		if m.handlers.hasAny(id) && !id.isTestHandler() {
   253  			return fmt.Errorf("handler %v: %w", id.String(), ErrHandlerAlreadyExists)
   254  		}
   255  
   256  		m.handlers.single[id] = h
   257  		return nil
   258  	}
   259  	subID := makeSubHandlerID(id, s)
   260  	if m.handlers.hasSubhandler(subID) && !id.isTestHandler() {
   261  		return fmt.Errorf("handler %v, subroute:%v: %w", id.String(), s, ErrHandlerAlreadyExists)
   262  	}
   263  	m.handlers.subSingle[subID] = h
   264  	// Copy so clients can also pick it up for other subpaths.
   265  	m.handlers.subSingle[makeZeroSubHandlerID(id)] = h
   266  	return nil
   267  }
   268  
   269  /*
   270  // RegisterStateless will register a stateless handler that serves
   271  // []byte -> stream of ([]byte, error) requests.
   272  func (m *Manager) RegisterStateless(id HandlerID, h StatelessHandler) error {
   273  	if !id.valid() {
   274  		return ErrUnknownHandler
   275  	}
   276  	if m.handlers.hasAny(id) && !id.isTestHandler() {
   277  		return ErrHandlerAlreadyExists
   278  	}
   279  
   280  	m.handlers.stateless[id] = &h
   281  	return nil
   282  }
   283  */
   284  
   285  // RegisterStreamingHandler will register a stateless handler that serves
   286  // two-way streaming requests.
   287  func (m *Manager) RegisterStreamingHandler(id HandlerID, h StreamHandler) error {
   288  	if !id.valid() {
   289  		return ErrUnknownHandler
   290  	}
   291  	if debugPrint {
   292  		fmt.Println("RegisterStreamingHandler: subroute:", h.Subroute)
   293  	}
   294  	if h.Subroute == "" {
   295  		if m.handlers.hasAny(id) && !id.isTestHandler() {
   296  			return ErrHandlerAlreadyExists
   297  		}
   298  		m.handlers.streams[id] = &h
   299  		return nil
   300  	}
   301  	subID := makeSubHandlerID(id, h.Subroute)
   302  	if m.handlers.hasSubhandler(subID) && !id.isTestHandler() {
   303  		return ErrHandlerAlreadyExists
   304  	}
   305  	m.handlers.subStreams[subID] = &h
   306  	// Copy so clients can also pick it up for other subpaths.
   307  	m.handlers.subStreams[makeZeroSubHandlerID(id)] = &h
   308  	return nil
   309  }
   310  
   311  // HostName returns the name of the local host.
   312  func (m *Manager) HostName() string {
   313  	return m.local
   314  }
   315  
   316  // Targets returns the names of all remote targets.
   317  func (m *Manager) Targets() []string {
   318  	var res []string
   319  	for k := range m.targets {
   320  		res = append(res, k)
   321  	}
   322  	return res
   323  }
   324  
   325  // debugMsg should *only* be used by tests.
   326  //
   327  //lint:ignore U1000 This is used by tests.
   328  func (m *Manager) debugMsg(d debugMsg, args ...any) {
   329  	for _, c := range m.targets {
   330  		c.debugMsg(d, args...)
   331  	}
   332  }