Skip to content

Commit

Permalink
Merge pull request #2704 from praminda/feature/default-api
Browse files Browse the repository at this point in the history
Add default version support
  • Loading branch information
Praminda authored Mar 4, 2022
2 parents 070b4ec + b2bbe87 commit 9ad3eee
Show file tree
Hide file tree
Showing 18 changed files with 467 additions and 48 deletions.
12 changes: 11 additions & 1 deletion adapter/internal/api/apis_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func ApplyAPIProjectFromAPIM(
if err != nil {
return nil, err
}
apiYaml := apiProject.APIYaml.Data
apiYaml := &apiProject.APIYaml.Data
if apiEnvProps, found := apiEnvs[apiProject.APIYaml.Data.ID]; found {
loggers.LoggerAPI.Infof("Environment specific values found for the API %v ", apiProject.APIYaml.Data.ID)
apiProject.APIEnvProps = apiEnvProps
Expand Down Expand Up @@ -298,6 +298,16 @@ func ApplyAPIProjectFromAPIM(
allEnvironments := xds.GetAllEnvironments(apiYaml.ID, vhost, environments)
loggers.LoggerAPI.Debugf("Update all environments (%v) of API %v %v:%v with UUID \"%v\".",
allEnvironments, vhost, apiYaml.Name, apiYaml.Version, apiYaml.ID)
// We don't need to be environment specific when checking default version. It's applied at API level
// hence picking 0th index here.
if api, ok := xds.APIListMap[allEnvironments[0]][apiYaml.ID]; ok {
apiYaml.IsDefaultVersion = api.IsDefaultVersion
} else {
// APIListMap is synchronously updated only for default version changes. In other API deployment
// events, this may not be updated. We can safely ignore this case since runtime artifact's
// `isDefaultVersion` prop is anyway updated for deployment events.
loggers.LoggerAPI.Debugf("API %s is not found in API Metadata map.", apiYaml.ID)
}
// first update the API for vhost
deployedRevision, err := xds.UpdateAPI(vhost, apiProject, allEnvironments)
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions adapter/internal/discovery/xds/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,18 @@ func GetAllEnvironments(apiUUID, vhost string, newEnvironments []string) []strin
return allEnvironments
}

// GetDeployedEnvironments returns all the environments the API with `apiUUID` is deployed to
func GetDeployedEnvironments(apiUUID string) []string {
var envs []string
if envMap, ok := apiUUIDToGatewayToVhosts[apiUUID]; ok {
envs = make([]string, 0, len(envMap))
for k := range envMap {
envs = append(envs, k)
}
}
return envs
}

// GetVhostOfAPI returns the vhost of API deployed in the given gateway environment
func GetVhostOfAPI(apiUUID, environment string) (vhost string, exists bool) {
if envToVhost, ok := apiUUIDToGatewayToVhosts[apiUUID]; ok {
Expand Down
9 changes: 9 additions & 0 deletions adapter/internal/eventhub/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@ func LoadSubscriptionData(configFile *config.Config, initialAPIUUIDListMap map[s
go retrieveAPIListFromChannel(APIListChannel, nil)
}

// UpdateAPIMetadataFromCP Invokes `ApisEndpoint` and updates APIList synchronously.
func UpdateAPIMetadataFromCP(params map[string]string) {
var apiList *types.APIList
var responseChannel = make(chan response)
go InvokeService(ApisEndpoint, apiList, params, responseChannel, 0)
response := <-responseChannel
retrieveAPIList(response, nil)
}

// InvokeService invokes the internal data resource
func InvokeService(endpoint string, responseType interface{}, queryParamMap map[string]string, c chan response,
retryAttempt int) {
Expand Down
47 changes: 41 additions & 6 deletions adapter/internal/messaging/notification_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const (
policyUpdate = "POLICY_UPDATE"
policyDelete = "POLICY_DELETE"
blockedStatus = "BLOCKED"
apiUpdate = "API_UPDATE"
)

// var variables
Expand Down Expand Up @@ -131,11 +132,34 @@ func processNotificationEvent(conf *config.Config, notification *msg.EventNotifi
return nil
}

// handleDefaultVersionUpdate will redeploy default versioned API.
// API runtime artifact doesn't get updated in CP side when default version is updated
// (isDefaultVersion prop in apiYaml is not updated). API deployment or should happen
// for it to get updated. However we need to redeploy the API when there is a default
// version change. For that we call `/apis` endpoint to get updated API metadata (this
// contains the updated `isDefaultVersion` field). Now we proceed with fetching runtime
// artifact from the CP. When creating CC deployment objects we refer to updated `APIList`
// map and update runtime artifact's `isDefaultVersion` field to correctly deploy default
// versioned API.
func handleDefaultVersionUpdate(event msg.APIEvent) {
deployedEnvs := xds.GetDeployedEnvironments(event.UUID)
for _, env := range deployedEnvs {
query := make(map[string]string, 3)
query[eh.GatewayLabelParam] = env
query[eh.ContextParam] = event.APIContext
query[eh.VersionParam] = event.APIVersion
eh.UpdateAPIMetadataFromCP(query)
}

synchronizer.FetchAPIsFromControlPlane(event.UUID, deployedEnvs)
}

// handleAPIEvents to process api related data
func handleAPIEvents(data []byte, eventType string) {
var (
apiEvent msg.APIEvent
currentTimeStamp int64 = apiEvent.Event.TimeStamp
apiEvent msg.APIEvent
currentTimeStamp int64 = apiEvent.Event.TimeStamp
isDefaultVersionEvent bool
)

apiEventErr := json.Unmarshal([]byte(string(data)), &apiEvent)
Expand All @@ -162,14 +186,21 @@ func handleAPIEvents(data []byte, eventType string) {
return
}

isDefaultVersionEvent = isDefaultVersionUpdate(apiEvent)

if isDefaultVersionEvent {
handleDefaultVersionUpdate(apiEvent)
return
}

// Per each revision, synchronization should happen.
if strings.EqualFold(deployAPIToGateway, apiEvent.Event.Type) {
go synchronizer.FetchAPIsFromControlPlane(apiEvent.UUID, apiEvent.GatewayLabels)
}

for _, env := range apiEvent.GatewayLabels {
if isLaterEvent(apiListTimeStampMap, apiEvent.UUID+":"+env, currentTimeStamp) {
return
break
}
// removeFromGateway event with multiple labels could only appear when the API is subjected
// to delete. Hence we could simply delete after checking against just one iteration.
Expand All @@ -181,7 +212,7 @@ func handleAPIEvents(data []byte, eventType string) {
xds.UpdateEnforcerAPIList(env, xdsAPIList)
}
}
return
break
}
if strings.EqualFold(deployAPIToGateway, apiEvent.Event.Type) {
conf, _ := config.ReadConfigs()
Expand All @@ -195,13 +226,13 @@ func handleAPIEvents(data []byte, eventType string) {
logger.LoggerInternalMsg.Debugf("API Metadata for api Id: %s is not updated as it already exists", apiEvent.UUID)
continue
}
logger.LoggerInternalMsg.Debugf("Fetching Metadata for api Id: %s ", apiEvent.UUID)
queryParamMap := make(map[string]string, 3)
queryParamMap[eh.GatewayLabelParam] = configuredEnv
queryParamMap[eh.ContextParam] = apiEvent.Context
queryParamMap[eh.VersionParam] = apiEvent.Version
var apiList *types.APIList
go eh.InvokeService(eh.ApisEndpoint, apiList, queryParamMap,
eh.APIListChannel, 0)
go eh.InvokeService(eh.ApisEndpoint, apiList, queryParamMap, eh.APIListChannel, 0)
}
}
}
Expand Down Expand Up @@ -423,6 +454,10 @@ func isLaterEvent(timeStampMap map[string]int64, mapKey string, currentTimeStamp
return false
}

func isDefaultVersionUpdate(event msg.APIEvent) bool {
return strings.EqualFold(apiUpdate, event.Event.Type) && strings.EqualFold("DEFAULT_VERSION", event.Action)
}

func belongsToTenant(tenantDomain string) bool {
// TODO : enable this once the events are fixed in apim
// return config.GetControlPlaneConnectedTenantDomain() == tenantDomain
Expand Down
38 changes: 22 additions & 16 deletions adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package envoyconf

import (
"fmt"
"io/ioutil"
"regexp"
"strings"
Expand Down Expand Up @@ -46,19 +47,16 @@ func TestGenerateRoutePaths(t *testing.T) {
basePath := "/basePath"
resourcePath := "/resource"

completeRoutePath := generateRoutePaths(xWso2BasePath, basePath, resourcePath)
filteredBasePath := getFilteredBasePath(xWso2BasePath, basePath)
completeRoutePath := generateRoutePath(filteredBasePath, resourcePath)
// TODO: (VirajSalaka) check if it is possible to perform an equals operation instead of prefix
if !strings.HasPrefix(completeRoutePath, "^/xWso2BasePath/resource") {
t.Error("The generated path should contain xWso2BasePath as a prefix if xWso2Basepath is available.")
}

xWso2BasePath = "/xWso2BasePath/"
if !strings.HasPrefix(completeRoutePath, "^/xWso2BasePath/resource") {
t.Error("The generated path should not contain the trailing '\\' of xWso2BasePath property within the generated route path.")
}

xWso2BasePath = ""
completeRoutePath = generateRoutePaths(xWso2BasePath, basePath, resourcePath)
filteredBasePath = getFilteredBasePath(xWso2BasePath, basePath)
completeRoutePath = generateRoutePath(filteredBasePath, resourcePath)
if !strings.HasPrefix(completeRoutePath, "^/basePath/resource") {
t.Error("The generated path should contain basePath as a prefix if xWso2Basepath is unavailable.")
}
Expand Down Expand Up @@ -136,7 +134,7 @@ func TestCreateRoute(t *testing.T) {
}

generatedRouteWithXWso2BasePath := createRoute(generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version,
endpoint.Basepath, resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), clusterName, "", nil))
endpoint.Basepath, resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), clusterName, "", nil, false))
assert.NotNil(t, generatedRouteWithXWso2BasePath, "Route should not be null.")
assert.Equal(t, expectedRouteActionWithXWso2BasePath, generatedRouteWithXWso2BasePath.Action,
"Route generation mismatch when xWso2BasePath option is provided.")
Expand All @@ -145,7 +143,7 @@ func TestCreateRoute(t *testing.T) {
"Assigned HTTP Method Regex is incorrect when single method is available.")

generatedRouteWithoutXWso2BasePath := createRoute(generateRouteCreateParamsForUnitTests(title, apiType, vHost, "", version,
endpoint.Basepath, resourceWithGetPost.GetPath(), resourceWithGetPost.GetMethodList(), clusterName, "", nil))
endpoint.Basepath, resourceWithGetPost.GetPath(), resourceWithGetPost.GetMethodList(), clusterName, "", nil, false))
assert.NotNil(t, generatedRouteWithoutXWso2BasePath, "Route should not be null")
assert.NotNil(t, generatedRouteWithoutXWso2BasePath.GetMatch().Headers, "Headers property should not be null")
assert.Equal(t, "^(GET|POST|OPTIONS)$", generatedRouteWithoutXWso2BasePath.GetMatch().Headers[0].GetStringMatch().GetSafeRegex().Regex,
Expand All @@ -154,6 +152,13 @@ func TestCreateRoute(t *testing.T) {
assert.Equal(t, expectedRouteActionWithoutXWso2BasePath, generatedRouteWithoutXWso2BasePath.Action,
"Route generation mismatch when xWso2BasePath option is provided")

context := fmt.Sprintf("%s/%s", xWso2BasePath, version)
generatedRouteWithDefaultVersion := createRoute(generateRouteCreateParamsForUnitTests(title, apiType, vHost, context, version,
endpoint.Basepath, resourceWithGetPost.GetPath(), resourceWithGetPost.GetMethodList(), clusterName, "", nil, true))
assert.NotNil(t, generatedRouteWithDefaultVersion, "Route should not be null")
assert.True(t, strings.HasPrefix(generatedRouteWithDefaultVersion.GetMatch().GetSafeRegex().Regex, fmt.Sprintf("^(%s|%s)", context, xWso2BasePath)),
"Default version basepath is not generated correctly")

}

func TestCreateRouteClusterSpecifier(t *testing.T) {
Expand All @@ -175,21 +180,21 @@ func TestCreateRouteClusterSpecifier(t *testing.T) {
"resource_operation_id", []model.Endpoint{}, []model.Endpoint{})

routeWithProdEp := createRoute(generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version, endpointBasePath,
resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), prodClusterName, "", nil))
resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), prodClusterName, "", nil, false))
assert.NotNil(t, routeWithProdEp, "Route should not be null")
assert.NotNil(t, routeWithProdEp.GetRoute().GetClusterHeader(), "Route Cluster Header should not be null.")
assert.Empty(t, routeWithProdEp.GetRoute().GetCluster(), "Route Cluster Name should be empty.")
assert.Equal(t, clusterHeaderName, routeWithProdEp.GetRoute().GetClusterHeader(), "Route Cluster Name mismatch.")

routeWithSandEp := createRoute(generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version, endpointBasePath,
resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), "", sandClusterName, nil))
resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), "", sandClusterName, nil, false))
assert.NotNil(t, routeWithSandEp, "Route should not be null")
assert.NotNil(t, routeWithSandEp.GetRoute().GetClusterHeader(), "Route Cluster Header should not be null.")
assert.Empty(t, routeWithSandEp.GetRoute().GetCluster(), "Route Cluster Name should be empty.")
assert.Equal(t, clusterHeaderName, routeWithSandEp.GetRoute().GetClusterHeader(), "Route Cluster Name mismatch.")

routeWithProdSandEp := createRoute(generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version, endpointBasePath,
resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), prodClusterName, sandClusterName, nil))
resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), prodClusterName, sandClusterName, nil, false))
assert.NotNil(t, routeWithProdSandEp, "Route should not be null")
assert.NotNil(t, routeWithProdSandEp.GetRoute().GetClusterHeader(), "Route Cluster Header should not be null.")
assert.Empty(t, routeWithProdSandEp.GetRoute().GetCluster(), "Route Cluster Name should be empty.")
Expand All @@ -214,7 +219,7 @@ func TestCreateRouteExtAuthzContext(t *testing.T) {
"resource_operation_id", []model.Endpoint{}, []model.Endpoint{})

routeWithProdEp := createRoute(generateRouteCreateParamsForUnitTests(title, apiType, vHost, xWso2BasePath, version,
endpointBasePath, resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), prodClusterName, sandClusterName, nil))
endpointBasePath, resourceWithGet.GetPath(), resourceWithGet.GetMethodList(), prodClusterName, sandClusterName, nil, false))
assert.NotNil(t, routeWithProdEp, "Route should not be null")
assert.NotNil(t, routeWithProdEp.GetTypedPerFilterConfig(), "TypedPerFilter config should not be null")
assert.NotNil(t, routeWithProdEp.GetTypedPerFilterConfig()[wellknown.HTTPExternalAuthorization],
Expand Down Expand Up @@ -487,18 +492,18 @@ func TestGetCorsPolicy(t *testing.T) {

// Route without CORS configuration
routeWithoutCors := createRoute(generateRouteCreateParamsForUnitTests("test", "HTTP", "localhost", "/test", "1.0.0", "/test",
"/testPath", []string{"GET"}, "test-cluster", "", nil))
"/testPath", []string{"GET"}, "test-cluster", "", nil, false))
assert.Nil(t, routeWithoutCors.GetRoute().Cors, "Cors Configuration should be null.")

// Route with CORS configuration
routeWithCors := createRoute(generateRouteCreateParamsForUnitTests("test", "HTTP", "localhost", "/test", "1.0.0", "/test",
"/testPath", []string{"GET"}, "test-cluster", "", corsConfigModel3))
"/testPath", []string{"GET"}, "test-cluster", "", corsConfigModel3, false))
assert.NotNil(t, routeWithCors.GetRoute().Cors, "Cors Configuration should not be null.")
}

func generateRouteCreateParamsForUnitTests(title string, apiType string, vhost string, xWso2Basepath string, version string, endpointBasepath string,
resourcePathParam string, resourceMethods []string, prodClusterName string, sandClusterName string,
corsConfig *model.CorsConfig) *routeCreateParams {
corsConfig *model.CorsConfig, isDefaultVersion bool) *routeCreateParams {
return &routeCreateParams{
title: title,
apiType: apiType,
Expand All @@ -511,5 +516,6 @@ func generateRouteCreateParamsForUnitTests(title string, apiType string, vhost s
corsPolicy: corsConfig,
resourcePathParam: resourcePathParam,
resourceMethods: resourceMethods,
isDefaultVersion: isDefaultVersion,
}
}
1 change: 1 addition & 0 deletions adapter/internal/oasparser/envoyconf/internal_dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ type routeCreateParams struct {
rewritePath string
rewriteMethod bool
passRequestPayloadToEnforcer bool
isDefaultVersion bool
}
6 changes: 3 additions & 3 deletions adapter/internal/oasparser/envoyconf/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ func testCreateRoutesForUnitTests(t *testing.T) []*routev3.Route {
}

route1 := createRoute(generateRouteCreateParamsForUnitTests("test", "HTTP", "localhost", "/test", "1.0.0", "/test",
"/testPath", []string{"GET"}, "test-cluster", "", corsConfigModel3))
"/testPath", []string{"GET"}, "test-cluster", "", corsConfigModel3, false))
route2 := createRoute(generateRouteCreateParamsForUnitTests("test", "HTTP", "localhost", "/test", "1.0.0", "/test",
"/testPath", []string{"POST"}, "test-cluster", "", corsConfigModel3))
"/testPath", []string{"POST"}, "test-cluster", "", corsConfigModel3, false))
route3 := createRoute(generateRouteCreateParamsForUnitTests("test", "HTTP", "localhost", "/test", "1.0.0", "/test",
"/testPath", []string{"PUT"}, "test-cluster", "", corsConfigModel3))
"/testPath", []string{"PUT"}, "test-cluster", "", corsConfigModel3, false))

routes := []*routev3.Route{route1, route2, route3}

Expand Down
Loading

0 comments on commit 9ad3eee

Please sign in to comment.