github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/grid/connection_test.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 "net" 23 "net/http" 24 "net/http/httptest" 25 "testing" 26 "time" 27 28 "github.com/minio/minio/internal/logger/target/testlogger" 29 ) 30 31 func TestDisconnect(t *testing.T) { 32 defer testlogger.T.SetLogTB(t)() 33 defer timeout(10 * time.Second)() 34 hosts, listeners, _ := getHosts(2) 35 dialer := &net.Dialer{ 36 Timeout: 1 * time.Second, 37 } 38 errFatal := func(err error) { 39 t.Helper() 40 if err != nil { 41 t.Fatal(err) 42 } 43 } 44 wrapServer := func(handler http.Handler) http.Handler { 45 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 46 t.Logf("Got a %s request for: %v", r.Method, r.URL) 47 handler.ServeHTTP(w, r) 48 }) 49 } 50 connReady := make(chan struct{}) 51 // We fake a local and remote server. 52 localHost := hosts[0] 53 remoteHost := hosts[1] 54 local, err := NewManager(context.Background(), ManagerOptions{ 55 Dialer: dialer.DialContext, 56 Local: localHost, 57 Hosts: hosts, 58 AddAuth: func(aud string) string { return aud }, 59 AuthRequest: dummyRequestValidate, 60 BlockConnect: connReady, 61 }) 62 errFatal(err) 63 64 // 1: Echo 65 errFatal(local.RegisterSingleHandler(handlerTest, func(payload []byte) ([]byte, *RemoteErr) { 66 t.Log("1: server payload: ", len(payload), "bytes.") 67 return append([]byte{}, payload...), nil 68 })) 69 // 2: Return as error 70 errFatal(local.RegisterSingleHandler(handlerTest2, func(payload []byte) ([]byte, *RemoteErr) { 71 t.Log("2: server payload: ", len(payload), "bytes.") 72 err := RemoteErr(payload) 73 return nil, &err 74 })) 75 76 remote, err := NewManager(context.Background(), ManagerOptions{ 77 Dialer: dialer.DialContext, 78 Local: remoteHost, 79 Hosts: hosts, 80 AddAuth: func(aud string) string { return aud }, 81 AuthRequest: dummyRequestValidate, 82 BlockConnect: connReady, 83 }) 84 errFatal(err) 85 86 localServer := startServer(t, listeners[0], wrapServer(local.Handler())) 87 remoteServer := startServer(t, listeners[1], wrapServer(remote.Handler())) 88 close(connReady) 89 90 defer func() { 91 local.debugMsg(debugShutdown) 92 remote.debugMsg(debugShutdown) 93 remoteServer.Close() 94 localServer.Close() 95 remote.debugMsg(debugWaitForExit) 96 local.debugMsg(debugWaitForExit) 97 }() 98 99 cleanReqs := make(chan struct{}) 100 gotCall := make(chan struct{}) 101 defer close(cleanReqs) 102 // 1: Block forever 103 h1 := func(payload []byte) ([]byte, *RemoteErr) { 104 gotCall <- struct{}{} 105 <-cleanReqs 106 return nil, nil 107 } 108 // 2: Also block, but with streaming. 109 h2 := StreamHandler{ 110 Handle: func(ctx context.Context, payload []byte, request <-chan []byte, resp chan<- []byte) *RemoteErr { 111 gotCall <- struct{}{} 112 select { 113 case <-ctx.Done(): 114 gotCall <- struct{}{} 115 case <-cleanReqs: 116 panic("should not be called") 117 } 118 return nil 119 }, 120 OutCapacity: 1, 121 InCapacity: 1, 122 } 123 errFatal(remote.RegisterSingleHandler(handlerTest, h1)) 124 errFatal(remote.RegisterStreamingHandler(handlerTest2, h2)) 125 errFatal(local.RegisterSingleHandler(handlerTest, h1)) 126 errFatal(local.RegisterStreamingHandler(handlerTest2, h2)) 127 128 // local to remote 129 remoteConn := local.Connection(remoteHost) 130 errFatal(remoteConn.WaitForConnect(context.Background())) 131 const testPayload = "Hello Grid World!" 132 133 gotResp := make(chan struct{}) 134 go func() { 135 start := time.Now() 136 t.Log("Roundtrip: sending request") 137 resp, err := remoteConn.Request(context.Background(), handlerTest, []byte(testPayload)) 138 t.Log("Roundtrip:", time.Since(start), resp, err) 139 gotResp <- struct{}{} 140 }() 141 <-gotCall 142 remote.debugMsg(debugKillInbound) 143 local.debugMsg(debugKillInbound) 144 <-gotResp 145 146 // Must reconnect 147 errFatal(remoteConn.WaitForConnect(context.Background())) 148 149 stream, err := remoteConn.NewStream(context.Background(), handlerTest2, []byte(testPayload)) 150 errFatal(err) 151 go func() { 152 for resp := range stream.responses { 153 t.Log("Resp:", resp, err) 154 } 155 gotResp <- struct{}{} 156 }() 157 158 <-gotCall 159 remote.debugMsg(debugKillOutbound) 160 local.debugMsg(debugKillOutbound) 161 errFatal(remoteConn.WaitForConnect(context.Background())) 162 163 <-gotResp 164 // Killing should cancel the context on the request. 165 <-gotCall 166 } 167 168 func dummyRequestValidate(r *http.Request) error { 169 return nil 170 } 171 172 func TestShouldConnect(t *testing.T) { 173 var c Connection 174 var cReverse Connection 175 hosts := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} 176 for x := range hosts { 177 should := 0 178 for y := range hosts { 179 if x == y { 180 continue 181 } 182 c.Local = hosts[x] 183 c.Remote = hosts[y] 184 cReverse.Local = hosts[y] 185 cReverse.Remote = hosts[x] 186 if c.shouldConnect() == cReverse.shouldConnect() { 187 t.Errorf("shouldConnect(%q, %q) != shouldConnect(%q, %q)", hosts[x], hosts[y], hosts[y], hosts[x]) 188 } 189 if c.shouldConnect() { 190 should++ 191 } 192 } 193 if should < 10 { 194 t.Errorf("host %q only connects to %d hosts", hosts[x], should) 195 } 196 t.Logf("host %q should connect to %d hosts", hosts[x], should) 197 } 198 } 199 200 func startServer(t testing.TB, listener net.Listener, handler http.Handler) (server *httptest.Server) { 201 t.Helper() 202 server = httptest.NewUnstartedServer(handler) 203 server.Config.Addr = listener.Addr().String() 204 server.Listener = listener 205 server.Start() 206 // t.Cleanup(server.Close) 207 t.Log("Started server on", server.Config.Addr, "URL:", server.URL) 208 return server 209 }