Skip to content

Commit

Permalink
Merge pull request #2353 from DDH13/grpc-apk-conf
Browse files Browse the repository at this point in the history
gRPC apk-conf support
  • Loading branch information
CrowleyRajapakse authored May 9, 2024
2 parents 2ea0b3a + b433690 commit c11392e
Show file tree
Hide file tree
Showing 40 changed files with 3,416 additions and 22 deletions.
3 changes: 3 additions & 0 deletions adapter/internal/operator/controllers/dp/api_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HttpObject> {

Expand All @@ -43,17 +46,34 @@ 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 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];
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();
Expand Down Expand Up @@ -91,4 +111,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 "";
}
}
151 changes: 144 additions & 7 deletions runtime/config-deployer-service/ballerina/APIClient.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 <string>k8sService.protocol + "://" + string:'join(".", <string>k8sService.name, <string>k8sService.namespace, "svc.cluster.local") + ":" + k8sService.port.toString();
}
Expand Down Expand Up @@ -442,6 +450,19 @@ public class APIClient {
sandboxRoutes.push(gqlRoute.metadata.name);
}
}
} 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);
}
}
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 {
Expand Down Expand Up @@ -532,6 +553,26 @@ 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 grpcRoute.spec.rules.length() > 0 {
if endpointType == PRODUCTION_TYPE {
apiArtifact.productionGrpcRoutes.push(grpcRoute);
} else {
apiArtifact.sandboxGrpcRoutes.push(grpcRoute);
}
}
} else {
model:HTTPRoute httpRoute = {
metadata:
Expand All @@ -553,10 +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;
Expand All @@ -571,7 +612,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 () {
Expand Down Expand Up @@ -625,12 +666,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}};
(<model:GRPCRouteFilter[]>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}};
(<model:GRPCRouteFilter[]>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}};
(<model:GRPCRouteFilter[]>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}};
(<model:GRPCRouteFilter[]>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 () {
Expand Down Expand Up @@ -754,7 +855,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 = ();
Expand All @@ -778,7 +879,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;
}
Expand Down Expand Up @@ -923,6 +1033,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: <string>apiOperation.verb, path: {'type: "RegularExpression", value: self.retrievePathPrefix(apkConf.basePath, apkConf.'version, apiOperation.target ?: "/*", organization)}};
Expand All @@ -937,6 +1054,17 @@ public class APIClient {
}
}

private isolated function retrieveGRPCRouteMatch(APKOperations apiOperation) returns model:GRPCRouteMatch {
model:GRPCRouteMatch grpcRouteMatch = {
method: {
'type: "Exact",
'service: <string>apiOperation.target,
method: <string>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);
Expand Down Expand Up @@ -973,6 +1101,10 @@ public class APIClient {
api1.setGraphQLSchema(definition);
return definition;
}
if apkConf.'type == API_TYPE_GRPC && definition is string {
api1.setProtoDefinition(definition);
return definition;
}
if definition is string && definition.toString().trim().length() > 0 {
retrievedDefinition = runtimeUtil:RuntimeAPICommonUtil_generateDefinition2(api1, definition);
} else {
Expand Down Expand Up @@ -1547,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();
}
Expand Down Expand Up @@ -1644,7 +1781,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 () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit c11392e

Please sign in to comment.