From bb2f1a597e15c2bc33ef070851d33a45496bbf5e Mon Sep 17 00:00:00 2001 From: bdchatham Date: Fri, 10 Apr 2026 12:37:52 -0700 Subject: [PATCH 1/2] feat: dual hostname generation for platform.sei.io MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SEI_GATEWAY_PUBLIC_DOMAIN (required) to platform config. Each HTTPRoute now emits two hostnames: - {name}-{protocol}.{domain} (existing internal domain) - {name}-{protocol}.{namespace}.{publicDomain} (public-facing) Both hostnames land in the same HTTPRoute spec.hostnames[] array. The Gateway selects the correct listener/cert via SNI matching. Hostname format changed from {name}.{protocol}.{domain} to {name}-{protocol}.{domain} for both domains — this is a breaking change for existing routes, requiring redeployment of affected SeiNodeDeployments. Co-Authored-By: Claude Opus 4.6 (1M context) --- cmd/main.go | 16 ++-- .../controller/nodedeployment/controller.go | 7 +- .../controller/nodedeployment/networking.go | 14 ++-- .../nodedeployment/networking_test.go | 76 ++++++++++--------- internal/platform/platform.go | 8 +- internal/platform/platformtest/config.go | 1 + 6 files changed, 67 insertions(+), 55 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index ce15ea6..c0083c9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -135,6 +135,7 @@ func main() { GatewayName: os.Getenv("SEI_GATEWAY_NAME"), GatewayNamespace: os.Getenv("SEI_GATEWAY_NAMESPACE"), GatewayDomain: os.Getenv("SEI_GATEWAY_DOMAIN"), + GatewayPublicDomain: os.Getenv("SEI_GATEWAY_PUBLIC_DOMAIN"), } if err := platformCfg.Validate(); err != nil { @@ -180,13 +181,14 @@ func main() { //nolint:staticcheck // migrating to events.EventRecorder API is a separate effort recorder := mgr.GetEventRecorderFor("seinodedeployment-controller") if err := (&nodedeploymentcontroller.SeiNodeDeploymentReconciler{ - Client: kc, - Scheme: mgr.GetScheme(), - Recorder: recorder, - ControllerSA: controllerSA, - GatewayName: platformCfg.GatewayName, - GatewayNamespace: platformCfg.GatewayNamespace, - GatewayDomain: platformCfg.GatewayDomain, + Client: kc, + Scheme: mgr.GetScheme(), + Recorder: recorder, + ControllerSA: controllerSA, + GatewayName: platformCfg.GatewayName, + GatewayNamespace: platformCfg.GatewayNamespace, + GatewayDomain: platformCfg.GatewayDomain, + GatewayPublicDomain: platformCfg.GatewayPublicDomain, PlanExecutor: &planner.Executor[*seiv1alpha1.SeiNodeDeployment]{ Client: kc, ConfigFor: func(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) task.ExecutionConfig { diff --git a/internal/controller/nodedeployment/controller.go b/internal/controller/nodedeployment/controller.go index 5b4c6a0..be7de09 100644 --- a/internal/controller/nodedeployment/controller.go +++ b/internal/controller/nodedeployment/controller.go @@ -43,9 +43,10 @@ type SeiNodeDeploymentReconciler struct { // GatewayName, GatewayNamespace, and GatewayDomain identify the platform // Gateway for HTTPRoute parentRefs and hostname derivation. // Read from SEI_GATEWAY_NAME / SEI_GATEWAY_NAMESPACE / SEI_GATEWAY_DOMAIN. - GatewayName string - GatewayNamespace string - GatewayDomain string + GatewayName string + GatewayNamespace string + GatewayDomain string + GatewayPublicDomain string // PlanExecutor drives group-level task plans (e.g. genesis assembly). PlanExecutor planner.PlanExecutor[*seiv1alpha1.SeiNodeDeployment] diff --git a/internal/controller/nodedeployment/networking.go b/internal/controller/nodedeployment/networking.go index 200297e..689230e 100644 --- a/internal/controller/nodedeployment/networking.go +++ b/internal/controller/nodedeployment/networking.go @@ -208,7 +208,7 @@ func portsForMode(mode seiconfig.NodeMode) []corev1.ServicePort { } func (r *SeiNodeDeploymentReconciler) reconcileRoute(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) error { - routes := resolveEffectiveRoutes(group, r.GatewayDomain) + routes := resolveEffectiveRoutes(group, r.GatewayDomain, r.GatewayPublicDomain) if len(routes) == 0 { removeCondition(group, seiv1alpha1.ConditionRouteReady) return r.deleteHTTPRoutesByLabel(ctx, group) @@ -234,7 +234,7 @@ func (r *SeiNodeDeploymentReconciler) deleteHTTPRoutesByLabel(ctx context.Contex return nil } -func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain string) []effectiveRoute { +func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain, publicDomain string) []effectiveRoute { modePorts := seiconfig.NodePortsForMode(groupMode(group)) activePorts := make(map[string]bool, len(modePorts)) @@ -247,10 +247,14 @@ func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain string) if !isProtocolActiveForMode(proto.Prefix, activePorts) { continue } + subdomain := fmt.Sprintf("%s-%s", group.Name, proto.Prefix) er := effectiveRoute{ - Name: fmt.Sprintf("%s-%s", group.Name, proto.Prefix), - Hostnames: []string{fmt.Sprintf("%s.%s.%s", group.Name, proto.Prefix, domain)}, - Port: proto.Port, + Name: subdomain, + Hostnames: []string{ + fmt.Sprintf("%s.%s", subdomain, domain), + fmt.Sprintf("%s.%s.%s", subdomain, group.Namespace, publicDomain), + }, + Port: proto.Port, } if proto.Prefix == "evm" && activePorts["evm-ws"] { er.WSPort = seiconfig.PortEVMWS diff --git a/internal/controller/nodedeployment/networking_test.go b/internal/controller/nodedeployment/networking_test.go index 58e1bd3..e4c0c93 100644 --- a/internal/controller/nodedeployment/networking_test.go +++ b/internal/controller/nodedeployment/networking_test.go @@ -135,29 +135,29 @@ func TestGenerateExternalService_FullModeIncludesAllPorts(t *testing.T) { func TestResolveEffectiveRoutes_FullMode_FourRoutes(t *testing.T) { g := NewWithT(t) - group := newTestGroup("pacific-1-rpc", "sei") + group := newTestGroup("pacific-1-wave", "pacific-1") group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ Service: &seiv1alpha1.ExternalServiceConfig{}, Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") g.Expect(routes).To(HaveLen(4)) type routeExpectation struct { - Name string - Hostname string - Port int32 + Name string + Hostnames []string + Port int32 } expected := []routeExpectation{ - {"pacific-1-rpc-evm", "pacific-1-rpc.evm.prod.platform.sei.io", 8545}, - {"pacific-1-rpc-rpc", "pacific-1-rpc.rpc.prod.platform.sei.io", 26657}, - {"pacific-1-rpc-rest", "pacific-1-rpc.rest.prod.platform.sei.io", 1317}, - {"pacific-1-rpc-grpc", "pacific-1-rpc.grpc.prod.platform.sei.io", 9090}, + {"pacific-1-wave-evm", []string{"pacific-1-wave-evm.prod.platform.sei.io", "pacific-1-wave-evm.pacific-1.platform.sei.io"}, 8545}, + {"pacific-1-wave-rpc", []string{"pacific-1-wave-rpc.prod.platform.sei.io", "pacific-1-wave-rpc.pacific-1.platform.sei.io"}, 26657}, + {"pacific-1-wave-rest", []string{"pacific-1-wave-rest.prod.platform.sei.io", "pacific-1-wave-rest.pacific-1.platform.sei.io"}, 1317}, + {"pacific-1-wave-grpc", []string{"pacific-1-wave-grpc.prod.platform.sei.io", "pacific-1-wave-grpc.pacific-1.platform.sei.io"}, 9090}, } for i, exp := range expected { g.Expect(routes[i].Name).To(Equal(exp.Name)) - g.Expect(routes[i].Hostnames).To(Equal([]string{exp.Hostname})) + g.Expect(routes[i].Hostnames).To(Equal(exp.Hostnames)) g.Expect(routes[i].Port).To(Equal(exp.Port)) } } @@ -172,7 +172,7 @@ func TestResolveEffectiveRoutes_ArchiveMode_FourRoutes(t *testing.T) { Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") g.Expect(routes).To(HaveLen(4)) } @@ -186,44 +186,45 @@ func TestResolveEffectiveRoutes_ValidatorMode_NoRoutes(t *testing.T) { Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") g.Expect(routes).To(BeEmpty()) } func TestGenerateHTTPRoute_HostnamePattern(t *testing.T) { g := NewWithT(t) - group := newTestGroup("pacific-1-rpc", "sei") + group := newTestGroup("pacific-1-wave", "pacific-1") group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ Service: &seiv1alpha1.ExternalServiceConfig{}, Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") g.Expect(routes).NotTo(BeEmpty()) for _, er := range routes { route := generateHTTPRoute(group, er, "sei-gateway", "istio-system") spec := route.Object["spec"].(map[string]any) hostnames := spec["hostnames"].([]any) - g.Expect(hostnames).To(HaveLen(1)) - g.Expect(hostnames[0]).To(MatchRegexp(`^pacific-1-rpc\.\w+\.prod\.platform\.sei\.io$`)) + g.Expect(hostnames).To(HaveLen(2)) + g.Expect(hostnames[0]).To(MatchRegexp(`^pacific-1-wave-\w+\.prod\.platform\.sei\.io$`)) + g.Expect(hostnames[1]).To(MatchRegexp(`^pacific-1-wave-\w+\.pacific-1\.platform\.sei\.io$`)) } } func TestGenerateHTTPRoute_EVMMerged(t *testing.T) { g := NewWithT(t) - group := newTestGroup("pacific-1-rpc", "sei") + group := newTestGroup("pacific-1-wave", "pacific-1") group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ Service: &seiv1alpha1.ExternalServiceConfig{}, Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") var evmRoute effectiveRoute evmCount := 0 for _, r := range routes { - if r.Name == "pacific-1-rpc-evm" { + if r.Name == "pacific-1-wave-evm" { evmCount++ evmRoute = r } @@ -240,15 +241,15 @@ func TestGenerateHTTPRoute_EVMMerged(t *testing.T) { func TestGenerateHTTPRoute_EVMWebSocketRule(t *testing.T) { g := NewWithT(t) - group := newTestGroup("pacific-1-rpc", "sei") + group := newTestGroup("pacific-1-wave", "pacific-1") group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ Service: &seiv1alpha1.ExternalServiceConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") var evmRoute effectiveRoute for _, r := range routes { - if r.Name == "pacific-1-rpc-evm" { + if r.Name == "pacific-1-wave-evm" { evmRoute = r break } @@ -284,7 +285,7 @@ func TestGenerateHTTPRoute_BasicFields(t *testing.T) { Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") g.Expect(routes).NotTo(BeEmpty()) route := generateHTTPRoute(group, routes[0], "sei-gateway", "istio-system") @@ -307,7 +308,7 @@ func TestGenerateHTTPRoute_ManagedByAnnotation(t *testing.T) { Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") route := generateHTTPRoute(group, routes[0], "sei-gateway", "istio-system") g.Expect(route.GetAnnotations()).To(HaveKeyWithValue("sei.io/managed-by", "seinodedeployment")) } @@ -320,7 +321,7 @@ func TestGenerateHTTPRoute_BackendRef(t *testing.T) { Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") var rpcRoute effectiveRoute for _, r := range routes { if r.Name == "archive-rpc-rpc" { @@ -344,17 +345,17 @@ func TestGenerateHTTPRoute_BackendRef(t *testing.T) { func TestGenerateHTTPRoute_GRPCRoutePort(t *testing.T) { g := NewWithT(t) - group := newTestGroup("pacific-1-rpc", "sei") + group := newTestGroup("pacific-1-wave", "pacific-1") group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ Service: &seiv1alpha1.ExternalServiceConfig{}, Gateway: &seiv1alpha1.GatewayRouteConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") var grpcRoute effectiveRoute for _, r := range routes { - if r.Name == "pacific-1-rpc-grpc" { + if r.Name == "pacific-1-wave-grpc" { grpcRoute = r break } @@ -364,12 +365,12 @@ func TestGenerateHTTPRoute_GRPCRoutePort(t *testing.T) { httpRoute := generateHTTPRoute(group, grpcRoute, "sei-gateway", "istio-system") spec := httpRoute.Object["spec"].(map[string]any) hostnames := spec["hostnames"].([]any) - g.Expect(hostnames).To(ConsistOf("pacific-1-rpc.grpc.prod.platform.sei.io")) + g.Expect(hostnames).To(ConsistOf("pacific-1-wave-grpc.prod.platform.sei.io", "pacific-1-wave-grpc.pacific-1.platform.sei.io")) rules := spec["rules"].([]any) backend := rules[0].(map[string]any)["backendRefs"].([]any)[0].(map[string]any) g.Expect(backend["port"]).To(Equal(int64(9090))) - g.Expect(backend["name"]).To(Equal("pacific-1-rpc-external")) + g.Expect(backend["name"]).To(Equal("pacific-1-wave-external")) } // --- isProtocolActiveForMode --- @@ -387,14 +388,15 @@ func TestIsProtocolActiveForMode_EVMMapping(t *testing.T) { func TestResolveEffectiveRoutes_EmptyDomain_MalformedHostnames(t *testing.T) { g := NewWithT(t) - group := newTestGroup("pacific-1-rpc", "sei") + group := newTestGroup("pacific-1-wave", "pacific-1") group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ Service: &seiv1alpha1.ExternalServiceConfig{}, } - routes := resolveEffectiveRoutes(group, "") + routes := resolveEffectiveRoutes(group, "", "") g.Expect(routes).To(HaveLen(4), "routes are still generated even with empty domain") - g.Expect(routes[0].Hostnames[0]).To(Equal("pacific-1-rpc.evm."), "empty domain produces trailing dot") + g.Expect(routes[0].Hostnames[0]).To(Equal("pacific-1-wave-evm."), "empty domain produces trailing dot") + g.Expect(routes[0].Hostnames[1]).To(Equal("pacific-1-wave-evm.pacific-1."), "empty public domain produces trailing dot") } func TestReconcileRoute_NoRoutesForValidatorMode(t *testing.T) { @@ -405,20 +407,20 @@ func TestReconcileRoute_NoRoutesForValidatorMode(t *testing.T) { Service: &seiv1alpha1.ExternalServiceConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") g.Expect(routes).To(BeEmpty(), "validator mode should produce zero routes") } func TestGenerateHTTPRoute_NonEVMRoute_SingleRule(t *testing.T) { g := NewWithT(t) - group := newTestGroup("pacific-1-rpc", "sei") + group := newTestGroup("pacific-1-wave", "pacific-1") group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ Service: &seiv1alpha1.ExternalServiceConfig{}, } - routes := resolveEffectiveRoutes(group, "prod.platform.sei.io") + routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io") for _, r := range routes { - if r.Name == "pacific-1-rpc-rpc" { + if r.Name == "pacific-1-wave-rpc" { httpRoute := generateHTTPRoute(group, r, "sei-gateway", "gateway") spec := httpRoute.Object["spec"].(map[string]any) rules := spec["rules"].([]any) diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 41bc9a5..6669824 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -37,9 +37,10 @@ type Config struct { GenesisBucket string GenesisRegion string - GatewayName string - GatewayNamespace string - GatewayDomain string + GatewayName string + GatewayNamespace string + GatewayDomain string + GatewayPublicDomain string } // Validate returns an error if required fields are missing. @@ -67,6 +68,7 @@ func (c Config) Validate() error { "SEI_GATEWAY_NAME": c.GatewayName, "SEI_GATEWAY_NAMESPACE": c.GatewayNamespace, "SEI_GATEWAY_DOMAIN": c.GatewayDomain, + "SEI_GATEWAY_PUBLIC_DOMAIN": c.GatewayPublicDomain, } for name, val := range required { if val == "" { diff --git a/internal/platform/platformtest/config.go b/internal/platform/platformtest/config.go index 9d90b27..2a78542 100644 --- a/internal/platform/platformtest/config.go +++ b/internal/platform/platformtest/config.go @@ -29,5 +29,6 @@ func Config() platform.Config { GatewayName: "sei-gateway", GatewayNamespace: "istio-system", GatewayDomain: "test.platform.sei.io", + GatewayPublicDomain: "platform.sei.io", } } From 155ccd20e87ce8caa01ac9a0a7e6c397306473ec Mon Sep 17 00:00:00 2001 From: bdchatham Date: Fri, 10 Apr 2026 13:14:27 -0700 Subject: [PATCH 2/2] fix: make SEI_GATEWAY_PUBLIC_DOMAIN optional Dev environments don't have a platform.sei.io zone or certs, so the public domain must be optional. When unset, HTTPRoutes emit only the internal hostname. When set, the public hostname is appended. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../controller/nodedeployment/networking.go | 17 +++++++++++------ .../nodedeployment/networking_test.go | 17 ++++++++++++++++- internal/platform/platform.go | 1 - internal/platform/platformtest/config.go | 1 - 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/internal/controller/nodedeployment/networking.go b/internal/controller/nodedeployment/networking.go index 689230e..f81b883 100644 --- a/internal/controller/nodedeployment/networking.go +++ b/internal/controller/nodedeployment/networking.go @@ -248,13 +248,18 @@ func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain, public continue } subdomain := fmt.Sprintf("%s-%s", group.Name, proto.Prefix) - er := effectiveRoute{ - Name: subdomain, - Hostnames: []string{ - fmt.Sprintf("%s.%s", subdomain, domain), + hostnames := []string{ + fmt.Sprintf("%s.%s", subdomain, domain), + } + if publicDomain != "" { + hostnames = append(hostnames, fmt.Sprintf("%s.%s.%s", subdomain, group.Namespace, publicDomain), - }, - Port: proto.Port, + ) + } + er := effectiveRoute{ + Name: subdomain, + Hostnames: hostnames, + Port: proto.Port, } if proto.Prefix == "evm" && activePorts["evm-ws"] { er.WSPort = seiconfig.PortEVMWS diff --git a/internal/controller/nodedeployment/networking_test.go b/internal/controller/nodedeployment/networking_test.go index e4c0c93..fd31bdb 100644 --- a/internal/controller/nodedeployment/networking_test.go +++ b/internal/controller/nodedeployment/networking_test.go @@ -395,8 +395,23 @@ func TestResolveEffectiveRoutes_EmptyDomain_MalformedHostnames(t *testing.T) { routes := resolveEffectiveRoutes(group, "", "") g.Expect(routes).To(HaveLen(4), "routes are still generated even with empty domain") + g.Expect(routes[0].Hostnames).To(HaveLen(1), "no public hostname when public domain is empty") g.Expect(routes[0].Hostnames[0]).To(Equal("pacific-1-wave-evm."), "empty domain produces trailing dot") - g.Expect(routes[0].Hostnames[1]).To(Equal("pacific-1-wave-evm.pacific-1."), "empty public domain produces trailing dot") +} + +func TestResolveEffectiveRoutes_NoPublicDomain_SingleHostname(t *testing.T) { + g := NewWithT(t) + group := newTestGroup("pacific-1-wave", "pacific-1") + group.Spec.Networking = &seiv1alpha1.NetworkingConfig{ + Service: &seiv1alpha1.ExternalServiceConfig{}, + } + + routes := resolveEffectiveRoutes(group, "dev.platform.sei.io", "") + g.Expect(routes).To(HaveLen(4)) + for _, r := range routes { + g.Expect(r.Hostnames).To(HaveLen(1), "only internal hostname when public domain is empty") + } + g.Expect(routes[0].Hostnames[0]).To(Equal("pacific-1-wave-evm.dev.platform.sei.io")) } func TestReconcileRoute_NoRoutesForValidatorMode(t *testing.T) { diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 6669824..26d9178 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -68,7 +68,6 @@ func (c Config) Validate() error { "SEI_GATEWAY_NAME": c.GatewayName, "SEI_GATEWAY_NAMESPACE": c.GatewayNamespace, "SEI_GATEWAY_DOMAIN": c.GatewayDomain, - "SEI_GATEWAY_PUBLIC_DOMAIN": c.GatewayPublicDomain, } for name, val := range required { if val == "" { diff --git a/internal/platform/platformtest/config.go b/internal/platform/platformtest/config.go index 2a78542..9d90b27 100644 --- a/internal/platform/platformtest/config.go +++ b/internal/platform/platformtest/config.go @@ -29,6 +29,5 @@ func Config() platform.Config { GatewayName: "sei-gateway", GatewayNamespace: "istio-system", GatewayDomain: "test.platform.sei.io", - GatewayPublicDomain: "platform.sei.io", } }