diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/BUILD b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/BUILD index 2d2ee97c014..0c225c746fe 100644 --- a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/BUILD +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/BUILD @@ -28,9 +28,11 @@ go_test( srcs = ["azure_routeclient_test.go"], embed = [":go_default_library"], deps = [ + "//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library", + "//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library", "//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library", "//vendor/github.com/golang/mock/gomock:go_default_library", diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/azure_routeclient_test.go b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/azure_routeclient_test.go index f9cfa7d8b3b..01830775b80 100644 --- a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/azure_routeclient_test.go +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/azure_routeclient_test.go @@ -25,17 +25,46 @@ import ( "io/ioutil" "net/http" "testing" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network" "github.com/Azure/go-autorest/autorest/to" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "k8s.io/client-go/util/flowcontrol" azclients "k8s.io/legacy-cloud-providers/azure/clients" "k8s.io/legacy-cloud-providers/azure/clients/armclient" "k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient" + "k8s.io/legacy-cloud-providers/azure/retry" ) +// 2065-01-24 05:20:00 +0000 UTC +func getFutureTime() time.Time { + return time.Unix(3000000000, 0) +} + +func TestNew(t *testing.T) { + config := &azclients.ClientConfig{ + SubscriptionID: "sub", + ResourceManagerEndpoint: "endpoint", + Location: "eastus", + RateLimitConfig: &azclients.RateLimitConfig{ + CloudProviderRateLimit: true, + CloudProviderRateLimitQPS: 0.5, + CloudProviderRateLimitBucket: 1, + CloudProviderRateLimitQPSWrite: 0.5, + CloudProviderRateLimitBucketWrite: 1, + }, + Backoff: &retry.Backoff{Steps: 1}, + } + + routeClient := New(config) + assert.Equal(t, "sub", routeClient.subscriptionID) + assert.NotEmpty(t, routeClient.rateLimiterReader) + assert.NotEmpty(t, routeClient.rateLimiterWriter) +} + func TestCreateOrUpdate(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -49,11 +78,91 @@ func TestCreateOrUpdate(t *testing.T) { armClient.EXPECT().PutResourceWithDecorators(gomock.Any(), to.String(r.ID), r, gomock.Any()).Return(response, nil).Times(1) armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1) - rtClient := getTestRouteClient(armClient) - rerr := rtClient.CreateOrUpdate(context.TODO(), "rg", "rt", "r1", r, "") + routeClient := getTestRouteClient(armClient) + rerr := routeClient.CreateOrUpdate(context.TODO(), "rg", "rt", "r1", r, "*") assert.Nil(t, rerr) } +func TestCreateOrUpdateWithNeverRateLimiter(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rcCreateOrUpdatetErr := &retry.Error{ + RawError: fmt.Errorf("azure cloud provider rate limited(%s) for operation %q", "write", "RouteCreateOrUpdate"), + Retriable: true, + } + + r := getTestRoute("r1") + armClient := mockarmclient.NewMockInterface(ctrl) + + routeClient := getTestRouteClientWithNeverRateLimiter(armClient) + rerr := routeClient.CreateOrUpdate(context.TODO(), "rg", "rt", "r1", r, "") + assert.Equal(t, rcCreateOrUpdatetErr, rerr) +} + +func TestCreateOrUpdateRetryAfterReader(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rcCreateOrUpdateErr := &retry.Error{ + RawError: fmt.Errorf("azure cloud provider throttled for operation %s with reason %q", "RouteCreateOrUpdate", "client throttled"), + Retriable: true, + RetryAfter: getFutureTime(), + } + + r := getTestRoute("r1") + armClient := mockarmclient.NewMockInterface(ctrl) + + routeClient := getTestRouteClientWithRetryAfterReader(armClient) + rerr := routeClient.CreateOrUpdate(context.TODO(), "rg", "rt", "r1", r, "") + assert.NotNil(t, rerr) + assert.Equal(t, rcCreateOrUpdateErr, rerr) +} + +func TestCreateOrUpdateThrottle(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + response := &http.Response{ + StatusCode: http.StatusTooManyRequests, + Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))), + } + throttleErr := &retry.Error{ + HTTPStatusCode: http.StatusTooManyRequests, + RawError: fmt.Errorf("error"), + Retriable: true, + RetryAfter: time.Unix(100, 0), + } + + r := getTestRoute("r1") + armClient := mockarmclient.NewMockInterface(ctrl) + armClient.EXPECT().PutResourceWithDecorators(gomock.Any(), to.String(r.ID), r, gomock.Any()).Return(response, throttleErr).Times(1) + armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1) + + routeClient := getTestRouteClient(armClient) + rerr := routeClient.CreateOrUpdate(context.TODO(), "rg", "rt", "r1", r, "") + assert.Equal(t, throttleErr, rerr) +} + +func TestCreateOrUpdateWithCreateOrUpdateResponderError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := getTestRoute("r1") + armClient := mockarmclient.NewMockInterface(ctrl) + response := &http.Response{ + StatusCode: http.StatusNotFound, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + } + + armClient.EXPECT().PutResourceWithDecorators(gomock.Any(), to.String(r.ID), r, gomock.Any()).Return(response, nil).Times(1) + armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1) + + routeClient := getTestRouteClient(armClient) + rerr := routeClient.CreateOrUpdate(context.TODO(), "rg", "rt", "r1", r, "") + assert.NotNil(t, rerr) +} + func TestDelete(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -62,11 +171,64 @@ func TestDelete(t *testing.T) { armClient := mockarmclient.NewMockInterface(ctrl) armClient.EXPECT().DeleteResource(gomock.Any(), to.String(r.ID), "").Return(nil).Times(1) - rtClient := getTestRouteClient(armClient) - rerr := rtClient.Delete(context.TODO(), "rg", "rt", "r1") + routeClient := getTestRouteClient(armClient) + rerr := routeClient.Delete(context.TODO(), "rg", "rt", "r1") assert.Nil(t, rerr) } +func TestDeleteWithNeverRateLimiter(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rcDeleteErr := &retry.Error{ + RawError: fmt.Errorf("azure cloud provider rate limited(%s) for operation %q", "write", "RouteDelete"), + Retriable: true, + } + + armClient := mockarmclient.NewMockInterface(ctrl) + + routeClient := getTestRouteClientWithNeverRateLimiter(armClient) + rerr := routeClient.Delete(context.TODO(), "rg", "rt", "r1") + assert.Equal(t, rcDeleteErr, rerr) +} + +func TestDeleteRetryAfterReader(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rcDeleteErr := &retry.Error{ + RawError: fmt.Errorf("azure cloud provider throttled for operation %s with reason %q", "RouteDelete", "client throttled"), + Retriable: true, + RetryAfter: getFutureTime(), + } + + armClient := mockarmclient.NewMockInterface(ctrl) + + routeClient := getTestRouteClientWithRetryAfterReader(armClient) + rerr := routeClient.Delete(context.TODO(), "rg", "rt", "r1") + assert.Equal(t, rcDeleteErr, rerr) +} + +func TestDeleteThrottle(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + throttleErr := &retry.Error{ + HTTPStatusCode: http.StatusTooManyRequests, + RawError: fmt.Errorf("error"), + Retriable: true, + RetryAfter: time.Unix(100, 0), + } + + r := getTestRoute("r1") + armClient := mockarmclient.NewMockInterface(ctrl) + armClient.EXPECT().DeleteResource(gomock.Any(), to.String(r.ID), "").Return(throttleErr).Times(1) + + routeClient := getTestRouteClient(armClient) + rerr := routeClient.Delete(context.TODO(), "rg", "rt", "r1") + assert.Equal(t, throttleErr, rerr) +} + func getTestRoute(name string) network.Route { return network.Route{ ID: to.StringPtr(fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/routeTables/rt/routes/%s", name)), @@ -83,3 +245,27 @@ func getTestRouteClient(armClient armclient.Interface) *Client { rateLimiterWriter: rateLimiterWriter, } } + +func getTestRouteClientWithNeverRateLimiter(armClient armclient.Interface) *Client { + rateLimiterReader := flowcontrol.NewFakeNeverRateLimiter() + rateLimiterWriter := flowcontrol.NewFakeNeverRateLimiter() + return &Client{ + armClient: armClient, + subscriptionID: "subscriptionID", + rateLimiterReader: rateLimiterReader, + rateLimiterWriter: rateLimiterWriter, + } +} + +func getTestRouteClientWithRetryAfterReader(armClient armclient.Interface) *Client { + rateLimiterReader := flowcontrol.NewFakeAlwaysRateLimiter() + rateLimiterWriter := flowcontrol.NewFakeAlwaysRateLimiter() + return &Client{ + armClient: armClient, + subscriptionID: "subscriptionID", + rateLimiterReader: rateLimiterReader, + rateLimiterWriter: rateLimiterWriter, + RetryAfterReader: getFutureTime(), + RetryAfterWriter: getFutureTime(), + } +}