Skip to content
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
24 changes: 8 additions & 16 deletions api/v1alpha1/networking_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ const (
// Routing uses the Kubernetes Gateway API exclusively; the platform must
// install the Gateway API CRDs (v1+) and a Gateway implementation such
// as Istio before HTTPRoute resources will take effect.
// +kubebuilder:validation:XValidation:rule="!has(self.gateway) || has(self.service)",message="gateway requires service to be configured"
type NetworkingConfig struct {
// Service creates a non-headless Service shared across all replicas.
// Each SeiNode still gets its own headless Service for pod DNS.
// +optional
Service *ExternalServiceConfig `json:"service,omitempty"`

// Gateway creates a gateway.networking.k8s.io/v1 HTTPRoute
// targeting a shared Gateway (e.g. Istio ingress gateway).
// Gateway provides optional annotations for generated HTTPRoute resources.
// HTTPRoutes are generated automatically when the node mode has public
// ports and the platform Gateway env vars are configured. This field is
// only needed to add custom annotations to the HTTPRoute metadata.
// +optional
Gateway *GatewayRouteConfig `json:"gateway,omitempty"`

Expand Down Expand Up @@ -55,20 +56,11 @@ type ExternalServiceConfig struct {
// targeting the platform Gateway (configured via SEI_GATEWAY_NAME and
// SEI_GATEWAY_NAMESPACE environment variables on the controller).
//
// +kubebuilder:validation:XValidation:rule=”(has(self.hostnames) && size(self.hostnames) > 0) || (has(self.baseDomain) && size(self.baseDomain) > 0)”,message=”at least one of hostnames or baseDomain must be set”
// Hostnames are derived automatically from the deployment name, protocol,
// and the platform domain (SEI_GATEWAY_DOMAIN). Which protocols get
// HTTPRoutes is determined by the node mode via seiconfig.NodePortsForMode.
type GatewayRouteConfig struct {
// Hostnames routes all listed hostnames to the RPC port (26657).
// For multi-protocol routing, use BaseDomain instead.
// +optional
Hostnames []string `json:"hostnames,omitempty"`

// BaseDomain generates HTTPRoutes for all standard Sei protocols
// using conventional subdomain prefixes (rpc.*, rest.*, grpc.*,
// evm-rpc.*, evm-ws.*), each routing to the correct backend port.
// +optional
BaseDomain string `json:"baseDomain,omitempty"`

// Annotations are merged onto the HTTPRoute metadata.
// Annotations are merged onto HTTPRoute metadata.
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
}
Expand Down
1 change: 0 additions & 1 deletion api/v1alpha1/seinodedeployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,6 @@ const (
// +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=`.status.replicas`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Revision",type=string,JSONPath=`.status.deployment.entrantRevision`,priority=1
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.networking.gateway.hostnames[0]`,priority=1
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

// SeiNodeDeployment is the Schema for the seinodedeployments API.
Expand Down
5 changes: 0 additions & 5 deletions api/v1alpha1/zz_generated.deepcopy.go

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

2 changes: 2 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ func main() {
GenesisRegion: os.Getenv("SEI_GENESIS_REGION"),
GatewayName: os.Getenv("SEI_GATEWAY_NAME"),
GatewayNamespace: os.Getenv("SEI_GATEWAY_NAMESPACE"),
GatewayDomain: os.Getenv("SEI_GATEWAY_DOMAIN"),
}

if err := platformCfg.Validate(); err != nil {
Expand Down Expand Up @@ -185,6 +186,7 @@ func main() {
ControllerSA: controllerSA,
GatewayName: platformCfg.GatewayName,
GatewayNamespace: platformCfg.GatewayNamespace,
GatewayDomain: platformCfg.GatewayDomain,
PlanExecutor: &planner.Executor[*seiv1alpha1.SeiNodeDeployment]{
Client: kc,
ConfigFor: func(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) task.ExecutionConfig {
Expand Down
32 changes: 5 additions & 27 deletions config/crd/sei.io_seinodedeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ spec:
name: Revision
priority: 1
type: string
- jsonPath: .spec.networking.gateway.hostnames[0]
name: Host
priority: 1
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
Expand Down Expand Up @@ -192,32 +188,17 @@ spec:
properties:
gateway:
description: |-
Gateway creates a gateway.networking.k8s.io/v1 HTTPRoute
targeting a shared Gateway (e.g. Istio ingress gateway).
Gateway provides optional annotations for generated HTTPRoute resources.
HTTPRoutes are generated automatically when the node mode has public
ports and the platform Gateway env vars are configured. This field is
only needed to add custom annotations to the HTTPRoute metadata.
properties:
annotations:
additionalProperties:
type: string
description: Annotations are merged onto the HTTPRoute metadata.
description: Annotations are merged onto HTTPRoute metadata.
type: object
baseDomain:
description: |-
BaseDomain generates HTTPRoutes for all standard Sei protocols
using conventional subdomain prefixes (rpc.*, rest.*, grpc.*,
evm-rpc.*, evm-ws.*), each routing to the correct backend port.
type: string
hostnames:
description: |-
Hostnames routes all listed hostnames to the RPC port (26657).
For multi-protocol routing, use BaseDomain instead.
items:
type: string
type: array
type: object
x-kubernetes-validations:
- message: at least one of hostnames or baseDomain must be set
rule: '(has(self.hostnames) && size(self.hostnames) > 0) ||
(has(self.baseDomain) && size(self.baseDomain) > 0)'
isolation:
description: Isolation configures network-level access control
for node pods.
Expand Down Expand Up @@ -280,9 +261,6 @@ spec:
type: string
type: object
type: object
x-kubernetes-validations:
- message: gateway requires service to be configured
rule: '!has(self.gateway) || has(self.service)'
replicas:
default: 1
description: Replicas is the number of SeiNode instances to create.
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/nodedeployment/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ type SeiNodeDeploymentReconciler struct {
// controller can always reach the seictl sidecar.
ControllerSA string

// GatewayName and GatewayNamespace identify the platform Gateway for
// HTTPRoute parentRefs. Read from SEI_GATEWAY_NAME / SEI_GATEWAY_NAMESPACE.
// 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

// PlanExecutor drives group-level task plans (e.g. genesis assembly).
PlanExecutor planner.PlanExecutor[*seiv1alpha1.SeiNodeDeployment]
Expand Down
51 changes: 29 additions & 22 deletions internal/controller/nodedeployment/networking.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ var seiProtocolRoutes = []struct {
Prefix string
Port int32
}{
{"evm", seiconfig.PortEVMHTTP},
{"rpc", seiconfig.PortRPC},
{"rest", seiconfig.PortREST},
{"grpc", seiconfig.PortGRPC},
{"evm-rpc", seiconfig.PortEVMHTTP},
{"evm-ws", seiconfig.PortEVMWS},
}

type effectiveRoute struct {
Expand Down Expand Up @@ -204,11 +203,12 @@ func portsForMode(mode seiconfig.NodeMode) []corev1.ServicePort {
}

func (r *SeiNodeDeploymentReconciler) reconcileRoute(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) error {
if group.Spec.Networking.Gateway == nil {
routes := resolveEffectiveRoutes(group, r.GatewayDomain)
if len(routes) == 0 {
removeCondition(group, seiv1alpha1.ConditionRouteReady)
return r.deleteHTTPRoutesByLabel(ctx, group)
}
return r.reconcileHTTPRoute(ctx, group)
return r.reconcileHTTPRoutes(ctx, group, routes)
}

func (r *SeiNodeDeploymentReconciler) deleteHTTPRoutesByLabel(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) error {
Expand All @@ -229,29 +229,36 @@ func (r *SeiNodeDeploymentReconciler) deleteHTTPRoutesByLabel(ctx context.Contex
return nil
}

func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment) []effectiveRoute {
cfg := group.Spec.Networking.Gateway
if cfg.BaseDomain != "" {
routes := make([]effectiveRoute, len(seiProtocolRoutes))
for i, p := range seiProtocolRoutes {
routes[i] = effectiveRoute{
Name: fmt.Sprintf("%s-%s", group.Name, p.Prefix),
Hostnames: []string{fmt.Sprintf("%s.%s", p.Prefix, cfg.BaseDomain)},
Port: p.Port,
}
func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain string) []effectiveRoute {
modePorts := seiconfig.NodePortsForMode(groupMode(group))

activePorts := make(map[string]bool, len(modePorts))
for _, p := range modePorts {
activePorts[p.Name] = true
}

var routes []effectiveRoute
for _, proto := range seiProtocolRoutes {
if !isProtocolActiveForMode(proto.Prefix, activePorts) {
continue
}
return routes
routes = append(routes, 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,
})
}
return []effectiveRoute{{
Name: group.Name,
Hostnames: cfg.Hostnames,
Port: seiconfig.PortRPC,
}}
return routes
}

func (r *SeiNodeDeploymentReconciler) reconcileHTTPRoute(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) error {
routes := resolveEffectiveRoutes(group)
func isProtocolActiveForMode(prefix string, activePorts map[string]bool) bool {
if prefix == "evm" {
return activePorts["evm-rpc"]
}
return activePorts[prefix]
}

func (r *SeiNodeDeploymentReconciler) reconcileHTTPRoutes(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment, routes []effectiveRoute) error {
desiredNames := make(map[string]bool, len(routes))
for _, er := range routes {
desiredNames[er.Name] = true
Expand Down
Loading
Loading