diff --git a/adapter/internal/discovery/xds/conf/config.toml b/adapter/internal/discovery/xds/conf/config.toml index e69de29bb..06fb27b9e 100644 --- a/adapter/internal/discovery/xds/conf/config.toml +++ b/adapter/internal/discovery/xds/conf/config.toml @@ -0,0 +1,2 @@ +[envoy] +enableIntelligentRouting = true \ No newline at end of file diff --git a/adapter/internal/discovery/xds/marshaller.go b/adapter/internal/discovery/xds/marshaller.go index d36cee776..0b4fcfc94 100644 --- a/adapter/internal/discovery/xds/marshaller.go +++ b/adapter/internal/discovery/xds/marshaller.go @@ -34,8 +34,6 @@ const ( DeleteEvent ) -const blockedStatus string = "BLOCKED" - // MarshalConfig will marshal a Config struct - read from the config toml - to // enfocer's CDS resource representation. func MarshalConfig(config *config.Config) *enforcer.Config { diff --git a/adapter/internal/discovery/xds/semantic_versioning.go b/adapter/internal/discovery/xds/semantic_versioning.go index 6a94e19d2..0a7224e87 100644 --- a/adapter/internal/discovery/xds/semantic_versioning.go +++ b/adapter/internal/discovery/xds/semantic_versioning.go @@ -21,7 +21,7 @@ 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" @@ -29,6 +29,13 @@ import ( 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 "\\." @@ -36,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 { @@ -47,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) } @@ -67,315 +74,266 @@ 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, apiName) - // 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) } - - 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 _, envoyInternalAPI := range orgAPIMap[organizationID] { - // API's all versions in the same vHost - if envoyInternalAPI.adapterInternalAPI.GetTitle() == apiName && isVHostMatched(organizationID, 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 + semVersion, _ := semantic_version.ValidateAndGetVersionComponents(adapterInternalAPI.GetVersion()) + + 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 !semVersion.Compare(currentAPISemVersion[GetMajorVersionRange(*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 !semVersion.Compare(currentAPISemVersion[GetMinorVersionRange(*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, true) +} - 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, remove bool) { + 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 + 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 { + 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 remove && updateMajor { + regex = strings.Replace(regex, GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, + GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + } + if remove && updateMinor { + regex = strings.Replace(regex, GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMinorSemVersion), + GetVersionMatchRegex(oldSelectedSemVersion.OldMinorSemVersion.Version), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMinorSemVersion), + GetVersionMatchRegex(oldSelectedSemVersion.OldMinorSemVersion.Version), 1) + } + if !remove && updateMajor { + regex = strings.Replace(regex, GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), + GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + regex = strings.Replace(regex, GetVersionMatchRegex(oldSelectedSemVersion.OldMajorSemVersion.Version), + GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), + GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, + GetVersionMatchRegex(oldSelectedSemVersion.OldMajorSemVersion.Version), + GetMajorMinorVersionRangeRegex(oldSelectedSemVersion.OldMajorSemVersion), 1) + } + if !remove && updateMinor { + regex = strings.Replace(regex, GetVersionMatchRegex(oldSelectedSemVersion.OldMinorSemVersion.Version), + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMinorSemVersion), 1) + regexRewritePattern = strings.Replace(regexRewritePattern, GetVersionMatchRegex(oldSelectedSemVersion.OldMinorSemVersion.Version), + GetMinorVersionRangeRegex(oldSelectedSemVersion.OldMinorSemVersion), 1) } pathSpecifier := &routev3.RouteMatch_SafeRegex{ - SafeRegex: &envoy_type_matcherv3.RegexMatcher{ + SafeRegex: &matcherv3.RegexMatcher{ Regex: regex, }, } - route.Match.PathSpecifier = pathSpecifier - action := &routev3.Route_Route{} - action = route.Action.(*routev3.Route_Route) + action := route.Action.(*routev3.Route_Route) action.Route.RegexRewrite.Pattern.Regex = regexRewritePattern route.Action = action } - } } -func updateRoutingRulesOnAPIDelete(organizationID, apiIdentifier string, api model.AdapterInternalAPI) { - // Update the intelligent routing if the deleting API is the latest version of the API range - // and the API range has other versions - vhost, err := ExtractVhostFromAPIIdentifier(apiIdentifier) - if err != nil { - logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1411, logging.MAJOR, - "Error extracting vhost from API identifier: %v for Organization %v. Ignore deploying the API, error: %v", - apiIdentifier, organizationID, err)) - } - apiRangeIdentifier := generateIdentifierForAPIWithoutVersion(vhost, api.GetTitle()) - - latestAPIVersionMap, latestAPIVersionMapExists := orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier] - if !latestAPIVersionMapExists { - return - } - deletingAPISemVersion, _ := semantic_version.ValidateAndGetVersionComponents(api.GetVersion(), api.GetTitle()) - if deletingAPISemVersion == nil { - return - } - majorVersionRange := GetMajorVersionRange(*deletingAPISemVersion) - newLatestMajorRangeAPIIdentifier := "" - - if deletingAPIsMajorRangeLatestAPISemVersion, ok := latestAPIVersionMap[majorVersionRange]; ok { - if deletingAPIsMajorRangeLatestAPISemVersion.Version == api.GetVersion() { - newLatestMajorRangeAPI := &semantic_version.SemVersion{ - Version: "", - Major: deletingAPISemVersion.Major, - Minor: 0, - Patch: nil, - } - for currentAPIIdentifier, envoyInternalAPI := range orgAPIMap[organizationID] { - // Iterate all the API versions other than the deleting API itself - if envoyInternalAPI.adapterInternalAPI.GetTitle() == api.GetTitle() && currentAPIIdentifier != apiIdentifier { - currentAPISemVersion, _ := semantic_version.ValidateAndGetVersionComponents(envoyInternalAPI.adapterInternalAPI.GetVersion(), envoyInternalAPI.adapterInternalAPI.GetTitle()) - if currentAPISemVersion != nil { - if currentAPISemVersion.Major == deletingAPISemVersion.Major { - if newLatestMajorRangeAPI.Compare(*currentAPISemVersion) { - newLatestMajorRangeAPI = currentAPISemVersion - newLatestMajorRangeAPIIdentifier = currentAPIIdentifier - } - } - } - } - } - if newLatestMajorRangeAPIIdentifier != "" { - orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier][majorVersionRange] = *newLatestMajorRangeAPI - apiRoutes := getRoutesForAPIIdentifier(organizationID, newLatestMajorRangeAPIIdentifier) - for _, route := range apiRoutes { - regex := route.GetMatch().GetSafeRegex().GetRegex() - regexRewritePattern := route.GetRoute().GetRegexRewrite().GetPattern().GetRegex() - newLatestMajorRangeAPIVersionRegex := GetVersionMatchRegex(newLatestMajorRangeAPI.Version) - // Remove any available minor version range regexes and apply the minor range regex - regex = strings.Replace( - regex, - GetMinorVersionRangeRegex(*newLatestMajorRangeAPI), - newLatestMajorRangeAPIVersionRegex, - 1, - ) - regexRewritePattern = strings.Replace( - regexRewritePattern, - GetMinorVersionRangeRegex(*newLatestMajorRangeAPI), - newLatestMajorRangeAPIVersionRegex, - 1, - ) - regex = strings.Replace( - regex, - newLatestMajorRangeAPIVersionRegex, - GetMajorMinorVersionRangeRegex(*newLatestMajorRangeAPI), - 1, - ) - regexRewritePattern = strings.Replace( - regexRewritePattern, - newLatestMajorRangeAPIVersionRegex, - GetMajorMinorVersionRangeRegex(*newLatestMajorRangeAPI), - 1, - ) - pathSpecifier := &routev3.RouteMatch_SafeRegex{ - SafeRegex: &envoy_type_matcherv3.RegexMatcher{ - Regex: regex, - }, - } - - route.Match.PathSpecifier = pathSpecifier - action := &routev3.Route_Route{} - action = route.Action.(*routev3.Route_Route) - action.Route.RegexRewrite.Pattern.Regex = regexRewritePattern - route.Action = action - } - } else { - delete(orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier], majorVersionRange) - } +// updateSemanticVersioningInMap updates the latest version ranges of the APIs in the organization +func updateSemanticVersioningInMap(org string, apiRangeIdentifiers map[string]struct{}) { + oldSemVersionsMap := make(map[string]*oldSemVersion) + 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) + if _, ok := apiRangeIdentifiers[apiRangeIdentifier]; !ok { + continue } - } - minorVersionRange := GetMinorVersionRange(*deletingAPISemVersion) - - if deletingAPIsMinorRangeLatestAPI, ok := latestAPIVersionMap[minorVersionRange]; ok { - if deletingAPIsMinorRangeLatestAPI.Version == api.GetVersion() { - newLatestMinorRangeAPI := &semantic_version.SemVersion{ - Version: "", - Major: deletingAPISemVersion.Major, - Minor: deletingAPISemVersion.Minor, - Patch: nil, + // get sem version from the api in orgmap + semVersion, err := semantic_version.ValidateAndGetVersionComponents(api.adapterInternalAPI.GetVersion()) + if err != nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1410, logging.MAJOR, + "Error validating the version of the API: %v for Organization: %v. Ignore deploying the API, error: %v", + 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 + oldSemVersionsMap[GetMajorVersionRange(*semVersion)] = &oldSemVersion{ + Vhost: vhost, + APIName: apiName, + OldMajorSemVersion: semVersion, + OldMinorSemVersion: semVersion, } - newLatestMinorRangeAPIIdentifier := "" - for currentAPIIdentifier, envoyInternalAPI := range orgAPIMap[organizationID] { - // Iterate all the API versions other than the deleting API itself - if envoyInternalAPI.adapterInternalAPI.GetTitle() == api.GetTitle() && currentAPIIdentifier != apiIdentifier { - currentAPISemVersion, _ := semantic_version.ValidateAndGetVersionComponents(envoyInternalAPI.adapterInternalAPI.GetVersion(), envoyInternalAPI.adapterInternalAPI.GetTitle()) - if currentAPISemVersion != nil { - if currentAPISemVersion.Major == deletingAPISemVersion.Major && - currentAPISemVersion.Minor == deletingAPISemVersion.Minor { - if newLatestMinorRangeAPI.Compare(*currentAPISemVersion) { - newLatestMinorRangeAPI = currentAPISemVersion - newLatestMinorRangeAPIIdentifier = currentAPIIdentifier - } - } - } + } else { + var newVersion *oldSemVersion + if _, ok := currentAPISemVersion[GetMajorVersionRange(*semVersion)]; !ok { + currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion + } else if !semVersion.Compare(currentAPISemVersion[GetMajorVersionRange(*semVersion)]) { + newVersion = &oldSemVersion{ + Vhost: vhost, + APIName: apiName, + OldMajorSemVersion: semVersion, } + currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion + oldSemVersionsMap[GetMajorVersionRange(*semVersion)] = newVersion } - if newLatestMinorRangeAPIIdentifier != "" && newLatestMinorRangeAPIIdentifier != newLatestMajorRangeAPIIdentifier { - orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier][minorVersionRange] = *newLatestMinorRangeAPI - apiRoutes := getRoutesForAPIIdentifier(organizationID, newLatestMinorRangeAPIIdentifier) - for _, route := range apiRoutes { - regex := route.GetMatch().GetSafeRegex().GetRegex() - newLatestMinorRangeAPIVersionRegex := GetVersionMatchRegex(newLatestMinorRangeAPI.Version) - regex = strings.Replace( - regex, - newLatestMinorRangeAPIVersionRegex, - GetMinorVersionRangeRegex(*newLatestMinorRangeAPI), - 1, - ) - pathSpecifier := &routev3.RouteMatch_SafeRegex{ - SafeRegex: &envoy_type_matcherv3.RegexMatcher{ - Regex: regex, - }, + if _, ok := currentAPISemVersion[GetMinorVersionRange(*semVersion)]; !ok { + currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion + } else if !semVersion.Compare(currentAPISemVersion[GetMinorVersionRange(*semVersion)]) { + if newVersion != nil { + newVersion.OldMinorSemVersion = semVersion + } else { + newVersion = &oldSemVersion{ + Vhost: vhost, + APIName: apiName, + OldMinorSemVersion: semVersion, } - regexRewritePattern := route.GetRoute().GetRegexRewrite().GetPattern().GetRegex() - regexRewritePattern = strings.Replace( - regexRewritePattern, - newLatestMinorRangeAPIVersionRegex, - GetMinorVersionRangeRegex(*newLatestMinorRangeAPI), - 1, - ) - route.Match.PathSpecifier = pathSpecifier - action := &routev3.Route_Route{} - action = route.Action.(*routev3.Route_Route) - action.Route.RegexRewrite.Pattern.Regex = regexRewritePattern - route.Action = action + oldSemVersionsMap[GetMinorVersionRange(*semVersion)] = newVersion } - } else { - delete(orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier], minorVersionRange) + currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion } } } - - if orgAPIMap, apiAvailable := orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier]; apiAvailable && len(orgAPIMap) == 0 { - delete(orgIDLatestAPIVersionMap[organizationID], apiRangeIdentifier) - if orgMap := orgIDLatestAPIVersionMap[organizationID]; len(orgMap) == 0 { - delete(orgIDLatestAPIVersionMap, organizationID) - } + for _, sem := range oldSemVersionsMap { + oldSemVersions = append(oldSemVersions, *sem) } - + updateOldRegex(org, oldSemVersions, false) } -func isVHostMatched(organizationID, vHost string) bool { - - if apis, ok := orgIDAPIvHostsMap[organizationID]; ok { - - for _, vHosts := range apis { - for _, vHostEntry := range vHosts { - if vHostEntry == vHost { - return true +func updateSemRegexForNewAPI(adapterInternalAPI model.AdapterInternalAPI, routes []*routev3.Route, vhost string) { + apiIdentifier := GenerateIdentifierForAPIWithoutVersion(vhost, adapterInternalAPI.GetTitle()) + + 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 } } } - } - return false -} - -func getRoutesForAPIIdentifier(organizationID, apiIdentifier string) []*routev3.Route { + 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, + }, + } - var routes []*routev3.Route - if _, ok := orgAPIMap[organizationID]; ok { - if _, ok := orgAPIMap[organizationID][apiIdentifier]; ok { - routes = orgAPIMap[organizationID][apiIdentifier].routes + route.Match.PathSpecifier = pathSpecifier + action := route.Action.(*routev3.Route_Route) + action.Route.RegexRewrite.Pattern.Regex = regexRewritePattern + route.Action = action + } } } - - return routes } -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, apiName) - if err != nil && apiSemVersion == nil { + apiSemVersion, err := semantic_version.ValidateAndGetVersionComponents(apiVersion) + if err != nil || apiSemVersion == nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1411, logging.MAJOR, + "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 d08d27718..e3826edcf 100644 --- a/adapter/internal/discovery/xds/semantic_versioning_test.go +++ b/adapter/internal/discovery/xds/semantic_versioning_test.go @@ -18,12 +18,12 @@ package xds import ( - "reflect" "regexp" "testing" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" envoy_type_matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" + "github.com/stretchr/testify/assert" "github.com/wso2/apk/adapter/config" "github.com/wso2/apk/adapter/internal/oasparser/model" semantic_version "github.com/wso2/apk/adapter/pkg/semanticversion" @@ -75,22 +75,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 +109,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 +255,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,250 +264,101 @@ func TestIsSemanticVersioningEnabled(t *testing.T) { } } -func TestIsVHostMatched(t *testing.T) { - // Mock orgIDAPIvHostsMap for testing - orgIDAPIvHostsMap = map[string]map[string][]string{ - "org1": { - "api1": {"example.com", "api.example.com"}, - "api2": {"test.com"}, - }, - "org2": { - "api3": {"example.org"}, - "api4": {"test.org"}, - }, - } - - tests := []struct { - name string - organizationID string - vHost string - expectedResult bool - }{ - { - name: "Matching vHost in org1", - organizationID: "org1", - vHost: "example.com", - expectedResult: true, - }, - { - name: "Matching vHost in org2", - organizationID: "org2", - vHost: "example.org", - expectedResult: true, - }, - { - name: "Non-matching vHost in org1", - organizationID: "org1", - vHost: "nonexistent.com", - expectedResult: false, - }, - { - name: "Non-matching vHost in org2", - organizationID: "org2", - vHost: "nonexistent.org", - expectedResult: false, - }, - { - name: "VHost not found for organization", - organizationID: "org3", - vHost: "example.com", - expectedResult: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := isVHostMatched(tt.organizationID, tt.vHost) - - if result != tt.expectedResult { - t.Errorf("Expected result: %v, Got: %v", tt.expectedResult, result) - } - }) - } -} - -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{ "org1": { "gw.com:apiID1": &EnvoyInternalAPI{ - adapterInternalAPI: apiID1, + adapterInternalAPI: &apiID1, routes: generateRoutes(apiID1ResourcePath), }, "gw.com:apiID2": &EnvoyInternalAPI{ - adapterInternalAPI: apiID2, + adapterInternalAPI: &apiID2, routes: generateRoutes(apiID2ResourcePath), }, "gw.com:apiID3": &EnvoyInternalAPI{ - adapterInternalAPI: apiID3, + adapterInternalAPI: &apiID3, routes: generateRoutes(apiID3ResourcePath), }, }, } - orgIDAPIvHostsMap = map[string]map[string][]string{ - "org1": { - "api1": {"gw.com", "api.example.com"}, - "api2": {"test.com"}, - }, - } - 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 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", - 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 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, + }, + { + 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", + 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 @@ -608,34 +459,47 @@ 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.SetVersion("v1.5") - apiID3ResourcePath := "^/mock-api/v1(?:\\.5)?/orders([/]{0,1})" + var apiID21 model.AdapterInternalAPI + apiID21.SetName("Mock API") + apiID21.UUID = "apiID21" + apiID21.SetVersion("v1.1") + apiID21ResourcePath := "^/mock-api/v1\\.1/orders([/]{0,1})" + + var apiID25 model.AdapterInternalAPI + apiID25.SetName("Mock API") + apiID25.UUID = "apiID25" + apiID25.SetVersion("v1.5") + apiID25ResourcePath := "^/mock-api/v1(?:\\.5)?/orders([/]{0,1})" orgAPIMap = map[string]map[string]*EnvoyInternalAPI{ "org3": { "gw.com:apiID1": &EnvoyInternalAPI{ - adapterInternalAPI: apiID1, + adapterInternalAPI: &apiID1, routes: generateRoutes(apiID1ResourcePath), }, }, "org4": { "gw.com:apiID2": &EnvoyInternalAPI{ - adapterInternalAPI: apiID2, + adapterInternalAPI: &apiID2, routes: generateRoutes(apiID2ResourcePath), }, - "gw.com:apiID3": &EnvoyInternalAPI{ - adapterInternalAPI: apiID3, - routes: generateRoutes(apiID3ResourcePath), + "gw.com:apiID21": &EnvoyInternalAPI{ + adapterInternalAPI: &apiID21, + routes: generateRoutes(apiID21ResourcePath), + }, + "gw.com:apiID25": &EnvoyInternalAPI{ + adapterInternalAPI: &apiID25, + routes: generateRoutes(apiID25ResourcePath), }, }, } @@ -644,29 +508,43 @@ func TestUpdateRoutingRulesOnAPIDelete(t *testing.T) { name string organizationID string apiIdentifier string - api model.AdapterInternalAPI + apiCheck string + expectedRegex string + api *model.AdapterInternalAPI deleteVersion string }{ { name: "Delete latest major version", organizationID: "org3", - apiIdentifier: "gw.com:apiID1", - api: apiID1, + apiIdentifier: "gw.com:Test API", + api: &apiID1, deleteVersion: "v1.0", + apiCheck: "gw.com:apiID25", + expectedRegex: "^/mock-api/v1(?:\\.5)?/orders([/]{0,1})", }, { - name: "Delete latest minor version", + name: "Delete latest minor version v1.5", organizationID: "org4", - apiIdentifier: "gw.com:apiID3", - api: apiID3, + apiIdentifier: "gw.com:Mock API", + api: &apiID25, deleteVersion: "v1.5", + apiCheck: "gw.com:apiID21", + expectedRegex: "^/mock-api/v1(?:\\.1)?/orders([/]{0,1})", + }, + { + name: "Delete latest minor version v1.1", + organizationID: "org4", + apiIdentifier: "gw.com:Mock API", + api: &apiID21, + deleteVersion: "v1.1", + apiCheck: "gw.com:apiID2", + expectedRegex: "^/mock-api/v1(?:\\.0)?/orders([/]{0,1})", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - updateRoutingRulesOnAPIDelete(tt.organizationID, tt.apiIdentifier, tt.api) - + 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 { @@ -674,6 +552,12 @@ func TestUpdateRoutingRulesOnAPIDelete(t *testing.T) { } } } + if tt.apiCheck != "" { + routes := orgAPIMap["org4"][tt.apiCheck].routes + assert.Equal(t, tt.expectedRegex, routes[0].GetMatch().GetSafeRegex().GetRegex(), + "Expected regex: %s, Got: %s", + ) + } }) } } diff --git a/adapter/internal/discovery/xds/server.go b/adapter/internal/discovery/xds/server.go index cd7607a78..84955b4a8 100644 --- a/adapter/internal/discovery/xds/server.go +++ b/adapter/internal/discovery/xds/server.go @@ -23,7 +23,6 @@ import ( crand "crypto/rand" "crypto/sha1" "encoding/hex" - "errors" "fmt" "math/big" "math/rand" @@ -45,23 +44,20 @@ import ( logger "github.com/wso2/apk/adapter/internal/loggers" logging "github.com/wso2/apk/adapter/internal/logging" oasParser "github.com/wso2/apk/adapter/internal/oasparser" - "github.com/wso2/apk/adapter/internal/oasparser/constants" "github.com/wso2/apk/adapter/internal/oasparser/envoyconf" "github.com/wso2/apk/adapter/internal/oasparser/model" - operatorconsts "github.com/wso2/apk/adapter/internal/operator/constants" "github.com/wso2/apk/adapter/pkg/discovery/api/wso2/discovery/subscription" wso2_cache "github.com/wso2/apk/adapter/pkg/discovery/protocol/cache/v3" wso2_resource "github.com/wso2/apk/adapter/pkg/discovery/protocol/resource/v3" eventhubTypes "github.com/wso2/apk/adapter/pkg/eventhub/types" semantic_version "github.com/wso2/apk/adapter/pkg/semanticversion" - "github.com/wso2/apk/adapter/pkg/utils/stringutils" gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) // EnvoyInternalAPI struct use to hold envoy resources and adapter internal resources type EnvoyInternalAPI struct { - adapterInternalAPI model.AdapterInternalAPI - envoyLabels []string + adapterInternalAPI *model.AdapterInternalAPI + envoyLabels map[string]struct{} routes []*routev3.Route clusters []*clusterv3.Cluster endpointAddresses []*corev3.Address @@ -99,11 +95,11 @@ var ( enforcerRevokedTokensCache wso2_cache.SnapshotCache enforcerThrottleDataCache wso2_cache.SnapshotCache - orgAPIMap map[string]map[string]*EnvoyInternalAPI // organizationID -> Vhost:API_UUID -> EnvoyInternalAPI struct map - orgIDvHostBasepathMap map[string]map[string]string // organizationID -> Vhost:basepath -> Vhost:API_UUID - orgIDAPIvHostsMap map[string]map[string][]string // organizationID -> UUID -> prod/sand -> Envoy Vhost Array map + // todo(amali) there can be multiple vhosts for one EnvoyInternalAPI so handle this apiuuid+sand/prod should be the key + + orgAPIMap map[string]map[string]*EnvoyInternalAPI // organizationID -> Vhost:API_UUID -> EnvoyInternalAPI struct map + orgIDLatestAPIVersionMap map[string]map[string]map[string]semantic_version.SemVersion // organizationID -> Vhost:APIName -> VersionRange(vx/vx.x; x is int) -> Latest API Version - orgIDLatestAPIVersionMap map[string]map[string]map[string]semantic_version.SemVersion // organizationID -> Vhost:APIName -> Version Range -> Latest API Version // Envoy Label as map key // TODO(amali) use this without generating all again. gatewayLabelConfigMap map[string]*EnvoyGatewayConfig // GW-Label -> EnvoyGatewayConfig struct map @@ -155,8 +151,6 @@ func init() { enforcerJwtIssuerCache = wso2_cache.NewSnapshotCache(false, IDHash{}, nil) gatewayLabelConfigMap = make(map[string]*EnvoyGatewayConfig) orgAPIMap = make(map[string]map[string]*EnvoyInternalAPI) - orgIDAPIvHostsMap = make(map[string]map[string][]string) // organizationID -> UUID-prod/sand -> Envoy Vhost Array map - orgIDvHostBasepathMap = make(map[string]map[string]string) orgIDLatestAPIVersionMap = make(map[string]map[string]map[string]semantic_version.SemVersion) enforcerLabelMap = make(map[string]*EnforcerInternalAPI) @@ -212,105 +206,17 @@ func GetEnforcerThrottleDataCache() wso2_cache.SnapshotCache { return enforcerThrottleDataCache } -// DeleteAPICREvent deletes API with the given UUID from the given gw environments -func DeleteAPICREvent(labels []string, apiUUID string, organizationID string) error { - mutexForInternalMapUpdate.Lock() - defer mutexForInternalMapUpdate.Unlock() - - prodvHostIdentifier := GetvHostsIdentifier(apiUUID, operatorconsts.Production) - sandvHostIdentifier := GetvHostsIdentifier(apiUUID, operatorconsts.Sandbox) - vHosts := append(orgIDAPIvHostsMap[organizationID][prodvHostIdentifier], - orgIDAPIvHostsMap[organizationID][sandvHostIdentifier]...) - - delete(orgIDAPIvHostsMap[organizationID], prodvHostIdentifier) - delete(orgIDAPIvHostsMap[organizationID], sandvHostIdentifier) - for _, vhost := range vHosts { - apiIdentifier := GenerateIdentifierForAPIWithUUID(vhost, apiUUID) - if err := deleteAPI(apiIdentifier, labels, organizationID); err != nil { - logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1410, logging.MAJOR, "Error undeploying API %v with UUID %v of Organization %v from environments %v, error: %v", - apiIdentifier, apiUUID, organizationID, labels, err.Error())) - return err - } - // if no error, update internal vhost maps - // error only happens when API not found in deleteAPI func - logger.LoggerXds.Infof("Successfully undeployed the API %v with UUID %v under Organization %s and environment %s", - apiIdentifier, apiUUID, organizationID, labels) - } - return nil -} - -// deleteAPI deletes an API, its resources and updates the caches of given environments -func deleteAPI(apiIdentifier string, environments []string, organizationID string) error { - apiUUID, _ := ExtractUUIDFromAPIIdentifier(apiIdentifier) - var api *EnvoyInternalAPI - - if _, orgExists := orgAPIMap[organizationID]; orgExists { - if oldAPI, apiExists := orgAPIMap[organizationID][apiIdentifier]; apiExists { - api = oldAPI - } else { - logger.LoggerXds.Infof("Unable to delete API: %v from Organization: %v. API Does not exist. API_UUID: %v", apiIdentifier, organizationID, apiUUID) - return errors.New(constants.NotFound) - } - - } else { - logger.LoggerXds.Infof("Unable to delete API: %v from Organization: %v. Organization Does not exist. API_UUID: %v", apiIdentifier, organizationID, apiUUID) - return errors.New(constants.NotFound) - } - - existingLabels := orgAPIMap[organizationID][apiIdentifier].envoyLabels - toBeDelEnvs, toBeKeptEnvs := getEnvironmentsToBeDeleted(existingLabels, environments) - - if isSemanticVersioningEnabled(api.adapterInternalAPI.GetTitle(), api.adapterInternalAPI.GetVersion()) { - updateRoutingRulesOnAPIDelete(organizationID, apiIdentifier, api.adapterInternalAPI) - } - - var isAllowedToDelete bool - updatedLabelsMap := make(map[string]struct{}) - for _, val := range toBeDelEnvs { - updatedLabelsMap[val] = struct{}{} - if stringutils.StringInSlice(val, existingLabels) { - isAllowedToDelete = true - } - } - if isAllowedToDelete { - // do not delete from all environments, hence do not clear routes, clusters, endpoints, enforcerAPIs - orgAPIMap[organizationID][apiIdentifier].envoyLabels = toBeKeptEnvs - if len(toBeKeptEnvs) != 0 { - UpdateXdsCacheOnAPIChange(updatedLabelsMap) - return nil - } - } +// DeleteAPI deletes API with the given UUID from the given gw environments +func DeleteAPI(uuid string, gatewayNames map[string]struct{}) error { - //clean maps of routes, clusters, endpoints, enforcerAPIs - if len(environments) == 0 || isAllowedToDelete { - cleanMapResources(apiIdentifier, organizationID, toBeDelEnvs) + oldGatewayNames := RemoveAPIFromAllInternalMaps(uuid) + for oldGatewayName := range oldGatewayNames { + gatewayNames[oldGatewayName] = struct{}{} } - UpdateXdsCacheOnAPIChange(updatedLabelsMap) + UpdateXdsCacheOnAPIChange(gatewayNames) return nil } -func cleanMapResources(apiIdentifier string, organizationID string, toBeDelEnvs []string) { - if _, orgExists := orgAPIMap[organizationID]; orgExists { - delete(orgAPIMap[organizationID], apiIdentifier) - } - - deleteBasepathForVHost(organizationID, apiIdentifier) - //TODO: (SuKSW) clean any remaining in label wise maps, if this is the last API of that label - logger.LoggerXds.Infof("Deleted API %v of organization %v", apiIdentifier, organizationID) -} - -func deleteBasepathForVHost(organizationID, apiIdentifier string) { - // Remove the basepath from map (that is used to avoid duplicate basepaths) - if _, orgExists := orgAPIMap[organizationID]; orgExists { - if oldOrgAPIAPI, ok := orgAPIMap[organizationID][apiIdentifier]; ok { - s := strings.Split(apiIdentifier, apiKeyFieldSeparator) - vHost := s[0] - oldBasepath := oldOrgAPIAPI.adapterInternalAPI.GetXWso2Basepath() - delete(orgIDvHostBasepathMap[organizationID], vHost+":"+oldBasepath) - } - } -} - // UpdateXdsCacheOnAPIChange when this method is called, openAPIEnvoy map is updated. // Old labels refers to the previously assigned labels // New labels refers to the the updated labels @@ -343,40 +249,34 @@ func SetReady() { func GenerateEnvoyResoucesForGateway(gatewayName string) ([]types.Resource, []types.Resource, []types.Resource, []types.Resource, []types.Resource) { var clusterArray []*clusterv3.Cluster + // Warning: Route order is important. The first route that matches the request will be used. var vhostToRouteArrayMap = make(map[string][]*routev3.Route) var endpointArray []*corev3.Address var apis []types.Resource for organizationID, entityMap := range orgAPIMap { for apiKey, envoyInternalAPI := range entityMap { - if stringutils.StringInSlice(gatewayName, envoyInternalAPI.envoyLabels) { - vhost, err := ExtractVhostFromAPIIdentifier(apiKey) - if err != nil { - logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1411, logging.MAJOR, "Error extracting vhost from API identifier: %v for Organization %v. Ignore deploying the API, error: %v", apiKey, organizationID, err)) - continue - } - isDefaultVersion := false - var orgAPI *EnvoyInternalAPI - // If the adapterInternalAPI is not found, proceed with other APIs. (Unreachable condition at this point) - // If that happens, there is no purpose in processing clusters too. - if org, ok := orgAPIMap[organizationID]; !ok { - continue - } else if orgAPI, ok = org[apiKey]; !ok { - continue - } - isDefaultVersion = orgAPI.adapterInternalAPI.IsDefaultVersion - // If it is a default versioned API, the routes are added to the end of the existing array. - // Otherwise the routes would be added to the front. - // /fooContext/2.0.0/* resource path should be matched prior to the /fooContext/* . - if isDefaultVersion { - vhostToRouteArrayMap[vhost] = append(vhostToRouteArrayMap[vhost], orgAPI.routes...) - } else { - vhostToRouteArrayMap[vhost] = append(orgAPI.routes, vhostToRouteArrayMap[vhost]...) - } - clusterArray = append(clusterArray, orgAPI.clusters...) - endpointArray = append(endpointArray, orgAPI.endpointAddresses...) - apis = append(apis, orgAPI.enforcerAPI) + if _, exists := envoyInternalAPI.envoyLabels[gatewayName]; !exists { + // do nothing if the gateway is not found in the envoyInternalAPI + continue + } + vhost, err := ExtractVhostFromAPIIdentifier(apiKey) + if err != nil { + logger.LoggerXds.ErrorC(logging.PrintError(logging.Error1411, logging.MAJOR, "Error extracting vhost from API identifier: %v for Organization %v. Ignore deploying the API, error: %v", apiKey, organizationID, err)) + continue } + isDefaultVersion := envoyInternalAPI.adapterInternalAPI.IsDefaultVersion + // If it is a default versioned API, the routes are added to the end of the existing array. + // Otherwise the routes would be added to the front. + // /fooContext/2.0.0/* resource path should be matched prior to the /fooContext/* . + if isDefaultVersion { + vhostToRouteArrayMap[vhost] = append(vhostToRouteArrayMap[vhost], envoyInternalAPI.routes...) + } else { + vhostToRouteArrayMap[vhost] = append(envoyInternalAPI.routes, vhostToRouteArrayMap[vhost]...) + } + clusterArray = append(clusterArray, envoyInternalAPI.clusters...) + endpointArray = append(endpointArray, envoyInternalAPI.endpointAddresses...) + apis = append(apis, envoyInternalAPI.enforcerAPI) } } @@ -398,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 } @@ -611,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) } @@ -641,99 +544,98 @@ func ExtractUUIDFromAPIIdentifier(id string) (string, error) { return "", err } -// RemoveAPICacheForEnv will remove all the internal mappings for a specific environment -func RemoveAPICacheForEnv(adapterInternalAPI model.AdapterInternalAPI, envType string) { - vHostIdentifier := GetvHostsIdentifier(adapterInternalAPI.UUID, envType) - var oldvHosts []string - if _, ok := orgIDAPIvHostsMap[adapterInternalAPI.OrganizationID]; ok { - oldvHosts = orgIDAPIvHostsMap[adapterInternalAPI.GetOrganizationID()][vHostIdentifier] - for _, oldvhost := range oldvHosts { - apiIdentifier := GenerateIdentifierForAPIWithUUID(oldvhost, adapterInternalAPI.UUID) - if orgMap, orgExists := orgAPIMap[adapterInternalAPI.GetOrganizationID()]; orgExists { - if _, apiExists := orgMap[apiIdentifier]; apiExists { - delete(orgAPIMap[adapterInternalAPI.GetOrganizationID()], apiIdentifier) - } - } +// 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{}{} } + updateSemanticVersioningInMapForUpdateAPI(adapterInternalAPI.OrganizationID, tobeUpdatedAPIRangeIdentifiers, adapterInternalAPI) } -} -// RemoveAPIFromOrgAPIMap removes api from orgAPI map -func RemoveAPIFromOrgAPIMap(uuid string, orgID string) { - if orgMap, ok := orgAPIMap[orgID]; ok { - for apiName := range orgMap { - if strings.Contains(apiName, uuid) { - delete(orgMap, apiName) - } - } - if len(orgMap) == 0 { - delete(orgAPIMap, orgID) - } + 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 } -// UpdateAPICache updates the xDS cache related to the API Lifecycle event. -func UpdateAPICache(vHosts []string, newLabels []string, listener string, sectionName string, - adapterInternalAPI model.AdapterInternalAPI) (map[string]struct{}, error) { +// RemoveAPIFromAllInternalMaps removes api from all maps +func RemoveAPIFromAllInternalMaps(uuid string) map[string]struct{} { mutexForInternalMapUpdate.Lock() defer mutexForInternalMapUpdate.Unlock() - vHostIdentifier := GetvHostsIdentifier(adapterInternalAPI.UUID, adapterInternalAPI.EnvType) - var oldvHosts []string - if _, ok := orgIDAPIvHostsMap[adapterInternalAPI.OrganizationID]; ok { - oldvHosts = orgIDAPIvHostsMap[adapterInternalAPI.GetOrganizationID()][vHostIdentifier] - orgIDAPIvHostsMap[adapterInternalAPI.GetOrganizationID()][vHostIdentifier] = vHosts - } else { - vHostsMap := make(map[string][]string) - vHostsMap[vHostIdentifier] = vHosts - orgIDAPIvHostsMap[adapterInternalAPI.GetOrganizationID()] = vHostsMap - } - + tobeUpdatedAPIRangeIdentifiers := make(map[string]struct{}, 0) updatedLabelsMap := make(map[string]struct{}, 0) - - // Remove internal mappings for old vHosts - for _, oldvhost := range oldvHosts { - apiIdentifier := GenerateIdentifierForAPIWithUUID(oldvhost, adapterInternalAPI.UUID) - if orgMap, orgExists := orgAPIMap[adapterInternalAPI.GetOrganizationID()]; orgExists { - if _, apiExists := orgMap[apiIdentifier]; apiExists { - for _, oldLabel := range orgMap[apiIdentifier].envoyLabels { + for orgID, orgAPI := range orgAPIMap { + for apiIdentifier, envoyInternalAPI := range orgAPI { + if strings.HasSuffix(apiIdentifier, ":"+uuid) { + for oldLabel := range envoyInternalAPI.envoyLabels { updatedLabelsMap[oldLabel] = struct{}{} } - delete(orgAPIMap[adapterInternalAPI.GetOrganizationID()], apiIdentifier) + delete(orgAPIMap[orgID], apiIdentifier) + // get vhost from the apiIdentifier + vhost, _ := ExtractVhostFromAPIIdentifier(apiIdentifier) + apiRangeID := GenerateIdentifierForAPIWithoutVersion(vhost, envoyInternalAPI.adapterInternalAPI.GetTitle()) + if _, exists := orgIDLatestAPIVersionMap[orgID]; exists { + if apiVersionMap, exists := orgIDLatestAPIVersionMap[orgID][apiRangeID]; exists { + for _, latestVersion := range apiVersionMap { + if latestVersion.Version == envoyInternalAPI.adapterInternalAPI.GetVersion() { + delete(orgIDLatestAPIVersionMap[orgID], apiRangeID) + tobeUpdatedAPIRangeIdentifiers[apiRangeID] = struct{}{} + } + } + } + } } } + if len(orgAPIMap[orgID]) == 0 { + delete(orgAPIMap, orgID) + delete(orgIDLatestAPIVersionMap, orgID) + } + if len(tobeUpdatedAPIRangeIdentifiers) > 0 { + updateSemanticVersioningInMap(orgID, tobeUpdatedAPIRangeIdentifiers) + } } + return updatedLabelsMap +} + +// 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 { + // 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) - var orgExists bool - - // get changing label set - if _, orgExists = orgAPIMap[adapterInternalAPI.GetOrganizationID()]; orgExists { - if _, apiExists := orgAPIMap[adapterInternalAPI.GetOrganizationID()][apiIdentifier]; apiExists { - for _, oldLabel := range orgAPIMap[adapterInternalAPI.GetOrganizationID()][apiIdentifier].envoyLabels { - updatedLabelsMap[oldLabel] = struct{}{} - } - } - } - for _, newLabel := range newLabels { - updatedLabelsMap[newLabel] = struct{}{} - } - - routes, clusters, endpoints, err := oasParser.GetRoutesClustersEndpoints(&adapterInternalAPI, nil, + routes, clusters, endpoints, err := envoyconf.CreateRoutesWithClusters(adapterInternalAPI, nil, vHost, adapterInternalAPI.GetOrganizationID()) if err != nil { - return nil, fmt.Errorf("error while deploying API. Name: %s Version: %s, OrgID: %s, API_UUID: %v, Error: %s", + return fmt.Errorf("error while deploying API. Name: %s Version: %s, OrgID: %s, API_UUID: %v, Error: %s", adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), adapterInternalAPI.GetOrganizationID(), apiUUID, err.Error()) } - if !orgExists { + + if IsSemanticVersioningEnabled(adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion()) { + updateSemRegexForNewAPI(*adapterInternalAPI, routes, vHost) + } + + if _, orgExists := orgAPIMap[adapterInternalAPI.GetOrganizationID()]; !orgExists { orgAPIMap[adapterInternalAPI.GetOrganizationID()] = make(map[string]*EnvoyInternalAPI) } + orgAPIMap[adapterInternalAPI.GetOrganizationID()][apiIdentifier] = &EnvoyInternalAPI{ adapterInternalAPI: adapterInternalAPI, envoyLabels: newLabels, @@ -742,15 +644,8 @@ func UpdateAPICache(vHosts []string, newLabels []string, listener string, sectio endpointAddresses: endpoints, enforcerAPI: oasParser.GetEnforcerAPI(adapterInternalAPI, vHost), } - - apiVersion := adapterInternalAPI.GetVersion() - apiName := adapterInternalAPI.GetTitle() - if isSemanticVersioningEnabled(apiName, apiVersion) { - updateRoutingRulesOnAPIUpdate(adapterInternalAPI.OrganizationID, apiIdentifier, apiName, apiVersion, vHost) - } } - - return updatedLabelsMap, nil + return nil } // UpdateGatewayCache updates the xDS cache related to the Gateway Lifecycle event. diff --git a/adapter/internal/discovery/xds/server_test.go b/adapter/internal/discovery/xds/server_test.go index 8b491a8c2..8499f598f 100644 --- a/adapter/internal/discovery/xds/server_test.go +++ b/adapter/internal/discovery/xds/server_test.go @@ -21,82 +21,614 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/wso2/apk/adapter/config" + "github.com/wso2/apk/adapter/internal/loggers" "github.com/wso2/apk/adapter/internal/oasparser/model" + semantic_version "github.com/wso2/apk/adapter/pkg/semanticversion" ) -func TestUpdateAPICache(t *testing.T) { +func TestOrgMapUpdates(t *testing.T) { + orgAPIMap = make(map[string]map[string]*EnvoyInternalAPI) + orgIDLatestAPIVersionMap = make(map[string]map[string]map[string]semantic_version.SemVersion) + conf := config.ReadConfigs() + conf.Envoy.EnableIntelligentRouting = true + + api1uuid := &model.AdapterInternalAPI{ + UUID: "api-1-uuid", + EnvType: "prod", + OrganizationID: "org-1", + } + api1uuid.SetName("api-1") + api1uuid.SetVersion("v1.0.0") + api1sanduuid := &model.AdapterInternalAPI{ + UUID: "api-1-uuid", + EnvType: "sand", + OrganizationID: "org-1", + } + api1sanduuid.SetName("api-1") + api1sanduuid.SetVersion("v1.0.1") + api2uuid := &model.AdapterInternalAPI{ + UUID: "api-2-uuid", + EnvType: "prod", + OrganizationID: "org-1", + } + 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 - labels []string - listeners []string - adapterInternalAPI model.AdapterInternalAPI - EnvType string - action string - deletedvHosts []string + name string + vHosts map[string]struct{} + labels map[string]struct{} + listeners []string + adapterInternalAPI *model.AdapterInternalAPI + EnvType string + action string + deletedvHosts []string + expectedOrgAPIMap map[string]map[string]*EnvoyInternalAPI + expectedOrgIDLatestAPIVersionMap map[string]map[string]map[string]semantic_version.SemVersion }{ { - name: "Test creating first prod api", - vHosts: []string{"prod1.gw.abc.com", "prod2.gw.abc.com"}, - labels: []string{"default"}, - listeners: []string{"httpslistener"}, - adapterInternalAPI: model.AdapterInternalAPI{ - UUID: "api-1-uuid", - EnvType: "prod", - OrganizationID: "org-1", + name: "Test creating first prod api", + vHosts: map[string]struct{}{"prod1.gw.abc.com": {}, "prod2.gw.abc.com": {}}, + labels: map[string]struct{}{"default": {}}, + listeners: []string{"httpslistener"}, + adapterInternalAPI: api1uuid, + EnvType: "prod", + action: "CREATE", + expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + "org-1": { + "prod1.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1uuid, + }, + "prod2.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1uuid, + }, + }, + }, + expectedOrgIDLatestAPIVersionMap: map[string]map[string]map[string]semantic_version.SemVersion{ + "org-1": { + "prod1.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), + }, + }, + "prod2.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), + }, + }, + }, }, - EnvType: "prod", - action: "CREATE", }, { - name: "Test creating first sand api", - vHosts: []string{"sand3.gw.abc.com", "sand4.gw.abc.com"}, - labels: []string{"default"}, - listeners: []string{"httpslistener"}, - adapterInternalAPI: model.AdapterInternalAPI{ - UUID: "app-1-uuid", - EnvType: "sand", - OrganizationID: "org-1", + name: "Test creating first sand api", + vHosts: map[string]struct{}{"sand3.gw.abc.com": {}, "sand4.gw.abc.com": {}}, + labels: map[string]struct{}{"default": {}}, + listeners: []string{"httpslistener"}, + adapterInternalAPI: api1sanduuid, + EnvType: "sand", + action: "UPDATE", + expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + "org-1": { + "prod1.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1uuid, + }, + "prod2.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, + }, + }, + }, + expectedOrgIDLatestAPIVersionMap: map[string]map[string]map[string]semantic_version.SemVersion{ + "org-1": { + "prod1.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), + }, + }, + "prod2.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, + }, + }, + }, }, - EnvType: "sand", - action: "CREATE", }, { - name: "Test creating second prod api", - vHosts: []string{"prod1.gw.pqr.com", "prod2.gw.pqr.com"}, - labels: []string{"default"}, - listeners: []string{"httpslistener"}, - adapterInternalAPI: model.AdapterInternalAPI{ - UUID: "api-2-uuid", - EnvType: "prod", - OrganizationID: "org-2", + name: "Test creating second prod api", + vHosts: map[string]struct{}{"prod1.gw.pqr.com": {}, "prod2.gw.pqr.com": {}}, + labels: map[string]struct{}{"default": {}}, + listeners: []string{"httpslistener"}, + adapterInternalAPI: api2uuid, + EnvType: "prod", + action: "CREATE", + expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + "org-1": { + "prod1.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + envoyLabels: map[string]struct{}{"default": {}}, + adapterInternalAPI: api1uuid, + }, + "prod2.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": { + "prod1.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), + }, + }, + "prod2.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, + }, + }, + }, }, - EnvType: "prod", - action: "CREATE", }, { - name: "Test updating first prod api 1 with new vhosts", - vHosts: []string{"prod1.gw.abc.com", "prod2.gw.abc.com"}, - labels: []string{"default"}, - listeners: []string{"httpslistener"}, - adapterInternalAPI: model.AdapterInternalAPI{ - UUID: "api-1-uuid", - EnvType: "prod", - OrganizationID: "org-1", + 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, + }, + }, + }, }, - action: "UPDATE", }, { - name: "Test deleting api 1 both prod and sand", - labels: []string{"default"}, - listeners: []string{"httpslistener"}, - adapterInternalAPI: model.AdapterInternalAPI{ - UUID: "app-1-uuid", - OrganizationID: "org-1", + 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, + }, + }, + }, }, - action: "DELETE", - deletedvHosts: []string{"prod1.gw.abc.com", "prod2.gw.abc.com", - "sand3.gw.abc.com", "sand4.gw.abc.com"}, }, } @@ -104,53 +636,80 @@ func TestUpdateAPICache(t *testing.T) { t.Run(test.name, func(t *testing.T) { switch test.action { case "CREATE": - case "UPDATE": - for _, label := range test.labels { + loggers.LoggerAPI.Infof("Creating API: %v", test.adapterInternalAPI.UUID) + for label := range test.labels { SanitizeGateway(label, true) } - UpdateAPICache(test.vHosts, test.labels, test.listeners[0], "httpslistener", test.adapterInternalAPI) - identifier := GetvHostsIdentifier(test.adapterInternalAPI.UUID, "prod") - actualvHosts, ok := orgIDAPIvHostsMap[test.adapterInternalAPI.OrganizationID][identifier] - if !ok { - t.Errorf("orgIDAPIvHostsMap has not updated with new entry with the key: %s, %v", - identifier, orgIDAPIvHostsMap) - } - assert.Equal(t, actualvHosts, test.vHosts, "Not expected vHosts found, expected: %v but found: %v", - test.vHosts, actualvHosts) - for _, vhsot := range actualvHosts { - testExistsInMapping(t, orgAPIMap[test.adapterInternalAPI.OrganizationID], - GenerateIdentifierForAPIWithUUID(vhsot, test.adapterInternalAPI.UUID), true) + RemoveAPIFromAllInternalMaps(test.adapterInternalAPI.UUID) + 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) } + PopulateInternalMaps(test.adapterInternalAPI, test.labels, test.vHosts, test.listeners[0], test.EnvType) case "DELETE": - DeleteAPICREvent(test.labels, test.adapterInternalAPI.UUID, test.adapterInternalAPI.OrganizationID) - prodIdentifier := GetvHostsIdentifier(test.adapterInternalAPI.UUID, "prod") - sandIdentifier := GetvHostsIdentifier(test.adapterInternalAPI.UUID, "sand") - _, prodExists := orgIDAPIvHostsMap[test.adapterInternalAPI.OrganizationID][prodIdentifier] - _, sandExists := orgIDAPIvHostsMap[test.adapterInternalAPI.OrganizationID][sandIdentifier] - if prodExists { - t.Errorf("orgIDAPIvHostsMap has a mapping for prod after api deletion") - } - if sandExists { - t.Errorf("orgIDAPIvHostsMap has a mapping for sand after api deletion") + loggers.LoggerAPI.Infof("Deleting API: %v", test.adapterInternalAPI.UUID) + DeleteAPI(test.adapterInternalAPI.UUID, test.labels) + } + assert.Equal(t, len(test.expectedOrgAPIMap), len(orgAPIMap), "orgAPIMap length is different, expected: %v but found: %v", + test.expectedOrgAPIMap, orgAPIMap) + for orgID, orgAPIs := range test.expectedOrgAPIMap { + if orgAPI, ok := orgAPIMap[orgID]; !ok { + t.Errorf("orgAPIMap has no entry with the organization: %s", orgID) + } else { + assert.Equal(t, len(test.expectedOrgAPIMap[orgID]), len(orgAPIs), "orgAPI length is different, expected: %v but found: %v", + len(test.expectedOrgAPIMap[orgID]), len(orgAPIs)) + for vuuid, expectedAPI := range test.expectedOrgAPIMap[orgID] { + if actualAPI, ok := orgAPI[vuuid]; !ok { + t.Errorf("orgAPIMap has not updated with new entry with the key: %s, %v", + vuuid, orgAPIMap) + } else { + assert.Equal(t, expectedAPI.adapterInternalAPI.UUID, actualAPI.adapterInternalAPI.UUID, "Not expected API UUID found, expected: %v but found: %v", + expectedAPI.adapterInternalAPI.UUID, actualAPI.adapterInternalAPI.UUID) + assert.Equal(t, expectedAPI.adapterInternalAPI.EnvType, actualAPI.adapterInternalAPI.EnvType, "Not expected API EnvType found, expected: %v but found: %v", + expectedAPI.adapterInternalAPI.EnvType, actualAPI.adapterInternalAPI.EnvType) + assert.Equal(t, expectedAPI.adapterInternalAPI.OrganizationID, actualAPI.adapterInternalAPI.OrganizationID, "Not expected API OrganizationID found, expected: %v but found: %v", + expectedAPI.adapterInternalAPI.OrganizationID, actualAPI.adapterInternalAPI.OrganizationID) + } + + } } - for _, vhsot := range test.deletedvHosts { - testExistsInMapping(t, orgAPIMap[test.adapterInternalAPI.OrganizationID], - GenerateIdentifierForAPIWithUUID(vhsot, test.adapterInternalAPI.UUID), false) + } + assert.Equal(t, len(test.expectedOrgIDLatestAPIVersionMap), len(orgIDLatestAPIVersionMap), "orgIDLatestAPIVersionMap length is different, expected: %v but found: %v", + len(test.expectedOrgIDLatestAPIVersionMap), len(orgIDLatestAPIVersionMap)) + for orgID, orgAPIs := range test.expectedOrgIDLatestAPIVersionMap { + if orgAPI, ok := orgIDLatestAPIVersionMap[orgID]; !ok { + t.Errorf("orgIDLatestAPIVersionMap has no entry with the organization: %s, %v", orgID, orgIDLatestAPIVersionMap) + } else { + assert.Equal(t, len(test.expectedOrgIDLatestAPIVersionMap[orgID]), len(orgAPIs), "orgAPI length is different, expected: %v but found: %v", + len(test.expectedOrgIDLatestAPIVersionMap[orgID]), len(orgAPIs)) + for vuuid, expectedAPI := range test.expectedOrgIDLatestAPIVersionMap[orgID] { + if actualAPI, ok := orgAPI[vuuid]; !ok { + t.Errorf("orgIDLatestAPIVersionMap has not updated with new entry with the key for %s, %v", + vuuid, orgIDLatestAPIVersionMap) + } else { + assert.Equal(t, len(expectedAPI), len(actualAPI), "orgAPI for %v length is different, expected: %v but found: %v", + vuuid, len(expectedAPI), len(actualAPI)) + for version, expectedVersion := range expectedAPI { + if actualVersion, ok := actualAPI[version]; !ok { + t.Errorf("orgIDLatestAPIVersionMap has not updated with new entry with the key for %v: %s, %v", + vuuid, version, orgIDLatestAPIVersionMap) + } else { + assert.Equal(t, expectedVersion.Version, actualVersion.Version, "Not expected API Version found for %v in %v, expected: %v but found: %v", + vuuid, version, expectedVersion.Version, actualVersion.Version) + assert.Equal(t, expectedVersion.Major, actualVersion.Major, "Not expected API Major found for %v in %v, expected: %v but found: %v", + vuuid, version, expectedVersion.Major, actualVersion.Major) + assert.Equal(t, expectedVersion.Minor, actualVersion.Minor, "Not expected API Minor found for %v in %v, expected: %v but found: %v", + vuuid, version, expectedVersion.Minor, actualVersion.Minor) + assert.Equal(t, *expectedVersion.Patch, *actualVersion.Patch, "Not expected API Patch found for %v in %v, expected: %v but found: %v", + vuuid, version, *expectedVersion.Patch, *actualVersion.Patch) + } + } + } + } } } }) } } - -func testExistsInMapping[V any, M map[string]V](t *testing.T, mapping M, key string, checkExists bool) { - _, ok := mapping[key] - if checkExists { - if !ok { - t.Errorf("Not found mapping for key %s in map %v", key, mapping) - } - } else { - if ok { - t.Errorf("Found mapping for key %s in map %v", key, mapping) - } - } -} diff --git a/adapter/internal/discovery/xds/server_utils.go b/adapter/internal/discovery/xds/server_utils.go index ffb776f58..671db1dba 100644 --- a/adapter/internal/discovery/xds/server_utils.go +++ b/adapter/internal/discovery/xds/server_utils.go @@ -19,31 +19,8 @@ package xds import ( "fmt" - - "github.com/wso2/apk/adapter/pkg/utils/stringutils" ) -// getEnvironmentsToBeDeleted returns an slice of environments APIs to be u-deployed from -// by considering existing environments list and environments that APIs are wished to be un-deployed -func getEnvironmentsToBeDeleted(existingEnvs, deleteEnvs []string) (toBeDel []string, toBeKept []string) { - toBeDel = make([]string, 0, len(deleteEnvs)) - toBeKept = make([]string, 0, len(deleteEnvs)) - - // if deleteEnvs is empty (deleteEnvs wished to be deleted), delete all environments - if len(deleteEnvs) == 0 { - return existingEnvs, []string{} - } - // otherwise delete env if it wished to - for _, existingEnv := range existingEnvs { - if stringutils.StringInSlice(existingEnv, deleteEnvs) { - toBeDel = append(toBeDel, existingEnv) - } else { - toBeKept = append(toBeKept, existingEnv) - } - } - return -} - // GetvHostsIdentifier creates a identifier for vHosts for a API considering prod // and sand env func GetvHostsIdentifier(UUID string, envType string) string { diff --git a/adapter/internal/discovery/xds/server_utils_test.go b/adapter/internal/discovery/xds/server_utils_test.go deleted file mode 100644 index 9cc01fba1..000000000 --- a/adapter/internal/discovery/xds/server_utils_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package xds - -import ( - "reflect" - "testing" -) - -func TestGetEnvironmentsToBeDeleted(t *testing.T) { - tests := []struct { - name string - existingEnvs, deleteEnvs []string - toBeDel, toBeKept []string - }{ - { - // Delete all envs - name: "Delete_all_environments_when_envs_supplied", - existingEnvs: []string{"Label1", "Label2"}, - deleteEnvs: []string{"Label1", "Label2"}, - toBeDel: []string{"Label1", "Label2"}, - toBeKept: []string{}, - }, - { - // Delete all envs - name: "Delete_all_environments_when_no_envs_supplied", - existingEnvs: []string{"Label1", "Label2", "Label3"}, - deleteEnvs: []string{}, - toBeDel: []string{"Label1", "Label2", "Label3"}, - toBeKept: []string{}, - }, - { - // Delete all envs - name: "Delete_all_environments_when_no_envs_supplied_nil", - existingEnvs: []string{"Label1", "Label2", "Label3"}, - deleteEnvs: nil, - toBeDel: []string{"Label1", "Label2", "Label3"}, - toBeKept: []string{}, - }, - { - // Delete some envs - name: "Delete_some_envs", - existingEnvs: []string{"Label1", "Label2", "Label3"}, - deleteEnvs: []string{"Label2", "Foo"}, // Foo should be ignored - toBeDel: []string{"Label2"}, - toBeKept: []string{"Label1", "Label3"}, - }, - { - // Delete nothing - name: "Delete_nothing", - existingEnvs: []string{"Label1", "Label2", "Label3"}, - deleteEnvs: []string{"Foo"}, // Foo should be ignored - toBeDel: []string{}, - toBeKept: []string{"Label1", "Label2", "Label3"}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - toBeDel, toBeKept := getEnvironmentsToBeDeleted(test.existingEnvs, test.deleteEnvs) - if !reflect.DeepEqual(toBeDel, test.toBeDel) { - t.Errorf("expected deleted environments %v but found %v", test.toBeDel, toBeDel) - } - if !reflect.DeepEqual(toBeKept, test.toBeKept) { - t.Errorf("expected deleted environments %v but found %v", test.toBeKept, toBeKept) - } - }) - } -} 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 9650162f6..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 ( @@ -141,7 +127,7 @@ func UpdateRoutesConfig(routeConfig *routev3.RouteConfiguration, vhostToRouteArr // GetEnforcerAPI retrieves the ApiDS object model for a given swagger definition // along with the vhost to deploy the API. -func GetEnforcerAPI(adapterInternalAPI model.AdapterInternalAPI, vhost string) *api.Api { +func GetEnforcerAPI(adapterInternalAPI *model.AdapterInternalAPI, vhost string) *api.Api { resources := []*api.Resource{} isMockedAPI := false clientCertificates := []*api.Certificate{} @@ -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 4998cca17..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 @@ -1619,7 +1619,7 @@ func createInterceptorAPIClusters(adapterInternalAPI *model.AdapterInternalAPI, apiResponseInterceptor = adapterInternalAPI.GetInterceptor(adapterInternalAPI.GetVendorExtensions(), xWso2responseInterceptor, APILevelInterceptor) // if lua filter exists on api level, add cluster if apiResponseInterceptor.Enable { - logger.LoggerOasparser.Debugln("API level response interceptors found for " + adapterInternalAPI.GetID()) + logger.LoggerOasparser.Debugln("API level response interceptors found for " + apiTitle) apiResponseInterceptor.ClusterName = getClusterName(responseInterceptClustersNamePrefix, organizationID, vHost, apiTitle, apiVersion, "") cluster, addresses, err := CreateLuaCluster(interceptorCerts, apiResponseInterceptor) 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/oasparser/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index a8746a788..a5318d66a 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -43,7 +43,6 @@ import ( // adapter internal representation. The values are populated from the operator. The pathItem level information is represented // by the resources array which contains the Resource entries. type AdapterInternalAPI struct { - id string UUID string apiType string description string @@ -315,11 +314,6 @@ func (adapterInternalAPI *AdapterInternalAPI) GetDisableMtls() bool { return adapterInternalAPI.disableMtls } -// GetID returns the Id of the API -func (adapterInternalAPI *AdapterInternalAPI) GetID() string { - return adapterInternalAPI.id -} - // GetXWso2RequestBodyPass returns boolean value to indicate // whether it is allowed to pass request body to the enforcer or not. func (adapterInternalAPI *AdapterInternalAPI) GetXWso2RequestBodyPass() bool { @@ -350,11 +344,6 @@ func (adapterInternalAPI *AdapterInternalAPI) SetClientCerts(apiName string, cer adapterInternalAPI.clientCertificates = clientCerts } -// SetID set the Id of the API -func (adapterInternalAPI *AdapterInternalAPI) SetID(id string) { - adapterInternalAPI.id = id -} - // SetAPIDefinitionFile sets the API Definition File. func (adapterInternalAPI *AdapterInternalAPI) SetAPIDefinitionFile(file []byte) { adapterInternalAPI.apiDefinitionFile = file diff --git a/adapter/internal/oasparser/model/policy_container.go b/adapter/internal/oasparser/model/policy_container.go index 80d87a0d3..031d43df4 100644 --- a/adapter/internal/oasparser/model/policy_container.go +++ b/adapter/internal/oasparser/model/policy_container.go @@ -41,6 +41,8 @@ var ( } ) +// todo(amali) remove these files as this is no longer functional + // PolicyFlow holds list of Policies in a operation (in one flow: In, Out or Fault) type PolicyFlow string @@ -98,7 +100,7 @@ func (p PolicyContainerMap) GetFormattedOperationalPolicies(policies OperationPo if fmtPolicy, err := p.getFormattedPolicyFromTemplated(policy, policyInFlow, swagger); err == nil { fmtPolicies.Request = append(fmtPolicies.Request, fmtPolicy) loggers.LoggerOasparser.Debugf("Applying operation policy %q in request flow, for API %q in org %q, formatted policy %v", - policy.GetFullName(), swagger.GetID(), swagger.OrganizationID, fmtPolicy) + policy.GetFullName(), swagger.UUID, swagger.OrganizationID, fmtPolicy) } else { return fmtPolicies, err } @@ -108,7 +110,7 @@ func (p PolicyContainerMap) GetFormattedOperationalPolicies(policies OperationPo if fmtPolicy, err := p.getFormattedPolicyFromTemplated(policy, policyOutFlow, swagger); err == nil { fmtPolicies.Response = append(fmtPolicies.Response, fmtPolicy) loggers.LoggerOasparser.Debugf("Applying operation policy %q in response flow, for API %q in org %q, formatted policy %v", - policy.GetFullName(), swagger.GetID(), swagger.OrganizationID, fmtPolicy) + policy.GetFullName(), swagger.UUID, swagger.OrganizationID, fmtPolicy) } else { return fmtPolicies, err } @@ -118,7 +120,7 @@ func (p PolicyContainerMap) GetFormattedOperationalPolicies(policies OperationPo if fmtPolicy, err := p.getFormattedPolicyFromTemplated(policy, policyFaultFlow, swagger); err == nil { fmtPolicies.Fault = append(fmtPolicies.Fault, fmtPolicy) loggers.LoggerOasparser.Debugf("Applying operation policy %q in fault flow, for API %q in org %q, formatted policy %v", - policy.GetFullName(), swagger.GetID(), swagger.OrganizationID, fmtPolicy) + policy.GetFullName(), swagger.UUID, swagger.OrganizationID, fmtPolicy) } else { return fmtPolicies, err } @@ -132,28 +134,27 @@ func (p PolicyContainerMap) getFormattedPolicyFromTemplated(policy Policy, flow policyFullName := policy.GetFullName() spec := p[policyFullName].Specification if err := spec.validatePolicy(policy, flow); err != nil { - swagger.GetID() - loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2204, logging.MINOR, "Operation policy validation failed for API %q in org %q:, policy %q: %v", swagger.GetID(), swagger.OrganizationID, policyFullName, err)) + loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2204, logging.MINOR, "Operation policy validation failed for API %q in org %q:, policy %q: %v", swagger.UUID, swagger.OrganizationID, policyFullName, err)) return policy, err } defRaw := p[policyFullName].Definition.RawData t, err := template.New("policy-def").Funcs(policyDefFuncMap).Parse(string(defRaw)) if err != nil { - loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2205, logging.MINOR, "Error parsing the operation policy definition %q into go template of the API %q in org %q: %v", policyFullName, swagger.GetID(), swagger.OrganizationID, err)) + loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2205, logging.MINOR, "Error parsing the operation policy definition %q into go template of the API %q in org %q: %v", policyFullName, swagger.UUID, swagger.OrganizationID, err)) return Policy{}, err } var out bytes.Buffer err = t.Execute(&out, policy.Parameters) if err != nil { - loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2206, logging.MINOR, "Error parsing operation policy definition %q of the API %q in org %q: %v", policyFullName, swagger.GetID(), swagger.OrganizationID, err)) + loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2206, logging.MINOR, "Error parsing operation policy definition %q of the API %q in org %q: %v", policyFullName, swagger.UUID, swagger.OrganizationID, err)) return Policy{}, err } def := PolicyDefinition{} if err := yaml.Unmarshal(out.Bytes(), &def); err != nil { - loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2207, logging.MINOR, "Error parsing formalized operation policy definition %q into yaml of the API %q in org %q: %v", policyFullName, swagger.GetID(), swagger.OrganizationID, err)) + loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2207, logging.MINOR, "Error parsing formalized operation policy definition %q into yaml of the API %q in org %q: %v", policyFullName, swagger.UUID, swagger.OrganizationID, err)) return Policy{}, err } @@ -168,7 +169,7 @@ func (p PolicyContainerMap) getFormattedPolicyFromTemplated(policy Policy, flow // Required params may be comming from default values as defined in the policy specification // Hence do the validation after filling default values if err := validatePolicyAction(&policy); err != nil { - loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2208, logging.MINOR, "API policy validation failed, policy: %q of the API %q in org %q: %v", policyFullName, swagger.GetID(), swagger.OrganizationID, err)) + loggers.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2208, logging.MINOR, "API policy validation failed, policy: %q of the API %q in org %q: %v", policyFullName, swagger.UUID, swagger.OrganizationID, err)) return Policy{}, err } return policy, nil diff --git a/adapter/internal/operator/synchronizer/api_state.go b/adapter/internal/operator/synchronizer/api_state.go index e15717249..10e50c223 100644 --- a/adapter/internal/operator/synchronizer/api_state.go +++ b/adapter/internal/operator/synchronizer/api_state.go @@ -41,7 +41,6 @@ type APIState struct { InterceptorServiceMapping map[string]v1alpha1.InterceptorService BackendJWTMapping map[string]v1alpha1.BackendJWT APIDefinitionFile []byte - OldOrganizationID string SubscriptionValidation bool MutualSSL *v1alpha2.MutualSSL } diff --git a/adapter/internal/operator/synchronizer/data_store.go b/adapter/internal/operator/synchronizer/data_store.go index 80265707d..8378998a3 100644 --- a/adapter/internal/operator/synchronizer/data_store.go +++ b/adapter/internal/operator/synchronizer/data_store.go @@ -77,7 +77,6 @@ func (ods *OperatorDataStore) processAPIState(apiNamespacedName types.Namespaced cachedAPI := ods.apiStore[apiNamespacedName] if apiState.APIDefinition.Generation > cachedAPI.APIDefinition.Generation { - cachedAPI.OldOrganizationID = cachedAPI.APIDefinition.Spec.Organization cachedAPI.APIDefinition = apiState.APIDefinition updated = true events = append(events, "API Definition") diff --git a/adapter/internal/operator/synchronizer/gql_api.go b/adapter/internal/operator/synchronizer/gql_api.go index b65566600..6561b9cdc 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,53 +103,27 @@ 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") - } - - updatedLabelsMap := make(map[string]struct{}) - listenerName := listeners[0] - sectionName := relativeSectionNames[0] - if len(listeners) != 0 { - updatedLabels, err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) - if err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MAJOR, "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", - adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) - return nil, nil, err - } - for newLabel := range updatedLabels { - updatedLabelsMap[newLabel] = struct{}{} - } - } - - return &adapterInternalAPI, updatedLabelsMap, nil + 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{} { + vHosts := make(map[string]struct{}) for _, hostName := range gqlRoute.Spec.Hostnames { - vHosts = append(vHosts, string(hostName)) + vHosts[string(hostName)] = struct{}{} } return vHosts } // getLabelsForAPI returns the labels related to an API. -func getLabelsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) []string { - var labels []string - var err error +func getLabelsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) map[string]struct{} { + labels := make(map[string]struct{}) for _, parentRef := range gqlRoute.Spec.ParentRefs { - err = xds.SanitizeGateway(string(parentRef.Name), false) + err := xds.SanitizeGateway(string(parentRef.Name), false) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2653, logging.CRITICAL, "Gateway Label is invalid: %s", string(parentRef.Name))) } else { - labels = append(labels, string(parentRef.Name)) + labels[string(parentRef.Name)] = struct{}{} } } return labels @@ -143,10 +145,7 @@ func getListenersForGQLAPI(gqlRoute *v1alpha2.GQLRoute, apiUUID string) ([]strin if found { // find the matching listener matchedListener, listenerFound := common.FindElement(gateway.Spec.Listeners, func(listener gwapiv1b1.Listener) bool { - if string(listener.Name) == string(*parentRef.SectionName) { - return true - } - return false + return string(listener.Name) == string(*parentRef.SectionName) }) if listenerFound { sectionNames = append(sectionNames, string(matchedListener.Name)) @@ -161,9 +160,8 @@ func getListenersForGQLAPI(gqlRoute *v1alpha2.GQLRoute, apiUUID string) ([]strin func deleteGQLAPIFromEnv(gqlRoute *v1alpha2.GQLRoute, apiState APIState) error { labels := getLabelsForGQLAPI(gqlRoute) - org := apiState.APIDefinition.Spec.Organization uuid := string(apiState.APIDefinition.ObjectMeta.UID) - return xds.DeleteAPICREvent(labels, uuid, org) + return xds.DeleteAPI(uuid, labels) } // undeployGQLAPIInGateway undeploys the related API in CREATE and UPDATE events. diff --git a/adapter/internal/operator/synchronizer/rest_api.go b/adapter/internal/operator/synchronizer/rest_api.go index 38e5b218c..0bf5a47e4 100644 --- a/adapter/internal/operator/synchronizer/rest_api.go +++ b/adapter/internal/operator/synchronizer/rest_api.go @@ -40,7 +40,7 @@ func undeployRestAPIInGateway(apiState APIState) error { if err != nil { loggers.LoggerXds.ErrorC(logging.PrintError(logging.Error2630, logging.MAJOR, "Error undeploying prod httpRoute of API : %v in Organization %v from environments %v."+ " Hence not checking on deleting the sand httpRoute of the API", string(apiState.APIDefinition.ObjectMeta.UID), apiState.APIDefinition.Spec.Organization, - getLabelsForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined))) + getGatewayNameForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined))) return err } if apiState.SandHTTPRoute != nil { @@ -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,55 +122,31 @@ 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 := getLabelsForAPI(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 { - updatedLabels, err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) - if err != nil { - loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2633, logging.MAJOR, "Error updating the API : %s:%s in vhosts: %s, API_UUID: %v. %v", - adapterInternalAPI.GetTitle(), adapterInternalAPI.GetVersion(), vHosts, adapterInternalAPI.UUID, err)) - return nil, nil, err - } - for newLabel := range updatedLabels { - updatedLabelsMap[newLabel] = struct{}{} - } + return 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{}) for _, hostName := range httpRoute.Spec.Hostnames { - vHosts = append(vHosts, string(hostName)) + vHosts[string(hostName)] = struct{}{} } return vHosts } -// getLabelsForAPI returns the labels related to an API. -func getLabelsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { - var labels []string +// getGatewayNameForAPI returns the labels related to an API. +func getGatewayNameForAPI(httpRoute *gwapiv1b1.HTTPRoute) map[string]struct{} { + labels := make(map[string]struct{}) var err error for _, parentRef := range httpRoute.Spec.ParentRefs { err = xds.SanitizeGateway(string(parentRef.Name), false) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2653, logging.CRITICAL, "Gateway Label is invalid: %s", string(parentRef.Name))) } else { - labels = append(labels, string(parentRef.Name)) + labels[string(parentRef.Name)] = struct{}{} } } return labels @@ -178,8 +182,7 @@ func getListenersForAPI(httpRoute *gwapiv1b1.HTTPRoute, apiUUID string) ([]strin } func deleteAPIFromEnv(httpRoute *gwapiv1b1.HTTPRoute, apiState APIState) error { - labels := getLabelsForAPI(httpRoute) - org := apiState.APIDefinition.Spec.Organization + labels := getGatewayNameForAPI(httpRoute) uuid := string(apiState.APIDefinition.ObjectMeta.UID) - return xds.DeleteAPICREvent(labels, uuid, org) + return xds.DeleteAPI(uuid, labels) } diff --git a/adapter/internal/operator/synchronizer/synchronizer.go b/adapter/internal/operator/synchronizer/synchronizer.go index 8f177d80c..a61aa181c 100644 --- a/adapter/internal/operator/synchronizer/synchronizer.go +++ b/adapter/internal/operator/synchronizer/synchronizer.go @@ -30,7 +30,6 @@ import ( "github.com/wso2/apk/adapter/config" "github.com/wso2/apk/adapter/internal/discovery/xds" "github.com/wso2/apk/adapter/internal/loggers" - "github.com/wso2/apk/adapter/internal/oasparser/model" "github.com/wso2/apk/adapter/internal/operator/constants" "github.com/wso2/apk/adapter/internal/operator/utils" "github.com/wso2/apk/adapter/pkg/logging" @@ -133,28 +132,19 @@ 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) - if len(apiState.OldOrganizationID) != 0 { - xds.RemoveAPIFromOrgAPIMap(string((*apiState.APIDefinition).ObjectMeta.UID), apiState.OldOrganizationID) + // Remove the API from the internal maps before adding it again + oldGatewayNames := xds.RemoveAPIFromAllInternalMaps(string((*apiState.APIDefinition).ObjectMeta.UID)) + for label := range oldGatewayNames { + updatedLabelsMap[label] = struct{}{} } if apiState.APIDefinition.Spec.APIType == "REST" { - if apiState.ProdHTTPRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Production) - } - if apiState.SandHTTPRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Sandbox) - } - 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", string(apiState.APIDefinition.Spec.APIName), apiState.APIDefinition.Spec.Organization, - getLabelsForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined), err)) + getGatewayNameForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined), err)) // removing failed updates from the events list because this will be sent to partition server event.Events = append(event.Events[:i], event.Events[i+1:]...) continue @@ -165,12 +155,12 @@ 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", string(apiState.APIDefinition.Spec.APIName), apiState.APIDefinition.Spec.Organization, - getLabelsForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined), err)) + getGatewayNameForAPI(apiState.ProdHTTPRoute.HTTPRouteCombined), err)) // removing failed updates from the events list because this will be sent to partition server event.Events = append(event.Events[:i], event.Events[i+1:]...) continue @@ -182,18 +172,8 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv } if apiState.APIDefinition.Spec.APIType == "GraphQL" { - if apiState.ProdGQLRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Production) - } - if apiState.SandGQLRoute == nil { - var adapterInternalAPI model.AdapterInternalAPI - adapterInternalAPI.SetInfoAPICR(*apiState.APIDefinition) - xds.RemoveAPICacheForEnv(adapterInternalAPI, constants.Sandbox) - } 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", @@ -206,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", diff --git a/adapter/pkg/semanticversion/semantic_version.go b/adapter/pkg/semanticversion/semantic_version.go index 3750e5940..db44db230 100644 --- a/adapter/pkg/semanticversion/semantic_version.go +++ b/adapter/pkg/semanticversion/semantic_version.go @@ -17,12 +17,9 @@ package semanticversion import ( - "errors" "fmt" "strconv" "strings" - - logger "github.com/wso2/apk/adapter/pkg/loggers" ) // SemVersion is the struct to store the version components of an API @@ -34,16 +31,13 @@ type SemVersion struct { } // ValidateAndGetVersionComponents validates version string and extracts version components -func ValidateAndGetVersionComponents(version string, apiName string) (*SemVersion, error) { +func ValidateAndGetVersionComponents(version string) (*SemVersion, error) { versionComponents := strings.Split(version, ".") // If the versionComponents length is less than 2, return error if len(versionComponents) < 2 || !strings.HasPrefix(versionComponents[0], "v") { - logger.LoggerSemanticVersion.Errorf("API version validation failed for API: %v. API Version: %v", apiName, version) - errMessage := "Invalid version: " + version + " for API: " + apiName + - ". API version should be in the format x.y.z, x.y, vx.y.z or vx.y where x,y,z are non-negative integers" + - " and v is version prefix" - return nil, errors.New(errMessage) + return nil, fmt.Errorf("invalid version: %v. API version should be in the format x.y.z, x.y, vx.y.z or vx.y where x,y,z are non-negative integers"+ + " and v is version prefix", version) } majorVersionStr := strings.TrimPrefix(versionComponents[0], "v") @@ -51,13 +45,11 @@ func ValidateAndGetVersionComponents(version string, apiName string) (*SemVersio majorVersion, majorVersionConvErr := strconv.Atoi(majorVersionStr) minorVersion, minorVersionConvErr := strconv.Atoi(versionComponents[1]) if majorVersionConvErr != nil || majorVersion < 0 { - logger.LoggerSemanticVersion.Errorf(fmt.Sprintf("API major version should be a non-negative integer in API: %v. API Version: %v", apiName, version), majorVersionConvErr) - return nil, errors.New("invalid version format") + return nil, fmt.Errorf("invalid version format. API major version should be a non-negative integer in API Version: %v, %v", version, majorVersionConvErr) } if minorVersionConvErr != nil || minorVersion < 0 { - logger.LoggerSemanticVersion.Errorf(fmt.Sprintf("API minor version should be a non-negative integer in API: %v. API Version: %v", apiName, version), minorVersionConvErr) - return nil, errors.New("invalid version format") + return nil, fmt.Errorf("invalid version format. API minor version should be a non-negative integer in API Version: %v, %v", version, minorVersionConvErr) } if len(versionComponents) == 2 { @@ -71,8 +63,7 @@ func ValidateAndGetVersionComponents(version string, apiName string) (*SemVersio patchVersion, patchVersionConvErr := strconv.Atoi(versionComponents[2]) if patchVersionConvErr != nil { - logger.LoggerSemanticVersion.Errorf(fmt.Sprintf("API patch version should be an integer in API: %v. API Version: %v", apiName, version), patchVersionConvErr) - return nil, errors.New("invalid version format") + return nil, fmt.Errorf("invalid version format. API patch version should be an integer in API Version: %v, %v", version, patchVersionConvErr) } return &SemVersion{ Version: version, diff --git a/adapter/pkg/semanticversion/semantic_version_test.go b/adapter/pkg/semanticversion/semantic_version_test.go index 53a7e1bf2..ff01d2f20 100644 --- a/adapter/pkg/semanticversion/semantic_version_test.go +++ b/adapter/pkg/semanticversion/semantic_version_test.go @@ -128,34 +128,34 @@ func TestValidateAndGetVersionComponents(t *testing.T) { version: "1.2.3", apiName: "TestAPI", expectedResult: nil, - expectedError: errors.New("Invalid version: 1.2.3 for API: TestAPI. API version should be in the format x.y.z, x.y, vx.y.z or vx.y where x,y,z are non-negative integers and v is version prefix"), + expectedError: errors.New("invalid version: 1.2.3. API version should be in the format x.y.z, x.y, vx.y.z or vx.y where x,y,z are non-negative integers and v is version prefix"), }, { name: "Invalid version format - negative major version", version: "v-1.2.3", apiName: "TestAPI", expectedResult: nil, - expectedError: errors.New("invalid version format"), + expectedError: errors.New("invalid version format. API major version should be a non-negative integer in API Version: v-1.2.3, "), }, { name: "Invalid version format - negative minor version", version: "v1.-2.3", apiName: "TestAPI", expectedResult: nil, - expectedError: errors.New("invalid version format"), + expectedError: errors.New("invalid version format. API minor version should be a non-negative integer in API Version: v1.-2.3, "), }, { name: "Invalid version format - patch version not an integer", version: "v1.2.three", apiName: "TestAPI", expectedResult: nil, - expectedError: errors.New("invalid version format"), + expectedError: errors.New("invalid version format. API patch version should be an integer in API Version: v1.2.three, strconv.Atoi: parsing \"three\": invalid syntax"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := ValidateAndGetVersionComponents(tt.version, tt.apiName) + result, err := ValidateAndGetVersionComponents(tt.version) // Check for errors if (err != nil && tt.expectedError == nil) || (err == nil && tt.expectedError != nil) || (err != nil && tt.expectedError != nil && err.Error() != tt.expectedError.Error()) { diff --git a/developer/tryout/samples/sample-api.yaml b/developer/tryout/samples/sample-api.yaml index a954bae37..50b051119 100644 --- a/developer/tryout/samples/sample-api.yaml +++ b/developer/tryout/samples/sample-api.yaml @@ -30,7 +30,7 @@ spec: sandbox: - routeRefs: - sand-http-route-http-bin-api - organization: a3b58ccf-6ecc-4557-b5bb-0a35cce38256 + organization: default --- apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute diff --git a/test/cucumber-tests/README.md b/test/cucumber-tests/README.md index 7d57f439c..1159238d4 100644 --- a/test/cucumber-tests/README.md +++ b/test/cucumber-tests/README.md @@ -46,7 +46,7 @@ To create a new feature, follow these steps: 3. Port forward router-service to use localhost. ```bash - kubectl port-forward svc/apk-test-setup-wso2-apk-router-service -n apk-integration-test 9095:9095 + kubectl port-forward svc/apk-test-setup-wso2-apk-gateway-service -n apk-integration-test 9095:9095 ``` 4. Add the following DNS mappings to `/etc/hosts` file.