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 }