Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add cloud endpoint pooling support #582

Merged
merged 2 commits into from
Jan 30, 2025
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
8 changes: 8 additions & 0 deletions api/ngrok/v1alpha1/cloudendpoint_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ type CloudEndpointSpec struct {
// +kubebuilder:validation:Optional
TrafficPolicyName string `json:"trafficPolicyName,omitempty"`

// Controlls whether or not the Cloud Endpoint should allow pooling with other
// Cloud Endpoints sharing the same URL. When Cloud Endpoints are pooled, any requests
// going to the URL for the pooled endpoint will be distributed among all Cloud Endpoints
// in the pool. A URL can only be shared across multiple Cloud Endpoints if they all have pooling enabled.
//
// +kubebuilder:validation:Optional
PoolingEnabled bool `json:"poolingEnabled"`

// Allows inline definition of a TrafficPolicy object
//
// +kubebuilder:validation:Optional
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/sync v0.10.0
google.golang.org/protobuf v1.36.4
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.29.2
k8s.io/apimachinery v0.29.2
k8s.io/client-go v0.29.2
Expand Down Expand Up @@ -97,7 +98,6 @@ require (
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.29.2 // indirect
k8s.io/component-base v0.29.2 // indirect
Expand Down

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

17 changes: 17 additions & 0 deletions internal/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
MappingStrategyAnnotationKey = "mapping-strategy"
MappingStrategy_Endpoints = "endpoints"
MappingStrategy_Edges = "edges"

EndpointPoolingAnnotation = "k8s.ngrok.com/pooling-enabled"
EndpointPoolingAnnotationKey = "pooling-enabled"
)

type RouteModules struct {
Expand Down Expand Up @@ -148,3 +151,17 @@
}
return strings.EqualFold(val, MappingStrategy_Endpoints), nil
}

// Whether or not we should use endpoint pooling
// from the annotation "k8s.ngrok.com/pooling-enabled" if it is present. Otherwise, it defaults to false
func ExtractUseEndpointPooling(obj client.Object) (bool, error) {
val, err := parser.GetStringAnnotation(EndpointPoolingAnnotationKey, obj)
if err != nil {
if errors.IsMissingAnnotations(err) {
return false, nil
}
return false, err

Check warning on line 163 in internal/annotations/annotations.go

View check run for this annotation

Codecov / codecov/patch

internal/annotations/annotations.go#L163

Added line #L163 was not covered by tests
}

return strings.EqualFold(val, "true"), nil
}
61 changes: 61 additions & 0 deletions internal/annotations/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,64 @@ func TestExtractUseEndpoints(t *testing.T) {
})
}
}

func TestExtractUseEndpointPooling(t *testing.T) {
tests := []struct {
name string
annotations map[string]string
expected bool
expectedErr error
}{
{
name: "Pooling enabled",
annotations: map[string]string{
"k8s.ngrok.com/pooling-enabled": "true",
},
expected: true,
expectedErr: nil,
},
{
name: "Pooling disabled",
annotations: map[string]string{
"k8s.ngrok.com/pooling-enabled": "false",
},
expected: false,
expectedErr: nil,
},
{
name: "Invalid value",
annotations: map[string]string{
"k8s.ngrok.com/pooling-enabled": "foo",
},
expected: false,
expectedErr: nil,
},
{
name: "Annotation not present",
annotations: nil,
expected: false,
expectedErr: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
obj := &networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ingress",
Namespace: "default",
Annotations: tc.annotations,
},
}

useEndpoints, err := annotations.ExtractUseEndpointPooling(obj)
if tc.expectedErr != nil {
require.Error(t, err)
assert.Equal(t, tc.expectedErr, err)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expected, useEndpoints)
}
})
}
}
12 changes: 11 additions & 1 deletion internal/controller/ingress/service_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,15 @@

internalURL := fmt.Sprintf("tcp://%s.%s.%s.internal:%d", svc.UID, svc.Name, svc.Namespace, port)

// Get whether endpoint pooling should be enabled/disabled from annotations
useEndpointPooling, err := annotations.ExtractUseEndpointPooling(svc)
if err != nil {
log.Error(err, "failed to check endpoints-enabled annotation for service",
"service", fmt.Sprintf("%s.%s", svc.Name, svc.Namespace),
)
return objects, err
}

Check warning on line 413 in internal/controller/ingress/service_controller.go

View check run for this annotation

Codecov / codecov/patch

internal/controller/ingress/service_controller.go#L406-L413

Added lines #L406 - L413 were not covered by tests

// The final traffic policy that will be applied to the CloudEndpoint
tp := trafficpolicy.NewTrafficPolicy()

Expand Down Expand Up @@ -476,7 +485,8 @@
},
},
Spec: ngrokv1alpha1.CloudEndpointSpec{
URL: cloudEndpointURL,
URL: cloudEndpointURL,
PoolingEnabled: useEndpointPooling,

Check warning on line 489 in internal/controller/ingress/service_controller.go

View check run for this annotation

Codecov / codecov/patch

internal/controller/ingress/service_controller.go#L488-L489

Added lines #L488 - L489 were not covered by tests
TrafficPolicy: &ngrokv1alpha1.NgrokTrafficPolicySpec{
Policy: rawPolicy,
},
Expand Down
26 changes: 14 additions & 12 deletions internal/controller/ngrok/cloudendpoint_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,13 @@
}

createParams := &ngrok.EndpointCreate{
Type: "cloud",
URL: clep.Spec.URL,
Description: &clep.Spec.Description,
Metadata: &clep.Spec.Metadata,
TrafficPolicy: policy,
Bindings: clep.Spec.Bindings,
Type: "cloud",
URL: clep.Spec.URL,
Description: &clep.Spec.Description,
Metadata: &clep.Spec.Metadata,
TrafficPolicy: policy,
Bindings: clep.Spec.Bindings,
PoolingEnabled: clep.Spec.PoolingEnabled,

Check warning on line 163 in internal/controller/ngrok/cloudendpoint_controller.go

View check run for this annotation

Codecov / codecov/patch

internal/controller/ngrok/cloudendpoint_controller.go#L157-L163

Added lines #L157 - L163 were not covered by tests
}

ngrokClep, err := r.NgrokClientset.Endpoints().Create(ctx, createParams)
Expand All @@ -184,12 +185,13 @@
}

updateParams := &ngrok.EndpointUpdate{
ID: clep.Status.ID,
Url: &clep.Spec.URL,
Description: &clep.Spec.Description,
Metadata: &clep.Spec.Metadata,
TrafficPolicy: &policy,
Bindings: clep.Spec.Bindings,
ID: clep.Status.ID,
Url: &clep.Spec.URL,
Description: &clep.Spec.Description,
Metadata: &clep.Spec.Metadata,
TrafficPolicy: &policy,
Bindings: clep.Spec.Bindings,
PoolingEnabled: clep.Spec.PoolingEnabled,

Check warning on line 194 in internal/controller/ngrok/cloudendpoint_controller.go

View check run for this annotation

Codecov / codecov/patch

internal/controller/ngrok/cloudendpoint_controller.go#L188-L194

Added lines #L188 - L194 were not covered by tests
}

ngrokClep, err := r.NgrokClientset.Endpoints().Update(ctx, updateParams)
Expand Down
11 changes: 7 additions & 4 deletions internal/ir/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ type IRHostname string
type IRVirtualHost struct {
// The names of any resources (such as Ingress) that were used in the construction of this IRVirtualHost
// Currently only used for debug/error logs, but can be added to generated resource statuses
OwningResources []OwningResource
Hostname string
OwningResources []OwningResource
Hostname string
EndpointPoolingEnabled bool

// Keeps track of the namespace for this hostname. Since we do not allow multiple endpoints with the same hostname, we cannot support multiple ingresses
// using the same hostname in different namespaces.
Namespace string

// This traffic policy will apply to all routes under this hostname
TrafficPolicy *trafficpolicy.TrafficPolicy
Routes []*IRRoute
TrafficPolicy *trafficpolicy.TrafficPolicy
TrafficPolicyObj *OwningResource // Reference to the object that the above traffic policy config was loaded from

Routes []*IRRoute

// The following is used to support ingress default backends (currently only supported for endpoints and not edges)
DefaultDestination *IRDestination
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Ingresses with conflicting default backends
input:
ingressClasses:
- apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ngrok-operator
app.kubernetes.io/name: ngrok-operator
app.kubernetes.io/part-of: ngrok-operator
name: ngrok
spec:
controller: k8s.ngrok.com/ingress-controller
ingresses:
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
k8s.ngrok.com/mapping-strategy: "endpoints"
name: test-ingress-1
namespace: default
spec:
ingressClassName: ngrok
defaultBackend:
service:
name: test-service-1
port:
number: 8080
rules:
- host: test-ingresses.ngrok.io
http:
paths:
- path: /test-1
pathType: Prefix
backend:
service:
name: test-service-1
port:
number: 8080
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
k8s.ngrok.com/mapping-strategy: "endpoints"
name: test-ingress-2
namespace: default
spec:
ingressClassName: ngrok
defaultBackend:
service:
name: test-service-2
port:
number: 8080
rules:
- host: test-ingresses.ngrok.io
http:
paths:
- path: /test-2
pathType: Prefix
backend:
service:
name: test-service-2
port:
number: 8080
services:
- apiVersion: v1
kind: Service
metadata:
name: test-service-1
namespace: default
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: http
type: ClusterIP
- apiVersion: v1
kind: Service
metadata:
name: test-service-2
namespace: default
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: http
type: ClusterIP
trafficPolicies: []
expected:
# Generated cloud endpoint should have the routes and default backend from the first ingress, but
# the second ingress will not be processed due to the conflicting default destination
cloudEndpoints:
- apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
labels:
k8s.ngrok.com/controller-name: test-manager-name
k8s.ngrok.com/controller-namespace: test-manager-namespace
name: test-ingresses.ngrok.io
namespace: default
spec:
url: https://test-ingresses.ngrok.io
trafficPolicy:
policy:
on_http_request:
- name: Generated-Route-/test-1
expressions:
- req.url.path.startsWith("/test-1")
actions:
- type: forward-internal
config:
url: https://e3b0c-test-service-1-default-8080.internal

- name: Generated-Route-Default-Backend
actions:
- type: forward-internal
config:
url: https://e3b0c-test-service-1-default-8080.internal
agentEndpoints:
- apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
labels:
k8s.ngrok.com/controller-name: test-manager-name
k8s.ngrok.com/controller-namespace: test-manager-namespace
name: e3b0c-test-service-1-default-8080
namespace: default
spec:
url: "https://e3b0c-test-service-1-default-8080.internal"
upstream:
url: "http://test-service-1.default:8080"

Loading