Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gateway/gateway-controller/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/prometheus/client_model v0.6.2
github.com/stretchr/testify v1.11.1
github.com/wso2/api-platform/common v0.0.0
github.com/wso2/api-platform/sdk/core v0.2.12
github.com/wso2/api-platform/sdk/core v0.2.18
github.com/wso2/go-httpkit v0.0.0-local
github.com/xeipuuv/gojsonschema v1.2.0
google.golang.org/grpc v1.79.3
Expand Down
4 changes: 2 additions & 2 deletions gateway/gateway-controller/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
github.com/wso2/api-platform/sdk/core v0.2.12 h1:todO77VOlxw8bWniFK/GyEbuM1R5ELnULgR+37Xdrak=
github.com/wso2/api-platform/sdk/core v0.2.12/go.mod h1:vgNVzR16g9k5cun3VXZ7wDg8UGbPxsVU2TW8EbRCv0o=
github.com/wso2/api-platform/sdk/core v0.2.18 h1:j6UhHjGBa9qCxqbT4p8cwLUKkQVtpvoMTMsjs1DfYR0=
github.com/wso2/api-platform/sdk/core v0.2.18/go.mod h1:NZMDIQadQDpbynlCLYdZT6duiHwnf7emr7SbLHdCjaE=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
Expand Down
12 changes: 11 additions & 1 deletion gateway/gateway-controller/pkg/models/runtime_deploy_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@

package models

import "time"
import (
"time"

policyenginev1 "github.com/wso2/api-platform/sdk/core/policyengine"
)

// RuntimeDeployConfig is the kind-agnostic intermediate representation produced by
// each transformer (RestAPI, LLM Provider, LLM Proxy). Both the Envoy xDS translator
Expand Down Expand Up @@ -90,6 +94,12 @@ type RouteUpstream struct {
ClusterKey string // key into UpstreamClusters map
UseClusterHeader bool // if true, policy selects upstream dynamically
DefaultCluster string // default cluster name when UseClusterHeader is true

// Default is this route's own compiled-in upstream (cluster name, URL, base
// path) — whichever slot this route belongs to (main or sandbox). Exposed to
// the policy engine as the route's single default upstream field, regardless
// of which slot it is.
Default *policyenginev1.UpstreamInfo
}

// PolicyChain is an ordered list of policies for a route.
Expand Down
6 changes: 6 additions & 0 deletions gateway/gateway-controller/pkg/policyxds/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ func (t *Translator) createRouteConfigResource(
data["default_upstream_cluster"] = route.Upstream.DefaultCluster
}

// This route's own compiled-in upstream (whichever slot it belongs to) — a single,
// always-present field for the policy engine, regardless of main/sandbox.
if route.Upstream.Default != nil {
data["default_upstream"] = route.Upstream.Default.ToMap()
}

return toAnyResource(data, RouteConfigTypeURL)
}

Expand Down
46 changes: 38 additions & 8 deletions gateway/gateway-controller/pkg/transform/restapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,22 @@ func (t *RestAPITransformer) Transform(cfg *models.StoredConfig) (*models.Runtim
if err != nil {
return nil, fmt.Errorf("failed to resolve main upstream: %w", err)
}
mainUpstreamInfo := mainUpstream.UpstreamInfo()

// Check if dynamic cluster selection should be used
useClusterHeader := apiData.UpstreamDefinitions != nil && len(*apiData.UpstreamDefinitions) > 0
// Determine vhosts to create routes for.
// Sandbox is active when a sandbox upstream is configured via either url or ref.
hasSandbox := apiData.Upstream.Sandbox != nil &&
((apiData.Upstream.Sandbox.Url != nil && strings.TrimSpace(*apiData.Upstream.Sandbox.Url) != "") ||
(apiData.Upstream.Sandbox.Ref != nil && strings.TrimSpace(*apiData.Upstream.Sandbox.Ref) != ""))

// Check if dynamic cluster selection should be used. Enabled whenever the API has named
// upstream definitions (so a policy can select one) OR a sandbox upstream (so a policy can
// redirect between the API's own main/sandbox slots). Must mirror pkg/xds/translator.go's
// useClusterHeader computation exactly — that's what determines whether Envoy's route uses
// cluster_header routing; if this RDC (which feeds the policy engine's default-cluster
// fallback) disagrees, Envoy expects a header the policy engine never sets.
hasUpstreamDefinitions := apiData.UpstreamDefinitions != nil && len(*apiData.UpstreamDefinitions) > 0
useClusterHeader := hasUpstreamDefinitions || hasSandbox
defaultCluster := ""
if useClusterHeader {
// The default cluster must be the name Envoy actually knows the cluster by.
Expand All @@ -147,12 +160,6 @@ func (t *RestAPITransformer) Transform(cfg *models.StoredConfig) (*models.Runtim
mainAutoHostRewrite = false
}

// Determine vhosts to create routes for.
// Sandbox is active when a sandbox upstream is configured via either url or ref.
hasSandbox := apiData.Upstream.Sandbox != nil &&
((apiData.Upstream.Sandbox.Url != nil && strings.TrimSpace(*apiData.Upstream.Sandbox.Url) != "") ||
(apiData.Upstream.Sandbox.Ref != nil && strings.TrimSpace(*apiData.Upstream.Sandbox.Ref) != ""))

// Guard: sandbox and main vhosts must differ, otherwise sandbox routes would
// overwrite main routes (same route key) and the sandbox patch would leave only
// a sandbox-cluster route with no main-cluster route at all.
Expand Down Expand Up @@ -199,6 +206,10 @@ func (t *RestAPITransformer) Transform(cfg *models.StoredConfig) (*models.Runtim
for _, vhost := range vhosts {
routeKey := xds.GenerateRouteNameWithDiscriminator(method, apiData.Context, apiData.Version, opPath, vhost, discriminator)

// Build route. Default is this route's own upstream (main's, until the sandbox
// patch below overwrites it for sandbox-vhost routes) — the single field exposed
// to the policy engine as the route's compiled-in upstream, regardless of slot.
routeMainInfo := mainUpstreamInfo
rdcRoute := &models.Route{
Method: method,
Path: xds.ConstructFullPath(apiData.Context, apiData.Version, opPath),
Expand All @@ -213,6 +224,7 @@ func (t *RestAPITransformer) Transform(cfg *models.StoredConfig) (*models.Runtim
ClusterKey: mainUpstream.ClusterKey,
UseClusterHeader: useClusterHeader,
DefaultCluster: defaultCluster,
Default: &routeMainInfo,
},
}
rdc.Routes[routeKey] = rdcRoute
Expand Down Expand Up @@ -280,6 +292,7 @@ func (t *RestAPITransformer) Transform(cfg *models.StoredConfig) (*models.Runtim
if err != nil {
return nil, fmt.Errorf("failed to resolve sandbox upstream: %w", err)
}
sbUpstreamInfo := sbUpstream.UpstreamInfo()

sbAutoHostRewrite := true
if apiData.Upstream.Sandbox.HostRewrite != nil && *apiData.Upstream.Sandbox.HostRewrite == api.Manual {
Expand All @@ -304,6 +317,10 @@ func (t *RestAPITransformer) Transform(cfg *models.StoredConfig) (*models.Runtim
} else {
r.Upstream.DefaultCluster = ""
}
// This route belongs to the sandbox slot — its own default upstream is
// the sandbox's, not main's.
routeSbInfo := sbUpstreamInfo
r.Upstream.Default = &routeSbInfo
r.AutoHostRewrite = sbAutoHostRewrite
}
}
Expand Down Expand Up @@ -434,6 +451,18 @@ type upstreamClusterResult struct {
EnvoyClusterName string
// BasePath is the URL path component of the upstream (e.g. "/anything/foo").
BasePath string
// URL is the resolved upstream origin (scheme://host[:port], no path — see BasePath).
URL string
}

// UpstreamInfo converts the resolved cluster result into the shared wire shape
// carried to the policy engine (sdk/core/policyengine.UpstreamInfo).
func (r *upstreamClusterResult) UpstreamInfo() policyenginev1.UpstreamInfo {
return policyenginev1.UpstreamInfo{
ClusterName: r.EnvoyClusterName,
URL: r.URL,
BasePath: r.BasePath,
}
}

// addUpstreamCluster resolves an upstream and adds it to the RuntimeDeployConfig.
Expand Down Expand Up @@ -482,6 +511,7 @@ func (t *RestAPITransformer) addUpstreamCluster(
ClusterKey: clusterKey,
EnvoyClusterName: sanitizeEnvoyClusterName(parsedURL.Host, parsedURL.Scheme),
BasePath: basePath,
URL: fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host),
}, nil
}

Expand Down
16 changes: 10 additions & 6 deletions gateway/gateway-controller/pkg/transform/restapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,12 @@ func TestResolvePort(t *testing.T) {
}

// TestRestAPITransformer_SandboxRouteClusterHeader pins the sandbox route's dynamic
// cluster selection: with upstreamDefinitions the sandbox route uses cluster_header
// routing (so a dynamic-endpoint policy can divert sandbox traffic) and defaults to the
// sandbox cluster; without them it stays a static sandbox route.
// cluster selection: whenever a sandbox upstream is configured — with or without
// upstreamDefinitions — the sandbox route uses cluster_header routing (so a
// dynamic-endpoint policy can divert sandbox traffic) and defaults to the sandbox
// cluster. This must mirror pkg/xds/translator.go's useClusterHeader computation,
// which Envoy's actual route uses; a mismatch leaves Envoy expecting a
// x-target-upstream header the policy engine never sets.
func TestRestAPITransformer_SandboxRouteClusterHeader(t *testing.T) {
defs := map[string]models.PolicyDefinition{}
const sandboxURL = "http://sandbox-backend:9080/sandbox"
Expand All @@ -433,7 +436,7 @@ func TestRestAPITransformer_SandboxRouteClusterHeader(t *testing.T) {
// "upstream_sandbox_<host>_<port>" — not the sanitized "cluster_<scheme>_<host>" form.
const expectedSandboxCluster = "upstream_sandbox_sandbox-backend_9080"

t.Run("without upstreamDefinitions the sandbox route is static", func(t *testing.T) {
t.Run("without upstreamDefinitions the sandbox route still uses cluster_header defaulting to the sandbox cluster", func(t *testing.T) {
transformer := NewRestAPITransformer(testRouterCfg(), &config.Config{}, defs)
cfg := makeRestAPIStoredConfig(nil, nil)
restAPI := cfg.Configuration.(api.RestAPI)
Expand All @@ -444,8 +447,9 @@ func TestRestAPITransformer_SandboxRouteClusterHeader(t *testing.T) {
require.NoError(t, err)
r, exists := rdc.Routes[sandboxRouteKey]
require.True(t, exists, "sandbox route should exist")
assert.False(t, r.Upstream.UseClusterHeader)
assert.Equal(t, "", r.Upstream.DefaultCluster)
assert.True(t, r.Upstream.UseClusterHeader)
assert.Equal(t, expectedSandboxCluster, r.Upstream.DefaultCluster,
"sandbox route must default to the sandbox cluster, not main")
})

t.Run("with upstreamDefinitions the sandbox route uses cluster_header defaulting to the sandbox cluster", func(t *testing.T) {
Expand Down
22 changes: 14 additions & 8 deletions gateway/gateway-controller/pkg/xds/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1088,11 +1088,18 @@ func (t *Translator) translateAPIConfig(cfg *models.StoredConfig, allConfigs []*
return nil, nil, fmt.Errorf("invalid API-level resilience: %w", err)
}

for _, op := range apiData.Operations {
// Determine if dynamic cluster selection should be used
// When upstreamDefinitions exist, use cluster_header routing so policies can select the upstream
useClusterHeader := apiData.UpstreamDefinitions != nil && len(*apiData.UpstreamDefinitions) > 0
// Determine if dynamic cluster selection should be used. Enabled whenever the API has
// named upstream definitions (so a policy can select one) OR a sandbox upstream (so a
// policy can redirect between the API's own main/sandbox slots). Sandbox activity here
// mirrors restapi.go's Url/Ref-aware hasSandbox check, not the looser nil check below
// (which only gates whether the sandbox cluster itself gets built).
hasSandboxForClusterHeader := apiData.Upstream.Sandbox != nil &&
((apiData.Upstream.Sandbox.Url != nil && strings.TrimSpace(*apiData.Upstream.Sandbox.Url) != "") ||
(apiData.Upstream.Sandbox.Ref != nil && strings.TrimSpace(*apiData.Upstream.Sandbox.Ref) != ""))
hasUpstreamDefinitions := apiData.UpstreamDefinitions != nil && len(*apiData.UpstreamDefinitions) > 0
useClusterHeader := hasUpstreamDefinitions || hasSandboxForClusterHeader

for _, op := range apiData.Operations {
opTimeout, opIdleTimeout, err := ResolveResilience(op.Resilience)
if err != nil {
return nil, nil, fmt.Errorf("invalid resilience for operation %s %s: %w", op.EffectiveMethod(), op.EffectivePath(), err)
Expand Down Expand Up @@ -1121,10 +1128,9 @@ func (t *Translator) translateAPIConfig(cfg *models.StoredConfig, allConfigs []*
sandboxCluster := t.createCluster(sbClusterName, parsedSbURL, nil, sbUpstreamClusterConnectTimeout)
clusters = append(clusters, sandboxCluster)

// Create sandbox routes. When upstreamDefinitions exist, enable dynamic cluster
// selection (mirrors main).
// Create sandbox routes. Mirrors main's useClusterHeader (dynamic cluster selection
// is on whenever upstreamDefinitions exist or a sandbox upstream is configured).
sbRoutesList := make([]*route.Route, 0)
sbUseClusterHeader := apiData.UpstreamDefinitions != nil && len(*apiData.UpstreamDefinitions) > 0
for _, op := range apiData.Operations {
opTimeout, opIdleTimeout, err := ResolveResilience(op.Resilience)
if err != nil {
Expand All @@ -1133,7 +1139,7 @@ func (t *Translator) translateAPIConfig(cfg *models.StoredConfig, allConfigs []*
opTimeoutCfg := combineRouteResilience(sbTimeout, apiTimeout, apiIdleTimeout, opTimeout, opIdleTimeout)

r := t.createRoute(cfg.UUID, apiData.DisplayName, apiData.Version, apiData.Context, op.EffectiveMethod(), op.EffectivePath(),
sbClusterName, parsedSbURL.Path, effectiveSandboxVHost, cfg.Kind, templateHandle, providerName, apiData.Upstream.Sandbox.HostRewrite, apiProjectID, opTimeoutCfg, sbUseClusterHeader, upstreamDefPaths)
sbClusterName, parsedSbURL.Path, effectiveSandboxVHost, cfg.Kind, templateHandle, providerName, apiData.Upstream.Sandbox.HostRewrite, apiProjectID, opTimeoutCfg, useClusterHeader, upstreamDefPaths)
sbRoutesList = append(sbRoutesList, r)
}
routesList = append(routesList, sbRoutesList...)
Expand Down
2 changes: 1 addition & 1 deletion gateway/gateway-runtime/policy-engine/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/prometheus/client_golang v1.23.2
github.com/stretchr/testify v1.11.1
github.com/wso2/api-platform/common v0.0.0-20260326194347-3d85c50eae71
github.com/wso2/api-platform/sdk/core v0.2.15
github.com/wso2/api-platform/sdk/core v0.2.18
go.opentelemetry.io/otel v1.41.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0
go.opentelemetry.io/otel/sdk v1.41.0
Expand Down
4 changes: 2 additions & 2 deletions gateway/gateway-runtime/policy-engine/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/wso2/api-platform/sdk/core v0.2.15 h1:CfI4+uUuHsCU4d0FVnavVpORvL1WZEtREb/ZEktIsw0=
github.com/wso2/api-platform/sdk/core v0.2.15/go.mod h1:TgjpOk3QBPc7xEQC+NctWcNq5dSPBkn7wSKh+hULTj8=
github.com/wso2/api-platform/sdk/core v0.2.18 h1:j6UhHjGBa9qCxqbT4p8cwLUKkQVtpvoMTMsjs1DfYR0=
github.com/wso2/api-platform/sdk/core v0.2.18/go.mod h1:NZMDIQadQDpbynlCLYdZT6duiHwnf7emr7SbLHdCjaE=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func dumpRouteMetadata(k *kernel.Kernel) RouteMetadataDump {
DefaultUpstreamCluster: cfg.Metadata.DefaultUpstreamCluster,
UpstreamBasePath: cfg.Metadata.UpstreamBasePath,
UpstreamDefinitionPaths: cfg.Metadata.UpstreamDefinitionPaths,
DefaultUpstream: cfg.Metadata.DefaultUpstream,
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@

package admin

import "time"
import (
"time"

policyenginev1 "github.com/wso2/api-platform/sdk/core/policyengine"
)

// ConfigDumpResponse is the top-level response structure for the config_dump endpoint
type ConfigDumpResponse struct {
Expand Down Expand Up @@ -111,6 +115,10 @@ type RouteMetadataEntry struct {
DefaultUpstreamCluster string `json:"default_upstream_cluster"`
UpstreamBasePath string `json:"upstream_base_path"`
UpstreamDefinitionPaths map[string]string `json:"upstream_definition_paths"`

// DefaultUpstream is this route's own compiled-in upstream (whichever slot it
// belongs to).
DefaultUpstream *policyenginev1.UpstreamInfo `json:"default_upstream,omitempty"`
}

// PolicySpec contains specification for a policy instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ const (
// Must match the gateway-controller constant
UpstreamDefinitionClusterPrefix = "upstream_"

// CurrentUpstreamKey is the dynamic metadata key for the request's current effective
// upstream (an UpstreamInfo object: cluster_name/url/base_path). Always present —
// starts as the route's compiled-in default and is overwritten in place whenever a
// dynamic-routing policy (UpstreamName) redirects the request, so
// downstream consumers (Lua filter, analytics, response-phase processing) always see
// the request's actual destination.
CurrentUpstreamKey = "current_upstream"

// Policy Engine Socket Path (matches gateway-controller constant)
DefaultPolicyEngineSocketPath = "/var/run/api-platform/policy-engine.sock"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/executor"
"github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/registry"
policy "github.com/wso2/api-platform/sdk/core/policy/v1alpha2"
policyenginev1 "github.com/wso2/api-platform/sdk/core/policyengine"
)

// maxStreamAccumulatorSize caps the amount of data accumulated before forcing
Expand Down Expand Up @@ -94,6 +95,11 @@ type PolicyExecutionContext struct {
// Used when UpstreamName is set to compute the correct path transformation.
upstreamDefinitionPaths map[string]string

// defaultUpstream is this route's own compiled-in upstream (cluster name, URL, base
// path) — whichever slot it belongs to. Always present; surfaced to policies via the
// "current_upstream" dynamic metadata object when no dynamic override is in effect.
defaultUpstream *policyenginev1.UpstreamInfo
Comment thread
tharindu1st marked this conversation as resolved.

// requestContentEncoding stores the Content-Encoding of the incoming request (e.g. "gzip", "br").
// The body is decompressed before being passed to policies, and re-compressed using this value
// before being forwarded to the upstream.
Expand Down Expand Up @@ -1010,6 +1016,7 @@ func (ec *PolicyExecutionContext) buildRequestContexts(headers *extprocv3.HttpHe
Authority: authority,
Scheme: scheme,
Vhost: routeMetadata.Vhost,
UpstreamInfo: ec.defaultUpstream,
}

// Build the streaming context once; reused across all chunks for this request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/executor"
"github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/metrics"
"github.com/wso2/api-platform/gateway/gateway-runtime/policy-engine/internal/tracing"
policyenginev1 "github.com/wso2/api-platform/sdk/core/policyengine"
)

// ExternalProcessorServer implements the Envoy external processor service
Expand Down Expand Up @@ -379,6 +380,7 @@ func (s *ExternalProcessorServer) initializeExecutionContext(ctx context.Context
(*execCtx).upstreamBasePath = routeMetadata.UpstreamBasePath
(*execCtx).apiContext = routeMetadata.Context
(*execCtx).upstreamDefinitionPaths = routeMetadata.UpstreamDefinitionPaths
(*execCtx).defaultUpstream = routeMetadata.DefaultUpstream
(*execCtx).buildRequestContexts(req.GetRequestHeaders(), routeMetadata)
return &routeMetadata
}
Expand Down Expand Up @@ -456,6 +458,11 @@ type RouteMetadata struct {
DefaultUpstreamCluster string // Default cluster for dynamic cluster routing
UpstreamBasePath string // Base path for the upstream (e.g., /anything)
UpstreamDefinitionPaths map[string]string // Maps upstream definition names to their URL base paths

// DefaultUpstream is this route's own compiled-in upstream (cluster name, URL, base
// path) — whichever slot it belongs to (main or sandbox). Always present; surfaced
// to the policy engine as the route's single default upstream.
DefaultUpstream *policyenginev1.UpstreamInfo
}

// generateRequestID generates a unique request identifier
Expand Down
Loading
Loading