Skip to content

Commit

Permalink
feat(translator): Implement BTP CircuitBreaker API (#2330)
Browse files Browse the repository at this point in the history
* Implement BTP CircuitBreaker API

Signed-off-by: Guy Daich <guy.daich@sap.com>

* Fix testdate gen

Signed-off-by: Guy Daich <guy.daich@sap.com>

* review fixes

Signed-off-by: Guy Daich <guy.daich@sap.com>

* small fix, rebase

Signed-off-by: Guy Daich <guy.daich@sap.com>

* gen fix

Signed-off-by: Guy Daich <guy.daich@sap.com>

---------

Signed-off-by: Guy Daich <guy.daich@sap.com>
  • Loading branch information
guydc authored Dec 21, 2023
1 parent a4f0396 commit b6f4306
Show file tree
Hide file tree
Showing 18 changed files with 984 additions and 12 deletions.
68 changes: 68 additions & 0 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
rl *ir.RateLimit
lb *ir.LoadBalancer
pp *ir.ProxyProtocol
cb *ir.CircuitBreaker
)

// Build IR
Expand All @@ -256,6 +257,10 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
if policy.Spec.ProxyProtocol != nil {
pp = t.buildProxyProtocol(policy)
}
if policy.Spec.CircuitBreaker != nil {
cb = t.buildCircuitBreaker(policy)
}

// Apply IR to all relevant routes
prefix := irRoutePrefix(route)
for _, ir := range xdsIR {
Expand All @@ -266,6 +271,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
r.RateLimit = rl
r.LoadBalancer = lb
r.ProxyProtocol = pp
r.CircuitBreaker = cb
}
}
}
Expand All @@ -278,6 +284,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
rl *ir.RateLimit
lb *ir.LoadBalancer
pp *ir.ProxyProtocol
cb *ir.CircuitBreaker
)

// Build IR
Expand All @@ -290,6 +297,9 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
if policy.Spec.ProxyProtocol != nil {
pp = t.buildProxyProtocol(policy)
}
if policy.Spec.CircuitBreaker != nil {
cb = t.buildCircuitBreaker(policy)
}
// Apply IR to all the routes within the specific Gateway
// If the feature is already set, then skip it, since it must be have
// set by a policy attaching to the route
Expand All @@ -309,6 +319,9 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
if r.ProxyProtocol == nil {
r.ProxyProtocol = pp
}
if r.CircuitBreaker == nil {
r.CircuitBreaker = cb
}
}
}

Expand Down Expand Up @@ -643,3 +656,58 @@ func ratelimitUnitToDuration(unit egv1a1.RateLimitUnit) int64 {
}
return seconds
}

func (t *Translator) buildCircuitBreaker(policy *egv1a1.BackendTrafficPolicy) *ir.CircuitBreaker {
var cb *ir.CircuitBreaker
pcb := policy.Spec.CircuitBreaker

if pcb != nil {
cb = &ir.CircuitBreaker{}

if pcb.MaxConnections != nil {
if ui32, ok := int64ToUint32(*pcb.MaxConnections); ok {
cb.MaxConnections = &ui32
} else {
setCircuitBreakerPolicyErrorCondition(policy, fmt.Sprintf("invalid MaxConnections value %d", *pcb.MaxConnections))
return nil
}
}

if pcb.MaxParallelRequests != nil {
if ui32, ok := int64ToUint32(*pcb.MaxParallelRequests); ok {
cb.MaxParallelRequests = &ui32
} else {
setCircuitBreakerPolicyErrorCondition(policy, fmt.Sprintf("invalid MaxParallelRequests value %d", *pcb.MaxParallelRequests))
return nil
}
}

if pcb.MaxPendingRequests != nil {
if ui32, ok := int64ToUint32(*pcb.MaxPendingRequests); ok {
cb.MaxPendingRequests = &ui32
} else {
setCircuitBreakerPolicyErrorCondition(policy, fmt.Sprintf("invalid MaxPendingRequests value %d", *pcb.MaxPendingRequests))
return nil
}
}
}

return cb
}

func setCircuitBreakerPolicyErrorCondition(policy *egv1a1.BackendTrafficPolicy, errMsg string) {
message := fmt.Sprintf("Unable to translate Circuit Breaker: %s", errMsg)
status.SetBackendTrafficPolicyCondition(policy,
gwv1a2.PolicyConditionAccepted,
metav1.ConditionFalse,
gwv1a2.PolicyReasonInvalid,
message,
)
}

func int64ToUint32(in int64) (uint32, bool) {
if in >= 0 && in <= math.MaxUint32 {
return uint32(in), true
}
return 0, false
}
52 changes: 52 additions & 0 deletions internal/gatewayapi/backendtrafficpolicy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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 gatewayapi

import (
"math"
"testing"

"github.com/stretchr/testify/require"
)

func TestInt64ToUint32(t *testing.T) {
type testCase struct {
Name string
In int64
Out uint32
Success bool
}

testCases := []testCase{
{
Name: "valid",
In: 1024,
Out: 1024,
Success: true,
},
{
Name: "invalid-underflow",
In: -1,
Out: 0,
Success: false,
},
{
Name: "invalid-overflow",
In: math.MaxUint32 + 1,
Out: 0,
Success: false,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
out, success := int64ToUint32(tc.In)
require.Equal(t, tc.Out, out)
require.Equal(t, tc.Success, success)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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
backendTrafficPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: envoy-gateway
name: policy-for-gateway
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-1
namespace: envoy-gateway
circuitBreaker:
maxConnections: -1
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
backendTrafficPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
creationTimestamp: null
name: policy-for-gateway
namespace: envoy-gateway
spec:
circuitBreaker:
maxConnections: -1
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-1
namespace: envoy-gateway
status:
conditions:
- lastTransitionTime: null
message: 'Unable to translate Circuit Breaker: invalid MaxConnections value
-1'
reason: Invalid
status: "False"
type: Accepted
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
creationTimestamp: null
name: gateway-1
namespace: envoy-gateway
spec:
gatewayClassName: envoy-gateway-class
listeners:
- allowedRoutes:
namespaces:
from: All
name: http
port: 80
protocol: HTTP
status:
listeners:
- attachedRoutes: 1
conditions:
- lastTransitionTime: null
message: Sending translated listener configuration to the data plane
reason: Programmed
status: "True"
type: Programmed
- lastTransitionTime: null
message: Listener has been successfully translated
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: null
message: Listener references have been resolved
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
name: http
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRoute
- group: gateway.networking.k8s.io
kind: GRPCRoute
grpcRoutes:
- apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GRPCRoute
metadata:
creationTimestamp: null
name: grpcroute-1
namespace: default
spec:
parentRefs:
- name: gateway-1
namespace: envoy-gateway
sectionName: http
rules:
- backendRefs:
- name: service-1
port: 8080
status:
parents:
- conditions:
- lastTransitionTime: null
message: Route is accepted
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: null
message: Resolved all the Object references for the Route
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parentRef:
name: gateway-1
namespace: envoy-gateway
sectionName: http
infraIR:
envoy-gateway/gateway-1:
proxy:
listeners:
- address: null
name: envoy-gateway/gateway-1/http
ports:
- containerPort: 10080
name: http
protocol: HTTP
servicePort: 80
metadata:
labels:
gateway.envoyproxy.io/owning-gateway-name: gateway-1
gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
name: envoy-gateway/gateway-1
xdsIR:
envoy-gateway/gateway-1:
accessLog:
text:
- path: /dev/stdout
http:
- address: 0.0.0.0
hostnames:
- '*'
isHTTP2: true
name: envoy-gateway/gateway-1/http
port: 10080
routes:
- backendWeights:
invalid: 0
valid: 0
destination:
name: grpcroute/default/grpcroute-1/rule/0
settings:
- addressType: IP
endpoints:
- host: 7.7.7.7
port: 8080
protocol: GRPC
weight: 1
hostname: '*'
name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
Loading

0 comments on commit b6f4306

Please sign in to comment.