github.com/google/cloudprober@v0.11.3/rds/client/client_test.go (about)

     1  // Copyright 2018-2019 The Cloudprober Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package client
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net"
    21  	"reflect"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/golang/protobuf/proto"
    26  	"github.com/google/cloudprober/logger"
    27  	configpb "github.com/google/cloudprober/rds/client/proto"
    28  	pb "github.com/google/cloudprober/rds/proto"
    29  	"github.com/google/cloudprober/rds/server"
    30  	serverpb "github.com/google/cloudprober/rds/server/proto"
    31  	"github.com/google/cloudprober/targets/endpoint"
    32  	dnsRes "github.com/google/cloudprober/targets/resolver"
    33  )
    34  
    35  type testProvider struct {
    36  	resources           []*pb.Resource
    37  	supportCacheControl bool
    38  	lastModified        int64
    39  	requestCache        []*pb.ListResourcesRequest
    40  	responseCache       []*pb.ListResourcesResponse
    41  }
    42  
    43  func (tp *testProvider) verifyRequestResponse(t *testing.T, runCount int, ifModifiedSince, lastModified int64) {
    44  	t.Helper()
    45  
    46  	if len(tp.requestCache) != runCount {
    47  		t.Errorf("Unexpected requests cache length: %d, want: %d", len(tp.requestCache), runCount)
    48  		return
    49  	}
    50  	if len(tp.responseCache) != runCount {
    51  		t.Errorf("Unexpected responses cache length: %d, want: %d", len(tp.responseCache), runCount)
    52  		return
    53  	}
    54  	if tp.requestCache[runCount-1].GetIfModifiedSince() != ifModifiedSince {
    55  		t.Errorf("Request's if_modified_since: %d, want: %d", tp.requestCache[runCount-1].GetIfModifiedSince(), ifModifiedSince)
    56  	}
    57  	if tp.responseCache[runCount-1].GetLastModified() != lastModified {
    58  		t.Errorf("Response's last_modified: %d, want: %d", tp.responseCache[runCount-1].GetLastModified(), lastModified)
    59  	}
    60  }
    61  
    62  var testProviderName = "test_provider1"
    63  
    64  var testResources = []*pb.Resource{
    65  	{
    66  		Name:   proto.String("testR21"),
    67  		Ip:     proto.String("10.0.2.1"),
    68  		Port:   proto.Int32(80),
    69  		Labels: map[string]string{"zone": "us-central1-b"},
    70  	},
    71  	{
    72  		Name:   proto.String("testR22"),
    73  		Ip:     proto.String("10.0.2.2"),
    74  		Port:   proto.Int32(8080),
    75  		Labels: map[string]string{"zone": "us-central1-a"},
    76  	},
    77  	{
    78  		Name:   proto.String("testR22v6"),
    79  		Ip:     proto.String("::1"),
    80  		Port:   proto.Int32(8080),
    81  		Labels: map[string]string{"zone": "us-central1-a"},
    82  	},
    83  	{
    84  		Name:   proto.String("testR3"),
    85  		Ip:     proto.String("testR3.test.com"),
    86  		Port:   proto.Int32(80),
    87  		Labels: map[string]string{"zone": "us-central1-c"},
    88  	},
    89  	// Duplicate resource, it should be removed from the result.
    90  	{
    91  		Name:   proto.String("testR3"),
    92  		Ip:     proto.String("testR3.test.com"),
    93  		Port:   proto.Int32(80),
    94  		Labels: map[string]string{"zone": "us-central1-c"},
    95  	},
    96  }
    97  
    98  var testResourcesMap = map[string][]*pb.Resource{
    99  	testProviderName: testResources,
   100  }
   101  
   102  // Expected resource list, note that we remove the duplicate resource from the
   103  // expected output.
   104  var expectedList = testResources[:len(testResources)-1]
   105  
   106  var expectedIPByVersion = map[string]map[int]string{
   107  	"testR21": map[int]string{
   108  		0: "10.0.2.1",
   109  		4: "10.0.2.1",
   110  		6: "err",
   111  	},
   112  	"testR22": map[int]string{
   113  		0: "10.0.2.2",
   114  		4: "10.0.2.2",
   115  		6: "err",
   116  	},
   117  	"testR22v6": map[int]string{
   118  		0: "::1",
   119  		4: "err",
   120  		6: "::1",
   121  	},
   122  	"testR3": map[int]string{
   123  		0: "10.1.1.2",
   124  		4: "10.1.1.2",
   125  		6: "::2",
   126  	},
   127  }
   128  
   129  var testNameToIP = map[string][]net.IP{
   130  	"testR3.test.com": []net.IP{net.ParseIP("10.1.1.2"), net.ParseIP("::2")},
   131  }
   132  
   133  func (tp *testProvider) ListResources(req *pb.ListResourcesRequest) (*pb.ListResourcesResponse, error) {
   134  	tp.requestCache = append(tp.requestCache, req)
   135  	var resp *pb.ListResourcesResponse
   136  	defer func() {
   137  		tp.responseCache = append(tp.responseCache, resp)
   138  	}()
   139  
   140  	// If provider doesn't support cache control, just return all resources.
   141  	if !tp.supportCacheControl {
   142  		resp = &pb.ListResourcesResponse{
   143  			Resources: tp.resources,
   144  		}
   145  		return resp, nil
   146  	}
   147  
   148  	// If we support cache-control, we should at least add last-modified to the
   149  	// response.
   150  	resp = &pb.ListResourcesResponse{
   151  		LastModified: proto.Int64(tp.lastModified),
   152  	}
   153  
   154  	// If request contains if_modified_since, use that field to decide if we
   155  	// should add resources to the response.
   156  	if req.GetIfModifiedSince() != 0 {
   157  		if tp.lastModified <= req.GetIfModifiedSince() {
   158  			return resp, nil
   159  		}
   160  	}
   161  	resp.Resources = tp.resources
   162  
   163  	return resp, nil
   164  }
   165  
   166  func setupTestServer(ctx context.Context, t *testing.T, testResourcesMap map[string][]*pb.Resource) *server.Server {
   167  	t.Helper()
   168  
   169  	testProviders := make(map[string]server.Provider)
   170  	for tp, testResources := range testResourcesMap {
   171  		testProviders[tp] = &testProvider{
   172  			resources: testResources,
   173  		}
   174  	}
   175  
   176  	srv, err := server.New(context.Background(), &serverpb.ServerConf{}, testProviders, &logger.Logger{})
   177  	if err != nil {
   178  		t.Fatalf("Got error creating RDS server: %v", err)
   179  	}
   180  
   181  	return srv
   182  }
   183  
   184  func verifyEndpoints(t *testing.T, epList []endpoint.Endpoint, expectedList []*pb.Resource) {
   185  	t.Helper()
   186  
   187  	if len(epList) != len(expectedList) {
   188  		t.Fatalf("Got endpoints:\n%v\nExpected:\n%v", epList, expectedList)
   189  		return
   190  	}
   191  
   192  	for i, res := range expectedList {
   193  		if epList[i].Name != res.GetName() {
   194  			t.Errorf("Resource name: got=%s, want=%s", epList[i].Name, res.GetName())
   195  		}
   196  
   197  		if epList[i].Port != int(res.GetPort()) {
   198  			t.Errorf("Resource port: got=%d, want=%d", epList[i].Port, res.GetPort())
   199  		}
   200  
   201  		if !reflect.DeepEqual(epList[i].Labels, res.GetLabels()) {
   202  			t.Errorf("Resource labels: got=%v, want=%v", epList[i].Labels, res.GetLabels())
   203  		}
   204  	}
   205  }
   206  
   207  func TestListAndResolve(t *testing.T) {
   208  	ctx, cancelFunc := context.WithCancel(context.Background())
   209  	defer cancelFunc()
   210  	srv := setupTestServer(ctx, t, map[string][]*pb.Resource{
   211  		testProviderName: testResources,
   212  	})
   213  
   214  	c := &configpb.ClientConf{
   215  		Request: &pb.ListResourcesRequest{
   216  			Provider: proto.String(testProviderName),
   217  		},
   218  	}
   219  	client, err := New(c, srv.ListResources, &logger.Logger{})
   220  	if err != nil {
   221  		t.Fatalf("Got error initializing RDS client: %v", err)
   222  	}
   223  	client.resolver = dnsRes.NewWithResolve(func(name string) ([]net.IP, error) {
   224  		return testNameToIP[name], nil
   225  	})
   226  
   227  	client.refreshState(time.Second)
   228  
   229  	// Test ListEndpoint(), note that we remove the duplicate resource from the
   230  	// exepected output.
   231  	verifyEndpoints(t, client.ListEndpoints(), expectedList)
   232  
   233  	// Test Resolve()
   234  	for _, res := range expectedList {
   235  		for _, ipVer := range []int{0, 4, 6} {
   236  			t.Run(fmt.Sprintf("resolve_%s_for_IPv%d", res.GetName(), ipVer), func(t *testing.T) {
   237  				expectedIP := expectedIPByVersion[res.GetName()][ipVer]
   238  
   239  				var gotIP string
   240  				ip, err := client.Resolve(res.GetName(), ipVer)
   241  				if err != nil {
   242  					t.Logf("Error resolving %s, err: %v", res.GetName(), err)
   243  					gotIP = "err"
   244  				} else {
   245  					gotIP = ip.String()
   246  				}
   247  
   248  				if gotIP != expectedIP {
   249  					t.Errorf("Didn't get expected IP for %s. Got: %s, Want: %s", res.GetName(), gotIP, expectedIP)
   250  				}
   251  			})
   252  		}
   253  	}
   254  }
   255  
   256  func TestCacheBehaviorWithServerSupport(t *testing.T) {
   257  	ctx, cancelFunc := context.WithCancel(context.Background())
   258  	defer cancelFunc()
   259  
   260  	initLastModified := time.Now().Unix()
   261  	tp := &testProvider{
   262  		resources:           testResources,
   263  		supportCacheControl: true,
   264  		lastModified:        initLastModified,
   265  	}
   266  
   267  	srv, err := server.New(ctx, &serverpb.ServerConf{}, map[string]server.Provider{testProviderName: tp}, &logger.Logger{})
   268  	if err != nil {
   269  		t.Fatalf("Got error creating RDS server: %v", err)
   270  	}
   271  
   272  	c := &configpb.ClientConf{
   273  		Request: &pb.ListResourcesRequest{
   274  			Provider: proto.String(testProviderName),
   275  		},
   276  	}
   277  	client, err := New(c, srv.ListResources, &logger.Logger{})
   278  	if err != nil {
   279  		t.Fatalf("Got error initializing RDS client: %v", err)
   280  	}
   281  	client.resolver = dnsRes.NewWithResolve(func(name string) ([]net.IP, error) {
   282  		return testNameToIP[name], nil
   283  	})
   284  
   285  	// Since New calls refreshState, there should already be a request.
   286  	runCount := 1
   287  	tp.verifyRequestResponse(t, runCount, 0, initLastModified)
   288  	if client.lastModified != initLastModified {
   289  		t.Errorf("Client's last modified: %d, want: %d.", client.lastModified, initLastModified)
   290  	}
   291  	verifyEndpoints(t, client.ListEndpoints(), expectedList)
   292  
   293  	// Verify that refreshing client state doesn't change its last-modified and
   294  	// we still list all the resources.
   295  	tp.resources = testResources[1:] // This should have no effect
   296  	client.refreshState(time.Second)
   297  	runCount++
   298  	tp.verifyRequestResponse(t, runCount, initLastModified, initLastModified)
   299  	if client.lastModified != initLastModified {
   300  		t.Errorf("Client's last modified: %d, expected: %d.", client.lastModified, initLastModified)
   301  	}
   302  	verifyEndpoints(t, client.ListEndpoints(), expectedList)
   303  
   304  	// Change provider's last modified to trigger client's refresh.
   305  	newLastModified := initLastModified + 1
   306  	tp.lastModified = newLastModified
   307  	tp.resources = testResources[1:]
   308  	client.refreshState(time.Second)
   309  	runCount++
   310  	tp.verifyRequestResponse(t, runCount, initLastModified, newLastModified)
   311  	if client.lastModified == initLastModified || client.lastModified != newLastModified {
   312  		t.Errorf("Unexpected last modified timestamps. Previous: %v, Now: %v, Expected: %v", initLastModified, client.lastModified, newLastModified)
   313  	}
   314  	// State will get updated this time, hence shorter expectedList
   315  	verifyEndpoints(t, client.ListEndpoints(), expectedList[1:])
   316  }
   317  
   318  func TestCacheBehaviorWithoutServerSupport(t *testing.T) {
   319  	ctx, cancelFunc := context.WithCancel(context.Background())
   320  	defer cancelFunc()
   321  
   322  	tp := &testProvider{
   323  		resources:           testResources,
   324  		supportCacheControl: false,
   325  	}
   326  	srv, err := server.New(ctx, &serverpb.ServerConf{}, map[string]server.Provider{testProviderName: tp}, &logger.Logger{})
   327  	if err != nil {
   328  		t.Fatalf("Got error creating RDS server: %v", err)
   329  	}
   330  
   331  	c := &configpb.ClientConf{
   332  		Request: &pb.ListResourcesRequest{
   333  			Provider: proto.String(testProviderName),
   334  		},
   335  	}
   336  	client, err := New(c, srv.ListResources, &logger.Logger{})
   337  	if err != nil {
   338  		t.Fatalf("Got error initializing RDS client: %v", err)
   339  	}
   340  	client.resolver = dnsRes.NewWithResolve(func(name string) ([]net.IP, error) {
   341  		return testNameToIP[name], nil
   342  	})
   343  
   344  	// Since New calls refreshState, there should already be a request.
   345  	runCount := 1
   346  	tp.verifyRequestResponse(t, runCount, 0, 0)
   347  	if client.lastModified != 0 {
   348  		t.Errorf("Client's last modified: %d, want: %d.", client.lastModified, 0)
   349  	}
   350  	verifyEndpoints(t, client.ListEndpoints(), expectedList)
   351  
   352  	// Verify that refreshing client's state changes its state.
   353  	tp.resources = testResources[1:]
   354  	client.refreshState(time.Second)
   355  	runCount++
   356  	tp.verifyRequestResponse(t, runCount, 0, 0)
   357  	verifyEndpoints(t, client.ListEndpoints(), expectedList[1:])
   358  }