Skip to content

Commit

Permalink
local rate limit
Browse files Browse the repository at this point in the history
Signed-off-by: huabing zhao <[email protected]>
  • Loading branch information
zhaohuabing committed Dec 1, 2023
1 parent f3e4e93 commit 47d6937
Show file tree
Hide file tree
Showing 16 changed files with 1,390 additions and 79 deletions.
39 changes: 38 additions & 1 deletion api/v1alpha1/ratelimit_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,25 @@ type RateLimitSpec struct {
//
// +optional
Global *GlobalRateLimit `json:"global,omitempty"`

// Local defines local rate limit configuration.
//
// +optional
Local *LocalRateLimit `json:"local,omitempty"`
}

// RateLimitType specifies the types of RateLimiting.
// +kubebuilder:validation:Enum=Global
type RateLimitType string

const (
// GlobalRateLimitType allows the rate limits to be applied across all Envoy proxy instances.
// GlobalRateLimitType allows the rate limits to be applied across all Envoy
// proxy instances.
GlobalRateLimitType RateLimitType = "Global"

// LocalRateLimitType allows the rate limits to be applied on a per Envoy
// proxy instance basis.
LocalRateLimitType RateLimitType = "Local"
)

// GlobalRateLimit defines global rate limit configuration.
Expand All @@ -42,6 +52,16 @@ type GlobalRateLimit struct {
Rules []RateLimitRule `json:"rules"`
}

// LocalRateLimit defines local rate limit configuration.
type LocalRateLimit struct {
// Rules are a list of RateLimit selectors and limits.
// Orders matters here as the rules are processed sequentially.
// The first rule that matches the request is applied.
//
// +kubebuilder:validation:MaxItems=16
Rules []RateLimitRule `json:"rules"`
}

// RateLimitRule defines the semantics for matching attributes
// from the incoming requests, and setting limits for them.
type RateLimitRule struct {
Expand Down Expand Up @@ -91,6 +111,7 @@ const (
SourceMatchExact SourceMatchType = "Exact"
// SourceMatchDistinct Each IP Address within the specified Source IP CIDR is treated as a distinct client selector
// and uses a separate rate limit bucket/counter.
// Note: This is only supported for Global Rate Limits.
SourceMatchDistinct SourceMatchType = "Distinct"
)

Expand Down Expand Up @@ -148,6 +169,7 @@ const (
// HeaderMatchDistinct matches any and all possible unique values encountered in the
// specified HTTP Header. Note that each unique value will receive its own rate limit
// bucket.
// Note: This is only supported for Global Rate Limits.
HeaderMatchDistinct HeaderMatchType = "Distinct"
)

Expand All @@ -162,3 +184,18 @@ type RateLimitValue struct {
//
// +kubebuilder:validation:Enum=Second;Minute;Hour;Day
type RateLimitUnit string

// RateLimitUnit constants.
const (
// RateLimitUnitSecond specifies the rate limit interval to be 1 second.
RateLimitUnitSecond RateLimitUnit = "Second"

// RateLimitUnitMinute specifies the rate limit interval to be 1 minute.
RateLimitUnitMinute RateLimitUnit = "Minute"

// RateLimitUnitHour specifies the rate limit interval to be 1 hour.
RateLimitUnitHour RateLimitUnit = "Hour"

// RateLimitUnitDay specifies the rate limit interval to be 1 day.
RateLimitUnitDay RateLimitUnit = "Day"
)
27 changes: 27 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.

221 changes: 149 additions & 72 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package gatewayapi

import (
"errors"
"fmt"
"net"
"sort"
Expand Down Expand Up @@ -313,8 +314,83 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
}

func (t *Translator) buildRateLimit(policy *egv1a1.BackendTrafficPolicy) *ir.RateLimit {
switch policy.Spec.RateLimit.Type {
case egv1a1.GlobalRateLimitType:
return t.buildGlobalRateLimit(policy)
case egv1a1.LocalRateLimitType:
return t.buildLocalRateLimit(policy)
}

status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
"Invalid rateLimit type",
)
return nil

Check warning on line 330 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L324-L330

Added lines #L324 - L330 were not covered by tests
}

func (t *Translator) buildLocalRateLimit(policy *egv1a1.BackendTrafficPolicy) *ir.RateLimit {
if policy.Spec.RateLimit.Local == nil {
message := "Local configuration empty for rateLimit."
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)
return nil
}

Check warning on line 343 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L335-L343

Added lines #L335 - L343 were not covered by tests

rateLimit := &ir.RateLimit{
Local: &ir.LocalRateLimit{
Rules: make([]*ir.RateLimitRule, len(policy.Spec.RateLimit.Local.Rules)),
},
}

irRules := rateLimit.Local.Rules
var err error
for i, rule := range policy.Spec.RateLimit.Local.Rules {
irRules[i], err = buildRateLimitRule(rule)
if err != nil {
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
status.Error2ConditionMsg(err),
)
return nil
}

Check warning on line 363 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L356-L363

Added lines #L356 - L363 were not covered by tests
if irRules[i].CIDRMatch != nil && irRules[i].CIDRMatch.Distinct {
message := "Local rateLimit does not support distinct CIDRMatch."
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)
return nil
}
for _, match := range irRules[i].HeaderMatches {
if match.Distinct {
message := "Local rateLimit does not support distinct HeaderMatch."
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)
return nil
}

Check warning on line 384 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L376-L384

Added lines #L376 - L384 were not covered by tests
}
}

return rateLimit
}

func (t *Translator) buildGlobalRateLimit(policy *egv1a1.BackendTrafficPolicy) *ir.RateLimit {
if policy.Spec.RateLimit.Global == nil {
message := "Global configuration empty for rateLimit"
message := "Global configuration empty for rateLimit."

Check warning on line 393 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L393

Added line #L393 was not covered by tests
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
Expand All @@ -323,8 +399,9 @@ func (t *Translator) buildRateLimit(policy *egv1a1.BackendTrafficPolicy) *ir.Rat
)
return nil
}

if !t.GlobalRateLimitEnabled {
message := "Enable Ratelimit in the EnvoyGateway config to configure global rateLimit"
message := "Enable Ratelimit in the EnvoyGateway config to configure global rateLimit."

Check warning on line 404 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L404

Added line #L404 was not covered by tests
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
Expand All @@ -333,92 +410,92 @@ func (t *Translator) buildRateLimit(policy *egv1a1.BackendTrafficPolicy) *ir.Rat
)
return nil
}

rateLimit := &ir.RateLimit{
Global: &ir.GlobalRateLimit{
Rules: make([]*ir.RateLimitRule, len(policy.Spec.RateLimit.Global.Rules)),
},
}

rules := rateLimit.Global.Rules
irRules := rateLimit.Global.Rules
var err error
for i, rule := range policy.Spec.RateLimit.Global.Rules {
rules[i] = &ir.RateLimitRule{
Limit: &ir.RateLimitValue{
Requests: rule.Limit.Requests,
Unit: ir.RateLimitUnit(rule.Limit.Unit),
},
HeaderMatches: make([]*ir.StringMatch, 0),
irRules[i], err = buildRateLimitRule(rule)
if err != nil {
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
status.Error2ConditionMsg(err),
)
return nil

Check warning on line 431 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L425-L431

Added lines #L425 - L431 were not covered by tests
}
for _, match := range rule.ClientSelectors {
for _, header := range match.Headers {
switch {
case header.Type == nil && header.Value != nil:
fallthrough
case *header.Type == egv1a1.HeaderMatchExact && header.Value != nil:
m := &ir.StringMatch{
Name: header.Name,
Exact: header.Value,
}
rules[i].HeaderMatches = append(rules[i].HeaderMatches, m)
case *header.Type == egv1a1.HeaderMatchRegularExpression && header.Value != nil:
m := &ir.StringMatch{
Name: header.Name,
SafeRegex: header.Value,
}
rules[i].HeaderMatches = append(rules[i].HeaderMatches, m)
case *header.Type == egv1a1.HeaderMatchDistinct && header.Value == nil:
m := &ir.StringMatch{
Name: header.Name,
Distinct: true,
}
rules[i].HeaderMatches = append(rules[i].HeaderMatches, m)
default:
// set negative status condition.
message := "Unable to translate rateLimit. Either the header.Type is not valid or the header is missing a value"
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)

return nil
}

return rateLimit
}

func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) {
irRule := &ir.RateLimitRule{
Limit: ir.RateLimitValue{
Requests: rule.Limit.Requests,
Unit: ir.RateLimitUnit(rule.Limit.Unit),
},
HeaderMatches: make([]*ir.StringMatch, 0),
}
for _, match := range rule.ClientSelectors {
for _, header := range match.Headers {
switch {
case header.Type == nil && header.Value != nil:
fallthrough
case *header.Type == egv1a1.HeaderMatchExact && header.Value != nil:
m := &ir.StringMatch{
Name: header.Name,
Exact: header.Value,
}
irRule.HeaderMatches = append(irRule.HeaderMatches, m)
case *header.Type == egv1a1.HeaderMatchRegularExpression && header.Value != nil:
m := &ir.StringMatch{
Name: header.Name,
SafeRegex: header.Value,
}
irRule.HeaderMatches = append(irRule.HeaderMatches, m)

Check warning on line 462 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L457-L462

Added lines #L457 - L462 were not covered by tests
case *header.Type == egv1a1.HeaderMatchDistinct && header.Value == nil:
m := &ir.StringMatch{
Name: header.Name,
Distinct: true,
}
irRule.HeaderMatches = append(irRule.HeaderMatches, m)
default:
return nil, errors.New(
"unable to translate rateLimit. Either the header.Type is not valid or the header is missing a value")

Check warning on line 471 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L469-L471

Added lines #L469 - L471 were not covered by tests
}
}

if match.SourceCIDR != nil {
// distinct means that each IP Address within the specified Source IP CIDR is treated as a
// distinct client selector and uses a separate rate limit bucket/counter.
distinct := false
sourceCIDR := match.SourceCIDR.Value
if match.SourceCIDR.Type != nil && *match.SourceCIDR.Type == egv1a1.SourceMatchDistinct {
distinct = true
}
if match.SourceCIDR != nil {
// distinct means that each IP Address within the specified Source IP CIDR is treated as a
// distinct client selector and uses a separate rate limit bucket/counter.
distinct := false
sourceCIDR := match.SourceCIDR.Value
if match.SourceCIDR.Type != nil && *match.SourceCIDR.Type == egv1a1.SourceMatchDistinct {
distinct = true
}

ip, ipn, err := net.ParseCIDR(sourceCIDR)
if err != nil {
message := "Unable to translate rateLimit"
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)

return nil
}
ip, ipn, err := net.ParseCIDR(sourceCIDR)
if err != nil {
return nil, errors.New("unable to translate rateLimit")
}

Check warning on line 487 in internal/gatewayapi/backendtrafficpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/backendtrafficpolicy.go#L486-L487

Added lines #L486 - L487 were not covered by tests

mask, _ := ipn.Mask.Size()
rules[i].CIDRMatch = &ir.CIDRMatch{
CIDR: ipn.String(),
IPv6: ip.To4() == nil,
MaskLen: mask,
Distinct: distinct,
}
mask, _ := ipn.Mask.Size()
irRule.CIDRMatch = &ir.CIDRMatch{
CIDR: ipn.String(),
IPv6: ip.To4() == nil,
MaskLen: mask,
Distinct: distinct,
}
}
}

return rateLimit
return irRule, nil
}

func (t *Translator) buildLoadBalancer(policy *egv1a1.BackendTrafficPolicy) *ir.LoadBalancer {
Expand Down
Loading

0 comments on commit 47d6937

Please sign in to comment.