diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go index 38773cf70a1..0d6d8337abb 100644 --- a/api/v1alpha1/backendtrafficpolicy_types.go +++ b/api/v1alpha1/backendtrafficpolicy_types.go @@ -57,6 +57,9 @@ type BackendTrafficPolicySpec struct { // +optional LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` + // +optional + BandwidthLimit *BandwidthLimit `json:"bandwidthLimit,omitempty"` + // ProxyProtocol enables the Proxy Protocol when communicating with the backend. // +optional ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty"` diff --git a/api/v1alpha1/loadbalancer_types.go b/api/v1alpha1/loadbalancer_types.go index 9fdaf26d5b4..afa9efb2b60 100644 --- a/api/v1alpha1/loadbalancer_types.go +++ b/api/v1alpha1/loadbalancer_types.go @@ -35,6 +35,12 @@ type LoadBalancer struct { // +optional SlowStart *SlowStart `json:"slowStart,omitempty"` } +type BandwidthLimit struct { + Mode string `json:"mode"` + Interval int32 `json:"interval"` + Limit int32 `json:"limit"` + Enable bool `json:"enable"` +} // LoadBalancerType specifies the types of LoadBalancer. // +kubebuilder:validation:Enum=ConsistentHash;LeastRequest;Random;RoundRobin diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 6d4ac03b663..2b4fe6debcf 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -7,6 +7,9 @@ package gatewayapi import ( "fmt" + "github.com/golang/protobuf/ptypes/duration" + "github.com/golang/protobuf/ptypes/wrappers" + "k8s.io/apimachinery/pkg/runtime" "net" "strconv" "strings" @@ -172,11 +175,33 @@ func (t *Translator) processHTTPRouteParentRefs(httpRoute *HTTPRouteContext, res func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRef *RouteParentContext, resources *Resources) ([]*ir.HTTPRoute, error) { var routeRoutes []*ir.HTTPRoute - + bandWidthLimit := &ir.BandWidthLimit{} // compute matches, filters, backends for ruleIdx, rule := range httpRoute.Spec.Rules { httpFiltersContext := t.ProcessHTTPFilters(parentRef, httpRoute, rule.Filters, ruleIdx, resources) + for _, extensionRef := range httpFiltersContext.ExtensionRefs { + policy := egv1a1.BackendTrafficPolicy{} + // unstructObj + err := runtime.DefaultUnstructuredConverter.FromUnstructured(extensionRef.Object.UnstructuredContent(), policy) + if err == nil && policy.Spec.BandwidthLimit != nil { + limit := wrappers.UInt64Value{} + limit.Value = uint64(policy.Spec.BandwidthLimit.Limit) + bandWidthLimit.Limit = &limit + enable := wrappers.BoolValue{} + enable.Value = policy.Spec.BandwidthLimit.Enable + bandWidthLimit.Enable = &enable + dura := duration.Duration{} + _duration, err := time.ParseDuration(fmt.Sprint(policy.Spec.BandwidthLimit.Interval)) + if err != nil { + continue + } + dura.Seconds = int64(_duration.Seconds()) + bandWidthLimit.Interval = &dura + bandWidthLimit.Mode = policy.Spec.BandwidthLimit.Mode + + } + } // A rule is matched if any one of its matches // is satisfied (i.e. a logical "OR"), so generate // a unique Xds IR HTTPRoute per match. @@ -239,7 +264,11 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe // TODO handle: // - sum of weights for valid backend refs is 0 // - etc. - + for i := 0; i < len(ruleRoutes); i++ { + if bandWidthLimit != nil { + ruleRoutes[i].BandWidthLimit = bandWidthLimit + } + } routeRoutes = append(routeRoutes, ruleRoutes...) } diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 9de48f29b33..6dc08b72e95 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -8,6 +8,8 @@ package ir import ( "cmp" "errors" + "github.com/golang/protobuf/ptypes/duration" + "github.com/golang/protobuf/ptypes/wrappers" "net/http" "net/netip" "reflect" @@ -562,6 +564,14 @@ type HTTPRoute struct { Security *SecurityFeatures `json:"security,omitempty" yaml:"security,omitempty"` // UseClientProtocol enables using the same protocol upstream that was used downstream UseClientProtocol *bool `json:"useClientProtocol,omitempty" yaml:"useClientProtocol,omitempty"` + BandWidthLimit *BandWidthLimit +} + +type BandWidthLimit struct { + Mode string + Interval *duration.Duration + Limit *wrappers.UInt64Value + Enable *wrappers.BoolValue } // TrafficFeatures holds the information associated with the Backend Traffic Policy. diff --git a/internal/xds/translator/bandwidthlimit.go b/internal/xds/translator/bandwidthlimit.go new file mode 100644 index 00000000000..67e69b688d0 --- /dev/null +++ b/internal/xds/translator/bandwidthlimit.go @@ -0,0 +1,49 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package translator + +import ( + "github.com/envoyproxy/gateway/internal/ir" + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + bandwidthlimitfilterv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/bandwidth_limit/v3" + "google.golang.org/protobuf/types/known/anypb" + "strings" +) + +func patchRouteWithBandWidthLimit(route *routev3.Route, irRoute *ir.HTTPRoute) error { + if irRoute.BandWidthLimit == nil { + return nil + } + + routeCfgAny, err := anypb.New(builbandWidthLimit(irRoute)) + if err != nil { + return err + } + route.TypedPerFilterConfig["envoy.filters.http.bandwidth_limit"] = routeCfgAny + return nil +} +func builbandWidthLimit(irRoute *ir.HTTPRoute) *bandwidthlimitfilterv3.BandwidthLimit { + banwidthLimitFilterProto := &bandwidthlimitfilterv3.BandwidthLimit{ + StatPrefix: "bandwidth_limiter_custom_route", + LimitKbps: irRoute.BandWidthLimit.Limit, + FillInterval: irRoute.BandWidthLimit.Interval, + } + flag := corev3.RuntimeFeatureFlag{} + flag.DefaultValue = irRoute.BandWidthLimit.Enable + banwidthLimitFilterProto.RuntimeEnabled = &flag + + if strings.ToLower(irRoute.BandWidthLimit.Mode) == "request" { + banwidthLimitFilterProto.EnableMode = bandwidthlimitfilterv3.BandwidthLimit_REQUEST + } else if strings.ToLower(irRoute.BandWidthLimit.Mode) == "response" { + banwidthLimitFilterProto.EnableMode = bandwidthlimitfilterv3.BandwidthLimit_RESPONSE + } else if strings.ToLower(irRoute.BandWidthLimit.Mode) == "all" { + banwidthLimitFilterProto.EnableMode = bandwidthlimitfilterv3.BandwidthLimit_REQUEST_AND_RESPONSE + } else { + banwidthLimitFilterProto.EnableMode = bandwidthlimitfilterv3.BandwidthLimit_DISABLED + } + return banwidthLimitFilterProto +} diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index 1765d2252eb..3db2e037711 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -88,7 +88,9 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) { } router.Action = &routev3.Route_Route{Route: routeAction} } - + if err := patchRouteWithBandWidthLimit(router, httpRoute); err != nil { + return nil, err + } // Hash Policy if router.GetRoute() != nil { router.GetRoute().HashPolicy = buildHashPolicy(httpRoute)