From 84e1c4f57ea7faac307e727cd98f8fcbd52c742d Mon Sep 17 00:00:00 2001 From: sgayangi Date: Tue, 8 Oct 2024 18:02:01 +0530 Subject: [PATCH 1/3] Add interceptor support for GRPC APIs --- .../operator/controllers/dp/api_controller.go | 75 ++++++++++++++++++- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index 53615bcbf..2cc69d699 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -467,7 +467,7 @@ func (apiReconciler *APIReconciler) resolveAPIRefs(ctx context.Context, api dpv1 // handle gRPC APIs if len(prodRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == constants.GRPC { if apiState.ProdGRPCRoute, err = apiReconciler.resolveGRPCRouteRefs(ctx, prodRouteRefs, - namespace, api); err != nil { + namespace, apiState.InterceptorServiceMapping, api); err != nil { return nil, fmt.Errorf("error while resolving production grpcRouteref %s in namespace :%s was not found. %s", prodRouteRefs, namespace, err.Error()) } @@ -483,7 +483,7 @@ func (apiReconciler *APIReconciler) resolveAPIRefs(ctx context.Context, api dpv1 if len(sandRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == constants.GRPC { if apiState.SandGRPCRoute, err = apiReconciler.resolveGRPCRouteRefs(ctx, sandRouteRefs, - namespace, api); err != nil { + namespace, apiState.InterceptorServiceMapping, api); err != nil { return nil, fmt.Errorf("error while resolving sandbox grpcRouteref %s in namespace :%s was not found. %s", sandRouteRefs, namespace, err.Error()) } @@ -603,11 +603,15 @@ func (apiReconciler *APIReconciler) resolveHTTPRouteRefs(ctx context.Context, ht } func (apiReconciler *APIReconciler) resolveGRPCRouteRefs(ctx context.Context, grpcRouteRefs []string, - namespace string, api dpv1alpha3.API) (*synchronizer.GRPCRouteState, error) { + namespace string, interceptorServiceMapping map[string]dpv1alpha1.InterceptorService, api dpv1alpha3.API) (*synchronizer.GRPCRouteState, error) { grpcRouteState, err := apiReconciler.concatGRPCRoutes(ctx, grpcRouteRefs, namespace, api) if err != nil { return nil, err } + grpcRouteState.BackendMapping, err = apiReconciler.getResolvedBackendsMappingForGRPC(ctx, &grpcRouteState, interceptorServiceMapping, api) + if err != nil { + return nil, err + } grpcRouteState.Scopes, err = apiReconciler.getScopesForGRPCRoute(ctx, grpcRouteState.GRPCRouteCombined, api) return &grpcRouteState, err } @@ -633,7 +637,6 @@ func (apiReconciler *APIReconciler) concatGRPCRoutes(ctx context.Context, grpcRo } grpcRouteState.GRPCRoutePartitions = grpcRoutePartitions backendNamespacedName := types.NamespacedName{ - //TODO check if this is correct Name: string(grpcRouteState.GRPCRouteCombined.Spec.Rules[0].BackendRefs[0].BackendRef.Name), Namespace: namespace, } @@ -1065,6 +1068,70 @@ func (apiReconciler *APIReconciler) getResolvedBackendsMapping(ctx context.Conte return backendMapping, airl, nil } +func (apiReconciler *APIReconciler) getResolvedBackendsMappingForGRPC(ctx context.Context, + grpcRouteState *synchronizer.GRPCRouteState, interceptorServiceMapping map[string]dpv1alpha1.InterceptorService, + api dpv1alpha3.API) (map[string]*dpv1alpha2.ResolvedBackend, error) { + backendMapping := make(map[string]*dpv1alpha2.ResolvedBackend) + grpcRoute := grpcRouteState.GRPCRouteCombined + + for _, rule := range grpcRoute.Spec.Rules { + for _, backend := range rule.BackendRefs { + backendNamespacedName := types.NamespacedName{ + Name: string(backend.Name), + Namespace: utils.GetNamespace(backend.Namespace, grpcRoute.Namespace), + } + if _, exists := backendMapping[backendNamespacedName.String()]; !exists { + resolvedBackend := utils.GetResolvedBackend(ctx, apiReconciler.client, backendNamespacedName, &api) + if resolvedBackend != nil { + backendMapping[backendNamespacedName.String()] = resolvedBackend + } else { + return nil, fmt.Errorf("unable to find backend %s", backendNamespacedName.String()) + } + } + } + + for _, filter := range rule.Filters { + if filter.RequestMirror != nil { + mirrorBackend := filter.RequestMirror.BackendRef + mirrorBackendNamespacedName := types.NamespacedName{ + Name: string(mirrorBackend.Name), + Namespace: utils.GetNamespace(mirrorBackend.Namespace, grpcRoute.Namespace), + } + if string(*mirrorBackend.Kind) == constants.KindBackend { + if _, exists := backendMapping[mirrorBackendNamespacedName.String()]; !exists { + resolvedMirrorBackend := utils.GetResolvedBackend(ctx, apiReconciler.client, mirrorBackendNamespacedName, &api) + if resolvedMirrorBackend != nil { + backendMapping[mirrorBackendNamespacedName.String()] = resolvedMirrorBackend + } else { + return nil, fmt.Errorf("unable to find backend %s", mirrorBackendNamespacedName.String()) + } + } + } else if string(*mirrorBackend.Kind) == constants.KindService { + var err error + service, err := utils.GetService(ctx, apiReconciler.client, utils.GetNamespace(mirrorBackend.Namespace, grpcRoute.Namespace), string(mirrorBackend.Name)) + if err != nil { + return nil, fmt.Errorf("unable to find service %s", mirrorBackendNamespacedName.String()) + } + backendMapping[mirrorBackendNamespacedName.String()], err = utils.GetResolvedBackendFromService(service, int(*mirrorBackend.Port)) + if err != nil { + return nil, fmt.Errorf("error in getting service information %s", service) + } + } + } + } + } + + // Resolve backends in InterceptorServices + interceptorServices := maps.Values(interceptorServiceMapping) + for _, interceptorService := range interceptorServices { + utils.ResolveAndAddBackendToMapping(ctx, apiReconciler.client, backendMapping, + interceptorService.Spec.BackendRef, interceptorService.Namespace, &api) + } + + loggers.LoggerAPKOperator.Debugf("Generated backendMapping: %v", backendMapping) + return backendMapping, nil +} + // These proxy methods are designed as intermediaries for the getAPIsFor methods. // Their purpose is to encapsulate the process of updating owner references within the reconciliation watch methods. // By employing these proxies, we prevent redundant owner reference updates for the same object due to the hierarchical structure of these functions. From 42d69e45295c1f1e4169ae2692abeb73fbde56a7 Mon Sep 17 00:00:00 2001 From: sgayangi Date: Thu, 10 Oct 2024 10:03:23 +0530 Subject: [PATCH 2/3] Update implementation for GRPC API policies --- .../oasparser/model/adapter_internal_api.go | 88 ++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 3dab4a249..3cdfe66c9 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -26,6 +26,7 @@ import ( "time" "github.com/google/uuid" + "github.com/sirupsen/logrus" "github.com/wso2/apk/adapter/config" "github.com/wso2/apk/adapter/internal/interceptor" "github.com/wso2/apk/adapter/internal/loggers" @@ -1274,45 +1275,72 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap var policies = OperationPolicies{} var endPoints []Endpoint resourceAuthScheme := authScheme + resourceAPIPolicy := apiPolicy resourceRatelimitPolicy := ratelimitPolicy var scopes []string for _, filter := range rule.Filters { - if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindAuthentication { - if ref, found := resourceParams.ResourceAuthSchemes[types.NamespacedName{ - Name: string(filter.ExtensionRef.Name), - Namespace: grpcRoute.Namespace, - }.String()]; found { - resourceAuthScheme = concatAuthSchemes(authScheme, &ref) - } else { - return fmt.Errorf(`auth scheme: %s has not been resolved, spec.targetRef.kind should be - 'Resource' in resource level Authentications`, filter.ExtensionRef.Name) + switch filter.Type { + case gwapiv1a2.GRPCRouteFilterExtensionRef: + if filter.ExtensionRef.Kind == constants.KindAuthentication { + if ref, found := resourceParams.ResourceAuthSchemes[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: grpcRoute.Namespace, + }.String()]; found { + resourceAuthScheme = concatAuthSchemes(authScheme, &ref) + } else { + return fmt.Errorf(`auth scheme: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level Authentications`, filter.ExtensionRef.Name) + } } - } - if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindScope { - if ref, found := resourceParams.ResourceScopes[types.NamespacedName{ - Name: string(filter.ExtensionRef.Name), - Namespace: grpcRoute.Namespace, - }.String()]; found { - scopes = ref.Spec.Names - disableScopes = false - } else { - return fmt.Errorf("scope: %s has not been resolved in namespace %s", filter.ExtensionRef.Name, grpcRoute.Namespace) + if filter.ExtensionRef.Kind == constants.KindAPIPolicy { + if ref, found := resourceParams.ResourceAPIPolicies[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: grpcRoute.Namespace, + }.String()]; found { + logrus.Info("filter.ExtensionRef.Kind == constants.KindAPIPolicy") + logrus.Info(apiPolicy.Name) + logrus.Info(apiPolicy.Name) + if apiPolicy.Spec.Default != nil { + logrus.Info(apiPolicy.Spec.Default.RequestInterceptors) + } + if apiPolicy.Spec.Default != nil { + logrus.Info(apiPolicy.Spec.Default.ResponseInterceptors) + } + resourceAPIPolicy = concatAPIPolicies(apiPolicy, &ref) + } else { + return fmt.Errorf(`apipolicy: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level APIPolicies`, filter.ExtensionRef.Name) + } } - } - if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindRateLimitPolicy { - if ref, found := resourceParams.ResourceRateLimitPolicies[types.NamespacedName{ - Name: string(filter.ExtensionRef.Name), - Namespace: grpcRoute.Namespace, - }.String()]; found { - resourceRatelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, &ref) - } else { - return fmt.Errorf(`ratelimitpolicy: %s has not been resolved, spec.targetRef.kind should be - 'Resource' in resource level RateLimitPolicies`, filter.ExtensionRef.Name) + if filter.ExtensionRef.Kind == constants.KindScope { + if ref, found := resourceParams.ResourceScopes[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: grpcRoute.Namespace, + }.String()]; found { + scopes = ref.Spec.Names + disableScopes = false + } else { + return fmt.Errorf("scope: %s has not been resolved in namespace %s", filter.ExtensionRef.Name, grpcRoute.Namespace) + } + } + if filter.ExtensionRef.Kind == constants.KindRateLimitPolicy { + if ref, found := resourceParams.ResourceRateLimitPolicies[types.NamespacedName{ + Name: string(filter.ExtensionRef.Name), + Namespace: grpcRoute.Namespace, + }.String()]; found { + resourceRatelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, &ref) + } else { + return fmt.Errorf(`ratelimitpolicy: %s has not been resolved, spec.targetRef.kind should be + 'Resource' in resource level RateLimitPolicies`, filter.ExtensionRef.Name) + } } } } + + resourceAPIPolicy = concatAPIPolicies(resourceAPIPolicy, nil) resourceAuthScheme = concatAuthSchemes(resourceAuthScheme, nil) resourceRatelimitPolicy = concatRateLimitPolicies(resourceRatelimitPolicy, nil) + addOperationLevelInterceptors(&policies, resourceAPIPolicy, resourceParams.InterceptorServiceMapping, resourceParams.BackendMapping, grpcRoute.Namespace) loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", adapterInternalAPI.UUID) apiAuth := getSecurity(resourceAuthScheme) @@ -1321,7 +1349,7 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap resourcePath := adapterInternalAPI.GetXWso2Basepath() + "." + *match.Method.Service + "/" + *match.Method.Method endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) resource := &Resource{path: resourcePath, pathMatchType: "Exact", - methods: []*Operation{{iD: uuid.New().String(), method: "GRPC", policies: policies, + methods: []*Operation{{iD: uuid.New().String(), method: "POST", policies: policies, auth: apiAuth, rateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, iD: uuid.New().String(), } From adf09934319ca093fa90bd8eaf05138cb71e9946 Mon Sep 17 00:00:00 2001 From: sgayangi Date: Thu, 10 Oct 2024 15:52:25 +0530 Subject: [PATCH 3/3] Add cucumber test for GRPC API interceptor --- .../oasparser/model/adapter_internal_api.go | 10 ----- .../operator/controllers/dp/api_controller.go | 30 ------------- .../wso2/apk/integration/api/BaseSteps.java | 18 +++++++- .../utils/GenericClientInterceptor.java | 26 +++++++++-- .../clients/SimpleGRPCStudentClient.java | 16 +++++++ .../apk-confs/grpc/grpc-interceptor.apk-conf | 44 +++++++++++++++++++ .../src/test/resources/tests/api/GRPC.feature | 21 +++++++++ 7 files changed, 120 insertions(+), 45 deletions(-) create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc-interceptor.apk-conf diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 3cdfe66c9..7bdd62768 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -26,7 +26,6 @@ import ( "time" "github.com/google/uuid" - "github.com/sirupsen/logrus" "github.com/wso2/apk/adapter/config" "github.com/wso2/apk/adapter/internal/interceptor" "github.com/wso2/apk/adapter/internal/loggers" @@ -1297,15 +1296,6 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap Name: string(filter.ExtensionRef.Name), Namespace: grpcRoute.Namespace, }.String()]; found { - logrus.Info("filter.ExtensionRef.Kind == constants.KindAPIPolicy") - logrus.Info(apiPolicy.Name) - logrus.Info(apiPolicy.Name) - if apiPolicy.Spec.Default != nil { - logrus.Info(apiPolicy.Spec.Default.RequestInterceptors) - } - if apiPolicy.Spec.Default != nil { - logrus.Info(apiPolicy.Spec.Default.ResponseInterceptors) - } resourceAPIPolicy = concatAPIPolicies(apiPolicy, &ref) } else { return fmt.Errorf(`apipolicy: %s has not been resolved, spec.targetRef.kind should be diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index 2cc69d699..44c2b5852 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -1089,36 +1089,6 @@ func (apiReconciler *APIReconciler) getResolvedBackendsMappingForGRPC(ctx contex } } } - - for _, filter := range rule.Filters { - if filter.RequestMirror != nil { - mirrorBackend := filter.RequestMirror.BackendRef - mirrorBackendNamespacedName := types.NamespacedName{ - Name: string(mirrorBackend.Name), - Namespace: utils.GetNamespace(mirrorBackend.Namespace, grpcRoute.Namespace), - } - if string(*mirrorBackend.Kind) == constants.KindBackend { - if _, exists := backendMapping[mirrorBackendNamespacedName.String()]; !exists { - resolvedMirrorBackend := utils.GetResolvedBackend(ctx, apiReconciler.client, mirrorBackendNamespacedName, &api) - if resolvedMirrorBackend != nil { - backendMapping[mirrorBackendNamespacedName.String()] = resolvedMirrorBackend - } else { - return nil, fmt.Errorf("unable to find backend %s", mirrorBackendNamespacedName.String()) - } - } - } else if string(*mirrorBackend.Kind) == constants.KindService { - var err error - service, err := utils.GetService(ctx, apiReconciler.client, utils.GetNamespace(mirrorBackend.Namespace, grpcRoute.Namespace), string(mirrorBackend.Name)) - if err != nil { - return nil, fmt.Errorf("unable to find service %s", mirrorBackendNamespacedName.String()) - } - backendMapping[mirrorBackendNamespacedName.String()], err = utils.GetResolvedBackendFromService(service, int(*mirrorBackend.Port)) - if err != nil { - return nil, fmt.Errorf("error in getting service information %s", service) - } - } - } - } } // Resolve backends in InterceptorServices diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java index e7245f198..50ce6d88d 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java @@ -78,6 +78,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import io.cucumber.java.en.And; import io.grpc.Status; import io.grpc.StatusRuntimeException; @@ -248,6 +249,18 @@ public void GetStudent(String arg0, int arg1) throws StatusRuntimeException { } } + @And("the GRPC response should contain header {string}") + public void GetGRPCMetadata(String arg0) throws StatusRuntimeException { + try { + String header = SimpleGRPCStudentClient.getResponseHeader(arg0); + Assert.assertNotNull(header); + Assert.assertEquals(header, "Interceptor-Response-header-value"); + } catch (StatusRuntimeException e) { + sharedContext.setGrpcStatusCode(e.getStatus().getCode().value()); + logger.error(e.getMessage() + " Status code: " + e.getStatus().getCode().value()); + } + } + @Then("I make grpc request GetStudent default version to {string} with port {int}") public void GetStudentDefaultVersion(String arg0, int arg1) throws StatusRuntimeException { try { @@ -329,8 +342,9 @@ public void checkEnforcerLogs(DataTable dataTable) throws IOException, Interrupt } try { String logs = api.readNamespacedPodLog(podName, namespace).container("enforcer").sinceSeconds(60).execute(); - Assert.assertNotNull(logs, String.format("Could not find any logs in the last 60 seconds. PodName: %s, namespace: %s", podName, namespace)); - for(String word : stringsToCheck) { + Assert.assertNotNull(logs, String.format( + "Could not find any logs in the last 60 seconds. PodName: %s, namespace: %s", podName, namespace)); + for (String word : stringsToCheck) { Assert.assertTrue(logs.contains(word), "Expected word '" + word + "' not found in logs"); } } catch (ApiException e) { diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/GenericClientInterceptor.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/GenericClientInterceptor.java index 0467b66f1..7d435b2c3 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/GenericClientInterceptor.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/GenericClientInterceptor.java @@ -2,6 +2,8 @@ import io.grpc.ClientInterceptor; import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.CallOptions; @@ -9,16 +11,23 @@ import io.grpc.Channel; import java.util.Map; -import java.util.Map; - public class GenericClientInterceptor implements ClientInterceptor { private Map headers; + private Metadata responseHeaders; public GenericClientInterceptor(Map headers) { this.headers = headers; } + public void setResponseHeaders(Metadata responseHeaders) { + this.responseHeaders = responseHeaders; + } + + public Metadata getResponseHeaders() { + return this.responseHeaders; + } + @Override public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { @@ -31,7 +40,18 @@ public void start(Listener responseListener, Metadata headersMetadata) { headers.forEach((key, value) -> headersMetadata.put( Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value)); - super.start(responseListener, headersMetadata); + super.start(new SimpleForwardingClientCallListener(responseListener) { + @Override + public void onHeaders(Metadata headers) { + /** + * if you don't need receive header from server, + * you can use {@link io.grpc.stub.MetadataUtils#attachHeaders} + * directly to send header + */ + setResponseHeaders(headers); + super.onHeaders(headers); + } + }, headersMetadata); } }; } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleGRPCStudentClient.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleGRPCStudentClient.java index 359b9eb86..92c1eba87 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleGRPCStudentClient.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleGRPCStudentClient.java @@ -9,6 +9,8 @@ import org.apache.commons.logging.LogFactory; import io.grpc.ManagedChannel; +import io.grpc.Metadata; + import org.wso2.apk.integration.utils.GenericClientInterceptor; import org.wso2.apk.integration.utils.clients.student_service.StudentRequest; import org.wso2.apk.integration.utils.clients.student_service.StudentResponse; @@ -24,6 +26,7 @@ public class SimpleGRPCStudentClient { private static final int EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS = 10; private final String host; private final int port; + private static Metadata responseHeaders; public SimpleGRPCStudentClient(String host, int port) { this.host = host; @@ -53,6 +56,7 @@ public StudentResponse GetStudent(Map headers) throws StatusRunt log.error("Failed to get student"); throw new RuntimeException("Failed to get student"); } + setResponseHeaders(interceptor.getResponseHeaders()); return response; } catch (SSLException e) { throw new RuntimeException("Failed to create SSL context", e); @@ -72,6 +76,18 @@ public StudentResponse GetStudent(Map headers) throws StatusRunt } } + public static String getResponseHeader(String headerName) { + Metadata.Key headerValue = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER); + if (responseHeaders == null) { + return ""; + } + return responseHeaders.get(headerValue); + } + + public void setResponseHeaders(Metadata metadata) { + SimpleGRPCStudentClient.responseHeaders = metadata; + } + public StudentResponse GetStudentDefaultVersion(Map headers) throws StatusRuntimeException { ManagedChannel managedChannel = null; try { diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc-interceptor.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc-interceptor.apk-conf new file mode 100644 index 000000000..2b4318225 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc-interceptor.apk-conf @@ -0,0 +1,44 @@ +name: "6a254687f3229c35dd0189aac7f7fc4b6228e97a" +basePath: "/org.apk" +version: "v1" +type: "GRPC" +id: "grpc-interceptor-api" +endpointConfigurations: + production: + endpoint: "http://grpc-backend:6565" +defaultVersion: false +subscriptionValidation: false +operations: + - target: "student_service.StudentService" + verb: "GetStudent" + secured: true + scopes: [] + - target: "student_service.StudentService" + verb: "GetStudentStream" + secured: true + scopes: [] + - target: "student_service.StudentService" + verb: "SendStudentStream" + secured: true + scopes: [] + - target: "student_service.StudentService" + verb: "SendAndGetStudentStream" + secured: true + scopes: [] +apiPolicies: + request: + - policyName: "Interceptor" + policyVersion: v1 + parameters: + backendUrl: "http://interceptor-service.apk-integration-test.svc.cluster.local:8443" + contextEnabled: true + headersEnabled: true + bodyEnabled: true + response: + - policyName: "Interceptor" + policyVersion: v1 + parameters: + backendUrl: "http://interceptor-service.apk-integration-test.svc.cluster.local:8443" + contextEnabled: true + headersEnabled: true + bodyEnabled: true diff --git a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature index dfbcc8f57..39dbf6ae3 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -120,4 +120,25 @@ Feature: Generating APK conf for gRPC API Given The system is ready And I have a valid subscription When I undeploy the API whose ID is "grpc-default-version-api" + Then the response status code should be 202 + + Scenario: Deploying gRPC API with interceptor policy + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc-interceptor.apk-conf" + And the definition file "artifacts/definitions/student.proto" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + | Authorization | bearer ${accessToken} | + And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 + And the gRPC response status code should be 0 + And the student response body should contain name: "Student" age: 10 + And the GRPC response should contain header "interceptor-response-header" + + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "grpc-interceptor-api" Then the response status code should be 202 \ No newline at end of file