github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/grpc/source_test.go (about)

     1  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o ../../../fakes/fake_registry_store.go ../../../../vendor/github.com/operator-framework/operator-registry/pkg/registry/interface.go Query
     2  package grpc
     3  
     4  import (
     5  	"context"
     6  	"net"
     7  	"net/url"
     8  	"os"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/sirupsen/logrus"
    14  	"github.com/stretchr/testify/require"
    15  	"google.golang.org/grpc"
    16  	"google.golang.org/grpc/connectivity"
    17  
    18  	"github.com/operator-framework/operator-lifecycle-manager/pkg/fakes"
    19  	"github.com/operator-framework/operator-registry/pkg/api"
    20  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    21  	opserver "github.com/operator-framework/operator-registry/pkg/server"
    22  
    23  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
    24  )
    25  
    26  func server(store opregistry.Query) (func(), string, func()) {
    27  	lis, err := net.Listen("tcp", "localhost:")
    28  	if err != nil {
    29  		logrus.Fatalf("failed to listen: %v", err)
    30  	}
    31  	s := grpc.NewServer()
    32  
    33  	api.RegisterRegistryServer(s, opserver.NewRegistryServer(store))
    34  
    35  	serve := func() {
    36  		if err := s.Serve(lis); err != nil {
    37  			logrus.Fatalf("failed to serve: %v", err)
    38  		}
    39  	}
    40  
    41  	stop := func() {
    42  		s.Stop()
    43  	}
    44  
    45  	return serve, lis.Addr().String(), stop
    46  }
    47  
    48  type FakeSourceSyncer struct {
    49  	// using a map[int] to preserve order
    50  	History map[registry.CatalogKey][]connectivity.State
    51  
    52  	sync.Mutex
    53  	expectedReadies int
    54  	done            chan struct{}
    55  }
    56  
    57  func (f *FakeSourceSyncer) sync(state SourceState) {
    58  	f.Lock()
    59  	if f.History[state.Key] == nil {
    60  		f.History[state.Key] = []connectivity.State{}
    61  	}
    62  	f.History[state.Key] = append(f.History[state.Key], state.State)
    63  	if state.State == connectivity.Ready {
    64  		f.expectedReadies--
    65  	}
    66  	if f.expectedReadies == 0 {
    67  		f.done <- struct{}{}
    68  	}
    69  	f.Unlock()
    70  }
    71  
    72  func NewFakeSourceSyncer(expectedReadies int) *FakeSourceSyncer {
    73  	return &FakeSourceSyncer{
    74  		History:         map[registry.CatalogKey][]connectivity.State{},
    75  		expectedReadies: expectedReadies,
    76  		done:            make(chan struct{}),
    77  	}
    78  }
    79  
    80  func TestConnectionEvents(t *testing.T) {
    81  	type testcase struct {
    82  		name            string
    83  		expectedHistory map[registry.CatalogKey][]connectivity.State
    84  	}
    85  
    86  	test := func(tt testcase) func(t *testing.T) {
    87  		return func(t *testing.T) {
    88  			// start server for each catalog
    89  			addresses := map[registry.CatalogKey]string{}
    90  
    91  			for catalog := range tt.expectedHistory {
    92  				serve, address, stop := server(&fakes.FakeQuery{})
    93  				addresses[catalog] = address
    94  				go serve()
    95  				defer stop()
    96  			}
    97  
    98  			// start source manager
    99  			syncer := NewFakeSourceSyncer(len(tt.expectedHistory))
   100  			sources := NewSourceStore(logrus.New(), 1*time.Second, 5*time.Second, syncer.sync)
   101  			ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
   102  			defer cancel()
   103  			sources.Start(ctx)
   104  
   105  			// add source for each catalog
   106  			for catalog, address := range addresses {
   107  				_, err := sources.Add(catalog, address)
   108  				require.NoError(t, err)
   109  			}
   110  
   111  			// wait for syncing to finish
   112  			<-syncer.done
   113  
   114  			// verify sync events
   115  			for catalog, events := range tt.expectedHistory {
   116  				recordedEvents := syncer.History[catalog]
   117  				for i := 0; i < len(recordedEvents); i++ {
   118  					found := false
   119  					for _, event := range events {
   120  						if event.String() == recordedEvents[i].String() {
   121  							found = true
   122  						}
   123  					}
   124  					require.True(t, found)
   125  				}
   126  			}
   127  		}
   128  	}
   129  
   130  	cases := []testcase{
   131  		{
   132  			name: "Basic",
   133  			expectedHistory: map[registry.CatalogKey][]connectivity.State{
   134  				{Name: "test", Namespace: "test"}: {
   135  					connectivity.Connecting,
   136  					connectivity.Ready,
   137  				},
   138  			},
   139  		},
   140  		{
   141  			name: "Multiple",
   142  			expectedHistory: map[registry.CatalogKey][]connectivity.State{
   143  				{Name: "test", Namespace: "test"}: {
   144  					connectivity.Connecting,
   145  					connectivity.Ready,
   146  				},
   147  				{Name: "test2", Namespace: "test2"}: {
   148  					connectivity.Connecting,
   149  					connectivity.Ready,
   150  				},
   151  			},
   152  		},
   153  	}
   154  
   155  	for _, tt := range cases {
   156  		t.Run(tt.name, test(tt))
   157  	}
   158  }
   159  
   160  func TestGetEnvAny(t *testing.T) {
   161  	type envVar struct {
   162  		key   string
   163  		value string
   164  	}
   165  
   166  	type testcase struct {
   167  		name          string
   168  		envVars       []envVar
   169  		expectedValue string
   170  	}
   171  
   172  	test := func(tt testcase) func(t *testing.T) {
   173  		return func(t *testing.T) {
   174  			for _, envVar := range tt.envVars {
   175  				os.Setenv(envVar.key, envVar.value)
   176  			}
   177  
   178  			defer func() {
   179  				for _, envVar := range tt.envVars {
   180  					os.Setenv(envVar.key, "")
   181  				}
   182  			}()
   183  
   184  			require.Equal(t, getEnvAny("NO_PROXY", "no_proxy"), tt.expectedValue)
   185  		}
   186  	}
   187  
   188  	cases := []testcase{
   189  		{
   190  			name:          "NotFound",
   191  			expectedValue: "",
   192  		},
   193  		{
   194  			name: "LowerCaseFound",
   195  			envVars: []envVar{
   196  				{
   197  					key:   "no_proxy",
   198  					value: "foo",
   199  				},
   200  			},
   201  			expectedValue: "foo",
   202  		},
   203  		{
   204  			name: "UpperCaseFound",
   205  			envVars: []envVar{
   206  				{
   207  					key:   "NO_PROXY",
   208  					value: "bar",
   209  				},
   210  			},
   211  			expectedValue: "bar",
   212  		},
   213  		{
   214  			name: "OrderPreference",
   215  			envVars: []envVar{
   216  				{
   217  					key:   "no_proxy",
   218  					value: "foo",
   219  				},
   220  				{
   221  					key:   "NO_PROXY",
   222  					value: "bar",
   223  				},
   224  			},
   225  			expectedValue: "bar",
   226  		},
   227  	}
   228  
   229  	for _, tt := range cases {
   230  		t.Run(tt.name, test(tt))
   231  	}
   232  }
   233  
   234  func TestGetGRPCProxyEnv(t *testing.T) {
   235  	type envVar struct {
   236  		key   string
   237  		value string
   238  	}
   239  
   240  	type testcase struct {
   241  		name          string
   242  		envVars       []envVar
   243  		expectedValue string
   244  	}
   245  
   246  	test := func(tt testcase) func(t *testing.T) {
   247  		return func(t *testing.T) {
   248  			for _, envVar := range tt.envVars {
   249  				os.Setenv(envVar.key, envVar.value)
   250  			}
   251  
   252  			defer func() {
   253  				for _, envVar := range tt.envVars {
   254  					os.Setenv(envVar.key, "")
   255  				}
   256  			}()
   257  
   258  			require.Equal(t, getGRPCProxyEnv(), tt.expectedValue)
   259  		}
   260  	}
   261  
   262  	cases := []testcase{
   263  		{
   264  			name:          "NotFound",
   265  			expectedValue: "",
   266  		},
   267  		{
   268  			name: "LowerCaseFound",
   269  			envVars: []envVar{
   270  				{
   271  					key:   "grpc_proxy",
   272  					value: "foo",
   273  				},
   274  			},
   275  			expectedValue: "foo",
   276  		},
   277  		{
   278  			name: "UpperCaseFound",
   279  			envVars: []envVar{
   280  				{
   281  					key:   "GRPC_PROXY",
   282  					value: "bar",
   283  				},
   284  			},
   285  			expectedValue: "bar",
   286  		},
   287  		{
   288  			name: "UpperCasePreference",
   289  			envVars: []envVar{
   290  				{
   291  					key:   "grpc_proxy",
   292  					value: "foo",
   293  				},
   294  				{
   295  					key:   "GRPC_PROXY",
   296  					value: "bar",
   297  				},
   298  			},
   299  			expectedValue: "bar",
   300  		},
   301  	}
   302  
   303  	for _, tt := range cases {
   304  		t.Run(tt.name, test(tt))
   305  	}
   306  }
   307  
   308  func TestGRPCProxyURL(t *testing.T) {
   309  	type envVar struct {
   310  		key   string
   311  		value string
   312  	}
   313  
   314  	type testcase struct {
   315  		name          string
   316  		address       string
   317  		envVars       []envVar
   318  		expectedProxy string
   319  		expectedError error
   320  	}
   321  
   322  	test := func(tt testcase) func(t *testing.T) {
   323  		return func(t *testing.T) {
   324  			for _, envVar := range tt.envVars {
   325  				os.Setenv(envVar.key, envVar.value)
   326  			}
   327  
   328  			defer func() {
   329  				for _, envVar := range tt.envVars {
   330  					os.Setenv(envVar.key, "")
   331  				}
   332  			}()
   333  
   334  			var expectedProxyURL *url.URL
   335  			var err error
   336  			if tt.expectedProxy != "" {
   337  				expectedProxyURL, err = url.Parse(tt.expectedProxy)
   338  				require.NoError(t, err)
   339  			}
   340  
   341  			proxyURL, err := grpcProxyURL(tt.address)
   342  			require.Equal(t, expectedProxyURL, proxyURL)
   343  			require.Equal(t, tt.expectedError, err)
   344  		}
   345  	}
   346  
   347  	cases := []testcase{
   348  		{
   349  			name:          "NoGRPCProxySet",
   350  			address:       "foo.com:8080",
   351  			expectedProxy: "",
   352  			expectedError: nil,
   353  		},
   354  		{
   355  			name:    "GRPCProxyFoundForAddress",
   356  			address: "foo.com:8080",
   357  			envVars: []envVar{
   358  				{
   359  					key:   "GRPC_PROXY",
   360  					value: "http://my-proxy:8080",
   361  				},
   362  			},
   363  			expectedProxy: "http://my-proxy:8080",
   364  			expectedError: nil,
   365  		},
   366  		{
   367  			name:    "GRPCNoProxyIncludesAddress",
   368  			address: "foo.com:8080",
   369  			envVars: []envVar{
   370  				{
   371  					key:   "GRPC_PROXY",
   372  					value: "http://my-proxy:8080",
   373  				},
   374  				{
   375  					key:   "NO_PROXY",
   376  					value: "foo.com:8080",
   377  				},
   378  			},
   379  			expectedProxy: "",
   380  			expectedError: nil,
   381  		},
   382  		{
   383  			name:          "MissingPort",
   384  			address:       "foo.com",
   385  			expectedProxy: "",
   386  			expectedError: error(&net.AddrError{Err: "missing port in address", Addr: "foo.com"}),
   387  		},
   388  		{
   389  			name:          "TooManyColons",
   390  			address:       "http://bar.com:8080",
   391  			expectedProxy: "",
   392  			expectedError: error(&net.AddrError{Err: "too many colons in address", Addr: "http://bar.com:8080"}),
   393  		},
   394  	}
   395  
   396  	for _, tt := range cases {
   397  		t.Run(tt.name, test(tt))
   398  	}
   399  }