This is an automated email from the ASF dual-hosted git repository.
ronething pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new ace684d1 feat: add conflict detection for gateway proxy (#2600)
ace684d1 is described below
commit ace684d18fa0001f4cff2a9ce291a80f8e9897a7
Author: Ashing Zheng <[email protected]>
AuthorDate: Tue Oct 14 14:31:03 2025 +0800
feat: add conflict detection for gateway proxy (#2600)
Signed-off-by: Ashing Zheng <[email protected]>
---
internal/controller/indexer/tlsroute.go | 3 +-
internal/webhook/v1/gatewayproxy_webhook.go | 139 ++++++++++++++-
internal/webhook/v1/gatewayproxy_webhook_test.go | 217 ++++++++++++++++++++++-
test/e2e/webhook/gatewayproxy.go | 159 ++++++++++++++++-
4 files changed, 506 insertions(+), 12 deletions(-)
diff --git a/internal/controller/indexer/tlsroute.go
b/internal/controller/indexer/tlsroute.go
index 567131c4..acef5317 100644
--- a/internal/controller/indexer/tlsroute.go
+++ b/internal/controller/indexer/tlsroute.go
@@ -20,10 +20,11 @@ package indexer
import (
"context"
- internaltypes
"github.com/apache/apisix-ingress-controller/internal/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+
+ internaltypes
"github.com/apache/apisix-ingress-controller/internal/types"
)
func setupTLSRouteIndexer(mgr ctrl.Manager) error {
diff --git a/internal/webhook/v1/gatewayproxy_webhook.go
b/internal/webhook/v1/gatewayproxy_webhook.go
index 75bccea3..b76a8cf7 100644
--- a/internal/webhook/v1/gatewayproxy_webhook.go
+++ b/internal/webhook/v1/gatewayproxy_webhook.go
@@ -18,6 +18,8 @@ package v1
import (
"context"
"fmt"
+ "sort"
+ "strings"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@@ -63,7 +65,12 @@ func (v *GatewayProxyCustomValidator) ValidateCreate(ctx
context.Context, obj ru
}
gatewayProxyLog.Info("Validation for GatewayProxy upon creation",
"name", gp.GetName(), "namespace", gp.GetNamespace())
- return v.collectWarnings(ctx, gp), nil
+ warnings := v.collectWarnings(ctx, gp)
+ if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
+ return nil, err
+ }
+
+ return warnings, nil
}
func (v *GatewayProxyCustomValidator) ValidateUpdate(ctx context.Context,
oldObj, newObj runtime.Object) (admission.Warnings, error) {
@@ -73,7 +80,12 @@ func (v *GatewayProxyCustomValidator) ValidateUpdate(ctx
context.Context, oldObj
}
gatewayProxyLog.Info("Validation for GatewayProxy upon update", "name",
gp.GetName(), "namespace", gp.GetNamespace())
- return v.collectWarnings(ctx, gp), nil
+ warnings := v.collectWarnings(ctx, gp)
+ if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
+ return nil, err
+ }
+
+ return warnings, nil
}
func (v *GatewayProxyCustomValidator) ValidateDelete(context.Context,
runtime.Object) (admission.Warnings, error) {
@@ -111,3 +123,126 @@ func (v *GatewayProxyCustomValidator) collectWarnings(ctx
context.Context, gp *v
return warnings
}
+
+func (v *GatewayProxyCustomValidator) validateGatewayProxyConflict(ctx
context.Context, gp *v1alpha1.GatewayProxy) error {
+ current := buildGatewayProxyConfig(gp)
+ if !current.readyForConflict() {
+ return nil
+ }
+
+ var list v1alpha1.GatewayProxyList
+ if err := v.Client.List(ctx, &list); err != nil {
+ gatewayProxyLog.Error(err, "failed to list GatewayProxy objects
for conflict detection")
+ return fmt.Errorf("failed to list existing GatewayProxy
resources: %w", err)
+ }
+
+ for _, other := range list.Items {
+ if other.GetNamespace() == gp.GetNamespace() && other.GetName()
== gp.GetName() {
+ // skip self
+ continue
+ }
+ otherConfig := buildGatewayProxyConfig(&other)
+ if !otherConfig.readyForConflict() {
+ continue
+ }
+ if !current.sharesAdminKeyWith(otherConfig) {
+ continue
+ }
+ if current.serviceKey != "" && current.serviceKey ==
otherConfig.serviceKey {
+ return fmt.Errorf("gateway proxy configuration
conflict: GatewayProxy %s/%s and %s/%s both target %s while sharing %s",
+ gp.GetNamespace(), gp.GetName(),
+ other.GetNamespace(), other.GetName(),
+ current.serviceDescription,
+ current.adminKeyDetail(),
+ )
+ }
+ if len(current.endpoints) > 0 && len(otherConfig.endpoints) > 0
{
+ if overlap := current.endpointOverlap(otherConfig);
len(overlap) > 0 {
+ return fmt.Errorf("gateway proxy configuration
conflict: GatewayProxy %s/%s and %s/%s both target control plane endpoints [%s]
while sharing %s",
+ gp.GetNamespace(), gp.GetName(),
+ other.GetNamespace(), other.GetName(),
+ strings.Join(overlap, ", "),
+ current.adminKeyDetail(),
+ )
+ }
+ }
+ }
+
+ return nil
+}
+
+type gatewayProxyConfig struct {
+ inlineAdminKey string
+ secretKey string
+ serviceKey string
+ serviceDescription string
+ endpoints map[string]struct{}
+}
+
+func buildGatewayProxyConfig(gp *v1alpha1.GatewayProxy) gatewayProxyConfig {
+ var cfg gatewayProxyConfig
+
+ if gp == nil || gp.Spec.Provider == nil || gp.Spec.Provider.Type !=
v1alpha1.ProviderTypeControlPlane || gp.Spec.Provider.ControlPlane == nil {
+ return cfg
+ }
+
+ cp := gp.Spec.Provider.ControlPlane
+
+ if cp.Auth.AdminKey != nil {
+ if value := strings.TrimSpace(cp.Auth.AdminKey.Value); value !=
"" {
+ cfg.inlineAdminKey = value
+ } else if cp.Auth.AdminKey.ValueFrom != nil &&
cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {
+ ref := cp.Auth.AdminKey.ValueFrom.SecretKeyRef
+ cfg.secretKey = fmt.Sprintf("%s/%s:%s",
gp.GetNamespace(), ref.Name, ref.Key)
+ }
+ }
+
+ if cp.Service != nil && cp.Service.Name != "" {
+ cfg.serviceKey = fmt.Sprintf("service:%s/%s:%d",
gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
+ cfg.serviceDescription = fmt.Sprintf("Service %s/%s port %d",
gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
+ }
+
+ if len(cp.Endpoints) > 0 {
+ cfg.endpoints = make(map[string]struct{}, len(cp.Endpoints))
+ for _, endpoint := range cp.Endpoints {
+ cfg.endpoints[endpoint] = struct{}{}
+ }
+ }
+
+ return cfg
+}
+
+func (c gatewayProxyConfig) adminKeyDetail() string {
+ if c.secretKey != "" {
+ return fmt.Sprintf("AdminKey secret %s", c.secretKey)
+ }
+ return "the same inline AdminKey value"
+}
+
+func (c gatewayProxyConfig) sharesAdminKeyWith(other gatewayProxyConfig) bool {
+ if c.inlineAdminKey != "" && other.inlineAdminKey != "" {
+ return c.inlineAdminKey == other.inlineAdminKey
+ }
+ if c.secretKey != "" && other.secretKey != "" {
+ return c.secretKey == other.secretKey
+ }
+ return false
+}
+
+func (c gatewayProxyConfig) readyForConflict() bool {
+ if c.inlineAdminKey == "" && c.secretKey == "" {
+ return false
+ }
+ return c.serviceKey != "" || len(c.endpoints) > 0
+}
+
+func (c gatewayProxyConfig) endpointOverlap(other gatewayProxyConfig) []string
{
+ var overlap []string
+ for endpoint := range c.endpoints {
+ if _, ok := other.endpoints[endpoint]; ok {
+ overlap = append(overlap, endpoint)
+ }
+ }
+ sort.Strings(overlap)
+ return overlap
+}
diff --git a/internal/webhook/v1/gatewayproxy_webhook_test.go
b/internal/webhook/v1/gatewayproxy_webhook_test.go
index c43253c1..2768ac7a 100644
--- a/internal/webhook/v1/gatewayproxy_webhook_test.go
+++ b/internal/webhook/v1/gatewayproxy_webhook_test.go
@@ -29,6 +29,10 @@ import (
v1alpha1 "github.com/apache/apisix-ingress-controller/api/v1alpha1"
)
+const (
+ candidateName = "candidate"
+)
+
func buildGatewayProxyValidator(t *testing.T, objects ...runtime.Object)
*GatewayProxyCustomValidator {
t.Helper()
@@ -54,7 +58,7 @@ func newGatewayProxy() *v1alpha1.GatewayProxy {
Provider: &v1alpha1.GatewayProxyProvider{
Type: v1alpha1.ProviderTypeControlPlane,
ControlPlane: &v1alpha1.ControlPlaneProvider{
- Service:
&v1alpha1.ProviderService{Name: "control-plane"},
+ Service:
&v1alpha1.ProviderService{Name: "control-plane", Port: 9180},
Auth: v1alpha1.ControlPlaneAuth{
Type: v1alpha1.AuthTypeAdminKey,
AdminKey:
&v1alpha1.AdminKeyAuth{
@@ -72,6 +76,41 @@ func newGatewayProxy() *v1alpha1.GatewayProxy {
}
}
+func newGatewayProxyWithEndpoints(name string, endpoints []string)
*v1alpha1.GatewayProxy {
+ gp := newGatewayProxy()
+ gp.Name = name
+ gp.Spec.Provider.ControlPlane.Service = nil
+ gp.Spec.Provider.ControlPlane.Endpoints = endpoints
+ return gp
+}
+
+func setInlineAdminKey(gp *v1alpha1.GatewayProxy, value string) {
+ if gp == nil || gp.Spec.Provider == nil ||
gp.Spec.Provider.ControlPlane == nil {
+ return
+ }
+ if gp.Spec.Provider.ControlPlane.Auth.AdminKey == nil {
+ gp.Spec.Provider.ControlPlane.Auth.AdminKey =
&v1alpha1.AdminKeyAuth{}
+ }
+ gp.Spec.Provider.ControlPlane.Auth.AdminKey.Value = value
+ gp.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom = nil
+}
+
+func setSecretAdminKey(gp *v1alpha1.GatewayProxy, name, key string) {
+ if gp == nil || gp.Spec.Provider == nil ||
gp.Spec.Provider.ControlPlane == nil {
+ return
+ }
+ if gp.Spec.Provider.ControlPlane.Auth.AdminKey == nil {
+ gp.Spec.Provider.ControlPlane.Auth.AdminKey =
&v1alpha1.AdminKeyAuth{}
+ }
+ gp.Spec.Provider.ControlPlane.Auth.AdminKey.Value = ""
+ gp.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom =
&v1alpha1.AdminKeyValueFrom{
+ SecretKeyRef: &v1alpha1.SecretKeySelector{
+ Name: name,
+ Key: key,
+ },
+ }
+}
+
func TestGatewayProxyValidator_MissingService(t *testing.T) {
gp := newGatewayProxy()
gp.Spec.Provider.ControlPlane.Auth.AdminKey = nil
@@ -150,3 +189,179 @@ func TestGatewayProxyValidator_NoWarnings(t *testing.T) {
require.NoError(t, err)
require.Empty(t, warnings)
}
+
+func TestGatewayProxyValidator_DetectsServiceConflict(t *testing.T) {
+ existing := newGatewayProxy()
+ existing.Name = "existing"
+
+ service := &corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "control-plane",
+ Namespace: "default",
+ },
+ }
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "admin-key",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ "token": []byte("value"),
+ },
+ }
+
+ validator := buildGatewayProxyValidator(t, existing, service, secret)
+
+ candidate := newGatewayProxy()
+ candidate.Name = candidateName
+
+ warnings, err := validator.ValidateCreate(context.Background(),
candidate)
+ require.Error(t, err)
+ require.Len(t, warnings, 0)
+ require.Contains(t, err.Error(), "gateway proxy configuration conflict")
+ require.Contains(t, err.Error(), "Service default/control-plane port
9180")
+ require.Contains(t, err.Error(), "AdminKey secret
default/admin-key:token")
+}
+
+func TestGatewayProxyValidator_DetectsEndpointConflict(t *testing.T) {
+ existing := newGatewayProxyWithEndpoints("existing",
[]string{"https://127.0.0.1:9443", "https://10.0.0.1:9443"})
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "admin-key",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ "token": []byte("value"),
+ },
+ }
+ validator := buildGatewayProxyValidator(t, existing, secret)
+
+ candidate := newGatewayProxyWithEndpoints(candidateName,
[]string{"https://10.0.0.1:9443", "https://127.0.0.1:9443"})
+
+ warnings, err := validator.ValidateCreate(context.Background(),
candidate)
+ require.Error(t, err)
+ require.Len(t, warnings, 0)
+ require.Contains(t, err.Error(), "gateway proxy configuration conflict")
+ require.Contains(t, err.Error(), "endpoints [https://10.0.0.1:9443,
https://127.0.0.1:9443]")
+ require.Contains(t, err.Error(), "AdminKey secret
default/admin-key:token")
+}
+
+func TestGatewayProxyValidator_AllowsDistinctGatewayGroups(t *testing.T) {
+ existing := newGatewayProxyWithEndpoints("existing",
[]string{"https://127.0.0.1:9443"})
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "admin-key",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ "token": []byte("value"),
+ },
+ }
+ service := &corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "control-plane",
+ Namespace: "default",
+ },
+ }
+ validator := buildGatewayProxyValidator(t, existing, secret, service)
+
+ candidate := newGatewayProxy()
+ candidate.Name = candidateName
+ candidate.Spec.Provider.ControlPlane.Service =
&v1alpha1.ProviderService{
+ Name: "control-plane",
+ Port: 9180,
+ }
+
+ warnings, err := validator.ValidateCreate(context.Background(),
candidate)
+ require.NoError(t, err)
+ require.Empty(t, warnings)
+}
+
+func TestGatewayProxyValidator_AllowsServiceConflictWithDifferentAdminSecret(t
*testing.T) {
+ existing := newGatewayProxy()
+ existing.Name = "existing"
+
+ candidate := newGatewayProxy()
+ candidate.Name = candidateName
+ setSecretAdminKey(candidate, "admin-key-alt", "token")
+
+ service := &corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "control-plane",
+ Namespace: "default",
+ },
+ }
+ existingSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "admin-key",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ "token": []byte("value"),
+ },
+ }
+ altSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "admin-key-alt",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ "token": []byte("value"),
+ },
+ }
+
+ validator := buildGatewayProxyValidator(t, existing, service,
existingSecret, altSecret)
+
+ warnings, err := validator.ValidateCreate(context.Background(),
candidate)
+ require.NoError(t, err)
+ require.Empty(t, warnings)
+}
+
+func TestGatewayProxyValidator_DetectsInlineAdminKeyConflict(t *testing.T) {
+ existing := newGatewayProxyWithEndpoints("existing",
[]string{"https://127.0.0.1:9443", "https://10.0.0.1:9443"})
+ setInlineAdminKey(existing, "inline-cred")
+
+ candidate := newGatewayProxyWithEndpoints(candidateName,
[]string{"https://10.0.0.1:9443"})
+ setInlineAdminKey(candidate, "inline-cred")
+
+ validator := buildGatewayProxyValidator(t, existing)
+
+ warnings, err := validator.ValidateCreate(context.Background(),
candidate)
+ require.Error(t, err)
+ require.Len(t, warnings, 0)
+ require.Contains(t, err.Error(), "gateway proxy configuration conflict")
+ require.Contains(t, err.Error(), "control plane endpoints
[https://10.0.0.1:9443]")
+ require.Contains(t, err.Error(), "inline AdminKey value")
+}
+
+func TestGatewayProxyValidator_AllowsEndpointOverlapWithDifferentAdminKey(t
*testing.T) {
+ existing := newGatewayProxyWithEndpoints("existing",
[]string{"https://127.0.0.1:9443", "https://10.0.0.1:9443"})
+
+ candidate := newGatewayProxyWithEndpoints(candidateName,
[]string{"https://10.0.0.1:9443", "https://192.168.0.1:9443"})
+ setSecretAdminKey(candidate, "admin-key-alt", "token")
+
+ existingSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "admin-key",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ "token": []byte("value"),
+ },
+ }
+ altSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "admin-key-alt",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ "token": []byte("value"),
+ },
+ }
+
+ validator := buildGatewayProxyValidator(t, existing, existingSecret,
altSecret)
+
+ warnings, err := validator.ValidateCreate(context.Background(),
candidate)
+ require.NoError(t, err)
+ require.Empty(t, warnings)
+}
diff --git a/test/e2e/webhook/gatewayproxy.go b/test/e2e/webhook/gatewayproxy.go
index 6b1f6189..4f2d12e4 100644
--- a/test/e2e/webhook/gatewayproxy.go
+++ b/test/e2e/webhook/gatewayproxy.go
@@ -33,11 +33,7 @@ var _ = Describe("Test GatewayProxy Webhook",
Label("webhook"), func() {
EnableWebhook: true,
})
- It("should warn on missing service or secret references", func() {
- missingService := "missing-control-plane"
- missingSecret := "missing-admin-secret"
- gpName := "webhook-gateway-proxy"
- gpWithSecrets := `
+ gatewayProxyTemplate := `
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
@@ -58,7 +54,12 @@ spec:
key: token
`
- output, err :=
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName,
missingService, missingSecret))
+ It("should warn on missing service or secret references", func() {
+ missingService := "missing-control-plane"
+ missingSecret := "missing-admin-secret"
+ gpName := "webhook-gateway-proxy"
+
+ output, err :=
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gatewayProxyTemplate,
gpName, missingService, missingSecret))
Expect(err).ShouldNot(HaveOccurred())
Expect(output).To(ContainSubstring(fmt.Sprintf("Warning:
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
Expect(output).To(ContainSubstring(fmt.Sprintf("Warning:
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
@@ -98,7 +99,7 @@ stringData:
err = s.DeleteResource("GatewayProxy", gpName)
Expect(err).ShouldNot(HaveOccurred())
- output, err =
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName,
missingService, missingSecret))
+ output, err =
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gatewayProxyTemplate,
gpName, missingService, missingSecret))
Expect(err).ShouldNot(HaveOccurred())
Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning:
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: Secret
key 'token' not found in Secret '%s/%s'", s.Namespace(), missingSecret)))
@@ -121,9 +122,151 @@ stringData:
err = s.DeleteResource("GatewayProxy", gpName)
Expect(err).ShouldNot(HaveOccurred())
- output, err =
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName,
missingService, missingSecret))
+ output, err =
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gatewayProxyTemplate,
gpName, missingService, missingSecret))
Expect(err).ShouldNot(HaveOccurred())
Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning:
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning:
Secret key 'token' not found in Secret '%s/%s'", s.Namespace(), missingSecret)))
})
+
+ Context("GatewayProxy configuration conflicts", func() {
+ It("should reject GatewayProxy that reuses the same Service and
AdminKey Secret as an existing one on create and update", func() {
+ serviceTemplate := `
+apiVersion: v1
+kind: Service
+metadata:
+ name: %s
+spec:
+ selector:
+ app: dummy-control-plane
+ ports:
+ - name: admin
+ port: 9180
+ targetPort: 9180
+`
+ secretTemplate := `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: %s
+type: Opaque
+stringData:
+ %s: %s
+`
+ serviceName := "gatewayproxy-shared-service"
+ secretName := "gatewayproxy-shared-secret"
+ initialProxy := "gatewayproxy-shared-primary"
+ conflictingProxy := "gatewayproxy-shared-conflict"
+
+
Expect(s.CreateResourceFromString(fmt.Sprintf(serviceTemplate,
serviceName))).ShouldNot(HaveOccurred(), "creating shared Service")
+
Expect(s.CreateResourceFromString(fmt.Sprintf(secretTemplate, secretName,
"token", "value"))).ShouldNot(HaveOccurred(), "creating shared Secret")
+
+ err :=
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, initialProxy,
serviceName, secretName))
+ Expect(err).ShouldNot(HaveOccurred(), "creating initial
GatewayProxy")
+
+ time.Sleep(2 * time.Second)
+
+ err =
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, conflictingProxy,
serviceName, secretName))
+ Expect(err).Should(HaveOccurred(), "expecting conflict
for duplicated GatewayProxy")
+ Expect(err.Error()).To(ContainSubstring("gateway proxy
configuration conflict"))
+
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(),
conflictingProxy)))
+
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(),
initialProxy)))
+ Expect(err.Error()).To(ContainSubstring("Service"))
+ Expect(err.Error()).To(ContainSubstring("AdminKey
secret"))
+
+ Expect(s.DeleteResource("GatewayProxy",
initialProxy)).ShouldNot(HaveOccurred())
+ Expect(s.DeleteResource("Service",
serviceName)).ShouldNot(HaveOccurred())
+ Expect(s.DeleteResource("Secret",
secretName)).ShouldNot(HaveOccurred())
+ })
+
+ It("should reject GatewayProxy that overlaps endpoints when
sharing inline AdminKey value", func() {
+ gatewayProxyTemplate := `
+apiVersion: apisix.apache.org/v1alpha1
+kind: GatewayProxy
+metadata:
+ name: %s
+spec:
+ provider:
+ type: ControlPlane
+ controlPlane:
+ endpoints:
+ - %s
+ - %s
+ auth:
+ type: AdminKey
+ adminKey:
+ value: "%s"
+`
+
+ existingProxy := "gatewayproxy-inline-primary"
+ conflictingProxy := "gatewayproxy-inline-conflict"
+ endpointA := "https://127.0.0.1:9443"
+ endpointB := "https://10.0.0.1:9443"
+ endpointC := "https://192.168.0.1:9443"
+ inlineKey := "inline-credential"
+
+ err :=
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, existingProxy,
endpointA, endpointB, inlineKey))
+ Expect(err).ShouldNot(HaveOccurred(), "creating
GatewayProxy with inline AdminKey")
+
+ time.Sleep(2 * time.Second)
+
+ err =
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, conflictingProxy,
endpointB, endpointC, inlineKey))
+ Expect(err).Should(HaveOccurred(), "expecting conflict
for overlapping endpoints with shared AdminKey")
+ Expect(err.Error()).To(ContainSubstring("gateway proxy
configuration conflict"))
+
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(),
conflictingProxy)))
+
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(),
existingProxy)))
+ Expect(err.Error()).To(ContainSubstring("control plane
endpoints"))
+ Expect(err.Error()).To(ContainSubstring("inline
AdminKey value"))
+ })
+
+ It("should reject GatewayProxy update that creates conflict
with another GatewayProxy", func() {
+ serviceTemplate := `
+apiVersion: v1
+kind: Service
+metadata:
+ name: %s
+spec:
+ selector:
+ app: dummy-control-plane
+ ports:
+ - name: admin
+ port: 9180
+ targetPort: 9180
+`
+ secretTemplate := `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: %s
+type: Opaque
+stringData:
+ %s: %s
+`
+ sharedServiceName :=
"gatewayproxy-update-shared-service"
+ sharedSecretName := "gatewayproxy-update-shared-secret"
+ uniqueServiceName :=
"gatewayproxy-update-unique-service"
+ proxyA := "gatewayproxy-update-a"
+ proxyB := "gatewayproxy-update-b"
+
+
Expect(s.CreateResourceFromString(fmt.Sprintf(serviceTemplate,
sharedServiceName))).ShouldNot(HaveOccurred(), "creating shared Service")
+
Expect(s.CreateResourceFromString(fmt.Sprintf(serviceTemplate,
uniqueServiceName))).ShouldNot(HaveOccurred(), "creating unique Service")
+
Expect(s.CreateResourceFromString(fmt.Sprintf(secretTemplate, sharedSecretName,
"token", "value"))).ShouldNot(HaveOccurred(), "creating shared Secret")
+
+ err :=
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, proxyA,
sharedServiceName, sharedSecretName))
+ Expect(err).ShouldNot(HaveOccurred(), "creating
GatewayProxy A with shared Service and Secret")
+
+ time.Sleep(2 * time.Second)
+
+ err =
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, proxyB,
uniqueServiceName, sharedSecretName))
+ Expect(err).ShouldNot(HaveOccurred(), "creating
GatewayProxy B with unique Service but same Secret")
+
+ time.Sleep(2 * time.Second)
+
+ By("updating GatewayProxy B to use the same Service as
GatewayProxy A, causing conflict")
+ err =
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, proxyB,
sharedServiceName, sharedSecretName))
+ Expect(err).Should(HaveOccurred(), "expecting conflict
when updating to same Service")
+ Expect(err.Error()).To(ContainSubstring("gateway proxy
configuration conflict"))
+
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(),
proxyA)))
+
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(),
proxyB)))
+ })
+ })
})