diff --git a/adapter/internal/discovery/xds/marshaller.go b/adapter/internal/discovery/xds/marshaller.go index d36cee776b..0b4fcfc943 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 6a94e19d2d..2a39d1f562 100644 --- a/adapter/internal/discovery/xds/semantic_versioning.go +++ b/adapter/internal/discovery/xds/semantic_versioning.go @@ -25,7 +25,6 @@ import ( "github.com/wso2/apk/adapter/config" logger "github.com/wso2/apk/adapter/internal/loggers" logging "github.com/wso2/apk/adapter/internal/logging" - "github.com/wso2/apk/adapter/internal/oasparser/model" semantic_version "github.com/wso2/apk/adapter/pkg/semanticversion" ) @@ -68,8 +67,7 @@ func GetMinorVersionRange(semVersion semantic_version.SemVersion) string { } func updateRoutingRulesOnAPIUpdate(organizationID, apiIdentifier, apiName, apiVersion, vHost string) { - - apiSemVersion, err := semantic_version.ValidateAndGetVersionComponents(apiVersion, apiName) + apiSemVersion, err := semantic_version.ValidateAndGetVersionComponents(apiVersion) // If the version validation is not success, we just proceed without intelligent version // Valid version pattern: vx.y.z or vx.y where x, y and z are non-negative integers and v is a prefix if err != nil && apiSemVersion == nil { @@ -90,9 +88,9 @@ func updateRoutingRulesOnAPIUpdate(organizationID, apiIdentifier, apiName, apiVe // 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] { + for vuuid, envoyInternalAPI := range orgAPIMap[organizationID] { // API's all versions in the same vHost - if envoyInternalAPI.adapterInternalAPI.GetTitle() == apiName && isVHostMatched(organizationID, vHost) { + if envoyInternalAPI.adapterInternalAPI.GetTitle() == apiName && strings.HasPrefix(vuuid+":", vHost) { if (isMajorRangeRegexAvailable && envoyInternalAPI.adapterInternalAPI.GetVersion() == existingMajorRangeLatestSemVersion.Version) || (isMinorRangeRegexAvailable && envoyInternalAPI.adapterInternalAPI.GetVersion() == existingMinorRangeLatestSemVersion.Version) { @@ -167,8 +165,7 @@ func updateRoutingRulesOnAPIUpdate(organizationID, apiIdentifier, apiName, apiVe } 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 } @@ -176,183 +173,46 @@ func updateRoutingRulesOnAPIUpdate(organizationID, apiIdentifier, apiName, apiVe } } -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) - } +func updateSemanticVersioning(org string, apiRangeIdentifiers map[string]struct{}) { + // 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, - } - 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 - } - } - } + // 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 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 { + if _, ok := currentAPISemVersion[GetMajorVersionRange(*semVersion)]; !ok { + currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion + } else { + if currentAPISemVersion[GetMajorVersionRange(*semVersion)].Compare(*semVersion) { + currentAPISemVersion[GetMajorVersionRange(*semVersion)] = *semVersion } } - 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, - }, - } - 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 - } + if _, ok := currentAPISemVersion[GetMinorVersionRange(*semVersion)]; !ok { + currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion } else { - delete(orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier], minorVersionRange) - } - } - } - - if orgAPIMap, apiAvailable := orgIDLatestAPIVersionMap[organizationID][apiRangeIdentifier]; apiAvailable && len(orgAPIMap) == 0 { - delete(orgIDLatestAPIVersionMap[organizationID], apiRangeIdentifier) - if orgMap := orgIDLatestAPIVersionMap[organizationID]; len(orgMap) == 0 { - delete(orgIDLatestAPIVersionMap, organizationID) - } - } - -} - -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 + if currentAPISemVersion[GetMinorVersionRange(*semVersion)].Compare(*semVersion) { + currentAPISemVersion[GetMinorVersionRange(*semVersion)] = *semVersion } } } } - return false } func getRoutesForAPIIdentifier(organizationID, apiIdentifier string) []*routev3.Route { @@ -374,7 +234,7 @@ func isSemanticVersioningEnabled(apiName, apiVersion string) bool { return false } - apiSemVersion, err := semantic_version.ValidateAndGetVersionComponents(apiVersion, apiName) + apiSemVersion, err := semantic_version.ValidateAndGetVersionComponents(apiVersion) if err != nil && apiSemVersion == nil { return false } diff --git a/adapter/internal/discovery/xds/semantic_versioning_test.go b/adapter/internal/discovery/xds/semantic_versioning_test.go index d08d277185..939a840566 100644 --- a/adapter/internal/discovery/xds/semantic_versioning_test.go +++ b/adapter/internal/discovery/xds/semantic_versioning_test.go @@ -264,68 +264,6 @@ 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{ @@ -433,27 +371,20 @@ func TestUpdateRoutingRulesOnAPIUpdate(t *testing.T) { 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 @@ -624,17 +555,17 @@ func TestUpdateRoutingRulesOnAPIDelete(t *testing.T) { 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, + adapterInternalAPI: &apiID3, routes: generateRoutes(apiID3ResourcePath), }, }, @@ -644,29 +575,28 @@ func TestUpdateRoutingRulesOnAPIDelete(t *testing.T) { name string organizationID string apiIdentifier string - api model.AdapterInternalAPI + api *model.AdapterInternalAPI deleteVersion string }{ { name: "Delete latest major version", organizationID: "org3", apiIdentifier: "gw.com:apiID1", - api: apiID1, + api: &apiID1, deleteVersion: "v1.0", }, { name: "Delete latest minor version", organizationID: "org4", apiIdentifier: "gw.com:apiID3", - api: apiID3, + api: &apiID3, deleteVersion: "v1.5", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - updateRoutingRulesOnAPIDelete(tt.organizationID, tt.apiIdentifier, tt.api) - + updateSemanticVersioning(tt.organizationID, map[string]struct{}{tt.apiIdentifier: {}}) if _, ok := orgIDLatestAPIVersionMap[tt.organizationID]; ok { if _, ok := orgIDLatestAPIVersionMap[tt.organizationID][tt.apiIdentifier]; ok { if _, ok := orgIDLatestAPIVersionMap[tt.organizationID][tt.apiIdentifier][tt.deleteVersion]; ok { diff --git a/adapter/internal/discovery/xds/server.go b/adapter/internal/discovery/xds/server.go index 183c69e856..8a7b4cf235 100644 --- a/adapter/internal/discovery/xds/server.go +++ b/adapter/internal/discovery/xds/server.go @@ -51,14 +51,13 @@ import ( 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 @@ -96,11 +95,10 @@ var ( enforcerRevokedTokensCache wso2_cache.SnapshotCache enforcerThrottleDataCache wso2_cache.SnapshotCache - // todo(amali) there can be multiple vhosts for one EnvoyInternalAPI so handle this + // 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 - 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 - orgIDLatestAPIVersionMap map[string]map[string]map[string]semantic_version.SemVersion // organizationID -> Vhost:APIName -> Version Range -> Latest API Version + orgIDLatestAPIVersionMap map[string]map[string]map[string]semantic_version.SemVersion // organizationID -> Vhost:APIName -> VersionRange(vx/vx.x; x is int) -> Latest API Version // Envoy Label as map key // TODO(amali) use this without generating all again. @@ -153,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,7 +208,11 @@ func GetEnforcerThrottleDataCache() wso2_cache.SnapshotCache { // DeleteAPI deletes API with the given UUID from the given gw environments func DeleteAPI(uuid string, gatewayNames map[string]struct{}) error { - RemoveAPIFromAllInternalMaps(uuid) + + oldGatewayNames := RemoveAPIFromAllInternalMaps(uuid) + for oldGatewayName := range oldGatewayNames { + gatewayNames[oldGatewayName] = struct{}{} + } UpdateXdsCacheOnAPIChange(gatewayNames) return nil } @@ -256,7 +256,7 @@ func GenerateEnvoyResoucesForGateway(gatewayName string) ([]types.Resource, for organizationID, entityMap := range orgAPIMap { for apiKey, envoyInternalAPI := range entityMap { - if !stringutils.StringInSlice(gatewayName, envoyInternalAPI.envoyLabels) { + if _, exists := envoyInternalAPI.envoyLabels[gatewayName]; !exists { // do nothing if the gateway is not found in the envoyInternalAPI continue } @@ -542,106 +542,71 @@ func ExtractUUIDFromAPIIdentifier(id string) (string, error) { } // RemoveAPIFromAllInternalMaps removes api from all maps -func RemoveAPIFromAllInternalMaps(uuid string) { +func RemoveAPIFromAllInternalMaps(uuid string) map[string]struct{} { mutexForInternalMapUpdate.Lock() defer mutexForInternalMapUpdate.Unlock() + + tobeUpdatedAPIRangeIdentifiers := make(map[string]struct{}, 0) + updatedLabelsMap := make(map[string]struct{}, 0) + logger.LoggerAPI.Error(orgIDLatestAPIVersionMap) for orgID, orgAPI := range orgAPIMap { - for apiIdentifier := range orgAPI { + for apiIdentifier, envoyInternalAPI := range orgAPI { if strings.HasSuffix(apiIdentifier, ":"+uuid) { + for oldLabel := range envoyInternalAPI.envoyLabels { + updatedLabelsMap[oldLabel] = struct{}{} + } 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 versionRange, latestVersion := range apiVersionMap { + if latestVersion.Version == envoyInternalAPI.adapterInternalAPI.GetVersion() { + delete(orgIDLatestAPIVersionMap[orgID][apiRangeID], versionRange) + tobeUpdatedAPIRangeIdentifiers[apiRangeID] = struct{}{} + } + } + } + } } } if len(orgAPIMap[orgID]) == 0 { delete(orgAPIMap, orgID) - delete(orgIDvHostBasepathMap, orgID) - delete(orgIDAPIvHostsMap, orgID) delete(orgIDLatestAPIVersionMap, orgID) } - } - - for orgID, basepathMap := range orgIDvHostBasepathMap { - for basepath, vhostAPIUUID := range basepathMap { - if strings.HasSuffix(vhostAPIUUID, ":"+uuid) { - delete(orgIDvHostBasepathMap[orgID], basepath) - } + if len(tobeUpdatedAPIRangeIdentifiers) > 0 { + updateSemanticVersioning(orgID, tobeUpdatedAPIRangeIdentifiers) } } - for orgID := range orgIDAPIvHostsMap { - delete(orgIDAPIvHostsMap[orgID], uuid) - } - - for orgID, orgIDLatestAPIVersion := range orgIDLatestAPIVersionMap { - for vHostAPIName := range orgIDLatestAPIVersion { - if strings.HasSuffix(vHostAPIName, ":"+uuid) { - delete(orgIDLatestAPIVersionMap[orgID], vHostAPIName) - } - } - } + return updatedLabelsMap } // 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) { +func UpdateAPICache(vHosts []string, newLabels map[string]struct{}, listener string, sectionName string, + adapterInternalAPI *model.AdapterInternalAPI) error { 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 - } - - 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 { - updatedLabelsMap[oldLabel] = struct{}{} - } - delete(orgAPIMap[adapterInternalAPI.GetOrganizationID()], apiIdentifier) - } - } - } - // Create internal mappings for new 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 := oasParser.GetRoutesClustersEndpoints(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 _, orgExists := orgAPIMap[adapterInternalAPI.GetOrganizationID()]; !orgExists { orgAPIMap[adapterInternalAPI.GetOrganizationID()] = make(map[string]*EnvoyInternalAPI) } + orgAPIMap[adapterInternalAPI.GetOrganizationID()][apiIdentifier] = &EnvoyInternalAPI{ adapterInternalAPI: adapterInternalAPI, envoyLabels: newLabels, @@ -654,11 +619,11 @@ func UpdateAPICache(vHosts []string, newLabels []string, listener string, sectio apiVersion := adapterInternalAPI.GetVersion() apiName := adapterInternalAPI.GetTitle() if isSemanticVersioningEnabled(apiName, apiVersion) { + logger.LoggerAPI.Errorf("Semantic versioning is enabled for API: %v", apiName) updateRoutingRulesOnAPIUpdate(adapterInternalAPI.OrganizationID, apiIdentifier, apiName, apiVersion, vHost) } } - - return 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 71e110920d..7e2af4c408 100644 --- a/adapter/internal/discovery/xds/server_test.go +++ b/adapter/internal/discovery/xds/server_test.go @@ -21,140 +21,455 @@ 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") + ptrOne := new(int) + *ptrOne = 1 tests := []struct { - name string - vHosts []string - labels []string - listeners []string - adapterInternalAPI model.AdapterInternalAPI - EnvType string - action string - deletedvHosts []string + name string + vHosts []string + 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: []string{"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, + }, + }, }, - 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", + 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: "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 first sand api", + vHosts: []string{"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, + }, + }, }, - 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", + 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, + }, + }, + }, }, - 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 creating second prod api", + vHosts: []string{"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, + }, + }, + }, }, - action: "DELETE", - deletedvHosts: []string{"prod1.gw.abc.com", "prod2.gw.abc.com", - "sand3.gw.abc.com", "sand4.gw.abc.com"}, }, + // { + // name: "Test updating first prod api 1 with new vhosts", + // vHosts: []string{"prod3.gw.abc.com", "prod4.gw.abc.com"}, + // labels: map[string]struct{}{"default": {}}, + // listeners: []string{"httpslistener"}, + // adapterInternalAPI: api1uuid, + // action: "UPDATE", + // expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + // "org-1": { + // "prod3.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api1uuid, + // }, + // "prod4.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api1uuid, + // }, + // "sand3.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api1sanduuid, + // }, + // "sand4.gw.abc.com:api-1-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api1sanduuid, + // }, + // "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api2uuid, + // }, + // "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api2uuid, + // }, + // }, + // }, + // }, + // { + // name: "Test deleting api 1 both prod and sand", + // labels: map[string]struct{}{"default": {}}, + // listeners: []string{"httpslistener"}, + // adapterInternalAPI: api1uuid, + // action: "DELETE", + // expectedOrgAPIMap: map[string]map[string]*EnvoyInternalAPI{ + // "org-1": { + // "prod1.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api2uuid, + // }, + // "prod2.gw.pqr.com:api-2-uuid": &EnvoyInternalAPI{ + // envoyLabels: map[string]struct{}{"default": {}}, + // adapterInternalAPI: api2uuid, + // }, + // }, + // }, + // }, } for _, test := range tests { 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) } + RemoveAPIFromAllInternalMaps(test.adapterInternalAPI.UUID) 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) + case "UPDATE": + loggers.LoggerAPI.Infof("Updating API: %v", test.adapterInternalAPI.UUID) + for label := range test.labels { + SanitizeGateway(label, true) } + UpdateAPICache(test.vHosts, test.labels, test.listeners[0], "httpslistener", test.adapterInternalAPI) case "DELETE": - gatewayNames := make(map[string]struct{}) - for _, label := range test.labels { - gatewayNames[label] = struct{}{} - } - DeleteAPI(test.adapterInternalAPI.UUID, gatewayNames) - 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 bc70833c2e..671db1dba3 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(existingGatewayNames, deleteGatewayNames []string) (toBeDel []string, toBeKept []string) { - toBeDel = make([]string, 0, len(deleteGatewayNames)) - toBeKept = make([]string, 0, len(deleteGatewayNames)) - - // if deleteEnvs is empty (deleteEnvs wished to be deleted), delete all environments - if len(deleteGatewayNames) == 0 { - return existingGatewayNames, []string{} - } - // otherwise delete env if it wished to - for _, existingEnv := range existingGatewayNames { - if stringutils.StringInSlice(existingEnv, deleteGatewayNames) { - 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 9cc01fba17..0000000000 --- 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/oasparser/config_generator.go b/adapter/internal/oasparser/config_generator.go index e5fc9314d0..5fc04d7864 100644 --- a/adapter/internal/oasparser/config_generator.go +++ b/adapter/internal/oasparser/config_generator.go @@ -141,7 +141,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{} diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index fb5e5f4d30..ac556245b2 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -1616,7 +1616,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/model/adapter_internal_api.go b/adapter/internal/oasparser/model/adapter_internal_api.go index 5e5bddddcc..498f22f1f5 100644 --- a/adapter/internal/oasparser/model/adapter_internal_api.go +++ b/adapter/internal/oasparser/model/adapter_internal_api.go @@ -42,7 +42,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 @@ -314,11 +313,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 { @@ -349,11 +343,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 80d87a0d3c..031d43df4f 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/controllers/dp/tokenissuer_controller.go b/adapter/internal/operator/controllers/dp/tokenissuer_controller.go index b4e5b987f9..cc0708136b 100644 --- a/adapter/internal/operator/controllers/dp/tokenissuer_controller.go +++ b/adapter/internal/operator/controllers/dp/tokenissuer_controller.go @@ -73,7 +73,7 @@ func (r *TokenssuerReconciler) Reconcile(ctx context.Context, req ctrl.Request) jwtIssuerMapping, err := getJWTIssuers(ctx, r.client, jwtKey) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2660, logging.CRITICAL, - "Unable to find associated JWTIssuers for %s : %s", req.NamespacedName.String(), err.Error())) + "Unable to resolve JWTIssuers after updating %s : %s", req.NamespacedName.String(), err.Error())) return ctrl.Result{}, nil } UpdateEnforcerJWTIssuers(jwtIssuerMapping) @@ -220,7 +220,7 @@ func getJWTIssuers(ctx context.Context, client k8client.Client, namespace types. jwtIssuer.Spec.SignatureValidation.JWKS.TLS.ConfigMapRef, jwtIssuer.Spec.SignatureValidation.JWKS.TLS.SecretRef) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2659, logging.MAJOR, - "Error resolving certificate for JWKS %v", err.Error())) + "Error resolving certificate for JWKS for issuer %s in CR %s, %v", resolvedJwtIssuer.Issuer, utils.NamespacedName(&jwtIssuer).String(), err.Error())) continue } jwks.TLS = &dpv1alpha1.ResolvedTLSConfig{ResolvedCertificate: tlsCertificate} @@ -233,8 +233,8 @@ func getJWTIssuers(ctx context.Context, client k8client.Client, namespace types. jwtIssuer.Spec.SignatureValidation.Certificate.ConfigMapRef, jwtIssuer.Spec.SignatureValidation.Certificate.SecretRef) if err != nil { loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2659, logging.MAJOR, - "Error resolving certificate for JWKS %v", err.Error())) - return nil, err + "Error resolving certificate for JWKS for issuer %s in CR %s, %v", resolvedJwtIssuer.Issuer, utils.NamespacedName(&jwtIssuer).String(), err.Error())) + continue } signatureValidation.Certificate = &dpv1alpha1.ResolvedTLSConfig{ResolvedCertificate: tlsCertificate} } diff --git a/adapter/internal/operator/synchronizer/gql_api.go b/adapter/internal/operator/synchronizer/gql_api.go index b66048f5e3..86e9513d7e 100644 --- a/adapter/internal/operator/synchronizer/gql_api.go +++ b/adapter/internal/operator/synchronizer/gql_api.go @@ -75,22 +75,18 @@ func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, e 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) + 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, labels, nil } // getVhostForAPI returns the vHosts related to an API. @@ -103,15 +99,14 @@ func getVhostsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) []string { } // 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 @@ -149,11 +144,7 @@ func getListenersForGQLAPI(gqlRoute *v1alpha2.GQLRoute, apiUUID string) ([]strin func deleteGQLAPIFromEnv(gqlRoute *v1alpha2.GQLRoute, apiState APIState) error { labels := getLabelsForGQLAPI(gqlRoute) uuid := string(apiState.APIDefinition.ObjectMeta.UID) - gatewayNames := make(map[string]struct{}) - for _, label := range labels { - gatewayNames[label] = struct{}{} - } - return xds.DeleteAPI(uuid, gatewayNames) + 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 253c4645c9..113c0ef5f5 100644 --- a/adapter/internal/operator/synchronizer/rest_api.go +++ b/adapter/internal/operator/synchronizer/rest_api.go @@ -110,13 +110,13 @@ func GenerateAdapterInternalAPI(apiState APIState, httpRoute *HTTPRouteState, en listenerName := listeners[0] sectionName := relativeSectionNames[0] if len(listeners) != 0 { - updatedLabels, err := xds.UpdateAPICache(vHosts, labels, listenerName, sectionName, adapterInternalAPI) + 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 { + for newLabel := range labels { updatedLabelsMap[newLabel] = struct{}{} } } @@ -133,17 +133,16 @@ func getVhostsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { return vHosts } -// todo(amali) make this return map[string]struct{} instead of []string // getGatewayNameForAPI returns the labels related to an API. -func getGatewayNameForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string { - var labels []string +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 @@ -180,10 +179,6 @@ func getListenersForAPI(httpRoute *gwapiv1b1.HTTPRoute, apiUUID string) ([]strin func deleteAPIFromEnv(httpRoute *gwapiv1b1.HTTPRoute, apiState APIState) error { labels := getGatewayNameForAPI(httpRoute) - gatewayNames := make(map[string]struct{}) - for _, label := range labels { - gatewayNames[label] = struct{}{} - } uuid := string(apiState.APIDefinition.ObjectMeta.UID) - return xds.DeleteAPI(uuid, gatewayNames) + return xds.DeleteAPI(uuid, labels) } diff --git a/adapter/internal/operator/synchronizer/synchronizer.go b/adapter/internal/operator/synchronizer/synchronizer.go index c2d27a13a7..38897b6491 100644 --- a/adapter/internal/operator/synchronizer/synchronizer.go +++ b/adapter/internal/operator/synchronizer/synchronizer.go @@ -132,8 +132,12 @@ func deployMultipleAPIsInGateway(event *APIEvent, successChannel *chan SuccessEv var updatedAPIs []types.NamespacedName for i, apiState := range event.Events { loggers.LoggerAPKOperator.Infof("%s event received for %s", event.EventType, apiState.APIDefinition.Name) + // TODO(amali) move this inside updateAPI method // Remove the API from the internal maps before adding it again - xds.RemoveAPIFromAllInternalMaps(string((*apiState.APIDefinition).ObjectMeta.UID)) + 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 { _, updatedLabels, err := GenerateAdapterInternalAPI(apiState, apiState.ProdHTTPRoute, constants.Production) diff --git a/adapter/pkg/semanticversion/semantic_version.go b/adapter/pkg/semanticversion/semantic_version.go index 3750e59407..db44db230d 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 53a7e1bf2e..ff01d2f205 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 e4e9e27d22..63cf542273 100644 --- a/developer/tryout/samples/sample-api.yaml +++ b/developer/tryout/samples/sample-api.yaml @@ -30,7 +30,7 @@ spec: sandbox: - httpRouteRefs: - 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/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml b/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml index 33bf221d92..ff435d3d13 100644 --- a/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml +++ b/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-runtime-deployment.yaml @@ -87,7 +87,7 @@ spec: - name: enforcer_admin_pwd value: admin - name: JAVA_OPTS - value: -Dhttpclient.hostnameVerifier=AllowAll -Xms512m -Xmx512m -XX:MaxRAMFraction=2 + value: -Dhttpclient.hostnameVerifier=AllowAll -Xms512m -Xmx512m -XX:MaxRAMFraction=2 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5006 {{- if .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis }} - name: REDIS_USERNAME value: {{ .Values.wso2.apk.dp.gatewayRuntime.deployment.enforcer.redis.username | default "default" }} diff --git a/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-service.yaml b/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-service.yaml index dca2ff7ed4..cc30b85b4c 100644 --- a/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-service.yaml +++ b/helm-charts/templates/data-plane/gateway-components/gateway-runtime/gateway-service.yaml @@ -38,4 +38,10 @@ spec: - name: "https-endpoint" protocol: TCP port: 9095 + - name: "debug-endpoint" + protocol: TCP + port: 5006 + - name: "admin" + protocol: TCP + port: 9000 {{- end -}} diff --git a/helm-charts/values.yaml b/helm-charts/values.yaml index 287caf8121..898ab2ed62 100644 --- a/helm-charts/values.yaml +++ b/helm-charts/values.yaml @@ -84,7 +84,7 @@ wso2: failureThreshold: 5 strategy: RollingUpdate replicas: 1 - imagePullPolicy: Always + imagePullPolicy: IfNotPresent image: wso2/apk-config-deployer-service:latest # configs: # tls: @@ -110,8 +110,8 @@ wso2: failureThreshold: 5 strategy: RollingUpdate replicas: 1 - imagePullPolicy: Always - image: wso2/apk-adapter:latest + imagePullPolicy: IfNotPresent + image: apk-adapter:1.1.0-SNAPSHOT security: sslHostname: "adapter" # logging: @@ -143,8 +143,8 @@ wso2: failureThreshold: 5 strategy: RollingUpdate replicas: 1 - imagePullPolicy: Always - image: wso2/apk-common-controller:latest + imagePullPolicy: IfNotPresent + image: apk-common-controller:1.1.0-SNAPSHOT security: sslHostname: "commoncontroller" # configs: @@ -170,7 +170,7 @@ wso2: failureThreshold: 5 strategy: RollingUpdate replicas: 1 - imagePullPolicy: Always + imagePullPolicy: IfNotPresent image: wso2/apk-ratelimiter:latest security: sslHostname: "ratelimiter" @@ -200,7 +200,7 @@ wso2: periodSeconds: 20 failureThreshold: 5 strategy: RollingUpdate - imagePullPolicy: Always + imagePullPolicy: IfNotPresent image: wso2/apk-router:latest # configs: # tls: @@ -231,8 +231,8 @@ wso2: periodSeconds: 20 failureThreshold: 5 strategy: RollingUpdate - imagePullPolicy: Always - image: wso2/apk-enforcer:latest + imagePullPolicy: IfNotPresent + image: apk-enforcer:1.1.0-SNAPSHOT security: sslHostname: "enforcer" # logging: @@ -284,7 +284,7 @@ idp: failureThreshold: 5 strategy: RollingUpdate replicas: 1 - imagePullPolicy: Always + imagePullPolicy: IfNotPresent image: wso2/apk-idp-domain-service:latest idpui: deployment: @@ -305,7 +305,7 @@ idp: failureThreshold: 5 strategy: RollingUpdate replicas: 1 - imagePullPolicy: Always + imagePullPolicy: IfNotPresent image: wso2/apk-idp-ui:latest configs: idpLoginUrl: "https://idp.am.wso2.com:9095/commonauth/login" diff --git a/test/cucumber-tests/CRs/artifacts.yaml b/test/cucumber-tests/CRs/artifacts.yaml index 2da9444f39..404ca916e7 100644 --- a/test/cucumber-tests/CRs/artifacts.yaml +++ b/test/cucumber-tests/CRs/artifacts.yaml @@ -161,8 +161,8 @@ spec: app: "interceptor_service" spec: containers: - - image: "tharindu1st/interceptor_service:latest" - imagePullPolicy: "Always" + - image: "interceptor_service:latest" + imagePullPolicy: "IfNotPresent" name: "interceptor-service-deployment" ports: - containerPort: 8443 diff --git a/test/cucumber-tests/README.md b/test/cucumber-tests/README.md index 7d57f439cb..1159238d4e 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. diff --git a/test/cucumber-tests/src/test/resources/testng.xml b/test/cucumber-tests/src/test/resources/testng.xml index a246b7238f..057a991e45 100644 --- a/test/cucumber-tests/src/test/resources/testng.xml +++ b/test/cucumber-tests/src/test/resources/testng.xml @@ -18,16 +18,16 @@ - + - + diff --git a/test/cucumber-tests/src/test/resources/tests/api/JWT.feature b/test/cucumber-tests/src/test/resources/tests/api/JWT.feature index c95b2a8d04..49f7fe79f7 100644 --- a/test/cucumber-tests/src/test/resources/tests/api/JWT.feature +++ b/test/cucumber-tests/src/test/resources/tests/api/JWT.feature @@ -16,106 +16,3 @@ Feature: Test JWT related functionalities |Authorization|bearer invalidToken| And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" And the response status code should be 401 - Scenario: Test JWT Token from different issuer with JWKS - Given The system is ready - Then I generate JWT token from idp1 with kid "123-456" - Then I set headers - |Authorization|bearer ${idp-1-token}| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" - And I eventually receive 200 response code, not accepting - |429| - |401| - Then I set headers - |Authorization|bearer "${idp-1-token}h"| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" - And I eventually receive 401 response code, not accepting - |429| - |200| - Then I set headers - |Authorization|bearer ${idp-1-token}| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" - And I eventually receive 200 response code, not accepting - |429| - |401| - Then I generate JWT token from idp1 with kid "456-789" - And I send "DELETE" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/1234" with body "" - And I eventually receive 200 response code, not accepting - |429| - |401| - Then I set headers - |Authorization|bearer ${idp-1-token}| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" - And I eventually receive 401 response code, not accepting - |429| - |200| - Scenario: Test disabled JWT configuration - Given The system is ready - And I have a valid subscription - When I use the APK Conf file "artifacts/apk-confs/jwt_disabled_conf.yaml" - And the definition file "artifacts/definitions/employees_api.json" - And make the API deployment request - Then the response status code should be 200 - Then I set headers - |Authorization|bearer invalidToken| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-disabled/3.14/employee/" with body "" - And I eventually receive 200 response code, not accepting - |429| - |401| - - Scenario: Test customized JWT headers - Given The system is ready - And I have a valid subscription - When I use the APK Conf file "artifacts/apk-confs/jwt_custom_header_conf.yaml" - And the definition file "artifacts/definitions/employees_api.json" - And make the API deployment request - Then the response status code should be 200 - Then I set headers - |Authorization|bearer ${accessToken}| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-custom-header/3.14/employee/" with body "" - And I eventually receive 401 response code, not accepting - |429| - |200| - Then I set headers - |testAuth|bearer ${accessToken}| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-custom-header/3.14/employee/" with body "" - And I eventually receive 200 response code, not accepting - |429| - |401| - And the response body should contain "\"X-Jwt-Assertion\"" - And the "X-Jwt-Assertion" jwt should validate from JWKS "https://api.am.wso2.com:9095/.wellknown/jwks" and contain - | claim | value | - | claim1 | value1 | - | claim2 | value2 | - - Scenario: Test customized JWT headers with Resource Endpoint - Given The system is ready - And I have a valid subscription - When I use the APK Conf file "artifacts/apk-confs/jwt_custom_header_resource_endpoint_conf.yaml" - And the definition file "artifacts/definitions/employees_api.json" - And make the API deployment request - Then the response status code should be 200 - Then I set headers - |Authorization|bearer ${accessToken}| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-custom-header-resource/3.14/employee/" with body "" - And I eventually receive 401 response code, not accepting - |429| - |200| - Then I set headers - |testAuth|bearer ${accessToken}| - And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-custom-header-resource/3.14/employee/" with body "" - And I eventually receive 200 response code, not accepting - |429| - |401| - - Scenario Outline: Undeploy API - Given The system is ready - And I have a valid subscription - When I undeploy the API whose ID is "" - Then the response status code should be - - Examples: - | apiID | expectedStatusCode | - | jwt-basic-test | 202 | - | jwt-disabled-test | 202 | - | jwt-custom-header-test | 202 | - | jwt-custom-header-resource-test | 202 | diff --git a/test/interceptor-backend/ballerina/Dependencies.toml b/test/interceptor-backend/ballerina/Dependencies.toml index 05723b9107..e8ccccb3c5 100644 --- a/test/interceptor-backend/ballerina/Dependencies.toml +++ b/test/interceptor-backend/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.7.0" +distribution-version = "2201.7.1" [[package]] org = "ballerina" @@ -41,7 +41,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.4.0" +version = "2.4.1" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "time"} @@ -61,7 +61,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.9.0" +version = "2.9.5" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "cache"}, diff --git a/test/k8s-resources/interceptor-service.yaml b/test/k8s-resources/interceptor-service.yaml index afb6597f17..ddd3bbab93 100644 --- a/test/k8s-resources/interceptor-service.yaml +++ b/test/k8s-resources/interceptor-service.yaml @@ -59,7 +59,7 @@ spec: app: "interceptor_service" spec: containers: - - image: "tharindu1st/interceptor_service:latest" + - image: "interceptor_service:latest" imagePullPolicy: "IfNotPresent" name: "interceptor-service-deployment" ports: