Skip to content

Commit 0c703e1

Browse files
authored
[TT-16121] Generate relative server urls for APIs with no matching tags (#7544)
[TT-16121](https://tyktech.atlassian.net/browse/TT-16121) Problem Solved Previously, when an API was tagged but its tags didn't match any configured edge endpoints, the system would generate server URLs pointing to the control plane's default host (e.g., localhost:8080). This caused issues in MDCB/hybrid deployments where: APIs need to be accessible through multiple edge gateways Edge gateways are dynamically added/removed APIs should work across different edge locations without re-configuration Solution The system now generates relative path URLs (e.g., /api/v1) when API tags don't match edge endpoints, allowing edge gateways to serve the API using their own hostname without requiring API definition updates. [TT-16121]: https://tyktech.atlassian.net/browse/TT-16121?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!---TykTechnologies/jira-linter starts here--> ### Ticket Details <details> <summary> <a href="https://tyktech.atlassian.net/browse/TT-16121" title="TT-16121" target="_blank">TT-16121</a> </summary> | | | |---------|----| | Status | In Dev | | Summary | Server URL generation logic enhancement for distributed deployments | Generated at: 2025-11-18 16:19:59 </details> <!---TykTechnologies/jira-linter ends here-->
1 parent 007b66a commit 0c703e1

File tree

4 files changed

+693
-29
lines changed

4 files changed

+693
-29
lines changed

apidef/oas/servers_regeneration.go

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type ServerRegenerationConfig struct {
1818
DefaultHost string
1919
// EdgeEndpoints contains edge gateway configurations.
2020
EdgeEndpoints []EdgeEndpoint
21+
// HybridEnabled indicates if the gateway is in MDCB/hybrid mode.
22+
HybridEnabled bool
2123
}
2224

2325
// EdgeEndpoint represents an edge gateway endpoint configuration.
@@ -234,45 +236,113 @@ func generateVersionedServers(
234236
}
235237

236238
// determineHosts determines which hosts to use for server URL generation.
237-
// Priority: Custom Domain > Edge Endpoints > Default Host
238239
func determineHosts(apiData *apidef.APIDefinition, config ServerRegenerationConfig) []string {
239-
// Priority 1: Custom domain
240240
if apiData.Domain != "" {
241241
return []string{apiData.Domain}
242242
}
243243

244-
// Priority 2: Edge endpoints matching API tags
245-
if len(config.EdgeEndpoints) > 0 && len(apiData.Tags) > 0 {
246-
hosts := make([]string, 0, len(config.EdgeEndpoints))
247-
for _, endpoint := range config.EdgeEndpoints {
248-
endpointTagsMap := make(map[string]bool, len(endpoint.Tags))
249-
for _, tag := range endpoint.Tags {
250-
endpointTagsMap[tag] = true
251-
}
244+
return determineHostsWithEdgeSupport(apiData, config)
245+
}
246+
247+
// determineHostsWithEdgeSupport determines hosts based on edge endpoints and API tags.
248+
func determineHostsWithEdgeSupport(apiData *apidef.APIDefinition, config ServerRegenerationConfig) []string {
249+
if len(config.EdgeEndpoints) == 0 {
250+
if config.HybridEnabled {
251+
return []string{""}
252+
}
253+
return []string{config.DefaultHost}
254+
}
255+
256+
if len(apiData.Tags) == 0 {
257+
if config.HybridEnabled {
258+
return []string{}
259+
}
260+
return []string{config.DefaultHost}
261+
}
262+
263+
matchingHosts := findEndpointsMatchingTags(apiData.Tags, config.EdgeEndpoints)
264+
if len(matchingHosts) == 0 {
265+
return []string{""}
266+
}
267+
268+
if allAPITagsHaveMatches(apiData.Tags, config.EdgeEndpoints) {
269+
return matchingHosts
270+
}
271+
272+
return appendRelativePathIfNotPresent(matchingHosts)
273+
}
274+
275+
// findEndpointsMatchingTags returns edge endpoint URLs with at least one matching tag.
276+
func findEndpointsMatchingTags(apiTags []string, edgeEndpoints []EdgeEndpoint) []string {
277+
var matchingHosts []string
278+
279+
for _, endpoint := range edgeEndpoints {
280+
if hasAnyTagMatch(apiTags, endpoint.Tags) {
281+
matchingHosts = append(matchingHosts, endpoint.Endpoint)
282+
}
283+
}
284+
285+
return matchingHosts
286+
}
287+
288+
// buildTagSet creates a map for O(1) tag lookups.
289+
func buildTagSet(tags []string) map[string]bool {
290+
tagSet := make(map[string]bool, len(tags))
291+
for _, tag := range tags {
292+
tagSet[tag] = true
293+
}
294+
return tagSet
295+
}
296+
297+
// hasAnyTagMatch returns true if any API tag matches any endpoint tag.
298+
func hasAnyTagMatch(apiTags []string, endpointTags []string) bool {
299+
tagSet := buildTagSet(endpointTags)
300+
for _, apiTag := range apiTags {
301+
if tagSet[apiTag] {
302+
return true
303+
}
304+
}
305+
return false
306+
}
252307

253-
for _, apiTag := range apiData.Tags {
254-
if endpointTagsMap[apiTag] {
255-
hosts = append(hosts, endpoint.Endpoint)
256-
break
257-
}
308+
// allAPITagsHaveMatches returns true if every API tag matches at least one edge endpoint.
309+
func allAPITagsHaveMatches(apiTags []string, edgeEndpoints []EdgeEndpoint) bool {
310+
for _, apiTag := range apiTags {
311+
matched := false
312+
for _, endpoint := range edgeEndpoints {
313+
if containsString(endpoint.Tags, apiTag) {
314+
matched = true
315+
break
258316
}
259317
}
318+
if !matched {
319+
return false
320+
}
321+
}
322+
return true
323+
}
260324

261-
if len(hosts) > 0 {
325+
// appendRelativePathIfNotPresent adds a relative path (empty string) if not present.
326+
func appendRelativePathIfNotPresent(hosts []string) []string {
327+
for _, host := range hosts {
328+
if host == "" {
262329
return hosts
263330
}
264331
}
265-
266-
// Priority 3: Default host
267-
return []string{config.DefaultHost}
332+
return append(hosts, "")
268333
}
269334

270-
// buildServerURL constructs a server URL from protocol, host, and path.
335+
// buildServerURL constructs a server URL. Returns relative path if host is empty.
271336
func buildServerURL(protocol, host, listenPath string) string {
272337
if !strings.HasPrefix(listenPath, "/") {
273338
listenPath = "/" + listenPath
274339
}
275340

341+
// Empty host means relative path - return just the listen path
342+
if host == "" {
343+
return path.Clean(listenPath)
344+
}
345+
276346
host = strings.TrimSuffix(host, "/")
277347

278348
// Clean the path to remove double slashes

0 commit comments

Comments
 (0)