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 }