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  }