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
16 changes: 16 additions & 0 deletions api/v1alpha1/envoyproxy_metric_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ type ProxyMetrics struct {
//
// +optional
EnableRequestResponseSizesStats *bool `json:"enableRequestResponseSizesStats,omitempty"`

// ClusterStatName defines the value of cluster alt_stat_name, determining how cluster stats are named.
// For more details, see envoy docs: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto.html
// The supported operators for this pattern are:
// %ROUTE_NAME%: name of Gateway API xRoute resource
// %ROUTE_NAMESPACE%: namespace of Gateway API xRoute resource
// %ROUTE_KIND%: kind of Gateway API xRoute resource
// %ROUTE_RULE_NAME%: name of the Gateway API xRoute section
// %ROUTE_RULE_NUMBER%: name of the Gateway API xRoute section
// %BACKEND_REFS%: names of all backends referenced in <NAMESPACE>/<NAME>|<NAMESPACE>/<NAME>|... format
// Only xDS Clusters created for HTTPRoute and GRPCRoute are currently supported.
// Default: %ROUTE_KIND%/%ROUTE_NAMESPACE%/%ROUTE_NAME%/rule/%ROUTE_RULE_NUMBER%
// Example: httproute/my-ns/my-route/rule/0
//
// +optional
ClusterStatName *string `json:"clusterStatName,omitempty"`
}

// ProxyMetricSink defines the sink of metrics.
Expand Down
18 changes: 18 additions & 0 deletions api/v1alpha1/envoyproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,24 @@ const (

// EnvoyFilterBuffer defines the Envoy HTTP buffer filter
EnvoyFilterBuffer EnvoyFilter = "envoy.filters.http.buffer"

// StatFormatterRouteName defines the Route Name formatter for stats
StatFormatterRouteName string = "%ROUTE_NAME%"

// StatFormatterRouteNamespace defines the Route Name formatter for stats
StatFormatterRouteNamespace string = "%ROUTE_NAMESPACE%"

// StatFormatterRouteKind defines the Route Name formatter for stats
StatFormatterRouteKind string = "%ROUTE_KIND%"

// StatFormatterRouteRuleName defines the Route Name formatter for stats
StatFormatterRouteRuleName string = "%ROUTE_RULE_NAME%"

// StatFormatterRouteRuleNumber defines the Route Name formatter for stats
StatFormatterRouteRuleNumber string = "%ROUTE_RULE_NUMBER%"

// StatFormatterBackendRefs defines the Route Name formatter for stats
StatFormatterBackendRefs string = "%BACKEND_REFS%"
)

type ProxyTelemetry struct {
Expand Down
30 changes: 30 additions & 0 deletions api/v1alpha1/validation/envoyproxy_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"net"
"net/netip"
"regexp"

"github.com/dominikbraun/graph"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
Expand Down Expand Up @@ -204,6 +205,12 @@ func validateProxyTelemetry(spec *egv1a1.EnvoyProxySpec) []error {
}
}
}

if spec.Telemetry.Metrics.ClusterStatName != nil {
if clusterStatErrs := validateClusterStatName(*spec.Telemetry.Metrics.ClusterStatName); clusterStatErrs != nil {
errs = append(errs, clusterStatErrs...)
}
}
}

return errs
Expand Down Expand Up @@ -283,3 +290,26 @@ func validateFilterOrder(filterOrder []egv1a1.FilterPosition) error {

return nil
}

func validateClusterStatName(clusterStatName string) []error {
supportedOperators := map[string]bool{
egv1a1.StatFormatterRouteName: true,
egv1a1.StatFormatterRouteNamespace: true,
egv1a1.StatFormatterRouteKind: true,
egv1a1.StatFormatterRouteRuleName: true,
egv1a1.StatFormatterRouteRuleNumber: true,
egv1a1.StatFormatterBackendRefs: true,
}

var errs []error
re := regexp.MustCompile("%[^%]*%")
matches := re.FindAllString(clusterStatName, -1)
for _, operator := range matches {
if _, ok := supportedOperators[operator]; !ok {
err := fmt.Errorf("unable to configure Cluster Stat Name with unsupported operator: %s", operator)
errs = append(errs, err)
}
}

return errs
}
37 changes: 37 additions & 0 deletions api/v1alpha1/validation/envoyproxy_validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package validation

import (
"fmt"
"reflect"
"testing"

Expand Down Expand Up @@ -759,6 +760,42 @@ func TestValidateEnvoyProxy(t *testing.T) {
},
expected: false,
},
{
name: "valid operators in ClusterStatName",
proxy: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.EnvoyProxySpec{
Telemetry: &egv1a1.ProxyTelemetry{
Metrics: &egv1a1.ProxyMetrics{
ClusterStatName: ptr.To(fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", egv1a1.StatFormatterRouteName,
egv1a1.StatFormatterRouteName, egv1a1.StatFormatterRouteNamespace, egv1a1.StatFormatterRouteKind,
egv1a1.StatFormatterRouteRuleName, egv1a1.StatFormatterRouteRuleNumber, egv1a1.StatFormatterBackendRefs)),
},
},
},
},
expected: true,
},
{
name: "invalid operators in ClusterStatName",
proxy: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.EnvoyProxySpec{
Telemetry: &egv1a1.ProxyTelemetry{
Metrics: &egv1a1.ProxyMetrics{
ClusterStatName: ptr.To("%ROUTE_NAME%.%FOO%.%BAR%/my/%BACKEND_REFS%/%FOOBAR%"),
},
},
},
},
expected: false,
},
}

for i := range testCases {
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12599,6 +12599,21 @@ spec:
description: Metrics defines metrics configuration for managed
proxies.
properties:
clusterStatName:
description: |-
ClusterStatName defines the value of cluster alt_stat_name, determining how cluster stats are named.
For more details, see envoy docs: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto.html
The supported operators for this pattern are:
%ROUTE_NAME%: name of Gateway API xRoute resource
%ROUTE_NAMESPACE%: namespace of Gateway API xRoute resource
%ROUTE_KIND%: kind of Gateway API xRoute resource
%ROUTE_RULE_NAME%: name of the Gateway API xRoute section
%ROUTE_RULE_NUMBER%: name of the Gateway API xRoute section
%BACKEND_REFS%: names of all backends referenced in <NAMESPACE>/<NAME>|<NAMESPACE>/<NAME>|... format
Only xDS Clusters created for HTTPRoute and GRPCRoute are currently supported.
Default: %ROUTE_KIND%/%ROUTE_NAMESPACE%/%ROUTE_NAME%/rule/%ROUTE_RULE_NUMBER%
Example: httproute/my-ns/my-route/rule/0
type: string
enablePerEndpointStats:
description: |-
EnablePerEndpointStats enables per endpoint envoy stats metrics.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12598,6 +12598,21 @@ spec:
description: Metrics defines metrics configuration for managed
proxies.
properties:
clusterStatName:
description: |-
ClusterStatName defines the value of cluster alt_stat_name, determining how cluster stats are named.
For more details, see envoy docs: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto.html
The supported operators for this pattern are:
%ROUTE_NAME%: name of Gateway API xRoute resource
%ROUTE_NAMESPACE%: namespace of Gateway API xRoute resource
%ROUTE_KIND%: kind of Gateway API xRoute resource
%ROUTE_RULE_NAME%: name of the Gateway API xRoute section
%ROUTE_RULE_NUMBER%: name of the Gateway API xRoute section
%BACKEND_REFS%: names of all backends referenced in <NAMESPACE>/<NAME>|<NAMESPACE>/<NAME>|... format
Only xDS Clusters created for HTTPRoute and GRPCRoute are currently supported.
Default: %ROUTE_KIND%/%ROUTE_NAMESPACE%/%ROUTE_NAME%/rule/%ROUTE_RULE_NUMBER%
Example: httproute/my-ns/my-route/rule/0
type: string
enablePerEndpointStats:
description: |-
EnablePerEndpointStats enables per endpoint envoy stats metrics.
Expand Down
45 changes: 44 additions & 1 deletion internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
irRoutes []*ir.HTTPRoute
errs = &status.MultiStatusError{}
)
pattern := getStatPattern(httpRoute, parentRef)

// process each HTTPRouteRule, generate a unique Xds IR HTTPRoute per match of the rule
for ruleIdx, rule := range httpRoute.Spec.Rules {
Expand Down Expand Up @@ -221,7 +222,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
allDs := []*ir.DestinationSetting{}
failedProcessDestination := false
hasDynamicResolver := false

backendRefNames := make([]string, len(rule.BackendRefs))
// process each backendRef, and calculate the destination settings for this rule
for i, backendRef := range rule.BackendRefs {
settingName := irDestinationSettingName(destName, i)
Expand All @@ -245,6 +246,8 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
if ds.IsDynamicResolver {
hasDynamicResolver = true
}
backendNamespace := NamespaceDerefOr(backendRef.Namespace, httpRoute.GetNamespace())
backendRefNames[i] = fmt.Sprintf("%s/%s", backendNamespace, backendRef.Name)
}

// process each ir route
Expand Down Expand Up @@ -280,6 +283,10 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
destination.Settings = allDs
irRoute.Destination = destination
}

if pattern != "" {
destination.StatName = ptr.To(buildStatName(pattern, httpRoute, rule.Name, ruleIdx, backendRefNames))
}
}

if hasDynamicResolver && len(rule.BackendRefs) > 1 {
Expand Down Expand Up @@ -627,6 +634,7 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
irRoutes []*ir.HTTPRoute
errs = &status.MultiStatusError{}
)
pattern := getStatPattern(grpcRoute, parentRef)

// compute matches, filters, backends
for ruleIdx, rule := range grpcRoute.Spec.Rules {
Expand Down Expand Up @@ -654,6 +662,7 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
allDs := []*ir.DestinationSetting{}
failedProcessDestination := false

backendRefNames := make([]string, len(rule.BackendRefs))
for i, backendRef := range rule.BackendRefs {
settingName := irDestinationSettingName(destName, i)
ds, err := t.processDestination(settingName, backendRef, parentRef, grpcRoute, resources)
Expand All @@ -670,6 +679,8 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
continue
}
allDs = append(allDs, ds)
backendNamespace := NamespaceDerefOr(backendRef.Namespace, grpcRoute.GetNamespace())
backendRefNames[i] = fmt.Sprintf("%s/%s", backendNamespace, backendRef.Name)
}

// process each ir route
Expand Down Expand Up @@ -700,6 +711,10 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe
destination.Settings = allDs
irRoute.Destination = destination
}

if pattern != "" {
destination.StatName = ptr.To(buildStatName(pattern, grpcRoute, rule.Name, ruleIdx, backendRefNames))
}
}

// TODO handle:
Expand Down Expand Up @@ -1973,3 +1988,31 @@ func backendAppProtocolToIRAppProtocol(ap egv1a1.AppProtocolType, defaultProtoco
return defaultProtocol
}
}

func getStatPattern(routeContext RouteContext, parentRef *RouteParentContext) string {
var pattern string
var envoyProxy *egv1a1.EnvoyProxy
gatewayCtx := GetRouteParentContext(routeContext, *parentRef.ParentReference).GetGateway()
if gatewayCtx != nil {
envoyProxy = gatewayCtx.envoyProxy
}
if envoyProxy != nil && envoyProxy.Spec.Telemetry != nil && envoyProxy.Spec.Telemetry.Metrics != nil &&
envoyProxy.Spec.Telemetry.Metrics.ClusterStatName != nil {
pattern = *envoyProxy.Spec.Telemetry.Metrics.ClusterStatName
}
return pattern
}

func buildStatName(pattern string, route RouteContext, ruleName *gwapiv1.SectionName, idx int, refs []string) string {
statName := strings.ReplaceAll(pattern, egv1a1.StatFormatterRouteName, route.GetName())
statName = strings.ReplaceAll(statName, egv1a1.StatFormatterRouteNamespace, route.GetNamespace())
statName = strings.ReplaceAll(statName, egv1a1.StatFormatterRouteKind, route.GetObjectKind().GroupVersionKind().Kind)
if ruleName == nil {
statName = strings.ReplaceAll(statName, egv1a1.StatFormatterRouteRuleName, "-")
} else {
statName = strings.ReplaceAll(statName, egv1a1.StatFormatterRouteRuleName, string(*ruleName))
}
statName = strings.ReplaceAll(statName, egv1a1.StatFormatterRouteRuleNumber, fmt.Sprintf("%d", idx))
statName = strings.ReplaceAll(statName, egv1a1.StatFormatterBackendRefs, strings.Join(refs, "|"))
return statName
}
78 changes: 78 additions & 0 deletions internal/gatewayapi/testdata/envoyproxy-with-statname.in.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
envoyProxyForGatewayClass:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
namespace: envoy-gateway-system
name: test
spec:
telemetry:
metrics:
clusterStatName: "%ROUTE_KIND%/%ROUTE_NAMESPACE%/%ROUTE_NAME%/%ROUTE_RULE_NAME%/%ROUTE_RULE_NUMBER%/%BACKEND_REFS%"
provider:
type: Kubernetes
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
grpcRoutes:
- apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GRPCRoute
metadata:
namespace: default
name: grpcroute-1
spec:
parentRefs:
- namespace: envoy-gateway
name: gateway-1
sectionName: http
rules:
- backendRefs:
- name: service-1
port: 8080
- name: service-2
port: 8080
- name: service-3
port: 8080
- name: service-4
port: 8080
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-1
spec:
hostnames:
- gateway.envoyproxy.io
parentRefs:
- namespace: envoy-gateway
name: gateway-1
sectionName: http
rules:
- matches:
- path:
value: "/foo"
name: "foo"
backendRefs:
- name: service-3
port: 8080
- name: service-4
port: 8080
- matches:
- path:
value: "/"
name: "fallback"
backendRefs:
- name: service-1
port: 8080
Loading