From 34b9b4e0897db7a3801a34c40d249c57a4fc55d7 Mon Sep 17 00:00:00 2001 From: AmaliMatharaarachchi Date: Thu, 28 Mar 2024 14:46:29 +0530 Subject: [PATCH] add simantic versioning support --- .../discovery/xds/semantic_versioning.go | 283 +++++++++----- .../discovery/xds/semantic_versioning_test.go | 209 ++++------ adapter/internal/discovery/xds/server.go | 61 ++- adapter/internal/discovery/xds/server_test.go | 362 +++++++++++++++--- adapter/internal/logging/logging_constant.go | 1 + .../internal/oasparser/config_generator.go | 16 +- .../envoyconf/routes_with_clusters.go | 6 +- .../envoyconf/routes_with_clusters_test.go | 18 +- .../internal/operator/synchronizer/gql_api.go | 61 +-- .../operator/synchronizer/rest_api.go | 69 ++-- .../operator/synchronizer/synchronizer.go | 9 +- 11 files changed, 678 insertions(+), 417 deletions(-) diff --git a/adapter/internal/discovery/xds/semantic_versioning.go b/adapter/internal/discovery/xds/semantic_versioning.go index 97384b388..cdf9d658b 100644 --- a/adapter/internal/discovery/xds/semantic_versioning.go +++ b/adapter/internal/discovery/xds/semantic_versioning.go @@ -21,13 +21,21 @@ import ( "strings" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" - envoy_type_matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" + matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" "github.com/wso2/apk/adapter/config" logger "github.com/wso2/apk/adapter/internal/loggers" logging "github.com/wso2/apk/adapter/internal/logging" + "github.com/wso2/apk/adapter/internal/oasparser/model" semantic_version "github.com/wso2/apk/adapter/pkg/semanticversion" ) +type oldSemVersion struct { + Vhost string + APIName string + OldMajorSemVersion *semantic_version.SemVersion + OldMinorSemVersion *semantic_version.SemVersion +} + // GetVersionMatchRegex returns the regex to match the full version string func GetVersionMatchRegex(version string) string { // Match "." character in the version by replacing it with "\\." @@ -35,7 +43,7 @@ func GetVersionMatchRegex(version string) string { } // GetMajorMinorVersionRangeRegex generates major and minor version compatible range regex for the given version -func GetMajorMinorVersionRangeRegex(semVersion semantic_version.SemVersion) string { +func GetMajorMinorVersionRangeRegex(semVersion *semantic_version.SemVersion) string { majorVersion := strconv.Itoa(semVersion.Major) minorVersion := strconv.Itoa(semVersion.Minor) if semVersion.Patch == nil { @@ -46,7 +54,7 @@ func GetMajorMinorVersionRangeRegex(semVersion semantic_version.SemVersion) stri } // GetMinorVersionRangeRegex generates minor version compatible range regex for the given version -func GetMinorVersionRangeRegex(semVersion semantic_version.SemVersion) string { +func GetMinorVersionRangeRegex(semVersion *semantic_version.SemVersion) string { if semVersion.Patch == nil { return GetVersionMatchRegex(semVersion.Version) } @@ -66,120 +74,133 @@ func GetMinorVersionRange(semVersion semantic_version.SemVersion) string { return "v" + strconv.Itoa(semVersion.Major) + "." + strconv.Itoa(semVersion.Minor) } -func updateRoutingRulesOnAPIUpdate(organizationID, apiIdentifier, apiName, apiVersion, vHost string) { - apiSemVersion, err := semantic_version.ValidateAndGetVersionComponents(apiVersion) - // If the version validation is not success, we just proceed without intelligent version - // Valid version pattern: vx.y.z or vx.y where x, y and z are non-negative integers and v is a prefix - if err != nil && apiSemVersion == nil { - return +// updateSemanticVersioningInMapForUpdateAPI updates the latest version ranges of the APIs in the organization +func updateSemanticVersioningInMapForUpdateAPI(org string, apiRangeIdentifiers map[string]struct{}, + adapterInternalAPI *model.AdapterInternalAPI) { + oldSemVersions := make([]oldSemVersion, 0) + if _, exist := orgIDLatestAPIVersionMap[org]; !exist { + orgIDLatestAPIVersionMap[org] = make(map[string]map[string]semantic_version.SemVersion) } + semVersion, _ := semantic_version.ValidateAndGetVersionComponents(adapterInternalAPI.GetVersion()) - apiRangeIdentifier := generateIdentifierForAPIWithoutVersion(vHost, apiName) - // Check the major and minor version ranges of the current API - existingMajorRangeLatestSemVersion, isMajorRangeRegexAvailable := - orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier][GetMajorVersionRange(*apiSemVersion)] - existingMinorRangeLatestSemVersion, isMinorRangeRegexAvailable := - orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier][GetMinorVersionRange(*apiSemVersion)] - - // Check whether the current API is the latest version in the major and minor version ranges - isLatestMajorVersion := !isMajorRangeRegexAvailable || existingMajorRangeLatestSemVersion.Compare(*apiSemVersion) - isLatestMinorVersion := !isMinorRangeRegexAvailable || existingMinorRangeLatestSemVersion.Compare(*apiSemVersion) - - // Remove the existing regexes from the path specifier when latest major and/or minor version is available - if (isMajorRangeRegexAvailable || isMinorRangeRegexAvailable) && (isLatestMajorVersion || isLatestMinorVersion) { - // Organization's all apis - for vuuid, envoyInternalAPI := range orgAPIMap[organizationID] { - // API's all versions in the same vHost - if envoyInternalAPI.adapterInternalAPI.GetTitle() == apiName && strings.HasPrefix(vuuid+":", vHost) { - - if (isMajorRangeRegexAvailable && envoyInternalAPI.adapterInternalAPI.GetVersion() == existingMajorRangeLatestSemVersion.Version) || - (isMinorRangeRegexAvailable && envoyInternalAPI.adapterInternalAPI.GetVersion() == existingMinorRangeLatestSemVersion.Version) { - - for _, route := range envoyInternalAPI.routes { - regex := route.GetMatch().GetSafeRegex().GetRegex() - regexRewritePattern := route.GetRoute().GetRegexRewrite().GetPattern().GetRegex() - existingMinorRangeLatestVersionRegex := GetVersionMatchRegex(existingMinorRangeLatestSemVersion.Version) - existingMajorRangeLatestVersionRegex := GetVersionMatchRegex(existingMajorRangeLatestSemVersion.Version) - if isMinorRangeRegexAvailable && envoyInternalAPI.adapterInternalAPI.GetVersion() == existingMinorRangeLatestSemVersion.Version && isLatestMinorVersion { - regex = strings.Replace(regex, GetMinorVersionRangeRegex(existingMinorRangeLatestSemVersion), existingMinorRangeLatestVersionRegex, 1) - regex = strings.Replace(regex, GetMajorMinorVersionRangeRegex(existingMajorRangeLatestSemVersion), existingMajorRangeLatestVersionRegex, 1) - regexRewritePattern = strings.Replace(regexRewritePattern, GetMinorVersionRangeRegex(existingMinorRangeLatestSemVersion), existingMinorRangeLatestVersionRegex, 1) - regexRewritePattern = strings.Replace(regexRewritePattern, GetMajorMinorVersionRangeRegex(existingMajorRangeLatestSemVersion), existingMajorRangeLatestVersionRegex, 1) - } - if isMajorRangeRegexAvailable && envoyInternalAPI.adapterInternalAPI.GetVersion() == existingMajorRangeLatestSemVersion.Version && isLatestMajorVersion { - regex = strings.Replace(regex, GetMajorMinorVersionRangeRegex(existingMajorRangeLatestSemVersion), GetMinorVersionRangeRegex(existingMajorRangeLatestSemVersion), 1) - regexRewritePattern = strings.Replace(regexRewritePattern, GetMajorMinorVersionRangeRegex(existingMajorRangeLatestSemVersion), GetMinorVersionRangeRegex(existingMajorRangeLatestSemVersion), 1) - } - pathSpecifier := &routev3.RouteMatch_SafeRegex{ - SafeRegex: &envoy_type_matcherv3.RegexMatcher{ - Regex: regex, - }, - } - route.Match.PathSpecifier = pathSpecifier - action := route.Action.(*routev3.Route_Route) - action.Route.RegexRewrite.Pattern.Regex = regexRewritePattern - route.Action = action + for apiRangeIdentifier := range apiRangeIdentifiers { + if currentAPISemVersion, exist := orgIDLatestAPIVersionMap[org][apiRangeIdentifier]; !exist { + orgIDLatestAPIVersionMap[org][apiRangeIdentifier] = make(map[string]semantic_version.SemVersion) + orgIDLatestAPIVersionMap[org][apiRangeIdentifier][GetMajorVersionRange(*semVersion)] = *semVersion + orgIDLatestAPIVersionMap[org][apiRangeIdentifier][GetMinorVersionRange(*semVersion)] = *semVersion + } else { + var oldVersion *oldSemVersion + vhost, _ := ExtractVhostFromAPIIdentifier(apiRangeIdentifier) + if _, ok := currentAPISemVersion[GetMajorVersionRange(*semVersion)]; !ok { + currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion + } else if currentAPISemVersion[GetMajorVersionRange(*semVersion)].Compare(*semVersion) { + version := currentAPISemVersion[GetMajorVersionRange(*semVersion)] + currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion + oldVersion = &oldSemVersion{ + Vhost: vhost, + APIName: adapterInternalAPI.GetTitle(), + OldMajorSemVersion: &version, + } + } + if _, ok := currentAPISemVersion[GetMinorVersionRange(*semVersion)]; !ok { + currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion + } else if currentAPISemVersion[GetMinorVersionRange(*semVersion)].Compare(*semVersion) { + version := currentAPISemVersion[GetMinorVersionRange(*semVersion)] + currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion + if oldVersion != nil { + oldVersion.OldMinorSemVersion = &version + } else { + oldVersion = &oldSemVersion{ + Vhost: vhost, + APIName: adapterInternalAPI.GetTitle(), + OldMinorSemVersion: &version, } } } + if oldVersion != nil { + oldSemVersions = append(oldSemVersions, *oldVersion) + } } } + updateOldRegex(org, oldSemVersions) +} - if isLatestMajorVersion || isLatestMinorVersion { - // Update local memory map with the latest version ranges - majorVersionRange := GetMajorVersionRange(*apiSemVersion) - minorVersionRange := GetMinorVersionRange(*apiSemVersion) - if _, orgExists := orgIDLatestAPIVersionMap[organizationID]; !orgExists { - orgIDLatestAPIVersionMap[organizationID] = make(map[string]map[string]semantic_version.SemVersion) - } - if _, apiRangeExists := orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier]; !apiRangeExists { - orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier] = make(map[string]semantic_version.SemVersion) +func updateOldRegex(org string, oldSemVersions []oldSemVersion) { + if len(oldSemVersions) < 1 { + return + } + for vuuid, api := range orgAPIMap[org] { + // get vhost from the api identifier + vhost, _ := ExtractVhostFromAPIIdentifier(vuuid) + var oldSelectedSemVersion *oldSemVersion + for _, oldSemVersion := range oldSemVersions { + if oldSemVersion.Vhost == vhost && oldSemVersion.APIName == api.adapterInternalAPI.GetTitle() { + if oldSemVersion.OldMajorSemVersion != nil && oldSemVersion.OldMajorSemVersion.Version == + api.adapterInternalAPI.GetVersion() { + oldSelectedSemVersion = &oldSemVersion + break + } + if oldSemVersion.OldMinorSemVersion != nil && + oldSemVersion.OldMinorSemVersion.Version == api.adapterInternalAPI.GetVersion() { + oldSelectedSemVersion = &oldSemVersion + break + } + } } - - latestVersions := orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier] - latestVersions[minorVersionRange] = *apiSemVersion - if isLatestMajorVersion { - latestVersions[majorVersionRange] = *apiSemVersion + logger.LoggerAPI.Error(oldSelectedSemVersion) + if oldSelectedSemVersion == nil { + continue } - // Add the major and/or minor version range matching regexes to the path specifier when - // latest major and/or minor version is available - apiRoutes := getRoutesForAPIIdentifier(organizationID, apiIdentifier) + updateMajor := oldSelectedSemVersion.OldMajorSemVersion != nil && api.adapterInternalAPI.GetVersion() == + oldSelectedSemVersion.OldMajorSemVersion.Version + updateMinor := oldSelectedSemVersion.OldMinorSemVersion != nil && api.adapterInternalAPI.GetVersion() == + oldSelectedSemVersion.OldMinorSemVersion.Version - for _, route := range apiRoutes { + // apiSemVersion, _ := semantic_version.ValidateAndGetVersionComponents(api.adapterInternalAPI.GetVersion()) + for _, route := range api.routes { regex := route.GetMatch().GetSafeRegex().GetRegex() regexRewritePattern := route.GetRoute().GetRegexRewrite().GetPattern().GetRegex() - apiVersionRegex := GetVersionMatchRegex(apiVersion) - - if isLatestMajorVersion { - regex = strings.Replace(regex, apiVersionRegex, GetMajorMinorVersionRangeRegex(*apiSemVersion), 1) - regexRewritePattern = strings.Replace(regexRewritePattern, apiVersionRegex, GetMajorMinorVersionRangeRegex(*apiSemVersion), 1) - } else if isLatestMinorVersion { - regex = strings.Replace(regex, apiVersionRegex, GetMinorVersionRangeRegex(*apiSemVersion), 1) - regexRewritePattern = strings.Replace(regexRewritePattern, apiVersionRegex, GetMinorVersionRangeRegex(*apiSemVersion), 1) + if updateMajor { + logger.LoggerAPI.Error(166, regex) + regex = strings.Replace(regex, GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, + GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + logger.LoggerAPI.Error(166, regex) + } + if updateMinor { + logger.LoggerAPI.Error(175, regex) + regex = strings.Replace(regex, GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMinorSemVersion), + GetVersionMatchRegex(oldSelectedSemVersion.OldMinorSemVersion.Version), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMinorSemVersion), + GetVersionMatchRegex(oldSelectedSemVersion.OldMinorSemVersion.Version), 1) + logger.LoggerAPI.Error(180, regex) } pathSpecifier := &routev3.RouteMatch_SafeRegex{ - SafeRegex: &envoy_type_matcherv3.RegexMatcher{ + SafeRegex: &matcherv3.RegexMatcher{ Regex: regex, }, } - route.Match.PathSpecifier = pathSpecifier action := route.Action.(*routev3.Route_Route) action.Route.RegexRewrite.Pattern.Regex = regexRewritePattern route.Action = action } - } } -func updateSemanticVersioning(org string, apiRangeIdentifiers map[string]struct{}) { +// updateSemanticVersioningInMap updates the latest version ranges of the APIs in the organization +func updateSemanticVersioningInMap(org string, apiRangeIdentifiers map[string]struct{}) { + oldSemVersions := make([]oldSemVersion, 0) // Iterate all the APIs in the API range for vuuid, api := range orgAPIMap[org] { // get vhost from the api identifier vhost, _ := ExtractVhostFromAPIIdentifier(vuuid) apiName := api.adapterInternalAPI.GetTitle() - apiRangeIdentifier := generateIdentifierForAPIWithoutVersion(vhost, apiName) + apiRangeIdentifier := GenerateIdentifierForAPIWithoutVersion(vhost, apiName) if _, ok := apiRangeIdentifiers[apiRangeIdentifier]; !ok { continue } @@ -191,53 +212,109 @@ func updateSemanticVersioning(org string, apiRangeIdentifiers map[string]struct{ vuuid, org, err)) continue } + if _, exist := orgIDLatestAPIVersionMap[org]; !exist { + orgIDLatestAPIVersionMap[org] = make(map[string]map[string]semantic_version.SemVersion) + } if currentAPISemVersion, exist := orgIDLatestAPIVersionMap[org][apiRangeIdentifier]; !exist { orgIDLatestAPIVersionMap[org][apiRangeIdentifier] = make(map[string]semantic_version.SemVersion) orgIDLatestAPIVersionMap[org][apiRangeIdentifier][GetMajorVersionRange(*semVersion)] = *semVersion orgIDLatestAPIVersionMap[org][apiRangeIdentifier][GetMinorVersionRange(*semVersion)] = *semVersion - } else { + var oldVersion *oldSemVersion if _, ok := currentAPISemVersion[GetMajorVersionRange(*semVersion)]; !ok { currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion - } else { - if currentAPISemVersion[GetMajorVersionRange(*semVersion)].Compare(*semVersion) { - currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion + } else if currentAPISemVersion[GetMajorVersionRange(*semVersion)].Compare(*semVersion) { + version := currentAPISemVersion[GetMajorVersionRange(*semVersion)] + oldVersion = &oldSemVersion{ + Vhost: vhost, + APIName: apiName, + OldMajorSemVersion: &version, } + currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion } if _, ok := currentAPISemVersion[GetMinorVersionRange(*semVersion)]; !ok { currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion - } else { - if currentAPISemVersion[GetMinorVersionRange(*semVersion)].Compare(*semVersion) { - currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion + } else if currentAPISemVersion[GetMinorVersionRange(*semVersion)].Compare(*semVersion) { + version := currentAPISemVersion[GetMinorVersionRange(*semVersion)] + if oldVersion != nil { + oldVersion.OldMinorSemVersion = &version + } else { + oldVersion = &oldSemVersion{ + Vhost: vhost, + APIName: apiName, + OldMinorSemVersion: &version, + } } + currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion + } + if oldVersion != nil { + oldSemVersions = append(oldSemVersions, *oldVersion) } } } + updateOldRegex(org, oldSemVersions) } -func getRoutesForAPIIdentifier(organizationID, apiIdentifier string) []*routev3.Route { +func updateSemRegexForNewAPI(adapterInternalAPI model.AdapterInternalAPI, routes []*routev3.Route, vhost string) { + apiIdentifier := GenerateIdentifierForAPIWithoutVersion(vhost, adapterInternalAPI.GetTitle()) - var routes []*routev3.Route - if _, ok := orgAPIMap[organizationID]; ok { - if _, ok := orgAPIMap[organizationID][apiIdentifier]; ok { - routes = orgAPIMap[organizationID][apiIdentifier].routes + if orgIDLatestAPIVersionMap[adapterInternalAPI.GetOrganizationID()] != nil && + orgIDLatestAPIVersionMap[adapterInternalAPI.GetOrganizationID()][apiIdentifier] != nil { + // get version list for the API + apiVersionMap := orgIDLatestAPIVersionMap[adapterInternalAPI.GetOrganizationID()][apiIdentifier] + // get the latest version of the API + isMajorVersion := false + update := false + for versionRange, latestVersion := range apiVersionMap { + if latestVersion.Version == adapterInternalAPI.GetVersion() { + update = true + versionArray := strings.Split(versionRange, ".") + if len(versionArray) == 1 { + isMajorVersion = true + break + } + } } - } + if update { + // update regex + for _, route := range routes { + regex := route.GetMatch().GetSafeRegex().GetRegex() + regexRewritePattern := route.GetRoute().GetRegexRewrite().GetPattern().GetRegex() + apiVersionRegex := GetVersionMatchRegex(adapterInternalAPI.GetVersion()) + apiSemVersion, _ := semantic_version.ValidateAndGetVersionComponents(adapterInternalAPI.GetVersion()) + if isMajorVersion { + regex = strings.Replace(regex, apiVersionRegex, GetMajorMinorVersionRangeRegex(apiSemVersion), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, apiVersionRegex, GetMajorMinorVersionRangeRegex(apiSemVersion), 1) + } else { + regex = strings.Replace(regex, apiVersionRegex, GetMinorVersionRangeRegex(apiSemVersion), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, apiVersionRegex, GetMinorVersionRangeRegex(apiSemVersion), 1) + } + pathSpecifier := &routev3.RouteMatch_SafeRegex{ + SafeRegex: &matcherv3.RegexMatcher{ + Regex: regex, + }, + } - return routes + route.Match.PathSpecifier = pathSpecifier + action := route.Action.(*routev3.Route_Route) + action.Route.RegexRewrite.Pattern.Regex = regexRewritePattern + route.Action = action + } + } + } } -func isSemanticVersioningEnabled(apiName, apiVersion string) bool { - +// IsSemanticVersioningEnabled checks whether semantic versioning is enabled for the given API +func IsSemanticVersioningEnabled(apiName, apiVersion string) bool { conf := config.ReadConfigs() if !conf.Envoy.EnableIntelligentRouting { return false } apiSemVersion, err := semantic_version.ValidateAndGetVersionComponents(apiVersion) - if err != nil && apiSemVersion == nil { + if err != nil || apiSemVersion == nil { logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1411, logging.MAJOR, - "Error validating the version of the API: %v. Intelligent routing is disabled for the API", apiName)) + "Error validating the version of the API: %v:%s. Intelligent routing is disabled for the API, %v", apiName, apiVersion, err)) return false } diff --git a/adapter/internal/discovery/xds/semantic_versioning_test.go b/adapter/internal/discovery/xds/semantic_versioning_test.go index 939a84056..41cfe1697 100644 --- a/adapter/internal/discovery/xds/semantic_versioning_test.go +++ b/adapter/internal/discovery/xds/semantic_versioning_test.go @@ -18,7 +18,6 @@ package xds import ( - "reflect" "regexp" "testing" @@ -75,22 +74,22 @@ func TestGetVersionMatchRegex(t *testing.T) { func TestGetMajorMinorVersionRangeRegex(t *testing.T) { tests := []struct { name string - semVersion semantic_version.SemVersion + semVersion *semantic_version.SemVersion expectedResult string }{ { name: "Major and minor version only", - semVersion: semantic_version.SemVersion{Major: 1, Minor: 2}, + semVersion: &semantic_version.SemVersion{Major: 1, Minor: 2}, expectedResult: "v1(?:\\.2)?", }, { name: "Major, minor, and patch version", - semVersion: semantic_version.SemVersion{Major: 1, Minor: 2, Patch: PtrInt(3)}, + semVersion: &semantic_version.SemVersion{Major: 1, Minor: 2, Patch: PtrInt(3)}, expectedResult: "v1(?:\\.2(?:\\.3)?)?", }, { name: "Major version only", - semVersion: semantic_version.SemVersion{Major: 1}, + semVersion: &semantic_version.SemVersion{Major: 1}, expectedResult: "v1(?:\\.0)?", }, } @@ -109,22 +108,22 @@ func TestGetMajorMinorVersionRangeRegex(t *testing.T) { func TestGetMinorVersionRangeRegex(t *testing.T) { tests := []struct { name string - semVersion semantic_version.SemVersion + semVersion *semantic_version.SemVersion expectedResult string }{ { name: "Major, minor, and patch version", - semVersion: semantic_version.SemVersion{Version: "v1.2.3", Major: 1, Minor: 2, Patch: PtrInt(3)}, + semVersion: &semantic_version.SemVersion{Version: "v1.2.3", Major: 1, Minor: 2, Patch: PtrInt(3)}, expectedResult: "v1\\.2(?:\\.3)?", }, { name: "Major and minor version only", - semVersion: semantic_version.SemVersion{Version: "v1.2", Major: 1, Minor: 2}, + semVersion: &semantic_version.SemVersion{Version: "v1.2", Major: 1, Minor: 2}, expectedResult: "v1\\.2", }, { name: "Major version only", - semVersion: semantic_version.SemVersion{Version: "v1", Major: 1}, + semVersion: &semantic_version.SemVersion{Version: "v1", Major: 1}, expectedResult: "v1", }, } @@ -255,7 +254,7 @@ func TestIsSemanticVersioningEnabled(t *testing.T) { t.Run(tt.name, func(t *testing.T) { conf.Envoy.EnableIntelligentRouting = tt.intelligentRoutingEnabled - result := isSemanticVersioningEnabled(tt.apiName, tt.apiVersion) + result := IsSemanticVersioningEnabled(tt.apiName, tt.apiVersion) if result != tt.expectedResult { t.Errorf("Expected result: %v, Got: %v", tt.expectedResult, result) @@ -264,108 +263,27 @@ func TestIsSemanticVersioningEnabled(t *testing.T) { } } -func TestGetRoutesForAPIIdentifier(t *testing.T) { - - orgAPIMap = map[string]map[string]*EnvoyInternalAPI{ - "org1": { - "gw.com:apiID1": &EnvoyInternalAPI{ - routes: []*routev3.Route{ - { - Name: "route1", - }, - { - Name: "route2", - }, - }, - }, - "gw.com:apiID2": &EnvoyInternalAPI{ - routes: []*routev3.Route{ - { - Name: "route3", - }, - }, - }, - }, - "org2": { - "test.gw.com:apiID1": &EnvoyInternalAPI{ - routes: []*routev3.Route{ - { - Name: "route4", - }, - }, - }, - }, - } - - tests := []struct { - name string - organizationID string - apiIdentifier string - expectedRoutes []*routev3.Route - expectedNumRoute int - }{ - { - name: "Existing organization and API identifier", - organizationID: "org1", - apiIdentifier: "gw.com:apiID1", - expectedRoutes: []*routev3.Route{ - { - Name: "route1", - }, - { - Name: "route2", - }, - }, - expectedNumRoute: 2, - }, - { - name: "Non-existing organization", - organizationID: "org3", - apiIdentifier: "dev.gw.com:apiID1", - expectedRoutes: []*routev3.Route{}, - expectedNumRoute: 0, - }, - { - name: "Non-existing API identifier", - organizationID: "org1", - apiIdentifier: "api3", - expectedRoutes: []*routev3.Route{}, - expectedNumRoute: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := getRoutesForAPIIdentifier(tt.organizationID, tt.apiIdentifier) - - if len(result) != tt.expectedNumRoute { - t.Errorf("Expected number of routes: %d, Got: %d", tt.expectedNumRoute, len(result)) - } - - if len(result) > 0 { - if !reflect.DeepEqual(result, tt.expectedRoutes) { - t.Errorf("Expected routes: %v, Got: %v", tt.expectedRoutes, result) - } - } - }) - } -} - func TestUpdateRoutingRulesOnAPIUpdate(t *testing.T) { var apiID1 model.AdapterInternalAPI apiID1.SetName("Test API") + apiID1.UUID = "apiID1" + apiID1.OrganizationID = "org1" apiID1.SetVersion("v1.0") apiID1ResourcePath := "^/test-api/v1\\.0/orders([/]{0,1})" var apiID2 model.AdapterInternalAPI apiID2.SetName("Mock API") + apiID2.UUID = "apiID2" + apiID2.OrganizationID = "org1" apiID2.SetVersion("v1.1") apiID2ResourcePath := "^/mock-api/v1\\.1/orders([/]{0,1})" var apiID3 model.AdapterInternalAPI apiID3.SetName("Test API") apiID3.SetVersion("v1.1") + apiID3.OrganizationID = "org1" + apiID3.UUID = "apiID3" apiID3ResourcePath := "^/test-api/v1\\.1/orders([/]{0,1})" orgAPIMap = map[string]map[string]*EnvoyInternalAPI{ @@ -386,59 +304,61 @@ func TestUpdateRoutingRulesOnAPIUpdate(t *testing.T) { } tests := []struct { - name string - organizationID string - apiIdentifier string - apiName string - apiVersion string - vHost string - expectedRegex string - expectedRewrite string - finalRegex string - finalRewrite string + name string + api model.AdapterInternalAPI + organizationID string + apiRangeIdentifier string + apiIdentifier string + vhost string + expectedRegex string + expectedRewrite string + finalRegex string + finalRewrite string }{ { - name: "Create an API with major version", - organizationID: "org1", - apiIdentifier: "gw.com:apiID1", - apiName: "Test API", - apiVersion: "v1.0", - vHost: "gw.com", - expectedRegex: "^/test-api/v1(?:\\.0)?/orders([/]{0,1})", - expectedRewrite: "^/test-api/v1(?:\\.0)?/orders([/]{0,1})", - finalRegex: apiID1ResourcePath, - finalRewrite: apiID1ResourcePath, - }, + name: "Create an API with major version", + organizationID: "org1", + apiRangeIdentifier: "gw.com:Test API", + apiIdentifier: "gw.com:apiID1", + vhost: "gw.com", + api: apiID1, + expectedRegex: "^/test-api/v1(?:\\.0)?/orders([/]{0,1})", + expectedRewrite: "^/test-api/v1(?:\\.0)?/orders([/]{0,1})", + finalRegex: apiID1ResourcePath, + finalRewrite: apiID1ResourcePath, + }, + // Expected final regex: ^/test-api/v1\.0/orders([/]{0,1}), Got: ^/test-api/v1(?:\.0)?/orders([/]{0,1}) { - name: "Create an API with major and minor version", - organizationID: "org1", - apiIdentifier: "gw.com:apiID2", - apiName: "Mock API", - apiVersion: "v1.1", - vHost: "gw.com", - expectedRegex: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", - expectedRewrite: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", - finalRegex: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", - finalRewrite: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", + name: "Create an API with major and minor version", + organizationID: "org1", + apiRangeIdentifier: "gw.com:Mock API", + apiIdentifier: "gw.com:apiID2", + vhost: "gw.com", + api: apiID2, + expectedRegex: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", + expectedRewrite: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", + finalRegex: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", + finalRewrite: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", }, { - name: "Create an API with major and minor version", - organizationID: "org1", - apiIdentifier: "gw.com:apiID3", - apiName: "Test API", - apiVersion: "v1.1", - vHost: "gw.com", - expectedRegex: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", - expectedRewrite: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", - finalRegex: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", - finalRewrite: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", + name: "Create an API with major and minor version", + organizationID: "org1", + apiRangeIdentifier: "gw.com:Test API", + apiIdentifier: "gw.com:apiID3", + vhost: "gw.com", + api: apiID3, + expectedRegex: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", + expectedRewrite: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", + finalRegex: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", + finalRewrite: "^/test-api/v1(?:\\.1)?/orders([/]{0,1})", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - updateRoutingRulesOnAPIUpdate(tt.organizationID, tt.apiIdentifier, tt.apiName, tt.apiVersion, tt.vHost) + updateSemanticVersioningInMapForUpdateAPI(tt.organizationID, + map[string]struct{}{tt.apiRangeIdentifier: {}}, &tt.api) + updateSemRegexForNewAPI(tt.api, orgAPIMap[tt.organizationID][tt.apiIdentifier].routes, tt.vhost) api1 := orgAPIMap[tt.organizationID][tt.apiIdentifier] routes := api1.routes @@ -539,16 +459,19 @@ func TestUpdateRoutingRulesOnAPIDelete(t *testing.T) { var apiID1 model.AdapterInternalAPI apiID1.SetName("Test API") + apiID1.UUID = "apiID1" apiID1.SetVersion("v1.0") apiID1ResourcePath := "^/test-api/v1\\.0/orders([/]{0,1})" var apiID2 model.AdapterInternalAPI apiID2.SetName("Mock API") + apiID2.UUID = "apiID2" apiID2.SetVersion("v1.0") apiID2ResourcePath := "^/mock-api/v1\\.0/orders([/]{0,1})" var apiID3 model.AdapterInternalAPI apiID3.SetName("Mock API") + apiID3.UUID = "apiID3" apiID3.SetVersion("v1.5") apiID3ResourcePath := "^/mock-api/v1(?:\\.5)?/orders([/]{0,1})" @@ -581,14 +504,14 @@ func TestUpdateRoutingRulesOnAPIDelete(t *testing.T) { { name: "Delete latest major version", organizationID: "org3", - apiIdentifier: "gw.com:apiID1", + apiIdentifier: "gw.com:Test API", api: &apiID1, deleteVersion: "v1.0", }, { name: "Delete latest minor version", organizationID: "org4", - apiIdentifier: "gw.com:apiID3", + apiIdentifier: "gw.com:Mock API", api: &apiID3, deleteVersion: "v1.5", }, @@ -596,7 +519,7 @@ func TestUpdateRoutingRulesOnAPIDelete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - updateSemanticVersioning(tt.organizationID, map[string]struct{}{tt.apiIdentifier: {}}) + RemoveAPIFromAllInternalMaps(tt.api.UUID) if _, ok := orgIDLatestAPIVersionMap[tt.organizationID]; ok { if _, ok := orgIDLatestAPIVersionMap[tt.organizationID][tt.apiIdentifier]; ok { if _, ok := orgIDLatestAPIVersionMap[tt.organizationID][tt.apiIdentifier][tt.deleteVersion]; ok { diff --git a/adapter/internal/discovery/xds/server.go b/adapter/internal/discovery/xds/server.go index bf6afe4fe..f0f120c52 100644 --- a/adapter/internal/discovery/xds/server.go +++ b/adapter/internal/discovery/xds/server.go @@ -298,8 +298,11 @@ func GenerateEnvoyResoucesForGateway(gatewayName string) ([]types.Resource, envoyGatewayConfig, gwFound := gatewayLabelConfigMap[gatewayName] // gwFound means that the gateway is configured in the envoy config. + if !gwFound { + return nil, nil, nil, nil, nil + } listeners := envoyGatewayConfig.listeners - if !gwFound || listeners == nil || len(listeners) == 0 { + if len(listeners) < 1 { return nil, nil, nil, nil, nil } @@ -511,7 +514,7 @@ func GenerateHashedAPINameVersionIDWithoutVhost(name, version string) string { } // GenerateIdentifierForAPIWithoutVersion generates an identifier unique to the API despite of the version -func generateIdentifierForAPIWithoutVersion(vhost, name string) string { +func GenerateIdentifierForAPIWithoutVersion(vhost, name string) string { return fmt.Sprint(vhost, apiKeyFieldSeparator, name) } @@ -541,6 +544,32 @@ func ExtractUUIDFromAPIIdentifier(id string) (string, error) { return "", err } +// PopulateInternalMaps populates the internal maps from the GQLRoute. +func PopulateInternalMaps(adapterInternalAPI *model.AdapterInternalAPI, labels, vHosts map[string]struct{}, sectionName, listenerName string) error { + mutexForInternalMapUpdate.Lock() + defer mutexForInternalMapUpdate.Unlock() + apiVersion := adapterInternalAPI.GetVersion() + apiName := adapterInternalAPI.GetTitle() + if IsSemanticVersioningEnabled(apiName, apiVersion) { + logger.LoggerXds.Debugf("Semantic versioning is enabled for API: %v", apiName) + tobeUpdatedAPIRangeIdentifiers := make(map[string]struct{}) + for vHost := range vHosts { + tobeUpdatedAPIRangeIdentifiers[GenerateIdentifierForAPIWithoutVersion(vHost, apiName)] = struct{}{} + } + logger.LoggerXds.Errorf("Updating semantic versioning for API: %v", tobeUpdatedAPIRangeIdentifiers) + updateSemanticVersioningInMapForUpdateAPI(adapterInternalAPI.OrganizationID, tobeUpdatedAPIRangeIdentifiers, adapterInternalAPI) + } + + err := UpdateOrgAPIMap(vHosts, labels, listenerName, sectionName, adapterInternalAPI) + if err != nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1415, logging.MAJOR, + "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", + adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) + return err + } + return nil +} + // RemoveAPIFromAllInternalMaps removes api from all maps func RemoveAPIFromAllInternalMaps(uuid string) map[string]struct{} { mutexForInternalMapUpdate.Lock() @@ -548,7 +577,6 @@ func RemoveAPIFromAllInternalMaps(uuid string) map[string]struct{} { tobeUpdatedAPIRangeIdentifiers := make(map[string]struct{}, 0) updatedLabelsMap := make(map[string]struct{}, 0) - logger.LoggerAPI.Error(orgIDLatestAPIVersionMap) for orgID, orgAPI := range orgAPIMap { for apiIdentifier, envoyInternalAPI := range orgAPI { if strings.HasSuffix(apiIdentifier, ":"+uuid) { @@ -558,7 +586,7 @@ func RemoveAPIFromAllInternalMaps(uuid string) map[string]struct{} { delete(orgAPIMap[orgID], apiIdentifier) // get vhost from the apiIdentifier vhost, _ := ExtractVhostFromAPIIdentifier(apiIdentifier) - apiRangeID := generateIdentifierForAPIWithoutVersion(vhost, envoyInternalAPI.adapterInternalAPI.GetTitle()) + apiRangeID := GenerateIdentifierForAPIWithoutVersion(vhost, envoyInternalAPI.adapterInternalAPI.GetTitle()) if _, exists := orgIDLatestAPIVersionMap[orgID]; exists { if apiVersionMap, exists := orgIDLatestAPIVersionMap[orgID][apiRangeID]; exists { for versionRange, latestVersion := range apiVersionMap { @@ -576,26 +604,23 @@ func RemoveAPIFromAllInternalMaps(uuid string) map[string]struct{} { delete(orgIDLatestAPIVersionMap, orgID) } if len(tobeUpdatedAPIRangeIdentifiers) > 0 { - updateSemanticVersioning(orgID, tobeUpdatedAPIRangeIdentifiers) + updateSemanticVersioningInMap(orgID, tobeUpdatedAPIRangeIdentifiers) } } return updatedLabelsMap } -// UpdateAPICache updates the xDS cache related to the API Lifecycle event. -func UpdateAPICache(vHosts []string, newLabels map[string]struct{}, listener string, sectionName string, +// UpdateOrgAPIMap updates the xDS cache related to the API Lifecycle event. +func UpdateOrgAPIMap(vHosts, newLabels map[string]struct{}, listener string, sectionName string, adapterInternalAPI *model.AdapterInternalAPI) error { - mutexForInternalMapUpdate.Lock() - defer mutexForInternalMapUpdate.Unlock() // Create internal mappings for new vHosts - for _, vHost := range vHosts { + for vHost := range vHosts { logger.LoggerAPKOperator.Debugf("Creating internal mapping for vhost: %s", vHost) apiUUID := adapterInternalAPI.UUID apiIdentifier := GenerateIdentifierForAPIWithUUID(vHost, apiUUID) - - routes, clusters, endpoints, err := oasParser.GetRoutesClustersEndpoints(adapterInternalAPI, nil, + routes, clusters, endpoints, err := envoyconf.CreateRoutesWithClusters(adapterInternalAPI, nil, vHost, adapterInternalAPI.GetOrganizationID()) if err != nil { @@ -603,6 +628,11 @@ func UpdateAPICache(vHosts []string, newLabels map[string]struct{}, listener str adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), adapterInternalAPI.GetOrganizationID(), apiUUID, err.Error()) } + + if IsSemanticVersioningEnabled(adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion()) { + updateSemRegexForNewAPI(*adapterInternalAPI, routes, vHost) + } + if _, orgExists := orgAPIMap[adapterInternalAPI.GetOrganizationID()]; !orgExists { orgAPIMap[adapterInternalAPI.GetOrganizationID()] = make(map[string]*EnvoyInternalAPI) } @@ -615,13 +645,6 @@ func UpdateAPICache(vHosts []string, newLabels map[string]struct{}, listener str endpointAddresses: endpoints, enforcerAPI: oasParser.GetEnforcerAPI(adapterInternalAPI, vHost), } - - apiVersion := adapterInternalAPI.GetVersion() - apiName := adapterInternalAPI.GetTitle() - if isSemanticVersioningEnabled(apiName, apiVersion) { - logger.LoggerAPI.Errorf("Semantic versioning is enabled for API: %v", apiName) - updateRoutingRulesOnAPIUpdate(adapterInternalAPI.OrganizationID, apiIdentifier, apiName, apiVersion, vHost) - } } return nil } diff --git a/adapter/internal/discovery/xds/server_test.go b/adapter/internal/discovery/xds/server_test.go index 7e2af4c40..8499f598f 100644 --- a/adapter/internal/discovery/xds/server_test.go +++ b/adapter/internal/discovery/xds/server_test.go @@ -54,11 +54,20 @@ func TestOrgMapUpdates(t *testing.T) { } api2uuid.SetName("api-2") api2uuid.SetVersion("v0.0.1") + api21uuid := &model.AdapterInternalAPI{ + UUID: "api-21-uuid", + EnvType: "prod", + OrganizationID: "org-1", + } + api21uuid.SetName("api-2") + api21uuid.SetVersion("v0.0.2") ptrOne := new(int) *ptrOne = 1 + ptrTwo := new(int) + *ptrTwo = 2 tests := []struct { name string - vHosts []string + vHosts map[string]struct{} labels map[string]struct{} listeners []string adapterInternalAPI *model.AdapterInternalAPI @@ -70,7 +79,7 @@ func TestOrgMapUpdates(t *testing.T) { }{ { name: "Test creating first prod api", - vHosts: []string{"prod1.gw.abc.com", "prod2.gw.abc.com"}, + vHosts: map[string]struct{}{"prod1.gw.abc.com": {}, "prod2.gw.abc.com": {}}, labels: map[string]struct{}{"default": {}}, listeners: []string{"httpslistener"}, adapterInternalAPI: api1uuid, @@ -123,7 +132,7 @@ func TestOrgMapUpdates(t *testing.T) { }, { name: "Test creating first sand api", - vHosts: []string{"sand3.gw.abc.com", "sand4.gw.abc.com"}, + vHosts: map[string]struct{}{"sand3.gw.abc.com": {}, "sand4.gw.abc.com": {}}, labels: map[string]struct{}{"default": {}}, listeners: []string{"httpslistener"}, adapterInternalAPI: api1sanduuid, @@ -212,7 +221,7 @@ func TestOrgMapUpdates(t *testing.T) { }, { name: "Test creating second prod api", - vHosts: []string{"prod1.gw.pqr.com", "prod2.gw.pqr.com"}, + vHosts: map[string]struct{}{"prod1.gw.pqr.com": {}, "prod2.gw.pqr.com": {}}, labels: map[string]struct{}{"default": {}}, listeners: []string{"httpslistener"}, adapterInternalAPI: api2uuid, @@ -335,61 +344,292 @@ func TestOrgMapUpdates(t *testing.T) { }, }, }, - // { - // name: "Test updating first prod api 1 with new vhosts", - // vHosts: []string{"prod3.gw.abc.com", "prod4.gw.abc.com"}, - // labels: map[string]struct{}{"default": {}}, - // listeners: []string{"httpslistener"}, - // adapterInternalAPI: api1uuid, - // action: "UPDATE", - // expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ - // "org-1": { - // "prod3.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api1uuid, - // }, - // "prod4.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api1uuid, - // }, - // "sand3.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api1sanduuid, - // }, - // "sand4.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api1sanduuid, - // }, - // "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api2uuid, - // }, - // "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api2uuid, - // }, - // }, - // }, - // }, - // { - // name: "Test deleting api 1 both prod and sand", - // labels: map[string]struct{}{"default": {}}, - // listeners: []string{"httpslistener"}, - // adapterInternalAPI: api1uuid, - // action: "DELETE", - // expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ - // "org-1": { - // "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api2uuid, - // }, - // "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ - // envoyLabels: map[string]struct{}{"default": {}}, - // adapterInternalAPI: api2uuid, - // }, - // }, - // }, - // }, + { + name: "Test updating first prod api 1 with new vhosts", + vHosts: map[string]struct{}{"prod3.gw.abc.com": {}, "prod4.gw.abc.com": {}}, + labels: map[string]struct{}{"default": {}}, + listeners: []string{"httpslistener"}, + adapterInternalAPI: api1uuid, + action: "UPDATE", + expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + "org-1": { + "prod3.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1uuid, + }, + "prod4.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1uuid, + }, + "sand3.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1sanduuid, + }, + "sand4.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1sanduuid, + }, + "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + }, + }, + expectedOrgIDLatestAPIVersionMap: map[string]map[string]map[string]semantic_version.SemVersion{ + "org-1": { + "prod3.gw.abc.com:api-1": { + "v1": semantic_version.SemVersion{ + Version: "v1.0.0", // fix it should still be v1.0.0 + Major: 1, + Minor: 0, + Patch: new(int), + }, + "v1.0": semantic_version.SemVersion{ + Version: "v1.0.0", + Major: 1, + Minor: 0, + Patch: new(int), + }, + }, + "prod4.gw.abc.com:api-1": { + "v1": semantic_version.SemVersion{ + Version: "v1.0.0", + Major: 1, + Minor: 0, + Patch: new(int), + }, + "v1.0": semantic_version.SemVersion{ + Version: "v1.0.0", + Major: 1, + Minor: 0, + Patch: new(int), + }, + }, + "sand3.gw.abc.com:api-1": { + "v1": semantic_version.SemVersion{ + Version: "v1.0.1", + Major: 1, + Minor: 0, + Patch: ptrOne, + }, + "v1.0": semantic_version.SemVersion{ + Version: "v1.0.1", + Major: 1, + Minor: 0, + Patch: ptrOne, + }, + }, + "sand4.gw.abc.com:api-1": { + "v1": semantic_version.SemVersion{ + Version: "v1.0.1", + Major: 1, + Minor: 0, + Patch: ptrOne, + }, + "v1.0": semantic_version.SemVersion{ + Version: "v1.0.1", + Major: 1, + Minor: 0, + Patch: ptrOne, + }, + }, + "prod1.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + }, + "prod2.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + }, + }, + }, + }, + { + name: "Test deleting api 1 both prod and sand", + labels: map[string]struct{}{"default": {}}, + listeners: []string{"httpslistener"}, + adapterInternalAPI: api1uuid, + action: "DELETE", + expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + "org-1": { + "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + }, + }, + expectedOrgIDLatestAPIVersionMap: map[string]map[string]map[string]semantic_version.SemVersion{ + "org-1": { + "prod1.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + }, + "prod2.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + }, + }, + }, + }, + { + name: "Test adding new api 2 version", + vHosts: map[string]struct{}{"prod1.gw.pqr.com": {}, "prod2.gw.pqr.com": {}}, + labels: map[string]struct{}{"default": {}}, + listeners: []string{"httpslistener"}, + adapterInternalAPI: api21uuid, + action: "CREATE", + expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + "org-1": { + "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + "prod1.gw.pqr.com:api-21-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api21uuid, + }, + "prod2.gw.pqr.com:api-21-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api21uuid, + }, + }, + }, + expectedOrgIDLatestAPIVersionMap: map[string]map[string]map[string]semantic_version.SemVersion{ + "org-1": { + "prod1.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.2", + Major: 0, + Minor: 0, + Patch: ptrTwo, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.2", + Major: 0, + Minor: 0, + Patch: ptrTwo, + }, + }, + "prod2.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.2", + Major: 0, + Minor: 0, + Patch: ptrTwo, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.2", + Major: 0, + Minor: 0, + Patch: ptrTwo, + }, + }, + }, + }, + }, + { + name: "Test deleting api 2 latest version", + labels: map[string]struct{}{"default": {}}, + listeners: []string{"httpslistener"}, + adapterInternalAPI: api21uuid, + action: "DELETE", + expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + "org-1": { + "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api2uuid, + }, + }, + }, + expectedOrgIDLatestAPIVersionMap: map[string]map[string]map[string]semantic_version.SemVersion{ + "org-1": { + "prod1.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + }, + "prod2.gw.pqr.com:api-2": { + "v0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + "v0.0": semantic_version.SemVersion{ + Version: "v0.0.1", + Major: 0, + Minor: 0, + Patch: ptrOne, + }, + }, + }, + }, + }, } for _, test := range tests { @@ -401,13 +641,13 @@ func TestOrgMapUpdates(t *testing.T) { SanitizeGateway(label, true) } RemoveAPIFromAllInternalMaps(test.adapterInternalAPI.UUID) - UpdateAPICache(test.vHosts, test.labels, test.listeners[0], "httpslistener", test.adapterInternalAPI) + PopulateInternalMaps(test.adapterInternalAPI, test.labels, test.vHosts, test.listeners[0], test.EnvType) case "UPDATE": loggers.LoggerAPI.Infof("Updating API: %v", test.adapterInternalAPI.UUID) for label := range test.labels { SanitizeGateway(label, true) } - UpdateAPICache(test.vHosts, test.labels, test.listeners[0], "httpslistener", test.adapterInternalAPI) + PopulateInternalMaps(test.adapterInternalAPI, test.labels, test.vHosts, test.listeners[0], test.EnvType) case "DELETE": loggers.LoggerAPI.Infof("Deleting API: %v", test.adapterInternalAPI.UUID) DeleteAPI(test.adapterInternalAPI.UUID, test.labels) diff --git a/adapter/internal/logging/logging_constant.go b/adapter/internal/logging/logging_constant.go index bc451aa1b..5d3b637b5 100644 --- a/adapter/internal/logging/logging_constant.go +++ b/adapter/internal/logging/logging_constant.go @@ -52,6 +52,7 @@ const ( Error1411 = 1411 Error1413 = 1413 Error1414 = 1414 + Error1415 = 1415 ) // Error Log Internal XDS(1700-1799) Config Constants diff --git a/adapter/internal/oasparser/config_generator.go b/adapter/internal/oasparser/config_generator.go index 25ee9d879..10b581f5d 100644 --- a/adapter/internal/oasparser/config_generator.go +++ b/adapter/internal/oasparser/config_generator.go @@ -18,7 +18,6 @@ package oasparser import ( - "fmt" "strconv" "strings" @@ -36,19 +35,6 @@ import ( gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -// GetRoutesClustersEndpoints generates the routes, clusters and endpoints (envoy) -// when the openAPI Json is provided. For websockets apiJsn created from api.yaml file is considered. -func GetRoutesClustersEndpoints(adapterInternalAPI *model.AdapterInternalAPI, interceptorCerts map[string][]byte, - vHost string, organizationID string) ([]*routev3.Route, []*clusterv3.Cluster, []*corev3.Address, error) { - - routes, clusters, endpoints, err := envoy.CreateRoutesWithClusters(adapterInternalAPI, interceptorCerts, - vHost, organizationID) - if err != nil { - return nil, nil, nil, fmt.Errorf("error while creating routes, clusters and endpoints. %v", err) - } - return routes, clusters, endpoints, nil -} - // GetGlobalClusters generates initial internal clusters for given environment. func GetGlobalClusters() ([]*clusterv3.Cluster, []*corev3.Address) { var ( @@ -258,7 +244,7 @@ func castAPIAuthenticationsToEnforcerAPIAuthentications(authentication *model.Au enforcerAuthentication.Jwt = &api.JWT{ Header: strings.ToLower(authentication.JWT.Header), SendTokenToUpstream: authentication.JWT.SendTokenToUpstream, - Audience: authentication.JWT.Audience, + Audience: authentication.JWT.Audience, } } var apiKeys []*api.APIKey diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 4d4ef90fa..cc3e4bfd2 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -1190,7 +1190,7 @@ func CreateAPIDefinitionEndpoint(adapterInternalAPI *model.AdapterInternalAPI, v rewritePath := basePath + "/" + vHost + "?" + apiDefinitionQueryParam basePath = strings.TrimSuffix(basePath, "/") var ( - router routev3.Route + router *routev3.Route action *routev3.Route_Route match *routev3.RouteMatch decorator *routev3.Decorator @@ -1248,7 +1248,7 @@ func CreateAPIDefinitionEndpoint(adapterInternalAPI *model.AdapterInternalAPI, v }, } - router = routev3.Route{ + router = &routev3.Route{ Name: endpoint, //Categorize routes with same base path Match: match, Action: action, @@ -1258,7 +1258,7 @@ func CreateAPIDefinitionEndpoint(adapterInternalAPI *model.AdapterInternalAPI, v wellknown.HTTPExternalAuthorization: filter, }, } - return &router + return router } // CreateHealthEndpoint generates a route for the jwt /health endpoint diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters_test.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters_test.go index 46b14168e..bd141ca35 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters_test.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters_test.go @@ -132,7 +132,7 @@ func TestCreateRoutesWithClustersWithExactAndRegularExpressionRules(t *testing.T apiState.ProdHTTPRoute = &httpRouteState - adapterInternalAPI, labels, err := synchronizer.GenerateAdapterInternalAPI(apiState, &httpRouteState, constants.Production) + adapterInternalAPI, labels, err := synchronizer.UpdateInternalMapsFromHTTPRoute(apiState, &httpRouteState, constants.Production) assert.Equal(t, map[string]struct{}{"default-gateway": {}}, labels, "Labels are incorrect.") assert.Nil(t, err, "Error should not be present when apiState is converted to a AdapterInternalAPI object") routes, clusters, _, _ := envoy.CreateRoutesWithClusters(adapterInternalAPI, nil, "prod.gw.wso2.com", "carbon.super") @@ -179,19 +179,19 @@ func TestCreateRoutesWithClustersWithExactAndRegularExpressionRules(t *testing.T "The route regex for the two paths should not be the same") } -func TestGenerateAdapterInternalAPIForDefaultCase(t *testing.T) { +func TestExtractAPIDetailsFromHTTPRouteForDefaultCase(t *testing.T) { apiState := generateSampleAPI("test-api-1", "1.0.0", "/test-api/1.0.0") httpRouteState := synchronizer.HTTPRouteState{} httpRouteState = *apiState.ProdHTTPRoute xds.SanitizeGateway("default-gateway", true) - adapterInternalAPI, labels, err := synchronizer.GenerateAdapterInternalAPI(apiState, &httpRouteState, constants.Production) + adapterInternalAPI, labels, err := synchronizer.UpdateInternalMapsFromHTTPRoute(apiState, &httpRouteState, constants.Production) assert.Equal(t, map[string]struct{}{"default-gateway": {}}, labels, "Labels are incorrect.") assert.Nil(t, err, "Error should not be present when apiState is converted to a AdapterInternalAPI object") assert.Equal(t, "Default", adapterInternalAPI.GetEnvironment(), "Environment is incorrect.") } -func TestGenerateAdapterInternalAPIForSpecificEnvironment(t *testing.T) { +func TestExtractAPIDetailsFromHTTPRouteForSpecificEnvironment(t *testing.T) { apiState := generateSampleAPI("test-api-2", "1.0.0", "/test-api2/1.0.0") httpRouteState := synchronizer.HTTPRouteState{} @@ -199,7 +199,7 @@ func TestGenerateAdapterInternalAPIForSpecificEnvironment(t *testing.T) { apiState.APIDefinition.Spec.Environment = "dev" xds.SanitizeGateway("default-gateway", true) - adapterInternalAPI, labels, err := synchronizer.GenerateAdapterInternalAPI(apiState, &httpRouteState, constants.Production) + adapterInternalAPI, labels, err := synchronizer.UpdateInternalMapsFromHTTPRoute(apiState, &httpRouteState, constants.Production) assert.Equal(t, map[string]struct{}{"default-gateway": {}}, labels, "Labels are incorrect.") assert.Nil(t, err, "Error should not be present when apiState is converted to a AdapterInternalAPI object") assert.Equal(t, "dev", adapterInternalAPI.GetEnvironment(), "Environment is incorrect.") @@ -349,7 +349,7 @@ func TestCreateRoutesWithClustersWithMultiplePathPrefixRules(t *testing.T) { apiState.ProdHTTPRoute = &httpRouteState xds.SanitizeGateway("default-gateway", true) - adapterInternalAPI, labels, err := synchronizer.GenerateAdapterInternalAPI(apiState, &httpRouteState, constants.Production) + adapterInternalAPI, labels, err := synchronizer.UpdateInternalMapsFromHTTPRoute(apiState, &httpRouteState, constants.Production) assert.Equal(t, map[string]struct{}{"default-gateway": {}}, labels, "Labels are incorrect.") assert.Nil(t, err, "Error should not be present when apiState is converted to a AdapterInternalAPI object") routes, clusters, _, _ := envoy.CreateRoutesWithClusters(adapterInternalAPI, nil, "prod.gw.wso2.com", "carbon.super") @@ -483,7 +483,7 @@ func TestCreateRoutesWithClustersWithBackendTLSConfigs(t *testing.T) { apiState.ProdHTTPRoute = &httpRouteState xds.SanitizeGateway("default-gateway", true) - adapterInternalAPI, labels, err := synchronizer.GenerateAdapterInternalAPI(apiState, &httpRouteState, constants.Production) + adapterInternalAPI, labels, err := synchronizer.UpdateInternalMapsFromHTTPRoute(apiState, &httpRouteState, constants.Production) assert.Equal(t, map[string]struct{}{"default-gateway": {}}, labels, "Labels are incorrect.") assert.Nil(t, err, "Error should not be present when apiState is converted to a AdapterInternalAPI object") _, clusters, _, _ := envoy.CreateRoutesWithClusters(adapterInternalAPI, nil, "prod.gw.wso2.com", "carbon.super") @@ -626,7 +626,7 @@ func TestCreateRoutesWithClustersDifferentBackendRefs(t *testing.T) { apiState.ProdHTTPRoute = &httpRouteState xds.SanitizeGateway("default-gateway", true) - adapterInternalAPI, labels, err := synchronizer.GenerateAdapterInternalAPI(apiState, &httpRouteState, constants.Production) + adapterInternalAPI, labels, err := synchronizer.UpdateInternalMapsFromHTTPRoute(apiState, &httpRouteState, constants.Production) assert.Equal(t, map[string]struct{}{"default-gateway": {}}, labels, "Labels are incorrect.") assert.Nil(t, err, "Error should not be present when apiState is converted to a AdapterInternalAPI object") _, clusters, _, _ := envoy.CreateRoutesWithClusters(adapterInternalAPI, nil, "prod.gw.wso2.com", "carbon.super") @@ -712,7 +712,7 @@ func TestCreateRoutesWithClustersSameBackendRefs(t *testing.T) { apiState.ProdHTTPRoute = &httpRouteState xds.SanitizeGateway("default-gateway", true) - adapterInternalAPI, labels, err := synchronizer.GenerateAdapterInternalAPI(apiState, &httpRouteState, constants.Production) + adapterInternalAPI, labels, err := synchronizer.UpdateInternalMapsFromHTTPRoute(apiState, &httpRouteState, constants.Production) assert.Equal(t, map[string]struct{}{"default-gateway": {}}, labels, "Labels are incorrect.") assert.Nil(t, err, "Error should not be present when apiState is converted to a AdapterInternalAPI object") _, clusters, _, _ := envoy.CreateRoutesWithClusters(adapterInternalAPI, nil, "prod.gw.wso2.com", "carbon.super") diff --git a/adapter/internal/operator/synchronizer/gql_api.go b/adapter/internal/operator/synchronizer/gql_api.go index 5c16a8eb4..d05c4482d 100644 --- a/adapter/internal/operator/synchronizer/gql_api.go +++ b/adapter/internal/operator/synchronizer/gql_api.go @@ -32,8 +32,36 @@ import ( gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) +// extract APIDetails from the GQLRoute +func updateInternalMapsFromGQLRoute(apiState APIState, gqlRoute *GQLRouteState, envType string) (*model.AdapterInternalAPI, map[string]struct{}, error) { + adapterInternalAPI, err := generateGQLAdapterInternalAPI(apiState, gqlRoute, envType) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error generating AdapterInternalAPI for GQLRoute: %v. %v", gqlRoute.GQLRouteCombined.Name, err)) + return nil, nil, err + } + + vHosts := getVhostsForGQLAPI(gqlRoute.GQLRouteCombined) + labels := getLabelsForGQLAPI(gqlRoute.GQLRouteCombined) + listeners, relativeSectionNames := getListenersForGQLAPI(gqlRoute.GQLRouteCombined, adapterInternalAPI.UUID) + // We dont have a use case where a perticular API's two different gql 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 gql route: %v. ", + gqlRoute.GQLRouteCombined.Name)) + return nil, nil, errors.New("failed to find matching listener name for the provided gql 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 +} + // generateGQLAdapterInternalAPI this will populate a AdapterInternalAPI representation for an GQLRoute -func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, envType string) (*model.AdapterInternalAPI, map[string]struct{}, error) { +func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, envType string) (*model.AdapterInternalAPI, error) { var adapterInternalAPI model.AdapterInternalAPI adapterInternalAPI.SetIsDefaultVersion(apiState.APIDefinition.Spec.IsDefaultVersion) adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) @@ -64,7 +92,7 @@ func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, e if err := adapterInternalAPI.SetInfoGQLRouteCR(gqlRoute.GQLRouteCombined, resourceParams); err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting GQLRoute CR info to adapterInternalAPI. %v", err)) - return nil, nil, err + return nil, err } if apiState.MutualSSL != nil && apiState.MutualSSL.Required != "" && !adapterInternalAPI.GetDisableAuthentications() { @@ -75,35 +103,14 @@ func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, e adapterInternalAPI.SetDisableMtls(true) } - vHosts := getVhostsForGQLAPI(gqlRoute.GQLRouteCombined) - labels := getLabelsForGQLAPI(gqlRoute.GQLRouteCombined) - listeners, relativeSectionNames := getListenersForGQLAPI(gqlRoute.GQLRouteCombined, adapterInternalAPI.UUID) - // We dont have a use case where a perticular API's two different gql 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 gql route: %v. ", - gqlRoute.GQLRouteCombined.Name)) - return nil, nil, errors.New("failed to find matching listener name for the provided gql route") - } - - listenerName := listeners[0] - sectionName := relativeSectionNames[0] - if len(listeners) != 0 { - 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 - } - } - - return &adapterInternalAPI, labels, nil + return &adapterInternalAPI, nil } // getVhostForAPI returns the vHosts related to an API. -func getVhostsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) []string { - var vHosts []string +func getVhostsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) map[string]struct{} { + var vHosts map[string]struct{} for _, hostName := range gqlRoute.Spec.Hostnames { - vHosts = append(vHosts, string(hostName)) + vHosts[string(hostName)] = struct{}{} } return vHosts } diff --git a/adapter/internal/operator/synchronizer/rest_api.go b/adapter/internal/operator/synchronizer/rest_api.go index 113c0ef5f..d5cdedfb5 100644 --- a/adapter/internal/operator/synchronizer/rest_api.go +++ b/adapter/internal/operator/synchronizer/rest_api.go @@ -49,8 +49,36 @@ func undeployRestAPIInGateway(apiState APIState) error { return err } -// GenerateAdapterInternalAPI this will populate a AdapterInternalAPI representation for an HTTPRoute -func GenerateAdapterInternalAPI(apiState APIState, httpRoute *HTTPRouteState, envType string) (*model.AdapterInternalAPI, map[string]struct{}, error) { +// UpdateInternalMapsFromHTTPRoute extracts the API details from the HTTPRoute. +func UpdateInternalMapsFromHTTPRoute(apiState APIState, httpRoute *HTTPRouteState, envType string) (*model.AdapterInternalAPI, map[string]struct{}, error) { + adapterInternalAPI, err := generateAdapterInternalAPI(apiState, httpRoute, envType) + if err != nil { + loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error generating AdapterInternalAPI for HTTPRoute: %v. %v", httpRoute.HTTPRouteCombined.Name, err)) + return nil, nil, err + } + + vHosts := getVhostsForAPI(httpRoute.HTTPRouteCombined) + labels := getGatewayNameForAPI(httpRoute.HTTPRouteCombined) + listeners, relativeSectionNames := getListenersForAPI(httpRoute.HTTPRouteCombined, adapterInternalAPI.UUID) + // We dont have a use case where a perticular API's two different http 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 http route: %v. ", + httpRoute.HTTPRouteCombined.Name)) + return nil, nil, errors.New("failed to find matching listener name for the provided http 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 +} + +// generateAdapterInternalAPI this will populate a AdapterInternalAPI representation for an HTTPRoute +func generateAdapterInternalAPI(apiState APIState, httpRoute *HTTPRouteState, envType string) (*model.AdapterInternalAPI, error) { var adapterInternalAPI model.AdapterInternalAPI adapterInternalAPI.SetIsDefaultVersion(apiState.APIDefinition.Spec.IsDefaultVersion) adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) @@ -81,7 +109,7 @@ func GenerateAdapterInternalAPI(apiState APIState, httpRoute *HTTPRouteState, en } if err := adapterInternalAPI.SetInfoHTTPRouteCR(httpRoute.HTTPRouteCombined, resourceParams); err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting HttpRoute CR info to adapterInternalAPI. %v", err)) - return nil, nil, err + return nil, err } if apiState.MutualSSL != nil && apiState.MutualSSL.Required != "" && !adapterInternalAPI.GetDisableAuthentications() { @@ -94,41 +122,18 @@ func GenerateAdapterInternalAPI(apiState APIState, httpRoute *HTTPRouteState, en 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 := getVhostsForAPI(httpRoute.HTTPRouteCombined) - labels := getGatewayNameForAPI(httpRoute.HTTPRouteCombined) - listeners, relativeSectionNames := getListenersForAPI(httpRoute.HTTPRouteCombined, adapterInternalAPI.UUID) - // We dont have a use case where a perticular API's two different http 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 http route: %v. ", - httpRoute.HTTPRouteCombined.Name)) - return nil, nil, errors.New("failed to find matching listener name for the provided http route") - } - - updatedLabelsMap := make(map[string]struct{}) - listenerName := listeners[0] - sectionName := relativeSectionNames[0] - if len(listeners) != 0 { - 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 labels { - updatedLabelsMap[newLabel] = struct{}{} - } + return nil, err } - return &adapterInternalAPI, updatedLabelsMap, nil + return &adapterInternalAPI, nil } // getVhostForAPI returns the vHosts related to an API. -func getVhostsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { - var vHosts []string +func getVhostsForAPI(httpRoute *gwapiv1b1.HTTPRoute) map[string]struct{} { + vHosts := make(map[string]struct{}) + loggers.LoggerAPI.Error("httpRoute.Spec.Hostnames: ", httpRoute.Spec.Hostnames) for _, hostName := range httpRoute.Spec.Hostnames { - vHosts = append(vHosts, string(hostName)) + vHosts[string(hostName)] = struct{}{} } return vHosts } diff --git a/adapter/internal/operator/synchronizer/synchronizer.go b/adapter/internal/operator/synchronizer/synchronizer.go index 1597d6428..a61aa181c 100644 --- a/adapter/internal/operator/synchronizer/synchronizer.go +++ b/adapter/internal/operator/synchronizer/synchronizer.go @@ -132,7 +132,6 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv var updatedAPIs []types.NamespacedName for i, apiState := range event.Events { loggers.LoggerAPKOperator.Infof("%s event received for %s", event.EventType, apiState.APIDefinition.Name) - // TODO(amali) move this inside updateAPI method // Remove the API from the internal maps before adding it again oldGatewayNames := xds.RemoveAPIFromAllInternalMaps(string((*apiState.APIDefinition).ObjectMeta.UID)) for label := range oldGatewayNames { @@ -140,7 +139,7 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv } if apiState.APIDefinition.Spec.APIType == "REST" { if apiState.ProdHTTPRoute != nil { - _, updatedLabels, err := GenerateAdapterInternalAPI(apiState, apiState.ProdHTTPRoute, constants.Production) + _, updatedLabels, err := UpdateInternalMapsFromHTTPRoute(apiState, apiState.ProdHTTPRoute, constants.Production) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.CRITICAL, "Error deploying prod httpRoute of API : %v in Organization %v from environments %v. Error: %v", @@ -156,7 +155,7 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv } if apiState.SandHTTPRoute != nil { - _, updatedLabels, err := GenerateAdapterInternalAPI(apiState, apiState.SandHTTPRoute, constants.Sandbox) + _, updatedLabels, err := UpdateInternalMapsFromHTTPRoute(apiState, apiState.SandHTTPRoute, constants.Sandbox) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2666, logging.CRITICAL, "Error deploying sand httpRoute of API : %v in Organization %v from environments %v. Error: %v", @@ -174,7 +173,7 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv if apiState.APIDefinition.Spec.APIType == "GraphQL" { if apiState.ProdGQLRoute != nil { - _, updatedLabels, err := generateGQLAdapterInternalAPI(apiState, apiState.ProdGQLRoute, constants.Production) + _, updatedLabels, err := updateInternalMapsFromGQLRoute(apiState, apiState.ProdGQLRoute, constants.Production) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.CRITICAL, "Error deploying prod gqlRoute of API : %v in Organization %v from environments %v. Error: %v", @@ -187,7 +186,7 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv } } if apiState.SandGQLRoute != nil { - _, updatedLabels, err := generateGQLAdapterInternalAPI(apiState, apiState.SandGQLRoute, constants.Sandbox) + _, updatedLabels, err := updateInternalMapsFromGQLRoute(apiState, apiState.SandGQLRoute, constants.Sandbox) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2665, logging.CRITICAL, "Error deploying sand gqlRoute of API : %v in Organization %v from environments %v. Error: %v",