From 58a6df59990930d0f1f847bd3207cda333204b26 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 18 Jan 2024 12:23:56 +0530 Subject: [PATCH 01/81] add GRPC api type for API CR --- common-go-libs/apis/dp/v1alpha2/api_types.go | 4 ++-- common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml | 3 ++- helm-charts/templates/crds/dp.wso2.com_apis.yaml | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/common-go-libs/apis/dp/v1alpha2/api_types.go b/common-go-libs/apis/dp/v1alpha2/api_types.go index 4579e5e0b..c5df17cb2 100644 --- a/common-go-libs/apis/dp/v1alpha2/api_types.go +++ b/common-go-libs/apis/dp/v1alpha2/api_types.go @@ -80,9 +80,9 @@ type APISpec struct { Sandbox []EnvConfig `json:"sandbox"` // APIType denotes the type of the API. - // Possible values could be REST, GraphQL, Async + // Possible values could be REST, GraphQL, Async, GRPC etc. // - // +kubebuilder:validation:Enum=REST;GraphQL + // +kubebuilder:validation:Enum=REST;GraphQL;GRPC APIType string `json:"apiType"` // BasePath denotes the basepath of the API. diff --git a/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml b/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml index 7846de3ca..353ad94fa 100644 --- a/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml +++ b/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml @@ -243,10 +243,11 @@ spec: type: array apiType: description: APIType denotes the type of the API. Possible values - could be REST, GraphQL, Async + could be REST, GraphQL, Async, GRPC etc. enum: - REST - GraphQL + - GRPC type: string apiVersion: description: APIVersion is the version number of the API. diff --git a/helm-charts/templates/crds/dp.wso2.com_apis.yaml b/helm-charts/templates/crds/dp.wso2.com_apis.yaml index 399c0c01f..c398f3988 100644 --- a/helm-charts/templates/crds/dp.wso2.com_apis.yaml +++ b/helm-charts/templates/crds/dp.wso2.com_apis.yaml @@ -256,10 +256,11 @@ spec: type: array apiType: description: APIType denotes the type of the API. Possible values - could be REST, GraphQL, Async + could be REST, GraphQL, Async, GRPC etc. enum: - REST - GraphQL + - GRPC type: string apiVersion: description: APIVersion is the version number of the API. From 8287d0fe1ef6e374280f0ee33ad827a42eebf475 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 18 Jan 2024 12:29:00 +0530 Subject: [PATCH 02/81] gitignore router/resources folder --- gateway/router/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 gateway/router/.gitignore diff --git a/gateway/router/.gitignore b/gateway/router/.gitignore new file mode 100644 index 000000000..e47e1b33a --- /dev/null +++ b/gateway/router/.gitignore @@ -0,0 +1 @@ +/resources From 29ee66ca3aba7ef8dbe27043339fc0de0bb100e1 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 19 Mar 2024 13:48:49 +0530 Subject: [PATCH 03/81] c1 after rebase --- .../operator/controllers/dp/api_controller.go | 204 +++++++++++++++++- .../operator/synchronizer/api_state.go | 13 ++ common-go-libs/apis/dp/v1alpha2/api_types.go | 1 - .../config/crd/bases/dp.wso2.com_apis.yaml | 1 - .../serviceAccount/apk-cluster-role.yaml | 2 +- 5 files changed, 217 insertions(+), 4 deletions(-) diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index 0c13b4570..c12cbbce1 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -54,6 +54,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ctrl "sigs.k8s.io/controller-runtime" @@ -67,6 +68,7 @@ import ( ) const ( + grpcRouteAPIIndex = "grpcRouteAPIIndex" httpRouteAPIIndex = "httpRouteAPIIndex" gqlRouteAPIIndex = "gqlRouteAPIIndex" // apiAuthenticationIndex Index for API level authentications @@ -157,6 +159,13 @@ func NewAPIController(mgr manager.Manager, operatorDataStore *synchronizer.Opera return err } + if err := c.Watch(source.Kind(mgr.GetCache(), &gwapiv1a2.GRPCRoute{}), handler.EnqueueRequestsFromMapFunc(apiReconciler.populateAPIReconcileRequestsForGRPCRoute), + predicates...); err != nil { + //TODO change the error number + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2667, logging.BLOCKER, "Error watching GRPCRoute resources: %v", err)) + return err + } + if err := c.Watch(source.Kind(mgr.GetCache(), &gwapiv1b1.Gateway{}), handler.EnqueueRequestsFromMapFunc(apiReconciler.getAPIsForGateway), predicates...); err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2611, logging.BLOCKER, "Error watching API resources: %v", err)) @@ -232,6 +241,9 @@ func NewAPIController(mgr manager.Manager, operatorDataStore *synchronizer.Opera // +kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes/status,verbs=get;update;patch // +kubebuilder:rbac:groups=dp.wso2.com,resources=gqlroutes/finalizers,verbs=update +// +kubebuilder:rbac:groups=dp.wso2.com,resources=grpcroutes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=dp.wso2.com,resources=grpcroutes/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=dp.wso2.com,resources=grpcroutes/finalizers,verbs=update // +kubebuilder:rbac:groups=dp.wso2.com,resources=authentications,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=dp.wso2.com,resources=authentications/status,verbs=get;update;patch // +kubebuilder:rbac:groups=dp.wso2.com,resources=authentications/finalizers,verbs=update @@ -419,6 +431,24 @@ func (apiReconciler *APIReconciler) resolveAPIRefs(ctx context.Context, api dpv1 } } + //handle grpc apis + if len(prodRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == "GRPC" { + apiState.ProdGRPCRoute = &synchronizer.GRPCRouteState{} + if apiState.ProdGRPCRoute, err = apiReconciler.resolveGRPCRouteRefs(ctx, prodRouteRefs, + namespace, api); err != nil { + return nil, fmt.Errorf("error while resolving production grpcRouteref %s in namespace :%s has not found. %s", + prodRouteRefs, namespace, err.Error()) + } + } + if len(sandRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == "GRPC" { + apiState.SandGRPCRoute = &synchronizer.GRPCRouteState{} + if apiState.SandGRPCRoute, err = apiReconciler.resolveGRPCRouteRefs(ctx, sandRouteRefs, + namespace, api); err != nil { + return nil, fmt.Errorf("error while resolving sandbox grpcRouteref %s in namespace :%s has not found. %s", + sandRouteRefs, namespace, err.Error()) + } + } + // handle gql apis if len(prodRouteRefs) > 0 && apiState.APIDefinition.Spec.APIType == "GraphQL" { if apiState.ProdGQLRoute, err = apiReconciler.resolveGQLRouteRefs(ctx, prodRouteRefs, namespace, @@ -516,6 +546,16 @@ func (apiReconciler *APIReconciler) resolveGQLRouteRefs(ctx context.Context, gql return &gqlRouteState, err } +func (apiReconciler *APIReconciler) resolveGRPCRouteRefs(ctx context.Context, grpcRouteRefs []string, + namespace string, api dpv1alpha2.API) (*synchronizer.GRPCRouteState, error) { + grpcRouteState, err := apiReconciler.concatGRPCRoutes(ctx, grpcRouteRefs, namespace, api) + if err != nil { + return nil, err + } + grpcRouteState.Scopes, err = apiReconciler.getScopesForGRPCRoute(ctx, grpcRouteState.GRPCRouteCombined, api) + return &grpcRouteState, err +} + // resolveHTTPRouteRefs validates following references related to the API // - Authentications func (apiReconciler *APIReconciler) resolveHTTPRouteRefs(ctx context.Context, httpRouteState *synchronizer.HTTPRouteState, @@ -534,7 +574,42 @@ func (apiReconciler *APIReconciler) resolveHTTPRouteRefs(ctx context.Context, ht return httpRouteState, err } +func (apiReconciler *APIReconciler) concatGRPCRoutes(ctx context.Context, grpcRouteRefs []string, + namespace string, api dpv1alpha2.API) (synchronizer.GRPCRouteState, error) { + grpcRouteState := synchronizer.GRPCRouteState{} + grpcRoutePartitions := make(map[string]*gwapiv1a2.GRPCRoute) + for _, grpcRouteRef := range grpcRouteRefs { + var grpcRoute gwapiv1a2.GRPCRoute + namespacedName := types.NamespacedName{Namespace: namespace, Name: grpcRouteRef} + if err := utils.ResolveRef(ctx, apiReconciler.client, &api, namespacedName, true, &grpcRoute); err != nil { + return grpcRouteState, fmt.Errorf("error while getting grpcroute %s in namespace :%s, %s", grpcRouteRef, + namespace, err.Error()) + } + grpcRoutePartitions[namespacedName.String()] = &grpcRoute + if grpcRouteState.GRPCRouteCombined == nil { + grpcRouteState.GRPCRouteCombined = &grpcRoute + } else { + grpcRouteState.GRPCRouteCombined.Spec.Rules = append(grpcRouteState.GRPCRouteCombined.Spec.Rules, + grpcRoute.Spec.Rules...) + } + } + grpcRouteState.GRPCRoutePartitions = grpcRoutePartitions + backendNamespacedName := types.NamespacedName{ + //TODO: replace with appropriate attributes in the grpcRoute + //Name: string(grpcRouteState.GRPCRouteCombined.Spec.BackendRefs[0].Name), + //Name: "grpc-backend", + Namespace: namespace, + } + resolvedBackend := utils.GetResolvedBackend(ctx, apiReconciler.client, backendNamespacedName, &api) + if resolvedBackend != nil { + grpcRouteState.BackendMapping = map[string]*dpv1alpha1.ResolvedBackend{ + backendNamespacedName.String(): resolvedBackend, + } + return grpcRouteState, nil + } + return grpcRouteState, errors.New("error while resolving backend for grpcroute") +} func (apiReconciler *APIReconciler) concatGQLRoutes(ctx context.Context, gqlRouteRefs []string, namespace string, api dpv1alpha2.API) (synchronizer.GQLRouteState, error) { gqlRouteState := synchronizer.GQLRouteState{} @@ -623,7 +698,25 @@ func (apiReconciler *APIReconciler) getRatelimitPoliciesForAPI(ctx context.Conte } return ratelimitPolicies, nil } - +func (apiReconciler *APIReconciler) getScopesForGRPCRoute(ctx context.Context, + grpcRoute *gwapiv1a2.GRPCRoute, api dpv1alpha2.API) (map[string]dpv1alpha1.Scope, error) { + scopes := make(map[string]dpv1alpha1.Scope) + for _, rule := range grpcRoute.Spec.Rules { + for _, filter := range rule.Filters { + if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindScope { + scope := &dpv1alpha1.Scope{} + if err := utils.ResolveRef(ctx, apiReconciler.client, &api, + types.NamespacedName{Namespace: grpcRoute.Namespace, Name: string(filter.ExtensionRef.Name)}, false, + scope); err != nil { + return nil, fmt.Errorf("error while getting scope %s in namespace :%s, %s", filter.ExtensionRef.Name, + grpcRoute.Namespace, err.Error()) + } + scopes[utils.NamespacedName(scope).String()] = *scope + } + } + } + return scopes, nil +} func (apiReconciler *APIReconciler) getScopesForGQLRoute(ctx context.Context, gqlRoute *dpv1alpha2.GQLRoute, api dpv1alpha2.API) (map[string]dpv1alpha1.Scope, error) { scopes := make(map[string]dpv1alpha1.Scope) @@ -876,6 +969,12 @@ func (apiReconciler *APIReconciler) populateAPIReconcileRequestsForHTTPRoute(ctx return requests } +func (apiReconciler *APIReconciler) populateAPIReconcileRequestsForGRPCRoute(ctx context.Context, obj k8client.Object) []reconcile.Request { + requests := apiReconciler.getAPIForGRPCRoute(ctx, obj) + apiReconciler.handleOwnerReference(ctx, obj, &requests) + return requests +} + func (apiReconciler *APIReconciler) populateAPIReconcileRequestsForConfigMap(ctx context.Context, obj k8client.Object) []reconcile.Request { requests := apiReconciler.getAPIsForConfigMap(ctx, obj) apiReconciler.handleOwnerReference(ctx, obj, &requests) @@ -952,6 +1051,16 @@ func (apiReconciler *APIReconciler) traverseAPIStateAndUpdateOwnerReferences(ctx apiReconciler.retriveParentAPIsAndUpdateOwnerReferene(ctx, gqlRoute) } } + if apiState.ProdGRPCRoute != nil { + for _, grpcRoute := range apiState.ProdGRPCRoute.GRPCRoutePartitions { + apiReconciler.retriveParentAPIsAndUpdateOwnerReferene(ctx, grpcRoute) + } + } + if apiState.SandGRPCRoute != nil { + for _, grpcRoute := range apiState.SandGRPCRoute.GRPCRoutePartitions { + apiReconciler.retriveParentAPIsAndUpdateOwnerReferene(ctx, grpcRoute) + } + } for _, auth := range apiState.Authentications { apiReconciler.retriveParentAPIsAndUpdateOwnerReferene(ctx, &auth) } @@ -1001,6 +1110,20 @@ func (apiReconciler *APIReconciler) traverseAPIStateAndUpdateOwnerReferences(ctx } } } + if apiState.ProdGRPCRoute != nil { + for _, backend := range apiState.ProdGRPCRoute.BackendMapping { + if &backend != nil { + apiReconciler.retriveParentAPIsAndUpdateOwnerReferene(ctx, &backend.Backend) + } + } + } + if apiState.SandGRPCRoute != nil { + for _, backend := range apiState.SandGRPCRoute.BackendMapping { + if &backend != nil { + apiReconciler.retriveParentAPIsAndUpdateOwnerReferene(ctx, &backend.Backend) + } + } + } for _, backendJwt := range apiState.BackendJWTMapping { apiReconciler.retriveParentAPIsAndUpdateOwnerReferene(ctx, &backendJwt) } @@ -1142,6 +1265,16 @@ func (apiReconciler *APIReconciler) retriveParentAPIsAndUpdateOwnerReferene(ctx } requests = apiReconciler.getAPIForGQLRoute(ctx, &gqlRoute) apiReconciler.handleOwnerReference(ctx, &gqlRoute, &requests) + case *gwapiv1a2.GRPCRoute: + var grpcRoute gwapiv1a2.GRPCRoute + namespaceName := types.NamespacedName{ + Name: string(obj.GetName()), + Namespace: string(obj.GetNamespace()), + } + if err := apiReconciler.client.Get(ctx, namespaceName, &grpcRoute); err != nil { + loggers.LoggerAPKOperator.Errorf("Unexpected error occured while loading the cr object from cluster %+v", err) + return + } default: loggers.LoggerAPKOperator.Errorf("Unexpected type found while processing owner reference %+v", obj) } @@ -1219,6 +1352,41 @@ func (apiReconciler *APIReconciler) getAPIForHTTPRoute(ctx context.Context, obj return requests } +func (apiReconciler *APIReconciler) getAPIForGRPCRoute(ctx context.Context, obj k8client.Object) []reconcile.Request { + grpcRoute, ok := obj.(*gwapiv1a2.GRPCRoute) + if !ok { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2622, logging.TRIVIAL, "Unexpected object type, bypassing reconciliation: %v", grpcRoute)) + return []reconcile.Request{} + } + + apiList := &dpv1alpha2.APIList{} + + if err := apiReconciler.client.List(ctx, apiList, &k8client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(grpcRouteAPIIndex, utils.NamespacedName(grpcRoute).String()), + }); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2623, logging.CRITICAL, "Unable to find associated APIs: %s", utils.NamespacedName(grpcRoute).String())) + return []reconcile.Request{} + } + + if len(apiList.Items) == 0 { + loggers.LoggerAPKOperator.Debugf("APIs for GRPCRoute not found: %s", utils.NamespacedName(grpcRoute).String()) + return []reconcile.Request{} + } + + requests := []reconcile.Request{} + for _, api := range apiList.Items { + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: api.Name, + Namespace: api.Namespace}, + } + requests = append(requests, req) + loggers.LoggerAPKOperator.Infof("Adding reconcile request for API: %s/%s with API UUID: %v", api.Namespace, api.Name, + string(api.ObjectMeta.UID)) + } + return requests +} + // getAPIsForConfigMap triggers the API controller reconcile method based on the changes detected // in configMap resources. func (apiReconciler *APIReconciler) getAPIsForConfigMap(ctx context.Context, obj k8client.Object) []reconcile.Request { @@ -1656,6 +1824,40 @@ func addIndexes(ctx context.Context, mgr manager.Manager) error { }); err != nil { return err } + if err := mgr.GetFieldIndexer().IndexField(ctx, &dpv1alpha2.API{}, grpcRouteAPIIndex, + func(rawObj k8client.Object) []string { + //check Spec.Kind + api := rawObj.(*dpv1alpha2.API) + if api.Spec.APIType != "GRPC" { + return nil + } + var grpcRoutes []string + if len(api.Spec.Production) > 0 { + for _, ref := range api.Spec.Production[0].RouteRefs { + if ref != "" { + grpcRoutes = append(grpcRoutes, + types.NamespacedName{ + Namespace: api.Namespace, + Name: ref, + }.String()) + } + } + } + if len(api.Spec.Sandbox) > 0 { + for _, ref := range api.Spec.Sandbox[0].RouteRefs { + if ref != "" { + grpcRoutes = append(grpcRoutes, + types.NamespacedName{ + Namespace: api.Namespace, + Name: ref, + }.String()) + } + } + } + return grpcRoutes + }); err != nil { + return err + } if err := mgr.GetFieldIndexer().IndexField(ctx, &dpv1alpha2.API{}, configMapAPIDefinition, func(rawObj k8client.Object) []string { diff --git a/adapter/internal/operator/synchronizer/api_state.go b/adapter/internal/operator/synchronizer/api_state.go index 10e50c223..cbf42bc2a 100644 --- a/adapter/internal/operator/synchronizer/api_state.go +++ b/adapter/internal/operator/synchronizer/api_state.go @@ -20,6 +20,7 @@ package synchronizer import ( "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha1" "github.com/wso2/apk/common-go-libs/apis/dp/v1alpha2" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) @@ -32,6 +33,8 @@ type APIState struct { SandHTTPRoute *HTTPRouteState ProdGQLRoute *GQLRouteState SandGQLRoute *GQLRouteState + ProdGRPCRoute *GRPCRouteState + SandGRPCRoute *GRPCRouteState Authentications map[string]v1alpha2.Authentication RateLimitPolicies map[string]v1alpha1.RateLimitPolicy ResourceAuthentications map[string]v1alpha2.Authentication @@ -64,3 +67,13 @@ type GQLRouteState struct { BackendMapping map[string]*v1alpha1.ResolvedBackend Scopes map[string]v1alpha1.Scope } + +// GRPCRouteState holds the state of the deployed grpcRoutes. This state is compared with +// the state of the Kubernetes controller cache to detect updates. +// +k8s:deepcopy-gen=true +type GRPCRouteState struct { + GRPCRouteCombined *gwapiv1a2.GRPCRoute + GRPCRoutePartitions map[string]*gwapiv1a2.GRPCRoute + BackendMapping map[string]*v1alpha1.ResolvedBackend + Scopes map[string]v1alpha1.Scope +} diff --git a/common-go-libs/apis/dp/v1alpha2/api_types.go b/common-go-libs/apis/dp/v1alpha2/api_types.go index c5df17cb2..2943fa784 100644 --- a/common-go-libs/apis/dp/v1alpha2/api_types.go +++ b/common-go-libs/apis/dp/v1alpha2/api_types.go @@ -89,7 +89,6 @@ type APISpec struct { // e.g: /pet-store-api/1.0.6 // // +kubectl:validation:MaxLength=232 - // +kubebuilder:validation:Pattern=^[/][a-zA-Z0-9~/_.-]*$ BasePath string `json:"basePath"` // Organization denotes the organization. diff --git a/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml b/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml index 353ad94fa..385c45e7e 100644 --- a/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml +++ b/common-go-libs/config/crd/bases/dp.wso2.com_apis.yaml @@ -257,7 +257,6 @@ spec: type: string basePath: description: 'BasePath denotes the basepath of the API. e.g: /pet-store-api/1.0.6' - pattern: ^[/][a-zA-Z0-9~/_.-]*$ type: string definitionFileRef: description: DefinitionFileRef contains the definition of the API diff --git a/helm-charts/templates/serviceAccount/apk-cluster-role.yaml b/helm-charts/templates/serviceAccount/apk-cluster-role.yaml index ccbb849a5..14f7ec717 100644 --- a/helm-charts/templates/serviceAccount/apk-cluster-role.yaml +++ b/helm-charts/templates/serviceAccount/apk-cluster-role.yaml @@ -24,7 +24,7 @@ rules: resources: ["services","configmaps","secrets"] verbs: ["get","list","watch","update","delete","create"] - apiGroups: ["gateway.networking.k8s.io"] - resources: ["httproutes","gateways","gatewayclasses"] + resources: ["httproutes","gateways","grpcroutes","gatewayclasses"] verbs: ["get","list","watch","update","delete","create"] - apiGroups: [ "gateway.networking.k8s.io" ] resources: [ "gateways/status","gatewayclasses/status","httproutes/status" ] From b63f91a2453266c3e6a92eda9e0e4c93485d50bb Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 29 Jan 2024 15:34:22 +0530 Subject: [PATCH 04/81] c2 --- .../operator/controllers/dp/api_controller.go | 82 ++++++++++++++++++- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index c12cbbce1..f05978e00 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -81,6 +81,8 @@ const ( apiRateLimitResourceIndex = "apiRateLimitResourceIndex" // gatewayHTTPRouteIndex Index for gateway httproutes gatewayHTTPRouteIndex = "gatewayHTTPRouteIndex" + // gatewayGRPCRouteIndex Index for gateway grpcroutes + gatewayGRPCRouteIndex = "gatewayGRPCRouteIndex" // apiAPIPolicyIndex Index for API level apipolicies apiAPIPolicyIndex = "apiAPIPolicyIndex" // apiAPIPolicyResourceIndex Index for resource level apipolicies @@ -95,6 +97,7 @@ const ( secretAuthentication = "secretAuthentication" backendHTTPRouteIndex = "backendHTTPRouteIndex" backendGQLRouteIndex = "backendGQLRouteIndex" + backendGRPCRouteIndex = "backendGRPCRouteIndex" interceptorServiceAPIPolicyIndex = "interceptorServiceAPIPolicyIndex" backendInterceptorServiceIndex = "backendInterceptorServiceIndex" backendJWTAPIPolicyIndex = "backendJWTAPIPolicyIndex" @@ -289,6 +292,7 @@ func (apiReconciler *APIReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, nil } + //TODO there's another error here associated with the deployment of grpcroutes if apiState, err := apiReconciler.resolveAPIRefs(ctx, apiCR); err != nil { loggers.LoggerAPKOperator.Warnf("Error retrieving ref CRs for API in namespace : %s with API UUID : %v, %v", req.NamespacedName.String(), string(apiCR.ObjectMeta.UID), err) @@ -596,9 +600,8 @@ func (apiReconciler *APIReconciler) concatGRPCRoutes(ctx context.Context, grpcRo } grpcRouteState.GRPCRoutePartitions = grpcRoutePartitions backendNamespacedName := types.NamespacedName{ - //TODO: replace with appropriate attributes in the grpcRoute - //Name: string(grpcRouteState.GRPCRouteCombined.Spec.BackendRefs[0].Name), - //Name: "grpc-backend", + //TODO check if this is correct + Name: string(grpcRouteState.GRPCRouteCombined.Spec.Rules[0].BackendRefs[0].BackendRef.Name), Namespace: namespace, } resolvedBackend := utils.GetResolvedBackend(ctx, apiReconciler.client, backendNamespacedName, &api) @@ -1352,6 +1355,9 @@ func (apiReconciler *APIReconciler) getAPIForHTTPRoute(ctx context.Context, obj return requests } +// getAPIForGRPCRoute triggers the API controller reconcile method based on the changes detected +// from GRPCRoute objects. If the changes are done for an API stored in the Operator Data store, +// a new reconcile event will be created and added to the reconcile event queue. func (apiReconciler *APIReconciler) getAPIForGRPCRoute(ctx context.Context, obj k8client.Object) []reconcile.Request { grpcRoute, ok := obj.(*gwapiv1a2.GRPCRoute) if !ok { @@ -1675,15 +1681,30 @@ func (apiReconciler *APIReconciler) getAPIsForBackend(ctx context.Context, obj k return []reconcile.Request{} } + grpcRouteList := &gwapiv1a2.GRPCRouteList{} + if err := apiReconciler.client.List(ctx, grpcRouteList, &k8client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(backendGRPCRouteIndex, utils.NamespacedName(backend).String()), + }); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2625, logging.CRITICAL, "Unable to find associated GRPCRoutes: %s", utils.NamespacedName(backend).String())) + return []reconcile.Request{} + } + if len(httpRouteList.Items) == 0 { loggers.LoggerAPKOperator.Debugf("HTTPRoutes for Backend not found: %s", utils.NamespacedName(backend).String()) } + if len(grpcRouteList.Items) == 0 { + loggers.LoggerAPKOperator.Debugf("GRPCRoutes for Backend not found: %s", utils.NamespacedName(backend).String()) + } requests := []reconcile.Request{} for item := range httpRouteList.Items { httpRoute := httpRouteList.Items[item] requests = append(requests, apiReconciler.getAPIForHTTPRoute(ctx, &httpRoute)...) } + for item := range grpcRouteList.Items { + grpcRoute := grpcRouteList.Items[item] + requests = append(requests, apiReconciler.getAPIForGRPCRoute(ctx, &grpcRoute)...) + } gqlRouteList := &dpv1alpha2.GQLRouteList{} if err := apiReconciler.client.List(ctx, gqlRouteList, &k8client.ListOptions{ @@ -1738,17 +1759,33 @@ func (apiReconciler *APIReconciler) getAPIsForGateway(ctx context.Context, obj k loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2625, logging.CRITICAL, "Unable to find associated HTTPRoutes: %s", utils.NamespacedName(gateway).String())) return []reconcile.Request{} } + grpcRouteList := &gwapiv1a2.GRPCRouteList{} + if err := apiReconciler.client.List(ctx, grpcRouteList, &k8client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(gatewayGRPCRouteIndex, utils.NamespacedName(gateway).String()), + }); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2625, logging.CRITICAL, "Unable to find associated GRPCRoutes: %s", utils.NamespacedName(gateway).String())) + return []reconcile.Request{} + } if len(httpRouteList.Items) == 0 { loggers.LoggerAPKOperator.Debugf("HTTPRoutes for Gateway not found: %s", utils.NamespacedName(gateway).String()) return []reconcile.Request{} } + if len(grpcRouteList.Items) == 0 { + loggers.LoggerAPKOperator.Debugf("GRPCRoutes for Gateway not found: %s", utils.NamespacedName(gateway).String()) + return []reconcile.Request{} + } requests := []reconcile.Request{} for item := range httpRouteList.Items { httpRoute := httpRouteList.Items[item] requests = append(requests, apiReconciler.getAPIForHTTPRoute(ctx, &httpRoute)...) } + for item := range grpcRouteList.Items { + grpcRoute := grpcRouteList.Items[item] + requests = append(requests, apiReconciler.getAPIForGRPCRoute(ctx, &grpcRoute)...) + } + return requests } @@ -1936,6 +1973,28 @@ func addIndexes(ctx context.Context, mgr manager.Manager) error { return err } + // Backend to GRPCRoute indexer + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.GRPCRoute{}, backendGRPCRouteIndex, + func(rawObj k8client.Object) []string { + grpcRoute := rawObj.(*gwapiv1a2.GRPCRoute) + var backends []string + for _, rule := range grpcRoute.Spec.Rules { + for _, backendRef := range rule.BackendRefs { + //TODO fix this if condition + if backendRef.Kind != nil && *backendRef.Kind == constants.KindBackend { + backends = append(backends, types.NamespacedName{ + Namespace: utils.GetNamespace(backendRef.Namespace, + grpcRoute.ObjectMeta.Namespace), + Name: string(backendRef.Name), + }.String()) + } + } + } + return backends + }); err != nil { + return err + } + // Backend to GQLRoute indexer if err := mgr.GetFieldIndexer().IndexField(ctx, &dpv1alpha2.GQLRoute{}, backendGQLRouteIndex, func(rawObj k8client.Object) []string { @@ -1973,6 +2032,23 @@ func addIndexes(ctx context.Context, mgr manager.Manager) error { return err } + //Gateway to GRPCRoute indexer + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.GRPCRoute{}, gatewayGRPCRouteIndex, + func(rawObj k8client.Object) []string { + grpcRoute := rawObj.(*gwapiv1a2.GRPCRoute) + var gateways []string + for _, parentRef := range grpcRoute.Spec.ParentRefs { + gateways = append(gateways, types.NamespacedName{ + Namespace: utils.GetNamespace(parentRef.Namespace, + grpcRoute.Namespace), + Name: string(parentRef.Name), + }.String()) + } + return gateways + }); err != nil { + return err + } + // ConfigMap to Backend indexer if err := mgr.GetFieldIndexer().IndexField(ctx, &dpv1alpha1.Backend{}, configMapBackend, func(rawObj k8client.Object) []string { From 1f62729552c4a7693c77c90b10712d5ffed9ec74 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 30 Jan 2024 11:51:39 +0530 Subject: [PATCH 05/81] reconciled grpc backend --- adapter/internal/operator/controllers/dp/api_controller.go | 1 - 1 file changed, 1 deletion(-) diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index f05978e00..8d27b83cc 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -1980,7 +1980,6 @@ func addIndexes(ctx context.Context, mgr manager.Manager) error { var backends []string for _, rule := range grpcRoute.Spec.Rules { for _, backendRef := range rule.BackendRefs { - //TODO fix this if condition if backendRef.Kind != nil && *backendRef.Kind == constants.KindBackend { backends = append(backends, types.NamespacedName{ Namespace: utils.GetNamespace(backendRef.Namespace, From ea2814e62c114f19fdb9f012ca6d9ba8f75ab9b1 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 31 Jan 2024 10:51:12 +0530 Subject: [PATCH 06/81] add function SetInfoGRPCRouteCR --- .../oasparser/model/adapter_internal_api.go | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index a5318d66a..c3245d7eb 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "net/url" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "strconv" "strings" "time" @@ -42,6 +43,7 @@ import ( // AdapterInternalAPI represents the object structure holding the information related to the // adapter internal representation. The values are populated from the operator. The pathItem level information is represented // by the resources array which contains the Resource entries. +// TODO task 1 type AdapterInternalAPI struct { UUID string apiType string @@ -940,6 +942,164 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGQLRouteCR(gqlRoute *dpv1al return nil } +// SetInfoGRPCRouteCR populates resources and endpoints of adapterInternalAPI. httpRoute.Spec.Rules.Matches +// are used to create resources and httpRoute.Spec.Rules.BackendRefs are used to create EndpointClusters. +func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwapiv1a2.GRPCRoute, resourceParams ResourceParams) error { + var resources []*Resource + outputAuthScheme := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.AuthSchemes))) + outputAPIPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.APIPolicies))) + outputRatelimitPolicy := utils.TieBreaker(utils.GetPtrSlice(maps.Values(resourceParams.RateLimitPolicies))) + + disableScopes := true + config := config.ReadConfigs() + + var authScheme *dpv1alpha2.Authentication + if outputAuthScheme != nil { + authScheme = *outputAuthScheme + } + var apiPolicy *dpv1alpha2.APIPolicy + if outputAPIPolicy != nil { + apiPolicy = *outputAPIPolicy + } + var ratelimitPolicy *dpv1alpha1.RateLimitPolicy + if outputRatelimitPolicy != nil { + ratelimitPolicy = *outputRatelimitPolicy + } + + //We are only supporting one backend for now + //TODO fix this later (Dineth) + backend := grpcRoute.Spec.Rules[0].BackendRefs[0] + backendName := types.NamespacedName{ + Name: string(backend.Name), + Namespace: utils.GetNamespace(backend.Namespace, grpcRoute.Namespace), + } + resolvedBackend, ok := resourceParams.BackendMapping[backendName.String()] + if ok { + endpointConfig := &EndpointConfig{} + if resolvedBackend.CircuitBreaker != nil { + endpointConfig.CircuitBreakers = &CircuitBreakers{ + MaxConnections: int32(resolvedBackend.CircuitBreaker.MaxConnections), + MaxRequests: int32(resolvedBackend.CircuitBreaker.MaxRequests), + MaxPendingRequests: int32(resolvedBackend.CircuitBreaker.MaxPendingRequests), + MaxRetries: int32(resolvedBackend.CircuitBreaker.MaxRetries), + MaxConnectionPools: int32(resolvedBackend.CircuitBreaker.MaxConnectionPools), + } + } + if resolvedBackend.Timeout != nil { + endpointConfig.TimeoutInMillis = resolvedBackend.Timeout.UpstreamResponseTimeout * 1000 + endpointConfig.IdleTimeoutInSeconds = resolvedBackend.Timeout.DownstreamRequestIdleTimeout + } + if resolvedBackend.Retry != nil { + statusCodes := config.Envoy.Upstream.Retry.StatusCodes + if len(resolvedBackend.Retry.StatusCodes) > 0 { + statusCodes = resolvedBackend.Retry.StatusCodes + } + endpointConfig.RetryConfig = &RetryConfig{ + Count: int32(resolvedBackend.Retry.Count), + StatusCodes: statusCodes, + BaseIntervalInMillis: int32(resolvedBackend.Retry.BaseIntervalMillis), + } + } + adapterInternalAPI.Endpoints = &EndpointCluster{ + Endpoints: GetEndpoints(backendName, resourceParams.BackendMapping), + Config: endpointConfig, + } + if resolvedBackend.HealthCheck != nil { + adapterInternalAPI.Endpoints.HealthCheck = &HealthCheck{ + Interval: resolvedBackend.HealthCheck.Interval, + Timeout: resolvedBackend.HealthCheck.Timeout, + UnhealthyThreshold: resolvedBackend.HealthCheck.UnhealthyThreshold, + HealthyThreshold: resolvedBackend.HealthCheck.HealthyThreshold, + } + } + + var securityConfig []EndpointSecurity + switch resolvedBackend.Security.Type { + case "Basic": + securityConfig = append(securityConfig, EndpointSecurity{ + Password: string(resolvedBackend.Security.Basic.Password), + Username: string(resolvedBackend.Security.Basic.Username), + Type: string(resolvedBackend.Security.Type), + Enabled: true, + }) + } + adapterInternalAPI.EndpointSecurity = utils.GetPtrSlice(securityConfig) + } else { + return fmt.Errorf("backend: %s has not been resolved", backendName) + } + + for _, rule := range grpcRoute.Spec.Rules { + var policies = OperationPolicies{} + resourceAuthScheme := authScheme + 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) + } + } + 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 != 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) + } + } + } + resourceAuthScheme = concatAuthSchemes(resourceAuthScheme, nil) + resourceRatelimitPolicy = concatRateLimitPolicies(resourceRatelimitPolicy, nil) + + loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", adapterInternalAPI.UUID) + apiAuth := getSecurity(resourceAuthScheme) + + for _, match := range rule.Matches { + resourcePath := *match.Method.Service + resource := &Resource{path: resourcePath, + methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Method.Type), policies: policies, + auth: apiAuth, RateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, + iD: uuid.New().String(), + } + resources = append(resources, resource) + } + } + + ratelimitPolicy = concatRateLimitPolicies(ratelimitPolicy, nil) + apiPolicy = concatAPIPolicies(apiPolicy, nil) + authScheme = concatAuthSchemes(authScheme, nil) + + adapterInternalAPI.RateLimitPolicy = parseRateLimitPolicyToInternal(ratelimitPolicy) + adapterInternalAPI.resources = resources + adapterInternalAPI.xWso2Cors = getCorsConfigFromAPIPolicy(apiPolicy) + if authScheme.Spec.Override != nil && authScheme.Spec.Override.Disabled != nil { + adapterInternalAPI.disableAuthentications = *authScheme.Spec.Override.Disabled + } + adapterInternalAPI.disableScopes = disableScopes + return nil +} + func (endpoint *Endpoint) validateEndpoint() error { if endpoint.Port == 0 || endpoint.Port > 65535 { return errors.New("endpoint port value should be between 0 and 65535") From bdcbf93e1859f056190807cdecf91ed444b0a9f4 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 31 Jan 2024 11:38:31 +0530 Subject: [PATCH 07/81] duplicate methods from GQL to grpc_api.go --- .../operator/synchronizer/grpc_api.go | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 adapter/internal/operator/synchronizer/grpc_api.go diff --git a/adapter/internal/operator/synchronizer/grpc_api.go b/adapter/internal/operator/synchronizer/grpc_api.go new file mode 100644 index 000000000..114328cbb --- /dev/null +++ b/adapter/internal/operator/synchronizer/grpc_api.go @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +//Todo task 3 dupliate for grpc +package synchronizer + +import ( + "errors" + "fmt" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + "github.com/wso2/apk/adapter/config" + "github.com/wso2/apk/adapter/internal/dataholder" + "github.com/wso2/apk/adapter/internal/discovery/xds" + "github.com/wso2/apk/adapter/internal/discovery/xds/common" + "github.com/wso2/apk/adapter/internal/loggers" + "github.com/wso2/apk/adapter/internal/oasparser/model" + "github.com/wso2/apk/adapter/pkg/logging" + "k8s.io/apimachinery/pkg/types" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// generateGRPCAdapterInternalAPI this will populate a AdapterInternalAPI representation for an GRPCRoute +func generateGRPCAdapterInternalAPI(apiState APIState, grpcRoute *GRPCRouteState, envType string) (*model.AdapterInternalAPI, map[string]struct{}, error) { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetIsDefaultVersion(apiState.APIDefinition.Spec.IsDefaultVersion) + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + adapterInternalAPI.SetAPIDefinitionFile(apiState.APIDefinitionFile) + adapterInternalAPI.SetAPIDefinitionEndpoint(apiState.APIDefinition.Spec.DefinitionPath) + adapterInternalAPI.SetSubscriptionValidation(apiState.SubscriptionValidation) + adapterInternalAPI.EnvType = envType + + environment := apiState.APIDefinition.Spec.Environment + if environment == "" { + conf := config.ReadConfigs() + environment = conf.Adapter.Environment + } + adapterInternalAPI.SetEnvironment(environment) + + resourceParams := model.ResourceParams{ + AuthSchemes: apiState.Authentications, + ResourceAuthSchemes: apiState.ResourceAuthentications, + BackendMapping: grpcRoute.BackendMapping, + APIPolicies: apiState.APIPolicies, + ResourceAPIPolicies: apiState.ResourceAPIPolicies, + ResourceScopes: grpcRoute.Scopes, + InterceptorServiceMapping: apiState.InterceptorServiceMapping, + BackendJWTMapping: apiState.BackendJWTMapping, + RateLimitPolicies: apiState.RateLimitPolicies, + ResourceRateLimitPolicies: apiState.ResourceRateLimitPolicies, + } + if err := adapterInternalAPI.SetInfoGRPCRouteCR(grpcRoute.GRPCRouteCombined, resourceParams); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting GRPCRoute CR info to adapterInternalAPI. %v", err)) + return nil, nil, err + } + if err := adapterInternalAPI.Validate(); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error validating adapterInternalAPI intermediate representation. %v", err)) + return nil, nil, err + } + vHosts := getVhostsForGRPCAPI(grpcRoute.GRPCRouteCombined) + labels := getLabelsForGRPCAPI(grpcRoute.GRPCRouteCombined) + listeners, relativeSectionNames := getListenersForGRPCAPI(grpcRoute.GRPCRouteCombined, adapterInternalAPI.UUID) + // We don't have a use case where a perticular API's two different grpc routes refer to two different gateway. Hence get the first listener name for the list for processing. + if len(listeners) == 0 || len(relativeSectionNames) == 0 { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MINOR, "Failed to find a matching listener for grpc route: %v. ", + grpcRoute.GRPCRouteCombined.Name)) + return nil, nil, errors.New("failed to find matching listener name for the provided grpc route") + } + + updatedLabelsMap := make(map[string]struct{}) + listenerName := listeners[0] + sectionName := relativeSectionNames[0] + if len(listeners) != 0 { + updatedLabels, err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MAJOR, "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", + adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) + return nil, nil, err + } + for newLabel := range updatedLabels { + updatedLabelsMap[newLabel] = struct{}{} + } + } + + return &adapterInternalAPI, updatedLabelsMap, nil +} + +// getVhostForAPI returns the vHosts related to an API. +func getVhostsForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute) []string { + var vHosts []string + for _, hostName := range grpcRoute.Spec.Hostnames { + vHosts = append(vHosts, string(hostName)) + } + fmt.Println("vhosts size: ", len(vHosts)) + return vHosts +} + +// getLabelsForAPI returns the labels related to an API. +func getLabelsForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute) []string { + var labels []string + var err error + for _, parentRef := range grpcRoute.Spec.ParentRefs { + err = xds.SanitizeGateway(string(parentRef.Name), false) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2653, logging.CRITICAL, "Gateway Label is invalid: %s", string(parentRef.Name))) + } else { + labels = append(labels, string(parentRef.Name)) + } + } + return labels +} + +// getListenersForGRPCAPI returns the listeners related to an API. +func getListenersForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute, apiUUID string) ([]string, []string) { + var listeners []string + var sectionNames []string + for _, parentRef := range grpcRoute.Spec.ParentRefs { + namespace := grpcRoute.GetNamespace() + if parentRef.Namespace != nil && *parentRef.Namespace != "" { + namespace = string(*parentRef.Namespace) + } + gateway, found := dataholder.GetGatewayMap()[types.NamespacedName{ + Namespace: namespace, + Name: string(parentRef.Name), + }.String()] + if found { + // find the matching listener + matchedListener, listenerFound := common.FindElement(gateway.Spec.Listeners, func(listener gwapiv1b1.Listener) bool { + if string(listener.Name) == string(*parentRef.SectionName) { + return true + } + return false + }) + if listenerFound { + sectionNames = append(sectionNames, string(matchedListener.Name)) + listeners = append(listeners, common.GetEnvoyListenerName(string(matchedListener.Protocol), uint32(matchedListener.Port))) + continue + } + } + loggers.LoggerAPKOperator.Errorf("Failed to find matching listeners for the grpcroute: %+v", grpcRoute.Name) + } + return listeners, sectionNames +} + +func deleteGRPCAPIFromEnv(grpcRoute *gwapiv1a2.GRPCRoute, apiState APIState) error { + labels := getLabelsForGRPCAPI(grpcRoute) + org := apiState.APIDefinition.Spec.Organization + uuid := string(apiState.APIDefinition.ObjectMeta.UID) + return xds.DeleteAPICREvent(labels, uuid, org) +} + +// undeployGRPCAPIInGateway undeploys the related API in CREATE and UPDATE events. +func undeployGRPCAPIInGateway(apiState APIState) error { + var err error + if apiState.ProdGRPCRoute != nil { + err = deleteGRPCAPIFromEnv(apiState.ProdGRPCRoute.GRPCRouteCombined, apiState) + } + if err != nil { + loggers.LoggerXds.ErrorC(logging.PrintError(logging.Error2630, logging.MAJOR, "Error undeploying prod grpcRoute of API : %v in Organization %v from environments."+ + " Hence not checking on deleting the sand grpcRoute of the API", string(apiState.APIDefinition.ObjectMeta.UID), apiState.APIDefinition.Spec.Organization)) + return err + } + if apiState.SandGRPCRoute != nil { + err = deleteGRPCAPIFromEnv(apiState.SandGRPCRoute.GRPCRouteCombined, apiState) + } + return err +} From 9e85a0386c61d9e32c8058adfa4b204b564c38d8 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 31 Jan 2024 11:38:55 +0530 Subject: [PATCH 08/81] minor changes --- adapter/internal/oasparser/model/adapter_internal_api.go | 1 + adapter/internal/operator/controllers/dp/api_controller.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index c3245d7eb..feb4e13e5 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -1076,6 +1076,7 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap apiAuth := getSecurity(resourceAuthScheme) for _, match := range rule.Matches { + //TODO double check this resourcePath := *match.Method.Service resource := &Resource{path: resourcePath, methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Method.Type), policies: policies, diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index 8d27b83cc..ce7846ffb 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -292,7 +292,6 @@ func (apiReconciler *APIReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, nil } - //TODO there's another error here associated with the deployment of grpcroutes if apiState, err := apiReconciler.resolveAPIRefs(ctx, apiCR); err != nil { loggers.LoggerAPKOperator.Warnf("Error retrieving ref CRs for API in namespace : %s with API UUID : %v, %v", req.NamespacedName.String(), string(apiCR.ObjectMeta.UID), err) From 7186f273aa3443d48687c8f7df83f45aabb5b3ba Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 31 Jan 2024 11:44:05 +0530 Subject: [PATCH 09/81] changed synchronizer.go. Builds without errors --- .../operator/synchronizer/synchronizer.go | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/adapter/internal/operator/synchronizer/synchronizer.go b/adapter/internal/operator/synchronizer/synchronizer.go index a61aa181c..34aeddfc1 100644 --- a/adapter/internal/operator/synchronizer/synchronizer.go +++ b/adapter/internal/operator/synchronizer/synchronizer.go @@ -117,12 +117,16 @@ func undeployAPIInGateway(apiEvent *APIEvent) error { if apiState.APIDefinition.Spec.APIType == "GraphQL" { err = undeployGQLAPIInGateway(apiState) } + if apiState.APIDefinition.Spec.APIType == "GRPC" { + return undeployGRPCAPIInGateway(apiState) + } if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2629, logging.CRITICAL, "API deployment failed for %s event : %v, %v", apiEvent.EventType, apiState.APIDefinition.Name, err)) } else if config.ReadConfigs().PartitionServer.Enabled { paritionCh <- apiEvent } + return nil } @@ -199,6 +203,44 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv } } } + if apiState.APIDefinition.Spec.APIType == "GRPC" { + if apiState.ProdGRPCRoute == nil { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Production) + } + if apiState.SandGRPCRoute == nil { + var adapterInternalAPI model.AdapterInternalAPI + adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) + xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Sandbox) + } + if apiState.ProdGRPCRoute != nil { + _, updatedLabels, err := generateGRPCAdapterInternalAPI(apiState, apiState.ProdGRPCRoute, constants.Production) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.CRITICAL, + "Error deploying prod grpcRoute of API : %v in Organization %v from environments %v. Error: %v", + string(apiState.APIDefinition.Spec.APIName), apiState.APIDefinition.Spec.Organization, + getLabelsForGRPCAPI(apiState.ProdGRPCRoute.GRPCRouteCombined), err)) + continue + } + for label := range updatedLabels { + updatedLabelsMap[label] = struct{}{} + } + } + if apiState.SandGRPCRoute != nil { + _, updatedLabels, err := generateGRPCAdapterInternalAPI(apiState, apiState.SandGRPCRoute, constants.Sandbox) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.CRITICAL, + "Error deploying sand grpcRoute of API : %v in Organization %v from environments %v. Error: %v", + string(apiState.APIDefinition.Spec.APIName), apiState.APIDefinition.Spec.Organization, + getLabelsForGRPCAPI(apiState.SandGRPCRoute.GRPCRouteCombined), err)) + continue + } + for label := range updatedLabels { + updatedLabelsMap[label] = struct{}{} + } + } + } updatedAPIs = append(updatedAPIs, utils.NamespacedName(apiState.APIDefinition)) } @@ -254,6 +296,13 @@ func SendEventToPartitionServer() { for _, hostName := range httpRoute.HTTPRouteCombined.Spec.Hostnames { hostNames = append(hostNames, string(hostName)) } + grpcRoute := api.ProdGRPCRoute + if grpcRoute == nil { + grpcRoute = api.SandGRPCRoute + } + for _, hostName := range grpcRoute.GRPCRouteCombined.Spec.Hostnames { + hostNames = append(hostNames, string(hostName)) + } data := PartitionEvent{ EventType: eventType, BasePath: basePath, From db8d739f58aa2c0eabfbccc118e4d3c2f79de960 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 2 Feb 2024 14:35:45 +0530 Subject: [PATCH 10/81] so far everything works --- .../internal/oasparser/constants/constants.go | 1 + .../envoyconf/routes_with_clusters.go | 44 ++++++++ .../oasparser/model/adapter_internal_api.go | 9 +- adapter/internal/oasparser/model/common.go | 2 +- .../config/crd/bases/dp.wso2.com_apis.yaml | 5 +- .../operator/synchronizer/data_store.go | 102 ++++++++++++++++++ .../config/crd/bases/dp.wso2.com_apis.yaml | 1 + .../serviceAccount/apk-cluster-role.yaml | 9 ++ 8 files changed, 168 insertions(+), 5 deletions(-) diff --git a/adapter/internal/oasparser/constants/constants.go b/adapter/internal/oasparser/constants/constants.go index df863dc5a..3893d473a 100644 --- a/adapter/internal/oasparser/constants/constants.go +++ b/adapter/internal/oasparser/constants/constants.go @@ -98,6 +98,7 @@ const ( SOAP string = "SOAP" WS string = "WS" GRAPHQL string = "GraphQL" + GRPC string = "GRPC" WEBHOOK string = "WEBHOOK" SSE string = "SSE" Prototyped string = "prototyped" diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index acb0edb51..05a7ac3c9 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -197,6 +197,50 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte } return routes, clusters, endpoints, nil } + if adapterInternalAPI.GetAPIType() == constants.GRPC { + basePath := strings.TrimSuffix(adapterInternalAPI.Endpoints.Endpoints[0].Basepath, "/") + + clusterName := getClusterName(adapterInternalAPI.Endpoints.EndpointPrefix, organizationID, vHost, + adapterInternalAPI.GetTitle(), apiVersion, "") + cluster, address, err := processEndpoints(clusterName, adapterInternalAPI.Endpoints, timeout, basePath) + + if err != nil { + logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2239, logging.MAJOR, + "Error while adding grpc endpoints for %s:%v. %v", apiTitle, apiVersion, err.Error())) + return nil, nil, nil, fmt.Errorf("error while adding grpc endpoints for %s:%v. %v", apiTitle, apiVersion, + err.Error()) + } + clusters = append(clusters, cluster) + endpoints = append(endpoints, address...) + + // The current code requires to create policy for all routes to support backend endpoint. + policyParameters := make(map[string]interface{}) + policyParameters[constants.RewritePathType] = gwapiv1b1.FullPathHTTPPathModifier + policyParameters[constants.IncludeQueryParams] = true + policyParameters[constants.RewritePathResourcePath] = basePath + var policies = model.OperationPolicies{ + Request: []model.Policy{ + { + PolicyName: string(gwapiv1b1.HTTPRouteFilterURLRewrite), + Action: constants.ActionRewritePath, + Parameters: policyParameters, + }, + }, + } + + grpcop := model.NewOperationWithPolicies("POST", policies) + resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{grpcop}, "", adapterInternalAPI.Endpoints, true, gwapiv1b1.PathMatchExact) + routesP, err := createRoutes(genRouteCreateParams(&adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, + nil, organizationID, false, false)) + if err != nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, + "Error while creating routes for GRPC API %s %s Error: %s", adapterInternalAPI.GetTitle(), + adapterInternalAPI.GetVersion(), err.Error())) + return nil, nil, nil, fmt.Errorf("error while creating routes. %v", err) + } + routes = append(routes, routesP...) + return routes, clusters, endpoints, nil + } for _, resource := range adapterInternalAPI.GetResources() { var clusterName string resourcePath := resource.GetPath() diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index feb4e13e5..2c94f6715 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -1030,10 +1030,11 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap for _, rule := range grpcRoute.Spec.Rules { var policies = OperationPolicies{} + var endPoints []Endpoint resourceAuthScheme := authScheme resourceRatelimitPolicy := ratelimitPolicy var scopes []string - + //TODO remove policies for _, filter := range rule.Filters { if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindAuthentication { if ref, found := resourceParams.ResourceAuthSchemes[types.NamespacedName{ @@ -1076,13 +1077,17 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap apiAuth := getSecurity(resourceAuthScheme) for _, match := range rule.Matches { - //TODO double check this resourcePath := *match.Method.Service + endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) resource := &Resource{path: resourcePath, methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Method.Type), policies: policies, auth: apiAuth, RateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, iD: uuid.New().String(), } + endpoints := GetEndpoints(backendName, resourceParams.BackendMapping) + resource.endpoints = &EndpointCluster{ + Endpoints: endpoints, + } resources = append(resources, resource) } } diff --git a/adapter/internal/oasparser/model/common.go b/adapter/internal/oasparser/model/common.go index d1d3ba664..8c5c2f54c 100644 --- a/adapter/internal/oasparser/model/common.go +++ b/adapter/internal/oasparser/model/common.go @@ -108,7 +108,7 @@ func getHostandBasepathandPort(apiType string, rawURL string) (*Endpoint, error) rawURL = strings.Trim(rawURL, " ") if !strings.Contains(rawURL, "://") { - if apiType == constants.REST || apiType == constants.GRAPHQL { + if apiType == constants.REST || apiType == constants.GRAPHQL || apiType == constants.GRPC { rawURL = "http://" + rawURL } else if apiType == constants.WS { rawURL = "ws://" + rawURL diff --git a/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml b/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml index fc4e0a2a9..c2a145323 100644 --- a/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml +++ b/adapter/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml @@ -72,7 +72,7 @@ spec: type: array apiType: description: APIType denotes the type of the API. Possible values - could be REST, GraphQL, Async + could be REST, GraphQL, GRPC Async enum: - REST type: string @@ -244,10 +244,11 @@ spec: type: array apiType: description: APIType denotes the type of the API. Possible values - could be REST, GraphQL, Async + could be REST, GraphQL, GRPC Async enum: - REST - GraphQL + - GRPC type: string apiVersion: description: APIVersion is the version number of the API. diff --git a/adapter/internal/operator/synchronizer/data_store.go b/adapter/internal/operator/synchronizer/data_store.go index 9b3afab1d..8c2c23d4b 100644 --- a/adapter/internal/operator/synchronizer/data_store.go +++ b/adapter/internal/operator/synchronizer/data_store.go @@ -18,6 +18,7 @@ package synchronizer import ( + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "sync" "github.com/wso2/apk/adapter/internal/loggers" @@ -115,6 +116,23 @@ func (ods *OperatorDataStore) processAPIState(apiNamespacedName types.Namespaced } cachedAPI.ProdGQLRoute = nil } + if apiState.ProdGRPCRoute != nil { + if cachedAPI.ProdGRPCRoute == nil { + cachedAPI.ProdGRPCRoute = apiState.ProdGRPCRoute + updated = true + events = append(events, "Production") + } else if routeEvents, routesUpdated := updateGRPCRoute(apiState.ProdGRPCRoute, cachedAPI.ProdGRPCRoute, + "Production"); routesUpdated { + updated = true + events = append(events, routeEvents...) + } + } else { + if cachedAPI.ProdGRPCRoute != nil { + updated = true + events = append(events, "Production") + } + cachedAPI.ProdGRPCRoute = nil + } if apiState.SandHTTPRoute != nil { if cachedAPI.SandHTTPRoute == nil { cachedAPI.SandHTTPRoute = apiState.SandHTTPRoute @@ -147,6 +165,22 @@ func (ods *OperatorDataStore) processAPIState(apiNamespacedName types.Namespaced } cachedAPI.SandGQLRoute = nil } + if apiState.SandGRPCRoute != nil { + if cachedAPI.SandGRPCRoute == nil { + cachedAPI.SandGRPCRoute = apiState.SandGRPCRoute + updated = true + events = append(events, "Sandbox") + } else if routeEvents, routesUpdated := updateGRPCRoute(apiState.SandGRPCRoute, cachedAPI.SandGRPCRoute, "Sandbox"); routesUpdated { + updated = true + events = append(events, routeEvents...) + } + } else { + if cachedAPI.SandGRPCRoute != nil { + updated = true + events = append(events, "Sandbox") + } + cachedAPI.SandGRPCRoute = nil + } if len(apiState.Authentications) != len(cachedAPI.Authentications) { cachedAPI.Authentications = apiState.Authentications cachedAPI.MutualSSL = apiState.MutualSSL @@ -443,6 +477,61 @@ func updateGQLRoute(gqlRoute *GQLRouteState, cachedGQLRoute *GQLRouteState, endp return events, updated } +func updateGRPCRoute(grpcRoute *GRPCRouteState, cachedGRPCRoute *GRPCRouteState, endpointType string) ([]string, bool) { + var updated bool + events := []string{} + if cachedGRPCRoute.GRPCRouteCombined == nil || !isEqualGRPCRoutes(cachedGRPCRoute.GRPCRoutePartitions, grpcRoute.GRPCRoutePartitions) { + cachedGRPCRoute.GRPCRouteCombined = grpcRoute.GRPCRouteCombined + cachedGRPCRoute.GRPCRoutePartitions = grpcRoute.GRPCRoutePartitions + updated = true + events = append(events, endpointType+" Endpoint") + } + + if len(grpcRoute.Scopes) != len(cachedGRPCRoute.Scopes) { + cachedGRPCRoute.Scopes = grpcRoute.Scopes + updated = true + events = append(events, "Resource Scopes") + } else { + for key, scope := range grpcRoute.Scopes { + if existingScope, found := cachedGRPCRoute.Scopes[key]; found { + if scope.UID != existingScope.UID || scope.Generation > existingScope.Generation { + cachedGRPCRoute.Scopes = grpcRoute.Scopes + updated = true + events = append(events, "Resource Scopes") + break + } + } else { + cachedGRPCRoute.Scopes = grpcRoute.Scopes + updated = true + events = append(events, "Resource Scopes") + break + } + } + } + + if len(grpcRoute.BackendMapping) != len(cachedGRPCRoute.BackendMapping) { + cachedGRPCRoute.BackendMapping = grpcRoute.BackendMapping + updated = true + events = append(events, endpointType+" Backend Properties") + } else { + for key, backend := range grpcRoute.BackendMapping { + if existingBackend, found := cachedGRPCRoute.BackendMapping[key]; found { + if backend.Backend.UID != existingBackend.Backend.UID || backend.Backend.Generation > existingBackend.Backend.Generation { + cachedGRPCRoute.BackendMapping = grpcRoute.BackendMapping + updated = true + events = append(events, endpointType+" Backend Properties") + break + } + } else { + cachedGRPCRoute.BackendMapping = grpcRoute.BackendMapping + updated = true + events = append(events, endpointType+" Backend Properties") + break + } + } + } + return events, updated +} func isEqualHTTPRoutes(cachedHTTPRoutes, newHTTPRoutes map[string]*gwapiv1b1.HTTPRoute) bool { for key, cachedHTTPRoute := range cachedHTTPRoutes { if newHTTPRoutes[key] == nil { @@ -469,6 +558,19 @@ func isEqualGQLRoutes(cachedGQLRoutes, newGQLRoutes map[string]*dpv1alpha2.GQLRo return true } +func isEqualGRPCRoutes(cachedGRPCRoutes, newGRPCRoutes map[string]*gwapiv1a2.GRPCRoute) bool { + for key, cachedGRPCRoute := range cachedGRPCRoutes { + if newGRPCRoutes[key] == nil { + return false + } + if newGRPCRoutes[key].UID == cachedGRPCRoute.UID && + newGRPCRoutes[key].Generation > cachedGRPCRoute.Generation { + return false + } + } + return true +} + // GetCachedAPI get cached apistate func (ods *OperatorDataStore) GetCachedAPI(apiName types.NamespacedName) (APIState, bool) { if cachedAPI, found := ods.apiStore[apiName]; found { diff --git a/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml b/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml index 304529c66..a674ff1d2 100644 --- a/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml +++ b/common-controller/internal/operator/config/crd/bases/dp.wso2.com_apis.yaml @@ -247,6 +247,7 @@ spec: enum: - REST - GraphQL + - GRPC type: string apiVersion: description: APIVersion is the version number of the API. diff --git a/helm-charts/templates/serviceAccount/apk-cluster-role.yaml b/helm-charts/templates/serviceAccount/apk-cluster-role.yaml index 14f7ec717..428ce7abf 100644 --- a/helm-charts/templates/serviceAccount/apk-cluster-role.yaml +++ b/helm-charts/templates/serviceAccount/apk-cluster-role.yaml @@ -122,6 +122,15 @@ rules: - apiGroups: ["dp.wso2.com"] resources: ["gqlroutes/status"] verbs: ["get","patch","update"] + - apiGroups: ["dp.wso2.com"] + resources: ["grpcroutes"] + verbs: ["get","list","watch","update","delete","create"] + - apiGroups: ["dp.wso2.com"] + resources: ["grpcroutes/finalizers"] + verbs: ["update"] + - apiGroups: ["dp.wso2.com"] + resources: ["grpcroutes/status"] + verbs: ["get","patch","update"] - apiGroups: ["cp.wso2.com"] resources: ["applications"] verbs: ["get","list","watch","update","delete","create"] From 93d1d756225efc9f7795565bac0a0241f0e3f186 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 7 Feb 2024 13:21:44 +0530 Subject: [PATCH 11/81] removed policies --- .../envoyconf/routes_with_clusters.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 05a7ac3c9..48c55cd88 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -213,23 +213,7 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte clusters = append(clusters, cluster) endpoints = append(endpoints, address...) - // The current code requires to create policy for all routes to support backend endpoint. - policyParameters := make(map[string]interface{}) - policyParameters[constants.RewritePathType] = gwapiv1b1.FullPathHTTPPathModifier - policyParameters[constants.IncludeQueryParams] = true - policyParameters[constants.RewritePathResourcePath] = basePath - var policies = model.OperationPolicies{ - Request: []model.Policy{ - { - PolicyName: string(gwapiv1b1.HTTPRouteFilterURLRewrite), - Action: constants.ActionRewritePath, - Parameters: policyParameters, - }, - }, - } - - grpcop := model.NewOperationWithPolicies("POST", policies) - resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{grpcop}, "", adapterInternalAPI.Endpoints, true, gwapiv1b1.PathMatchExact) + resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{}, "", adapterInternalAPI.Endpoints, true, gwapiv1b1.PathMatchExact) routesP, err := createRoutes(genRouteCreateParams(&adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, nil, organizationID, false, false)) if err != nil { From b16758e76652755ec3362c7ab66c61863b7ca3ba Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 8 Feb 2024 08:49:06 +0530 Subject: [PATCH 12/81] enable http2 backend for grpc --- adapter/internal/oasparser/envoyconf/routes_with_clusters.go | 1 + 1 file changed, 1 insertion(+) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 48c55cd88..eeb56dee5 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -202,6 +202,7 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte clusterName := getClusterName(adapterInternalAPI.Endpoints.EndpointPrefix, organizationID, vHost, adapterInternalAPI.GetTitle(), apiVersion, "") + adapterInternalAPI.Endpoints.HTTP2BackendEnabled = true cluster, address, err := processEndpoints(clusterName, adapterInternalAPI.Endpoints, timeout, basePath) if err != nil { From 7e3b2c8e1445fbda96ce5da43bc2c07766852a97 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 19 Feb 2024 14:57:43 +0530 Subject: [PATCH 13/81] remove comment --- adapter/internal/operator/synchronizer/grpc_api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/adapter/internal/operator/synchronizer/grpc_api.go b/adapter/internal/operator/synchronizer/grpc_api.go index 114328cbb..3c0671ed8 100644 --- a/adapter/internal/operator/synchronizer/grpc_api.go +++ b/adapter/internal/operator/synchronizer/grpc_api.go @@ -14,7 +14,6 @@ * limitations under the License. * */ -//Todo task 3 dupliate for grpc package synchronizer import ( From b4a5ba9b00d31c4adbbe69352c96ac88bea5ca32 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 19 Feb 2024 15:26:25 +0530 Subject: [PATCH 14/81] add constants --- .../org/wso2/apk/enforcer/commons/model/ResourceConfig.java | 2 +- .../src/main/java/org/wso2/apk/enforcer/api/APIFactory.java | 6 ++++++ .../java/org/wso2/apk/enforcer/constants/APIConstants.java | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java b/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java index 2d938a80e..44428a76b 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java +++ b/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java @@ -41,7 +41,7 @@ public class ResourceConfig { */ public enum HttpMethods { GET("get"), POST("post"), PUT("put"), DELETE("delete"), HEAD("head"), - PATCH("patch"), OPTIONS("options"), QUERY("query"), MUTATION("mutation"), + PATCH("patch"), OPTIONS("options"), QUERY("query"), MUTATION("mutation"), EXACT("Exact"), SUBSCRIPTION("subscription"); private String value; diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java index 58c9c3dd2..83096ba22 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java @@ -78,6 +78,12 @@ public void addApis(List apis) { graphQLAPI.init(api); String apiKey = getApiKey(graphQLAPI); newApis.put(apiKey, graphQLAPI); + } else if (APIConstants.ApiType.GRPC.equals(api.getApiType())) { + GRPCAPI grpcAPI = new GRPCAPI(); + grpcAPI.init(api); + String apiKey = getApiKey(grpcAPI); + newApis.put(apiKey, grpcAPI); + logger.error("GRPC API is not supported Yet"); } else { RestAPI enforcerApi = new RestAPI(); enforcerApi.init(api); diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java index 348f29150..a911a06af 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/constants/APIConstants.java @@ -270,6 +270,7 @@ public static class ApiType { public static final String WEB_SOCKET = "WS"; public static final String GRAPHQL = "GraphQL"; + public static final String GRPC = "GRPC"; } /** From 302b36d5387924d7799d37ae1eea8e83c7888f23 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 20 Feb 2024 10:10:07 +0530 Subject: [PATCH 15/81] commented out unimplemented function in enforcer --- .../main/java/org/wso2/apk/enforcer/api/APIFactory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java index 83096ba22..7c50e2c2d 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java @@ -79,10 +79,10 @@ public void addApis(List apis) { String apiKey = getApiKey(graphQLAPI); newApis.put(apiKey, graphQLAPI); } else if (APIConstants.ApiType.GRPC.equals(api.getApiType())) { - GRPCAPI grpcAPI = new GRPCAPI(); - grpcAPI.init(api); - String apiKey = getApiKey(grpcAPI); - newApis.put(apiKey, grpcAPI); + // GRPCAPI grpcAPI = new GRPCAPI(); + // grpcAPI.init(api); + // String apiKey = getApiKey(grpcAPI); + // newApis.put(apiKey, grpcAPI); logger.error("GRPC API is not supported Yet"); } else { RestAPI enforcerApi = new RestAPI(); From 0c548bb1b1210d667e02cc6b468ac21d6619c99a Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 20 Feb 2024 19:45:54 +0530 Subject: [PATCH 16/81] bug fixes after rebasing --- adapter/internal/oasparser/envoyconf/routes_with_clusters.go | 2 +- adapter/internal/oasparser/model/adapter_internal_api.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index eeb56dee5..f62d3e05c 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -215,7 +215,7 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte endpoints = append(endpoints, address...) resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{}, "", adapterInternalAPI.Endpoints, true, gwapiv1b1.PathMatchExact) - routesP, err := createRoutes(genRouteCreateParams(&adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, + routesP, err := createRoutes(genRouteCreateParams(adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, nil, organizationID, false, false)) if err != nil { logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 2c94f6715..00f50ac8d 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -43,7 +43,6 @@ import ( // AdapterInternalAPI represents the object structure holding the information related to the // adapter internal representation. The values are populated from the operator. The pathItem level information is represented // by the resources array which contains the Resource entries. -// TODO task 1 type AdapterInternalAPI struct { UUID string apiType string @@ -1081,7 +1080,7 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) resource := &Resource{path: resourcePath, methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Method.Type), policies: policies, - auth: apiAuth, RateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, + auth: apiAuth, rateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, iD: uuid.New().String(), } endpoints := GetEndpoints(backendName, resourceParams.BackendMapping) From 3e443ae230baa3c314d1c1d0ac00563a293d7891 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 26 Feb 2024 11:26:39 +0530 Subject: [PATCH 17/81] fixed envoy configurations --- .../envoyconf/routes_with_clusters.go | 24 +++++++++---------- .../oasparser/model/adapter_internal_api.go | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index f62d3e05c..041ba9249 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -213,18 +213,18 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte } clusters = append(clusters, cluster) endpoints = append(endpoints, address...) - - resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{}, "", adapterInternalAPI.Endpoints, true, gwapiv1b1.PathMatchExact) - routesP, err := createRoutes(genRouteCreateParams(adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, - nil, organizationID, false, false)) - if err != nil { - logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, - "Error while creating routes for GRPC API %s %s Error: %s", adapterInternalAPI.GetTitle(), - adapterInternalAPI.GetVersion(), err.Error())) - return nil, nil, nil, fmt.Errorf("error while creating routes. %v", err) - } - routes = append(routes, routesP...) - return routes, clusters, endpoints, nil + //TODO check if creating a minimal resource and appeding this to routes is necessary + //resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{}, "", adapterInternalAPI.Endpoints, false, gwapiv1b1.PathMatchExact) + //routesP, err := createRoutes(genRouteCreateParams(adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, + // nil, organizationID, false, false)) + //if err != nil { + // logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, + // "Error while creating routes for GRPC API %s %s Error: %s", adapterInternalAPI.GetTitle(), + // adapterInternalAPI.GetVersion(), err.Error())) + // return nil, nil, nil, fmt.Errorf("error while creating routes. %v", err) + //} + //routes = append(routes, routesP...) + //return routes, clusters, endpoints, nil } for _, resource := range adapterInternalAPI.GetResources() { var clusterName string diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 00f50ac8d..74362ca6b 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -1076,7 +1076,7 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap apiAuth := getSecurity(resourceAuthScheme) for _, match := range rule.Matches { - resourcePath := *match.Method.Service + resourcePath := *match.Method.Service + "/" + *match.Method.Method endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) resource := &Resource{path: resourcePath, methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Method.Type), policies: policies, From 7d7af556320c27910f4d328a0343acbd1f019b29 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 28 Feb 2024 17:35:48 +0530 Subject: [PATCH 18/81] configured grpc routes on adapter side --- .../envoyconf/routes_with_clusters.go | 41 +++++++++++++------ .../oasparser/model/adapter_internal_api.go | 4 +- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 041ba9249..f5d16e182 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -213,18 +213,6 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte } clusters = append(clusters, cluster) endpoints = append(endpoints, address...) - //TODO check if creating a minimal resource and appeding this to routes is necessary - //resource := model.CreateMinimalResource(adapterInternalAPI.GetXWso2Basepath(), []*model.Operation{}, "", adapterInternalAPI.Endpoints, false, gwapiv1b1.PathMatchExact) - //routesP, err := createRoutes(genRouteCreateParams(adapterInternalAPI, &resource, vHost, basePath, clusterName, nil, - // nil, organizationID, false, false)) - //if err != nil { - // logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, - // "Error while creating routes for GRPC API %s %s Error: %s", adapterInternalAPI.GetTitle(), - // adapterInternalAPI.GetVersion(), err.Error())) - // return nil, nil, nil, fmt.Errorf("error while creating routes. %v", err) - //} - //routes = append(routes, routesP...) - //return routes, clusters, endpoints, nil } for _, resource := range adapterInternalAPI.GetResources() { var clusterName string @@ -1074,6 +1062,35 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error routes = append(routes, route) } } + } else if apiType == "GRPC" { + //TODO this is only a temporary measure, gets triggered by grpc api + logger.LoggerOasparser.Debugf("Creating routes for resource : %s that has no policies", resourcePath) + // No policies defined for the resource. Therefore, create one route for all operations. + methodRegex := strings.Join(resourceMethods, "|") + if !strings.Contains(methodRegex, "OPTIONS") { + methodRegex = methodRegex + "|OPTIONS" + } + match := generateRouteMatch(routePath) + //match.Headers = generateHTTPMethodMatcher(methodRegex, clusterName) + action := generateRouteAction(apiType, routeConfig, rateLimitPolicyCriteria) + rewritePath := generateRoutePathForReWrite(basePath, resourcePath, pathMatchType) + action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, resourcePath, pathMatchType) + + //TODO think of a better way to do this + //remove xws2basepaath from resourcepath + suffix := strings.TrimPrefix(resourcePath, xWso2Basepath) + action.Route.RegexRewrite.Substitution = suffix + action.Route.RegexRewrite.Pattern.EngineType = &envoy_type_matcherv3.RegexMatcher_GoogleRe2{} + action.Route.UpgradeConfigs = []*routev3.RouteAction_UpgradeConfig{} + action.Route.ClusterSpecifier = &routev3.RouteAction_Cluster{Cluster: clusterName} + //remove autohostrewrite value + action.Route.HostRewriteSpecifier = nil + decorator = nil + perRouteFilterConfigs = nil + // general headers to add and remove are included in this methods + route := generateRouteConfig(xWso2Basepath, match, action, nil, decorator, perRouteFilterConfigs, nil, nil, nil, nil) + + routes = append(routes, route) } else { logger.LoggerOasparser.Debugf("Creating routes for resource : %s that has no policies", resourcePath) // No policies defined for the resource. Therefore, create one route for all operations. diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 74362ca6b..42dc4afaf 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -1076,9 +1076,9 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap apiAuth := getSecurity(resourceAuthScheme) for _, match := range rule.Matches { - resourcePath := *match.Method.Service + "/" + *match.Method.Method + resourcePath := adapterInternalAPI.GetXWso2Basepath() + "/" + *match.Method.Service + "/" + *match.Method.Method endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) - resource := &Resource{path: resourcePath, + resource := &Resource{path: resourcePath, pathMatchType: "PathPrefix", methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Method.Type), policies: policies, auth: apiAuth, rateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, iD: uuid.New().String(), From c2b99c8a10ecd3ffebad0eba0b1a6034802997b4 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 1 Mar 2024 10:04:55 +0530 Subject: [PATCH 19/81] debugged context extensions not being set --- .../internal/oasparser/envoyconf/routes_with_clusters.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index f5d16e182..9417a80aa 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -1080,13 +1080,6 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error //remove xws2basepaath from resourcepath suffix := strings.TrimPrefix(resourcePath, xWso2Basepath) action.Route.RegexRewrite.Substitution = suffix - action.Route.RegexRewrite.Pattern.EngineType = &envoy_type_matcherv3.RegexMatcher_GoogleRe2{} - action.Route.UpgradeConfigs = []*routev3.RouteAction_UpgradeConfig{} - action.Route.ClusterSpecifier = &routev3.RouteAction_Cluster{Cluster: clusterName} - //remove autohostrewrite value - action.Route.HostRewriteSpecifier = nil - decorator = nil - perRouteFilterConfigs = nil // general headers to add and remove are included in this methods route := generateRouteConfig(xWso2Basepath, match, action, nil, decorator, perRouteFilterConfigs, nil, nil, nil, nil) From 035e9efe4e6bef7819faf6c64554abdd0cd27ae4 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 1 Mar 2024 10:24:52 +0530 Subject: [PATCH 20/81] Request gets matched to grpc api correctly --- .../org/wso2/apk/enforcer/api/APIFactory.java | 9 +- .../org/wso2/apk/enforcer/api/GRPCAPI.java | 223 ++++++++++++++++++ 2 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java index 7c50e2c2d..ab20bffc3 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java @@ -79,11 +79,10 @@ public void addApis(List apis) { String apiKey = getApiKey(graphQLAPI); newApis.put(apiKey, graphQLAPI); } else if (APIConstants.ApiType.GRPC.equals(api.getApiType())) { - // GRPCAPI grpcAPI = new GRPCAPI(); - // grpcAPI.init(api); - // String apiKey = getApiKey(grpcAPI); - // newApis.put(apiKey, grpcAPI); - logger.error("GRPC API is not supported Yet"); + GRPCAPI grpcAPI = new GRPCAPI(); + grpcAPI.init(api); + String apiKey = getApiKey(grpcAPI); + newApis.put(apiKey, grpcAPI); } else { RestAPI enforcerApi = new RestAPI(); enforcerApi.init(api); diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java new file mode 100644 index 000000000..ca68c9a59 --- /dev/null +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java @@ -0,0 +1,223 @@ +package org.wso2.apk.enforcer.api; + +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.wso2.apk.enforcer.analytics.AnalyticsFilter; +import org.wso2.apk.enforcer.commons.Filter; +import org.wso2.apk.enforcer.commons.dto.ClaimValueDTO; +import org.wso2.apk.enforcer.commons.dto.JWTConfigurationDto; +import org.wso2.apk.enforcer.commons.model.*; +import org.wso2.apk.enforcer.commons.model.EndpointCluster; +import org.wso2.apk.enforcer.config.ConfigHolder; +import org.wso2.apk.enforcer.config.EnforcerConfig; +import org.wso2.apk.enforcer.constants.APIConstants; +import org.wso2.apk.enforcer.constants.HttpConstants; +import org.wso2.apk.enforcer.cors.CorsFilter; +import org.wso2.apk.enforcer.discovery.api.*; +import org.wso2.apk.enforcer.security.AuthFilter; +import org.wso2.apk.enforcer.security.mtls.MtlsUtils; +import org.wso2.apk.enforcer.server.swagger.APIDefinitionUtils; +import org.wso2.apk.enforcer.util.EndpointUtils; +import org.wso2.apk.enforcer.util.FilterUtils; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GRPCAPI implements API{ + + private static final Logger logger = LogManager.getLogger(GRPCAPI.class); + private final List filters = new ArrayList<>(); + private APIConfig apiConfig; + @Override + public List getFilters() { + return filters; + } + + @Override + public String init(Api api) { + String vhost = api.getVhost(); + String basePath = api.getBasePath(); + String name = api.getTitle(); + String version = api.getVersion(); + String apiType = api.getApiType(); + List resources = new ArrayList<>(); + Map mtlsCertificateTiers = new HashMap<>(); + String mutualSSL = api.getMutualSSL(); + boolean applicationSecurity = api.getApplicationSecurity(); + + EndpointCluster endpoints = Utils.processEndpoints(api.getEndpoints()); + EndpointSecurity[] endpointSecurity = APIProcessUtils.convertProtoEndpointSecurity( + api.getEndpointSecurityList()); + + for (Resource res : api.getResourcesList()) { + for (Operation operation : res.getMethodsList()) { + ResourceConfig resConfig = Utils.buildResource(operation, res.getPath(), endpointSecurity); + resConfig.setEndpoints(endpoints); + resConfig.setPolicyConfig(Utils.genPolicyConfig(operation.getPolicies())); + resources.add(resConfig); + } + } + + KeyStore trustStore; + try { + trustStore = MtlsUtils.createTrustStore(api.getClientCertificatesList()); + } catch (KeyStoreException e) { + throw new SecurityException(e); + } + + BackendJWTTokenInfo backendJWTTokenInfo = api.getBackendJWTTokenInfo(); + JWTConfigurationDto jwtConfigurationDto = new JWTConfigurationDto(); + + // If backendJWTTokeInfo is available + if (api.hasBackendJWTTokenInfo()) { + Map claims = backendJWTTokenInfo.getCustomClaimsMap(); + Map claimsMap = new HashMap<>(); + for (Map.Entry claimEntry : claims.entrySet()) { + Claim claim = claimEntry.getValue(); + ClaimValueDTO claimVal = new ClaimValueDTO(claim.getValue(), claim.getType()); + claimsMap.put(claimEntry.getKey(), claimVal); + } + EnforcerConfig enforcerConfig = ConfigHolder.getInstance().getConfig(); + jwtConfigurationDto.populateConfigValues(backendJWTTokenInfo.getEnabled(), + backendJWTTokenInfo.getHeader(), backendJWTTokenInfo.getSigningAlgorithm(), + backendJWTTokenInfo.getEncoding(), enforcerConfig.getJwtConfigurationDto().getPublicCert(), + enforcerConfig.getJwtConfigurationDto().getPrivateKey(), backendJWTTokenInfo.getTokenTTL(), + claimsMap, enforcerConfig.getJwtConfigurationDto().useKid(), + enforcerConfig.getJwtConfigurationDto().getKidValue()); + } + + + + SchemaParser schemaParser = new SchemaParser(); + + byte[] apiDefinition = api.getApiDefinitionFile().toByteArray(); + TypeDefinitionRegistry registry; + + //TODO fix this +// try { +// String scheme = APIDefinitionUtils.ReadGzip(apiDefinition); +// registry = schemaParser.parse(scheme); +// } catch (IOException e) { +// logger.error("Error while parsing the GRPC schema definition of the API: " + name, e); +// throw new RuntimeException(e); +// } +// GraphQLSchema schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registry); + +// GraphQLSchemaDTO graphQLSchemaDTO = new GraphQLSchemaDTO(schema, registry, +// GraphQLPayloadUtils.parseComplexityDTO(api.getGraphqlComplexityInfoList())); + + String apiLifeCycleState = api.getApiLifeCycleState(); + this.apiConfig = new APIConfig.Builder(name).uuid(api.getId()).vhost(vhost).basePath(basePath).version(version) + .resources(resources).apiType(apiType).apiLifeCycleState(apiLifeCycleState).tier(api.getTier()) + .envType(api.getEnvType()).disableAuthentication(api.getDisableAuthentications()) + .disableScopes(api.getDisableScopes()).trustStore(trustStore).organizationId(api.getOrganizationId()) + .mutualSSL(mutualSSL) + .applicationSecurity(applicationSecurity).jwtConfigurationDto(jwtConfigurationDto) +// .apiDefinition(apiDefinition).environment(api.getEnvironment()) + .environment(api.getEnvironment()) +// .subscriptionValidation(api.getSubscriptionValidation()).graphQLSchemaDTO(graphQLSchemaDTO).build(); + .subscriptionValidation(api.getSubscriptionValidation()).build(); + initFilters(); + logger.info("APIConfig: " + this.apiConfig); + return basePath; + } + + @Override + public ResponseObject process(RequestContext requestContext) { + + ResponseObject responseObject = new ResponseObject(requestContext.getRequestID()); + responseObject.setRequestPath(requestContext.getRequestPath()); + boolean analyticsEnabled = ConfigHolder.getInstance().getConfig().getAnalyticsConfig().isEnabled(); + + Utils.handleCommonHeaders(requestContext); + boolean isExistsMatchedOperations = requestContext.getMatchedResourcePaths() != null && + requestContext.getMatchedResourcePaths().size() > 0; + // This flag is used to apply CORS filter + boolean isOptionCall = requestContext.getRequestMethod().contains(HttpConstants.OPTIONS); + + // handle other not allowed && non option request && not yet handled error + // scenarios. + if ((!isOptionCall && !isExistsMatchedOperations) && !requestContext.getProperties() + .containsKey(APIConstants.MessageFormat.ERROR_CODE)) { + requestContext.getProperties() + .put(APIConstants.MessageFormat.STATUS_CODE, APIConstants.StatusCodes.NOTFOUND.getCode()); + requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_CODE, + APIConstants.StatusCodes.NOTFOUND.getValue()); + requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_MESSAGE, + APIConstants.NOT_FOUND_MESSAGE); + requestContext.getProperties().put(APIConstants.MessageFormat.ERROR_DESCRIPTION, + APIConstants.NOT_FOUND_DESCRIPTION); + } + + if ((isExistsMatchedOperations || isOptionCall) && executeFilterChain(requestContext)) { + EndpointUtils.updateClusterHeaderAndCheckEnv(requestContext); + responseObject.setOrganizationId(requestContext.getMatchedAPI().getOrganizationId()); + responseObject.setRemoveHeaderMap(requestContext.getRemoveHeaders()); + responseObject.setQueryParamsToRemove(requestContext.getQueryParamsToRemove()); + responseObject.setRemoveAllQueryParams(requestContext.isRemoveAllQueryParams()); + responseObject.setQueryParamsToAdd(requestContext.getQueryParamsToAdd()); + responseObject.setQueryParamMap(requestContext.getQueryParameters()); + responseObject.setStatusCode(APIConstants.StatusCodes.OK.getCode()); + if (requestContext.getAddHeaders() != null && requestContext.getAddHeaders().size() > 0) { + responseObject.setHeaderMap(requestContext.getAddHeaders()); + } + if (analyticsEnabled) { + AnalyticsFilter.getInstance().handleSuccessRequest(requestContext); + } + // set metadata for interceptors + responseObject.setMetaDataMap(requestContext.getMetadataMap()); + } else { + // If enforcer stops with a false, it will be passed directly to the client. + responseObject.setDirectResponse(true); + responseObject.setStatusCode(Integer.parseInt( + requestContext.getProperties().get(APIConstants.MessageFormat.STATUS_CODE).toString())); + if (requestContext.getProperties().containsKey(APIConstants.MessageFormat.ERROR_CODE)) { + responseObject.setErrorCode( + requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_CODE).toString()); + } + if (requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_MESSAGE) != null) { + responseObject.setErrorMessage(requestContext.getProperties() + .get(APIConstants.MessageFormat.ERROR_MESSAGE).toString()); + } + if (requestContext.getProperties().get(APIConstants.MessageFormat.ERROR_DESCRIPTION) != null) { + responseObject.setErrorDescription(requestContext.getProperties() + .get(APIConstants.MessageFormat.ERROR_DESCRIPTION).toString()); + } + if (requestContext.getAddHeaders() != null && requestContext.getAddHeaders().size() > 0) { + responseObject.setHeaderMap(requestContext.getAddHeaders()); + } + if (analyticsEnabled && !FilterUtils.isSkippedAnalyticsFaultEvent(responseObject.getErrorCode())) { + AnalyticsFilter.getInstance().handleFailureRequest(requestContext); + responseObject.setMetaDataMap(new HashMap<>(0)); + } + } + + return responseObject; + } + + @Override + public APIConfig getAPIConfig() { + return this.apiConfig; + } + + private void initFilters() { + AuthFilter authFilter = new AuthFilter(); + authFilter.init(apiConfig, null); + this.filters.add(authFilter); + +// GraphQLQueryAnalysisFilter queryAnalysisFilter = new GraphQLQueryAnalysisFilter(); +// queryAnalysisFilter.init(apiConfig, null); +// this.filters.add(queryAnalysisFilter); + + // CORS filter is added as the first filter, and it is not customizable. + CorsFilter corsFilter = new CorsFilter(); + this.filters.add(0, corsFilter); + } +} From e91eb18ba7b6713a9c8ef63054ef2f9fcef37817 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 1 Mar 2024 12:45:22 +0530 Subject: [PATCH 21/81] Request gets matched to resource correctly --- .../src/main/java/org/wso2/apk/enforcer/api/APIFactory.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java index ab20bffc3..ea97c7f68 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java @@ -138,6 +138,12 @@ public byte[] getAPIDefinition(final String basePath, final String version, fina public ResourceConfig getMatchedResource(API api, String matchedResourcePath, String method) { List resourceConfigList = api.getAPIConfig().getResources(); + if (APIConstants.ApiType.GRPC.equals(api.getAPIConfig().getApiType())) { + //TODO check if there's another way to do this, perhaps in the adapter side by setting the method + return resourceConfigList.stream() + .filter(resourceConfig -> resourceConfig.getPath().equals(matchedResourcePath)) + .findFirst().orElse(null); + } return resourceConfigList.stream() .filter(resourceConfig -> resourceConfig.getPath().equals(matchedResourcePath)). filter(resourceConfig -> (method == null) || resourceConfig.getMethod() From 6f1c0b0b8c4bb359c2bd54fec2920aafb180e3df Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 1 Mar 2024 17:59:30 +0530 Subject: [PATCH 22/81] enabled http2 backend and unset transport socket matching --- adapter/internal/oasparser/envoyconf/routes_with_clusters.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 9417a80aa..67439767d 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -204,7 +204,6 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte adapterInternalAPI.GetTitle(), apiVersion, "") adapterInternalAPI.Endpoints.HTTP2BackendEnabled = true cluster, address, err := processEndpoints(clusterName, adapterInternalAPI.Endpoints, timeout, basePath) - if err != nil { logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2239, logging.MAJOR, "Error while adding grpc endpoints for %s:%v. %v", apiTitle, apiVersion, err.Error())) @@ -218,12 +217,16 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte var clusterName string resourcePath := resource.GetPath() endpoint := resource.GetEndpoints() + //TODO see if I can configure this elsewhere + endpoint.HTTP2BackendEnabled = true basePath := strings.TrimSuffix(endpoint.Endpoints[0].Basepath, "/") existingClusterName := getExistingClusterName(*endpoint, processedEndpoints) if existingClusterName == "" { clusterName = getClusterName(endpoint.EndpointPrefix, organizationID, vHost, adapterInternalAPI.GetTitle(), apiVersion, resource.GetID()) cluster, address, err := processEndpoints(clusterName, endpoint, timeout, basePath) + //TODO see if I can configure this elsewhere + cluster.TransportSocketMatches = nil if err != nil { logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2239, logging.MAJOR, "Error while adding resource level endpoints for %s:%v-%v. %v", apiTitle, apiVersion, resourcePath, err.Error())) } else { From 3da8731deccd94d4fed279762fc307698439752e Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 5 Mar 2024 11:36:54 +0530 Subject: [PATCH 23/81] add grpc CRs for testing --- .../tests/resources/tests/grpc-api.yaml | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 test/integration/integration/tests/resources/tests/grpc-api.yaml diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml new file mode 100644 index 000000000..eabb527a9 --- /dev/null +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -0,0 +1,130 @@ +# Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +apiVersion: dp.wso2.com/v1alpha2 +kind: API +metadata: + name: dineth-grpc-api + namespace: gateway-integration-test-infra + labels: + api-name: "dineth-grpc-api" + api-version: "v1" + organization: "7505d64a54e061b7acd54ccd58b49dc43500b635" + managed-by: "apk" +spec: + apiName: Dineth GRPC API + apiType: GRPC + apiVersion: v1 + basePath: /dineth-grpc-api/v1 + isDefaultVersion: true + production: + - httpRouteRefs: + - dineth-grpc-route + organization: apk-org + +--- + +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: GRPCRoute +metadata: + name: dineth-grpc-route + namespace: gateway-integration-test-infra + labels: + api-name: "dineth-grpc-api" + api-version: "v1" + organization: "7505d64a54e061b7acd54ccd58b49dc43500b635" + managed-by: "apk" +spec: + hostnames: + - grpc.test.gw.wso2.com + parentRefs: + - group: "gateway.networking.k8s.io" + kind: "Gateway" + name: "default" + sectionName: "httpslistener" + rules: + - matches: + - method: + service: StudentService + method: GetStudent + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + - matches: + - method: + service: StudentService + method: SendStudent + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + - matches: + - method: + service: TeacherService + method: GetTeacher + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + +--- + +#apiVersion: "dp.wso2.com/v1alpha1" +#kind: "Authentication" +#metadata: +# name: dineth-grpc-api-auth +# namespace: gateway-integration-test-infra +# spec: +# default: +# authTypes: +# disabled: true +# targetRef: +# group: "gateway.networking.k8s.io" +# kind: Resource +# namespace: gateway-integration-test-infra +# name: dineth-grpc-api +apiVersion: dp.wso2.com/v1alpha1 +kind: Authentication +metadata: + name: disable-grpc-api-security + namespace: gateway-integration-test-infra +spec: + override: + disabled: true + targetRef: + group: gateway.networking.k8s.io + kind: API + namespace: gateway-integration-test-infra + name: dineth-grpc-api +--- + +apiVersion: dp.wso2.com/v1alpha1 +kind: Backend +metadata: + name: dineth-grpc-backend + namespace: gateway-integration-test-infra + labels: + api-name: "dineth-grpc-api" + api-version: "v1" + organization: "7505d64a54e061b7acd54ccd58b49dc43500b635" + managed-by: "apk" +spec: + services: + - host: grpc-backend + port: 6565 + basePath: "" + protocol: https From 8220d52165988a0e67ba559bec65caeae7e460d7 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 5 Mar 2024 14:51:41 +0530 Subject: [PATCH 24/81] add custom grpc backend deployment to manifest --- .../tests/resources/base/manifests.yaml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/integration/integration/tests/resources/base/manifests.yaml b/test/integration/integration/tests/resources/base/manifests.yaml index 5d4ef2460..c902689fd 100644 --- a/test/integration/integration/tests/resources/base/manifests.yaml +++ b/test/integration/integration/tests/resources/base/manifests.yaml @@ -113,6 +113,57 @@ spec: --- apiVersion: v1 kind: Service +metadata: + name: grpc-demo-app + namespace: gateway-integration-test-infra +spec: + selector: + app: grpc-demo-app + ports: + - protocol: TCP + port: 6565 + targetPort: 6565 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grpc-demo-app + namespace: gateway-integration-test-infra + labels: + app: grpc-demo-app +spec: + replicas: 1 + selector: + matchLabels: + app: grpc-demo-app + template: + metadata: + labels: + app: grpc-demo-app + spec: + containers: + - name: grpc-demo-app + image: grpc-demo-app:latest + imagePullPolicy: IfNotPresent + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + requests: + cpu: "100m" # Adjust as needed + memory: "100Mi" # Adjust as needed + ports: + - containerPort: 6565 # Ensuring the container exposes port 6565 for gRPC traffic + +--- +apiVersion: v1 +kind: Service metadata: name: infra-backend-v2 namespace: gateway-integration-test-infra From 0af1bfb62bd46a70beeaf0fb8c5a7230750917de Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 5 Mar 2024 15:22:49 +0530 Subject: [PATCH 25/81] update grpc CRs --- .../tests/resources/tests/grpc-api.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml index eabb527a9..184672b78 100644 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -22,7 +22,7 @@ metadata: labels: api-name: "dineth-grpc-api" api-version: "v1" - organization: "7505d64a54e061b7acd54ccd58b49dc43500b635" + organization: wso2-org managed-by: "apk" spec: apiName: Dineth GRPC API @@ -33,7 +33,7 @@ spec: production: - httpRouteRefs: - dineth-grpc-route - organization: apk-org + organization: wso2-org --- @@ -45,16 +45,16 @@ metadata: labels: api-name: "dineth-grpc-api" api-version: "v1" - organization: "7505d64a54e061b7acd54ccd58b49dc43500b635" + organization: "wso2-org" managed-by: "apk" spec: hostnames: - grpc.test.gw.wso2.com parentRefs: - - group: "gateway.networking.k8s.io" - kind: "Gateway" - name: "default" - sectionName: "httpslistener" + - group: gateway.networking.k8s.io + kind: Gateway + name: default + sectionName: httpslistener rules: - matches: - method: @@ -120,11 +120,11 @@ metadata: labels: api-name: "dineth-grpc-api" api-version: "v1" - organization: "7505d64a54e061b7acd54ccd58b49dc43500b635" + organization: wso2-org managed-by: "apk" spec: services: - - host: grpc-backend + - host: grpc-backend.gateway-integration-test-infra port: 6565 basePath: "" protocol: https From a1e1ff611c4851cf0eeef3e21c9e7096c42698e7 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 8 Mar 2024 14:52:25 +0530 Subject: [PATCH 26/81] fix transport socket not matching --- .../envoyconf/routes_with_clusters.go | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 67439767d..acebab5be 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -212,21 +212,73 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte } clusters = append(clusters, cluster) endpoints = append(endpoints, address...) + + //TODO factor out common code (Dineth) + for _, resource := range adapterInternalAPI.GetResources() { + var clusterName string + resourcePath := resource.GetPath() + endpoint := resource.GetEndpoints() + //TODO see if this should be configured elsewhere (Dineth) + endpoint.HTTP2BackendEnabled = true + basePath := strings.TrimSuffix(endpoint.Endpoints[0].Basepath, "/") + existingClusterName := getExistingClusterName(*endpoint, processedEndpoints) + + if existingClusterName == "" { + clusterName = getClusterName(endpoint.EndpointPrefix, organizationID, vHost, adapterInternalAPI.GetTitle(), apiVersion, resource.GetID()) + cluster, address, err := processEndpoints(clusterName, endpoint, timeout, basePath) + //TODO see if this should be configured elsewhere (Dineth) + cluster.TransportSocketMatches = nil + if err != nil { + logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2239, logging.MAJOR, "Error while adding resource level endpoints for %s:%v-%v. %v", apiTitle, apiVersion, resourcePath, err.Error())) + } else { + clusters = append(clusters, cluster) + endpoints = append(endpoints, address...) + processedEndpoints[clusterName] = *endpoint + } + } else { + clusterName = existingClusterName + } + // Create resource level interceptor clusters if required + //TODO remove this if no interceptor needed for grpc (Dineth) + clustersI, endpointsI, operationalReqInterceptors, operationalRespInterceptorVal := createInterceptorResourceClusters(adapterInternalAPI, + interceptorCerts, vHost, organizationID, apiRequestInterceptor, apiResponseInterceptor, resource) + clusters = append(clusters, clustersI...) + endpoints = append(endpoints, endpointsI...) + routeParams := genRouteCreateParams(adapterInternalAPI, resource, vHost, basePath, clusterName, *operationalReqInterceptors, *operationalRespInterceptorVal, organizationID, + false, false) + + routeP, err := createRoutes(routeParams) + if err != nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, + "Error while creating routes for GRPC API %s %s for path: %s Error: %s", adapterInternalAPI.GetTitle(), + adapterInternalAPI.GetVersion(), resource.GetPath(), err.Error())) + return nil, nil, nil, fmt.Errorf("error while creating routes. %v", err) + } + routes = append(routes, routeP...) + if adapterInternalAPI.IsDefaultVersion { + defaultRoutes, errDefaultPath := createRoutes(genRouteCreateParams(adapterInternalAPI, resource, vHost, basePath, clusterName, *operationalReqInterceptors, *operationalRespInterceptorVal, organizationID, + false, true)) + if errDefaultPath != nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2231, logging.MAJOR, "Error while creating routes for GRPC API %s %s for path: %s Error: %s", adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), removeFirstOccurrence(resource.GetPath(), adapterInternalAPI.GetVersion()), errDefaultPath.Error())) + return nil, nil, nil, fmt.Errorf("error while creating routes. %v", errDefaultPath) + } + routes = append(routes, defaultRoutes...) + } + + } + + return routes, clusters, endpoints, nil } for _, resource := range adapterInternalAPI.GetResources() { var clusterName string resourcePath := resource.GetPath() endpoint := resource.GetEndpoints() - //TODO see if I can configure this elsewhere - endpoint.HTTP2BackendEnabled = true basePath := strings.TrimSuffix(endpoint.Endpoints[0].Basepath, "/") existingClusterName := getExistingClusterName(*endpoint, processedEndpoints) if existingClusterName == "" { clusterName = getClusterName(endpoint.EndpointPrefix, organizationID, vHost, adapterInternalAPI.GetTitle(), apiVersion, resource.GetID()) cluster, address, err := processEndpoints(clusterName, endpoint, timeout, basePath) - //TODO see if I can configure this elsewhere - cluster.TransportSocketMatches = nil if err != nil { logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2239, logging.MAJOR, "Error while adding resource level endpoints for %s:%v-%v. %v", apiTitle, apiVersion, resourcePath, err.Error())) } else { From 807edbf480eec84b23b7564344adf1dd8267f37b Mon Sep 17 00:00:00 2001 From: DinethH Date: Sat, 9 Mar 2024 09:35:56 +0530 Subject: [PATCH 27/81] change gRPC CRs --- .../tests/resources/base/manifests.yaml | 22 ++++++------ .../tests/resources/tests/grpc-api.yaml | 34 ++++++++----------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/test/integration/integration/tests/resources/base/manifests.yaml b/test/integration/integration/tests/resources/base/manifests.yaml index c902689fd..08b7b8b63 100644 --- a/test/integration/integration/tests/resources/base/manifests.yaml +++ b/test/integration/integration/tests/resources/base/manifests.yaml @@ -114,11 +114,11 @@ spec: apiVersion: v1 kind: Service metadata: - name: grpc-demo-app + name: dineth-grpc-demo-server namespace: gateway-integration-test-infra spec: selector: - app: grpc-demo-app + app: dineth-grpc-demo-server ports: - protocol: TCP port: 6565 @@ -127,23 +127,23 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: grpc-demo-app + name: dineth-grpc-demo-server namespace: gateway-integration-test-infra labels: - app: grpc-demo-app + app: dineth-grpc-demo-server spec: replicas: 1 selector: matchLabels: - app: grpc-demo-app + app: dineth-grpc-demo-server template: metadata: labels: - app: grpc-demo-app + app: dineth-grpc-demo-server spec: containers: - - name: grpc-demo-app - image: grpc-demo-app:latest + - name: dineth-grpc-demo-server + image: dineth-grpc-demo-server:latest imagePullPolicy: IfNotPresent env: - name: POD_NAME @@ -156,10 +156,10 @@ spec: fieldPath: metadata.namespace resources: requests: - cpu: "100m" # Adjust as needed - memory: "100Mi" # Adjust as needed + cpu: "100m" + memory: "100Mi" ports: - - containerPort: 6565 # Ensuring the container exposes port 6565 for gRPC traffic + - containerPort: 6565 --- apiVersion: v1 diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml index 184672b78..3dc983e24 100644 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -31,7 +31,7 @@ spec: basePath: /dineth-grpc-api/v1 isDefaultVersion: true production: - - httpRouteRefs: + - routeRefs: - dineth-grpc-route organization: wso2-org @@ -58,7 +58,7 @@ spec: rules: - matches: - method: - service: StudentService + service: student.StudentService method: GetStudent backendRefs: - name: dineth-grpc-backend @@ -66,16 +66,24 @@ spec: port: 6565 - matches: - method: - service: StudentService - method: SendStudent + service: student.StudentService + method: SendStudentStream backendRefs: - name: dineth-grpc-backend kind: Backend port: 6565 - matches: - method: - service: TeacherService - method: GetTeacher + service: student.StudentService + method: GetStudentStream + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + - matches: + - method: + service: student.StudentService + method: SendAndGetStudentStream backendRefs: - name: dineth-grpc-backend kind: Backend @@ -83,20 +91,6 @@ spec: --- -#apiVersion: "dp.wso2.com/v1alpha1" -#kind: "Authentication" -#metadata: -# name: dineth-grpc-api-auth -# namespace: gateway-integration-test-infra -# spec: -# default: -# authTypes: -# disabled: true -# targetRef: -# group: "gateway.networking.k8s.io" -# kind: Resource -# namespace: gateway-integration-test-infra -# name: dineth-grpc-api apiVersion: dp.wso2.com/v1alpha1 kind: Authentication metadata: From d3ed16a2234d5b4536978a99b8aebfbcc827522c Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 11 Mar 2024 10:27:41 +0530 Subject: [PATCH 28/81] remove deployment and manifest for grpc backend pod --- .../tests/resources/base/manifests.yaml | 51 ------- .../tests/resources/tests/grpc-api.yaml | 124 ------------------ 2 files changed, 175 deletions(-) delete mode 100644 test/integration/integration/tests/resources/tests/grpc-api.yaml diff --git a/test/integration/integration/tests/resources/base/manifests.yaml b/test/integration/integration/tests/resources/base/manifests.yaml index 08b7b8b63..5d4ef2460 100644 --- a/test/integration/integration/tests/resources/base/manifests.yaml +++ b/test/integration/integration/tests/resources/base/manifests.yaml @@ -113,57 +113,6 @@ spec: --- apiVersion: v1 kind: Service -metadata: - name: dineth-grpc-demo-server - namespace: gateway-integration-test-infra -spec: - selector: - app: dineth-grpc-demo-server - ports: - - protocol: TCP - port: 6565 - targetPort: 6565 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: dineth-grpc-demo-server - namespace: gateway-integration-test-infra - labels: - app: dineth-grpc-demo-server -spec: - replicas: 1 - selector: - matchLabels: - app: dineth-grpc-demo-server - template: - metadata: - labels: - app: dineth-grpc-demo-server - spec: - containers: - - name: dineth-grpc-demo-server - image: dineth-grpc-demo-server:latest - imagePullPolicy: IfNotPresent - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - resources: - requests: - cpu: "100m" - memory: "100Mi" - ports: - - containerPort: 6565 - ---- -apiVersion: v1 -kind: Service metadata: name: infra-backend-v2 namespace: gateway-integration-test-infra diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml deleted file mode 100644 index 3dc983e24..000000000 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. -# -# WSO2 LLC. licenses this file to you under the Apache License, -# Version 2.0 (the "License"); you may not use this file except -# in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -apiVersion: dp.wso2.com/v1alpha2 -kind: API -metadata: - name: dineth-grpc-api - namespace: gateway-integration-test-infra - labels: - api-name: "dineth-grpc-api" - api-version: "v1" - organization: wso2-org - managed-by: "apk" -spec: - apiName: Dineth GRPC API - apiType: GRPC - apiVersion: v1 - basePath: /dineth-grpc-api/v1 - isDefaultVersion: true - production: - - routeRefs: - - dineth-grpc-route - organization: wso2-org - ---- - -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: GRPCRoute -metadata: - name: dineth-grpc-route - namespace: gateway-integration-test-infra - labels: - api-name: "dineth-grpc-api" - api-version: "v1" - organization: "wso2-org" - managed-by: "apk" -spec: - hostnames: - - grpc.test.gw.wso2.com - parentRefs: - - group: gateway.networking.k8s.io - kind: Gateway - name: default - sectionName: httpslistener - rules: - - matches: - - method: - service: student.StudentService - method: GetStudent - backendRefs: - - name: dineth-grpc-backend - kind: Backend - port: 6565 - - matches: - - method: - service: student.StudentService - method: SendStudentStream - backendRefs: - - name: dineth-grpc-backend - kind: Backend - port: 6565 - - matches: - - method: - service: student.StudentService - method: GetStudentStream - backendRefs: - - name: dineth-grpc-backend - kind: Backend - port: 6565 - - matches: - - method: - service: student.StudentService - method: SendAndGetStudentStream - backendRefs: - - name: dineth-grpc-backend - kind: Backend - port: 6565 - ---- - -apiVersion: dp.wso2.com/v1alpha1 -kind: Authentication -metadata: - name: disable-grpc-api-security - namespace: gateway-integration-test-infra -spec: - override: - disabled: true - targetRef: - group: gateway.networking.k8s.io - kind: API - namespace: gateway-integration-test-infra - name: dineth-grpc-api ---- - -apiVersion: dp.wso2.com/v1alpha1 -kind: Backend -metadata: - name: dineth-grpc-backend - namespace: gateway-integration-test-infra - labels: - api-name: "dineth-grpc-api" - api-version: "v1" - organization: wso2-org - managed-by: "apk" -spec: - services: - - host: grpc-backend.gateway-integration-test-infra - port: 6565 - basePath: "" - protocol: https From 0ff06d687d053260f387c670ad10095bfa40c254 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 11 Mar 2024 15:23:33 +0530 Subject: [PATCH 29/81] add CRs for grpc api --- .../tests/resources/base/manifests.yaml | 47 +++++++ .../tests/resources/tests/grpc-api.yaml | 124 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 test/integration/integration/tests/resources/tests/grpc-api.yaml diff --git a/test/integration/integration/tests/resources/base/manifests.yaml b/test/integration/integration/tests/resources/base/manifests.yaml index 5d4ef2460..fb369f030 100644 --- a/test/integration/integration/tests/resources/base/manifests.yaml +++ b/test/integration/integration/tests/resources/base/manifests.yaml @@ -113,6 +113,53 @@ spec: --- apiVersion: v1 kind: Service +metadata: + name: dineth-grpc-demo-server + namespace: gateway-integration-test-infra +spec: + selector: + app: dineth-grpc-demo-server + ports: + - protocol: TCP + port: 6565 + targetPort: 6565 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dineth-grpc-demo-server + namespace: gateway-integration-test-infra + labels: + app: dineth-grpc-demo-server +spec: + replicas: 1 + selector: + matchLabels: + app: dineth-grpc-demo-server + template: + metadata: + labels: + app: dineth-grpc-demo-server + spec: + containers: + - name: dineth-grpc-demo-server + image: ddh13/dineth-grpc-demo-server:1.0.0 + imagePullPolicy: Always + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + requests: + cpu: 10m +--- +apiVersion: v1 +kind: Service metadata: name: infra-backend-v2 namespace: gateway-integration-test-infra diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml new file mode 100644 index 000000000..3eba502fa --- /dev/null +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -0,0 +1,124 @@ +# Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +apiVersion: dp.wso2.com/v1alpha2 +kind: API +metadata: + name: dineth-grpc-api + namespace: gateway-integration-test-infra + labels: + api-name: "dineth-grpc-api" + api-version: "v1" + organization: wso2-org + managed-by: "apk" +spec: + apiName: Dineth GRPC API + apiType: GRPC + apiVersion: v1 + basePath: /dineth-grpc-api/v1 + isDefaultVersion: false + production: + - routeRefs: + - dineth-grpc-route + organization: wso2-org + +--- + +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: GRPCRoute +metadata: + name: dineth-grpc-route + namespace: gateway-integration-test-infra + labels: + api-name: "dineth-grpc-api" + api-version: "v1" + organization: "wso2-org" + managed-by: "apk" +spec: + hostnames: + - grpc.test.gw.wso2.com + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: default + sectionName: httpslistener + rules: + - matches: + - method: + service: student.StudentService + method: GetStudent + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + - matches: + - method: + service: student.StudentService + method: SendStudentStream + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + - matches: + - method: + service: student.StudentService + method: GetStudentStream + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + - matches: + - method: + service: student.StudentService + method: SendAndGetStudentStream + backendRefs: + - name: dineth-grpc-backend + kind: Backend + port: 6565 + +--- + +apiVersion: dp.wso2.com/v1alpha1 +kind: Authentication +metadata: + name: disable-grpc-api-security + namespace: gateway-integration-test-infra +spec: + override: + disabled: true + targetRef: + group: gateway.networking.k8s.io + kind: API + namespace: gateway-integration-test-infra + name: dineth-grpc-api +--- + +apiVersion: dp.wso2.com/v1alpha1 +kind: Backend +metadata: + name: dineth-grpc-backend + namespace: gateway-integration-test-infra + labels: + api-name: "dineth-grpc-api" + api-version: "v1" + organization: wso2-org + managed-by: "apk" +spec: + services: + - host: grpc-backend.gateway-integration-test-infra + port: 6565 + basePath: "" + protocol: https From 96b3f6aaf6efcda9f6d402516f6225d4a706ce1f Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 11 Mar 2024 15:40:15 +0530 Subject: [PATCH 30/81] add empty test for grpc api --- .../integration/integration/tests/grpc-api.go | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/integration/integration/tests/grpc-api.go diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go new file mode 100644 index 000000000..69738db0f --- /dev/null +++ b/test/integration/integration/tests/grpc-api.go @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package tests + +import ( + "testing" + + "github.com/wso2/apk/test/integration/integration/utils/http" + "github.com/wso2/apk/test/integration/integration/utils/suite" +) + +func init() { + IntegrationTests = append(IntegrationTests, GRPCAPI) +} + +// DisableAPISecurity test +var GRPCAPI = suite.IntegrationTest{ + ShortName: "GRPCAPI", + Description: "Tests gRPC API", + Manifests: []string{"tests/grpc-api.yaml"}, + Test: func(t *testing.T, suite *suite.IntegrationTestSuite) { + gwAddr := "grpc.test.gw.wso2.com:9095" + //token := http.GetTestToken(t) + + testCases := []http.ExpectedResponse{ + //{ + // Request: http.Request{ + // Host: "gql.test.gw.wso2.com", + // Path: "/gql/v1", + // Method: "POST", + // Headers: map[string]string{ + // "Content-Type": "application/json", + // }, + // Body: `{"query":"query{\n human(id:1000){\n id\n name\n }\n}","variables":{}}`, + // }, + // ExpectedRequest: &http.ExpectedRequest{ + // Request: http.Request{ + // Method: ""}, + // }, + // Response: http.Response{StatusCode: 200}, + //}, + + } + for i := range testCases { + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + }) + } + }, +} From 4ce242b2f06add25d402de3ba3fe1a80e6805ca4 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 15 Mar 2024 01:22:42 +0530 Subject: [PATCH 31/81] works with postman client --- .../envoyconf/routes_with_clusters.go | 28 ++++--------------- .../oasparser/model/adapter_internal_api.go | 7 ++--- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index acebab5be..040882a29 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -1117,28 +1117,6 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error routes = append(routes, route) } } - } else if apiType == "GRPC" { - //TODO this is only a temporary measure, gets triggered by grpc api - logger.LoggerOasparser.Debugf("Creating routes for resource : %s that has no policies", resourcePath) - // No policies defined for the resource. Therefore, create one route for all operations. - methodRegex := strings.Join(resourceMethods, "|") - if !strings.Contains(methodRegex, "OPTIONS") { - methodRegex = methodRegex + "|OPTIONS" - } - match := generateRouteMatch(routePath) - //match.Headers = generateHTTPMethodMatcher(methodRegex, clusterName) - action := generateRouteAction(apiType, routeConfig, rateLimitPolicyCriteria) - rewritePath := generateRoutePathForReWrite(basePath, resourcePath, pathMatchType) - action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, resourcePath, pathMatchType) - - //TODO think of a better way to do this - //remove xws2basepaath from resourcepath - suffix := strings.TrimPrefix(resourcePath, xWso2Basepath) - action.Route.RegexRewrite.Substitution = suffix - // general headers to add and remove are included in this methods - route := generateRouteConfig(xWso2Basepath, match, action, nil, decorator, perRouteFilterConfigs, nil, nil, nil, nil) - - routes = append(routes, route) } else { logger.LoggerOasparser.Debugf("Creating routes for resource : %s that has no policies", resourcePath) // No policies defined for the resource. Therefore, create one route for all operations. @@ -1152,6 +1130,12 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error rewritePath := generateRoutePathForReWrite(basePath, resourcePath, pathMatchType) action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, resourcePath, pathMatchType) + if apiType == "GRPC" { + match.Headers = nil + newRoutePath := "/" + strings.TrimPrefix(resourcePath, basePath+".") + action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, newRoutePath, pathMatchType) + } + route := generateRouteConfig(xWso2Basepath, match, action, nil, decorator, perRouteFilterConfigs, nil, nil, nil, nil) // general headers to add and remove are included in this methods routes = append(routes, route) diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 42dc4afaf..6f0ac2f82 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -966,7 +966,6 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap } //We are only supporting one backend for now - //TODO fix this later (Dineth) backend := grpcRoute.Spec.Rules[0].BackendRefs[0] backendName := types.NamespacedName{ Name: string(backend.Name), @@ -1033,7 +1032,6 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap resourceAuthScheme := authScheme resourceRatelimitPolicy := ratelimitPolicy var scopes []string - //TODO remove policies for _, filter := range rule.Filters { if filter.ExtensionRef != nil && filter.ExtensionRef.Kind == constants.KindAuthentication { if ref, found := resourceParams.ResourceAuthSchemes[types.NamespacedName{ @@ -1075,10 +1073,11 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", adapterInternalAPI.UUID) apiAuth := getSecurity(resourceAuthScheme) + //TODO Add path match type (Dineth) for _, match := range rule.Matches { - resourcePath := adapterInternalAPI.GetXWso2Basepath() + "/" + *match.Method.Service + "/" + *match.Method.Method + resourcePath := adapterInternalAPI.GetXWso2Basepath() + "." + *match.Method.Service + "/" + *match.Method.Method endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) - resource := &Resource{path: resourcePath, pathMatchType: "PathPrefix", + resource := &Resource{path: resourcePath, pathMatchType: "Exact", methods: []*Operation{{iD: uuid.New().String(), method: string(*match.Method.Type), policies: policies, auth: apiAuth, rateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, iD: uuid.New().String(), From ece6f36d5feaafbd149668cc9dad2c3865b93ad0 Mon Sep 17 00:00:00 2001 From: DinethH Date: Sat, 16 Mar 2024 11:39:14 +0530 Subject: [PATCH 32/81] changed grpc CR for test --- .../integration/integration/tests/resources/tests/grpc-api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml index 3eba502fa..71f80a74b 100644 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -28,7 +28,7 @@ spec: apiName: Dineth GRPC API apiType: GRPC apiVersion: v1 - basePath: /dineth-grpc-api/v1 + basePath: /dineth.grpc.api.v1 isDefaultVersion: false production: - routeRefs: From dc5a56783cb9711dcdc603f877291ab294b73942 Mon Sep 17 00:00:00 2001 From: DinethH Date: Sat, 16 Mar 2024 12:35:59 +0530 Subject: [PATCH 33/81] move basepath validation for API CRs --- .../apis/dp/v1alpha2/api_webhook.go | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/common-go-libs/apis/dp/v1alpha2/api_webhook.go b/common-go-libs/apis/dp/v1alpha2/api_webhook.go index 2e3348be3..67faddea5 100644 --- a/common-go-libs/apis/dp/v1alpha2/api_webhook.go +++ b/common-go-libs/apis/dp/v1alpha2/api_webhook.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "io" + "regexp" "strings" gqlparser "github.com/vektah/gqlparser" @@ -103,6 +104,8 @@ func (r *API) validateAPI() error { if r.Spec.BasePath == "" { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("basePath"), "API basePath is required")) + } else if errMsg := validateAPIBasePathRegex(r.Spec.BasePath, r.Spec.APIType); errMsg != "" { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("basePath"), r.Spec.BasePath, errMsg)) } else if errMsg := validateAPIBasePathFormat(r.Spec.BasePath, r.Spec.APIVersion); errMsg != "" { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("basePath"), r.Spec.BasePath, errMsg)) } else if err := r.validateAPIBasePathExistsAndDefaultVersion(); err != nil { @@ -250,6 +253,23 @@ func validateAPIBasePathFormat(basePath string, apiVersion string) string { return "" } +func validateAPIBasePathRegex(basePath, apiType string) string { + var pattern string + if apiType == "GRPC" { + pattern = `^[/][a-zA-Z][a-zA-Z0-9_.]*$` + } else { + pattern = `^[/][a-zA-Z0-9~/_.-]*$` + } + re, err := regexp.Compile(pattern) + if err != nil { + return "Failed to compile basePath regex pattern" + } + if !re.MatchString(basePath) { + return "API basePath is not in a valid format for the specified API type" + } + return "" +} + // getBasePathWithoutVersion returns the basePath without version func getBasePathWithoutVersion(basePath string) string { lastIndex := strings.LastIndex(basePath, "/") From fe60f67cd5d5f9853ea153ab27750fa1c4c5ced8 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 18 Mar 2024 19:15:20 +0530 Subject: [PATCH 34/81] change test files --- .../integration/integration/tests/grpc-api.go | 160 +++++++++++++++--- .../tests/resources/base/manifests.yaml | 14 +- .../tests/resources/tests/grpc-api.yaml | 45 ++--- test/integration/scripts/run-tests.sh | 1 + test/integration/scripts/setup-hosts.sh | 1 + 5 files changed, 160 insertions(+), 61 deletions(-) diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 69738db0f..8dcba85e1 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -18,9 +18,14 @@ package tests import ( + "context" + "crypto/tls" + "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "testing" + "time" - "github.com/wso2/apk/test/integration/integration/utils/http" "github.com/wso2/apk/test/integration/integration/utils/suite" ) @@ -28,40 +33,147 @@ func init() { IntegrationTests = append(IntegrationTests, GRPCAPI) } -// DisableAPISecurity test +// GRPCAPI tests gRPC API var GRPCAPI = suite.IntegrationTest{ ShortName: "GRPCAPI", Description: "Tests gRPC API", - Manifests: []string{"tests/grpc-api.yaml"}, + //Manifests: []string{"tests/grpc-api.yaml"}, Test: func(t *testing.T, suite *suite.IntegrationTestSuite) { gwAddr := "grpc.test.gw.wso2.com:9095" //token := http.GetTestToken(t) - testCases := []http.ExpectedResponse{ - //{ - // Request: http.Request{ - // Host: "gql.test.gw.wso2.com", - // Path: "/gql/v1", - // Method: "POST", - // Headers: map[string]string{ - // "Content-Type": "application/json", - // }, - // Body: `{"query":"query{\n human(id:1000){\n id\n name\n }\n}","variables":{}}`, - // }, - // ExpectedRequest: &http.ExpectedRequest{ - // Request: http.Request{ - // Method: ""}, - // }, - // Response: http.Response{StatusCode: 200}, - //}, - + //testCases := []http.ExpectedResponse{ + //{ + // Request: http.Request{ + // Host: "gql.test.gw.wso2.com", + // Path: "/gql/v1", + // Method: "POST", + // Headers: map[string]string{ + // "Content-Type": "application/json", + // }, + // Body: `{"query":"query{\n human(id:1000){\n id\n name\n }\n}","variables":{}}`, + // }, + // ExpectedRequest: &http.ExpectedRequest{ + // Request: http.Request{ + // Method: ""}, + // }, + // Response: http.Response{StatusCode: 200}, + //}, + //} + testCases := []ExpectedResponse{ + { + out: &student.StudentResponse{ + Name: "Dineth", + Age: 10, + }, + err: nil, + }, } for i := range testCases { tc := testCases[i] - t.Run(tc.GetTestCaseName(i), func(t *testing.T) { - t.Parallel() - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + //t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + // t.Parallel() + // http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + //}) + t.Run("Invoke gRPC API", func(t *testing.T) { + out, err := invokeGRPCClientUntilSatisfied(gwAddr, t) + if err != nil { + if tc.err != nil { + t.Errorf("Err -> \nWant: %q\nGot: %q\n", tc.err, err) + } + } else { + if tc.out.Name != out.Name || + tc.out.Age != out.Age { + t.Errorf("Out -> \nWant: %q\nGot : %q", tc.out, out) + } + } + }) } }, } + +func invokeGRPCClient(gwAddr string, t *testing.T) (*student.StudentResponse, error) { + + t.Logf("Starting gRPC client...") + + // Set up TLS credentials for the connection without enforcing server certificate validation. + t.Logf("Setting up TLS credentials without server certificate validation...") + config := &tls.Config{ + InsecureSkipVerify: true, // CAUTION: This disables SSL certificate verification. + } + creds := credentials.NewTLS(config) + + // Dial the server with the TLS credentials and a dial timeout. + dialCtx, dialCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer dialCancel() + t.Logf("Dialing to server at %s with timeout...", gwAddr) + conn, err := grpc.DialContext(dialCtx, gwAddr, grpc.WithTransportCredentials(creds), grpc.WithBlock()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer conn.Close() + t.Log("Successfully connected to the server.") + + c := student.NewStudentServiceClient(conn) + + // Prepare the context with a timeout for the request. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + t.Log("Sending request to the server...") + // Create a StudentRequest message + r := &student.StudentRequest{Id: 1234} // Adjust the ID according to your actual implementation + response, err := c.GetStudent(ctx, r) + if err != nil { + t.Logf("Could not fetch student: %v", err) + } + + t.Logf("Received response from server: %v\n", response) + t.Logf("Student Details: %v\n", response) + return response, nil +} + +type ExpectedResponse struct { + out *student.StudentResponse + err error +} + +func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T) (*student.StudentResponse, error) { + var out *student.StudentResponse + var err error + attempt := 0 + maxAttempts := 4 + + for attempt < maxAttempts { + t.Logf("Attempt %d to invoke gRPC client...", attempt+1) + out, err = invokeGRPCClient(gwAddr, t) + + if err != nil { + t.Logf("Error on attempt %d: %v", attempt+1, err) + } else { + // Check if the response is satisfactory. This condition needs to be defined. + // For example, assuming a satisfactory condition is when out.Satisfied is true. + // This is a placeholder condition and should be replaced with your actual success criteria. + if out != nil && isResponseSatisfactory(out) { + t.Logf("Satisfactory response received: %+v", out) + return out, nil + } + } + + if attempt < maxAttempts-1 { + t.Logf("Waiting 20 seconds before next attempt...") + time.Sleep(20 * time.Second) + } + attempt++ + } + + t.Logf("Failed to receive a satisfactory response after %d attempts", maxAttempts) + return out, err // Returning the last response and error, might need adjustment based on requirements. +} + +func isResponseSatisfactory(response *student.StudentResponse) bool { + // Define the condition for a response to be considered satisfactory. + // This is a placeholder function and should contain actual logic to evaluate the response. + return false // Placeholder: assume every response is satisfactory. +} diff --git a/test/integration/integration/tests/resources/base/manifests.yaml b/test/integration/integration/tests/resources/base/manifests.yaml index fb369f030..58883fa98 100644 --- a/test/integration/integration/tests/resources/base/manifests.yaml +++ b/test/integration/integration/tests/resources/base/manifests.yaml @@ -114,11 +114,11 @@ spec: apiVersion: v1 kind: Service metadata: - name: dineth-grpc-demo-server + name: grpc-backend-v1 namespace: gateway-integration-test-infra spec: selector: - app: dineth-grpc-demo-server + app: grpc-backend-v1 ports: - protocol: TCP port: 6565 @@ -127,22 +127,22 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: dineth-grpc-demo-server + name: grpc-backend-v1 namespace: gateway-integration-test-infra labels: - app: dineth-grpc-demo-server + app: grpc-backend-v1 spec: replicas: 1 selector: matchLabels: - app: dineth-grpc-demo-server + app: grpc-backend-v1 template: metadata: labels: - app: dineth-grpc-demo-server + app: grpc-backend-v1 spec: containers: - - name: dineth-grpc-demo-server + - name: grpc-backend-v1 image: ddh13/dineth-grpc-demo-server:1.0.0 imagePullPolicy: Always env: diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml index 71f80a74b..e8585acea 100644 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -17,22 +17,17 @@ apiVersion: dp.wso2.com/v1alpha2 kind: API metadata: - name: dineth-grpc-api + name: grpc-api namespace: gateway-integration-test-infra - labels: - api-name: "dineth-grpc-api" - api-version: "v1" - organization: wso2-org - managed-by: "apk" spec: - apiName: Dineth GRPC API + apiName: GRPC API apiType: GRPC apiVersion: v1 basePath: /dineth.grpc.api.v1 isDefaultVersion: false production: - routeRefs: - - dineth-grpc-route + - grpc-api-grpcroute organization: wso2-org --- @@ -40,28 +35,24 @@ spec: apiVersion: gateway.networking.k8s.io/v1alpha2 kind: GRPCRoute metadata: - name: dineth-grpc-route + name: grpc-api-grpcroute namespace: gateway-integration-test-infra - labels: - api-name: "dineth-grpc-api" - api-version: "v1" - organization: "wso2-org" - managed-by: "apk" spec: - hostnames: - - grpc.test.gw.wso2.com parentRefs: - group: gateway.networking.k8s.io kind: Gateway - name: default + name: wso2-apk-default + namespace: apk-integration-test sectionName: httpslistener + hostnames: + - grpc.test.gw.wso2.com rules: - matches: - method: service: student.StudentService method: GetStudent backendRefs: - - name: dineth-grpc-backend + - name: grpc-backend-v1 kind: Backend port: 6565 - matches: @@ -69,7 +60,7 @@ spec: service: student.StudentService method: SendStudentStream backendRefs: - - name: dineth-grpc-backend + - name: grpc-backend-v1 kind: Backend port: 6565 - matches: @@ -77,7 +68,7 @@ spec: service: student.StudentService method: GetStudentStream backendRefs: - - name: dineth-grpc-backend + - name: grpc-backend-v1 kind: Backend port: 6565 - matches: @@ -85,7 +76,7 @@ spec: service: student.StudentService method: SendAndGetStudentStream backendRefs: - - name: dineth-grpc-backend + - name: grpc-backend-v1 kind: Backend port: 6565 @@ -103,22 +94,16 @@ spec: group: gateway.networking.k8s.io kind: API namespace: gateway-integration-test-infra - name: dineth-grpc-api + name: grpc-api --- - apiVersion: dp.wso2.com/v1alpha1 kind: Backend metadata: - name: dineth-grpc-backend + name: grpc-backend-v1 namespace: gateway-integration-test-infra - labels: - api-name: "dineth-grpc-api" - api-version: "v1" - organization: wso2-org - managed-by: "apk" spec: services: - - host: grpc-backend.gateway-integration-test-infra + - host: grpc-backend-v1.gateway-integration-test-infra port: 6565 basePath: "" protocol: https diff --git a/test/integration/scripts/run-tests.sh b/test/integration/scripts/run-tests.sh index 7a50bd056..ecdd329ac 100644 --- a/test/integration/scripts/run-tests.sh +++ b/test/integration/scripts/run-tests.sh @@ -79,6 +79,7 @@ sudo echo "$IP ratelimit-priority.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP different-endpoint-with-same-route.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP custom-auth-header.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP gql.test.gw.wso2.com" | sudo tee -a /etc/hosts +sudo echo "$IP grpc.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP api-level-jwt.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP resource-level-jwt.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "255.255.255.255 broadcasthost" | sudo tee -a /etc/hosts diff --git a/test/integration/scripts/setup-hosts.sh b/test/integration/scripts/setup-hosts.sh index 7ff4ff8af..ec36022eb 100644 --- a/test/integration/scripts/setup-hosts.sh +++ b/test/integration/scripts/setup-hosts.sh @@ -38,6 +38,7 @@ sudo echo "$IP ratelimit-priority.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP different-endpoint-with-same-route.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP custom-auth-header.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP gql.test.gw.wso2.com" | sudo tee -a /etc/hosts +sudo echo "$IP grpc.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP api-level-jwt.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "$IP resource-level-jwt.test.gw.wso2.com" | sudo tee -a /etc/hosts sudo echo "255.255.255.255 broadcasthost" | sudo tee -a /etc/hosts From 19084f0ee083f45d922bf09dedc9438bde813b25 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 19 Mar 2024 09:31:59 +0530 Subject: [PATCH 35/81] add testcases and helpers --- .../integration/integration/tests/grpc-api.go | 75 ++++++++----------- .../integration/utils/grpcutils/helpers.go | 18 +++++ 2 files changed, 50 insertions(+), 43 deletions(-) create mode 100644 test/integration/integration/utils/grpcutils/helpers.go diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 8dcba85e1..5eb2bf734 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -21,8 +21,10 @@ import ( "context" "crypto/tls" "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" + "github.com/wso2/apk/test/integration/integration/utils/grpcutils" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "sigs.k8s.io/gateway-api/conformance/utils/config" "testing" "time" @@ -37,7 +39,7 @@ func init() { var GRPCAPI = suite.IntegrationTest{ ShortName: "GRPCAPI", Description: "Tests gRPC API", - //Manifests: []string{"tests/grpc-api.yaml"}, + Manifests: []string{"tests/grpc-api.yaml"}, Test: func(t *testing.T, suite *suite.IntegrationTestSuite) { gwAddr := "grpc.test.gw.wso2.com:9095" //token := http.GetTestToken(t) @@ -60,13 +62,18 @@ var GRPCAPI = suite.IntegrationTest{ // Response: http.Response{StatusCode: 200}, //}, //} - testCases := []ExpectedResponse{ + + testCases := []grpcutils.GRPCTestCase{ { - out: &student.StudentResponse{ - Name: "Dineth", - Age: 10, + ExpectedResponse: grpcutils.ExpectedResponse{ + Out: &student.StudentResponse{ + Name: "Dineth", + Age: 10, + }, + Err: nil, }, - err: nil, + ActualResponse: &student.StudentResponse{}, + Name: "Get Student Details", }, } for i := range testCases { @@ -76,17 +83,8 @@ var GRPCAPI = suite.IntegrationTest{ // http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) //}) t.Run("Invoke gRPC API", func(t *testing.T) { - out, err := invokeGRPCClientUntilSatisfied(gwAddr, t) - if err != nil { - if tc.err != nil { - t.Errorf("Err -> \nWant: %q\nGot: %q\n", tc.err, err) - } - } else { - if tc.out.Name != out.Name || - tc.out.Age != out.Age { - t.Errorf("Out -> \nWant: %q\nGot : %q", tc.out, out) - } - } + t.Parallel() + invokeGRPCClientUntilSatisfied(gwAddr, t, tc, suite.TimeoutConfig) }) } @@ -97,8 +95,6 @@ func invokeGRPCClient(gwAddr string, t *testing.T) (*student.StudentResponse, er t.Logf("Starting gRPC client...") - // Set up TLS credentials for the connection without enforcing server certificate validation. - t.Logf("Setting up TLS credentials without server certificate validation...") config := &tls.Config{ InsecureSkipVerify: true, // CAUTION: This disables SSL certificate verification. } @@ -110,10 +106,10 @@ func invokeGRPCClient(gwAddr string, t *testing.T) (*student.StudentResponse, er t.Logf("Dialing to server at %s with timeout...", gwAddr) conn, err := grpc.DialContext(dialCtx, gwAddr, grpc.WithTransportCredentials(creds), grpc.WithBlock()) if err != nil { - t.Fatalf("Failed to connect: %v", err) + t.Logf("Failed to connect: %v", err) } defer conn.Close() - t.Log("Successfully connected to the server.") + //t.Log("Successfully connected to the server.") c := student.NewStudentServiceClient(conn) @@ -123,28 +119,23 @@ func invokeGRPCClient(gwAddr string, t *testing.T) (*student.StudentResponse, er t.Log("Sending request to the server...") // Create a StudentRequest message - r := &student.StudentRequest{Id: 1234} // Adjust the ID according to your actual implementation + r := &student.StudentRequest{Id: 1234} response, err := c.GetStudent(ctx, r) if err != nil { t.Logf("Could not fetch student: %v", err) } - t.Logf("Received response from server: %v\n", response) - t.Logf("Student Details: %v\n", response) return response, nil } -type ExpectedResponse struct { - out *student.StudentResponse - err error -} - -func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T) (*student.StudentResponse, error) { +func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcutils.GRPCTestCase, timeout config.TimeoutConfig) { var out *student.StudentResponse var err error attempt := 0 maxAttempts := 4 - + expected := testCase.ExpectedResponse + //timeoutDuration := timeout.RequestTimeout * time.Second + timeoutDuration := 10 * time.Second for attempt < maxAttempts { t.Logf("Attempt %d to invoke gRPC client...", attempt+1) out, err = invokeGRPCClient(gwAddr, t) @@ -152,28 +143,26 @@ func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T) (*student.Stude if err != nil { t.Logf("Error on attempt %d: %v", attempt+1, err) } else { - // Check if the response is satisfactory. This condition needs to be defined. - // For example, assuming a satisfactory condition is when out.Satisfied is true. - // This is a placeholder condition and should be replaced with your actual success criteria. - if out != nil && isResponseSatisfactory(out) { + if out != nil && isResponseSatisfactory(out, expected) { t.Logf("Satisfactory response received: %+v", out) - return out, nil + return } } if attempt < maxAttempts-1 { - t.Logf("Waiting 20 seconds before next attempt...") - time.Sleep(20 * time.Second) + t.Logf("Waiting %s seconds before next attempt...", timeoutDuration) + time.Sleep(timeoutDuration) } attempt++ } t.Logf("Failed to receive a satisfactory response after %d attempts", maxAttempts) - return out, err // Returning the last response and error, might need adjustment based on requirements. + t.Fail() } -func isResponseSatisfactory(response *student.StudentResponse) bool { - // Define the condition for a response to be considered satisfactory. - // This is a placeholder function and should contain actual logic to evaluate the response. - return false // Placeholder: assume every response is satisfactory. +func isResponseSatisfactory(response *student.StudentResponse, expectedResponse grpcutils.ExpectedResponse) bool { + if response.Name == expectedResponse.Out.Name && response.Age == expectedResponse.Out.Age { + return true + } + return false } diff --git a/test/integration/integration/utils/grpcutils/helpers.go b/test/integration/integration/utils/grpcutils/helpers.go new file mode 100644 index 000000000..71ed6d92c --- /dev/null +++ b/test/integration/integration/utils/grpcutils/helpers.go @@ -0,0 +1,18 @@ +package grpcutils + +import "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" + +type ExpectedResponse struct { + Out *student.StudentResponse + Err error +} +type Request struct { + Host string + Headers map[string]string +} +type GRPCTestCase struct { + Request Request + ExpectedResponse ExpectedResponse + ActualResponse *student.StudentResponse + Name string +} From 16498abdd233a23a11b3045db960360bacb95aef Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 19 Mar 2024 09:32:18 +0530 Subject: [PATCH 36/81] add generated client code --- .../utils/generatedcode/student/student.pb.go | 254 ++++++++++++++ .../generatedcode/student/student_grpc.pb.go | 314 ++++++++++++++++++ 2 files changed, 568 insertions(+) create mode 100644 test/integration/integration/utils/generatedcode/student/student.pb.go create mode 100644 test/integration/integration/utils/generatedcode/student/student_grpc.pb.go diff --git a/test/integration/integration/utils/generatedcode/student/student.pb.go b/test/integration/integration/utils/generatedcode/student/student.pb.go new file mode 100644 index 000000000..c8efeffa3 --- /dev/null +++ b/test/integration/integration/utils/generatedcode/student/student.pb.go @@ -0,0 +1,254 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc v3.12.4 +// source: student.proto + +package student + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StudentRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *StudentRequest) Reset() { + *x = StudentRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_student_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StudentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StudentRequest) ProtoMessage() {} + +func (x *StudentRequest) ProtoReflect() protoreflect.Message { + mi := &file_student_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StudentRequest.ProtoReflect.Descriptor instead. +func (*StudentRequest) Descriptor() ([]byte, []int) { + return file_student_proto_rawDescGZIP(), []int{0} +} + +func (x *StudentRequest) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +type StudentResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` +} + +func (x *StudentResponse) Reset() { + *x = StudentResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_student_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StudentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StudentResponse) ProtoMessage() {} + +func (x *StudentResponse) ProtoReflect() protoreflect.Message { + mi := &file_student_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StudentResponse.ProtoReflect.Descriptor instead. +func (*StudentResponse) Descriptor() ([]byte, []int) { + return file_student_proto_rawDescGZIP(), []int{1} +} + +func (x *StudentResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *StudentResponse) GetAge() int32 { + if x != nil { + return x.Age + } + return 0 +} + +var File_student_proto protoreflect.FileDescriptor + +var file_student_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x1a, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x22, 0x20, 0x0a, 0x0e, 0x53, + 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0x37, 0x0a, + 0x0f, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x03, 0x61, 0x67, 0x65, 0x32, 0xd6, 0x03, 0x0a, 0x0e, 0x53, 0x74, 0x75, 0x64, 0x65, + 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x67, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, 0x75, + 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, + 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x6f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, 0x75, 0x64, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, + 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x30, 0x01, 0x12, 0x70, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x75, 0x64, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, + 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, + 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, + 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x78, 0x0a, 0x17, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x6e, 0x64, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, + 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, + 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, + 0x30, 0x5a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, + 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x73, 0x2f, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2f, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, + 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_student_proto_rawDescOnce sync.Once + file_student_proto_rawDescData = file_student_proto_rawDesc +) + +func file_student_proto_rawDescGZIP() []byte { + file_student_proto_rawDescOnce.Do(func() { + file_student_proto_rawDescData = protoimpl.X.CompressGZIP(file_student_proto_rawDescData) + }) + return file_student_proto_rawDescData +} + +var file_student_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_student_proto_goTypes = []interface{}{ + (*StudentRequest)(nil), // 0: dineth.grpc.api.v1.student.StudentRequest + (*StudentResponse)(nil), // 1: dineth.grpc.api.v1.student.StudentResponse +} +var file_student_proto_depIdxs = []int32{ + 0, // 0: dineth.grpc.api.v1.student.StudentService.GetStudent:input_type -> dineth.grpc.api.v1.student.StudentRequest + 0, // 1: dineth.grpc.api.v1.student.StudentService.GetStudentStream:input_type -> dineth.grpc.api.v1.student.StudentRequest + 0, // 2: dineth.grpc.api.v1.student.StudentService.SendStudentStream:input_type -> dineth.grpc.api.v1.student.StudentRequest + 0, // 3: dineth.grpc.api.v1.student.StudentService.SendAndGetStudentStream:input_type -> dineth.grpc.api.v1.student.StudentRequest + 1, // 4: dineth.grpc.api.v1.student.StudentService.GetStudent:output_type -> dineth.grpc.api.v1.student.StudentResponse + 1, // 5: dineth.grpc.api.v1.student.StudentService.GetStudentStream:output_type -> dineth.grpc.api.v1.student.StudentResponse + 1, // 6: dineth.grpc.api.v1.student.StudentService.SendStudentStream:output_type -> dineth.grpc.api.v1.student.StudentResponse + 1, // 7: dineth.grpc.api.v1.student.StudentService.SendAndGetStudentStream:output_type -> dineth.grpc.api.v1.student.StudentResponse + 4, // [4:8] is the sub-list for method output_type + 0, // [0:4] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_student_proto_init() } +func file_student_proto_init() { + if File_student_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_student_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StudentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_student_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StudentResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_student_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_student_proto_goTypes, + DependencyIndexes: file_student_proto_depIdxs, + MessageInfos: file_student_proto_msgTypes, + }.Build() + File_student_proto = out.File + file_student_proto_rawDesc = nil + file_student_proto_goTypes = nil + file_student_proto_depIdxs = nil +} diff --git a/test/integration/integration/utils/generatedcode/student/student_grpc.pb.go b/test/integration/integration/utils/generatedcode/student/student_grpc.pb.go new file mode 100644 index 000000000..be4b29e7d --- /dev/null +++ b/test/integration/integration/utils/generatedcode/student/student_grpc.pb.go @@ -0,0 +1,314 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.12.4 +// source: student.proto + +package student + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + StudentService_GetStudent_FullMethodName = "/dineth.grpc.api.v1.student.StudentService/GetStudent" + StudentService_GetStudentStream_FullMethodName = "/dineth.grpc.api.v1.student.StudentService/GetStudentStream" + StudentService_SendStudentStream_FullMethodName = "/dineth.grpc.api.v1.student.StudentService/SendStudentStream" + StudentService_SendAndGetStudentStream_FullMethodName = "/dineth.grpc.api.v1.student.StudentService/SendAndGetStudentStream" +) + +// StudentServiceClient is the client API for StudentService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StudentServiceClient interface { + GetStudent(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (*StudentResponse, error) + GetStudentStream(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (StudentService_GetStudentStreamClient, error) + SendStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendStudentStreamClient, error) + SendAndGetStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendAndGetStudentStreamClient, error) +} + +type studentServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewStudentServiceClient(cc grpc.ClientConnInterface) StudentServiceClient { + return &studentServiceClient{cc} +} + +func (c *studentServiceClient) GetStudent(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (*StudentResponse, error) { + out := new(StudentResponse) + err := c.cc.Invoke(ctx, StudentService_GetStudent_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *studentServiceClient) GetStudentStream(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (StudentService_GetStudentStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &StudentService_ServiceDesc.Streams[0], StudentService_GetStudentStream_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &studentServiceGetStudentStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type StudentService_GetStudentStreamClient interface { + Recv() (*StudentResponse, error) + grpc.ClientStream +} + +type studentServiceGetStudentStreamClient struct { + grpc.ClientStream +} + +func (x *studentServiceGetStudentStreamClient) Recv() (*StudentResponse, error) { + m := new(StudentResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *studentServiceClient) SendStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendStudentStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &StudentService_ServiceDesc.Streams[1], StudentService_SendStudentStream_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &studentServiceSendStudentStreamClient{stream} + return x, nil +} + +type StudentService_SendStudentStreamClient interface { + Send(*StudentRequest) error + CloseAndRecv() (*StudentResponse, error) + grpc.ClientStream +} + +type studentServiceSendStudentStreamClient struct { + grpc.ClientStream +} + +func (x *studentServiceSendStudentStreamClient) Send(m *StudentRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *studentServiceSendStudentStreamClient) CloseAndRecv() (*StudentResponse, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(StudentResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *studentServiceClient) SendAndGetStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendAndGetStudentStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &StudentService_ServiceDesc.Streams[2], StudentService_SendAndGetStudentStream_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &studentServiceSendAndGetStudentStreamClient{stream} + return x, nil +} + +type StudentService_SendAndGetStudentStreamClient interface { + Send(*StudentRequest) error + Recv() (*StudentResponse, error) + grpc.ClientStream +} + +type studentServiceSendAndGetStudentStreamClient struct { + grpc.ClientStream +} + +func (x *studentServiceSendAndGetStudentStreamClient) Send(m *StudentRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *studentServiceSendAndGetStudentStreamClient) Recv() (*StudentResponse, error) { + m := new(StudentResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// StudentServiceServer is the server API for StudentService service. +// All implementations must embed UnimplementedStudentServiceServer +// for forward compatibility +type StudentServiceServer interface { + GetStudent(context.Context, *StudentRequest) (*StudentResponse, error) + GetStudentStream(*StudentRequest, StudentService_GetStudentStreamServer) error + SendStudentStream(StudentService_SendStudentStreamServer) error + SendAndGetStudentStream(StudentService_SendAndGetStudentStreamServer) error + mustEmbedUnimplementedStudentServiceServer() +} + +// UnimplementedStudentServiceServer must be embedded to have forward compatible implementations. +type UnimplementedStudentServiceServer struct { +} + +func (UnimplementedStudentServiceServer) GetStudent(context.Context, *StudentRequest) (*StudentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStudent not implemented") +} +func (UnimplementedStudentServiceServer) GetStudentStream(*StudentRequest, StudentService_GetStudentStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetStudentStream not implemented") +} +func (UnimplementedStudentServiceServer) SendStudentStream(StudentService_SendStudentStreamServer) error { + return status.Errorf(codes.Unimplemented, "method SendStudentStream not implemented") +} +func (UnimplementedStudentServiceServer) SendAndGetStudentStream(StudentService_SendAndGetStudentStreamServer) error { + return status.Errorf(codes.Unimplemented, "method SendAndGetStudentStream not implemented") +} +func (UnimplementedStudentServiceServer) mustEmbedUnimplementedStudentServiceServer() {} + +// UnsafeStudentServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StudentServiceServer will +// result in compilation errors. +type UnsafeStudentServiceServer interface { + mustEmbedUnimplementedStudentServiceServer() +} + +func RegisterStudentServiceServer(s grpc.ServiceRegistrar, srv StudentServiceServer) { + s.RegisterService(&StudentService_ServiceDesc, srv) +} + +func _StudentService_GetStudent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StudentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StudentServiceServer).GetStudent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StudentService_GetStudent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StudentServiceServer).GetStudent(ctx, req.(*StudentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StudentService_GetStudentStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(StudentRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StudentServiceServer).GetStudentStream(m, &studentServiceGetStudentStreamServer{stream}) +} + +type StudentService_GetStudentStreamServer interface { + Send(*StudentResponse) error + grpc.ServerStream +} + +type studentServiceGetStudentStreamServer struct { + grpc.ServerStream +} + +func (x *studentServiceGetStudentStreamServer) Send(m *StudentResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _StudentService_SendStudentStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StudentServiceServer).SendStudentStream(&studentServiceSendStudentStreamServer{stream}) +} + +type StudentService_SendStudentStreamServer interface { + SendAndClose(*StudentResponse) error + Recv() (*StudentRequest, error) + grpc.ServerStream +} + +type studentServiceSendStudentStreamServer struct { + grpc.ServerStream +} + +func (x *studentServiceSendStudentStreamServer) SendAndClose(m *StudentResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *studentServiceSendStudentStreamServer) Recv() (*StudentRequest, error) { + m := new(StudentRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _StudentService_SendAndGetStudentStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StudentServiceServer).SendAndGetStudentStream(&studentServiceSendAndGetStudentStreamServer{stream}) +} + +type StudentService_SendAndGetStudentStreamServer interface { + Send(*StudentResponse) error + Recv() (*StudentRequest, error) + grpc.ServerStream +} + +type studentServiceSendAndGetStudentStreamServer struct { + grpc.ServerStream +} + +func (x *studentServiceSendAndGetStudentStreamServer) Send(m *StudentResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *studentServiceSendAndGetStudentStreamServer) Recv() (*StudentRequest, error) { + m := new(StudentRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// StudentService_ServiceDesc is the grpc.ServiceDesc for StudentService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var StudentService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dineth.grpc.api.v1.student.StudentService", + HandlerType: (*StudentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetStudent", + Handler: _StudentService_GetStudent_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "GetStudentStream", + Handler: _StudentService_GetStudentStream_Handler, + ServerStreams: true, + }, + { + StreamName: "SendStudentStream", + Handler: _StudentService_SendStudentStream_Handler, + ClientStreams: true, + }, + { + StreamName: "SendAndGetStudentStream", + Handler: _StudentService_SendAndGetStudentStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "student.proto", +} From dfe02b478e68cc387b1546d2e0ddb60e71ace808 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 21 Mar 2024 09:25:41 +0530 Subject: [PATCH 37/81] change test case timeout --- test/integration/integration/tests/grpc-api.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 5eb2bf734..49c1ce4d3 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -91,7 +91,7 @@ var GRPCAPI = suite.IntegrationTest{ }, } -func invokeGRPCClient(gwAddr string, t *testing.T) (*student.StudentResponse, error) { +func invokeGRPCClient(gwAddr string, t *testing.T, timeout config.TimeoutConfig) (*student.StudentResponse, error) { t.Logf("Starting gRPC client...") @@ -129,16 +129,18 @@ func invokeGRPCClient(gwAddr string, t *testing.T) (*student.StudentResponse, er } func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcutils.GRPCTestCase, timeout config.TimeoutConfig) { + //(delay to allow CRs to be applied) + time.Sleep(5 * time.Second) var out *student.StudentResponse var err error attempt := 0 maxAttempts := 4 expected := testCase.ExpectedResponse //timeoutDuration := timeout.RequestTimeout * time.Second - timeoutDuration := 10 * time.Second + timeoutDuration := 50 * time.Second for attempt < maxAttempts { t.Logf("Attempt %d to invoke gRPC client...", attempt+1) - out, err = invokeGRPCClient(gwAddr, t) + out, err = invokeGRPCClient(gwAddr, t, timeout) if err != nil { t.Logf("Error on attempt %d: %v", attempt+1, err) From 6bb830dbe92da5ec47e523c3054629fa3a387995 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 26 Mar 2024 11:26:55 +0530 Subject: [PATCH 38/81] changed request timeout --- .../integration/integration/tests/grpc-api.go | 34 +++---------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 49c1ce4d3..37c01f8e3 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -42,26 +42,6 @@ var GRPCAPI = suite.IntegrationTest{ Manifests: []string{"tests/grpc-api.yaml"}, Test: func(t *testing.T, suite *suite.IntegrationTestSuite) { gwAddr := "grpc.test.gw.wso2.com:9095" - //token := http.GetTestToken(t) - - //testCases := []http.ExpectedResponse{ - //{ - // Request: http.Request{ - // Host: "gql.test.gw.wso2.com", - // Path: "/gql/v1", - // Method: "POST", - // Headers: map[string]string{ - // "Content-Type": "application/json", - // }, - // Body: `{"query":"query{\n human(id:1000){\n id\n name\n }\n}","variables":{}}`, - // }, - // ExpectedRequest: &http.ExpectedRequest{ - // Request: http.Request{ - // Method: ""}, - // }, - // Response: http.Response{StatusCode: 200}, - //}, - //} testCases := []grpcutils.GRPCTestCase{ { @@ -78,10 +58,6 @@ var GRPCAPI = suite.IntegrationTest{ } for i := range testCases { tc := testCases[i] - //t.Run(tc.GetTestCaseName(i), func(t *testing.T) { - // t.Parallel() - // http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) - //}) t.Run("Invoke gRPC API", func(t *testing.T) { t.Parallel() invokeGRPCClientUntilSatisfied(gwAddr, t, tc, suite.TimeoutConfig) @@ -96,12 +72,12 @@ func invokeGRPCClient(gwAddr string, t *testing.T, timeout config.TimeoutConfig) t.Logf("Starting gRPC client...") config := &tls.Config{ - InsecureSkipVerify: true, // CAUTION: This disables SSL certificate verification. + InsecureSkipVerify: true, } creds := credentials.NewTLS(config) // Dial the server with the TLS credentials and a dial timeout. - dialCtx, dialCancel := context.WithTimeout(context.Background(), 10*time.Second) + dialCtx, dialCancel := context.WithTimeout(context.Background(), 60*time.Second) defer dialCancel() t.Logf("Dialing to server at %s with timeout...", gwAddr) conn, err := grpc.DialContext(dialCtx, gwAddr, grpc.WithTransportCredentials(creds), grpc.WithBlock()) @@ -114,7 +90,7 @@ func invokeGRPCClient(gwAddr string, t *testing.T, timeout config.TimeoutConfig) c := student.NewStudentServiceClient(conn) // Prepare the context with a timeout for the request. - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), timeout.RequestTimeout) defer cancel() t.Log("Sending request to the server...") @@ -123,8 +99,9 @@ func invokeGRPCClient(gwAddr string, t *testing.T, timeout config.TimeoutConfig) response, err := c.GetStudent(ctx, r) if err != nil { t.Logf("Could not fetch student: %v", err) + t.Logf("Error: %v\n", response) } - t.Logf("Received response from server: %v\n", response) + return response, nil } @@ -136,7 +113,6 @@ func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcut attempt := 0 maxAttempts := 4 expected := testCase.ExpectedResponse - //timeoutDuration := timeout.RequestTimeout * time.Second timeoutDuration := 50 * time.Second for attempt < maxAttempts { t.Logf("Attempt %d to invoke gRPC client...", attempt+1) From cc05715ef11d8bbc739f9b723d5c49990bfda6b3 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 15 Apr 2024 14:27:02 +0530 Subject: [PATCH 39/81] fix after release --- .../operator/synchronizer/grpc_api.go | 87 ++++++++++--------- .../operator/synchronizer/synchronizer.go | 14 +-- 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/adapter/internal/operator/synchronizer/grpc_api.go b/adapter/internal/operator/synchronizer/grpc_api.go index 3c0671ed8..aadf86098 100644 --- a/adapter/internal/operator/synchronizer/grpc_api.go +++ b/adapter/internal/operator/synchronizer/grpc_api.go @@ -18,7 +18,6 @@ package synchronizer import ( "errors" - "fmt" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "github.com/wso2/apk/adapter/config" @@ -32,8 +31,36 @@ import ( gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) +// extract APIDetails from the GQLRoute +func updateInternalMapsFromGRPCRoute(apiState APIState, grpcRoute *GRPCRouteState, envType string) (*model.AdapterInternalAPI, map[string]struct{}, error) { + adapterInternalAPI, err := generateGRPCAdapterInternalAPI(apiState, grpcRoute, envType) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error generating AdapterInternalAPI for GRPCRoute: %v. %v", grpcRoute.GRPCRouteCombined.Name, err)) + return nil, nil, err + } + + vHosts := getVhostsForGRPCAPI(grpcRoute.GRPCRouteCombined) + labels := getLabelsForGRPCAPI(grpcRoute.GRPCRouteCombined) + listeners, relativeSectionNames := getListenersForGRPCAPI(grpcRoute.GRPCRouteCombined, adapterInternalAPI.UUID) + // We dont have a use case where a perticular API's two different grpc routes refer to two different gateway. Hence get the first listener name for the list for processing. + if len(listeners) == 0 || len(relativeSectionNames) == 0 { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MINOR, "Failed to find a matching listener for grpc route: %v. ", + grpcRoute.GRPCRouteCombined.Name)) + return nil, nil, errors.New("failed to find matching listener name for the provided grpc route") + } + listenerName := listeners[0] + sectionName := relativeSectionNames[0] + + if len(listeners) > 0 { + if err := xds.PopulateInternalMaps(adapterInternalAPI, labels, vHosts, sectionName, listenerName); err != nil { + return nil, nil, err + } + } + return adapterInternalAPI, labels, nil +} + // generateGRPCAdapterInternalAPI this will populate a AdapterInternalAPI representation for an GRPCRoute -func generateGRPCAdapterInternalAPI(apiState APIState, grpcRoute *GRPCRouteState, envType string) (*model.AdapterInternalAPI, map[string]struct{}, error) { +func generateGRPCAdapterInternalAPI(apiState APIState, grpcRoute *GRPCRouteState, envType string) (*model.AdapterInternalAPI, error) { var adapterInternalAPI model.AdapterInternalAPI adapterInternalAPI.SetIsDefaultVersion(apiState.APIDefinition.Spec.IsDefaultVersion) adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) @@ -48,6 +75,7 @@ func generateGRPCAdapterInternalAPI(apiState APIState, grpcRoute *GRPCRouteState environment = conf.Adapter.Environment } adapterInternalAPI.SetEnvironment(environment) + adapterInternalAPI.SetXWso2RequestBodyPass(true) resourceParams := model.ResourceParams{ AuthSchemes: apiState.Authentications, @@ -63,60 +91,38 @@ func generateGRPCAdapterInternalAPI(apiState APIState, grpcRoute *GRPCRouteState } if err := adapterInternalAPI.SetInfoGRPCRouteCR(grpcRoute.GRPCRouteCombined, resourceParams); err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting GRPCRoute CR info to adapterInternalAPI. %v", err)) - return nil, nil, err + return nil, err } - if err := adapterInternalAPI.Validate(); err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error validating adapterInternalAPI intermediate representation. %v", err)) - return nil, nil, err - } - vHosts := getVhostsForGRPCAPI(grpcRoute.GRPCRouteCombined) - labels := getLabelsForGRPCAPI(grpcRoute.GRPCRouteCombined) - listeners, relativeSectionNames := getListenersForGRPCAPI(grpcRoute.GRPCRouteCombined, adapterInternalAPI.UUID) - // We don't have a use case where a perticular API's two different grpc routes refer to two different gateway. Hence get the first listener name for the list for processing. - if len(listeners) == 0 || len(relativeSectionNames) == 0 { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MINOR, "Failed to find a matching listener for grpc route: %v. ", - grpcRoute.GRPCRouteCombined.Name)) - return nil, nil, errors.New("failed to find matching listener name for the provided grpc route") + if apiState.MutualSSL != nil && apiState.MutualSSL.Required != "" && !adapterInternalAPI.GetDisableAuthentications() { + adapterInternalAPI.SetDisableMtls(apiState.MutualSSL.Disabled) + adapterInternalAPI.SetMutualSSL(apiState.MutualSSL.Required) + adapterInternalAPI.SetClientCerts(apiState.APIDefinition.Name, apiState.MutualSSL.ClientCertificates) + } else { + adapterInternalAPI.SetDisableMtls(true) } - updatedLabelsMap := make(map[string]struct{}) - listenerName := listeners[0] - sectionName := relativeSectionNames[0] - if len(listeners) != 0 { - updatedLabels, err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) - if err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MAJOR, "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", - adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) - return nil, nil, err - } - for newLabel := range updatedLabels { - updatedLabelsMap[newLabel] = struct{}{} - } - } + return &adapterInternalAPI, nil - return &adapterInternalAPI, updatedLabelsMap, nil } // getVhostForAPI returns the vHosts related to an API. -func getVhostsForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute) []string { - var vHosts []string +func getVhostsForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute) map[string]struct{} { + vHosts := make(map[string]struct{}) for _, hostName := range grpcRoute.Spec.Hostnames { - vHosts = append(vHosts, string(hostName)) + vHosts[string(hostName)] = struct{}{} } - fmt.Println("vhosts size: ", len(vHosts)) return vHosts } // getLabelsForAPI returns the labels related to an API. -func getLabelsForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute) []string { - var labels []string - var err error +func getLabelsForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute) map[string]struct{} { + labels := make(map[string]struct{}) for _, parentRef := range grpcRoute.Spec.ParentRefs { - err = xds.SanitizeGateway(string(parentRef.Name), false) + err := xds.SanitizeGateway(string(parentRef.Name), false) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2653, logging.CRITICAL, "Gateway Label is invalid: %s", string(parentRef.Name))) } else { - labels = append(labels, string(parentRef.Name)) + labels[string(parentRef.Name)] = struct{}{} } } return labels @@ -156,9 +162,8 @@ func getListenersForGRPCAPI(grpcRoute *gwapiv1a2.GRPCRoute, apiUUID string) ([]s func deleteGRPCAPIFromEnv(grpcRoute *gwapiv1a2.GRPCRoute, apiState APIState) error { labels := getLabelsForGRPCAPI(grpcRoute) - org := apiState.APIDefinition.Spec.Organization uuid := string(apiState.APIDefinition.ObjectMeta.UID) - return xds.DeleteAPICREvent(labels, uuid, org) + return xds.DeleteAPI(uuid, labels) } // undeployGRPCAPIInGateway undeploys the related API in CREATE and UPDATE events. diff --git a/adapter/internal/operator/synchronizer/synchronizer.go b/adapter/internal/operator/synchronizer/synchronizer.go index 34aeddfc1..0ce9bbea7 100644 --- a/adapter/internal/operator/synchronizer/synchronizer.go +++ b/adapter/internal/operator/synchronizer/synchronizer.go @@ -204,18 +204,8 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv } } if apiState.APIDefinition.Spec.APIType == "GRPC" { - if apiState.ProdGRPCRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Production) - } - if apiState.SandGRPCRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Sandbox) - } if apiState.ProdGRPCRoute != nil { - _, updatedLabels, err := generateGRPCAdapterInternalAPI(apiState, apiState.ProdGRPCRoute, constants.Production) + _, updatedLabels, err := updateInternalMapsFromGRPCRoute(apiState, apiState.ProdGRPCRoute, constants.Production) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.CRITICAL, "Error deploying prod grpcRoute of API : %v in Organization %v from environments %v. Error: %v", @@ -228,7 +218,7 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv } } if apiState.SandGRPCRoute != nil { - _, updatedLabels, err := generateGRPCAdapterInternalAPI(apiState, apiState.SandGRPCRoute, constants.Sandbox) + _, updatedLabels, err := updateInternalMapsFromGRPCRoute(apiState, apiState.SandGRPCRoute, constants.Sandbox) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.CRITICAL, "Error deploying sand grpcRoute of API : %v in Organization %v from environments %v. Error: %v", From 0170a21e893b0da5e9b9be7357aed47d57a94495 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 15 Apr 2024 14:36:44 +0530 Subject: [PATCH 40/81] changed method to post --- adapter/internal/oasparser/model/adapter_internal_api.go | 2 +- .../org/wso2/apk/enforcer/commons/model/ResourceConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 6f0ac2f82..486f28826 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -1078,7 +1078,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: string(*match.Method.Type), policies: policies, + methods: []*Operation{{iD: uuid.New().String(), method: "post", policies: policies, auth: apiAuth, rateLimitPolicy: parseRateLimitPolicyToInternal(resourceRatelimitPolicy), scopes: scopes}}, iD: uuid.New().String(), } diff --git a/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java b/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java index 44428a76b..2d938a80e 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java +++ b/gateway/enforcer/org.wso2.apk.enforcer.commons/src/main/java/org/wso2/apk/enforcer/commons/model/ResourceConfig.java @@ -41,7 +41,7 @@ public class ResourceConfig { */ public enum HttpMethods { GET("get"), POST("post"), PUT("put"), DELETE("delete"), HEAD("head"), - PATCH("patch"), OPTIONS("options"), QUERY("query"), MUTATION("mutation"), EXACT("Exact"), + PATCH("patch"), OPTIONS("options"), QUERY("query"), MUTATION("mutation"), SUBSCRIPTION("subscription"); private String value; From 67fc8e32e06f35be024b869f7ef674527bad8c10 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 15 Apr 2024 14:43:50 +0530 Subject: [PATCH 41/81] fixed auth --- .../oasparser/model/adapter_internal_api.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 486f28826..09e120b79 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -1100,7 +1100,21 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap if authScheme.Spec.Override != nil && authScheme.Spec.Override.Disabled != nil { adapterInternalAPI.disableAuthentications = *authScheme.Spec.Override.Disabled } + authSpec := utils.SelectPolicy(&authScheme.Spec.Override, &authScheme.Spec.Default, nil, nil) + if authSpec != nil && authSpec.AuthTypes != nil && authSpec.AuthTypes.Oauth2.Required != "" { + adapterInternalAPI.SetXWSO2ApplicationSecurity(authSpec.AuthTypes.Oauth2.Required == "mandatory") + } else { + adapterInternalAPI.SetXWSO2ApplicationSecurity(true) + } adapterInternalAPI.disableScopes = disableScopes + // Check whether the API has a backend JWT token + if apiPolicy != nil && apiPolicy.Spec.Override != nil && apiPolicy.Spec.Override.BackendJWTPolicy != nil { + backendJWTPolicy := resourceParams.BackendJWTMapping[types.NamespacedName{ + Name: apiPolicy.Spec.Override.BackendJWTPolicy.Name, + Namespace: grpcRoute.Namespace, + }.String()].Spec + adapterInternalAPI.backendJWTTokenInfo = parseBackendJWTTokenToInternal(backendJWTPolicy) + } return nil } From d0a8a48ddedd12ced34fa49bd88b901e8f08bbbb Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 16 Apr 2024 10:08:30 +0530 Subject: [PATCH 42/81] change 2023 to 2024 --- .../integration/integration/tests/resources/tests/grpc-api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml index e8585acea..8c908830c 100644 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +# Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. # # WSO2 LLC. licenses this file to you under the Apache License, # Version 2.0 (the "License"); you may not use this file except From c03b37efc6c182e1211e1706022f2845907c2e68 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 16 Apr 2024 10:24:17 +0530 Subject: [PATCH 43/81] removed TODOs --- adapter/internal/oasparser/envoyconf/routes_with_clusters.go | 2 -- .../src/main/java/org/wso2/apk/enforcer/api/APIFactory.java | 1 - 2 files changed, 3 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 040882a29..19fc922c5 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -213,12 +213,10 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte clusters = append(clusters, cluster) endpoints = append(endpoints, address...) - //TODO factor out common code (Dineth) for _, resource := range adapterInternalAPI.GetResources() { var clusterName string resourcePath := resource.GetPath() endpoint := resource.GetEndpoints() - //TODO see if this should be configured elsewhere (Dineth) endpoint.HTTP2BackendEnabled = true basePath := strings.TrimSuffix(endpoint.Endpoints[0].Basepath, "/") existingClusterName := getExistingClusterName(*endpoint, processedEndpoints) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java index ea97c7f68..0b21aaf78 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/APIFactory.java @@ -139,7 +139,6 @@ public byte[] getAPIDefinition(final String basePath, final String version, fina public ResourceConfig getMatchedResource(API api, String matchedResourcePath, String method) { List resourceConfigList = api.getAPIConfig().getResources(); if (APIConstants.ApiType.GRPC.equals(api.getAPIConfig().getApiType())) { - //TODO check if there's another way to do this, perhaps in the adapter side by setting the method return resourceConfigList.stream() .filter(resourceConfig -> resourceConfig.getPath().equals(matchedResourcePath)) .findFirst().orElse(null); From cbdc119c9a95b22b50e837791d6688d7cd799b21 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 16 Apr 2024 11:23:06 +0530 Subject: [PATCH 44/81] change https to http in testcase --- adapter/internal/oasparser/envoyconf/routes_with_clusters.go | 2 -- .../integration/integration/tests/resources/tests/grpc-api.yaml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 19fc922c5..19b8ae1dd 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -224,8 +224,6 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte if existingClusterName == "" { clusterName = getClusterName(endpoint.EndpointPrefix, organizationID, vHost, adapterInternalAPI.GetTitle(), apiVersion, resource.GetID()) cluster, address, err := processEndpoints(clusterName, endpoint, timeout, basePath) - //TODO see if this should be configured elsewhere (Dineth) - cluster.TransportSocketMatches = nil if err != nil { logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2239, logging.MAJOR, "Error while adding resource level endpoints for %s:%v-%v. %v", apiTitle, apiVersion, resourcePath, err.Error())) } else { diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml index 8c908830c..9209fb93a 100644 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -106,4 +106,4 @@ spec: - host: grpc-backend-v1.gateway-integration-test-infra port: 6565 basePath: "" - protocol: https + protocol: http From 3e666720824afc28487a4e376d0750abe64c7021 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 16 Apr 2024 14:35:09 +0530 Subject: [PATCH 45/81] generalize testcase --- .../integration/integration/tests/grpc-api.go | 13 +++---------- .../integration/utils/grpcutils/helpers.go | 13 +++++++++---- .../integration/utils/grpcutils/student.go | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 test/integration/integration/utils/grpcutils/student.go diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 37c01f8e3..8f5ef2f38 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -60,7 +60,7 @@ var GRPCAPI = suite.IntegrationTest{ tc := testCases[i] t.Run("Invoke gRPC API", func(t *testing.T) { t.Parallel() - invokeGRPCClientUntilSatisfied(gwAddr, t, tc, suite.TimeoutConfig) + invokeGRPCClientUntilSatisfied(gwAddr, t, tc, suite.TimeoutConfig, grpcutils.StudentResponseSatisfier{}) }) } @@ -105,7 +105,7 @@ func invokeGRPCClient(gwAddr string, t *testing.T, timeout config.TimeoutConfig) return response, nil } -func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcutils.GRPCTestCase, timeout config.TimeoutConfig) { +func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcutils.GRPCTestCase, timeout config.TimeoutConfig, satisfier grpcutils.ResponseSatisfier) { //(delay to allow CRs to be applied) time.Sleep(5 * time.Second) var out *student.StudentResponse @@ -121,7 +121,7 @@ func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcut if err != nil { t.Logf("Error on attempt %d: %v", attempt+1, err) } else { - if out != nil && isResponseSatisfactory(out, expected) { + if satisfier.IsSatisfactory(out, expected) { t.Logf("Satisfactory response received: %+v", out) return } @@ -137,10 +137,3 @@ func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcut t.Logf("Failed to receive a satisfactory response after %d attempts", maxAttempts) t.Fail() } - -func isResponseSatisfactory(response *student.StudentResponse, expectedResponse grpcutils.ExpectedResponse) bool { - if response.Name == expectedResponse.Out.Name && response.Age == expectedResponse.Out.Age { - return true - } - return false -} diff --git a/test/integration/integration/utils/grpcutils/helpers.go b/test/integration/integration/utils/grpcutils/helpers.go index 71ed6d92c..a803f7952 100644 --- a/test/integration/integration/utils/grpcutils/helpers.go +++ b/test/integration/integration/utils/grpcutils/helpers.go @@ -2,17 +2,22 @@ package grpcutils import "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" -type ExpectedResponse struct { - Out *student.StudentResponse - Err error -} type Request struct { Host string Headers map[string]string } + +type ExpectedResponse struct { + Out *student.StudentResponse + Err error +} + type GRPCTestCase struct { Request Request ExpectedResponse ExpectedResponse ActualResponse *student.StudentResponse Name string } +type ResponseSatisfier interface { + IsSatisfactory(response interface{}, expectedResponse ExpectedResponse) bool +} diff --git a/test/integration/integration/utils/grpcutils/student.go b/test/integration/integration/utils/grpcutils/student.go new file mode 100644 index 000000000..e2b379979 --- /dev/null +++ b/test/integration/integration/utils/grpcutils/student.go @@ -0,0 +1,19 @@ +package grpcutils + +import "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" + +type StudentResponseSatisfier struct{} + +// IsSatisfactory checks if the given response is satisfactory based on the expected response. +func (srs StudentResponseSatisfier) IsSatisfactory(response interface{}, expectedResponse ExpectedResponse) bool { + // Type assert the response to *student.StudentResponse + resp, ok := response.(*student.StudentResponse) + if !ok { + return false // or panic, or handle the error according to your error handling policy + } + + if resp.Name == expectedResponse.Out.Name && resp.Age == expectedResponse.Out.Age { + return true + } + return false +} From 760761b5aa9554a7624b61f2f90ae347a0216e14 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 16 Apr 2024 15:24:47 +0530 Subject: [PATCH 46/81] generalize testcase a bit more generalize testcase a bit more generalize testcase a bit more generalize testcase a bit more generalize testcase a bit more generalize testcase a bit more generalize testcase a bit more --- .../integration/integration/tests/grpc-api.go | 86 +------------------ .../utils/grpc-code/student/student.go | 47 ++++++++++ .../student/student.pb.go | 0 .../student/student_grpc.pb.go | 0 .../integration/utils/grpcutils/helpers.go | 75 +++++++++++++++- .../integration/utils/grpcutils/student.go | 19 ---- 6 files changed, 122 insertions(+), 105 deletions(-) create mode 100644 test/integration/integration/utils/grpc-code/student/student.go rename test/integration/integration/utils/{generatedcode => grpc-code}/student/student.pb.go (100%) rename test/integration/integration/utils/{generatedcode => grpc-code}/student/student_grpc.pb.go (100%) delete mode 100644 test/integration/integration/utils/grpcutils/student.go diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 8f5ef2f38..354cbd1c8 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -18,17 +18,10 @@ package tests import ( - "context" - "crypto/tls" - "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" + "github.com/wso2/apk/test/integration/integration/utils/grpc-code/student" "github.com/wso2/apk/test/integration/integration/utils/grpcutils" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "sigs.k8s.io/gateway-api/conformance/utils/config" - "testing" - "time" - "github.com/wso2/apk/test/integration/integration/utils/suite" + "testing" ) func init() { @@ -54,86 +47,15 @@ var GRPCAPI = suite.IntegrationTest{ }, ActualResponse: &student.StudentResponse{}, Name: "Get Student Details", + Method: student.GetStudent, }, } for i := range testCases { tc := testCases[i] t.Run("Invoke gRPC API", func(t *testing.T) { t.Parallel() - invokeGRPCClientUntilSatisfied(gwAddr, t, tc, suite.TimeoutConfig, grpcutils.StudentResponseSatisfier{}) - + grpcutils.InvokeGRPCClientUntilSatisfied(gwAddr, t, tc, student.StudentResponseSatisfier{}, tc.Method) }) } }, } - -func invokeGRPCClient(gwAddr string, t *testing.T, timeout config.TimeoutConfig) (*student.StudentResponse, error) { - - t.Logf("Starting gRPC client...") - - config := &tls.Config{ - InsecureSkipVerify: true, - } - creds := credentials.NewTLS(config) - - // Dial the server with the TLS credentials and a dial timeout. - dialCtx, dialCancel := context.WithTimeout(context.Background(), 60*time.Second) - defer dialCancel() - t.Logf("Dialing to server at %s with timeout...", gwAddr) - conn, err := grpc.DialContext(dialCtx, gwAddr, grpc.WithTransportCredentials(creds), grpc.WithBlock()) - if err != nil { - t.Logf("Failed to connect: %v", err) - } - defer conn.Close() - //t.Log("Successfully connected to the server.") - - c := student.NewStudentServiceClient(conn) - - // Prepare the context with a timeout for the request. - ctx, cancel := context.WithTimeout(context.Background(), timeout.RequestTimeout) - defer cancel() - - t.Log("Sending request to the server...") - // Create a StudentRequest message - r := &student.StudentRequest{Id: 1234} - response, err := c.GetStudent(ctx, r) - if err != nil { - t.Logf("Could not fetch student: %v", err) - t.Logf("Error: %v\n", response) - } - - return response, nil -} - -func invokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase grpcutils.GRPCTestCase, timeout config.TimeoutConfig, satisfier grpcutils.ResponseSatisfier) { - //(delay to allow CRs to be applied) - time.Sleep(5 * time.Second) - var out *student.StudentResponse - var err error - attempt := 0 - maxAttempts := 4 - expected := testCase.ExpectedResponse - timeoutDuration := 50 * time.Second - for attempt < maxAttempts { - t.Logf("Attempt %d to invoke gRPC client...", attempt+1) - out, err = invokeGRPCClient(gwAddr, t, timeout) - - if err != nil { - t.Logf("Error on attempt %d: %v", attempt+1, err) - } else { - if satisfier.IsSatisfactory(out, expected) { - t.Logf("Satisfactory response received: %+v", out) - return - } - } - - if attempt < maxAttempts-1 { - t.Logf("Waiting %s seconds before next attempt...", timeoutDuration) - time.Sleep(timeoutDuration) - } - attempt++ - } - - t.Logf("Failed to receive a satisfactory response after %d attempts", maxAttempts) - t.Fail() -} diff --git a/test/integration/integration/utils/grpc-code/student/student.go b/test/integration/integration/utils/grpc-code/student/student.go new file mode 100644 index 000000000..52d4c3e6f --- /dev/null +++ b/test/integration/integration/utils/grpc-code/student/student.go @@ -0,0 +1,47 @@ +package student + +import ( + "context" + "github.com/wso2/apk/test/integration/integration/utils/grpcutils" + "google.golang.org/grpc" + "log" + "time" +) + +type StudentResponseSatisfier struct{} + +// IsSatisfactory checks if the given response is satisfactory based on the expected response. +func (srs StudentResponseSatisfier) IsSatisfactory(response any, expectedResponse grpcutils.ExpectedResponse) bool { + // Type assert the response to *student.StudentResponse + resp, respOk := response.(*StudentResponse) + if !respOk { + log.Println("Failed to assert response as *student.StudentResponse") + return false + } + // Type assert the expected output to *student.StudentResponse + expectedResp, expOk := expectedResponse.Out.(*StudentResponse) + if !expOk { + log.Println("Failed to assert expectedResponse.Out as *student.StudentResponse") + return false + } + + // Compare the actual response with the expected response + if resp.Name == expectedResp.Name && resp.Age == expectedResp.Age { + log.Println("Response is satisfactory") + return true + } else { + log.Println("Response does not match the expected output") + return false + } +} +func GetStudent(conn *grpc.ClientConn) (any, error) { + c := NewStudentServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + r := &StudentRequest{Id: 1234} + response, err := c.GetStudent(ctx, r) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/test/integration/integration/utils/generatedcode/student/student.pb.go b/test/integration/integration/utils/grpc-code/student/student.pb.go similarity index 100% rename from test/integration/integration/utils/generatedcode/student/student.pb.go rename to test/integration/integration/utils/grpc-code/student/student.pb.go diff --git a/test/integration/integration/utils/generatedcode/student/student_grpc.pb.go b/test/integration/integration/utils/grpc-code/student/student_grpc.pb.go similarity index 100% rename from test/integration/integration/utils/generatedcode/student/student_grpc.pb.go rename to test/integration/integration/utils/grpc-code/student/student_grpc.pb.go diff --git a/test/integration/integration/utils/grpcutils/helpers.go b/test/integration/integration/utils/grpcutils/helpers.go index a803f7952..197bcfac0 100644 --- a/test/integration/integration/utils/grpcutils/helpers.go +++ b/test/integration/integration/utils/grpcutils/helpers.go @@ -1,23 +1,90 @@ package grpcutils -import "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" +import ( + "crypto/tls" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "testing" + "time" +) type Request struct { Host string Headers map[string]string } - +type ClientCreator[T any] func(conn *grpc.ClientConn) T type ExpectedResponse struct { - Out *student.StudentResponse + Out any Err error } type GRPCTestCase struct { Request Request ExpectedResponse ExpectedResponse - ActualResponse *student.StudentResponse + ActualResponse any Name string + Method func(conn *grpc.ClientConn) (any, error) } type ResponseSatisfier interface { IsSatisfactory(response interface{}, expectedResponse ExpectedResponse) bool } + +func DialGRPCServer(gwAddr string, t *testing.T) (*grpc.ClientConn, error) { + // Set up a connection to the server. + t.Logf("Dialing gRPC server at %s...", gwAddr) + creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) + conn, err := grpc.Dial(gwAddr, grpc.WithTransportCredentials(creds)) + if err != nil { + t.Fatalf("Could not connect to the server: %v", err) + } + return conn, nil +} +func InvokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase GRPCTestCase, satisfier ResponseSatisfier, fn ExecuteClientCall) { + //(delay to allow CRs to be applied) + time.Sleep(5 * time.Second) + + var out any + var err error + attempt := 0 + maxAttempts := 4 + expected := testCase.ExpectedResponse + timeoutDuration := 50 * time.Second + for attempt < maxAttempts { + t.Logf("Attempt %d to invoke gRPC client...", attempt+1) + out, err = InvokeGRPCClient(gwAddr, t, fn) + + if err != nil { + t.Logf("Error on attempt %d: %v", attempt+1, err) + } else { + if satisfier.IsSatisfactory(out, expected) { + t.Logf("Satisfactory response received: %+v", out) + return + } + } + + if attempt < maxAttempts-1 { + t.Logf("Waiting %s seconds before next attempt...", timeoutDuration) + time.Sleep(timeoutDuration) + } + attempt++ + } + + t.Logf("Failed to receive a satisfactory response after %d attempts", maxAttempts) + t.Fail() +} + +type ExecuteClientCall func(conn *grpc.ClientConn) (any, error) + +func InvokeGRPCClient(gwAddr string, t *testing.T, fn ExecuteClientCall) (any, error) { + + conn, err := DialGRPCServer(gwAddr, t) + if err != nil { + t.Fatalf("Could not connect to the server: %v", err) + } + + response, err := fn(conn) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/test/integration/integration/utils/grpcutils/student.go b/test/integration/integration/utils/grpcutils/student.go deleted file mode 100644 index e2b379979..000000000 --- a/test/integration/integration/utils/grpcutils/student.go +++ /dev/null @@ -1,19 +0,0 @@ -package grpcutils - -import "github.com/wso2/apk/test/integration/integration/utils/generatedcode/student" - -type StudentResponseSatisfier struct{} - -// IsSatisfactory checks if the given response is satisfactory based on the expected response. -func (srs StudentResponseSatisfier) IsSatisfactory(response interface{}, expectedResponse ExpectedResponse) bool { - // Type assert the response to *student.StudentResponse - resp, ok := response.(*student.StudentResponse) - if !ok { - return false // or panic, or handle the error according to your error handling policy - } - - if resp.Name == expectedResponse.Out.Name && resp.Age == expectedResponse.Out.Age { - return true - } - return false -} From 271ff0f05121bef4fadfeb06e1a1e42c6ec53b60 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 17 Apr 2024 10:06:44 +0530 Subject: [PATCH 47/81] add testcase for grpc api default version --- .../integration/integration/tests/grpc-api.go | 12 + .../tests/resources/tests/grpc-api.yaml | 2 +- .../utils/grpc-code/student/student.go | 1 - .../student_default_version/student2.go | 46 +++ .../student_default_version.pb.go | 251 ++++++++++++++ .../student_default_version_grpc.pb.go | 314 ++++++++++++++++++ .../integration/utils/grpcutils/helpers.go | 1 - 7 files changed, 624 insertions(+), 3 deletions(-) create mode 100644 test/integration/integration/utils/grpc-code/student_default_version/student2.go create mode 100644 test/integration/integration/utils/grpc-code/student_default_version/student_default_version.pb.go create mode 100644 test/integration/integration/utils/grpc-code/student_default_version/student_default_version_grpc.pb.go diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 354cbd1c8..761e5fcab 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -49,6 +49,18 @@ var GRPCAPI = suite.IntegrationTest{ Name: "Get Student Details", Method: student.GetStudent, }, + //{ + // ExpectedResponse: grpcutils.ExpectedResponse{ + // Out: &student_default_version.StudentResponse{ + // Name: "Dineth", + // Age: 10, + // }, + // Err: nil, + // }, + // ActualResponse: &student_default_version.StudentResponse{}, + // Name: "Get Student Details (Default API Version)", + // Method: student_default_version.GetStudent, + //}, } for i := range testCases { tc := testCases[i] diff --git a/test/integration/integration/tests/resources/tests/grpc-api.yaml b/test/integration/integration/tests/resources/tests/grpc-api.yaml index 9209fb93a..6329c8777 100644 --- a/test/integration/integration/tests/resources/tests/grpc-api.yaml +++ b/test/integration/integration/tests/resources/tests/grpc-api.yaml @@ -24,7 +24,7 @@ spec: apiType: GRPC apiVersion: v1 basePath: /dineth.grpc.api.v1 - isDefaultVersion: false + isDefaultVersion: true production: - routeRefs: - grpc-api-grpcroute diff --git a/test/integration/integration/utils/grpc-code/student/student.go b/test/integration/integration/utils/grpc-code/student/student.go index 52d4c3e6f..36143eb56 100644 --- a/test/integration/integration/utils/grpc-code/student/student.go +++ b/test/integration/integration/utils/grpc-code/student/student.go @@ -27,7 +27,6 @@ func (srs StudentResponseSatisfier) IsSatisfactory(response any, expectedRespons // Compare the actual response with the expected response if resp.Name == expectedResp.Name && resp.Age == expectedResp.Age { - log.Println("Response is satisfactory") return true } else { log.Println("Response does not match the expected output") diff --git a/test/integration/integration/utils/grpc-code/student_default_version/student2.go b/test/integration/integration/utils/grpc-code/student_default_version/student2.go new file mode 100644 index 000000000..939bb3b90 --- /dev/null +++ b/test/integration/integration/utils/grpc-code/student_default_version/student2.go @@ -0,0 +1,46 @@ +package student_default_version + +import ( + "context" + "github.com/wso2/apk/test/integration/integration/utils/grpcutils" + "google.golang.org/grpc" + "log" + "time" +) + +type StudentResponseSatisfier struct{} + +// IsSatisfactory checks if the given response is satisfactory based on the expected response. +func (srs StudentResponseSatisfier) IsSatisfactory(response any, expectedResponse grpcutils.ExpectedResponse) bool { + // Type assert the response to *student.StudentResponse + resp, respOk := response.(*StudentResponse) + if !respOk { + log.Println("Failed to assert response as *student.StudentResponse") + return false + } + // Type assert the expected output to *student.StudentResponse + expectedResp, expOk := expectedResponse.Out.(*StudentResponse) + if !expOk { + log.Println("Failed to assert expectedResponse.Out as *student.StudentResponse") + return false + } + + // Compare the actual response with the expected response + if resp.Name == expectedResp.Name && resp.Age == expectedResp.Age { + return true + } else { + log.Println("Response does not match the expected output") + return false + } +} +func GetStudent(conn *grpc.ClientConn) (any, error) { + c := NewStudentServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + r := &StudentRequest{Id: 1234} + response, err := c.GetStudent(ctx, r) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/test/integration/integration/utils/grpc-code/student_default_version/student_default_version.pb.go b/test/integration/integration/utils/grpc-code/student_default_version/student_default_version.pb.go new file mode 100644 index 000000000..a006069e8 --- /dev/null +++ b/test/integration/integration/utils/grpc-code/student_default_version/student_default_version.pb.go @@ -0,0 +1,251 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc v3.12.4 +// source: student_default_version.proto + +package student_default_version + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StudentRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *StudentRequest) Reset() { + *x = StudentRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_student_default_version_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StudentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StudentRequest) ProtoMessage() {} + +func (x *StudentRequest) ProtoReflect() protoreflect.Message { + mi := &file_student_default_version_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StudentRequest.ProtoReflect.Descriptor instead. +func (*StudentRequest) Descriptor() ([]byte, []int) { + return file_student_default_version_proto_rawDescGZIP(), []int{0} +} + +func (x *StudentRequest) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +type StudentResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` +} + +func (x *StudentResponse) Reset() { + *x = StudentResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_student_default_version_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StudentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StudentResponse) ProtoMessage() {} + +func (x *StudentResponse) ProtoReflect() protoreflect.Message { + mi := &file_student_default_version_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StudentResponse.ProtoReflect.Descriptor instead. +func (*StudentResponse) Descriptor() ([]byte, []int) { + return file_student_default_version_proto_rawDescGZIP(), []int{1} +} + +func (x *StudentResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *StudentResponse) GetAge() int32 { + if x != nil { + return x.Age + } + return 0 +} + +var File_student_default_version_proto protoreflect.FileDescriptor + +var file_student_default_version_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x17, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x22, 0x20, 0x0a, 0x0e, 0x53, 0x74, 0x75, 0x64, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0x37, 0x0a, 0x0f, 0x53, 0x74, + 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, + 0x61, 0x67, 0x65, 0x32, 0xbe, 0x03, 0x0a, 0x0e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x61, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x74, 0x75, + 0x64, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, + 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x69, 0x0a, 0x10, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x27, 0x2e, + 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, + 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x30, 0x01, 0x12, 0x6a, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x75, 0x64, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x27, 0x2e, 0x64, 0x69, 0x6e, 0x65, + 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x74, 0x75, 0x64, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, + 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, + 0x12, 0x72, 0x0a, 0x17, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x6e, 0x64, 0x47, 0x65, 0x74, 0x53, 0x74, + 0x75, 0x64, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x27, 0x2e, 0x64, 0x69, + 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x74, + 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x69, 0x6e, 0x65, 0x74, 0x68, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x53, + 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x28, 0x01, 0x30, 0x01, 0x42, 0x0f, 0x0a, 0x0b, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x50, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_student_default_version_proto_rawDescOnce sync.Once + file_student_default_version_proto_rawDescData = file_student_default_version_proto_rawDesc +) + +func file_student_default_version_proto_rawDescGZIP() []byte { + file_student_default_version_proto_rawDescOnce.Do(func() { + file_student_default_version_proto_rawDescData = protoimpl.X.CompressGZIP(file_student_default_version_proto_rawDescData) + }) + return file_student_default_version_proto_rawDescData +} + +var file_student_default_version_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_student_default_version_proto_goTypes = []interface{}{ + (*StudentRequest)(nil), // 0: dineth.grpc.api.student.StudentRequest + (*StudentResponse)(nil), // 1: dineth.grpc.api.student.StudentResponse +} +var file_student_default_version_proto_depIdxs = []int32{ + 0, // 0: dineth.grpc.api.student.StudentService.GetStudent:input_type -> dineth.grpc.api.student.StudentRequest + 0, // 1: dineth.grpc.api.student.StudentService.GetStudentStream:input_type -> dineth.grpc.api.student.StudentRequest + 0, // 2: dineth.grpc.api.student.StudentService.SendStudentStream:input_type -> dineth.grpc.api.student.StudentRequest + 0, // 3: dineth.grpc.api.student.StudentService.SendAndGetStudentStream:input_type -> dineth.grpc.api.student.StudentRequest + 1, // 4: dineth.grpc.api.student.StudentService.GetStudent:output_type -> dineth.grpc.api.student.StudentResponse + 1, // 5: dineth.grpc.api.student.StudentService.GetStudentStream:output_type -> dineth.grpc.api.student.StudentResponse + 1, // 6: dineth.grpc.api.student.StudentService.SendStudentStream:output_type -> dineth.grpc.api.student.StudentResponse + 1, // 7: dineth.grpc.api.student.StudentService.SendAndGetStudentStream:output_type -> dineth.grpc.api.student.StudentResponse + 4, // [4:8] is the sub-list for method output_type + 0, // [0:4] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_student_default_version_proto_init() } +func file_student_default_version_proto_init() { + if File_student_default_version_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_student_default_version_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StudentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_student_default_version_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StudentResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_student_default_version_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_student_default_version_proto_goTypes, + DependencyIndexes: file_student_default_version_proto_depIdxs, + MessageInfos: file_student_default_version_proto_msgTypes, + }.Build() + File_student_default_version_proto = out.File + file_student_default_version_proto_rawDesc = nil + file_student_default_version_proto_goTypes = nil + file_student_default_version_proto_depIdxs = nil +} diff --git a/test/integration/integration/utils/grpc-code/student_default_version/student_default_version_grpc.pb.go b/test/integration/integration/utils/grpc-code/student_default_version/student_default_version_grpc.pb.go new file mode 100644 index 000000000..b3fe9b74b --- /dev/null +++ b/test/integration/integration/utils/grpc-code/student_default_version/student_default_version_grpc.pb.go @@ -0,0 +1,314 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.12.4 +// source: student_default_version.proto + +package student_default_version + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + StudentService_GetStudent_FullMethodName = "/dineth.grpc.api.student.StudentService/GetStudent" + StudentService_GetStudentStream_FullMethodName = "/dineth.grpc.api.student.StudentService/GetStudentStream" + StudentService_SendStudentStream_FullMethodName = "/dineth.grpc.api.student.StudentService/SendStudentStream" + StudentService_SendAndGetStudentStream_FullMethodName = "/dineth.grpc.api.student.StudentService/SendAndGetStudentStream" +) + +// StudentServiceClient is the client API for StudentService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StudentServiceClient interface { + GetStudent(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (*StudentResponse, error) + GetStudentStream(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (StudentService_GetStudentStreamClient, error) + SendStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendStudentStreamClient, error) + SendAndGetStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendAndGetStudentStreamClient, error) +} + +type studentServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewStudentServiceClient(cc grpc.ClientConnInterface) StudentServiceClient { + return &studentServiceClient{cc} +} + +func (c *studentServiceClient) GetStudent(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (*StudentResponse, error) { + out := new(StudentResponse) + err := c.cc.Invoke(ctx, StudentService_GetStudent_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *studentServiceClient) GetStudentStream(ctx context.Context, in *StudentRequest, opts ...grpc.CallOption) (StudentService_GetStudentStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &StudentService_ServiceDesc.Streams[0], StudentService_GetStudentStream_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &studentServiceGetStudentStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type StudentService_GetStudentStreamClient interface { + Recv() (*StudentResponse, error) + grpc.ClientStream +} + +type studentServiceGetStudentStreamClient struct { + grpc.ClientStream +} + +func (x *studentServiceGetStudentStreamClient) Recv() (*StudentResponse, error) { + m := new(StudentResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *studentServiceClient) SendStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendStudentStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &StudentService_ServiceDesc.Streams[1], StudentService_SendStudentStream_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &studentServiceSendStudentStreamClient{stream} + return x, nil +} + +type StudentService_SendStudentStreamClient interface { + Send(*StudentRequest) error + CloseAndRecv() (*StudentResponse, error) + grpc.ClientStream +} + +type studentServiceSendStudentStreamClient struct { + grpc.ClientStream +} + +func (x *studentServiceSendStudentStreamClient) Send(m *StudentRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *studentServiceSendStudentStreamClient) CloseAndRecv() (*StudentResponse, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(StudentResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *studentServiceClient) SendAndGetStudentStream(ctx context.Context, opts ...grpc.CallOption) (StudentService_SendAndGetStudentStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &StudentService_ServiceDesc.Streams[2], StudentService_SendAndGetStudentStream_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &studentServiceSendAndGetStudentStreamClient{stream} + return x, nil +} + +type StudentService_SendAndGetStudentStreamClient interface { + Send(*StudentRequest) error + Recv() (*StudentResponse, error) + grpc.ClientStream +} + +type studentServiceSendAndGetStudentStreamClient struct { + grpc.ClientStream +} + +func (x *studentServiceSendAndGetStudentStreamClient) Send(m *StudentRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *studentServiceSendAndGetStudentStreamClient) Recv() (*StudentResponse, error) { + m := new(StudentResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// StudentServiceServer is the server API for StudentService service. +// All implementations must embed UnimplementedStudentServiceServer +// for forward compatibility +type StudentServiceServer interface { + GetStudent(context.Context, *StudentRequest) (*StudentResponse, error) + GetStudentStream(*StudentRequest, StudentService_GetStudentStreamServer) error + SendStudentStream(StudentService_SendStudentStreamServer) error + SendAndGetStudentStream(StudentService_SendAndGetStudentStreamServer) error + mustEmbedUnimplementedStudentServiceServer() +} + +// UnimplementedStudentServiceServer must be embedded to have forward compatible implementations. +type UnimplementedStudentServiceServer struct { +} + +func (UnimplementedStudentServiceServer) GetStudent(context.Context, *StudentRequest) (*StudentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStudent not implemented") +} +func (UnimplementedStudentServiceServer) GetStudentStream(*StudentRequest, StudentService_GetStudentStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetStudentStream not implemented") +} +func (UnimplementedStudentServiceServer) SendStudentStream(StudentService_SendStudentStreamServer) error { + return status.Errorf(codes.Unimplemented, "method SendStudentStream not implemented") +} +func (UnimplementedStudentServiceServer) SendAndGetStudentStream(StudentService_SendAndGetStudentStreamServer) error { + return status.Errorf(codes.Unimplemented, "method SendAndGetStudentStream not implemented") +} +func (UnimplementedStudentServiceServer) mustEmbedUnimplementedStudentServiceServer() {} + +// UnsafeStudentServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StudentServiceServer will +// result in compilation errors. +type UnsafeStudentServiceServer interface { + mustEmbedUnimplementedStudentServiceServer() +} + +func RegisterStudentServiceServer(s grpc.ServiceRegistrar, srv StudentServiceServer) { + s.RegisterService(&StudentService_ServiceDesc, srv) +} + +func _StudentService_GetStudent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StudentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StudentServiceServer).GetStudent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StudentService_GetStudent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StudentServiceServer).GetStudent(ctx, req.(*StudentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StudentService_GetStudentStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(StudentRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StudentServiceServer).GetStudentStream(m, &studentServiceGetStudentStreamServer{stream}) +} + +type StudentService_GetStudentStreamServer interface { + Send(*StudentResponse) error + grpc.ServerStream +} + +type studentServiceGetStudentStreamServer struct { + grpc.ServerStream +} + +func (x *studentServiceGetStudentStreamServer) Send(m *StudentResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _StudentService_SendStudentStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StudentServiceServer).SendStudentStream(&studentServiceSendStudentStreamServer{stream}) +} + +type StudentService_SendStudentStreamServer interface { + SendAndClose(*StudentResponse) error + Recv() (*StudentRequest, error) + grpc.ServerStream +} + +type studentServiceSendStudentStreamServer struct { + grpc.ServerStream +} + +func (x *studentServiceSendStudentStreamServer) SendAndClose(m *StudentResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *studentServiceSendStudentStreamServer) Recv() (*StudentRequest, error) { + m := new(StudentRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _StudentService_SendAndGetStudentStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StudentServiceServer).SendAndGetStudentStream(&studentServiceSendAndGetStudentStreamServer{stream}) +} + +type StudentService_SendAndGetStudentStreamServer interface { + Send(*StudentResponse) error + Recv() (*StudentRequest, error) + grpc.ServerStream +} + +type studentServiceSendAndGetStudentStreamServer struct { + grpc.ServerStream +} + +func (x *studentServiceSendAndGetStudentStreamServer) Send(m *StudentResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *studentServiceSendAndGetStudentStreamServer) Recv() (*StudentRequest, error) { + m := new(StudentRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// StudentService_ServiceDesc is the grpc.ServiceDesc for StudentService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var StudentService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dineth.grpc.api.student.StudentService", + HandlerType: (*StudentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetStudent", + Handler: _StudentService_GetStudent_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "GetStudentStream", + Handler: _StudentService_GetStudentStream_Handler, + ServerStreams: true, + }, + { + StreamName: "SendStudentStream", + Handler: _StudentService_SendStudentStream_Handler, + ClientStreams: true, + }, + { + StreamName: "SendAndGetStudentStream", + Handler: _StudentService_SendAndGetStudentStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "student_default_version.proto", +} diff --git a/test/integration/integration/utils/grpcutils/helpers.go b/test/integration/integration/utils/grpcutils/helpers.go index 197bcfac0..d2158aea5 100644 --- a/test/integration/integration/utils/grpcutils/helpers.go +++ b/test/integration/integration/utils/grpcutils/helpers.go @@ -57,7 +57,6 @@ func InvokeGRPCClientUntilSatisfied(gwAddr string, t *testing.T, testCase GRPCTe t.Logf("Error on attempt %d: %v", attempt+1, err) } else { if satisfier.IsSatisfactory(out, expected) { - t.Logf("Satisfactory response received: %+v", out) return } } From 1d7aea95b74b75c80edab60c72ad1ba4a847ab53 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 17 Apr 2024 13:11:41 +0530 Subject: [PATCH 48/81] add default version capability for grpc api --- .../envoyconf/routes_with_clusters.go | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 19b8ae1dd..172917eb6 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -928,8 +928,14 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error decorator *routev3.Decorator ) if params.createDefaultPath { - xWso2Basepath = removeFirstOccurrence(xWso2Basepath, "/"+version) - resourcePath = removeFirstOccurrence(resource.GetPath(), "/"+version) + //check if basepath is separated from version by a . or / + if strings.Contains(basePath, "."+version) { + xWso2Basepath = removeFirstOccurrence(basePath, "."+version) + resourcePath = removeFirstOccurrence(resource.GetPath(), "."+version) + } else { + xWso2Basepath = removeFirstOccurrence(xWso2Basepath, "/"+version) + resourcePath = removeFirstOccurrence(resource.GetPath(), "/"+version) + } } if pathMatchType != gwapiv1.PathMatchExact { @@ -1129,6 +1135,10 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error if apiType == "GRPC" { match.Headers = nil newRoutePath := "/" + strings.TrimPrefix(resourcePath, basePath+".") + if newRoutePath == "/"+resourcePath { + temp := removeFirstOccurrence(basePath, "."+version) + newRoutePath = "/" + strings.TrimPrefix(resourcePath, temp+".") + } action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, newRoutePath, pathMatchType) } @@ -1283,8 +1293,14 @@ func CreateAPIDefinitionEndpoint(adapterInternalAPI *model.AdapterInternalAPI, v matchPath := basePath + endpoint if isDefaultversion { - basePathWithoutVersion := removeLastOccurrence(basePath, "/"+version) - matchPath = basePathWithoutVersion + endpoint + if adapterInternalAPI.GetAPIType() == "GRPC" { + basePathWithoutVersion := removeLastOccurrence(basePath, "."+version) + matchPath = basePathWithoutVersion + "/" + vHost + endpoint + } else { + basePathWithoutVersion := removeLastOccurrence(basePath, "/"+version) + matchPath = basePathWithoutVersion + endpoint + + } } matchPath = strings.Replace(matchPath, basePath, regexp.QuoteMeta(basePath), 1) From f5e918529f202891ef9b4f158a3abf738add5445 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 17 Apr 2024 13:21:50 +0530 Subject: [PATCH 49/81] modify test for grpc default version testing --- .../integration/integration/tests/grpc-api.go | 29 ++++++++++--------- .../integration/utils/grpcutils/helpers.go | 1 + 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 761e5fcab..054bda3f3 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -19,6 +19,7 @@ package tests import ( "github.com/wso2/apk/test/integration/integration/utils/grpc-code/student" + "github.com/wso2/apk/test/integration/integration/utils/grpc-code/student_default_version" "github.com/wso2/apk/test/integration/integration/utils/grpcutils" "github.com/wso2/apk/test/integration/integration/utils/suite" "testing" @@ -48,25 +49,27 @@ var GRPCAPI = suite.IntegrationTest{ ActualResponse: &student.StudentResponse{}, Name: "Get Student Details", Method: student.GetStudent, + Satisfier: student.StudentResponseSatisfier{}, + }, + { + ExpectedResponse: grpcutils.ExpectedResponse{ + Out: &student_default_version.StudentResponse{ + Name: "Dineth", + Age: 10, + }, + Err: nil, + }, + ActualResponse: &student_default_version.StudentResponse{}, + Name: "Get Student Details (Default API Version)", + Method: student_default_version.GetStudent, + Satisfier: student_default_version.StudentResponseSatisfier{}, }, - //{ - // ExpectedResponse: grpcutils.ExpectedResponse{ - // Out: &student_default_version.StudentResponse{ - // Name: "Dineth", - // Age: 10, - // }, - // Err: nil, - // }, - // ActualResponse: &student_default_version.StudentResponse{}, - // Name: "Get Student Details (Default API Version)", - // Method: student_default_version.GetStudent, - //}, } for i := range testCases { tc := testCases[i] t.Run("Invoke gRPC API", func(t *testing.T) { t.Parallel() - grpcutils.InvokeGRPCClientUntilSatisfied(gwAddr, t, tc, student.StudentResponseSatisfier{}, tc.Method) + grpcutils.InvokeGRPCClientUntilSatisfied(gwAddr, t, tc, tc.Satisfier, tc.Method) }) } }, diff --git a/test/integration/integration/utils/grpcutils/helpers.go b/test/integration/integration/utils/grpcutils/helpers.go index d2158aea5..66d339215 100644 --- a/test/integration/integration/utils/grpcutils/helpers.go +++ b/test/integration/integration/utils/grpcutils/helpers.go @@ -24,6 +24,7 @@ type GRPCTestCase struct { ActualResponse any Name string Method func(conn *grpc.ClientConn) (any, error) + Satisfier ResponseSatisfier } type ResponseSatisfier interface { IsSatisfactory(response interface{}, expectedResponse ExpectedResponse) bool From 290c65f22356386ad308edf91fec86af583e44a0 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 24 Apr 2024 21:34:39 +0530 Subject: [PATCH 50/81] resolved comments --- .../operator/synchronizer/grpc_api.go | 2 +- .../org/wso2/apk/enforcer/api/GRPCAPI.java | 31 ++++++------------- .../integration/integration/tests/grpc-api.go | 2 +- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/adapter/internal/operator/synchronizer/grpc_api.go b/adapter/internal/operator/synchronizer/grpc_api.go index aadf86098..26c0bd92c 100644 --- a/adapter/internal/operator/synchronizer/grpc_api.go +++ b/adapter/internal/operator/synchronizer/grpc_api.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java index ca68c9a59..0ec44ae71 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/api/GRPCAPI.java @@ -8,14 +8,21 @@ import org.wso2.apk.enforcer.commons.Filter; import org.wso2.apk.enforcer.commons.dto.ClaimValueDTO; import org.wso2.apk.enforcer.commons.dto.JWTConfigurationDto; -import org.wso2.apk.enforcer.commons.model.*; +import org.wso2.apk.enforcer.commons.model.APIConfig; +import org.wso2.apk.enforcer.commons.model.ResourceConfig; +import org.wso2.apk.enforcer.commons.model.EndpointSecurity; +import org.wso2.apk.enforcer.commons.model.RequestContext; import org.wso2.apk.enforcer.commons.model.EndpointCluster; import org.wso2.apk.enforcer.config.ConfigHolder; import org.wso2.apk.enforcer.config.EnforcerConfig; import org.wso2.apk.enforcer.constants.APIConstants; import org.wso2.apk.enforcer.constants.HttpConstants; import org.wso2.apk.enforcer.cors.CorsFilter; -import org.wso2.apk.enforcer.discovery.api.*; +import org.wso2.apk.enforcer.discovery.api.Api; +import org.wso2.apk.enforcer.discovery.api.Resource; +import org.wso2.apk.enforcer.discovery.api.Operation; +import org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo; +import org.wso2.apk.enforcer.discovery.api.Claim; import org.wso2.apk.enforcer.security.AuthFilter; import org.wso2.apk.enforcer.security.mtls.MtlsUtils; import org.wso2.apk.enforcer.server.swagger.APIDefinitionUtils; @@ -94,24 +101,11 @@ public String init(Api api) { } - SchemaParser schemaParser = new SchemaParser(); byte[] apiDefinition = api.getApiDefinitionFile().toByteArray(); TypeDefinitionRegistry registry; - //TODO fix this -// try { -// String scheme = APIDefinitionUtils.ReadGzip(apiDefinition); -// registry = schemaParser.parse(scheme); -// } catch (IOException e) { -// logger.error("Error while parsing the GRPC schema definition of the API: " + name, e); -// throw new RuntimeException(e); -// } -// GraphQLSchema schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registry); - -// GraphQLSchemaDTO graphQLSchemaDTO = new GraphQLSchemaDTO(schema, registry, -// GraphQLPayloadUtils.parseComplexityDTO(api.getGraphqlComplexityInfoList())); String apiLifeCycleState = api.getApiLifeCycleState(); this.apiConfig = new APIConfig.Builder(name).uuid(api.getId()).vhost(vhost).basePath(basePath).version(version) @@ -120,9 +114,8 @@ public String init(Api api) { .disableScopes(api.getDisableScopes()).trustStore(trustStore).organizationId(api.getOrganizationId()) .mutualSSL(mutualSSL) .applicationSecurity(applicationSecurity).jwtConfigurationDto(jwtConfigurationDto) -// .apiDefinition(apiDefinition).environment(api.getEnvironment()) + .apiDefinition(apiDefinition).environment(api.getEnvironment()) .environment(api.getEnvironment()) -// .subscriptionValidation(api.getSubscriptionValidation()).graphQLSchemaDTO(graphQLSchemaDTO).build(); .subscriptionValidation(api.getSubscriptionValidation()).build(); initFilters(); logger.info("APIConfig: " + this.apiConfig); @@ -212,10 +205,6 @@ private void initFilters() { authFilter.init(apiConfig, null); this.filters.add(authFilter); -// GraphQLQueryAnalysisFilter queryAnalysisFilter = new GraphQLQueryAnalysisFilter(); -// queryAnalysisFilter.init(apiConfig, null); -// this.filters.add(queryAnalysisFilter); - // CORS filter is added as the first filter, and it is not customizable. CorsFilter corsFilter = new CorsFilter(); this.filters.add(0, corsFilter); diff --git a/test/integration/integration/tests/grpc-api.go b/test/integration/integration/tests/grpc-api.go index 054bda3f3..43632c1f9 100644 --- a/test/integration/integration/tests/grpc-api.go +++ b/test/integration/integration/tests/grpc-api.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 43899079b60ab13e9894486785d8c82f82993135 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 25 Apr 2024 12:53:45 +0530 Subject: [PATCH 51/81] reconciles grpc scopes --- .../operator/controllers/dp/api_controller.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index ce7846ffb..ed541a472 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -90,6 +90,7 @@ const ( serviceHTTPRouteIndex = "serviceHTTPRouteIndex" httprouteScopeIndex = "httprouteScopeIndex" gqlRouteScopeIndex = "gqlRouteScopeIndex" + grpcRouteScopeIndex = "grpcRouteScopeIndex" configMapBackend = "configMapBackend" configMapAPIDefinition = "configMapAPIDefinition" secretBackend = "secretBackend" @@ -1660,6 +1661,22 @@ func (apiReconciler *APIReconciler) getAPIsForScope(ctx context.Context, obj k8c requests = append(requests, apiReconciler.getAPIForGQLRoute(ctx, &httpRoute)...) } + grpcRouteList := &gwapiv1a2.GRPCRouteList{} + if err := apiReconciler.client.List(ctx, grpcRouteList, &k8client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(grpcRouteScopeIndex, utils.NamespacedName(scope).String()), + }); err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2625, logging.CRITICAL, "Unable to find associated GRPCRoutes: %s", utils.NamespacedName(scope).String())) + return []reconcile.Request{} + } + + if len(grpcRouteList.Items) == 0 { + loggers.LoggerAPKOperator.Debugf("GRPCRoutes for scope not found: %s", utils.NamespacedName(scope).String()) + } + for item := range grpcRouteList.Items { + grpcRoute := grpcRouteList.Items[item] + requests = append(requests, apiReconciler.getAPIForGRPCRoute(ctx, &grpcRoute)...) + } + return requests } From 60fcea5895055d7d2aa7bfafe1c28a99ef4ed193 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 26 Apr 2024 11:53:19 +0530 Subject: [PATCH 52/81] remove todo --- adapter/internal/oasparser/envoyconf/routes_with_clusters.go | 1 - adapter/internal/oasparser/model/adapter_internal_api.go | 1 - 2 files changed, 2 deletions(-) diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 172917eb6..dc2b983c3 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -235,7 +235,6 @@ func CreateRoutesWithClusters(adapterInternalAPI *model.AdapterInternalAPI, inte clusterName = existingClusterName } // Create resource level interceptor clusters if required - //TODO remove this if no interceptor needed for grpc (Dineth) clustersI, endpointsI, operationalReqInterceptors, operationalRespInterceptorVal := createInterceptorResourceClusters(adapterInternalAPI, interceptorCerts, vHost, organizationID, apiRequestInterceptor, apiResponseInterceptor, resource) clusters = append(clusters, clustersI...) diff --git a/adapter/internal/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 09e120b79..0e698a741 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -1073,7 +1073,6 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGRPCRouteCR(grpcRoute *gwap loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", adapterInternalAPI.UUID) apiAuth := getSecurity(resourceAuthScheme) - //TODO Add path match type (Dineth) for _, match := range rule.Matches { resourcePath := adapterInternalAPI.GetXWso2Basepath() + "." + *match.Method.Service + "/" + *match.Method.Method endPoints = append(endPoints, GetEndpoints(backendName, resourceParams.BackendMapping)...) From bdbcaba38214e108511c6d07df85893833aaa8da Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 19 Apr 2024 09:56:37 +0530 Subject: [PATCH 53/81] added grpc spec structs for ballerina --- .../ballerina/APIClient.bal | 99 ++++++++++++++++++- .../ballerina/ConfigGenreatorClient.bal | 2 +- .../ballerina/constants.bal | 3 +- .../ballerina/modules/model/APIArtifact.bal | 2 + .../ballerina/modules/model/GRPCRoute.bal | 76 ++++++++++++++ 5 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 5fcdd78ee..58b0921d3 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -442,6 +442,18 @@ public class APIClient { sandboxRoutes.push(gqlRoute.metadata.name); } } + } else if apkConf.'type == API_TYPE_GRPC{ + foreach model:GRPCRoute grpcRoute in apiArtifact.productionGrpcRoutes { + if grpcRoute.spec.rules.length() > 0 { + productionRoutes.push(grpcRoute.metadata.name); + } + } + foreach model:GRPCRoute grpcRoute in apiArtifact.sandboxGrpcRoutes { + if grpcRoute.spec.rules.length() > 0 { + sandboxRoutes.push(grpcRoute.metadata.name); + } + } + } else { foreach model:HTTPRoute httpRoute in apiArtifact.productionHttpRoutes { if httpRoute.spec.rules.length() > 0 { @@ -532,6 +544,28 @@ public class APIClient { apiArtifact.sandboxGqlRoutes.push(gqlRoute); } } + } else if apkConf.'type == API_TYPE_GRPC { + model:GRPCRoute grpcRoute = { + metadata: + { + name: uniqueId + "-" + endpointType + "-grpcroute-" + count.toString(), + labels: self.getLabels(apkConf, organization) + }, + spec: { + parentRefs: self.generateAndRetrieveParentRefs(apkConf, uniqueId), + rules: check self.generateGRPCRouteRules(apiArtifact, apkConf, endpoint, endpointType, organization), + hostnames: self.getHostNames(apkConf, uniqueId, endpointType, organization) + } + }; + if endpoint is model:Endpoint { + grpcRoute.spec.backendRefs = self.retrieveGeneratedBackend(apkConf, endpoint, endpointType); + } + if grpcRoute.spec.rules.length() > 0 { + if endpointType == PRODUCTION_TYPE { + apiArtifact.productionGrpcRoutes.push(grpcRoute); + } else { + apiArtifact.sandboxGrpcRoutes.push(grpcRoute); + } } else { model:HTTPRoute httpRoute = { metadata: @@ -555,6 +589,7 @@ public class APIClient { } return; + } } private isolated function generateAndRetrieveParentRefs(APKConf apkConf, string uniqueId) returns model:ParentReference[] { @@ -571,7 +606,7 @@ public class APIClient { APKOperations[]? operations = apkConf.operations; if operations is APKOperations[] { foreach APKOperations operation in operations { - model:HTTPRouteRule|model:GQLRouteRule|() routeRule = check self.generateRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); + model:HTTPRouteRule|model:GQLRouteRule|model:GRPCRouteRule|() routeRule = check self.generateRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); if routeRule is model:HTTPRouteRule { model:HTTPRouteFilter[]? filters = routeRule.filters; if filters is () { @@ -625,12 +660,72 @@ public class APIClient { return httpRouteRules; } + private isolated function generateGRPCRouteRules(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string endpointType, commons:Organization organization) returns model:GRPCRouteRule[]|commons:APKError|error { + model:GRPCRouteRule[] grpcRouteRules = []; + APKOperations[]? operations = apkConf.operations; + if operations is APKOperations[] { + foreach APKOperations operation in operations { + model:HTTPRouteRule|model:GQLRouteRule|model:GRPCRouteRule|() routeRule = check self.generateRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); + if routeRule is model:GRPCRouteRule { + model:GRPCRouteFilter[]? filters = routeRule.filters; + if filters is () { + filters = []; + routeRule.filters = filters; + } + string disableAuthenticationRefName = self.retrieveDisableAuthenticationRefName(apkConf, endpointType, organization); + if !(operation.secured ?: true) { + if !apiArtifact.authenticationMap.hasKey(disableAuthenticationRefName) { + model:Authentication generateDisableAuthenticationCR = self.generateDisableAuthenticationCR(apiArtifact, apkConf, endpointType, organization); + apiArtifact.authenticationMap[disableAuthenticationRefName] = generateDisableAuthenticationCR; + } + model:GRPCRouteFilter disableAuthenticationFilter = {'type: "ExtensionRef", extensionRef: {group: "dp.wso2.com", kind: "Authentication", name: disableAuthenticationRefName}}; + (filters).push(disableAuthenticationFilter); + } + string[]? scopes = operation.scopes; + if scopes is string[] { + int count = 1; + foreach string scope in scopes { + model:Scope scopeCr; + if apiArtifact.scopes.hasKey(scope) { + scopeCr = apiArtifact.scopes.get(scope); + } else { + scopeCr = self.generateScopeCR(apiArtifact, apkConf, organization, scope, count); + count = count + 1; + } + model:GRPCRouteFilter scopeFilter = {'type: "ExtensionRef", extensionRef: {group: "dp.wso2.com", kind: scopeCr.kind, name: scopeCr.metadata.name}}; + (filters).push(scopeFilter); + } + } + if operation.rateLimit != () { + model:RateLimitPolicy? rateLimitPolicyCR = self.generateRateLimitPolicyCR(apkConf, operation.rateLimit, apiArtifact.uniqueId, operation, organization); + if rateLimitPolicyCR != () { + apiArtifact.rateLimitPolicies[rateLimitPolicyCR.metadata.name] = rateLimitPolicyCR; + model:GRPCRouteFilter rateLimitPolicyFilter = {'type: "ExtensionRef", extensionRef: {group: "dp.wso2.com", kind: "RateLimitPolicy", name: rateLimitPolicyCR.metadata.name}}; + (filters).push(rateLimitPolicyFilter); + } + } + if operation.operationPolicies != () { + model:APIPolicy? apiPolicyCR = check self.generateAPIPolicyAndBackendCR(apiArtifact, apkConf, operation, operation.operationPolicies, organization, apiArtifact.uniqueId); + if apiPolicyCR != () { + apiArtifact.apiPolicies[apiPolicyCR.metadata.name] = apiPolicyCR; + model:GRPCRouteFilter apiPolicyFilter = {'type: "ExtensionRef", extensionRef: {group: "dp.wso2.com", kind: "APIPolicy", name: apiPolicyCR.metadata.name}}; + (filters).push(apiPolicyFilter); + } + } + grpcRouteRules.push(routeRule); + } + } + } + return grpcRouteRules; + } + + private isolated function generateGQLRouteRules(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, string endpointType, commons:Organization organization) returns model:GQLRouteRule[]|commons:APKError|error { model:GQLRouteRule[] gqlRouteRules = []; APKOperations[]? operations = apkConf.operations; if operations is APKOperations[] { foreach APKOperations operation in operations { - model:HTTPRouteRule|model:GQLRouteRule|() routeRule = check self.generateRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); + model:HTTPRouteRule|model:GQLRouteRule|model:GRPCRouteRule|() routeRule = check self.generateRouteRule(apiArtifact, apkConf, endpoint, operation, endpointType, organization); if routeRule is model:GQLRouteRule { model:GQLRouteFilter[]? filters = routeRule.filters; if filters is () { diff --git a/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal b/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal index e410cdb51..723c471f3 100644 --- a/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal +++ b/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal @@ -93,7 +93,7 @@ public class ConfigGeneratorClient { private isolated function validateAndRetrieveDefinition(string 'type, string? url, byte[]? content, string? fileName) returns runtimeapi:APIDefinitionValidationResponse|runtimeapi:APIManagementException|error|commons:APKError { runtimeapi:APIDefinitionValidationResponse|runtimeapi:APIManagementException|error validationResponse; boolean typeAvailable = 'type.length() > 0; - string[] ALLOWED_API_DEFINITION_TYPES = [API_TYPE_REST, API_TYPE_GRAPHQL, "ASYNC"]; + string[] ALLOWED_API_DEFINITION_TYPES = [API_TYPE_REST, API_TYPE_GRAPHQL, "ASYNC",API_TYPE_GRPC]; if !typeAvailable { return e909005("type"); } diff --git a/runtime/config-deployer-service/ballerina/constants.bal b/runtime/config-deployer-service/ballerina/constants.bal index 66f523456..ed2ad0f6f 100644 --- a/runtime/config-deployer-service/ballerina/constants.bal +++ b/runtime/config-deployer-service/ballerina/constants.bal @@ -11,6 +11,7 @@ public final string[] WS_SUPPORTED_METHODS = ["subscribe", "publish"]; const string API_TYPE_REST = "REST"; const string API_TYPE_GRAPHQL = "GRAPHQL"; +const string API_TYPE_GRPC = "GRPC"; const string API_TYPE_SOAP = "SOAP"; const string API_TYPE_SSE = "SSE"; const string API_TYPE_WS = "WS"; @@ -52,7 +53,7 @@ const string ENDPOINT_SECURITY_PASSWORD = "password"; const string ZIP_FILE_EXTENSTION = ".zip"; const string PROTOCOL_HTTP = "http"; const string PROTOCOL_HTTPS = "https"; -final string[] & readonly ALLOWED_API_TYPES = [API_TYPE_REST, API_TYPE_GRAPHQL]; +final string[] & readonly ALLOWED_API_TYPES = [API_TYPE_REST, API_TYPE_GRAPHQL, API_TYPE_GRPC]; const string MEDIATION_POLICY_TYPE_REQUEST_HEADER_MODIFIER = "RequestHeaderModifier"; const string MEDIATION_POLICY_TYPE_RESPONSE_HEADER_MODIFIER = "ResponseHeaderModifier"; diff --git a/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal b/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal index 7f272176a..b40f95448 100644 --- a/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal +++ b/runtime/config-deployer-service/ballerina/modules/model/APIArtifact.bal @@ -6,6 +6,8 @@ public type APIArtifact record {| HTTPRoute[] sandboxHttpRoutes = []; GQLRoute[] productionGqlRoutes = []; GQLRoute[] sandboxGqlRoutes = []; + GRPCRoute[] productionGrpcRoutes = []; + GRPCRoute[] sandboxGrpcRoutes = []; ConfigMap definition?; map endpointCertificates = {}; map certificateMap = {}; diff --git a/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal new file mode 100644 index 000000000..5f77e6434 --- /dev/null +++ b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal @@ -0,0 +1,76 @@ +// +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +public type GRPCRouteSpec record { + *CommonRouteSpec; + string[] hostnames?; + GRPCRouteRule[] rules = []; + BackendRef[] backendRefs?; +}; + +public type GRPCRouteRule record { + GRPCRouteMatch[] matches?; + GRPCRouteFilter[] filters?; + GRPCBackendRef[] backendRefs?; +}; +public type GRPCRouteMatch record { + GRPCMethodMatch path?; + GRPCHeaderMatch[] headers?; +}; + +public type GRPCHeaderMatch record { + string 'type; + string name; + string value; +}; + +public type GRPCMethodMatch record { + string 'type; + string 'service; + string method; +}; + +public type GRPCRouteFilter record { + string 'type; + HTTPHeaderFilter requestHeaderModifier?; + HTTPHeaderFilter responseHeaderModifier?; + HTTPRequestMirrorFilter requestMirror?; + LocalObjectReference extensionRef?; +}; + +public type GRPCBackendRef record { + *BackendRef; + GRPCRouteFilter[] filters?; +}; + +public type GRPCRoute record {| + string apiVersion = "gateway.networking.k8s.io/v1alpha2"; + string kind = "GRPCRoute"; + Metadata metadata; + GRPCRouteSpec spec; +|}; + + +public type GRPCRouteList record {| + string apiVersion = "gateway.networking.k8s.io/v1alpha2"; + string kind = "GRPCRouteList"; + ListMeta metadata; + GRPCRoute[] items; +|}; + + From aab6843996492df3e8a3eb235b00d4cfee276634 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 19 Apr 2024 23:31:37 +0530 Subject: [PATCH 54/81] conf gets generated from proto file --- .../org/wso2/apk/config/APIConstants.java | 3 +- .../apk/config/DefinitionParserFactory.java | 2 + .../wso2/apk/config/RuntimeAPICommonUtil.java | 33 ++++ .../wso2/apk/config/api/ExceptionCodes.java | 4 + .../apk/config/definitions/OASParserUtil.java | 28 +++ .../apk/config/definitions/ProtoParser.java | 165 ++++++++++++++++++ 6 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/ProtoParser.java diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java index ed4a91e2a..1fcaaadb0 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/APIConstants.java @@ -49,6 +49,7 @@ public final class APIConstants { public static final String OBJECT = "object"; public static final String GRAPHQL_API = "GRAPHQL"; + public static final String GRPC_API = "GRPC"; public static final String APPLICATION_JSON_MEDIA_TYPE = "application/json"; // registry location for OpenAPI files @@ -90,7 +91,7 @@ public final class APIConstants { public static final String GRAPHQL_QUERY = "QUERY"; public enum ParserType { - REST, ASYNC, GRAPHQL + REST, ASYNC, GRAPHQL, GRPC } public static class OperationParameter { diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java index 8cfb71084..167b2d138 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java @@ -25,6 +25,8 @@ public static APIDefinition getParser(API api) { return new OAS3Parser(); } else if (APIConstants.ParserType.ASYNC.name().equals(api.getType())) { return new AsyncApiParser(); + } else if (APIConstants.ParserType.GRPC.name().equals(api.getType())) { + return new OAS3Parser(); } return null; } diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java index 03f043359..4477692c1 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java @@ -6,6 +6,7 @@ import org.wso2.apk.config.api.ExceptionCodes; import org.wso2.apk.config.definitions.GraphQLSchemaDefinition; import org.wso2.apk.config.definitions.OASParserUtil; +import org.wso2.apk.config.definitions.ProtoParser; import org.wso2.apk.config.model.API; import org.wso2.apk.config.model.URITemplate; @@ -66,7 +67,18 @@ public static APIDefinitionValidationResponse validateOpenAPIDefinition(String t OASParserUtil.addErrorToValidationResponse(validationResponse, "Invalid definition file type provided."); } + } else if (APIConstants.ParserType.GRPC.name().equals(type.toUpperCase())) { + if (fileName.endsWith(".proto")) { + validationResponse = OASParserUtil.validateProtoDefinition( + new String(inputByteArray, StandardCharsets.UTF_8), + returnContent); + } else { + OASParserUtil.addErrorToValidationResponse(validationResponse, + "Invalid definition file type provided."); + } } + + return validationResponse; } @@ -99,6 +111,8 @@ public static API getAPIFromDefinition(String definition, String apiType) throws if (apiType.toUpperCase().equals(APIConstants.GRAPHQL_API)) { return getGQLAPIFromDefinition(definition); + } else if (apiType.toUpperCase().equals(APIConstants.GRPC_API)) { + return getGRPCAPIFromProtoDefinition(definition); } else { APIDefinition parser = DefinitionParserFactory.getParser(apiType); if (parser != null) { @@ -128,6 +142,25 @@ private static API getGQLAPIFromDefinition(String definition) { return api; } + private static API getGRPCAPIFromProtoDefinition(String definition) { + ProtoParser protoParser = new ProtoParser(definition); + List uriTemplates = new ArrayList<>(); + API api = new API(); + api.setBasePath(protoParser.protoFile.basePath); + api.setVersion(protoParser.protoFile.version); + for (ProtoParser.Service service : protoParser.getServices()) { + for (String method : service.methods) { + URITemplate uriTemplate = new URITemplate(); + uriTemplate.setUriTemplate("/" + protoParser.protoFile.packageName + "." + service.name); + uriTemplate.setVerb(method); + uriTemplates.add(uriTemplate); + } + } + api.setUriTemplates(uriTemplates.toArray(new URITemplate[uriTemplates.size()])); + + return api; + } + private RuntimeAPICommonUtil() { } diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/ExceptionCodes.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/ExceptionCodes.java index f24a3a2fe..6324d8700 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/ExceptionCodes.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/api/ExceptionCodes.java @@ -361,6 +361,10 @@ public enum ExceptionCodes implements ErrorHandler { UNSUPPORTED_GRAPHQL_FILE_EXTENSION(900802, "Unsupported GraphQL Schema File Extension", 400, "Unsupported extension. Only supported extensions are .graphql, .txt and .sdl"), + //GRPC API related codes + GRPC_PROTO_DEFINTION_CANNOT_BE_NULL(900803, "gRPC Proto Definition cannot be empty or null", 400, + "gRPC Proto Definition cannot be empty or null"), + // Oauth related codes AUTH_GENERAL_ERROR(900900, "Authorization Error", 403, " Error in authorization"), INVALID_CREDENTIALS(900901, "Invalid Credentials", 401, " Invalid username or password"), diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OASParserUtil.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OASParserUtil.java index 66eae511c..4ec23b7df 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OASParserUtil.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/OASParserUtil.java @@ -668,6 +668,34 @@ public static APIDefinitionValidationResponse validateGraphQLSchema(String apiDe return validationResponse; } + /** + * Validate gRPC proto definition + * + * @return Validation response + */ + public static APIDefinitionValidationResponse validateProtoDefinition(String apiDefinition, + boolean returnContent) { + APIDefinitionValidationResponse validationResponse = new APIDefinitionValidationResponse(); + ArrayList errors = new ArrayList<>(); + try { + if (apiDefinition.isBlank()) { + validationResponse.setValid(false); + errors.add(ExceptionCodes.GRPC_PROTO_DEFINTION_CANNOT_BE_NULL); + validationResponse.setErrorItems(errors); + } else { + validationResponse.setValid(true); + validationResponse.setContent(apiDefinition); + } + } catch (Exception e) { + OASParserUtil.addErrorToValidationResponse(validationResponse, e.getMessage()); + validationResponse.setValid(false); + errors.add(new ErrorItem("API Definition Validation Error", "API Definition is invalid", 400, 400)); + validationResponse.setErrorItems(errors); + } + return validationResponse; + + } + /** * This method removes the unsupported json blocks from the given json string. * diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/ProtoParser.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/ProtoParser.java new file mode 100644 index 000000000..2f371f420 --- /dev/null +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/definitions/ProtoParser.java @@ -0,0 +1,165 @@ +package org.wso2.apk.config.definitions; + +import java.util.List; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ProtoParser { + + public ProtoFile protoFile; + public ProtoParser(String content) { + protoFile = parseProtoContent(content); + } + + public String getPackageString(String content) { + Pattern packagePattern = Pattern.compile("package\\s+([\\w\\.]+);"); + Matcher packageMatcher = packagePattern.matcher(content); + if (packageMatcher.find()) { + return packageMatcher.group(1); + } + return null; + } + public String getVersion(String packageString) { + Pattern versionPattern = Pattern.compile("v\\d+(\\.\\d+)*"); + Matcher versionMatcher = versionPattern.matcher(packageString); + if (versionMatcher.find()) { + return versionMatcher.group(0); + } + System.out.println("No version found"); + return null; + } + public String getPackageName(String packageString){ + Pattern namePattern = Pattern.compile("v\\d+(\\.\\d+)*\\.(\\w+)"); + Matcher nameMatcher = namePattern.matcher(packageString); + if (nameMatcher.find()) { + return nameMatcher.group(2); + } + System.out.println("No name found"); + return null; + } + + public String getBasePath(String packageString){ + Pattern basePathPattern = Pattern.compile("^(.*?)v\\d"); + + Matcher basePathMatcher = basePathPattern.matcher(packageString); + if (basePathMatcher.find()) { + String basePath = basePathMatcher.group(1); + if(basePath.charAt(basePath.length()-1) == '.'){ + basePath = basePath.substring(0, basePath.length()-1); + } + return basePath; + } + System.out.println("No base path found"); + return null; + } + + // Method to extract service blocks from a given text + public List extractServiceBlocks(String text) { + // Regular expression pattern to match the service blocks + String patternString = "service\\s+\\w+\\s*\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}"; + + // Compile the regular expression + Pattern pattern = Pattern.compile(patternString, Pattern.DOTALL); + Matcher matcher = pattern.matcher(text); + + // Find all matches and append them to the result + List result = new ArrayList<>(); + while (matcher.find()) { + result.add(matcher.group()); + } + return result; + } + + public List extractMethodNames(String serviceBlock) { + // Regular expression pattern to match the method names + String patternString = "(?<=rpc\\s)\\w+"; + + // Compile the regular expression + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(serviceBlock); + + // Find all matches and append them to the result + List result = new ArrayList<>(); + while (matcher.find()) { + result.add(matcher.group()); + } + return result; + } + + public String getServiceName(String serviceBlock) { + // Regular expression pattern to match the service name + String patternString = "(?<=service\\s)\\w+"; + + // Compile the regular expression + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(serviceBlock); + + // Find the first match and return it + if (matcher.find()) { + return matcher.group(); + } + return null; + } + + public ProtoFile parseProtoContent(String content) { + ProtoFile protoFile = new ProtoFile(); + protoFile.services = new ArrayList<>(); + + List serviceBlocks = extractServiceBlocks(content); + for (String serviceBlock : serviceBlocks) { + Service service = new Service(); + service.name = getServiceName(serviceBlock); + service.methods = new ArrayList<>(); + service.methods.addAll(extractMethodNames(serviceBlock)); + protoFile.services.add(service); + } + + // Extract package name + String packageName = getPackageString(content); + protoFile.packageName = getPackageName(packageName); + protoFile.version = getVersion(packageName); + protoFile.basePath = getBasePath(packageName); + +// System.out.println(protoFile); + + return protoFile; + } + + public List getMethods(Service Service){ + return Service.methods; + } + public List getServices(){ + return this.protoFile.services; + } + public class ProtoFile { + public String packageName; + public String basePath; + public String version; + public List services; + + @Override + public String toString() { + return "ProtoFile{" + + "packageName='" + packageName + '\'' + + ", basePath='" + basePath + '\'' + + ", version='" + version + '\'' + + ", services=" + services + + '}'; + } + + } + + public class Service { + public String name; + public List methods; + + @Override + public String toString() { + return " Service{" + + "name='" + name + '\'' + + ", methods=" + methods + + '}'; + } + } +} \ No newline at end of file From 1c93b91d9a824b7a95734b653c250039461ae17d Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 22 Apr 2024 12:20:16 +0530 Subject: [PATCH 55/81] CRs get generated --- .../ballerina/APIClient.bal | 47 +++++++++++++++++-- .../ballerina/ConfigGenreatorClient.bal | 8 ++++ .../ballerina/DeployerClient.bal | 16 +++++++ .../ballerina/modules/model/GRPCRoute.bal | 2 +- .../java/org/wso2/apk/config/model/API.java | 3 ++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 58b0921d3..85d2b4cf5 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -280,6 +280,14 @@ public class APIClient { return fullBasePath; } + isolated function returnFullGRPCBasePath(string basePath, string 'version) returns string { + string fullBasePath = basePath; + if (!string:endsWith(basePath, 'version)) { + fullBasePath = string:'join(".", basePath, 'version); + } + return fullBasePath; + } + private isolated function constructURlFromK8sService(K8sService 'k8sService) returns string { return k8sService.protocol + "://" + string:'join(".", k8sService.name, k8sService.namespace, "svc.cluster.local") + ":" + k8sService.port.toString(); } @@ -443,6 +451,7 @@ public class APIClient { } } } else if apkConf.'type == API_TYPE_GRPC{ + k8sAPI.spec.basePath = self.returnFullGRPCBasePath(apkConf.basePath, apkConf.'version); foreach model:GRPCRoute grpcRoute in apiArtifact.productionGrpcRoutes { if grpcRoute.spec.rules.length() > 0 { productionRoutes.push(grpcRoute.metadata.name); @@ -849,7 +858,7 @@ public class APIClient { return authentication; } - private isolated function generateRouteRule(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, APKOperations operation, string endpointType, commons:Organization organization) returns model:HTTPRouteRule|model:GQLRouteRule|()|commons:APKError { + private isolated function generateRouteRule(model:APIArtifact apiArtifact, APKConf apkConf, model:Endpoint? endpoint, APKOperations operation, string endpointType, commons:Organization organization) returns model:HTTPRouteRule|model:GQLRouteRule|model:GRPCRouteRule|()|commons:APKError { do { EndpointConfigurations? endpointConfig = operation.endpointConfigurations; model:Endpoint? endpointToUse = (); @@ -873,7 +882,16 @@ public class APIClient { } else { return e909022("Provided Type currently not supported for GraphQL APIs.", error("Provided Type currently not supported for GraphQL APIs.")); } - } else { + } else if apkConf.'type == API_TYPE_GRPC { + model:GRPCRouteMatch[]|error routeMatches = self.retrieveGRPCMatches(apkConf, operation, organization); + if routeMatches is model:GRPCRouteMatch[] && routeMatches.length() > 0 { + model:GRPCRouteRule grpcRouteRule = {matches: routeMatches, backendRefs: self.retrieveGeneratedBackend(apkConf, endpointToUse, endpointType)}; + return grpcRouteRule; + } else { + return e909022("Provided Type currently not supported for GRPC APIs.", error("Provided Type currently not supported for GRPC APIs.")); + } + } + else { model:HTTPRouteRule httpRouteRule = {matches: self.retrieveHTTPMatches(apkConf, operation, organization), backendRefs: self.retrieveGeneratedBackend(apkConf, endpointToUse, endpointType), filters: self.generateFilters(apiArtifact, apkConf, endpointToUse, operation, endpointType, organization)}; return httpRouteRule; } @@ -1018,6 +1036,13 @@ public class APIClient { return gqlRouteMatch; } + + private isolated function retrieveGRPCMatches(APKConf apkConf, APKOperations apiOperation, commons:Organization organization) returns model:GRPCRouteMatch[] { + model:GRPCRouteMatch[] grpcRouteMatch = []; + model:GRPCRouteMatch grpcRoute = self.retrieveGRPCRouteMatch(apiOperation); + grpcRouteMatch.push(grpcRoute); + return grpcRouteMatch; + } private isolated function retrieveHttpRouteMatch(APKConf apkConf, APKOperations apiOperation, commons:Organization organization) returns model:HTTPRouteMatch { return {method: apiOperation.verb, path: {'type: "RegularExpression", value: self.retrievePathPrefix(apkConf.basePath, apkConf.'version, apiOperation.target ?: "/*", organization)}}; @@ -1032,6 +1057,17 @@ public class APIClient { } } + private isolated function retrieveGRPCRouteMatch(APKOperations apiOperation) returns model:GRPCRouteMatch { + model:GRPCRouteMatch grpcRouteMatch = { + method: { + 'type: "RegularExpression", + 'service: apiOperation.target, + method: apiOperation.verb + } + }; + return grpcRouteMatch; + } + isolated function retrieveGeneratedSwaggerDefinition(APKConf apkConf, string? definition) returns string|json|commons:APKError|error { runtimeModels:API api1 = runtimeModels:newAPI1(); api1.setName(apkConf.name); @@ -1068,6 +1104,11 @@ public class APIClient { api1.setGraphQLSchema(definition); return definition; } + if apkConf.'type == API_TYPE_GRPC && definition is string { + // TODO (Dineth) fix this + // api1.setProtoDefinition(definition); + return definition; + } if definition is string && definition.toString().trim().length() > 0 { retrievedDefinition = runtimeUtil:RuntimeAPICommonUtil_generateDefinition2(api1, definition); } else { @@ -1739,7 +1780,7 @@ public class APIClient { } else if definitionFile.fileName.endsWith(".json") { apiDefinition = definitionFileContent; } - } else if apiType == API_TYPE_GRAPHQL { + } else if apiType == API_TYPE_GRAPHQL || apiType == API_TYPE_GRPC { apiDefinition = definitionFileContent; } if apkConf is () { diff --git a/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal b/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal index 723c471f3..5ca4070f1 100644 --- a/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal +++ b/runtime/config-deployer-service/ballerina/ConfigGenreatorClient.bal @@ -187,6 +187,14 @@ public class ConfigGeneratorClient { string yamlString = check self.convertJsonToYaml(gqlRoute.toJsonString()); _ = check self.storeFile(yamlString, gqlRoute.metadata.name, zipDir); } + foreach model:GRPCRoute grpcRoute in apiArtifact.productionGrpcRoutes { + string yamlString = check self.convertJsonToYaml(grpcRoute.toJsonString()); + _ = check self.storeFile(yamlString, grpcRoute.metadata.name, zipDir); + } + foreach model:GRPCRoute grpcRoute in apiArtifact.sandboxGrpcRoutes { + string yamlString = check self.convertJsonToYaml(grpcRoute.toJsonString()); + _ = check self.storeFile(yamlString, grpcRoute.metadata.name, zipDir); + } foreach model:Backend backend in apiArtifact.backendServices { string yamlString = check self.convertJsonToYaml(backend.toJsonString()); _ = check self.storeFile(yamlString, backend.metadata.name, zipDir); diff --git a/runtime/config-deployer-service/ballerina/DeployerClient.bal b/runtime/config-deployer-service/ballerina/DeployerClient.bal index e5beb5d2d..30dfdd9c5 100644 --- a/runtime/config-deployer-service/ballerina/DeployerClient.bal +++ b/runtime/config-deployer-service/ballerina/DeployerClient.bal @@ -442,6 +442,22 @@ public class DeployerClient { return e909022("Error occured while sorting gqlRoutes", e); } } + public isolated function createGrpcRoutesOrder(model:GRPCRoute[] grpcRoutes) returns model:GRPCRoute[]|commons:APKError { + do { + foreach model:GRPCRoute route in grpcRoutes { + model:GRPCRouteRule[] routeRules = route.spec.rules; + // TODO (Dineth) order the routes + // model:GRPCRouteRule[] sortedRouteRules = from var routeRule in routeRules + // order by ((routeRule.matches)[0]).path descending + // select routeRule; + route.spec.rules = routeRules; + } + return grpcRoutes; + } on fail var e { + log:printError("Error occured while sorting grpcRoutes", e); + return e909022("Error occured while sorting grpcRoutes", e); + } + } private isolated function deployAuthenticationCRs(model:APIArtifact apiArtifact, model:OwnerReference ownerReference) returns error? { string[] keys = apiArtifact.authenticationMap.keys(); diff --git a/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal index 5f77e6434..ff519281d 100644 --- a/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal +++ b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal @@ -29,7 +29,7 @@ public type GRPCRouteRule record { GRPCBackendRef[] backendRefs?; }; public type GRPCRouteMatch record { - GRPCMethodMatch path?; + GRPCMethodMatch method?; GRPCHeaderMatch[] headers?; }; diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java index d2715d77b..9ec779906 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java @@ -79,6 +79,9 @@ public void setGraphQLSchema(String graphQLSchema) { this.graphQLSchema = graphQLSchema; } + public void setProtoDefinition(String protoDefinition) { + this.swaggerDefinition = protoDefinition; + } public String getSwaggerDefinition() { return swaggerDefinition; } From a6169225424f8f7c2be3615a22260ab73fc21c92 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 22 Apr 2024 15:08:07 +0530 Subject: [PATCH 56/81] gRPC CRs get generated --- runtime/config-deployer-service/ballerina/APIClient.bal | 3 --- .../ballerina/modules/model/GRPCRoute.bal | 1 - 2 files changed, 4 deletions(-) diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 85d2b4cf5..ea269744a 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -566,9 +566,6 @@ public class APIClient { hostnames: self.getHostNames(apkConf, uniqueId, endpointType, organization) } }; - if endpoint is model:Endpoint { - grpcRoute.spec.backendRefs = self.retrieveGeneratedBackend(apkConf, endpoint, endpointType); - } if grpcRoute.spec.rules.length() > 0 { if endpointType == PRODUCTION_TYPE { apiArtifact.productionGrpcRoutes.push(grpcRoute); diff --git a/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal index ff519281d..887fb3119 100644 --- a/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal +++ b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal @@ -20,7 +20,6 @@ public type GRPCRouteSpec record { *CommonRouteSpec; string[] hostnames?; GRPCRouteRule[] rules = []; - BackendRef[] backendRefs?; }; public type GRPCRouteRule record { From 75a2b9f021eefc30406b988ab0d86c979b235bc0 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 22 Apr 2024 15:29:09 +0530 Subject: [PATCH 57/81] fixed basepath and service path issue --- .../main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java index 4477692c1..ebca7e8e4 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java @@ -146,12 +146,12 @@ private static API getGRPCAPIFromProtoDefinition(String definition) { ProtoParser protoParser = new ProtoParser(definition); List uriTemplates = new ArrayList<>(); API api = new API(); - api.setBasePath(protoParser.protoFile.basePath); + api.setBasePath("/"+protoParser.protoFile.basePath); api.setVersion(protoParser.protoFile.version); for (ProtoParser.Service service : protoParser.getServices()) { for (String method : service.methods) { URITemplate uriTemplate = new URITemplate(); - uriTemplate.setUriTemplate("/" + protoParser.protoFile.packageName + "." + service.name); + uriTemplate.setUriTemplate(protoParser.protoFile.packageName + "." + service.name); uriTemplate.setVerb(method); uriTemplates.add(uriTemplate); } From 250f3ffc8c41acc99016b601d5df8a5d0da1a163 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 22 Apr 2024 17:22:02 +0530 Subject: [PATCH 58/81] fixed TODOs --- .../ballerina/APIClient.bal | 3 +-- .../ballerina/DeployerClient.bal | 9 ++++----- .../ballerina/modules/model/GRPCRoute.bal | 4 ++-- .../modules/org.wso2.apk.config.model/API.bal | 20 +++++++++++++++++++ .../java/org/wso2/apk/config/model/API.java | 7 ++++++- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index ea269744a..02e78c1e6 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -1102,8 +1102,7 @@ public class APIClient { return definition; } if apkConf.'type == API_TYPE_GRPC && definition is string { - // TODO (Dineth) fix this - // api1.setProtoDefinition(definition); + api1.setProtoDefinition(definition); return definition; } if definition is string && definition.toString().trim().length() > 0 { diff --git a/runtime/config-deployer-service/ballerina/DeployerClient.bal b/runtime/config-deployer-service/ballerina/DeployerClient.bal index 30dfdd9c5..e2c770dd7 100644 --- a/runtime/config-deployer-service/ballerina/DeployerClient.bal +++ b/runtime/config-deployer-service/ballerina/DeployerClient.bal @@ -446,11 +446,10 @@ public class DeployerClient { do { foreach model:GRPCRoute route in grpcRoutes { model:GRPCRouteRule[] routeRules = route.spec.rules; - // TODO (Dineth) order the routes - // model:GRPCRouteRule[] sortedRouteRules = from var routeRule in routeRules - // order by ((routeRule.matches)[0]).path descending - // select routeRule; - route.spec.rules = routeRules; + model:GRPCRouteRule[] sortedRouteRules = from var routeRule in routeRules + order by (routeRule.matches[0].method.'service) descending + select routeRule; + route.spec.rules = sortedRouteRules; } return grpcRoutes; } on fail var e { diff --git a/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal index 887fb3119..4087ea485 100644 --- a/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal +++ b/runtime/config-deployer-service/ballerina/modules/model/GRPCRoute.bal @@ -23,12 +23,12 @@ public type GRPCRouteSpec record { }; public type GRPCRouteRule record { - GRPCRouteMatch[] matches?; + GRPCRouteMatch[] matches; GRPCRouteFilter[] filters?; GRPCBackendRef[] backendRefs?; }; public type GRPCRouteMatch record { - GRPCMethodMatch method?; + GRPCMethodMatch method; GRPCHeaderMatch[] headers?; }; diff --git a/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal b/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal index 8638e05be..42470efc5 100644 --- a/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal +++ b/runtime/config-deployer-service/ballerina/modules/org.wso2.apk.config.model/API.bal @@ -183,6 +183,13 @@ public distinct class API { org_wso2_apk_config_model_API_setGraphQLSchema(self.jObj, java:fromString(arg0)); } + # The function that maps to the `setProtoDefinition` method of `org.wso2.apk.config.model.API`. + # + // # + arg0 - The `string` value required to map with the Java method parameter. + public isolated function setProtoDefinition(string arg0) { + org_wso2_apk_config_model_API_setProtoDefinition(self.jObj, java:fromString(arg0)); + } + # The function that maps to the `setName` method of `org.wso2.apk.config.model.API`. # # + arg0 - The `string` value required to map with the Java method parameter. @@ -328,6 +335,13 @@ isolated function org_wso2_apk_config_model_API_getGraphQLSchema(handle receiver paramTypes: [] } external; + +isolated function org_wso2_apk_config_model_API_getProtoDefinition(handle receiver) returns handle = @java:Method { + name: "getProtoDefinition", + 'class: "org.wso2.apk.config.model.API", + paramTypes: [] +} external; + isolated function org_wso2_apk_config_model_API_getName(handle receiver) returns handle = @java:Method { name: "getName", 'class: "org.wso2.apk.config.model.API", @@ -412,6 +426,12 @@ isolated function org_wso2_apk_config_model_API_setGraphQLSchema(handle receiver paramTypes: ["java.lang.String"] } external; +isolated function org_wso2_apk_config_model_API_setProtoDefinition(handle receiver, handle arg0) = @java:Method { + name: "setProtoDefinition", + 'class: "org.wso2.apk.config.model.API", + paramTypes: ["java.lang.String"] +} external; + isolated function org_wso2_apk_config_model_API_setName(handle receiver, handle arg0) = @java:Method { name: "setName", 'class: "org.wso2.apk.config.model.API", diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java index 9ec779906..a88c2508f 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/model/API.java @@ -11,6 +11,7 @@ public class API { private String apiSecurity; private String[] scopes; private String graphQLSchema; + private String protoDefinition; private String swaggerDefinition; private String environment; @@ -80,7 +81,11 @@ public void setGraphQLSchema(String graphQLSchema) { } public void setProtoDefinition(String protoDefinition) { - this.swaggerDefinition = protoDefinition; + this.protoDefinition = protoDefinition; + } + + public String getProtoDefinition() { + return protoDefinition; } public String getSwaggerDefinition() { return swaggerDefinition; From 41ae5cc0eac756ca8848f4117c393dd4f7c16f09 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 24 Apr 2024 10:51:59 +0530 Subject: [PATCH 59/81] Add deploying api using apk-conf and proto file --- .../ballerina/DeployerClient.bal | 60 ++++++++++++++++++- .../ballerina/K8sClient.bal | 25 +++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/runtime/config-deployer-service/ballerina/DeployerClient.bal b/runtime/config-deployer-service/ballerina/DeployerClient.bal index e2c770dd7..21d0aef40 100644 --- a/runtime/config-deployer-service/ballerina/DeployerClient.bal +++ b/runtime/config-deployer-service/ballerina/DeployerClient.bal @@ -95,6 +95,7 @@ public class DeployerClient { apiArtifact.namespace = apiPartition.namespace; if existingAPI is model:API { check self.deleteHttpRoutes(existingAPI, apiArtifact?.organization); + check self.deleteGrpcRoutes(existingAPI, apiArtifact?.organization); check self.deleteAuthenticationCRs(existingAPI, apiArtifact?.organization); _ = check self.deleteScopeCrsForAPI(existingAPI, apiArtifact?.organization); check self.deleteBackends(existingAPI, apiArtifact?.organization); @@ -122,8 +123,8 @@ public class DeployerClient { check self.deployBackendJWTConfigs(apiArtifact, ownerReference); check self.deployAPIPolicyCRs(apiArtifact, ownerReference); - check self.deployRoutes(apiArtifact.productionHttpRoutes, apiArtifact.productionGqlRoutes, apiArtifact?.namespace, ownerReference); - check self.deployRoutes(apiArtifact.sandboxHttpRoutes, apiArtifact.sandboxGqlRoutes, apiArtifact?.namespace, ownerReference); + check self.deployRoutes(apiArtifact.productionHttpRoutes, apiArtifact.productionGqlRoutes,apiArtifact.productionGrpcRoutes, apiArtifact?.namespace, ownerReference); + check self.deployRoutes(apiArtifact.sandboxHttpRoutes, apiArtifact.sandboxGqlRoutes,apiArtifact.sandboxGrpcRoutes, apiArtifact?.namespace, ownerReference); return deployK8sAPICrResult; } on fail var e { @@ -173,6 +174,30 @@ public class DeployerClient { } } + private isolated function deleteGrpcRoutes(model:API api, string organization) returns commons:APKError? { + do { + model:GRPCRouteList|http:ClientError grpcRouteListResponse = check getGrpcRoutesForAPIs(api.spec.apiName, api.spec.apiVersion, api.metadata?.namespace, organization); + if grpcRouteListResponse is model:GRPCRouteList { + foreach model:GRPCRoute item in grpcRouteListResponse.items { + http:Response|http:ClientError grpcRouteDeletionResponse = deleteGrpcRoute(item.metadata.name, api.metadata?.namespace); + if grpcRouteDeletionResponse is http:Response { + if grpcRouteDeletionResponse.statusCode != http:STATUS_OK { + json responsePayLoad = check grpcRouteDeletionResponse.getJsonPayload(); + model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status); + check self.handleK8sTimeout(statusResponse); + } + } else { + log:printError("Error occured while deleting GrpcRoute", grpcRouteDeletionResponse); + } + } + return; + } + } on fail var e { + log:printError("Error occured deleting grpcRoutes", e); + return e909022("Error occured deleting grpcRoutes", e); + } + } + private isolated function deleteHttpRoutes(model:API api, string organization) returns commons:APKError? { do { model:HTTPRouteList|http:ClientError httpRouteListResponse = check getHttproutesForAPIS(api.spec.apiName, api.spec.apiVersion, api.metadata?.namespace, organization); @@ -349,7 +374,7 @@ public class DeployerClient { return e909022("Internal error occured", e = error("Internal error occured")); } } - private isolated function deployRoutes(model:HTTPRoute[]? httproutes, model:GQLRoute[]? gqlroutes, string namespace, model:OwnerReference ownerReference) returns error? { + private isolated function deployRoutes(model:HTTPRoute[]? httproutes, model:GQLRoute[]? gqlroutes,model:GRPCRoute[]? grpcroutes, string namespace, model:OwnerReference ownerReference) returns error? { if httproutes is model:HTTPRoute[] && httproutes.length() > 0 { model:HTTPRoute[] deployReadyHttproutes = httproutes; model:HTTPRoute[]|commons:APKError orderedHttproutes = self.createHttpRoutesOrder(httproutes); @@ -408,6 +433,35 @@ public class DeployerClient { } } } + } else if grpcroutes is model:GRPCRoute[] && grpcroutes.length() > 0 { + model:GRPCRoute[] deployReadyGrpcRoutes = grpcroutes; + model:GRPCRoute[]|commons:APKError orderedGrpcRoutes = self.createGrpcRoutesOrder(grpcroutes); + if orderedGrpcRoutes is model:GRPCRoute[] { + deployReadyGrpcRoutes = orderedGrpcRoutes; + } + foreach model:GRPCRoute grpcRoute in deployReadyGrpcRoutes { + grpcRoute.metadata.ownerReferences = [ownerReference]; + if grpcRoute.spec.rules.length() > 0 { + http:Response deployGrpcRouteResult = check deployGrpcRoute(grpcRoute, namespace); + if deployGrpcRouteResult.statusCode == http:STATUS_CREATED { + log:printDebug("Deployed GrpcRoute Successfully" + grpcRoute.toString()); + } else if deployGrpcRouteResult.statusCode == http:STATUS_CONFLICT { + log:printDebug("GrpcRoute already exists" + grpcRoute.toString()); + model:GRPCRoute grpcRouteFromK8s = check getGrpcRoute(grpcRoute.metadata.name, namespace); + grpcRoute.metadata.resourceVersion = grpcRouteFromK8s.metadata.resourceVersion; + http:Response grpcRouteCR = check updateGrpcRoute(grpcRoute, namespace); + if grpcRouteCR.statusCode != http:STATUS_OK { + json responsePayLoad = check grpcRouteCR.getJsonPayload(); + model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status); + check self.handleK8sTimeout(statusResponse); + } + } else { + json responsePayLoad = check deployGrpcRouteResult.getJsonPayload(); + model:Status statusResponse = check responsePayLoad.cloneWithType(model:Status); + check self.handleK8sTimeout(statusResponse); + } + } + } } } diff --git a/runtime/config-deployer-service/ballerina/K8sClient.bal b/runtime/config-deployer-service/ballerina/K8sClient.bal index 5e7757206..cdc03e00b 100644 --- a/runtime/config-deployer-service/ballerina/K8sClient.bal +++ b/runtime/config-deployer-service/ballerina/K8sClient.bal @@ -100,6 +100,16 @@ isolated function deleteGqlRoute(string name, string namespace) returns http:Res return k8sApiServerEp->delete(endpoint, targetType = http:Response); } +isolated function getGrpcRoute(string name, string namespace) returns model:GRPCRoute|http:ClientError { + string endpoint = "/apis/gateway.networking.k8s.io/v1alpha2/namespaces/" + namespace + "/grpcroutes/" + name; + return k8sApiServerEp->get(endpoint, targetType = model:GRPCRoute); +} + +isolated function deleteGrpcRoute(string name, string namespace) returns http:Response|http:ClientError { + string endpoint = "/apis/gateway.networking.k8s.io/v1alpha2/namespaces/" + namespace + "/grpcroutes/" + name; + return k8sApiServerEp->delete(endpoint, targetType = http:Response); +} + isolated function getConfigMap(string name, string namespace) returns model:ConfigMap|http:ClientError { string endpoint = "/api/v1/namespaces/" + namespace + "/configmaps/" + name; return k8sApiServerEp->get(endpoint, targetType = model:ConfigMap); @@ -150,6 +160,16 @@ isolated function updateGqlRoute(model:GQLRoute gqlroute, string namespace) retu return k8sApiServerEp->put(endpoint, gqlroute, targetType = http:Response); } +isolated function deployGrpcRoute(model:GRPCRoute grpcRoute, string namespace) returns http:Response|http:ClientError { + string endpoint = "/apis/gateway.networking.k8s.io/v1alpha2/namespaces/" + namespace + "/grpcroutes"; + return k8sApiServerEp->post(endpoint, grpcRoute, targetType = http:Response); +} + +isolated function updateGrpcRoute(model:GRPCRoute grpcRoute, string namespace) returns http:Response|http:ClientError { + string endpoint = "/apis/gateway.networking.k8s.io/v1alpha2/namespaces/" + namespace + "/grpcroutes/" + grpcRoute.metadata.name; + return k8sApiServerEp->put(endpoint, grpcRoute, targetType = http:Response); +} + public isolated function getK8sAPIByNameAndNamespace(string name, string namespace) returns model:API?|commons:APKError { string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/apis/" + name; do { @@ -244,7 +264,10 @@ public isolated function getGqlRoutesForAPIs(string apiName, string apiVersion, string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/gqlroutes/?labelSelector=" + check generateUrlEncodedLabelSelector(apiName, apiVersion, organization); return k8sApiServerEp->get(endpoint, targetType = model:GQLRouteList); } - +public isolated function getGrpcRoutesForAPIs(string apiName, string apiVersion, string namespace, string organization) returns model:GRPCRouteList|http:ClientError|error { + string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/grpcroutes/?labelSelector=" + check generateUrlEncodedLabelSelector(apiName, apiVersion, organization); + return k8sApiServerEp->get(endpoint, targetType = model:GRPCRouteList); +} isolated function deployRateLimitPolicyCR(model:RateLimitPolicy rateLimitPolicy, string namespace) returns http:Response|http:ClientError { string endpoint = "/apis/dp.wso2.com/v1alpha1/namespaces/" + namespace + "/ratelimitpolicies"; return k8sApiServerEp->post(endpoint, rateLimitPolicy, targetType = http:Response); From f4249420ac485bf8081cb4da14a22d82d07971d3 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 24 Apr 2024 11:23:49 +0530 Subject: [PATCH 60/81] add cucumber tests --- .../artifacts/apk-confs/grpc/grpc.apk-conf | 27 +++++++++++++++++++ .../artifacts/definitions/student.proto | 21 +++++++++++++++ .../src/test/resources/tests/api/GRPC.feature | 6 +++++ 3 files changed, 54 insertions(+) create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf create mode 100644 test/cucumber-tests/src/test/resources/artifacts/definitions/student.proto create mode 100644 test/cucumber-tests/src/test/resources/tests/api/GRPC.feature diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf new file mode 100644 index 000000000..1653a1f88 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf @@ -0,0 +1,27 @@ +--- +name: "demo-grpc-api" +basePath: "/dineth.grpc.api" +version: "v1" +type: "GRPC" +endpointConfigurations: + production: + endpoint: "http://grpc-backend:6565" +defaultVersion: false +subscriptionValidation: false +operations: +- target: "student.StudentService" + verb: "GetStudent" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "GetStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendAndGetStudentStream" + secured: true + scopes: [] \ No newline at end of file diff --git a/test/cucumber-tests/src/test/resources/artifacts/definitions/student.proto b/test/cucumber-tests/src/test/resources/artifacts/definitions/student.proto new file mode 100644 index 000000000..885c45cfb --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/definitions/student.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.example"; +package dineth.grpc.api.v1.student; + +service StudentService { + rpc GetStudent(StudentRequest) returns (StudentResponse) {}; + rpc GetStudentStream(StudentRequest) returns (stream StudentResponse) {}; + rpc SendStudentStream(stream StudentRequest) returns (StudentResponse) {}; + rpc SendAndGetStudentStream(stream StudentRequest) returns (stream StudentResponse) {} +} + +message StudentRequest { + int32 id = 3; +} + +message StudentResponse { + string name = 1; + int32 age = 2; +} diff --git a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature new file mode 100644 index 000000000..66fd7e748 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -0,0 +1,6 @@ +Feature: Generating APK conf for gRPC API + Scenario: Generating APK conf using a valid GRPC API definition + Given The system is ready + When I use the definition file "artifacts/definitions/student.proto" in resources + And generate the APK conf file for a "gRPC" API + Then the response status code should be 200 From 7fb480c495c311426867e25618941e19107cc201 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 24 Apr 2024 14:49:39 +0530 Subject: [PATCH 61/81] add grpc backend deployment and service CRs --- test/cucumber-tests/CRs/artifacts.yaml | 48 +++++++++++++++++++ .../artifacts/apk-confs/grpc/grpc.apk-conf | 1 + 2 files changed, 49 insertions(+) diff --git a/test/cucumber-tests/CRs/artifacts.yaml b/test/cucumber-tests/CRs/artifacts.yaml index cf592dad8..c49bb4680 100644 --- a/test/cucumber-tests/CRs/artifacts.yaml +++ b/test/cucumber-tests/CRs/artifacts.yaml @@ -1154,3 +1154,51 @@ spec: protocol: TCP selector: app: graphql-faker +--- +apiVersion: v1 +kind: Service +metadata: + name: grpc-backend-v1 + namespace: apk-integration-test +spec: + selector: + app: grpc-backend-v1 + ports: + - protocol: TCP + port: 6565 + targetPort: 6565 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grpc-backend-v1 + namespace: apk-integration-test + labels: + app: grpc-backend-v1 +spec: + replicas: 1 + selector: + matchLabels: + app: grpc-backend-v1 + template: + metadata: + labels: + app: grpc-backend-v1 + spec: + containers: + - name: grpc-backend-v1 + image: ddh13/dineth-grpc-demo-server:1.0.0 + imagePullPolicy: Always + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + requests: + cpu: 10m +--- \ No newline at end of file diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf index 1653a1f88..4d291742b 100644 --- a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc.apk-conf @@ -3,6 +3,7 @@ name: "demo-grpc-api" basePath: "/dineth.grpc.api" version: "v1" type: "GRPC" +id: "grpc-basic-api" endpointConfigurations: production: endpoint: "http://grpc-backend:6565" From 5fa31113eec2efbfc1687a643d9cb546bfe4f9c3 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 24 Apr 2024 15:58:02 +0530 Subject: [PATCH 62/81] add code needed for cucumber tests --- .../ballerina/DeployerClient.bal | 2 + test/cucumber-tests/build.gradle | 3 + .../wso2/apk/integration/api/BaseSteps.java | 7 + .../apk/integration/api/SharedContext.java | 20 + .../clients/SimpleGRPCStudentClient.java | 40 ++ .../clients/studentGrpcClient/Student.java | 68 ++ .../studentGrpcClient/StudentRequest.java | 483 ++++++++++++++ .../StudentRequestOrBuilder.java | 15 + .../studentGrpcClient/StudentResponse.java | 621 ++++++++++++++++++ .../StudentResponseOrBuilder.java | 27 + .../studentGrpcClient/StudentServiceGrpc.java | 458 +++++++++++++ .../src/test/resources/tests/api/GRPC.feature | 29 + 12 files changed, 1773 insertions(+) create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleGRPCStudentClient.java create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java diff --git a/runtime/config-deployer-service/ballerina/DeployerClient.bal b/runtime/config-deployer-service/ballerina/DeployerClient.bal index 21d0aef40..3bcfb20e6 100644 --- a/runtime/config-deployer-service/ballerina/DeployerClient.bal +++ b/runtime/config-deployer-service/ballerina/DeployerClient.bal @@ -337,8 +337,10 @@ public class DeployerClient { model:StatusCause[] 'causes = details.'causes; foreach model:StatusCause 'cause in 'causes { if 'cause.'field == "spec.basePath" { + log:printError("Error occured while updating K8sAPI due to basePath ", e909015(k8sAPI.spec.basePath)); return e909015(k8sAPI.spec.basePath); } else if 'cause.'field == "spec.apiName" { + log:printError("Error occured while updating K8sAPI due to apiName ", e909016(k8sAPI.spec.apiName)); return e909016(k8sAPI.spec.apiName); } } diff --git a/test/cucumber-tests/build.gradle b/test/cucumber-tests/build.gradle index 1e348b543..c589336f9 100644 --- a/test/cucumber-tests/build.gradle +++ b/test/cucumber-tests/build.gradle @@ -35,6 +35,9 @@ dependencies { testImplementation 'io.cucumber:cucumber-testng:7.13.0' testImplementation 'commons-io:commons-io:2.13.0' testImplementation 'com.nimbusds:nimbus-jose-jwt:9.31' + implementation 'io.grpc:grpc-netty-shaded:1.57.0' + implementation 'io.grpc:grpc-protobuf:1.57.0' + implementation 'io.grpc:grpc-stub:1.57.0' } test { 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 bdc423ec6..053a0ab0e 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 @@ -49,6 +49,7 @@ import org.testng.Assert; import org.wso2.apk.integration.utils.Constants; import org.wso2.apk.integration.utils.Utils; +import org.wso2.apk.integration.utils.clients.SimpleGRPCStudentClient; import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; import java.io.IOException; @@ -139,6 +140,12 @@ public void sendHttpRequest(String httpMethod, String url, String body) throws I } } + @Then("I make grpc request") + public void sendGrpcRequest() throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(); + sharedContext.setStudentResponse(grpcStudentClient.GetStudent()); + } + // It will send request using a new thread and forget about the response @Then("I send {string} async request to {string} with body {string}") public void sendAsyncHttpRequest(String httpMethod, String url, String body) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java index 782e06173..822ff6335 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -18,7 +18,9 @@ package org.wso2.apk.integration.api; import org.apache.http.HttpResponse; +import org.wso2.apk.integration.utils.clients.SimpleGRPCStudentClient; import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; +import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -31,6 +33,8 @@ public class SharedContext { private SimpleHTTPClient httpClient; + private SimpleGRPCStudentClient grpcStudentClient; + private StudentResponse studentResponse; private String accessToken; private HttpResponse response; private String responseBody; @@ -43,6 +47,12 @@ public SimpleHTTPClient getHttpClient() throws NoSuchAlgorithmException, KeyStor } return httpClient; } + public SimpleGRPCStudentClient getGrpcStudentClient()throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + if (grpcStudentClient == null) { + grpcStudentClient = new SimpleGRPCStudentClient(); + } + return grpcStudentClient; + } public String getAccessToken() { @@ -59,6 +69,16 @@ public HttpResponse getResponse() { return response; } + public StudentResponse getStudentResponse() { + + return studentResponse; + } + + public void setStudentResponse(StudentResponse studentResponse) { + + this.studentResponse = studentResponse; + } + public void setResponse(HttpResponse response) { this.response = response; 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 new file mode 100644 index 000000000..0ab586bbd --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleGRPCStudentClient.java @@ -0,0 +1,40 @@ +package org.wso2.apk.integration.utils.clients; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentRequest; +import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse; +import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentServiceGrpc; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +public class SimpleGRPCStudentClient { + protected Log log = LogFactory.getLog(SimpleGRPCStudentClient.class); + private static final int EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS = 10; + + private ManagedChannel managedChannel; + + public SimpleGRPCStudentClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + this.managedChannel = ManagedChannelBuilder.forAddress("default.gw.wso2.com", 9095).usePlaintext().build(); + log.info("ManagedChannel created"); + } + + public StudentResponse GetStudent() { + //Create a blocking stub for the StudentService + StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); + + // Make a synchronous gRPC call to get student details for ID 1 + StudentResponse studentResponse = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + + // Log the response received from the gRPC server + log.info("response = " + studentResponse.getName() + " " + studentResponse.getAge()); + return studentResponse; + } + + +} + diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java new file mode 100644 index 000000000..30ef19bd2 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java @@ -0,0 +1,68 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: student.proto + +package org.wso2.apk.integration.utils.clients.studentGrpcClient; + +public final class Student { + private Student() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + static final com.google.protobuf.Descriptors.Descriptor + internal_static_student_StudentRequest_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_student_StudentRequest_fieldAccessorTable; + static final com.google.protobuf.Descriptors.Descriptor + internal_static_student_StudentResponse_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_student_StudentResponse_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + String[] descriptorData = { + "\n\rstudent.proto\022\007student\"\034\n\016StudentReque" + + "st\022\n\n\002id\030\003 \001(\005\",\n\017StudentResponse\022\014\n\004nam" + + "e\030\001 \001(\t\022\013\n\003age\030\002 \001(\0052\276\002\n\016StudentService\022" + + "A\n\nGetStudent\022\027.student.StudentRequest\032\030" + + ".student.StudentResponse\"\000\022I\n\020GetStudent" + + "Stream\022\027.student.StudentRequest\032\030.studen" + + "t.StudentResponse\"\0000\001\022J\n\021SendStudentStre" + + "am\022\027.student.StudentRequest\032\030.student.St" + + "udentResponse\"\000(\001\022R\n\027SendAndGetStudentSt" + + "ream\022\027.student.StudentRequest\032\030.student." + + "StudentResponse\"\000(\0010\001B\017\n\013org.exampleP\001b\006" + + "proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_student_StudentRequest_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_student_StudentRequest_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_student_StudentRequest_descriptor, + new String[] { "Id", }); + internal_static_student_StudentResponse_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_student_StudentResponse_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_student_StudentResponse_descriptor, + new String[] { "Name", "Age", }); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java new file mode 100644 index 000000000..a79d2d9d9 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java @@ -0,0 +1,483 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: student.proto + +package org.wso2.apk.integration.utils.clients.studentGrpcClient; + +/** + * Protobuf type {@code student.StudentRequest} + */ +public final class StudentRequest extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:student.StudentRequest) + StudentRequestOrBuilder { +private static final long serialVersionUID = 0L; + // Use StudentRequest.newBuilder() to construct. + private StudentRequest(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private StudentRequest() { + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance( + UnusedPrivateParameter unused) { + return new StudentRequest(); + } + + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private StudentRequest( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 24: { + + id_ = input.readInt32(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return Student.internal_static_student_StudentRequest_descriptor; + } + + @Override + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return Student.internal_static_student_StudentRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + StudentRequest.class, StudentRequest.Builder.class); + } + + public static final int ID_FIELD_NUMBER = 3; + private int id_; + /** + * int32 id = 3; + * @return The id. + */ + @Override + public int getId() { + return id_; + } + + private byte memoizedIsInitialized = -1; + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (id_ != 0) { + output.writeInt32(3, id_); + } + unknownFields.writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (id_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(3, id_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof StudentRequest)) { + return super.equals(obj); + } + StudentRequest other = (StudentRequest) obj; + + if (getId() + != other.getId()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static StudentRequest parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static StudentRequest parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static StudentRequest parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static StudentRequest parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static StudentRequest parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static StudentRequest parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static StudentRequest parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static StudentRequest parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static StudentRequest parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static StudentRequest parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static StudentRequest parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static StudentRequest parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(StudentRequest prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code student.StudentRequest} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:student.StudentRequest) + StudentRequestOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return Student.internal_static_student_StudentRequest_descriptor; + } + + @Override + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return Student.internal_static_student_StudentRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + StudentRequest.class, StudentRequest.Builder.class); + } + + // Construct using StudentRequest.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @Override + public Builder clear() { + super.clear(); + id_ = 0; + + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return Student.internal_static_student_StudentRequest_descriptor; + } + + @Override + public StudentRequest getDefaultInstanceForType() { + return StudentRequest.getDefaultInstance(); + } + + @Override + public StudentRequest build() { + StudentRequest result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public StudentRequest buildPartial() { + StudentRequest result = new StudentRequest(this); + result.id_ = id_; + onBuilt(); + return result; + } + + @Override + public Builder clone() { + return super.clone(); + } + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + Object value) { + return super.setField(field, value); + } + @Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, Object value) { + return super.setRepeatedField(field, index, value); + } + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + Object value) { + return super.addRepeatedField(field, value); + } + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof StudentRequest) { + return mergeFrom((StudentRequest)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(StudentRequest other) { + if (other == StudentRequest.getDefaultInstance()) return this; + if (other.getId() != 0) { + setId(other.getId()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + StudentRequest parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (StudentRequest) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int id_ ; + /** + * int32 id = 3; + * @return The id. + */ + @Override + public int getId() { + return id_; + } + /** + * int32 id = 3; + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId(int value) { + + id_ = value; + onChanged(); + return this; + } + /** + * int32 id = 3; + * @return This builder for chaining. + */ + public Builder clearId() { + + id_ = 0; + onChanged(); + return this; + } + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:student.StudentRequest) + } + + // @@protoc_insertion_point(class_scope:student.StudentRequest) + private static final StudentRequest DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new StudentRequest(); + } + + public static StudentRequest getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @Override + public StudentRequest parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new StudentRequest(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public StudentRequest getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java new file mode 100644 index 000000000..266630f4c --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java @@ -0,0 +1,15 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: student.proto + +package org.wso2.apk.integration.utils.clients.studentGrpcClient; + +public interface StudentRequestOrBuilder extends + // @@protoc_insertion_point(interface_extends:student.StudentRequest) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 id = 3; + * @return The id. + */ + int getId(); +} diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java new file mode 100644 index 000000000..906538a20 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java @@ -0,0 +1,621 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: student.proto + +package org.wso2.apk.integration.utils.clients.studentGrpcClient; + +/** + * Protobuf type {@code student.StudentResponse} + */ +public final class StudentResponse extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:student.StudentResponse) + StudentResponseOrBuilder { +private static final long serialVersionUID = 0L; + // Use StudentResponse.newBuilder() to construct. + private StudentResponse(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private StudentResponse() { + name_ = ""; + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance( + UnusedPrivateParameter unused) { + return new StudentResponse(); + } + + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private StudentResponse( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + String s = input.readStringRequireUtf8(); + + name_ = s; + break; + } + case 16: { + + age_ = input.readInt32(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return Student.internal_static_student_StudentResponse_descriptor; + } + + @Override + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return Student.internal_static_student_StudentResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + StudentResponse.class, StudentResponse.Builder.class); + } + + public static final int NAME_FIELD_NUMBER = 1; + private volatile Object name_; + /** + * string name = 1; + * @return The name. + */ + @Override + public String getName() { + Object ref = name_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + name_ = s; + return s; + } + } + /** + * string name = 1; + * @return The bytes for name. + */ + @Override + public com.google.protobuf.ByteString + getNameBytes() { + Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int AGE_FIELD_NUMBER = 2; + private int age_; + /** + * int32 age = 2; + * @return The age. + */ + @Override + public int getAge() { + return age_; + } + + private byte memoizedIsInitialized = -1; + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!getNameBytes().isEmpty()) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_); + } + if (age_ != 0) { + output.writeInt32(2, age_); + } + unknownFields.writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!getNameBytes().isEmpty()) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_); + } + if (age_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(2, age_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof StudentResponse)) { + return super.equals(obj); + } + StudentResponse other = (StudentResponse) obj; + + if (!getName() + .equals(other.getName())) return false; + if (getAge() + != other.getAge()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + hash = (37 * hash) + AGE_FIELD_NUMBER; + hash = (53 * hash) + getAge(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static StudentResponse parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static StudentResponse parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static StudentResponse parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static StudentResponse parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static StudentResponse parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static StudentResponse parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static StudentResponse parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static StudentResponse parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static StudentResponse parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static StudentResponse parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static StudentResponse parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static StudentResponse parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(StudentResponse prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code student.StudentResponse} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:student.StudentResponse) + StudentResponseOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return Student.internal_static_student_StudentResponse_descriptor; + } + + @Override + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return Student.internal_static_student_StudentResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + StudentResponse.class, StudentResponse.Builder.class); + } + + // Construct using StudentResponse.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @Override + public Builder clear() { + super.clear(); + name_ = ""; + + age_ = 0; + + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return Student.internal_static_student_StudentResponse_descriptor; + } + + @Override + public StudentResponse getDefaultInstanceForType() { + return StudentResponse.getDefaultInstance(); + } + + @Override + public StudentResponse build() { + StudentResponse result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public StudentResponse buildPartial() { + StudentResponse result = new StudentResponse(this); + result.name_ = name_; + result.age_ = age_; + onBuilt(); + return result; + } + + @Override + public Builder clone() { + return super.clone(); + } + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + Object value) { + return super.setField(field, value); + } + @Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, Object value) { + return super.setRepeatedField(field, index, value); + } + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + Object value) { + return super.addRepeatedField(field, value); + } + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof StudentResponse) { + return mergeFrom((StudentResponse)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(StudentResponse other) { + if (other == StudentResponse.getDefaultInstance()) return this; + if (!other.getName().isEmpty()) { + name_ = other.name_; + onChanged(); + } + if (other.getAge() != 0) { + setAge(other.getAge()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + StudentResponse parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (StudentResponse) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private Object name_ = ""; + /** + * string name = 1; + * @return The name. + */ + public String getName() { + Object ref = name_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + name_ = s; + return s; + } else { + return (String) ref; + } + } + /** + * string name = 1; + * @return The bytes for name. + */ + public com.google.protobuf.ByteString + getNameBytes() { + Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string name = 1; + * @param value The name to set. + * @return This builder for chaining. + */ + public Builder setName( + String value) { + if (value == null) { + throw new NullPointerException(); + } + + name_ = value; + onChanged(); + return this; + } + /** + * string name = 1; + * @return This builder for chaining. + */ + public Builder clearName() { + + name_ = getDefaultInstance().getName(); + onChanged(); + return this; + } + /** + * string name = 1; + * @param value The bytes for name to set. + * @return This builder for chaining. + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + name_ = value; + onChanged(); + return this; + } + + private int age_ ; + /** + * int32 age = 2; + * @return The age. + */ + @Override + public int getAge() { + return age_; + } + /** + * int32 age = 2; + * @param value The age to set. + * @return This builder for chaining. + */ + public Builder setAge(int value) { + + age_ = value; + onChanged(); + return this; + } + /** + * int32 age = 2; + * @return This builder for chaining. + */ + public Builder clearAge() { + + age_ = 0; + onChanged(); + return this; + } + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:student.StudentResponse) + } + + // @@protoc_insertion_point(class_scope:student.StudentResponse) + private static final StudentResponse DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new StudentResponse(); + } + + public static StudentResponse getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @Override + public StudentResponse parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new StudentResponse(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public StudentResponse getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java new file mode 100644 index 000000000..4b16af775 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java @@ -0,0 +1,27 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: student.proto + +package org.wso2.apk.integration.utils.clients.studentGrpcClient; + +public interface StudentResponseOrBuilder extends + // @@protoc_insertion_point(interface_extends:student.StudentResponse) + com.google.protobuf.MessageOrBuilder { + + /** + * string name = 1; + * @return The name. + */ + String getName(); + /** + * string name = 1; + * @return The bytes for name. + */ + com.google.protobuf.ByteString + getNameBytes(); + + /** + * int32 age = 2; + * @return The age. + */ + int getAge(); +} diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java new file mode 100644 index 000000000..60a066612 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java @@ -0,0 +1,458 @@ +package org.wso2.apk.integration.utils.clients.studentGrpcClient; + +import static io.grpc.MethodDescriptor.generateFullMethodName; + +/** + */ +//@javax.annotation.Generated( +// value = "by gRPC proto compiler (version 1.39.0)", +// comments = "Source: student.proto") +public final class StudentServiceGrpc { + + private StudentServiceGrpc() {} + + public static final String SERVICE_NAME = "student.StudentService"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getGetStudentMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetStudent", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetStudentMethod() { + io.grpc.MethodDescriptor getGetStudentMethod; + if ((getGetStudentMethod = StudentServiceGrpc.getGetStudentMethod) == null) { + synchronized (StudentServiceGrpc.class) { + if ((getGetStudentMethod = StudentServiceGrpc.getGetStudentMethod) == null) { + StudentServiceGrpc.getGetStudentMethod = getGetStudentMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetStudent")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("GetStudent")) + .build(); + } + } + } + return getGetStudentMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetStudentStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetStudentStream", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + public static io.grpc.MethodDescriptor getGetStudentStreamMethod() { + io.grpc.MethodDescriptor getGetStudentStreamMethod; + if ((getGetStudentStreamMethod = StudentServiceGrpc.getGetStudentStreamMethod) == null) { + synchronized (StudentServiceGrpc.class) { + if ((getGetStudentStreamMethod = StudentServiceGrpc.getGetStudentStreamMethod) == null) { + StudentServiceGrpc.getGetStudentStreamMethod = getGetStudentStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetStudentStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("GetStudentStream")) + .build(); + } + } + } + return getGetStudentStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getSendStudentStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "SendStudentStream", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + public static io.grpc.MethodDescriptor getSendStudentStreamMethod() { + io.grpc.MethodDescriptor getSendStudentStreamMethod; + if ((getSendStudentStreamMethod = StudentServiceGrpc.getSendStudentStreamMethod) == null) { + synchronized (StudentServiceGrpc.class) { + if ((getSendStudentStreamMethod = StudentServiceGrpc.getSendStudentStreamMethod) == null) { + StudentServiceGrpc.getSendStudentStreamMethod = getSendStudentStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SendStudentStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("SendStudentStream")) + .build(); + } + } + } + return getSendStudentStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getSendAndGetStudentStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "SendAndGetStudentStream", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getSendAndGetStudentStreamMethod() { + io.grpc.MethodDescriptor getSendAndGetStudentStreamMethod; + if ((getSendAndGetStudentStreamMethod = StudentServiceGrpc.getSendAndGetStudentStreamMethod) == null) { + synchronized (StudentServiceGrpc.class) { + if ((getSendAndGetStudentStreamMethod = StudentServiceGrpc.getSendAndGetStudentStreamMethod) == null) { + StudentServiceGrpc.getSendAndGetStudentStreamMethod = getSendAndGetStudentStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SendAndGetStudentStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("SendAndGetStudentStream")) + .build(); + } + } + } + return getSendAndGetStudentStreamMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static StudentServiceStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @Override + public StudentServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceStub(channel, callOptions); + } + }; + return StudentServiceStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static StudentServiceBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @Override + public StudentServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceBlockingStub(channel, callOptions); + } + }; + return StudentServiceBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static StudentServiceFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @Override + public StudentServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceFutureStub(channel, callOptions); + } + }; + return StudentServiceFutureStub.newStub(factory, channel); + } + + /** + */ + public static abstract class StudentServiceImplBase implements io.grpc.BindableService { + + /** + */ + public void getStudent(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetStudentMethod(), responseObserver); + } + + /** + */ + public void getStudentStream(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetStudentStreamMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getSendStudentStreamMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendAndGetStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getSendAndGetStudentStreamMethod(), responseObserver); + } + + @Override public final io.grpc.ServerServiceDefinition bindService() { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getGetStudentMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_GET_STUDENT))) + .addMethod( + getGetStudentStreamMethod(), + io.grpc.stub.ServerCalls.asyncServerStreamingCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_GET_STUDENT_STREAM))) + .addMethod( + getSendStudentStreamMethod(), + io.grpc.stub.ServerCalls.asyncClientStreamingCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_SEND_STUDENT_STREAM))) + .addMethod( + getSendAndGetStudentStreamMethod(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_SEND_AND_GET_STUDENT_STREAM))) + .build(); + } + } + + /** + */ + public static final class StudentServiceStub extends io.grpc.stub.AbstractAsyncStub { + private StudentServiceStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @Override + protected StudentServiceStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceStub(channel, callOptions); + } + + /** + */ + public void getStudent(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetStudentMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public void getStudentStream(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncServerStreamingCall( + getChannel().newCall(getGetStudentStreamMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncClientStreamingCall( + getChannel().newCall(getSendStudentStreamMethod(), getCallOptions()), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendAndGetStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getSendAndGetStudentStreamMethod(), getCallOptions()), responseObserver); + } + } + + /** + */ + public static final class StudentServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub { + private StudentServiceBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @Override + protected StudentServiceBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceBlockingStub(channel, callOptions); + } + + /** + */ + public StudentResponse getStudent(StudentRequest request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetStudentMethod(), getCallOptions(), request); + } + + /** + */ + public java.util.Iterator getStudentStream( + StudentRequest request) { + return io.grpc.stub.ClientCalls.blockingServerStreamingCall( + getChannel(), getGetStudentStreamMethod(), getCallOptions(), request); + } + } + + /** + */ + public static final class StudentServiceFutureStub extends io.grpc.stub.AbstractFutureStub { + private StudentServiceFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @Override + protected StudentServiceFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceFutureStub(channel, callOptions); + } + + /** + */ + public com.google.common.util.concurrent.ListenableFuture getStudent( + StudentRequest request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetStudentMethod(), getCallOptions()), request); + } + } + + private static final int METHODID_GET_STUDENT = 0; + private static final int METHODID_GET_STUDENT_STREAM = 1; + private static final int METHODID_SEND_STUDENT_STREAM = 2; + private static final int METHODID_SEND_AND_GET_STUDENT_STREAM = 3; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final StudentServiceImplBase serviceImpl; + private final int methodId; + + MethodHandlers(StudentServiceImplBase serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @Override + @SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_GET_STUDENT: + serviceImpl.getStudent((StudentRequest) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_STUDENT_STREAM: + serviceImpl.getStudentStream((StudentRequest) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + default: + throw new AssertionError(); + } + } + + @Override + @SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_SEND_STUDENT_STREAM: + return (io.grpc.stub.StreamObserver) serviceImpl.sendStudentStream( + (io.grpc.stub.StreamObserver) responseObserver); + case METHODID_SEND_AND_GET_STUDENT_STREAM: + return (io.grpc.stub.StreamObserver) serviceImpl.sendAndGetStudentStream( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + private static abstract class StudentServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + StudentServiceBaseDescriptorSupplier() {} + + @Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return Student.getDescriptor(); + } + + @Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("StudentService"); + } + } + + private static final class StudentServiceFileDescriptorSupplier + extends StudentServiceBaseDescriptorSupplier { + StudentServiceFileDescriptorSupplier() {} + } + + private static final class StudentServiceMethodDescriptorSupplier + extends StudentServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final String methodName; + + StudentServiceMethodDescriptorSupplier(String methodName) { + this.methodName = methodName; + } + + @Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (StudentServiceGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new StudentServiceFileDescriptorSupplier()) + .addMethod(getGetStudentMethod()) + .addMethod(getGetStudentStreamMethod()) + .addMethod(getSendStudentStreamMethod()) + .addMethod(getSendAndGetStudentStreamMethod()) + .build(); + } + } + } + return result; + } +} 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 66fd7e748..1821732b4 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -4,3 +4,32 @@ Feature: Generating APK conf for gRPC API When I use the definition file "artifacts/definitions/student.proto" in resources And generate the APK conf file for a "gRPC" API Then the response status code should be 200 + + Scenario: Deploying APK conf using a valid gRPC API definition + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc.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 + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And the response body should contain "\"name\":\"string\"" + Then I set headers + | Authorization | bearer ${accessToken} | + And I send "POST" request to "https://default.gw.wso2.com:9095/grpc" + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And the response body should contain "\"name\":\"string\"" + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "grpc-basic-api" + Then the response status code should be 202 + From 80be9cccda584e94931fbf9b3dff3a16e4ca9103 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 25 Apr 2024 14:25:08 +0530 Subject: [PATCH 63/81] add more code for cucumber test --- test/cucumber-tests/build.gradle | 8 +-- .../apk/integration/api/SharedContext.java | 1 - .../utils/JWTClientInterceptor.java | 28 +++++++++++ .../clients/SimpleGRPCStudentClient.java | 50 ++++++++++++++----- .../clients/studentGrpcClient/Student.java | 48 ++++++++++-------- .../studentGrpcClient/StudentRequest.java | 28 +++++------ .../StudentRequestOrBuilder.java | 2 +- .../studentGrpcClient/StudentResponse.java | 28 +++++------ .../StudentResponseOrBuilder.java | 2 +- .../studentGrpcClient/StudentServiceGrpc.java | 16 +++--- 10 files changed, 134 insertions(+), 77 deletions(-) create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java diff --git a/test/cucumber-tests/build.gradle b/test/cucumber-tests/build.gradle index c589336f9..decdfb1ca 100644 --- a/test/cucumber-tests/build.gradle +++ b/test/cucumber-tests/build.gradle @@ -35,9 +35,11 @@ dependencies { testImplementation 'io.cucumber:cucumber-testng:7.13.0' testImplementation 'commons-io:commons-io:2.13.0' testImplementation 'com.nimbusds:nimbus-jose-jwt:9.31' - implementation 'io.grpc:grpc-netty-shaded:1.57.0' - implementation 'io.grpc:grpc-protobuf:1.57.0' - implementation 'io.grpc:grpc-stub:1.57.0' + implementation 'javax.annotation:javax.annotation-api:1.3.2' + implementation 'io.grpc:grpc-netty:1.48.0' + implementation 'io.grpc:grpc-protobuf:1.48.0' + implementation 'io.grpc:grpc-stub:1.48.0' + implementation 'io.grpc:grpc-auth:1.48.0' } test { diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java index 822ff6335..6ba56a673 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -25,7 +25,6 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java new file mode 100644 index 000000000..fe01777ea --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java @@ -0,0 +1,28 @@ +package org.wso2.apk.integration.utils; +import io.grpc.*; + +public class JWTClientInterceptor implements ClientInterceptor { + + private String jwtToken; + + public JWTClientInterceptor(String jwtToken) { + this.jwtToken = jwtToken; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + headers.put( + Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER), + "Bearer " + jwtToken); + super.start(responseListener, headers); + } + }; + } +} + 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 0ab586bbd..c3533fa3a 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 @@ -1,13 +1,19 @@ package org.wso2.apk.integration.utils.clients; +import io.grpc.StatusRuntimeException; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NettyChannelBuilder; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; +import org.wso2.apk.integration.utils.JWTClientInterceptor; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentRequest; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentServiceGrpc; +import javax.net.ssl.SSLException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -16,23 +22,41 @@ public class SimpleGRPCStudentClient { protected Log log = LogFactory.getLog(SimpleGRPCStudentClient.class); private static final int EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS = 10; - private ManagedChannel managedChannel; - public SimpleGRPCStudentClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { - this.managedChannel = ManagedChannelBuilder.forAddress("default.gw.wso2.com", 9095).usePlaintext().build(); - log.info("ManagedChannel created"); + this.GetStudent(); } public StudentResponse GetStudent() { - //Create a blocking stub for the StudentService - StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); - - // Make a synchronous gRPC call to get student details for ID 1 - StudentResponse studentResponse = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + try { + //sleep for 5 seconds + try { + log.info("Sleeping for 5 seconds"); + Thread.sleep(5000); + log.info("Woke up after 5 seconds"); + } catch (InterruptedException e) { + log.error("Thread sleep interrupted"); + } + SslContext sslContext = GrpcSslContexts.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); - // Log the response received from the gRPC server - log.info("response = " + studentResponse.getName() + " " + studentResponse.getAge()); - return studentResponse; + ManagedChannel managedChannel = NettyChannelBuilder.forAddress("default.gw.wso2.com", 9095) + .sslContext(sslContext) + .intercept(new JWTClientInterceptor("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QiLCAia2lkIjoiZ2F0ZXdheV9jZXJ0aWZpY2F0ZV9hbGlhcyJ9.eyJpc3MiOiJodHRwczovL2lkcC5hbS53c28yLmNvbS90b2tlbiIsICJzdWIiOiI0NWYxYzVjOC1hOTJlLTExZWQtYWZhMS0wMjQyYWMxMjAwMDIiLCAiYXVkIjoiYXVkMSIsICJleHAiOjE3MTQwMzc1NDMsICJuYmYiOjE3MTQwMzM5NDMsICJpYXQiOjE3MTQwMzM5NDMsICJqdGkiOiIwMWVmMDJkZS01NjhmLTE0NTgtYTJlMS0wYTk3N2NmMTA5MGMiLCAiY2xpZW50SWQiOiI0NWYxYzVjOC1hOTJlLTExZWQtYWZhMS0wMjQyYWMxMjAwMDIiLCAic2NvcGUiOiJhcGs6YXBpX2NyZWF0ZSJ9.NbvlpbNL0Q3Op7I36nWSMb9R6zCtUeI7las0vOYMNQxMgLrTOBLkXmd9EfSg46fqOD7a9YqoGmKgn5UhXcQSFhtUwAKvbYDvnTyYfT6X3fBqFWl59xt74yJ8f6cSRSBb88Is0qDCWoTVgM-5eTqb93uU8KC0LG1YwU3OoxoiuM1_ix1qbugb-X7gYVvfqttnHl_0e-4jgNN5YLQl8xo8DBs9D-yDWDkDQpj_NonCY1AXqlrynmKbf7kRPR3abHJiF07BQoXJzOXUv4lyHJ1K6DnHj9l2w-KNAWDTQ-kffkVtkQ3hNjbl0Q5ieHsVXLQ9HB1AkOGrho6W8CIO4qlb9A")) // replace "your-jwt-token" with your actual JWT token + .build(); + //Create a blocking stub for the StudentService + StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); + // Make a synchronous gRPC call to get student details for ID 1 + StudentResponse studentResponse = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + // Log the response received from the gRPC server + log.info("response = " + studentResponse.getName() + " " + studentResponse.getAge()); + return studentResponse; + } catch (StatusRuntimeException e) { + log.error("Failed to retrieve student: " + e.getStatus().getDescription()); + throw e; + } catch (SSLException e) { + throw new RuntimeException(e); + } } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java index 30ef19bd2..8ec85fec1 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/Student.java @@ -15,15 +15,15 @@ public static void registerAllExtensions( (com.google.protobuf.ExtensionRegistryLite) registry); } static final com.google.protobuf.Descriptors.Descriptor - internal_static_student_StudentRequest_descriptor; + internal_static_dineth_grpc_v1_student_StudentRequest_descriptor; static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_student_StudentRequest_fieldAccessorTable; + internal_static_dineth_grpc_v1_student_StudentRequest_fieldAccessorTable; static final com.google.protobuf.Descriptors.Descriptor - internal_static_student_StudentResponse_descriptor; + internal_static_dineth_grpc_v1_student_StudentResponse_descriptor; static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_student_StudentResponse_fieldAccessorTable; + internal_static_dineth_grpc_v1_student_StudentResponse_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -33,34 +33,38 @@ public static void registerAllExtensions( descriptor; static { String[] descriptorData = { - "\n\rstudent.proto\022\007student\"\034\n\016StudentReque" + - "st\022\n\n\002id\030\003 \001(\005\",\n\017StudentResponse\022\014\n\004nam" + - "e\030\001 \001(\t\022\013\n\003age\030\002 \001(\0052\276\002\n\016StudentService\022" + - "A\n\nGetStudent\022\027.student.StudentRequest\032\030" + - ".student.StudentResponse\"\000\022I\n\020GetStudent" + - "Stream\022\027.student.StudentRequest\032\030.studen" + - "t.StudentResponse\"\0000\001\022J\n\021SendStudentStre" + - "am\022\027.student.StudentRequest\032\030.student.St" + - "udentResponse\"\000(\001\022R\n\027SendAndGetStudentSt" + - "ream\022\027.student.StudentRequest\032\030.student." + - "StudentResponse\"\000(\0010\001B\017\n\013org.exampleP\001b\006" + - "proto3" + "\n\rstudent.proto\022\026dineth.grpc.v1.student\"" + + "\034\n\016StudentRequest\022\n\n\002id\030\003 \001(\005\",\n\017Student" + + "Response\022\014\n\004name\030\001 \001(\t\022\013\n\003age\030\002 \001(\0052\266\003\n\016" + + "StudentService\022_\n\nGetStudent\022&.dineth.gr" + + "pc.v1.student.StudentRequest\032\'.dineth.gr" + + "pc.v1.student.StudentResponse\"\000\022g\n\020GetSt" + + "udentStream\022&.dineth.grpc.v1.student.Stu" + + "dentRequest\032\'.dineth.grpc.v1.student.Stu" + + "dentResponse\"\0000\001\022h\n\021SendStudentStream\022&." + + "dineth.grpc.v1.student.StudentRequest\032\'." + + "dineth.grpc.v1.student.StudentResponse\"\000" + + "(\001\022p\n\027SendAndGetStudentStream\022&.dineth.g" + + "rpc.v1.student.StudentRequest\032\'.dineth.g" + + "rpc.v1.student.StudentResponse\"\000(\0010\001B<\n8" + + "org.wso2.apk.integration.utils.clients.s" + + "tudentGrpcClientP\001b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] { }); - internal_static_student_StudentRequest_descriptor = + internal_static_dineth_grpc_v1_student_StudentRequest_descriptor = getDescriptor().getMessageTypes().get(0); - internal_static_student_StudentRequest_fieldAccessorTable = new + internal_static_dineth_grpc_v1_student_StudentRequest_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_student_StudentRequest_descriptor, + internal_static_dineth_grpc_v1_student_StudentRequest_descriptor, new String[] { "Id", }); - internal_static_student_StudentResponse_descriptor = + internal_static_dineth_grpc_v1_student_StudentResponse_descriptor = getDescriptor().getMessageTypes().get(1); - internal_static_student_StudentResponse_fieldAccessorTable = new + internal_static_dineth_grpc_v1_student_StudentResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_student_StudentResponse_descriptor, + internal_static_dineth_grpc_v1_student_StudentResponse_descriptor, new String[] { "Name", "Age", }); } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java index a79d2d9d9..c487c6567 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequest.java @@ -4,11 +4,11 @@ package org.wso2.apk.integration.utils.clients.studentGrpcClient; /** - * Protobuf type {@code student.StudentRequest} + * Protobuf type {@code dineth.grpc.v1.student.StudentRequest} */ public final class StudentRequest extends com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:student.StudentRequest) + // @@protoc_insertion_point(message_implements:dineth.grpc.v1.student.StudentRequest) StudentRequestOrBuilder { private static final long serialVersionUID = 0L; // Use StudentRequest.newBuilder() to construct. @@ -74,15 +74,15 @@ private StudentRequest( } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return Student.internal_static_student_StudentRequest_descriptor; + return Student.internal_static_dineth_grpc_v1_student_StudentRequest_descriptor; } @Override protected FieldAccessorTable internalGetFieldAccessorTable() { - return Student.internal_static_student_StudentRequest_fieldAccessorTable + return Student.internal_static_dineth_grpc_v1_student_StudentRequest_fieldAccessorTable .ensureFieldAccessorsInitialized( - StudentRequest.class, StudentRequest.Builder.class); + StudentRequest.class, Builder.class); } public static final int ID_FIELD_NUMBER = 3; @@ -252,26 +252,26 @@ protected Builder newBuilderForType( return builder; } /** - * Protobuf type {@code student.StudentRequest} + * Protobuf type {@code dineth.grpc.v1.student.StudentRequest} */ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:student.StudentRequest) + // @@protoc_insertion_point(builder_implements:dineth.grpc.v1.student.StudentRequest) StudentRequestOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return Student.internal_static_student_StudentRequest_descriptor; + return Student.internal_static_dineth_grpc_v1_student_StudentRequest_descriptor; } @Override protected FieldAccessorTable internalGetFieldAccessorTable() { - return Student.internal_static_student_StudentRequest_fieldAccessorTable + return Student.internal_static_dineth_grpc_v1_student_StudentRequest_fieldAccessorTable .ensureFieldAccessorsInitialized( - StudentRequest.class, StudentRequest.Builder.class); + StudentRequest.class, Builder.class); } - // Construct using StudentRequest.newBuilder() + // Construct using org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentRequest.newBuilder() private Builder() { maybeForceBuilderInitialization(); } @@ -297,7 +297,7 @@ public Builder clear() { @Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return Student.internal_static_student_StudentRequest_descriptor; + return Student.internal_static_dineth_grpc_v1_student_StudentRequest_descriptor; } @Override @@ -441,10 +441,10 @@ public final Builder mergeUnknownFields( } - // @@protoc_insertion_point(builder_scope:student.StudentRequest) + // @@protoc_insertion_point(builder_scope:dineth.grpc.v1.student.StudentRequest) } - // @@protoc_insertion_point(class_scope:student.StudentRequest) + // @@protoc_insertion_point(class_scope:dineth.grpc.v1.student.StudentRequest) private static final StudentRequest DEFAULT_INSTANCE; static { DEFAULT_INSTANCE = new StudentRequest(); diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java index 266630f4c..62c36bc77 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentRequestOrBuilder.java @@ -4,7 +4,7 @@ package org.wso2.apk.integration.utils.clients.studentGrpcClient; public interface StudentRequestOrBuilder extends - // @@protoc_insertion_point(interface_extends:student.StudentRequest) + // @@protoc_insertion_point(interface_extends:dineth.grpc.v1.student.StudentRequest) com.google.protobuf.MessageOrBuilder { /** diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java index 906538a20..33e343eba 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponse.java @@ -4,11 +4,11 @@ package org.wso2.apk.integration.utils.clients.studentGrpcClient; /** - * Protobuf type {@code student.StudentResponse} + * Protobuf type {@code dineth.grpc.v1.student.StudentResponse} */ public final class StudentResponse extends com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:student.StudentResponse) + // @@protoc_insertion_point(message_implements:dineth.grpc.v1.student.StudentResponse) StudentResponseOrBuilder { private static final long serialVersionUID = 0L; // Use StudentResponse.newBuilder() to construct. @@ -81,15 +81,15 @@ private StudentResponse( } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return Student.internal_static_student_StudentResponse_descriptor; + return Student.internal_static_dineth_grpc_v1_student_StudentResponse_descriptor; } @Override protected FieldAccessorTable internalGetFieldAccessorTable() { - return Student.internal_static_student_StudentResponse_fieldAccessorTable + return Student.internal_static_dineth_grpc_v1_student_StudentResponse_fieldAccessorTable .ensureFieldAccessorsInitialized( - StudentResponse.class, StudentResponse.Builder.class); + StudentResponse.class, Builder.class); } public static final int NAME_FIELD_NUMBER = 1; @@ -307,26 +307,26 @@ protected Builder newBuilderForType( return builder; } /** - * Protobuf type {@code student.StudentResponse} + * Protobuf type {@code dineth.grpc.v1.student.StudentResponse} */ public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:student.StudentResponse) + // @@protoc_insertion_point(builder_implements:dineth.grpc.v1.student.StudentResponse) StudentResponseOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return Student.internal_static_student_StudentResponse_descriptor; + return Student.internal_static_dineth_grpc_v1_student_StudentResponse_descriptor; } @Override protected FieldAccessorTable internalGetFieldAccessorTable() { - return Student.internal_static_student_StudentResponse_fieldAccessorTable + return Student.internal_static_dineth_grpc_v1_student_StudentResponse_fieldAccessorTable .ensureFieldAccessorsInitialized( - StudentResponse.class, StudentResponse.Builder.class); + StudentResponse.class, Builder.class); } - // Construct using StudentResponse.newBuilder() + // Construct using org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse.newBuilder() private Builder() { maybeForceBuilderInitialization(); } @@ -354,7 +354,7 @@ public Builder clear() { @Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return Student.internal_static_student_StudentResponse_descriptor; + return Student.internal_static_dineth_grpc_v1_student_StudentResponse_descriptor; } @Override @@ -579,10 +579,10 @@ public final Builder mergeUnknownFields( } - // @@protoc_insertion_point(builder_scope:student.StudentResponse) + // @@protoc_insertion_point(builder_scope:dineth.grpc.v1.student.StudentResponse) } - // @@protoc_insertion_point(class_scope:student.StudentResponse) + // @@protoc_insertion_point(class_scope:dineth.grpc.v1.student.StudentResponse) private static final StudentResponse DEFAULT_INSTANCE; static { DEFAULT_INSTANCE = new StudentResponse(); diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java index 4b16af775..08946635c 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentResponseOrBuilder.java @@ -4,7 +4,7 @@ package org.wso2.apk.integration.utils.clients.studentGrpcClient; public interface StudentResponseOrBuilder extends - // @@protoc_insertion_point(interface_extends:student.StudentResponse) + // @@protoc_insertion_point(interface_extends:dineth.grpc.v1.student.StudentResponse) com.google.protobuf.MessageOrBuilder { /** diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java index 60a066612..b9b158c0f 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceGrpc.java @@ -4,14 +4,14 @@ /** */ -//@javax.annotation.Generated( -// value = "by gRPC proto compiler (version 1.39.0)", -// comments = "Source: student.proto") +@javax.annotation.Generated( + value = "by gRPC proto compiler (version 1.39.0)", + comments = "Source: student.proto") public final class StudentServiceGrpc { private StudentServiceGrpc() {} - public static final String SERVICE_NAME = "student.StudentService"; + public static final String SERVICE_NAME = "dineth.grpc.api.v1.student.StudentService"; // Static method descriptors that strictly reflect the proto. private static volatile io.grpc.MethodDescriptor responseObserver) { + io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetStudentMethod(), responseObserver); } /** */ public void getStudentStream(StudentRequest request, - io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetStudentStreamMethod(), responseObserver); } @@ -265,7 +265,7 @@ protected StudentServiceStub build( /** */ public void getStudent(StudentRequest request, - io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ClientCalls.asyncUnaryCall( getChannel().newCall(getGetStudentMethod(), getCallOptions()), request, responseObserver); } @@ -273,7 +273,7 @@ public void getStudent(StudentRequest request, /** */ public void getStudentStream(StudentRequest request, - io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ClientCalls.asyncServerStreamingCall( getChannel().newCall(getGetStudentStreamMethod(), getCallOptions()), request, responseObserver); } From 781793ce3ec4ed76057c07a23c6af0c4eb895dd7 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 25 Apr 2024 16:11:47 +0530 Subject: [PATCH 64/81] cucumber test for grpc works --- .../wso2/apk/integration/api/BaseSteps.java | 22 ++++++-- .../apk/integration/api/SharedContext.java | 6 --- .../clients/SimpleGRPCStudentClient.java | 50 +++++++++++-------- .../src/test/resources/tests/api/GRPC.feature | 13 ++--- 4 files changed, 51 insertions(+), 40 deletions(-) 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 053a0ab0e..e3c713805 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * Copyright (c) 2024, WSO2 LLC (http://www.wso2.com). * * WSO2 LLC licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -93,6 +93,14 @@ public void systemIsReady() { } + @Then("the student response body should contain name: {string} age: {int}") + public void theStudentResponseBodyShouldContainNameAndAge(String arg0, int arg1) { + int age = sharedContext.getStudentResponse().getAge(); + String name = sharedContext.getStudentResponse().getName(); + Assert.assertEquals(name, arg0); + Assert.assertEquals(age, arg1); + } + @Then("the response body should contain {string}") public void theResponseBodyShouldContain(String expectedText) throws IOException { Assert.assertTrue(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); @@ -140,12 +148,18 @@ public void sendHttpRequest(String httpMethod, String url, String body) throws I } } - @Then("I make grpc request") - public void sendGrpcRequest() throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { - SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(); + @Then("I make grpc request to GetStudent to {string} with port {int}") + public void GetStudent(String arg0, int arg1) { + SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0,arg1); sharedContext.setStudentResponse(grpcStudentClient.GetStudent()); } + @Then("I make grpc request GetStudent with token to {string} with port {int}") + public void GetStudentWithToken(String arg0, int arg1 ) { + SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0,arg1); + sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getAccessToken())); + } + // It will send request using a new thread and forget about the response @Then("I send {string} async request to {string} with body {string}") public void sendAsyncHttpRequest(String httpMethod, String url, String body) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java index 6ba56a673..cca88b764 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -46,12 +46,6 @@ public SimpleHTTPClient getHttpClient() throws NoSuchAlgorithmException, KeyStor } return httpClient; } - public SimpleGRPCStudentClient getGrpcStudentClient()throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { - if (grpcStudentClient == null) { - grpcStudentClient = new SimpleGRPCStudentClient(); - } - return grpcStudentClient; - } public String getAccessToken() { 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 c3533fa3a..b028a70dc 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 @@ -14,43 +14,51 @@ import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentServiceGrpc; import javax.net.ssl.SSLException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; + public class SimpleGRPCStudentClient { protected Log log = LogFactory.getLog(SimpleGRPCStudentClient.class); private static final int EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS = 10; + private final String host; + private final int port; - public SimpleGRPCStudentClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { - this.GetStudent(); + public SimpleGRPCStudentClient(String host, int port) { + this.host = host; + this.port = port; } public StudentResponse GetStudent() { try { - //sleep for 5 seconds - try { - log.info("Sleeping for 5 seconds"); - Thread.sleep(5000); - log.info("Woke up after 5 seconds"); - } catch (InterruptedException e) { - log.error("Thread sleep interrupted"); - } SslContext sslContext = GrpcSslContexts.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .build(); - ManagedChannel managedChannel = NettyChannelBuilder.forAddress("default.gw.wso2.com", 9095) + ManagedChannel managedChannel = NettyChannelBuilder.forAddress(host, port) + .sslContext(sslContext) + .build(); + StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); + + return blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + } catch (StatusRuntimeException e) { + log.error("Failed to retrieve student: " + e.getStatus().getDescription()); + throw e; + } catch (SSLException e) { + throw new RuntimeException(e); + } + } + public StudentResponse GetStudent(String token) { + try { + SslContext sslContext = GrpcSslContexts.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + + ManagedChannel managedChannel = NettyChannelBuilder.forAddress(host, port) .sslContext(sslContext) - .intercept(new JWTClientInterceptor("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QiLCAia2lkIjoiZ2F0ZXdheV9jZXJ0aWZpY2F0ZV9hbGlhcyJ9.eyJpc3MiOiJodHRwczovL2lkcC5hbS53c28yLmNvbS90b2tlbiIsICJzdWIiOiI0NWYxYzVjOC1hOTJlLTExZWQtYWZhMS0wMjQyYWMxMjAwMDIiLCAiYXVkIjoiYXVkMSIsICJleHAiOjE3MTQwMzc1NDMsICJuYmYiOjE3MTQwMzM5NDMsICJpYXQiOjE3MTQwMzM5NDMsICJqdGkiOiIwMWVmMDJkZS01NjhmLTE0NTgtYTJlMS0wYTk3N2NmMTA5MGMiLCAiY2xpZW50SWQiOiI0NWYxYzVjOC1hOTJlLTExZWQtYWZhMS0wMjQyYWMxMjAwMDIiLCAic2NvcGUiOiJhcGs6YXBpX2NyZWF0ZSJ9.NbvlpbNL0Q3Op7I36nWSMb9R6zCtUeI7las0vOYMNQxMgLrTOBLkXmd9EfSg46fqOD7a9YqoGmKgn5UhXcQSFhtUwAKvbYDvnTyYfT6X3fBqFWl59xt74yJ8f6cSRSBb88Is0qDCWoTVgM-5eTqb93uU8KC0LG1YwU3OoxoiuM1_ix1qbugb-X7gYVvfqttnHl_0e-4jgNN5YLQl8xo8DBs9D-yDWDkDQpj_NonCY1AXqlrynmKbf7kRPR3abHJiF07BQoXJzOXUv4lyHJ1K6DnHj9l2w-KNAWDTQ-kffkVtkQ3hNjbl0Q5ieHsVXLQ9HB1AkOGrho6W8CIO4qlb9A")) // replace "your-jwt-token" with your actual JWT token + .intercept(new JWTClientInterceptor(token)) // replace "your-jwt-token" with your actual JWT token .build(); - //Create a blocking stub for the StudentService StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); - // Make a synchronous gRPC call to get student details for ID 1 - StudentResponse studentResponse = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); - // Log the response received from the gRPC server - log.info("response = " + studentResponse.getName() + " " + studentResponse.getAge()); - return studentResponse; + + return blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); } catch (StatusRuntimeException e) { log.error("Failed to retrieve student: " + e.getStatus().getDescription()); throw e; 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 1821732b4..9c69c0b5d 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -14,18 +14,13 @@ Feature: Generating APK conf for gRPC API Then the response status code should be 200 Then I set headers | Authorization | bearer ${accessToken} | - And I make grpc request + And I make grpc request GetStudent with token to "default.gw.wso2.com" with port 9095 And I eventually receive 200 response code, not accepting | 429 | | 500 | - And the response body should contain "\"name\":\"string\"" - Then I set headers - | Authorization | bearer ${accessToken} | - And I send "POST" request to "https://default.gw.wso2.com:9095/grpc" - And I eventually receive 200 response code, not accepting - | 429 | - | 500 | - And the response body should contain "\"name\":\"string\"" + And the student response body should contain name: "Dineth" age: 10 + + Scenario: Undeploy API Given The system is ready From 55cf2ad22fe973d9dc794cba4e85aa082f0feb96 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 26 Apr 2024 09:45:59 +0530 Subject: [PATCH 65/81] generalized interceptor --- .../wso2/apk/integration/api/BaseSteps.java | 12 ++---- .../utils/GenericClientInterceptor.java | 39 +++++++++++++++++++ .../utils/JWTClientInterceptor.java | 28 ------------- .../clients/SimpleGRPCStudentClient.java | 28 +++---------- .../src/test/resources/tests/api/GRPC.feature | 4 +- 5 files changed, 48 insertions(+), 63 deletions(-) create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/GenericClientInterceptor.java delete mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java 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 e3c713805..e747a56f3 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 @@ -148,16 +148,10 @@ public void sendHttpRequest(String httpMethod, String url, String body) throws I } } - @Then("I make grpc request to GetStudent to {string} with port {int}") - public void GetStudent(String arg0, int arg1) { + @Then("I make grpc request GetStudent to {string} with port {int}") + public void GetStudent(String arg0, int arg1 ) { SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0,arg1); - sharedContext.setStudentResponse(grpcStudentClient.GetStudent()); - } - - @Then("I make grpc request GetStudent with token to {string} with port {int}") - public void GetStudentWithToken(String arg0, int arg1 ) { - SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0,arg1); - sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getAccessToken())); + sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getHeaders())); } // It will send request using a new thread and forget about the response 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 new file mode 100644 index 000000000..17ab5702e --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/GenericClientInterceptor.java @@ -0,0 +1,39 @@ +package org.wso2.apk.integration.utils; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.CallOptions; +import io.grpc.ClientCall; +import io.grpc.Channel; +import java.util.Map; + + +import java.util.Map; + +public class GenericClientInterceptor implements ClientInterceptor { + + private Map headers; + + public GenericClientInterceptor(Map headers) { + this.headers = headers; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headersMetadata) { + // Set each header in the map to the Metadata headers + headers.forEach((key, value) -> headersMetadata.put( + Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value)); + + super.start(responseListener, headersMetadata); + } + }; + } +} + diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java deleted file mode 100644 index fe01777ea..000000000 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/JWTClientInterceptor.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.wso2.apk.integration.utils; -import io.grpc.*; - -public class JWTClientInterceptor implements ClientInterceptor { - - private String jwtToken; - - public JWTClientInterceptor(String jwtToken) { - this.jwtToken = jwtToken; - } - - @Override - public ClientCall interceptCall( - MethodDescriptor method, CallOptions callOptions, Channel next) { - return new ForwardingClientCall.SimpleForwardingClientCall( - next.newCall(method, callOptions)) { - - @Override - public void start(Listener responseListener, Metadata headers) { - headers.put( - Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER), - "Bearer " + jwtToken); - super.start(responseListener, headers); - } - }; - } -} - 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 b028a70dc..19c0111df 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 @@ -8,12 +8,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import io.grpc.ManagedChannel; -import org.wso2.apk.integration.utils.JWTClientInterceptor; +import org.wso2.apk.integration.utils.GenericClientInterceptor; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentRequest; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentServiceGrpc; import javax.net.ssl.SSLException; +import java.util.Map; public class SimpleGRPCStudentClient { @@ -27,34 +28,15 @@ public SimpleGRPCStudentClient(String host, int port) { this.port = port; } - public StudentResponse GetStudent() { + public StudentResponse GetStudent(Map headers) { try { SslContext sslContext = GrpcSslContexts.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .build(); - - ManagedChannel managedChannel = NettyChannelBuilder.forAddress(host, port) - .sslContext(sslContext) - .build(); - StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); - - return blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); - } catch (StatusRuntimeException e) { - log.error("Failed to retrieve student: " + e.getStatus().getDescription()); - throw e; - } catch (SSLException e) { - throw new RuntimeException(e); - } - } - public StudentResponse GetStudent(String token) { - try { - SslContext sslContext = GrpcSslContexts.forClient() - .trustManager(InsecureTrustManagerFactory.INSTANCE) - .build(); - + GenericClientInterceptor interceptor = new GenericClientInterceptor(headers); ManagedChannel managedChannel = NettyChannelBuilder.forAddress(host, port) .sslContext(sslContext) - .intercept(new JWTClientInterceptor(token)) // replace "your-jwt-token" with your actual JWT token + .intercept(interceptor) .build(); StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); 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 9c69c0b5d..af54d3123 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -14,14 +14,12 @@ Feature: Generating APK conf for gRPC API Then the response status code should be 200 Then I set headers | Authorization | bearer ${accessToken} | - And I make grpc request GetStudent with token to "default.gw.wso2.com" with port 9095 + And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 And I eventually receive 200 response code, not accepting | 429 | | 500 | And the student response body should contain name: "Dineth" age: 10 - - Scenario: Undeploy API Given The system is ready And I have a valid subscription From 2667f325950e581008002432b15df03cb0b83de0 Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 26 Apr 2024 09:53:32 +0530 Subject: [PATCH 66/81] add cucumber test for oauth2 disabled --- .../grpc/grpc_with_disabled_auth.apk-conf | 32 +++++++++++++++++++ .../src/test/resources/tests/api/GRPC.feature | 18 +++++++++++ 2 files changed, 50 insertions(+) create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_disabled_auth.apk-conf diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_disabled_auth.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_disabled_auth.apk-conf new file mode 100644 index 000000000..50bdc465b --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_disabled_auth.apk-conf @@ -0,0 +1,32 @@ +--- +name: "demo-grpc-api" +basePath: "/dineth.grpc.api" +version: "v1" +type: "GRPC" +id: "grpc-auth-disabled-api" +endpointConfigurations: + production: + endpoint: "http://grpc-backend:6565" +defaultVersion: false +subscriptionValidation: false +operations: +- target: "student.StudentService" + verb: "GetStudent" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "GetStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendAndGetStudentStream" + secured: true + scopes: [] +authentication: + - authType: OAuth2 + required: mandatory + enabled: false 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 af54d3123..13a19a501 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -26,3 +26,21 @@ Feature: Generating APK conf for gRPC API When I undeploy the API whose ID is "grpc-basic-api" Then the response status code should be 202 + Scenario: Deploying gRPC API with OAuth2 disabled + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc_with_disabled_auth.apk-conf" + And the definition file "artifacts/definitions/student.proto" + And make the API deployment request + Then the response status code should be 200 + And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And the student response body should contain name: "Dineth" age: 10 + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "grpc-auth-disabled-api" + Then the response status code should be 202 From 3182a1303fe01eb12f0e8283601d5f222acf055f Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 26 Apr 2024 11:02:51 +0530 Subject: [PATCH 67/81] add cucumber test for scopes --- .../wso2/apk/integration/api/BaseSteps.java | 21 ++++++++++++-- .../apk/integration/api/SharedContext.java | 8 +++++- .../clients/SimpleGRPCStudentClient.java | 28 +++++++++++++------ .../apk-confs/grpc/grpc_scopes.apk-conf | 28 +++++++++++++++++++ .../src/test/resources/tests/api/GRPC.feature | 27 ++++++++++++++++++ 5 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_scopes.apk-conf 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 e747a56f3..ba54e504e 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 @@ -39,6 +39,8 @@ import io.cucumber.java.Before; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -125,6 +127,13 @@ public void theResponseStatusCodeShouldBe(int expectedStatusCode) throws IOExcep Assert.assertEquals(actualStatusCode, expectedStatusCode); } + @Then("the grpc error response status code should be {int}") + public void theGrpcErrorResponseStatusCodeShouldBe(int expectedStatusCode) throws IOException { + + int actualStatusCode = sharedContext.getGrpcErrorCode(); + Assert.assertEquals(actualStatusCode, expectedStatusCode); + } + @Then("I send {string} request to {string} with body {string}") public void sendHttpRequest(String httpMethod, String url, String body) throws IOException { body = Utils.resolveVariables(body, sharedContext.getValueStore()); @@ -149,9 +158,15 @@ public void sendHttpRequest(String httpMethod, String url, String body) throws I } @Then("I make grpc request GetStudent to {string} with port {int}") - public void GetStudent(String arg0, int arg1 ) { - SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0,arg1); - sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getHeaders())); + public void GetStudent(String arg0, int arg1) throws StatusRuntimeException { + try { + SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0, arg1); + sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getHeaders())); + } catch (StatusRuntimeException e) { + if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ + sharedContext.setGrpcErrorCode(403); + } + } } // It will send request using a new thread and forget about the response diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java index cca88b764..2b7c88bb0 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -32,11 +32,11 @@ public class SharedContext { private SimpleHTTPClient httpClient; - private SimpleGRPCStudentClient grpcStudentClient; private StudentResponse studentResponse; private String accessToken; private HttpResponse response; private String responseBody; + private int grpcErrorCode; private HashMap valueStore = new HashMap<>(); private HashMap headers = new HashMap<>(); @@ -56,6 +56,12 @@ public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + public int getGrpcErrorCode() { + return grpcErrorCode; + } + public void setGrpcErrorCode(int grpcErrorCode) { + this.grpcErrorCode = grpcErrorCode; + } public HttpResponse getResponse() { 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 19c0111df..c6a133183 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 @@ -15,6 +15,7 @@ import javax.net.ssl.SSLException; import java.util.Map; +import java.util.concurrent.TimeUnit; public class SimpleGRPCStudentClient { @@ -28,24 +29,35 @@ public SimpleGRPCStudentClient(String host, int port) { this.port = port; } - public StudentResponse GetStudent(Map headers) { + public StudentResponse GetStudent(Map headers) throws StatusRuntimeException{ + ManagedChannel managedChannel = null; try { SslContext sslContext = GrpcSslContexts.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .build(); + GenericClientInterceptor interceptor = new GenericClientInterceptor(headers); - ManagedChannel managedChannel = NettyChannelBuilder.forAddress(host, port) + managedChannel = NettyChannelBuilder.forAddress(host, port) .sslContext(sslContext) .intercept(interceptor) .build(); StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); - return blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); - } catch (StatusRuntimeException e) { - log.error("Failed to retrieve student: " + e.getStatus().getDescription()); - throw e; - } catch (SSLException e) { - throw new RuntimeException(e); + }catch (SSLException e) { + throw new RuntimeException("Failed to create SSL context", e); + } finally { + // Shut down the channel to release resources + if (managedChannel != null) { + managedChannel.shutdown(); // Initiates a graceful shutdown + try { + // Wait at most 5 seconds for the channel to terminate + if (!managedChannel.awaitTermination(5, TimeUnit.SECONDS)) { + managedChannel.shutdownNow(); // Force shutdown if it does not complete within the timeout + } + } catch (InterruptedException ie) { + managedChannel.shutdownNow(); // Force shutdown if the thread is interrupted + } + } } } diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_scopes.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_scopes.apk-conf new file mode 100644 index 000000000..c4edb6983 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_scopes.apk-conf @@ -0,0 +1,28 @@ +--- +name: "demo-grpc-api" +basePath: "/dineth.grpc.api" +version: "v1" +type: "GRPC" +id: "grpc-scopes" +endpointConfigurations: + production: + endpoint: "http://grpc-backend:6565" +defaultVersion: false +subscriptionValidation: false +operations: +- target: "student.StudentService" + verb: "GetStudent" + secured: true + scopes: ["wso2"] +- target: "student.StudentService" + verb: "GetStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendAndGetStudentStream" + secured: true + scopes: [] \ No newline at end of file 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 13a19a501..a851159c3 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -44,3 +44,30 @@ Feature: Generating APK conf for gRPC API And I have a valid subscription When I undeploy the API whose ID is "grpc-auth-disabled-api" Then the response status code should be 202 + + Scenario: Deploying gRPC API with scopes + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc_scopes.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 error response status code should be 403 + Given I have a valid subscription with scopes + | wso2 | + Then I set headers + | Authorization | bearer ${accessToken} | + And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And the student response body should contain name: "Dineth" age: 10 + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "grpc-scopes" + Then the response status code should be 202 \ No newline at end of file From efc2d79dad7e4992965c9c1a9b8c92e26142123e Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 26 Apr 2024 11:26:54 +0530 Subject: [PATCH 68/81] add cucumber test for default version --- .../wso2/apk/integration/api/BaseSteps.java | 12 + .../clients/SimpleGRPCStudentClient.java | 31 ++ .../StudentServiceDefaultVersionGrpc.java | 458 ++++++++++++++++++ .../grpc/grpc_with_default_version.apk-conf | 28 ++ .../src/test/resources/tests/api/GRPC.feature | 29 ++ 5 files changed, 558 insertions(+) create mode 100644 test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceDefaultVersionGrpc.java create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_default_version.apk-conf 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 ba54e504e..6321d4ee7 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 @@ -169,6 +169,18 @@ public void GetStudent(String arg0, int arg1) throws StatusRuntimeException { } } + @Then("I make grpc request GetStudent default version to {string} with port {int}") + public void GetStudentDefaultVersion(String arg0, int arg1) throws StatusRuntimeException { + try { + SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0, arg1); + sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getHeaders())); + } catch (StatusRuntimeException e) { + if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ + sharedContext.setGrpcErrorCode(403); + } + } + } + // It will send request using a new thread and forget about the response @Then("I send {string} async request to {string} with body {string}") public void sendAsyncHttpRequest(String httpMethod, String url, String body) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { 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 c6a133183..d8fcc7e7c 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 @@ -11,6 +11,7 @@ import org.wso2.apk.integration.utils.GenericClientInterceptor; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentRequest; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse; +import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentServiceDefaultVersionGrpc; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentServiceGrpc; import javax.net.ssl.SSLException; @@ -60,7 +61,37 @@ public StudentResponse GetStudent(Map headers) throws StatusRunt } } } + public StudentResponse GetStudentDefaultVersion(Map headers) throws StatusRuntimeException{ + ManagedChannel managedChannel = null; + try { + SslContext sslContext = GrpcSslContexts.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + GenericClientInterceptor interceptor = new GenericClientInterceptor(headers); + managedChannel = NettyChannelBuilder.forAddress(host, port) + .sslContext(sslContext) + .intercept(interceptor) + .build(); + StudentServiceDefaultVersionGrpc.StudentServiceBlockingStub blockingStub = StudentServiceDefaultVersionGrpc.newBlockingStub(managedChannel); + return blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + }catch (SSLException e) { + throw new RuntimeException("Failed to create SSL context", e); + } finally { + // Shut down the channel to release resources + if (managedChannel != null) { + managedChannel.shutdown(); // Initiates a graceful shutdown + try { + // Wait at most 5 seconds for the channel to terminate + if (!managedChannel.awaitTermination(5, TimeUnit.SECONDS)) { + managedChannel.shutdownNow(); // Force shutdown if it does not complete within the timeout + } + } catch (InterruptedException ie) { + managedChannel.shutdownNow(); // Force shutdown if the thread is interrupted + } + } + } + } } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceDefaultVersionGrpc.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceDefaultVersionGrpc.java new file mode 100644 index 000000000..9a2878343 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/studentGrpcClient/StudentServiceDefaultVersionGrpc.java @@ -0,0 +1,458 @@ +package org.wso2.apk.integration.utils.clients.studentGrpcClient; + +import static io.grpc.MethodDescriptor.generateFullMethodName; + +/** + */ +@javax.annotation.Generated( + value = "by gRPC proto compiler (version 1.39.0)", + comments = "Source: student.proto") +public final class StudentServiceDefaultVersionGrpc { + + private StudentServiceDefaultVersionGrpc() {} + + public static final String SERVICE_NAME = "dineth.grpc.api.student.StudentService"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getGetStudentMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetStudent", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetStudentMethod() { + io.grpc.MethodDescriptor getGetStudentMethod; + if ((getGetStudentMethod = StudentServiceDefaultVersionGrpc.getGetStudentMethod) == null) { + synchronized (StudentServiceDefaultVersionGrpc.class) { + if ((getGetStudentMethod = StudentServiceDefaultVersionGrpc.getGetStudentMethod) == null) { + StudentServiceDefaultVersionGrpc.getGetStudentMethod = getGetStudentMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetStudent")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("GetStudent")) + .build(); + } + } + } + return getGetStudentMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetStudentStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetStudentStream", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + public static io.grpc.MethodDescriptor getGetStudentStreamMethod() { + io.grpc.MethodDescriptor getGetStudentStreamMethod; + if ((getGetStudentStreamMethod = StudentServiceDefaultVersionGrpc.getGetStudentStreamMethod) == null) { + synchronized (StudentServiceDefaultVersionGrpc.class) { + if ((getGetStudentStreamMethod = StudentServiceDefaultVersionGrpc.getGetStudentStreamMethod) == null) { + StudentServiceDefaultVersionGrpc.getGetStudentStreamMethod = getGetStudentStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetStudentStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("GetStudentStream")) + .build(); + } + } + } + return getGetStudentStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getSendStudentStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "SendStudentStream", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + public static io.grpc.MethodDescriptor getSendStudentStreamMethod() { + io.grpc.MethodDescriptor getSendStudentStreamMethod; + if ((getSendStudentStreamMethod = StudentServiceDefaultVersionGrpc.getSendStudentStreamMethod) == null) { + synchronized (StudentServiceDefaultVersionGrpc.class) { + if ((getSendStudentStreamMethod = StudentServiceDefaultVersionGrpc.getSendStudentStreamMethod) == null) { + StudentServiceDefaultVersionGrpc.getSendStudentStreamMethod = getSendStudentStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SendStudentStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("SendStudentStream")) + .build(); + } + } + } + return getSendStudentStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getSendAndGetStudentStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "SendAndGetStudentStream", + requestType = StudentRequest.class, + responseType = StudentResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getSendAndGetStudentStreamMethod() { + io.grpc.MethodDescriptor getSendAndGetStudentStreamMethod; + if ((getSendAndGetStudentStreamMethod = StudentServiceDefaultVersionGrpc.getSendAndGetStudentStreamMethod) == null) { + synchronized (StudentServiceDefaultVersionGrpc.class) { + if ((getSendAndGetStudentStreamMethod = StudentServiceDefaultVersionGrpc.getSendAndGetStudentStreamMethod) == null) { + StudentServiceDefaultVersionGrpc.getSendAndGetStudentStreamMethod = getSendAndGetStudentStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SendAndGetStudentStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + StudentResponse.getDefaultInstance())) + .setSchemaDescriptor(new StudentServiceMethodDescriptorSupplier("SendAndGetStudentStream")) + .build(); + } + } + } + return getSendAndGetStudentStreamMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static StudentServiceStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @Override + public StudentServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceStub(channel, callOptions); + } + }; + return StudentServiceStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static StudentServiceBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @Override + public StudentServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceBlockingStub(channel, callOptions); + } + }; + return StudentServiceBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static StudentServiceFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @Override + public StudentServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceFutureStub(channel, callOptions); + } + }; + return StudentServiceFutureStub.newStub(factory, channel); + } + + /** + */ + public static abstract class StudentServiceImplBase implements io.grpc.BindableService { + + /** + */ + public void getStudent(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetStudentMethod(), responseObserver); + } + + /** + */ + public void getStudentStream(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetStudentStreamMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getSendStudentStreamMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendAndGetStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getSendAndGetStudentStreamMethod(), responseObserver); + } + + @Override public final io.grpc.ServerServiceDefinition bindService() { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getGetStudentMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_GET_STUDENT))) + .addMethod( + getGetStudentStreamMethod(), + io.grpc.stub.ServerCalls.asyncServerStreamingCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_GET_STUDENT_STREAM))) + .addMethod( + getSendStudentStreamMethod(), + io.grpc.stub.ServerCalls.asyncClientStreamingCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_SEND_STUDENT_STREAM))) + .addMethod( + getSendAndGetStudentStreamMethod(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + StudentRequest, + StudentResponse>( + this, METHODID_SEND_AND_GET_STUDENT_STREAM))) + .build(); + } + } + + /** + */ + public static final class StudentServiceStub extends io.grpc.stub.AbstractAsyncStub { + private StudentServiceStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @Override + protected StudentServiceStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceStub(channel, callOptions); + } + + /** + */ + public void getStudent(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetStudentMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public void getStudentStream(StudentRequest request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncServerStreamingCall( + getChannel().newCall(getGetStudentStreamMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncClientStreamingCall( + getChannel().newCall(getSendStudentStreamMethod(), getCallOptions()), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver sendAndGetStudentStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getSendAndGetStudentStreamMethod(), getCallOptions()), responseObserver); + } + } + + /** + */ + public static final class StudentServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub { + private StudentServiceBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @Override + protected StudentServiceBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceBlockingStub(channel, callOptions); + } + + /** + */ + public StudentResponse getStudent(StudentRequest request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetStudentMethod(), getCallOptions(), request); + } + + /** + */ + public java.util.Iterator getStudentStream( + StudentRequest request) { + return io.grpc.stub.ClientCalls.blockingServerStreamingCall( + getChannel(), getGetStudentStreamMethod(), getCallOptions(), request); + } + } + + /** + */ + public static final class StudentServiceFutureStub extends io.grpc.stub.AbstractFutureStub { + private StudentServiceFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @Override + protected StudentServiceFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new StudentServiceFutureStub(channel, callOptions); + } + + /** + */ + public com.google.common.util.concurrent.ListenableFuture getStudent( + StudentRequest request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetStudentMethod(), getCallOptions()), request); + } + } + + private static final int METHODID_GET_STUDENT = 0; + private static final int METHODID_GET_STUDENT_STREAM = 1; + private static final int METHODID_SEND_STUDENT_STREAM = 2; + private static final int METHODID_SEND_AND_GET_STUDENT_STREAM = 3; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final StudentServiceImplBase serviceImpl; + private final int methodId; + + MethodHandlers(StudentServiceImplBase serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @Override + @SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_GET_STUDENT: + serviceImpl.getStudent((StudentRequest) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_STUDENT_STREAM: + serviceImpl.getStudentStream((StudentRequest) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + default: + throw new AssertionError(); + } + } + + @Override + @SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_SEND_STUDENT_STREAM: + return (io.grpc.stub.StreamObserver) serviceImpl.sendStudentStream( + (io.grpc.stub.StreamObserver) responseObserver); + case METHODID_SEND_AND_GET_STUDENT_STREAM: + return (io.grpc.stub.StreamObserver) serviceImpl.sendAndGetStudentStream( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + private static abstract class StudentServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + StudentServiceBaseDescriptorSupplier() {} + + @Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return Student.getDescriptor(); + } + + @Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("StudentService"); + } + } + + private static final class StudentServiceFileDescriptorSupplier + extends StudentServiceBaseDescriptorSupplier { + StudentServiceFileDescriptorSupplier() {} + } + + private static final class StudentServiceMethodDescriptorSupplier + extends StudentServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final String methodName; + + StudentServiceMethodDescriptorSupplier(String methodName) { + this.methodName = methodName; + } + + @Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (StudentServiceDefaultVersionGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new StudentServiceFileDescriptorSupplier()) + .addMethod(getGetStudentMethod()) + .addMethod(getGetStudentStreamMethod()) + .addMethod(getSendStudentStreamMethod()) + .addMethod(getSendAndGetStudentStreamMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_default_version.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_default_version.apk-conf new file mode 100644 index 000000000..2c2f88441 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_default_version.apk-conf @@ -0,0 +1,28 @@ +--- +name: "demo-grpc-api" +basePath: "/dineth.grpc.api" +version: "v1" +type: "GRPC" +id: "grpc-default-version-api" +endpointConfigurations: + production: + endpoint: "http://grpc-backend:6565" +defaultVersion: true +subscriptionValidation: false +operations: +- target: "student.StudentService" + verb: "GetStudent" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "GetStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendAndGetStudentStream" + secured: true + scopes: [] \ No newline at end of file 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 a851159c3..c6b7ebcd8 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -70,4 +70,33 @@ 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-scopes" + Then the response status code should be 202 + + Scenario: Deploying gRPC API with default version enabled + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc_with_default_version.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 I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And the student response body should contain name: "Dineth" age: 10 + Given I have a valid subscription + Then I set headers + | Authorization | bearer ${accessToken} | + And I make grpc request GetStudent default version to "default.gw.wso2.com" with port 9095 + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And the student response body should contain name: "Dineth" age: 10 + + Scenario: Undeploy 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 \ No newline at end of file From 9a5f436b11968bef4b42c28deef7a92ac5cc825e Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 26 Apr 2024 14:19:46 +0530 Subject: [PATCH 69/81] add missing bracket to fix issue in commit bdc279f566d230cce3c195b5adeee76666caff1a --- runtime/config-deployer-service/ballerina/APIClient.bal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 02e78c1e6..5c86fc3bc 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -572,6 +572,7 @@ public class APIClient { } else { apiArtifact.sandboxGrpcRoutes.push(grpcRoute); } + } } else { model:HTTPRoute httpRoute = { metadata: @@ -593,11 +594,10 @@ public class APIClient { } } } - return; - } } + private isolated function generateAndRetrieveParentRefs(APKConf apkConf, string uniqueId) returns model:ParentReference[] { string gatewayName = gatewayConfiguration.name; string listenerName = gatewayConfiguration.listenerName; From 1d8ed4dd8f0093fc695f56b485291f9ca5d5547c Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 26 Apr 2024 16:29:12 +0530 Subject: [PATCH 70/81] resolved comments --- runtime/config-deployer-service/ballerina/K8sClient.bal | 2 +- .../java/org/wso2/apk/config/DefinitionParserFactory.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/runtime/config-deployer-service/ballerina/K8sClient.bal b/runtime/config-deployer-service/ballerina/K8sClient.bal index cdc03e00b..45723229a 100644 --- a/runtime/config-deployer-service/ballerina/K8sClient.bal +++ b/runtime/config-deployer-service/ballerina/K8sClient.bal @@ -265,7 +265,7 @@ public isolated function getGqlRoutesForAPIs(string apiName, string apiVersion, return k8sApiServerEp->get(endpoint, targetType = model:GQLRouteList); } public isolated function getGrpcRoutesForAPIs(string apiName, string apiVersion, string namespace, string organization) returns model:GRPCRouteList|http:ClientError|error { - string endpoint = "/apis/dp.wso2.com/v1alpha2/namespaces/" + namespace + "/grpcroutes/?labelSelector=" + check generateUrlEncodedLabelSelector(apiName, apiVersion, organization); + string endpoint = "/apis/gateway.networking.k8s.io/v1alpha2/namespaces/" + namespace + "/grpcroutes/?labelSelector=" + check generateUrlEncodedLabelSelector(apiName, apiVersion, organization); return k8sApiServerEp->get(endpoint, targetType = model:GRPCRouteList); } isolated function deployRateLimitPolicyCR(model:RateLimitPolicy rateLimitPolicy, string namespace) returns http:Response|http:ClientError { diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java index 167b2d138..585e1010e 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/DefinitionParserFactory.java @@ -21,12 +21,11 @@ private DefinitionParserFactory() { public static APIDefinition getParser(API api) { if (APIConstants.ParserType.REST.name().equals(api.getType()) - || APIConstants.ParserType.GRAPHQL.name().equals(api.getType())) { + || APIConstants.ParserType.GRAPHQL.name().equals(api.getType()) + || APIConstants.ParserType.GRPC.name().equals(api.getType())) { return new OAS3Parser(); } else if (APIConstants.ParserType.ASYNC.name().equals(api.getType())) { return new AsyncApiParser(); - } else if (APIConstants.ParserType.GRPC.name().equals(api.getType())) { - return new OAS3Parser(); } return null; } From 3c10d001e9545d3c025960fbc3778cf6ef5c77fc Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 29 Apr 2024 10:24:46 +0530 Subject: [PATCH 71/81] changed grpc deployment and service CR --- test/cucumber-tests/CRs/artifacts.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/cucumber-tests/CRs/artifacts.yaml b/test/cucumber-tests/CRs/artifacts.yaml index c49bb4680..0ee441c99 100644 --- a/test/cucumber-tests/CRs/artifacts.yaml +++ b/test/cucumber-tests/CRs/artifacts.yaml @@ -1158,11 +1158,11 @@ spec: apiVersion: v1 kind: Service metadata: - name: grpc-backend-v1 + name: grpc-backend namespace: apk-integration-test spec: selector: - app: grpc-backend-v1 + app: grpc-backend ports: - protocol: TCP port: 6565 @@ -1171,22 +1171,22 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: grpc-backend-v1 + name: grpc-backend namespace: apk-integration-test labels: - app: grpc-backend-v1 + app: grpc-backend spec: replicas: 1 selector: matchLabels: - app: grpc-backend-v1 + app: grpc-backend template: metadata: labels: - app: grpc-backend-v1 + app: grpc-backend spec: containers: - - name: grpc-backend-v1 + - name: grpc-backend image: ddh13/dineth-grpc-demo-server:1.0.0 imagePullPolicy: Always env: From 064a67827e7d89723e1a9446776725cfc3fa9f20 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 29 Apr 2024 12:02:32 +0530 Subject: [PATCH 72/81] Add handling for errors --- .../wso2/apk/integration/api/BaseSteps.java | 13 +++++++++++-- .../utils/clients/SimpleGRPCStudentClient.java | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) 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 6321d4ee7..cba1b1530 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 @@ -53,6 +53,7 @@ import org.wso2.apk.integration.utils.Utils; import org.wso2.apk.integration.utils.clients.SimpleGRPCStudentClient; import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; +import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse; import java.io.IOException; import java.io.InputStream; @@ -97,8 +98,12 @@ public void systemIsReady() { @Then("the student response body should contain name: {string} age: {int}") public void theStudentResponseBodyShouldContainNameAndAge(String arg0, int arg1) { - int age = sharedContext.getStudentResponse().getAge(); - String name = sharedContext.getStudentResponse().getName(); + StudentResponse studentResponse = sharedContext.getStudentResponse(); + if (studentResponse == null) { + Assert.fail("Student response is null."); + } + int age = studentResponse.getAge(); + String name = studentResponse.getName(); Assert.assertEquals(name, arg0); Assert.assertEquals(age, arg1); } @@ -165,6 +170,8 @@ public void GetStudent(String arg0, int arg1) throws StatusRuntimeException { } catch (StatusRuntimeException e) { if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ sharedContext.setGrpcErrorCode(403); + } else { + logger.error(e.getMessage()); } } } @@ -177,6 +184,8 @@ public void GetStudentDefaultVersion(String arg0, int arg1) throws StatusRuntime } catch (StatusRuntimeException e) { if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ sharedContext.setGrpcErrorCode(403); + } else { + logger.error(e.getMessage()); } } } 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 d8fcc7e7c..c7f04210b 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 @@ -43,7 +43,14 @@ public StudentResponse GetStudent(Map headers) throws StatusRunt .intercept(interceptor) .build(); StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); - return blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + if (blockingStub == null) { + throw new RuntimeException("Failed to create blocking stub"); + } + StudentResponse response = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + if (response == null) { + throw new RuntimeException("Failed to get student"); + } + return response; }catch (SSLException e) { throw new RuntimeException("Failed to create SSL context", e); } finally { @@ -74,7 +81,14 @@ public StudentResponse GetStudentDefaultVersion(Map headers) thr .intercept(interceptor) .build(); StudentServiceDefaultVersionGrpc.StudentServiceBlockingStub blockingStub = StudentServiceDefaultVersionGrpc.newBlockingStub(managedChannel); - return blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + if (blockingStub == null) { + throw new RuntimeException("Failed to create blocking stub"); + } + StudentResponse response = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); + if (response == null) { + throw new RuntimeException("Failed to get student"); + } + return response; }catch (SSLException e) { throw new RuntimeException("Failed to create SSL context", e); } finally { From fe87a63a34068358af0e662a1d0516eef4c89dc9 Mon Sep 17 00:00:00 2001 From: DinethH Date: Tue, 30 Apr 2024 09:30:49 +0530 Subject: [PATCH 73/81] Slight change to test --- test/cucumber-tests/scripts/setup-hosts.sh | 1 + .../src/test/java/org/wso2/apk/integration/api/BaseSteps.java | 2 +- .../integration/utils/clients/SimpleGRPCStudentClient.java | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/cucumber-tests/scripts/setup-hosts.sh b/test/cucumber-tests/scripts/setup-hosts.sh index 5fb451248..2f541a7b5 100644 --- a/test/cucumber-tests/scripts/setup-hosts.sh +++ b/test/cucumber-tests/scripts/setup-hosts.sh @@ -6,6 +6,7 @@ kubectl wait deployment/backend-retry-deployment -n apk-integration-test --for=c kubectl wait deployment/dynamic-backend -n apk-integration-test --for=condition=available --timeout=600s kubectl wait deployment/interceptor-service-deployment -n apk-integration-test --for=condition=available --timeout=600s kubectl wait deployment/graphql-faker -n apk-integration-test --for=condition=available --timeout=600s +kubectl wait deployment/grpc-backend -n apk-integration-test --for=condition=available --timeout=600s kubectl wait --timeout=5m -n apk-integration-test deployment/apk-test-setup-wso2-apk-adapter-deployment --for=condition=Available kubectl wait --timeout=15m -n apk-integration-test deployment/apk-test-setup-wso2-apk-gateway-runtime-deployment --for=condition=Available IP=$(kubectl get svc apk-test-setup-wso2-apk-gateway-service -n apk-integration-test --output jsonpath='{.status.loadBalancer.ingress[0].ip}') 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 cba1b1530..d31b2c245 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 @@ -180,7 +180,7 @@ public void GetStudent(String arg0, int arg1) throws StatusRuntimeException { public void GetStudentDefaultVersion(String arg0, int arg1) throws StatusRuntimeException { try { SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0, arg1); - sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getHeaders())); + sharedContext.setStudentResponse(grpcStudentClient.GetStudentDefaultVersion(sharedContext.getHeaders())); } catch (StatusRuntimeException e) { if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ sharedContext.setGrpcErrorCode(403); 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 c7f04210b..0f7ba0b98 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 @@ -44,10 +44,12 @@ public StudentResponse GetStudent(Map headers) throws StatusRunt .build(); StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel); if (blockingStub == null) { + log.error("Failed to create blocking stub"); throw new RuntimeException("Failed to create blocking stub"); } StudentResponse response = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); if (response == null) { + log.error("Failed to get student"); throw new RuntimeException("Failed to get student"); } return response; @@ -82,10 +84,12 @@ public StudentResponse GetStudentDefaultVersion(Map headers) thr .build(); StudentServiceDefaultVersionGrpc.StudentServiceBlockingStub blockingStub = StudentServiceDefaultVersionGrpc.newBlockingStub(managedChannel); if (blockingStub == null) { + log.error("Failed to create blocking stub"); throw new RuntimeException("Failed to create blocking stub"); } StudentResponse response = blockingStub.getStudent(StudentRequest.newBuilder().setId(1).build()); if (response == null) { + log.error("Failed to get student"); throw new RuntimeException("Failed to get student"); } return response; From ddd006652193debddf480b28f8bf0369755ba661 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 2 May 2024 11:34:04 +0530 Subject: [PATCH 74/81] fixed api-definition-endpoint --- .../server/swagger/SwaggerServerHandler.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java index 16538f4d0..c97326e8b 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java @@ -16,8 +16,11 @@ import org.wso2.apk.enforcer.constants.AdminConstants; import org.wso2.apk.enforcer.constants.HttpConstants; import org.wso2.apk.enforcer.models.ResponsePayload; + import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class SwaggerServerHandler extends SimpleChannelInboundHandler { @@ -43,17 +46,32 @@ public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Excep boolean isSwagger = false; - String [] params = request.uri().split("/"); - final String basePath = "/" + params[1] + "/" + params[2]; - final String vHost = params[3].split("\\?")[0]; - final String queryParam = params[3].split("\\?")[1]; + //check if it's GRPC request using the header + String header = request.headers().get("ApiType"); + String[] params = request.uri().split("/"); + final String basePath; + final String vHost; + final String queryParam; + final String version; + if (header != null && header.equals("GRPC")) { + basePath = "/" + params[1]; + vHost = params[2].split("\\?")[0]; + queryParam = params[2].split("\\?")[1]; + version = extractVersionFromGrpcBasePath(params[1]); + } else { + basePath = "/" + params[1] + "/" + params[2]; + vHost = params[3].split("\\?")[0]; + queryParam = params[3].split("\\?")[1]; + version = params[2]; + } + if (APIDefinitionConstants.SWAGGER_DEFINITION.equalsIgnoreCase(queryParam)) { isSwagger = true; } if(isSwagger){ // load the corresponding swagger definition from the API name - byte[] apiDefinition = apiFactory.getAPIDefinition(basePath, params[2], vHost); + byte[] apiDefinition = apiFactory.getAPIDefinition(basePath, version, vHost); if (apiDefinition == null || apiDefinition.length == 0) { String error = AdminConstants.ErrorMessages.NOT_FOUND; responsePayload = new ResponsePayload(); @@ -91,4 +109,15 @@ private void buildAndSendResponse(ChannelHandlerContext ctx, ResponsePayload res httpResponse.headers().set(HTTP.CONTENT_LEN, httpResponse.content().readableBytes()); ctx.writeAndFlush(httpResponse); } + + private static String extractVersionFromGrpcBasePath(String input) { + // Pattern to match '.v' followed by digits and optional periods followed by more digits + Pattern pattern = Pattern.compile("\\.v\\d+(\\.\\d+)*"); + Matcher matcher = pattern.matcher(input); + + if (matcher.find()) { + return matcher.group().substring(1); // Returns the matched version + } + return ""; + } } From bbaa071e8b563ce400bbf95c45767723dabec219 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 2 May 2024 12:47:17 +0530 Subject: [PATCH 75/81] fixed api-definition-endpoint --- .../apk/enforcer/server/swagger/SwaggerServerHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java index c97326e8b..04d6cd220 100644 --- a/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java +++ b/gateway/enforcer/org.wso2.apk.enforcer/src/main/java/org/wso2/apk/enforcer/server/swagger/SwaggerServerHandler.java @@ -53,7 +53,9 @@ public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Excep final String vHost; final String queryParam; final String version; - if (header != null && header.equals("GRPC")) { + //if len params is 3, then it's a GRPC request else other + final String type = params.length == 3 ? "GRPC" : "REST"; + if (type.equals("GRPC")) { basePath = "/" + params[1]; vHost = params[2].split("\\?")[0]; queryParam = params[2].split("\\?")[1]; From c2a09975aadf6ea10e3a058345fb5821cdecf829 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 2 May 2024 13:48:36 +0530 Subject: [PATCH 76/81] changed path match type to Exact --- runtime/config-deployer-service/ballerina/APIClient.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 5c86fc3bc..9151f2119 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -1057,7 +1057,7 @@ public class APIClient { private isolated function retrieveGRPCRouteMatch(APKOperations apiOperation) returns model:GRPCRouteMatch { model:GRPCRouteMatch grpcRouteMatch = { method: { - 'type: "RegularExpression", + 'type: "Exact", 'service: apiOperation.target, method: apiOperation.verb } From ed46c2c316adffc8d6e6db9adbbebe76dd9c3780 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 2 May 2024 16:27:45 +0530 Subject: [PATCH 77/81] add cucumber test for api definition endpoint --- .../wso2/apk/integration/api/BaseSteps.java | 6 ++++- .../src/test/resources/tests/api/GRPC.feature | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) 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 d31b2c245..7f4e7826a 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 @@ -112,6 +112,11 @@ public void theStudentResponseBodyShouldContainNameAndAge(String arg0, int arg1) public void theResponseBodyShouldContain(String expectedText) throws IOException { Assert.assertTrue(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); } + @Then("the response body should contain endpoint definition for student.proto") + public void theResponseBodyShouldContainEndpointDefinition() throws IOException { + String expectedText = "{\"apiDefinition\":\"syntax = \\\"proto3\\\";\\n\\noption java_multiple_files = true;\\noption java_package = \\\"org.example\\\";\\npackage dineth.grpc.api.v1.student;\\n\\nservice StudentService {\\n rpc GetStudent(StudentRequest) returns (StudentResponse) {};\\n rpc GetStudentStream(StudentRequest) returns (stream StudentResponse) {};\\n rpc SendStudentStream(stream StudentRequest) returns (StudentResponse) {};\\n rpc SendAndGetStudentStream(stream StudentRequest) returns (stream StudentResponse) {}\\n}\\n\\nmessage StudentRequest {\\n int32 id = 3;\\n}\\n\\nmessage StudentResponse {\\n string name = 1;\\n int32 age = 2;\\n}\\n\"}"; + Assert.assertTrue(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); + } @Then("the response body should not contain {string}") public void theResponseBodyShouldNotContain(String expectedText) throws IOException { Assert.assertFalse(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); @@ -134,7 +139,6 @@ public void theResponseStatusCodeShouldBe(int expectedStatusCode) throws IOExcep @Then("the grpc error response status code should be {int}") public void theGrpcErrorResponseStatusCodeShouldBe(int expectedStatusCode) throws IOException { - int actualStatusCode = sharedContext.getGrpcErrorCode(); Assert.assertEquals(actualStatusCode, expectedStatusCode); } 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 c6b7ebcd8..8ebc10e4d 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -20,6 +20,29 @@ Feature: Generating APK conf for gRPC API | 500 | And the student response body should contain name: "Dineth" age: 10 + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "grpc-basic-api" + Then the response status code should be 202 + + Scenario: Checking api-definition endpoint to get proto file + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc.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} | + | Host | default.gw.wso2.com | + And I send "GET" request to "https://default.gw.wso2.com:9095/dineth.grpc.api.v1/api-definition/" with body "" + And I eventually receive 200 response code, not accepting + | 429 | + | 500 | + And the response body should contain endpoint definition for student.proto + + Scenario: Undeploy API Given The system is ready And I have a valid subscription From 2282d778d8631b0009ef5b9378557e8216094b6f Mon Sep 17 00:00:00 2001 From: DinethH Date: Fri, 3 May 2024 11:48:51 +0530 Subject: [PATCH 78/81] Add name generation for apk-conf --- .../config-deployer-service/ballerina/APIClient.bal | 7 ++++++- .../java/org/wso2/apk/config/RuntimeAPICommonUtil.java | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/runtime/config-deployer-service/ballerina/APIClient.bal b/runtime/config-deployer-service/ballerina/APIClient.bal index 9151f2119..549ae6423 100644 --- a/runtime/config-deployer-service/ballerina/APIClient.bal +++ b/runtime/config-deployer-service/ballerina/APIClient.bal @@ -46,7 +46,7 @@ public class APIClient { encodedString = encodedString.substring(0, encodedString.length() - 1); } APKConf apkConf = { - name: api.getName(), + name: api.getType() == API_TYPE_GRPC ? self.getUniqueNameForGrpcApi(api.getName()) : api.getName(), basePath: api.getBasePath().length() > 0 ? api.getBasePath() : encodedString, version: api.getVersion(), 'type: api.getType() == "" ? API_TYPE_REST : api.getType().toUpperAscii() @@ -1679,6 +1679,11 @@ public class APIClient { return hashedValue.toBase16(); } + public isolated function getUniqueNameForGrpcApi(string concatanatedServices) returns string { + byte[] hashedValue = crypto:hashSha1(concatanatedServices.toBytes()); + return hashedValue.toBase16(); + } + public isolated function retrieveHttpRouteRefName(APKConf apkConf, string 'type, commons:Organization organization) returns string { return uuid:createType1AsString(); } diff --git a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java index ebca7e8e4..c93073e6d 100644 --- a/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java +++ b/runtime/config-deployer-service/java/src/main/java/org/wso2/apk/config/RuntimeAPICommonUtil.java @@ -148,7 +148,11 @@ private static API getGRPCAPIFromProtoDefinition(String definition) { API api = new API(); api.setBasePath("/"+protoParser.protoFile.basePath); api.setVersion(protoParser.protoFile.version); + StringBuilder apiName = new StringBuilder(); + List sortedServices = new ArrayList<>(); + for (ProtoParser.Service service : protoParser.getServices()) { + sortedServices.add(service.name); for (String method : service.methods) { URITemplate uriTemplate = new URITemplate(); uriTemplate.setUriTemplate(protoParser.protoFile.packageName + "." + service.name); @@ -156,6 +160,12 @@ private static API getGRPCAPIFromProtoDefinition(String definition) { uriTemplates.add(uriTemplate); } } + sortedServices.sort(String::compareTo); + for (String service : sortedServices) { + apiName.append(service).append("-"); + } + apiName.deleteCharAt(apiName.length() - 1); + api.setName(apiName.toString()); api.setUriTemplates(uriTemplates.toArray(new URITemplate[uriTemplates.size()])); return api; From af1445e41d97322aacba8db08b97d9cf63a3c666 Mon Sep 17 00:00:00 2001 From: DinethH Date: Mon, 6 May 2024 10:26:46 +0530 Subject: [PATCH 79/81] prevent GRPC API from propagating to CP --- adapter/internal/operator/controllers/dp/api_controller.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adapter/internal/operator/controllers/dp/api_controller.go b/adapter/internal/operator/controllers/dp/api_controller.go index ed541a472..4a18a7b58 100644 --- a/adapter/internal/operator/controllers/dp/api_controller.go +++ b/adapter/internal/operator/controllers/dp/api_controller.go @@ -529,6 +529,9 @@ func (apiReconciler *APIReconciler) resolveAPIRefs(ctx context.Context, api dpv1 func isAPIPropagatable(apiState *synchronizer.APIState) bool { validOrgs := []string{"carbon.super"} + if apiState.APIDefinition.Spec.APIType == "GRPC" { + return false + } // System APIs should not be propagated to CP if apiState.APIDefinition.Spec.SystemAPI { return false From aaa9180d79eb1fefaac2090cb34d301a4f3063f2 Mon Sep 17 00:00:00 2001 From: DinethH Date: Wed, 8 May 2024 17:34:38 +0530 Subject: [PATCH 80/81] add cucumber test for mtls --- .../wso2/apk/integration/api/BaseSteps.java | 10 ++++- ...th_mtls_mandatory_oauth2_disabled.apk-conf | 36 +++++++++++++++++ ...ith_mtls_optional_oauth2_optional.apk-conf | 36 +++++++++++++++++ .../test/resources/tests/api/GRPCMTLS.feature | 39 +++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_mandatory_oauth2_disabled.apk-conf create mode 100644 test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_optional_oauth2_optional.apk-conf create mode 100644 test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature 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 7f4e7826a..7663f9dee 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 @@ -174,8 +174,16 @@ public void GetStudent(String arg0, int arg1) throws StatusRuntimeException { } catch (StatusRuntimeException e) { if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ sharedContext.setGrpcErrorCode(403); + } else if (e.getStatus().getCode()== Status.Code.UNIMPLEMENTED){ + sharedContext.setGrpcErrorCode(501); + } else if (e.getStatus().getCode()== Status.Code.UNAVAILABLE){ + sharedContext.setGrpcErrorCode(503); + } else if (e.getStatus().getCode()== Status.Code.NOT_FOUND){ + sharedContext.setGrpcErrorCode(404); + } else if (e.getStatus().getCode()== Status.Code.UNAUTHENTICATED){ + sharedContext.setGrpcErrorCode(401); } else { - logger.error(e.getMessage()); + logger.error(e.getMessage() + "code: " + e.getStatus().getCode()); } } } diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_mandatory_oauth2_disabled.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_mandatory_oauth2_disabled.apk-conf new file mode 100644 index 000000000..8341e9095 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_mandatory_oauth2_disabled.apk-conf @@ -0,0 +1,36 @@ +--- +name: "demo-grpc-api" +basePath: "/dineth.grpc.api" +version: "v1" +type: "GRPC" +id: "grpc-mtls-mandatory-oauth2-disabled" +endpointConfigurations: + production: + endpoint: "http://grpc-backend:6565" +defaultVersion: false +subscriptionValidation: false +operations: +- target: "student.StudentService" + verb: "GetStudent" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "GetStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendAndGetStudentStream" + secured: true + scopes: [] +authentication: + - authType: OAuth2 + enabled: false + - authType: mTLS + required: mandatory + certificates: + - name: mtls-test-configmap + key: tls.crt diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_optional_oauth2_optional.apk-conf b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_optional_oauth2_optional.apk-conf new file mode 100644 index 000000000..57a849c5a --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/grpc/grpc_with_mtls_optional_oauth2_optional.apk-conf @@ -0,0 +1,36 @@ +--- +name: "demo-grpc-api" +basePath: "/dineth.grpc.api" +version: "v1" +type: "GRPC" +id: "grpc-mtls-optional-oauth2-optional" +endpointConfigurations: + production: + endpoint: "http://grpc-backend:6565" +defaultVersion: false +subscriptionValidation: false +operations: +- target: "student.StudentService" + verb: "GetStudent" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "GetStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendStudentStream" + secured: true + scopes: [] +- target: "student.StudentService" + verb: "SendAndGetStudentStream" + secured: true + scopes: [] +authentication: + - authType: OAuth2 + required: optional + - authType: mTLS + required: optional + certificates: + - name: mtls-test-configmap + key: tls.crt diff --git a/test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature b/test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature new file mode 100644 index 000000000..05548f7ca --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature @@ -0,0 +1,39 @@ +Feature: Test mTLS between client and gateway with client certificate sent in header + Scenario: Test API with mandatory mTLS and OAuth2 disabled + Given The system is ready + And I have a valid token with a client certificate "invalid-cert.txt" + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc_with_mtls_mandatory_oauth2_disabled.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 + | X-WSO2-CLIENT-CERTIFICATE | ${clientCertificate} | + And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 + And I eventually receive 200 response code, not accepting + | 401 | + And the student response body should contain name: "Dineth" age: 10 + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "grpc-mtls-mandatory-oauth2-disabled" + Then the response status code should be 202 + + Scenario: Test optional mTLS and optional OAuth2 with an invalid client certificate and invalid token in header + Given The system is ready + And I have a valid token with a client certificate "invalid-cert.txt" + When I use the APK Conf file "artifacts/apk-confs/grpc/grpc_with_mtls_optional_oauth2_optional.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 + | X-WSO2-CLIENT-CERTIFICATE | ${clientCertificate} | + | Authorization | bearer {accessToken} | + And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 + And the grpc error response status code should be 401 + + Scenario: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "grpc-mtls-optional-oauth2-optional" + Then the response status code should be 202 From b43369023e8cdc2a2a9b166ba838f14b7df1b101 Mon Sep 17 00:00:00 2001 From: DinethH Date: Thu, 9 May 2024 10:09:42 +0530 Subject: [PATCH 81/81] changed gRPC status code --- .../wso2/apk/integration/api/BaseSteps.java | 30 ++++++------------- .../apk/integration/api/SharedContext.java | 11 ++++--- .../src/test/resources/tests/api/GRPC.feature | 22 ++++---------- .../test/resources/tests/api/GRPCMTLS.feature | 5 ++-- 4 files changed, 22 insertions(+), 46 deletions(-) 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 7663f9dee..faf39122f 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 @@ -137,9 +137,9 @@ public void theResponseStatusCodeShouldBe(int expectedStatusCode) throws IOExcep Assert.assertEquals(actualStatusCode, expectedStatusCode); } - @Then("the grpc error response status code should be {int}") - public void theGrpcErrorResponseStatusCodeShouldBe(int expectedStatusCode) throws IOException { - int actualStatusCode = sharedContext.getGrpcErrorCode(); + @Then("the gRPC response status code should be {int}") + public void theGrpcResponseStatusCodeShouldBe(int expectedStatusCode) throws IOException { + int actualStatusCode = sharedContext.getGrpcStatusCode(); Assert.assertEquals(actualStatusCode, expectedStatusCode); } @@ -171,20 +171,10 @@ public void GetStudent(String arg0, int arg1) throws StatusRuntimeException { try { SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0, arg1); sharedContext.setStudentResponse(grpcStudentClient.GetStudent(sharedContext.getHeaders())); + sharedContext.setGrpcStatusCode(0); } catch (StatusRuntimeException e) { - if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ - sharedContext.setGrpcErrorCode(403); - } else if (e.getStatus().getCode()== Status.Code.UNIMPLEMENTED){ - sharedContext.setGrpcErrorCode(501); - } else if (e.getStatus().getCode()== Status.Code.UNAVAILABLE){ - sharedContext.setGrpcErrorCode(503); - } else if (e.getStatus().getCode()== Status.Code.NOT_FOUND){ - sharedContext.setGrpcErrorCode(404); - } else if (e.getStatus().getCode()== Status.Code.UNAUTHENTICATED){ - sharedContext.setGrpcErrorCode(401); - } else { - logger.error(e.getMessage() + "code: " + e.getStatus().getCode()); - } + sharedContext.setGrpcStatusCode(e.getStatus().getCode().value()); + logger.error(e.getMessage() + " Status code: " + e.getStatus().getCode().value()) ; } } @@ -193,12 +183,10 @@ public void GetStudentDefaultVersion(String arg0, int arg1) throws StatusRuntime try { SimpleGRPCStudentClient grpcStudentClient = new SimpleGRPCStudentClient(arg0, arg1); sharedContext.setStudentResponse(grpcStudentClient.GetStudentDefaultVersion(sharedContext.getHeaders())); + sharedContext.setGrpcStatusCode(0); } catch (StatusRuntimeException e) { - if (e.getStatus().getCode()== Status.Code.PERMISSION_DENIED){ - sharedContext.setGrpcErrorCode(403); - } else { - logger.error(e.getMessage()); - } + sharedContext.setGrpcStatusCode(e.getStatus().getCode().value()); + logger.error(e.getMessage() + " Status code: " + e.getStatus().getCode().value()) ; } } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java index 2b7c88bb0..66bc9bfbc 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -18,7 +18,6 @@ package org.wso2.apk.integration.api; import org.apache.http.HttpResponse; -import org.wso2.apk.integration.utils.clients.SimpleGRPCStudentClient; import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; import org.wso2.apk.integration.utils.clients.studentGrpcClient.StudentResponse; @@ -36,7 +35,7 @@ public class SharedContext { private String accessToken; private HttpResponse response; private String responseBody; - private int grpcErrorCode; + private int grpcStatusCode; private HashMap valueStore = new HashMap<>(); private HashMap headers = new HashMap<>(); @@ -56,11 +55,11 @@ public void setAccessToken(String accessToken) { this.accessToken = accessToken; } - public int getGrpcErrorCode() { - return grpcErrorCode; + public int getGrpcStatusCode() { + return grpcStatusCode; } - public void setGrpcErrorCode(int grpcErrorCode) { - this.grpcErrorCode = grpcErrorCode; + public void setGrpcStatusCode(int grpcStatusCode) { + this.grpcStatusCode = grpcStatusCode; } public HttpResponse getResponse() { 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 8ebc10e4d..873ad7c48 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPC.feature @@ -15,9 +15,7 @@ Feature: Generating APK conf for gRPC API Then I set headers | Authorization | bearer ${accessToken} | And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 - And I eventually receive 200 response code, not accepting - | 429 | - | 500 | + And the gRPC response status code should be 0 And the student response body should contain name: "Dineth" age: 10 Scenario: Undeploy API @@ -57,9 +55,7 @@ Feature: Generating APK conf for gRPC API And make the API deployment request Then the response status code should be 200 And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 - And I eventually receive 200 response code, not accepting - | 429 | - | 500 | + And the gRPC response status code should be 0 And the student response body should contain name: "Dineth" age: 10 Scenario: Undeploy API @@ -78,15 +74,13 @@ Feature: Generating APK conf for gRPC API Then I set headers | Authorization | bearer ${accessToken} | And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 - And the grpc error response status code should be 403 + And the gRPC response status code should be 7 Given I have a valid subscription with scopes | wso2 | Then I set headers | Authorization | bearer ${accessToken} | And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 - And I eventually receive 200 response code, not accepting - | 429 | - | 500 | + And the gRPC response status code should be 0 And the student response body should contain name: "Dineth" age: 10 Scenario: Undeploy API @@ -105,17 +99,13 @@ Feature: Generating APK conf for gRPC API Then I set headers | Authorization | bearer ${accessToken} | And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 - And I eventually receive 200 response code, not accepting - | 429 | - | 500 | + And the gRPC response status code should be 0 And the student response body should contain name: "Dineth" age: 10 Given I have a valid subscription Then I set headers | Authorization | bearer ${accessToken} | And I make grpc request GetStudent default version to "default.gw.wso2.com" with port 9095 - And I eventually receive 200 response code, not accepting - | 429 | - | 500 | + And the gRPC response status code should be 0 And the student response body should contain name: "Dineth" age: 10 Scenario: Undeploy API diff --git a/test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature b/test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature index 05548f7ca..87fa0fac0 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/GRPCMTLS.feature @@ -9,8 +9,7 @@ Feature: Test mTLS between client and gateway with client certificate sent in he Then I set headers | X-WSO2-CLIENT-CERTIFICATE | ${clientCertificate} | And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 - And I eventually receive 200 response code, not accepting - | 401 | + And the gRPC response status code should be 0 And the student response body should contain name: "Dineth" age: 10 Scenario: Undeploy API @@ -30,7 +29,7 @@ Feature: Test mTLS between client and gateway with client certificate sent in he | X-WSO2-CLIENT-CERTIFICATE | ${clientCertificate} | | Authorization | bearer {accessToken} | And I make grpc request GetStudent to "default.gw.wso2.com" with port 9095 - And the grpc error response status code should be 401 + And the gRPC response status code should be 16 Scenario: Undeploy API Given The system is ready