github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/apiproxy/access_api_proxy_test.go (about) 1 package apiproxy 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "testing" 8 "time" 9 10 "github.com/onflow/flow/protobuf/go/flow/access" 11 "github.com/stretchr/testify/assert" 12 "google.golang.org/grpc" 13 grpcinsecure "google.golang.org/grpc/credentials/insecure" 14 15 "github.com/onflow/flow-go/engine/access/rpc/connection" 16 "github.com/onflow/flow-go/engine/common/grpc/forwarder" 17 "github.com/onflow/flow-go/model/flow" 18 "github.com/onflow/flow-go/module/metrics" 19 "github.com/onflow/flow-go/utils/grpcutils" 20 "github.com/onflow/flow-go/utils/unittest" 21 ) 22 23 // Methodology 24 // 25 // We test the proxy and fall-over logic to reach basic coverage. 26 // 27 // * Basic coverage means that all conditional checks happen once but only once. 28 // * We embrace the simplest adequate solution to reduce engineering cost. 29 // * Any use cases requiring multiple conditionals exercised in a row are considered ignorable due to cost constraints. 30 31 // TestNetE2E tests the basic unix network first 32 func TestNetE2E(t *testing.T) { 33 done := make(chan int) 34 // Bring up 1st upstream server 35 charlie1, err := makeFlowLite("/tmp/TestProxyE2E1", done) 36 if err != nil { 37 t.Fatal(err) 38 } 39 // Wait until proxy call passes 40 err = callFlowLite("/tmp/TestProxyE2E1") 41 if err != nil { 42 t.Fatal(err) 43 } 44 // Bring up 2nd upstream server 45 charlie2, err := makeFlowLite("/tmp/TestProxyE2E2", done) 46 if err != nil { 47 t.Fatal(err) 48 } 49 // Both proxy calls should pass 50 err = callFlowLite("/tmp/TestProxyE2E1") 51 if err != nil { 52 t.Fatal(err) 53 } 54 err = callFlowLite("/tmp/TestProxyE2E2") 55 if err != nil { 56 t.Fatal(err) 57 } 58 // Stop 1st upstream server 59 _ = charlie1.Close() 60 // Proxy call falls through 61 err = callFlowLite("/tmp/TestProxyE2E1") 62 if err == nil { 63 t.Fatal(fmt.Errorf("backend still active after close")) 64 } 65 // Stop 2nd upstream server 66 _ = charlie2.Close() 67 // System errors out on shut down servers 68 err = callFlowLite("/tmp/TestProxyE2E1") 69 if err == nil { 70 t.Fatal(fmt.Errorf("backend still active after close")) 71 } 72 err = callFlowLite("/tmp/TestProxyE2E2") 73 if err == nil { 74 t.Fatal(fmt.Errorf("backend still active after close")) 75 } 76 // wait for all 77 <-done 78 <-done 79 } 80 81 // TestgRPCE2E tests whether gRPC works 82 func TestGRPCE2E(t *testing.T) { 83 done := make(chan int) 84 // Bring up 1st upstream server 85 charlie1, _, err := newFlowLite("unix", "/tmp/TestProxyE2E1", done) 86 if err != nil { 87 t.Fatal(err) 88 } 89 // Wait until proxy call passes 90 err = openFlowLite("/tmp/TestProxyE2E1") 91 if err != nil { 92 t.Fatal(err) 93 } 94 // Bring up 2nd upstream server 95 charlie2, _, err := newFlowLite("unix", "/tmp/TestProxyE2E2", done) 96 if err != nil { 97 t.Fatal(err) 98 } 99 // Both proxy calls should pass 100 err = openFlowLite("/tmp/TestProxyE2E1") 101 if err != nil { 102 t.Fatal(err) 103 } 104 err = openFlowLite("/tmp/TestProxyE2E2") 105 if err != nil { 106 t.Fatal(err) 107 } 108 // Stop 1st upstream server 109 charlie1.Stop() 110 // Proxy call falls through 111 err = openFlowLite("/tmp/TestProxyE2E1") 112 if err == nil { 113 t.Fatal(fmt.Errorf("backend still active after close")) 114 } 115 // Stop 2nd upstream server 116 charlie2.Stop() 117 // System errors out on shut down servers 118 err = openFlowLite("/tmp/TestProxyE2E1") 119 if err == nil { 120 t.Fatal(fmt.Errorf("backend still active after close")) 121 } 122 err = openFlowLite("/tmp/TestProxyE2E2") 123 if err == nil { 124 t.Fatal(fmt.Errorf("backend still active after close")) 125 } 126 // wait for all 127 <-done 128 <-done 129 } 130 131 // TestNewFlowCachedAccessAPIProxy tests the round robin end to end 132 func TestNewFlowCachedAccessAPIProxy(t *testing.T) { 133 done := make(chan int) 134 135 // Bring up 1st upstream server 136 charlie1, _, err := newFlowLite("tcp", unittest.IPPort("11634"), done) 137 if err != nil { 138 t.Fatal(err) 139 } 140 141 // Bring up 2nd upstream server 142 charlie2, _, err := newFlowLite("tcp", unittest.IPPort("11635"), done) 143 if err != nil { 144 t.Fatal(err) 145 } 146 147 metrics := metrics.NewNoopCollector() 148 149 // create the factory 150 connectionFactory := &connection.ConnectionFactoryImpl{ 151 // set metrics reporting 152 AccessMetrics: metrics, 153 CollectionNodeGRPCTimeout: time.Second, 154 Manager: connection.NewManager( 155 unittest.Logger(), 156 metrics, 157 nil, 158 grpcutils.DefaultMaxMsgSize, 159 connection.CircuitBreakerConfig{}, 160 grpcutils.NoCompressor, 161 ), 162 } 163 164 // Prepare a proxy that fails due to the second connection being idle 165 l := flow.IdentitySkeletonList{{Address: unittest.IPPort("11634")}, {Address: unittest.IPPort("11635")}} 166 c := FlowAccessAPIForwarder{} 167 c.Forwarder, err = forwarder.NewForwarder(l, connectionFactory) 168 if err != nil { 169 t.Fatal(err) 170 } 171 172 ctx := context.Background() 173 174 // Wait until proxy call passes 175 _, err = c.Ping(ctx, &access.PingRequest{}) 176 if err != nil { 177 t.Fatal(err) 178 } 179 180 // get and close first connection 181 _, closer, err := c.Forwarder.FaultTolerantClient() 182 assert.NoError(t, err) 183 closer.Close() 184 185 // connection factory created a new gRPC connection which was closed before 186 // if creation fails should use second connection 187 // Wait until proxy call passes 188 _, err = c.Ping(ctx, &access.PingRequest{}) 189 if err != nil { 190 t.Fatal(err) 191 } 192 193 // Wait until proxy call passes 194 _, err = c.Ping(ctx, &access.PingRequest{}) 195 if err != nil { 196 t.Fatal(err) 197 } 198 199 charlie1.Stop() 200 charlie2.Stop() 201 202 // Wait until proxy call fails 203 _, err = c.Ping(ctx, &access.PingRequest{}) 204 if err == nil { 205 t.Fatal(fmt.Errorf("should fail on no connections")) 206 } 207 208 <-done 209 <-done 210 } 211 212 func makeFlowLite(address string, done chan int) (net.Listener, error) { 213 l, err := net.Listen("unix", address) 214 if err != nil { 215 return nil, err 216 } 217 218 go func(done chan int) { 219 for { 220 c, err := l.Accept() 221 if err != nil { 222 break 223 } 224 225 b := make([]byte, 3) 226 _, _ = c.Read(b) 227 _, _ = c.Write(b) 228 _ = c.Close() 229 } 230 done <- 1 231 }(done) 232 return l, err 233 } 234 235 func callFlowLite(address string) error { 236 c, err := net.Dial("unix", address) 237 if err != nil { 238 return err 239 } 240 o := []byte("abc") 241 _, _ = c.Write(o) 242 i := make([]byte, 3) 243 _, _ = c.Read(i) 244 if string(o) != string(i) { 245 return fmt.Errorf("no match") 246 } 247 _ = c.Close() 248 _ = MockFlowAccessAPI{} 249 return err 250 } 251 252 func newFlowLite(network string, address string, done chan int) (*grpc.Server, *net.Listener, error) { 253 l, err := net.Listen(network, address) 254 if err != nil { 255 return nil, nil, err 256 } 257 s := grpc.NewServer() 258 go func(done chan int) { 259 access.RegisterAccessAPIServer(s, MockFlowAccessAPI{}) 260 _ = s.Serve(l) 261 done <- 1 262 }(done) 263 return s, &l, nil 264 } 265 266 func openFlowLite(address string) error { 267 c, err := grpc.Dial( 268 "unix://"+address, 269 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutils.DefaultMaxMsgSize)), 270 grpc.WithTransportCredentials(grpcinsecure.NewCredentials())) 271 if err != nil { 272 return err 273 } 274 a := access.NewAccessAPIClient(c) 275 276 background := context.Background() 277 278 _, err = a.Ping(background, &access.PingRequest{}) 279 if err != nil { 280 return err 281 } 282 283 return nil 284 } 285 286 type MockFlowAccessAPI struct { 287 access.AccessAPIServer 288 } 289 290 // Ping is used to check if the access node is alive and healthy. 291 func (p MockFlowAccessAPI) Ping(context.Context, *access.PingRequest) (*access.PingResponse, error) { 292 return &access.PingResponse{}, nil 293 }